searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

libvfio-user API 使用介绍

2026-07-08 13:42:48
1
0

1. 核心上下文管理

1.1 vfu_create_ctx

创建 libvfio-user 上下文。这是使用库的第一步。

vfu_ctx_t *
vfu_create_ctx(vfu_trans_t trans, const char *path,
               int flags, void *pvt, vfu_dev_type_t dev_type);

参数:

参数 类型 描述
trans vfu_trans_t 传输类型。VFU_TRANS_SOCK(UNIX socket)或 VFU_TRANS_PIPE(管道,仅内部测试用)
path const char * socket 文件路径
flags int 上下文标志。0(阻塞模式)或 LIBVFIO_USER_FLAG_ATTACH_NB(非阻塞模式)
pvt void * 用户私有数据指针,可通过 vfu_get_private() 获取
dev_type vfu_dev_type_t 设备类型,当前仅支持 VFU_DEV_TYPE_PCI

返回值:

  • 成功:返回 vfu_ctx_t * 指针
  • 失败:返回 NULL,设置 errno

说明:

  • 默认会初始化 1 个 ERR IRQ 和 1 个 REQ IRQ,可通过 vfu_setup_device_nr_irqs() 覆盖
  • 内部会分配 VFU_PCI_DEV_NUM_REGIONS (9) 个 region 信息结构体
  • 内部会调用 transport 的 init 方法

示例:

vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
                                     0, &my_private_data, VFU_DEV_TYPE_PCI);
if (vfu_ctx == NULL) {
    perror("vfu_create_ctx");
    exit(1);
}

1.2 vfu_realize_ctx

完成设备初始化,使设备进入可 attach 状态。必须在 vfu_attach_ctx() 之前调用。

int
vfu_realize_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 如果未手动设置 config region,则创建默认的 256 字节 PCI config space
  • 为各 BAR 区域设置类型标志(MEM/IO/64位/预取)
  • 如果配置了 PCI capabilities,设置 status 寄存器的 capability list 位
  • 初始化 IRQ 结构体(如果未手动设置)
  • 设置 realized = true

示例:

int ret = vfu_realize_ctx(vfu_ctx);
if (ret < 0) {
    perror("vfu_realize_ctx");
    exit(1);
}

1.3 vfu_attach_ctx

尝试 attach 到 transport。必须在 vfu_run_ctx() 之前调用。

int
vfu_attach_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • EAGAIN / EWOULDBLOCK:transport 尚未就绪(非阻塞模式),需重试
    • 其他错误:attach 失败

说明:

  • 如果上下文是用 LIBVFIO_USER_FLAG_ATTACH_NB 创建的,此调用是非阻塞的
  • 需要重试直到成功

示例:

int ret;
do {
    ret = vfu_attach_ctx(vfu_ctx);
} while (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
if (ret < 0) {
    perror("vfu_attach_ctx");
    exit(1);
}

1.4 vfu_run_ctx

轮询 vfu_ctx 并处理来自客户端的请求。这是设备模拟的主循环。

int
vfu_run_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 阻塞模式:持续处理请求,仅在错误或客户端断开连接时返回
  • 非阻塞模式:处理一个请求(如果有),立即返回
  • 成功时返回已处理的请求数(0 或更多)
  • 失败时返回 -1,设置 errno:
    • ENOTCONN:客户端关闭连接,需重新调用 vfu_attach_ctx()
    • EBUSY:设备正在静默中
    • EAGAIN:非阻塞模式下无请求

示例:

do {
    ret = vfu_run_ctx(vfu_ctx);
} while (ret >= 0 || (ret == -1 && errno == EAGAIN));
if (ret == -1 && errno != ENOTCONN && errno != ESHUTDOWN) {
    perror("vfu_run_ctx");
}

1.5 vfu_get_poll_fd

获取可用于 epoll() 或类似机制等待的文件描述符。

int
vfu_get_poll_fd(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回文件描述符
  • 失败:返回 -1

说明:

  • 文件描述符可能在 vfu_attach_ctx() 成功后或 vfu_run_ctx() 返回 ENOTCONN 后改变,需要重新调用此函数获取

1.6 vfu_destroy_ctx

销毁 libvfio-user 上下文。调用时设备必须已处于静默状态。

void
vfu_destroy_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文,可以为 NULL

说明:

  • 会依次释放 config space、transport、DMA controller、sparse mmap areas、regions、migration 结构、IRQs 等资源

1.7 vfu_get_private

获取在 vfu_create_ctx() 中设置的私有数据指针。

void *
vfu_get_private(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 返回在 vfu_create_ctx() 中设置的 pvt 指针

2. 日志设置

2.1 vfu_setup_log

设置日志函数和日志级别。

int
vfu_setup_log(vfu_ctx_t *vfu_ctx, vfu_log_fn_t *log, int level);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
log vfu_log_fn_t * 日志回调函数
level int 日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG)

日志回调签名:

typedef void (vfu_log_fn_t)(vfu_ctx_t *vfu_ctx, int level, const char *msg);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno (EINVAL:级别无效)

示例:

static void my_log(vfu_ctx_t *vfu_ctx, int level, char const *msg) {
    fprintf(stderr, "device: %s\n", msg);
}

vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);

2.2 vfu_log

使用上下文配置的日志函数记录日志(供内部使用,也可在你自己的回调中使用)。

void
vfu_log(vfu_ctx_t *vfu_ctx, int level, const char *fmt, ...)
    __attribute__((format(printf, 3, 4)));

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
level int 日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG)
fmt const char * printf 风格的格式字符串
... - 格式字符串对应的可变参数

3. PCI 设备初始化与配置

3.1 vfu_pci_init

初始化 PCI 设备上下文。每个 vfu_ctx 只能调用一次。

int
vfu_pci_init(vfu_ctx_t *vfu_ctx, vfu_pci_type_t pci_type,
             int hdr_type, int revision);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
pci_type vfu_pci_type_t PCI 类型,可选值见下表
hdr_type int PCI 头类型,当前仅支持 PCI_HEADER_TYPE_NORMAL
revision int PCI/PCI-X/PCIe 版本号(当前未使用)

vfu_pci_type_t 枚举:

值 描述 Config Space 大小
VFU_PCI_TYPE_CONVENTIONAL 传统 PCI 256 字节
VFU_PCI_TYPE_PCI_X_1 PCI-X 模式 1 256 字节
VFU_PCI_TYPE_PCI_X_2 PCI-X 模式 2 4096 字节
VFU_PCI_TYPE_EXPRESS PCI Express 4096 字节

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 自动分配 config space 缓冲区
  • 自动设置 config region(VFU_PCI_DEV_CFG_REGION_IDX)的大小和读写标志

示例:

vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);

3.2 vfu_pci_set_id

设置 PCI 设备标识符(Vendor ID、Device ID、Subsystem Vendor ID、Subsystem ID)。

void
vfu_pci_set_id(vfu_ctx_t *vfu_ctx, uint16_t vid, uint16_t did,
               uint16_t ssvid, uint16_t ssid);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
vid uint16_t Vendor ID
did uint16_t Device ID
ssvid uint16_t Subsystem Vendor ID
ssid uint16_t Subsystem ID

示例:

vfu_pci_set_id(vfu_ctx, 0x1af4, 0x1001, 0x1af4, 0x0010);

3.3 vfu_pci_set_class

设置 PCI Class Code(基本类、子类、编程接口)。

void
vfu_pci_set_class(vfu_ctx_t *vfu_ctx, uint8_t base, uint8_t sub, uint8_t pi);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
base uint8_t 基本类码(Base Class Code)
sub uint8_t 子类码(Sub-Class Code)
pi uint8_t 编程接口(Programming Interface)

说明:

  • 如果不调用此函数,所有字段初始化为 0

示例(NVMe 控制器:Mass Storage / Non-Volatile Memory):

vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02);

3.4 vfu_pci_get_config_space

获取 PCI 配置空间指针。

vfu_pci_config_space_t *
vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 返回 vfu_pci_config_space_t *,即 PCI 配置空间的指针

说明:

  • PCI config space 由 64 字节的 vfu_pci_hdr_t 标准头部,后接 capability 区域和设备特定配置区域组成
  • 标准 config space 为 256 字节(PCI_CFG_SPACE_SIZE)
  • 扩展 config space 为 4096 字节(PCI_CFG_SPACE_EXP_SIZE)
  • 可以直接修改返回的 config space 来设置自定义 PCI 寄存器值

示例:

vfu_pci_config_space_t *cfg = vfu_pci_get_config_space(vfu_ctx);
cfg->hdr.intr.ipin = 1;  // INTA#

3.5 vfu_pci_add_capability

向 PCI 配置空间添加 capability。

ssize_t
vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
pos size_t capability 在 config space 中的偏移。0 表示自动分配位置
flags int capability 标志(见下表)
data void * capability 数据(包括头部),会被复制到 config space 中

flags 标志:

标志 描述
VFU_CAP_FLAG_EXTENDED 这是一个扩展 capability(需要 VFU_PCI_TYPE_PCI_X_2 或 VFU_PCI_TYPE_EXPRESS)
VFU_CAP_FLAG_CALLBACK 所有对该 capability 的访问委托给 VFU_PCI_DEV_CFG_REGION_IDX 的回调
VFU_CAP_FLAG_READONLY 禁止客户端写入 capability(头部除外)
组合使用 可以按位或组合上述标志

返回值:

  • 成功:返回 capability 在 config space 中的偏移量
  • 失败:返回 -1,设置 errno

说明:

  • 库内部已完全处理的 capability 类型:PCI_CAP_ID_PM、PCI_CAP_ID_EXP、PCI_CAP_ID_MSI、PCI_CAP_ID_MSIX
  • 上述 capability 仍需显式在此函数中注册
  • 支持的扩展 capability:PCI_EXT_CAP_ID_DSN(设备序列号)、PCI_EXT_CAP_ID_VNDR(厂商特定)
  • data 必须从 struct cap_hdr(普通)或 struct pcie_ext_cap_hdr(扩展)开始,ID 字段必须已设置
  • 对于 PCI_CAP_ID_VNDR 或 PCI_EXT_CAP_ID_VNDR,内嵌的 size 字段也必须设置
  • 最多支持 VFU_MAX_CAPS(128) 个普通 capability 和 128 个扩展 capability

示例(添加 MSI-X capability):

struct msixcap msix = { 0 };
msix.hdr.id = PCI_CAP_ID_MSIX;
msix.mxc.ts = 16;       // Table size: 16 vectors
msix.mxc.mxe = 0;
msix.mxc.fm = 0;
// mtab 和 mpba 的 BIR 和 offset 根据实际 BAR 配置设置
msix.mtab.to = 0;       // MSI-X Table offset in BAR
msix.mtab.tbir = 4;     // BAR index for table
msix.mpba.pbao = 0x1000; // PBA offset in BAR
msix.mpba.pbir = 4;     // BAR index for PBA

ssize_t offset = vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);
if (offset < 0) {
    perror("add MSI-X capability");
}

示例(添加 PCI Express capability):

struct pxcap px = { 0 };
px.hdr.id = PCI_CAP_ID_EXP;
px.pxcaps.ver = 2;       // PCIe Gen2
px.pxcaps.dpt = 0;       // Endpoint
px.pxdcap.mps = 0;       // 128 bytes max payload
px.pxdcap.flrc = 1;      // Support FLR
// ... 设置其他必要的字段

vfu_pci_add_capability(vfu_ctx, 0, 0, &px);

示例(添加 PM capability):

struct pmcap pm = { 0 };
pm.hdr.id = PCI_CAP_ID_PM;
pm.pc.vs = 2;            // PCI PM v1.1
pm.pc.dsi = 1;           // No device specific init
pm.pc.auxc = 0;          // 0 mA auxiliary current

vfu_pci_add_capability(vfu_ctx, 0, 0, &pm);

3.6 vfu_pci_find_capability

查找指定 capability 在 config space 中的偏移。

size_t
vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
extended bool true 查找扩展 capability,false 查找普通 capability
cap_id int capability ID(PCI_CAP_ID_* 或 PCI_EXT_CAP_ID_*)

返回值:

  • 成功:返回 capability 的偏移量
  • 未找到:返回 0,设置 errno(ENOENT)

3.7 vfu_pci_find_next_capability

从指定位置开始查找下一个匹配的 capability(用于遍历多个相同 ID 的 capability)。

size_t
vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
                              size_t pos, int cap_id);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
extended bool 是否查找扩展 capability
pos size_t 起始偏移(必须是现有 capability 的有效偏移)
cap_id int capability ID

返回值:

  • 成功:返回下一个匹配 capability 的偏移量
  • 未找到:返回 0,设置 errno

4. 设备区域(Region)设置

4.1 vfu_setup_region

设置设备区域。区域是设备内存的可访问范围,客户端可通过 VFIO_USER_REGION_READ/WRITE 或通过 mmap 直接映射访问。

int
vfu_setup_region(vfu_ctx_t *vfu_ctx, int region_idx, size_t size,
                 vfu_region_access_cb_t *region_access, int flags,
                 struct iovec *mmap_areas, uint32_t nr_mmap_areas,
                 int fd, uint64_t offset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
region_idx int 区域索引(见下表)
size size_t 区域大小(字节)
region_access vfu_region_access_cb_t * 区域访问回调函数(可为 NULL)
flags int 区域标志(见下表)
mmap_areas struct iovec * 可 mmap 的子区域数组;可为 NULL
nr_mmap_areas uint32_t mmap_areas 数组的元素数量
fd int 支持该区域的文件描述符;不可 mmap 则为 -1
offset uint64_t 区域在 fd 中的偏移量

区域索引(region_idx):

常量 值 描述
VFU_PCI_DEV_BAR0_REGION_IDX 0 BAR0
VFU_PCI_DEV_BAR1_REGION_IDX 1 BAR1
VFU_PCI_DEV_BAR2_REGION_IDX 2 BAR2
VFU_PCI_DEV_BAR3_REGION_IDX 3 BAR3
VFU_PCI_DEV_BAR4_REGION_IDX 4 BAR4
VFU_PCI_DEV_BAR5_REGION_IDX 5 BAR5
VFU_PCI_DEV_ROM_REGION_IDX 6 ROM
VFU_PCI_DEV_CFG_REGION_IDX 7 配置空间
VFU_PCI_DEV_VGA_REGION_IDX 8 VGA

区域标志(flags):

标志 描述
VFU_REGION_FLAG_READ 区域可读
VFU_REGION_FLAG_WRITE 区域可写
VFU_REGION_FLAG_RW 区域可读写(READ | WRITE)
VFU_REGION_FLAG_MEM 该区域为内存区域(未设置则为 IO 区域),影响 BAR 类型
VFU_REGION_FLAG_ALWAYS_CB 始终使用回调(对 config region 特别有用)
VFU_REGION_FLAG_64_BITS 64 位 BAR
VFU_REGION_FLAG_PREFETCH 预取 BAR(通常需同时设置 64_BITS)

区域访问回调签名:

typedef ssize_t (vfu_region_access_cb_t)(vfu_ctx_t *vfu_ctx, char *buf,
                                         size_t count, loff_t offset,
                                         bool is_write);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • config region (VFU_PCI_DEV_CFG_REGION_IDX) 有特殊处理:标准 PCI 头部和已知 capability 的访问由库处理,其他区域通过回调或 memcpy 处理
  • 如果提供了 fd 但 mmap_areas 为 NULL,则整个区域可 mmap
  • 客户端可以 mmap 文件描述符的任意部分,即使 mmap_areas 不允许
  • 64 位 BAR 需要在相邻的高位 BAR 区域(如 BAR0+1、BAR2+3、BAR4+5)不设置任何回调

示例(简单的非映射 BAR):

static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
                           loff_t offset, bool is_write) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    if (is_write) {
        memcpy(data->bar0 + offset, buf, count);
    } else {
        memcpy(buf, data->bar0 + offset, count);
    }
    return count;
}

vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
                 bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);

示例(可 mmap 的 BAR,带 sparse mmap):

int tmpfd = mkstemp(template);
ftruncate(tmpfd, 0x3000);
void *bar1_mem = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED, tmpfd, 0);

struct iovec bar1_mmap_areas[] = {
    { .iov_base = (void*)0,      .iov_len = 0x1000 },  // 第 0 页可 mmap
    { .iov_base = (void*)0x2000,  .iov_len = 0x1000 },  // 第 2 页可 mmap
    // 第 1 页不可 mmap
};
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR1_REGION_IDX, 0x3000,
                 bar1_access, VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM,
                 bar1_mmap_areas, 2, tmpfd, 0);

示例(64 位 BAR):

// BAR2+BAR3 组合为 64 位 BAR
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR2_REGION_IDX, 0x1000000,
                 bar2_access,
                 VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM | VFU_REGION_FLAG_64_BITS,
                 NULL, 0, -1, 0);

5. 中断(IRQ)设置

5.1 vfu_setup_device_nr_irqs

设置设备的各类型 IRQ 数量。

int
vfu_setup_device_nr_irqs(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
                         uint32_t count);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
type enum vfu_dev_irq_type IRQ 类型
count uint32_t IRQ 数量

IRQ 类型:

枚举值 描述
VFU_DEV_INTX_IRQ INTx 中断(通常设置为 1)
VFU_DEV_MSI_IRQ MSI 中断(1, 2, 4, 8, 16, 32)
VFU_DEV_MSIX_IRQ MSI-X 中断(最多 2048)
VFU_DEV_ERR_IRQ 错误中断(默认 1)
VFU_DEV_REQ_IRQ 请求中断(默认 1)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 默认已初始化 1 个 ERR IRQ 和 1 个 REQ IRQ
  • 如果 INTx 数量不为 0,则自动设置 config space 的 intr.ipin = 1(INTA#)

示例:

vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSI_IRQ, 8);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);

5.2 vfu_setup_irq_state_callback

设置 IRQ 状态变化回调(当客户端 mask/unmask IRQ 时触发)。

int
vfu_setup_irq_state_callback(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
                             vfu_dev_irq_state_cb_t *cb);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
type enum vfu_dev_irq_type IRQ 类型
cb vfu_dev_irq_state_cb_t * IRQ 状态变化回调

回调签名:

typedef void (vfu_dev_irq_state_cb_t)(vfu_ctx_t *vfu_ctx, uint32_t start,
                                       uint32_t count, bool mask);
// mask == true: IRQ 被 mask
// mask == false: IRQ 被 unmask

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

5.3 vfu_irq_trigger

触发一个中断。

int
vfu_irq_trigger(vfu_ctx_t *vfu_ctx, uint32_t subindex);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
subindex uint32_t IRQ 子索引(向量编号)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • libvfio-user 自动选择合适的 IRQ 类型(INTx/MSI/MSI-X),调用者只需指定子索引
  • 如果 IRQ 对应的 eventfd 未设置(-1),返回 ENOENT 错误

示例:

// 触发第 0 号中断向量
vfu_irq_trigger(vfu_ctx, 0);

6. DMA 设置与操作

6.1 vfu_setup_device_dma

设置设备 DMA 注册/注销回调。

int
vfu_setup_device_dma(vfu_ctx_t *vfu_ctx, vfu_dma_register_cb_t *dma_register,
                     vfu_dma_unregister_cb_t *dma_unregister);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
dma_register vfu_dma_register_cb_t * DMA 区域注册回调(可选)
dma_unregister vfu_dma_unregister_cb_t * DMA 区域注销回调(可选)

回调签名:

typedef void (vfu_dma_register_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);
typedef void (vfu_dma_unregister_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 如果不调用此函数,则无法通过 vfu_addr_to_sgl() 访问客户端的 DMA 内存
  • 要使用 vfu_sgl_get() 直接映射访问,至少需要提供 dma_unregister 回调
  • 内部创建 DMA controller,最多支持 MAX_DMA_REGIONS(64) 个区域,每个最大 MAX_DMA_SIZE(x86_64 上为 8TB)

示例:

static void dma_register(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 记录 DMA 区域信息
    data->dma_iova = info->iova;
}

static void dma_unregister(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 释放所有对该区域的引用
    memset(&data->dma_iova, 0, sizeof(data->dma_iova));
}

vfu_setup_device_dma(vfu_ctx, dma_register, dma_unregister);

6.2 vfu_addr_to_sgl

将客户端物理地址范围转换为 scatter/gather 列表。

int
vfu_addr_to_sgl(vfu_ctx_t *vfu_ctx, vfu_dma_addr_t dma_addr, size_t len,
                dma_sg_t *sgl, size_t max_nr_sgs, int prot);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
dma_addr vfu_dma_addr_t 客户端物理地址
len size_t 要映射的内存大小
sgl dma_sg_t * 接收 scatter/gather 条目的数组
max_nr_sgs size_t 数组的最大元素数
prot int 访问保护(PROT_READ / PROT_WRITE,定义在 ``)

返回值:

  • 成功:返回创建的 SG 条目数
  • 失败:
    • -1:地址范围无效(errno=ENOENT)或保护违规(errno=EACCES)
    • (-x - 1):max_nr_sgs 太小,x 为实际需要的条目数(errno=0)

说明:

  • 必须先调用 vfu_setup_device_dma()
  • dma_sg_t 结构体大小需通过 dma_sg_size() 获取,不能使用 sizeof(dma_sg_t)

示例:

dma_sg_t *sg = alloca(dma_sg_size());
int ret = vfu_addr_to_sgl(vfu_ctx, (vfu_dma_addr_t)0x1000, 4096, sg, 1, PROT_READ);
if (ret < 0) {
    perror("vfu_addr_to_sgl");
}

6.3 vfu_sgl_get

将 scatter/gather 列表映射到本地进程的虚拟地址空间(iovec 数组)。

int
vfu_sgl_get(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt,
            int flags);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 数组
iov struct iovec * 输出 iovec 数组
cnt size_t SG 条目数
flags int 必须为 0

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 仅在提供 dma_unregister 回调时才支持(即直接 mmap 方式)
  • 使用完毕后必须调用 vfu_sgl_put() 释放
  • 直接映射访问方式下,服务端负责跟踪脏页

6.4 vfu_sgl_put

释放由 vfu_sgl_get() 获取的 iovec 映射。

void
vfu_sgl_put(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * SG 数组
iov struct iovec * 对应的 iovec 数组
cnt size_t 条目数

说明:

  • 如果 SG 条目标记为可写,会自动标记脏页
  • 如果已调用 vfu_sgl_mark_dirty(),脏页只会被标记一次(原子操作)

6.5 vfu_sgl_mark_dirty

标记 SG 条目为脏(已写入)。仅在需要标记脏页但不释放映射时使用。

void
vfu_sgl_mark_dirty(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, size_t cnt);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * SG 数组
cnt size_t 条目数

6.6 vfu_sgl_read

从客户端 DMA 区域读取数据(基于消息的方式)。

int
vfu_sgl_read(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 条目
cnt size_t 条目数(当前仅支持 1)
data void * 读入缓冲区

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 通过 VFIO_USER_DMA_READ 消息实现,在区域不可直接 mmap 或未设置 DMA 回调时使用
  • 不需要 dma_unregister 回调

示例:

char buf[4096];
vfu_sgl_read(vfu_ctx, sg, 1, buf);

6.7 vfu_sgl_write

向客户端 DMA 区域写入数据(基于消息的方式)。

int
vfu_sgl_write(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 条目
cnt size_t 条目数(当前仅支持 1)
data void * 要写入的数据

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 通过 VFIO_USER_DMA_WRITE 消息实现
  • 热迁移期间,此调用不会标记脏页(客户端负责跟踪)

6.8 vfu_sg_is_mappable

检查 SG 条目是否可映射。

bool
vfu_sg_is_mappable(vfu_ctx_t *vfu_ctx, dma_sg_t *sg);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * SG 条目

返回值:

  • true:SG 条目可直接映射访问
  • false:需要消息方式访问

6.9 dma_sg_size

获取 dma_sg_t 的大小。

size_t
dma_sg_size(void);

说明:

  • 不能直接使用 sizeof(dma_sg_t),因为 dma_sg_t 是不透明类型
  • 用于配合 alloca() 或 malloc() 分配 SG 数组

7. 设备复位与静默(Quiesce)回调

7.1 vfu_setup_device_reset_cb

设置设备复位回调。

int
vfu_setup_device_reset_cb(vfu_ctx_t *vfu_ctx, vfu_reset_cb_t *reset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
reset vfu_reset_cb_t * 复位回调函数

回调签名:

typedef int (vfu_reset_cb_t)(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type);

复位类型(vfu_reset_type_t):

值 描述
VFU_RESET_DEVICE 客户端请求设备复位(如虚拟机重启),vfu_ctx 保持有效
VFU_RESET_LOST_CONN 客户端连接断开,attach 上下文被清理,需重新 vfu_attach_ctx()
VFU_RESET_PCI_FLR PCI Function Level Reset

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 复位回调应确保所有正在使用的 IRQ 或客户端内存访问在返回前完成或取消

示例:

static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 重置所有设备状态
    memset(data->regs, 0, sizeof(data->regs));
    return 0;
}

vfu_setup_device_reset_cb(vfu_ctx, device_reset);

7.2 vfu_setup_device_quiesce_cb

设置设备静默回调。当库需要请求设备暂停操作(如处理 DMA map/unmap 或迁移状态转换)时调用。

void
vfu_setup_device_quiesce_cb(vfu_ctx_t *vfu_ctx,
                            vfu_device_quiesce_cb_t *quiesce_cb);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
quiesce_cb vfu_device_quiesce_cb_t * 静默回调函数

回调签名:

typedef int (vfu_device_quiesce_cb_t)(vfu_ctx_t *vfu_ctx);

返回值:

  • 0:设备已立即静默
  • -1 且 errno=EBUSY:设备无法立即静默,需异步静默后调用 vfu_device_quiesced()

说明:

  • 静默状态下设备不能调用 vfu_addr_to_sgl() 或 vfu_sgl_*(),除非在设备回调中调用
  • 静默期间的合法回调:vfu_dma_register_cb_t、vfu_dma_unregister_cb_t、vfu_reset_cb_t、迁移转换回调

7.3 vfu_device_quiesced

由设备调用以完成待处理的静默操作。

int
vfu_device_quiesced(vfu_ctx_t *vfu_ctx, int quiesce_errno);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
quiesce_errno int 0 表示成功,否则为错误码(会导致操作失败并复位设备)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 调用返回后,设备不再是静默状态

8. 热迁移(Migration)设置

8.1 vfu_setup_device_migration_callbacks

设置设备热迁移回调。

int
vfu_setup_device_migration_callbacks(vfu_ctx_t *vfu_ctx,
    const vfu_migration_callbacks_t *callbacks);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
callbacks vfu_migration_callbacks_t * 迁移回调结构体

回调结构体定义:

#define VFU_MIGR_CALLBACKS_VERS 2

typedef struct {
    int version;  // 必须设为 VFU_MIGR_CALLBACKS_VERS

    // 迁移状态转换回调
    int (*transition)(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state);

    // 读取迁移数据回调
    ssize_t (*read_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);

    // 写入迁移数据回调
    ssize_t (*write_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);
} vfu_migration_callbacks_t;

迁移状态(vfu_migr_state_t):

状态 描述
VFU_MIGR_STATE_STOP 设备已停止
VFU_MIGR_STATE_RUNNING 设备正在运行
VFU_MIGR_STATE_STOP_AND_COPY 停止并拷贝状态
VFU_MIGR_STATE_PRE_COPY 预拷贝阶段
VFU_MIGR_STATE_RESUME 恢复状态

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 三个回调都是必需的(transition、read_data、write_data)
  • transition 回调返回 -1 表示错误,需设置 errno
  • read_data 回调返回读取的字节数,返回 0 表示无更多数据
  • write_data 回调不支持部分写入,返回非 count 值视为错误

示例:

static int migr_transition(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state) {
    switch (state) {
    case VFU_MIGR_STATE_STOP_AND_COPY:
        // 停止设备操作,准备迁移
        break;
    case VFU_MIGR_STATE_RUNNING:
        // 恢复设备运行
        break;
    // ...
    }
    return 0;
}

static ssize_t migr_read_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    uint64_t to_read = MIN(count, sizeof(data->device_state) - data->bytes_xferred);
    memcpy(buf, (char*)&data->device_state + data->bytes_xferred, to_read);
    data->bytes_xferred += to_read;
    return to_read;
}

static ssize_t migr_write_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    memcpy((char*)&data->device_state + data->bytes_xferred, buf, count);
    data->bytes_xferred += count;
    return count;
}

const vfu_migration_callbacks_t migr_cbs = {
    .version = VFU_MIGR_CALLBACKS_VERS,
    .transition = migr_transition,
    .read_data = migr_read_data,
    .write_data = migr_write_data,
};

vfu_setup_device_migration_callbacks(vfu_ctx, &migr_cbs);

9. ioeventfd 设置

9.1 vfu_create_ioeventfd

在指定区域创建一个 ioeventfd。

int
vfu_create_ioeventfd(vfu_ctx_t *vfu_ctx, uint32_t region_idx, int fd,
                     size_t gpa_offset, uint32_t size, uint32_t flags,
                     uint64_t datamatch, int shadow_fd, size_t shadow_offset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
region_idx uint32_t 区域索引
fd int ioeventfd 的文件描述符
gpa_offset size_t 区域内的偏移量
size uint32_t ioeventfd 的大小(字节)
flags uint32_t ioeventfd 标志
datamatch uint64_t 数据匹配值
shadow_fd int shadow ioeventfd 的文件描述符,-1 表示普通 ioeventfd
shadow_offset size_t shadow 内存中的写入偏移

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • shadow ioeventfd 需要编译时定义 SHADOW_IOEVENTFD,且内核支持
  • gpa_offset + size 必须在区域大小范围内

10. 完整示例流程

以下是一个典型的 PCI 模拟设备创建流程:

#include "libvfio-user.h"

struct my_device {
    // 设备私有数据
    char bar0_data[0x1000];
};

// --- 步骤 1: 定义日志回调 ---
static void my_log(vfu_ctx_t *vfu_ctx, int level, const char *msg) {
    fprintf(stderr, "mydev: %s\n", msg);
}

// --- 步骤 2: 定义区域访问回调 ---
static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
                           loff_t offset, bool is_write) {
    struct my_device *dev = vfu_get_private(vfu_ctx);
    if (is_write)
        memcpy(dev->bar0_data + offset, buf, count);
    else
        memcpy(buf, dev->bar0_data + offset, count);
    return count;
}

// --- 步骤 3: 定义复位回调 ---
static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
    struct my_device *dev = vfu_get_private(vfu_ctx);
    memset(dev->bar0_data, 0, sizeof(dev->bar0_data));
    return 0;
}

int main(int argc, char **argv) {
    int ret;
    struct my_device dev = { 0 };
    vfu_ctx_t *vfu_ctx;

    // 1. 创建上下文
    vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
                             0, &dev, VFU_DEV_TYPE_PCI);
    if (vfu_ctx == NULL) { err(1, "vfu_create_ctx"); }

    // 2. 设置日志
    vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);

    // 3. 初始化 PCI 设备
    vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);

    // 4. 设置 PCI ID
    vfu_pci_set_id(vfu_ctx, 0x1234, 0x5678, 0x1234, 0x5678);

    // 5. 设置 Class Code
    vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02);  // NVMe

    // 6. 设置 BAR 区域
    vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
                     bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);

    // 7. (可选) 添加 PCI capabilities
    struct msixcap msix = { 0 };
    msix.hdr.id = PCI_CAP_ID_MSIX;
    msix.mxc.ts = 15;  // 16 vectors (0-based)
    vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);

    struct pxcap px = { 0 };
    px.hdr.id = PCI_CAP_ID_EXP;
    px.pxcaps.ver = 2;
    px.pxcaps.dpt = 0;  // Endpoint
    px.pxdcap.flrc = 1;
    vfu_pci_add_capability(vfu_ctx, 0, 0, &px);

    // 8. 设置 IRQ 数量
    vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
    vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);

    // 9. 设置复位回调
    vfu_setup_device_reset_cb(vfu_ctx, device_reset);

    // 10. 完成设备初始化
    ret = vfu_realize_ctx(vfu_ctx);
    if (ret < 0) { err(1, "vfu_realize_ctx"); }

    // 11. Attach 到 transport
    ret = vfu_attach_ctx(vfu_ctx);
    if (ret < 0) { err(1, "vfu_attach_ctx"); }

    // 12. 运行主循环
    do {
        ret = vfu_run_ctx(vfu_ctx);
    } while (ret >= 0);

    // 13. 清理
    vfu_destroy_ctx(vfu_ctx);
    return 0;
}

11. 数据结构参考

11.1 PCI 配置空间头部 (vfu_pci_hdr_t)

typedef union {
    uint8_t raw[PCI_STD_HEADER_SIZEOF];  // 64 字节
    struct {
        vfu_pci_hdr_id_t    id;      // Vendor ID + Device ID (4B)
        vfu_pci_hdr_cmd_t   cmd;     // Command (2B)
        vfu_pci_hdr_sts_t   sts;     // Status (2B)
        uint8_t             rid;     // Revision ID (1B)
        vfu_pci_hdr_cc_t    cc;      // Class Code (3B)
        uint8_t             cls;     // Cache Line Size (1B)
        uint8_t             mlt;     // Master Latency Timer (1B)
        vfu_pci_hdr_htype_t htype;   // Header Type (1B)
        vfu_pci_hdr_bist_t  bist;    // BIST (1B)
        vfu_bar_t           bars[6]; // BAR0-BAR5 (4B each)
        uint32_t            ccptr;   // Cardbus CIS Pointer
        vfu_pci_hdr_ss_t    ss;      // Subsystem Vendor/Device ID
        uint32_t            erom;    // Expansion ROM Base Address
        uint8_t             cap;     // Capability Pointer
        uint8_t             res1[7]; // Reserved
        vfu_pci_hdr_intr_t  intr;    // Interrupt Line + Pin
        uint8_t             mgnt;    // Min Grant
        uint8_t             mlat;    // Max Latency
    };
} vfu_pci_hdr_t;  // 64 字节

11.2 PCI 配置空间 (vfu_pci_config_space_t)

typedef struct {
    union {
        uint8_t raw[PCI_CFG_SPACE_SIZE];  // 256 字节标准空间
        vfu_pci_hdr_t hdr;                 // 前 64 字节为头部
    };
    uint8_t extended[];  // 扩展空间 (到 PCI_CFG_SPACE_EXP_SIZE = 4096)
} vfu_pci_config_space_t;

11.3 DMA 信息 (vfu_dma_info_t)

typedef struct vfu_dma_info {
    struct iovec iova;       // 客户端的 IOVA 范围
    void *vaddr;             // 映射到本进程的虚拟地址(可为 NULL)
    struct iovec mapping;    // 实际 mmap 的范围(可能因大页对齐而不同)
    size_t page_size;        // 映射的页面大小
    uint32_t prot;           // 映射的保护属性(PROT_READ/PROT_WRITE)
} vfu_dma_info_t;

11.4 BAR 寄存器 (vfu_bar_t)

typedef union {
    uint32_t raw;
    union {
        struct {
            unsigned int region_type:1;   // 0=Memory, 1=I/O
            unsigned int locatable:2;     // 内存类型的定位位
            unsigned int prefetchable:1;  // 预取
            unsigned int base_address:28; // 基地址
        } mem;
        struct {
            unsigned int region_type:1;   // 始终为 1
            unsigned int reserved:1;
            unsigned int base_address:30; // I/O 基地址
        } io;
    };
} vfu_bar_t;

11.5 dma_sg_t 结构

struct dma_sg {
    vfu_dma_addr_t dma_addr;  // 所属 DMA 区域的起始地址
    int region;               // DMA 区域索引
    uint64_t length;          // 此 SG 条目的长度
    uint64_t offset;          // 在此 DMA 区域内的偏移
    bool writeable;           // 是否可写
};

API 调用顺序总结

创建 vfio-user PCI 模拟设备的推荐 API 调用顺序:

1.  vfu_create_ctx()              -- 创建上下文
2.  vfu_setup_log()               -- (可选) 设置日志
3.  vfu_pci_init()                -- 初始化 PCI 设备
4.  vfu_pci_set_id()              -- 设置 PCI ID
5.  vfu_pci_set_class()           -- (可选) 设置 Class Code
6.  vfu_setup_region()            -- 设置各 BAR 区域
7.  vfu_pci_add_capability()      -- (可选) 添加 PCI capabilities
8.  vfu_setup_device_nr_irqs()    -- 设置 IRQ 数量
9.  vfu_setup_irq_state_callback() -- (可选) 设置 IRQ 状态回调
10. vfu_setup_device_dma()        -- (可选) 设置 DMA
11. vfu_setup_device_reset_cb()   -- (可选) 设置复位回调
12. vfu_setup_device_quiesce_cb() -- (可选) 设置静默回调
13. vfu_setup_device_migration_   -- (可选) 设置热迁移回调
    callbacks()
14. vfu_realize_ctx()             -- 完成设备初始化 ★必须
15. vfu_attach_ctx()              -- Attach 到 transport ★必须
16. vfu_run_ctx()                 -- 运行主循环 ★必须
17. vfu_destroy_ctx()             -- 销毁上下文

运行时使用的 API:

  • vfu_get_private() -- 获取私有数据
  • vfu_irq_trigger() -- 触发中断
  • vfu_addr_to_sgl() + vfu_sgl_get() + vfu_sgl_put() -- DMA 直接映射访问
  • vfu_addr_to_sgl() + vfu_sgl_read() / vfu_sgl_write() -- DMA 消息访问
  • vfu_sgl_mark_dirty() -- 标记脏页
  • vfu_pci_get_config_space() -- 获取 PCI config space 指针
  • vfu_pci_find_capability() / vfu_pci_find_next_capability() -- 查找 capability
  • vfu_device_quiesced() -- 完成异步静默
  • vfu_get_poll_fd() -- 获取轮询文件描述符(用于 epoll)
  • vfu_create_ioeventfd() -- 创建 ioeventfd

0条评论
0 / 1000
j****n
4文章数
0粉丝数
j****n
4 文章 | 0 粉丝
j****n
4文章数
0粉丝数
j****n
4 文章 | 0 粉丝
原创

libvfio-user API 使用介绍

2026-07-08 13:42:48
1
0

1. 核心上下文管理

1.1 vfu_create_ctx

创建 libvfio-user 上下文。这是使用库的第一步。

vfu_ctx_t *
vfu_create_ctx(vfu_trans_t trans, const char *path,
               int flags, void *pvt, vfu_dev_type_t dev_type);

参数:

参数 类型 描述
trans vfu_trans_t 传输类型。VFU_TRANS_SOCK(UNIX socket)或 VFU_TRANS_PIPE(管道,仅内部测试用)
path const char * socket 文件路径
flags int 上下文标志。0(阻塞模式)或 LIBVFIO_USER_FLAG_ATTACH_NB(非阻塞模式)
pvt void * 用户私有数据指针,可通过 vfu_get_private() 获取
dev_type vfu_dev_type_t 设备类型,当前仅支持 VFU_DEV_TYPE_PCI

返回值:

  • 成功:返回 vfu_ctx_t * 指针
  • 失败:返回 NULL,设置 errno

说明:

  • 默认会初始化 1 个 ERR IRQ 和 1 个 REQ IRQ,可通过 vfu_setup_device_nr_irqs() 覆盖
  • 内部会分配 VFU_PCI_DEV_NUM_REGIONS (9) 个 region 信息结构体
  • 内部会调用 transport 的 init 方法

示例:

vfu_ctx_t *vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
                                     0, &my_private_data, VFU_DEV_TYPE_PCI);
if (vfu_ctx == NULL) {
    perror("vfu_create_ctx");
    exit(1);
}

1.2 vfu_realize_ctx

完成设备初始化,使设备进入可 attach 状态。必须在 vfu_attach_ctx() 之前调用。

int
vfu_realize_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 如果未手动设置 config region,则创建默认的 256 字节 PCI config space
  • 为各 BAR 区域设置类型标志(MEM/IO/64位/预取)
  • 如果配置了 PCI capabilities,设置 status 寄存器的 capability list 位
  • 初始化 IRQ 结构体(如果未手动设置)
  • 设置 realized = true

示例:

int ret = vfu_realize_ctx(vfu_ctx);
if (ret < 0) {
    perror("vfu_realize_ctx");
    exit(1);
}

1.3 vfu_attach_ctx

尝试 attach 到 transport。必须在 vfu_run_ctx() 之前调用。

int
vfu_attach_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno
    • EAGAIN / EWOULDBLOCK:transport 尚未就绪(非阻塞模式),需重试
    • 其他错误:attach 失败

说明:

  • 如果上下文是用 LIBVFIO_USER_FLAG_ATTACH_NB 创建的,此调用是非阻塞的
  • 需要重试直到成功

示例:

int ret;
do {
    ret = vfu_attach_ctx(vfu_ctx);
} while (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK));
if (ret < 0) {
    perror("vfu_attach_ctx");
    exit(1);
}

1.4 vfu_run_ctx

轮询 vfu_ctx 并处理来自客户端的请求。这是设备模拟的主循环。

int
vfu_run_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 阻塞模式:持续处理请求,仅在错误或客户端断开连接时返回
  • 非阻塞模式:处理一个请求(如果有),立即返回
  • 成功时返回已处理的请求数(0 或更多)
  • 失败时返回 -1,设置 errno:
    • ENOTCONN:客户端关闭连接,需重新调用 vfu_attach_ctx()
    • EBUSY:设备正在静默中
    • EAGAIN:非阻塞模式下无请求

示例:

do {
    ret = vfu_run_ctx(vfu_ctx);
} while (ret >= 0 || (ret == -1 && errno == EAGAIN));
if (ret == -1 && errno != ENOTCONN && errno != ESHUTDOWN) {
    perror("vfu_run_ctx");
}

1.5 vfu_get_poll_fd

获取可用于 epoll() 或类似机制等待的文件描述符。

int
vfu_get_poll_fd(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 成功:返回文件描述符
  • 失败:返回 -1

说明:

  • 文件描述符可能在 vfu_attach_ctx() 成功后或 vfu_run_ctx() 返回 ENOTCONN 后改变,需要重新调用此函数获取

1.6 vfu_destroy_ctx

销毁 libvfio-user 上下文。调用时设备必须已处于静默状态。

void
vfu_destroy_ctx(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文,可以为 NULL

说明:

  • 会依次释放 config space、transport、DMA controller、sparse mmap areas、regions、migration 结构、IRQs 等资源

1.7 vfu_get_private

获取在 vfu_create_ctx() 中设置的私有数据指针。

void *
vfu_get_private(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 返回在 vfu_create_ctx() 中设置的 pvt 指针

2. 日志设置

2.1 vfu_setup_log

设置日志函数和日志级别。

int
vfu_setup_log(vfu_ctx_t *vfu_ctx, vfu_log_fn_t *log, int level);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
log vfu_log_fn_t * 日志回调函数
level int 日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG)

日志回调签名:

typedef void (vfu_log_fn_t)(vfu_ctx_t *vfu_ctx, int level, const char *msg);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno (EINVAL:级别无效)

示例:

static void my_log(vfu_ctx_t *vfu_ctx, int level, char const *msg) {
    fprintf(stderr, "device: %s\n", msg);
}

vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);

2.2 vfu_log

使用上下文配置的日志函数记录日志(供内部使用,也可在你自己的回调中使用)。

void
vfu_log(vfu_ctx_t *vfu_ctx, int level, const char *fmt, ...)
    __attribute__((format(printf, 3, 4)));

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
level int 日志级别,使用 syslog 级别(LOG_EMERG ~ LOG_DEBUG)
fmt const char * printf 风格的格式字符串
... - 格式字符串对应的可变参数

3. PCI 设备初始化与配置

3.1 vfu_pci_init

初始化 PCI 设备上下文。每个 vfu_ctx 只能调用一次。

int
vfu_pci_init(vfu_ctx_t *vfu_ctx, vfu_pci_type_t pci_type,
             int hdr_type, int revision);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
pci_type vfu_pci_type_t PCI 类型,可选值见下表
hdr_type int PCI 头类型,当前仅支持 PCI_HEADER_TYPE_NORMAL
revision int PCI/PCI-X/PCIe 版本号(当前未使用)

vfu_pci_type_t 枚举:

值 描述 Config Space 大小
VFU_PCI_TYPE_CONVENTIONAL 传统 PCI 256 字节
VFU_PCI_TYPE_PCI_X_1 PCI-X 模式 1 256 字节
VFU_PCI_TYPE_PCI_X_2 PCI-X 模式 2 4096 字节
VFU_PCI_TYPE_EXPRESS PCI Express 4096 字节

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 自动分配 config space 缓冲区
  • 自动设置 config region(VFU_PCI_DEV_CFG_REGION_IDX)的大小和读写标志

示例:

vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);

3.2 vfu_pci_set_id

设置 PCI 设备标识符(Vendor ID、Device ID、Subsystem Vendor ID、Subsystem ID)。

void
vfu_pci_set_id(vfu_ctx_t *vfu_ctx, uint16_t vid, uint16_t did,
               uint16_t ssvid, uint16_t ssid);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
vid uint16_t Vendor ID
did uint16_t Device ID
ssvid uint16_t Subsystem Vendor ID
ssid uint16_t Subsystem ID

示例:

vfu_pci_set_id(vfu_ctx, 0x1af4, 0x1001, 0x1af4, 0x0010);

3.3 vfu_pci_set_class

设置 PCI Class Code(基本类、子类、编程接口)。

void
vfu_pci_set_class(vfu_ctx_t *vfu_ctx, uint8_t base, uint8_t sub, uint8_t pi);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
base uint8_t 基本类码(Base Class Code)
sub uint8_t 子类码(Sub-Class Code)
pi uint8_t 编程接口(Programming Interface)

说明:

  • 如果不调用此函数,所有字段初始化为 0

示例(NVMe 控制器:Mass Storage / Non-Volatile Memory):

vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02);

3.4 vfu_pci_get_config_space

获取 PCI 配置空间指针。

vfu_pci_config_space_t *
vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文

返回值:

  • 返回 vfu_pci_config_space_t *,即 PCI 配置空间的指针

说明:

  • PCI config space 由 64 字节的 vfu_pci_hdr_t 标准头部,后接 capability 区域和设备特定配置区域组成
  • 标准 config space 为 256 字节(PCI_CFG_SPACE_SIZE)
  • 扩展 config space 为 4096 字节(PCI_CFG_SPACE_EXP_SIZE)
  • 可以直接修改返回的 config space 来设置自定义 PCI 寄存器值

示例:

vfu_pci_config_space_t *cfg = vfu_pci_get_config_space(vfu_ctx);
cfg->hdr.intr.ipin = 1;  // INTA#

3.5 vfu_pci_add_capability

向 PCI 配置空间添加 capability。

ssize_t
vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
pos size_t capability 在 config space 中的偏移。0 表示自动分配位置
flags int capability 标志(见下表)
data void * capability 数据(包括头部),会被复制到 config space 中

flags 标志:

标志 描述
VFU_CAP_FLAG_EXTENDED 这是一个扩展 capability(需要 VFU_PCI_TYPE_PCI_X_2 或 VFU_PCI_TYPE_EXPRESS)
VFU_CAP_FLAG_CALLBACK 所有对该 capability 的访问委托给 VFU_PCI_DEV_CFG_REGION_IDX 的回调
VFU_CAP_FLAG_READONLY 禁止客户端写入 capability(头部除外)
组合使用 可以按位或组合上述标志

返回值:

  • 成功:返回 capability 在 config space 中的偏移量
  • 失败:返回 -1,设置 errno

说明:

  • 库内部已完全处理的 capability 类型:PCI_CAP_ID_PM、PCI_CAP_ID_EXP、PCI_CAP_ID_MSI、PCI_CAP_ID_MSIX
  • 上述 capability 仍需显式在此函数中注册
  • 支持的扩展 capability:PCI_EXT_CAP_ID_DSN(设备序列号)、PCI_EXT_CAP_ID_VNDR(厂商特定)
  • data 必须从 struct cap_hdr(普通)或 struct pcie_ext_cap_hdr(扩展)开始,ID 字段必须已设置
  • 对于 PCI_CAP_ID_VNDR 或 PCI_EXT_CAP_ID_VNDR,内嵌的 size 字段也必须设置
  • 最多支持 VFU_MAX_CAPS(128) 个普通 capability 和 128 个扩展 capability

示例(添加 MSI-X capability):

struct msixcap msix = { 0 };
msix.hdr.id = PCI_CAP_ID_MSIX;
msix.mxc.ts = 16;       // Table size: 16 vectors
msix.mxc.mxe = 0;
msix.mxc.fm = 0;
// mtab 和 mpba 的 BIR 和 offset 根据实际 BAR 配置设置
msix.mtab.to = 0;       // MSI-X Table offset in BAR
msix.mtab.tbir = 4;     // BAR index for table
msix.mpba.pbao = 0x1000; // PBA offset in BAR
msix.mpba.pbir = 4;     // BAR index for PBA

ssize_t offset = vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);
if (offset < 0) {
    perror("add MSI-X capability");
}

示例(添加 PCI Express capability):

struct pxcap px = { 0 };
px.hdr.id = PCI_CAP_ID_EXP;
px.pxcaps.ver = 2;       // PCIe Gen2
px.pxcaps.dpt = 0;       // Endpoint
px.pxdcap.mps = 0;       // 128 bytes max payload
px.pxdcap.flrc = 1;      // Support FLR
// ... 设置其他必要的字段

vfu_pci_add_capability(vfu_ctx, 0, 0, &px);

示例(添加 PM capability):

struct pmcap pm = { 0 };
pm.hdr.id = PCI_CAP_ID_PM;
pm.pc.vs = 2;            // PCI PM v1.1
pm.pc.dsi = 1;           // No device specific init
pm.pc.auxc = 0;          // 0 mA auxiliary current

vfu_pci_add_capability(vfu_ctx, 0, 0, &pm);

3.6 vfu_pci_find_capability

查找指定 capability 在 config space 中的偏移。

size_t
vfu_pci_find_capability(vfu_ctx_t *vfu_ctx, bool extended, int cap_id);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
extended bool true 查找扩展 capability,false 查找普通 capability
cap_id int capability ID(PCI_CAP_ID_* 或 PCI_EXT_CAP_ID_*)

返回值:

  • 成功:返回 capability 的偏移量
  • 未找到:返回 0,设置 errno(ENOENT)

3.7 vfu_pci_find_next_capability

从指定位置开始查找下一个匹配的 capability(用于遍历多个相同 ID 的 capability)。

size_t
vfu_pci_find_next_capability(vfu_ctx_t *vfu_ctx, bool extended,
                              size_t pos, int cap_id);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
extended bool 是否查找扩展 capability
pos size_t 起始偏移(必须是现有 capability 的有效偏移)
cap_id int capability ID

返回值:

  • 成功:返回下一个匹配 capability 的偏移量
  • 未找到:返回 0,设置 errno

4. 设备区域(Region)设置

4.1 vfu_setup_region

设置设备区域。区域是设备内存的可访问范围,客户端可通过 VFIO_USER_REGION_READ/WRITE 或通过 mmap 直接映射访问。

int
vfu_setup_region(vfu_ctx_t *vfu_ctx, int region_idx, size_t size,
                 vfu_region_access_cb_t *region_access, int flags,
                 struct iovec *mmap_areas, uint32_t nr_mmap_areas,
                 int fd, uint64_t offset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
region_idx int 区域索引(见下表)
size size_t 区域大小(字节)
region_access vfu_region_access_cb_t * 区域访问回调函数(可为 NULL)
flags int 区域标志(见下表)
mmap_areas struct iovec * 可 mmap 的子区域数组;可为 NULL
nr_mmap_areas uint32_t mmap_areas 数组的元素数量
fd int 支持该区域的文件描述符;不可 mmap 则为 -1
offset uint64_t 区域在 fd 中的偏移量

区域索引(region_idx):

常量 值 描述
VFU_PCI_DEV_BAR0_REGION_IDX 0 BAR0
VFU_PCI_DEV_BAR1_REGION_IDX 1 BAR1
VFU_PCI_DEV_BAR2_REGION_IDX 2 BAR2
VFU_PCI_DEV_BAR3_REGION_IDX 3 BAR3
VFU_PCI_DEV_BAR4_REGION_IDX 4 BAR4
VFU_PCI_DEV_BAR5_REGION_IDX 5 BAR5
VFU_PCI_DEV_ROM_REGION_IDX 6 ROM
VFU_PCI_DEV_CFG_REGION_IDX 7 配置空间
VFU_PCI_DEV_VGA_REGION_IDX 8 VGA

区域标志(flags):

标志 描述
VFU_REGION_FLAG_READ 区域可读
VFU_REGION_FLAG_WRITE 区域可写
VFU_REGION_FLAG_RW 区域可读写(READ | WRITE)
VFU_REGION_FLAG_MEM 该区域为内存区域(未设置则为 IO 区域),影响 BAR 类型
VFU_REGION_FLAG_ALWAYS_CB 始终使用回调(对 config region 特别有用)
VFU_REGION_FLAG_64_BITS 64 位 BAR
VFU_REGION_FLAG_PREFETCH 预取 BAR(通常需同时设置 64_BITS)

区域访问回调签名:

typedef ssize_t (vfu_region_access_cb_t)(vfu_ctx_t *vfu_ctx, char *buf,
                                         size_t count, loff_t offset,
                                         bool is_write);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • config region (VFU_PCI_DEV_CFG_REGION_IDX) 有特殊处理:标准 PCI 头部和已知 capability 的访问由库处理,其他区域通过回调或 memcpy 处理
  • 如果提供了 fd 但 mmap_areas 为 NULL,则整个区域可 mmap
  • 客户端可以 mmap 文件描述符的任意部分,即使 mmap_areas 不允许
  • 64 位 BAR 需要在相邻的高位 BAR 区域(如 BAR0+1、BAR2+3、BAR4+5)不设置任何回调

示例(简单的非映射 BAR):

static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
                           loff_t offset, bool is_write) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    if (is_write) {
        memcpy(data->bar0 + offset, buf, count);
    } else {
        memcpy(buf, data->bar0 + offset, count);
    }
    return count;
}

vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
                 bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);

示例(可 mmap 的 BAR,带 sparse mmap):

int tmpfd = mkstemp(template);
ftruncate(tmpfd, 0x3000);
void *bar1_mem = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_SHARED, tmpfd, 0);

struct iovec bar1_mmap_areas[] = {
    { .iov_base = (void*)0,      .iov_len = 0x1000 },  // 第 0 页可 mmap
    { .iov_base = (void*)0x2000,  .iov_len = 0x1000 },  // 第 2 页可 mmap
    // 第 1 页不可 mmap
};
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR1_REGION_IDX, 0x3000,
                 bar1_access, VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM,
                 bar1_mmap_areas, 2, tmpfd, 0);

示例(64 位 BAR):

// BAR2+BAR3 组合为 64 位 BAR
vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR2_REGION_IDX, 0x1000000,
                 bar2_access,
                 VFU_REGION_FLAG_RW | VFU_REGION_FLAG_MEM | VFU_REGION_FLAG_64_BITS,
                 NULL, 0, -1, 0);

5. 中断(IRQ)设置

5.1 vfu_setup_device_nr_irqs

设置设备的各类型 IRQ 数量。

int
vfu_setup_device_nr_irqs(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
                         uint32_t count);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
type enum vfu_dev_irq_type IRQ 类型
count uint32_t IRQ 数量

IRQ 类型:

枚举值 描述
VFU_DEV_INTX_IRQ INTx 中断(通常设置为 1)
VFU_DEV_MSI_IRQ MSI 中断(1, 2, 4, 8, 16, 32)
VFU_DEV_MSIX_IRQ MSI-X 中断(最多 2048)
VFU_DEV_ERR_IRQ 错误中断(默认 1)
VFU_DEV_REQ_IRQ 请求中断(默认 1)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 默认已初始化 1 个 ERR IRQ 和 1 个 REQ IRQ
  • 如果 INTx 数量不为 0,则自动设置 config space 的 intr.ipin = 1(INTA#)

示例:

vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSI_IRQ, 8);
vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);

5.2 vfu_setup_irq_state_callback

设置 IRQ 状态变化回调(当客户端 mask/unmask IRQ 时触发)。

int
vfu_setup_irq_state_callback(vfu_ctx_t *vfu_ctx, enum vfu_dev_irq_type type,
                             vfu_dev_irq_state_cb_t *cb);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
type enum vfu_dev_irq_type IRQ 类型
cb vfu_dev_irq_state_cb_t * IRQ 状态变化回调

回调签名:

typedef void (vfu_dev_irq_state_cb_t)(vfu_ctx_t *vfu_ctx, uint32_t start,
                                       uint32_t count, bool mask);
// mask == true: IRQ 被 mask
// mask == false: IRQ 被 unmask

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

5.3 vfu_irq_trigger

触发一个中断。

int
vfu_irq_trigger(vfu_ctx_t *vfu_ctx, uint32_t subindex);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
subindex uint32_t IRQ 子索引(向量编号)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • libvfio-user 自动选择合适的 IRQ 类型(INTx/MSI/MSI-X),调用者只需指定子索引
  • 如果 IRQ 对应的 eventfd 未设置(-1),返回 ENOENT 错误

示例:

// 触发第 0 号中断向量
vfu_irq_trigger(vfu_ctx, 0);

6. DMA 设置与操作

6.1 vfu_setup_device_dma

设置设备 DMA 注册/注销回调。

int
vfu_setup_device_dma(vfu_ctx_t *vfu_ctx, vfu_dma_register_cb_t *dma_register,
                     vfu_dma_unregister_cb_t *dma_unregister);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
dma_register vfu_dma_register_cb_t * DMA 区域注册回调(可选)
dma_unregister vfu_dma_unregister_cb_t * DMA 区域注销回调(可选)

回调签名:

typedef void (vfu_dma_register_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);
typedef void (vfu_dma_unregister_cb_t)(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info);

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 如果不调用此函数,则无法通过 vfu_addr_to_sgl() 访问客户端的 DMA 内存
  • 要使用 vfu_sgl_get() 直接映射访问,至少需要提供 dma_unregister 回调
  • 内部创建 DMA controller,最多支持 MAX_DMA_REGIONS(64) 个区域,每个最大 MAX_DMA_SIZE(x86_64 上为 8TB)

示例:

static void dma_register(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 记录 DMA 区域信息
    data->dma_iova = info->iova;
}

static void dma_unregister(vfu_ctx_t *vfu_ctx, vfu_dma_info_t *info) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 释放所有对该区域的引用
    memset(&data->dma_iova, 0, sizeof(data->dma_iova));
}

vfu_setup_device_dma(vfu_ctx, dma_register, dma_unregister);

6.2 vfu_addr_to_sgl

将客户端物理地址范围转换为 scatter/gather 列表。

int
vfu_addr_to_sgl(vfu_ctx_t *vfu_ctx, vfu_dma_addr_t dma_addr, size_t len,
                dma_sg_t *sgl, size_t max_nr_sgs, int prot);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
dma_addr vfu_dma_addr_t 客户端物理地址
len size_t 要映射的内存大小
sgl dma_sg_t * 接收 scatter/gather 条目的数组
max_nr_sgs size_t 数组的最大元素数
prot int 访问保护(PROT_READ / PROT_WRITE,定义在 ``)

返回值:

  • 成功:返回创建的 SG 条目数
  • 失败:
    • -1:地址范围无效(errno=ENOENT)或保护违规(errno=EACCES)
    • (-x - 1):max_nr_sgs 太小,x 为实际需要的条目数(errno=0)

说明:

  • 必须先调用 vfu_setup_device_dma()
  • dma_sg_t 结构体大小需通过 dma_sg_size() 获取,不能使用 sizeof(dma_sg_t)

示例:

dma_sg_t *sg = alloca(dma_sg_size());
int ret = vfu_addr_to_sgl(vfu_ctx, (vfu_dma_addr_t)0x1000, 4096, sg, 1, PROT_READ);
if (ret < 0) {
    perror("vfu_addr_to_sgl");
}

6.3 vfu_sgl_get

将 scatter/gather 列表映射到本地进程的虚拟地址空间(iovec 数组)。

int
vfu_sgl_get(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt,
            int flags);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 数组
iov struct iovec * 输出 iovec 数组
cnt size_t SG 条目数
flags int 必须为 0

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 仅在提供 dma_unregister 回调时才支持(即直接 mmap 方式)
  • 使用完毕后必须调用 vfu_sgl_put() 释放
  • 直接映射访问方式下,服务端负责跟踪脏页

6.4 vfu_sgl_put

释放由 vfu_sgl_get() 获取的 iovec 映射。

void
vfu_sgl_put(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, struct iovec *iov, size_t cnt);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * SG 数组
iov struct iovec * 对应的 iovec 数组
cnt size_t 条目数

说明:

  • 如果 SG 条目标记为可写,会自动标记脏页
  • 如果已调用 vfu_sgl_mark_dirty(),脏页只会被标记一次(原子操作)

6.5 vfu_sgl_mark_dirty

标记 SG 条目为脏(已写入)。仅在需要标记脏页但不释放映射时使用。

void
vfu_sgl_mark_dirty(vfu_ctx_t *vfu_ctx, dma_sg_t *sgl, size_t cnt);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sgl dma_sg_t * SG 数组
cnt size_t 条目数

6.6 vfu_sgl_read

从客户端 DMA 区域读取数据(基于消息的方式)。

int
vfu_sgl_read(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 条目
cnt size_t 条目数(当前仅支持 1)
data void * 读入缓冲区

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 通过 VFIO_USER_DMA_READ 消息实现,在区域不可直接 mmap 或未设置 DMA 回调时使用
  • 不需要 dma_unregister 回调

示例:

char buf[4096];
vfu_sgl_read(vfu_ctx, sg, 1, buf);

6.7 vfu_sgl_write

向客户端 DMA 区域写入数据(基于消息的方式)。

int
vfu_sgl_write(vfu_ctx_t *vfu_ctx, dma_sg_t *sg, size_t cnt, void *data);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * 从 vfu_addr_to_sgl() 获取的 SG 条目
cnt size_t 条目数(当前仅支持 1)
data void * 要写入的数据

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 通过 VFIO_USER_DMA_WRITE 消息实现
  • 热迁移期间,此调用不会标记脏页(客户端负责跟踪)

6.8 vfu_sg_is_mappable

检查 SG 条目是否可映射。

bool
vfu_sg_is_mappable(vfu_ctx_t *vfu_ctx, dma_sg_t *sg);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
sg dma_sg_t * SG 条目

返回值:

  • true:SG 条目可直接映射访问
  • false:需要消息方式访问

6.9 dma_sg_size

获取 dma_sg_t 的大小。

size_t
dma_sg_size(void);

说明:

  • 不能直接使用 sizeof(dma_sg_t),因为 dma_sg_t 是不透明类型
  • 用于配合 alloca() 或 malloc() 分配 SG 数组

7. 设备复位与静默(Quiesce)回调

7.1 vfu_setup_device_reset_cb

设置设备复位回调。

int
vfu_setup_device_reset_cb(vfu_ctx_t *vfu_ctx, vfu_reset_cb_t *reset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
reset vfu_reset_cb_t * 复位回调函数

回调签名:

typedef int (vfu_reset_cb_t)(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type);

复位类型(vfu_reset_type_t):

值 描述
VFU_RESET_DEVICE 客户端请求设备复位(如虚拟机重启),vfu_ctx 保持有效
VFU_RESET_LOST_CONN 客户端连接断开,attach 上下文被清理,需重新 vfu_attach_ctx()
VFU_RESET_PCI_FLR PCI Function Level Reset

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 复位回调应确保所有正在使用的 IRQ 或客户端内存访问在返回前完成或取消

示例:

static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    // 重置所有设备状态
    memset(data->regs, 0, sizeof(data->regs));
    return 0;
}

vfu_setup_device_reset_cb(vfu_ctx, device_reset);

7.2 vfu_setup_device_quiesce_cb

设置设备静默回调。当库需要请求设备暂停操作(如处理 DMA map/unmap 或迁移状态转换)时调用。

void
vfu_setup_device_quiesce_cb(vfu_ctx_t *vfu_ctx,
                            vfu_device_quiesce_cb_t *quiesce_cb);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
quiesce_cb vfu_device_quiesce_cb_t * 静默回调函数

回调签名:

typedef int (vfu_device_quiesce_cb_t)(vfu_ctx_t *vfu_ctx);

返回值:

  • 0:设备已立即静默
  • -1 且 errno=EBUSY:设备无法立即静默,需异步静默后调用 vfu_device_quiesced()

说明:

  • 静默状态下设备不能调用 vfu_addr_to_sgl() 或 vfu_sgl_*(),除非在设备回调中调用
  • 静默期间的合法回调:vfu_dma_register_cb_t、vfu_dma_unregister_cb_t、vfu_reset_cb_t、迁移转换回调

7.3 vfu_device_quiesced

由设备调用以完成待处理的静默操作。

int
vfu_device_quiesced(vfu_ctx_t *vfu_ctx, int quiesce_errno);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
quiesce_errno int 0 表示成功,否则为错误码(会导致操作失败并复位设备)

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 调用返回后,设备不再是静默状态

8. 热迁移(Migration)设置

8.1 vfu_setup_device_migration_callbacks

设置设备热迁移回调。

int
vfu_setup_device_migration_callbacks(vfu_ctx_t *vfu_ctx,
    const vfu_migration_callbacks_t *callbacks);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
callbacks vfu_migration_callbacks_t * 迁移回调结构体

回调结构体定义:

#define VFU_MIGR_CALLBACKS_VERS 2

typedef struct {
    int version;  // 必须设为 VFU_MIGR_CALLBACKS_VERS

    // 迁移状态转换回调
    int (*transition)(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state);

    // 读取迁移数据回调
    ssize_t (*read_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);

    // 写入迁移数据回调
    ssize_t (*write_data)(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count);
} vfu_migration_callbacks_t;

迁移状态(vfu_migr_state_t):

状态 描述
VFU_MIGR_STATE_STOP 设备已停止
VFU_MIGR_STATE_RUNNING 设备正在运行
VFU_MIGR_STATE_STOP_AND_COPY 停止并拷贝状态
VFU_MIGR_STATE_PRE_COPY 预拷贝阶段
VFU_MIGR_STATE_RESUME 恢复状态

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • 三个回调都是必需的(transition、read_data、write_data)
  • transition 回调返回 -1 表示错误,需设置 errno
  • read_data 回调返回读取的字节数,返回 0 表示无更多数据
  • write_data 回调不支持部分写入,返回非 count 值视为错误

示例:

static int migr_transition(vfu_ctx_t *vfu_ctx, vfu_migr_state_t state) {
    switch (state) {
    case VFU_MIGR_STATE_STOP_AND_COPY:
        // 停止设备操作,准备迁移
        break;
    case VFU_MIGR_STATE_RUNNING:
        // 恢复设备运行
        break;
    // ...
    }
    return 0;
}

static ssize_t migr_read_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    uint64_t to_read = MIN(count, sizeof(data->device_state) - data->bytes_xferred);
    memcpy(buf, (char*)&data->device_state + data->bytes_xferred, to_read);
    data->bytes_xferred += to_read;
    return to_read;
}

static ssize_t migr_write_data(vfu_ctx_t *vfu_ctx, void *buf, uint64_t count) {
    struct my_data *data = vfu_get_private(vfu_ctx);
    memcpy((char*)&data->device_state + data->bytes_xferred, buf, count);
    data->bytes_xferred += count;
    return count;
}

const vfu_migration_callbacks_t migr_cbs = {
    .version = VFU_MIGR_CALLBACKS_VERS,
    .transition = migr_transition,
    .read_data = migr_read_data,
    .write_data = migr_write_data,
};

vfu_setup_device_migration_callbacks(vfu_ctx, &migr_cbs);

9. ioeventfd 设置

9.1 vfu_create_ioeventfd

在指定区域创建一个 ioeventfd。

int
vfu_create_ioeventfd(vfu_ctx_t *vfu_ctx, uint32_t region_idx, int fd,
                     size_t gpa_offset, uint32_t size, uint32_t flags,
                     uint64_t datamatch, int shadow_fd, size_t shadow_offset);

参数:

参数 类型 描述
vfu_ctx vfu_ctx_t * libvfio-user 上下文
region_idx uint32_t 区域索引
fd int ioeventfd 的文件描述符
gpa_offset size_t 区域内的偏移量
size uint32_t ioeventfd 的大小(字节)
flags uint32_t ioeventfd 标志
datamatch uint64_t 数据匹配值
shadow_fd int shadow ioeventfd 的文件描述符,-1 表示普通 ioeventfd
shadow_offset size_t shadow 内存中的写入偏移

返回值:

  • 成功:返回 0
  • 失败:返回 -1,设置 errno

说明:

  • shadow ioeventfd 需要编译时定义 SHADOW_IOEVENTFD,且内核支持
  • gpa_offset + size 必须在区域大小范围内

10. 完整示例流程

以下是一个典型的 PCI 模拟设备创建流程:

#include "libvfio-user.h"

struct my_device {
    // 设备私有数据
    char bar0_data[0x1000];
};

// --- 步骤 1: 定义日志回调 ---
static void my_log(vfu_ctx_t *vfu_ctx, int level, const char *msg) {
    fprintf(stderr, "mydev: %s\n", msg);
}

// --- 步骤 2: 定义区域访问回调 ---
static ssize_t bar0_access(vfu_ctx_t *vfu_ctx, char *buf, size_t count,
                           loff_t offset, bool is_write) {
    struct my_device *dev = vfu_get_private(vfu_ctx);
    if (is_write)
        memcpy(dev->bar0_data + offset, buf, count);
    else
        memcpy(buf, dev->bar0_data + offset, count);
    return count;
}

// --- 步骤 3: 定义复位回调 ---
static int device_reset(vfu_ctx_t *vfu_ctx, vfu_reset_type_t type) {
    struct my_device *dev = vfu_get_private(vfu_ctx);
    memset(dev->bar0_data, 0, sizeof(dev->bar0_data));
    return 0;
}

int main(int argc, char **argv) {
    int ret;
    struct my_device dev = { 0 };
    vfu_ctx_t *vfu_ctx;

    // 1. 创建上下文
    vfu_ctx = vfu_create_ctx(VFU_TRANS_SOCK, "/tmp/vfio-user.sock",
                             0, &dev, VFU_DEV_TYPE_PCI);
    if (vfu_ctx == NULL) { err(1, "vfu_create_ctx"); }

    // 2. 设置日志
    vfu_setup_log(vfu_ctx, my_log, LOG_DEBUG);

    // 3. 初始化 PCI 设备
    vfu_pci_init(vfu_ctx, VFU_PCI_TYPE_EXPRESS, PCI_HEADER_TYPE_NORMAL, 0);

    // 4. 设置 PCI ID
    vfu_pci_set_id(vfu_ctx, 0x1234, 0x5678, 0x1234, 0x5678);

    // 5. 设置 Class Code
    vfu_pci_set_class(vfu_ctx, 0x01, 0x08, 0x02);  // NVMe

    // 6. 设置 BAR 区域
    vfu_setup_region(vfu_ctx, VFU_PCI_DEV_BAR0_REGION_IDX, 0x1000,
                     bar0_access, VFU_REGION_FLAG_RW, NULL, 0, -1, 0);

    // 7. (可选) 添加 PCI capabilities
    struct msixcap msix = { 0 };
    msix.hdr.id = PCI_CAP_ID_MSIX;
    msix.mxc.ts = 15;  // 16 vectors (0-based)
    vfu_pci_add_capability(vfu_ctx, 0, 0, &msix);

    struct pxcap px = { 0 };
    px.hdr.id = PCI_CAP_ID_EXP;
    px.pxcaps.ver = 2;
    px.pxcaps.dpt = 0;  // Endpoint
    px.pxdcap.flrc = 1;
    vfu_pci_add_capability(vfu_ctx, 0, 0, &px);

    // 8. 设置 IRQ 数量
    vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_INTX_IRQ, 1);
    vfu_setup_device_nr_irqs(vfu_ctx, VFU_DEV_MSIX_IRQ, 16);

    // 9. 设置复位回调
    vfu_setup_device_reset_cb(vfu_ctx, device_reset);

    // 10. 完成设备初始化
    ret = vfu_realize_ctx(vfu_ctx);
    if (ret < 0) { err(1, "vfu_realize_ctx"); }

    // 11. Attach 到 transport
    ret = vfu_attach_ctx(vfu_ctx);
    if (ret < 0) { err(1, "vfu_attach_ctx"); }

    // 12. 运行主循环
    do {
        ret = vfu_run_ctx(vfu_ctx);
    } while (ret >= 0);

    // 13. 清理
    vfu_destroy_ctx(vfu_ctx);
    return 0;
}

11. 数据结构参考

11.1 PCI 配置空间头部 (vfu_pci_hdr_t)

typedef union {
    uint8_t raw[PCI_STD_HEADER_SIZEOF];  // 64 字节
    struct {
        vfu_pci_hdr_id_t    id;      // Vendor ID + Device ID (4B)
        vfu_pci_hdr_cmd_t   cmd;     // Command (2B)
        vfu_pci_hdr_sts_t   sts;     // Status (2B)
        uint8_t             rid;     // Revision ID (1B)
        vfu_pci_hdr_cc_t    cc;      // Class Code (3B)
        uint8_t             cls;     // Cache Line Size (1B)
        uint8_t             mlt;     // Master Latency Timer (1B)
        vfu_pci_hdr_htype_t htype;   // Header Type (1B)
        vfu_pci_hdr_bist_t  bist;    // BIST (1B)
        vfu_bar_t           bars[6]; // BAR0-BAR5 (4B each)
        uint32_t            ccptr;   // Cardbus CIS Pointer
        vfu_pci_hdr_ss_t    ss;      // Subsystem Vendor/Device ID
        uint32_t            erom;    // Expansion ROM Base Address
        uint8_t             cap;     // Capability Pointer
        uint8_t             res1[7]; // Reserved
        vfu_pci_hdr_intr_t  intr;    // Interrupt Line + Pin
        uint8_t             mgnt;    // Min Grant
        uint8_t             mlat;    // Max Latency
    };
} vfu_pci_hdr_t;  // 64 字节

11.2 PCI 配置空间 (vfu_pci_config_space_t)

typedef struct {
    union {
        uint8_t raw[PCI_CFG_SPACE_SIZE];  // 256 字节标准空间
        vfu_pci_hdr_t hdr;                 // 前 64 字节为头部
    };
    uint8_t extended[];  // 扩展空间 (到 PCI_CFG_SPACE_EXP_SIZE = 4096)
} vfu_pci_config_space_t;

11.3 DMA 信息 (vfu_dma_info_t)

typedef struct vfu_dma_info {
    struct iovec iova;       // 客户端的 IOVA 范围
    void *vaddr;             // 映射到本进程的虚拟地址(可为 NULL)
    struct iovec mapping;    // 实际 mmap 的范围(可能因大页对齐而不同)
    size_t page_size;        // 映射的页面大小
    uint32_t prot;           // 映射的保护属性(PROT_READ/PROT_WRITE)
} vfu_dma_info_t;

11.4 BAR 寄存器 (vfu_bar_t)

typedef union {
    uint32_t raw;
    union {
        struct {
            unsigned int region_type:1;   // 0=Memory, 1=I/O
            unsigned int locatable:2;     // 内存类型的定位位
            unsigned int prefetchable:1;  // 预取
            unsigned int base_address:28; // 基地址
        } mem;
        struct {
            unsigned int region_type:1;   // 始终为 1
            unsigned int reserved:1;
            unsigned int base_address:30; // I/O 基地址
        } io;
    };
} vfu_bar_t;

11.5 dma_sg_t 结构

struct dma_sg {
    vfu_dma_addr_t dma_addr;  // 所属 DMA 区域的起始地址
    int region;               // DMA 区域索引
    uint64_t length;          // 此 SG 条目的长度
    uint64_t offset;          // 在此 DMA 区域内的偏移
    bool writeable;           // 是否可写
};

API 调用顺序总结

创建 vfio-user PCI 模拟设备的推荐 API 调用顺序:

1.  vfu_create_ctx()              -- 创建上下文
2.  vfu_setup_log()               -- (可选) 设置日志
3.  vfu_pci_init()                -- 初始化 PCI 设备
4.  vfu_pci_set_id()              -- 设置 PCI ID
5.  vfu_pci_set_class()           -- (可选) 设置 Class Code
6.  vfu_setup_region()            -- 设置各 BAR 区域
7.  vfu_pci_add_capability()      -- (可选) 添加 PCI capabilities
8.  vfu_setup_device_nr_irqs()    -- 设置 IRQ 数量
9.  vfu_setup_irq_state_callback() -- (可选) 设置 IRQ 状态回调
10. vfu_setup_device_dma()        -- (可选) 设置 DMA
11. vfu_setup_device_reset_cb()   -- (可选) 设置复位回调
12. vfu_setup_device_quiesce_cb() -- (可选) 设置静默回调
13. vfu_setup_device_migration_   -- (可选) 设置热迁移回调
    callbacks()
14. vfu_realize_ctx()             -- 完成设备初始化 ★必须
15. vfu_attach_ctx()              -- Attach 到 transport ★必须
16. vfu_run_ctx()                 -- 运行主循环 ★必须
17. vfu_destroy_ctx()             -- 销毁上下文

运行时使用的 API:

  • vfu_get_private() -- 获取私有数据
  • vfu_irq_trigger() -- 触发中断
  • vfu_addr_to_sgl() + vfu_sgl_get() + vfu_sgl_put() -- DMA 直接映射访问
  • vfu_addr_to_sgl() + vfu_sgl_read() / vfu_sgl_write() -- DMA 消息访问
  • vfu_sgl_mark_dirty() -- 标记脏页
  • vfu_pci_get_config_space() -- 获取 PCI config space 指针
  • vfu_pci_find_capability() / vfu_pci_find_next_capability() -- 查找 capability
  • vfu_device_quiesced() -- 完成异步静默
  • vfu_get_poll_fd() -- 获取轮询文件描述符(用于 epoll)
  • vfu_create_ioeventfd() -- 创建 ioeventfd

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0