vfio-user 协议规范
简介
vfio-user 允许在虚拟机监视器(VMM)之外的独立进程中仿真设备。 设备由两部分组成:VMM 内的通用 VFIO 设备类型,称为客户端; VMM 外的核心设备实现,称为服务器。
本规范部分基于 Linux VFIO ioctl 接口。
VFIO 是成熟稳定、广泛使用的框架与 API。
QEMU 现有客户端实现 qemu/hw/vfio/ 可大量复用,但本规范不要求使用该实现。
客户端和服务器均无需任何 VFIO 内核模块。vfio-user 复用部分 VFIO 源码定义。
主要思路是让虚拟设备在同一主机的独立进程中,通过 UNIX 域套接字工作。 选择 AF_UNIX 是因为它能方便传递文件描述符,从而实现:
与服务器共享客户端内存,供 VFIO_USER_DMA 使用。
与客户端共享服务器内存,实现快速 MMIO。
高效共享 eventfd,触发中断。
也可使用 AF_VSOCK,让服务器在同主机的另一客户机运行, 或使用 AF_INET 远程运行。理论上传输层不一定是套接字,但本文不讨论其他方式。 本版本着重 UNIX 域套接字,对另外两种提供基本支持,不考虑性能影响。
传递文件描述符有利于性能,但双方实现协议时并非必须支持, 始终存在带内消息传递的回退机制。
概述
VFIO 允许将物理设备安全直通给用户空间进程,设备专用内核驱动完全不再驱动设备。 该进程通常是 VMM,通过直通获取高性能。VFIO 提供 API 和所需内核功能, QEMU 用它让客户机直接访问物理设备,而不通过软件仿真。
vfio-user 复用 VFIO API 的核心概念,但以套接字消息实现。 它不改变内核 VFIO,使用时无需加载任何 VFIO 模块。 客户端也可同时为一个设备使用内核 VFIO、为另一个使用 vfio-user。
VFIO 设备模型
VFIO 设备向用户进程呈现标准接口。现有接口的许多操作使用 ioctl() 系统调用, 本文将其称为 ioctl() 实现。
以下章节描述通过套接字实现 vfio-user 的消息集合。 许多消息对应 ioctl() 使用的数据结构,名称也从 ioctl 命令派生。 例如 VFIO_DEVICE_GET_INFO 变为 VFIO_USER_DEVICE_GET_INFO, 目的是尽可能复用现有实现的代码。
建立连接
客户端连接服务器后,首先发送 VFIO_USER_VERSION, 提出本会话使用的版本和能力。服务器回复兼容版本及所支持能力; 无法支持所提版本时关闭连接。
设备信息
客户端用 VFIO_USER_DEVICE_GET_INFO 查询:
设备类型及是否支持复位,
VFIO_DEVICE_FLAGS_。设备区域数量。
设备支持的中断类型数量。
区域信息
客户端用 VFIO_USER_DEVICE_GET_REGION_INFO 查询区域信息:
读写权限、是否可内存映射,以及是否支持额外能力
VFIO_REGION_INFO_CAP_。区域索引、大小和偏移。
区域可映射时,服务器提供供客户端 mmap() 的文件描述符。 服务器负责轮询映射区,以发现客户端更新。
区域能力
某些区域的额外能力无法用基本结构充分描述。 这些能力在回复中以列表返回,类似 PCI 配置空间的能力列表。
稀疏区域
区域可全部或部分映射。仅部分可映射时,回复包含 VFIO_REGION_INFO_CAP_SPARSE_MMAP,描述客户端可映射的部分。
备注
例如虚拟 NVMe 控制器可通过稀疏区域捕获 BAR0 起始处寄存器的低频访问, 同时允许直接访问随后一页的门铃。门铃访问极频繁,每次 I/O 提交都需写入。
设备专用区域
设备可定义标准区域(例如 PCI 索引 0–8)之外的区域, 在这些区域的信息回复中包含 VFIO_REGION_INFO_CAP_TYPE。 这些区域计入 struct vfio_user_device_info.num_regions, 因此 PCI 设备的该值可等于或大于 VFIO_PCI_NUM_REGIONS。
通过文件描述符进行区域 I/O
未映射区域通过 VFIO_USER_REGION_READ/WRITE 访问。 可为部分区域的子区域配置 ioeventfd 或 ioregionfd 以优化。 客户端用 VFIO_USER_DEVICE_GET_REGION_IO_FDS 查询, 将返回描述符配置为 ioeventfd/ioregionfd 后,KVM 等可直接向服务器通知 I/O, 无需经客户端中转。
中断
客户端用 VFIO_USER_DEVICE_GET_IRQ_INFO 查询设备中断类型。 类型由设备所在总线定义,客户端应了解各类型能力。 服务器可通过事件文件描述符直接向客户机注入中断, 客户端通过 VFIO_USER_SET_IRQS 配置其通知方式。
设备读写
客户机对未映射设备区域加载或存储时,客户端以 VFIO_USER_REGION_READ 或 VFIO_USER_REGION_WRITE 转发。服务器对读取返回设备数据,对写入返回确认。 见 Read and Write Operations。
客户端内存访问
客户端通过 VFIO_USER_DMA_MAP 和 VFIO_USER_DMA_UNMAP, 告知服务器可以代表设备访问的有效 VFIO_USER_DMA 范围,通常是客户机内存。 服务器可通过套接字的 VFIO_USER_DMA_READ/WRITE 访问; 此时名称中的 VFIO_USER_DMA 实际并非直接内存访问。
若客户端提供可 mmap() 的文件描述符,服务器才可真正直接访问客户端内存。 客户端无法撤销 mmap() 权限,因此仅在信任服务器不会破坏客户机内存时, 才应导出描述符。
客户端与服务器交互
套接字
一个服务器可服务:
一个或多个客户端;和/或
属于一个或多个客户端的一个或多个虚拟设备。
本版本要求每个连接使用专用套接字。客户端向服务器的命令及回复使用主通信套接字。 反向命令及回复可选用协商时建立的另一套接字,AF_UNIX 服务器直接传递描述符。
每个命令通道独立使用套接字,避免人为引入通道同步点, 也无需将传入消息拆分为命令与回复、交错处理,简化实现。 但等待另一通道回复时,仍不能无限期停止处理命令或回复,否则可能死锁。 由于消息在不同套接字上到达,可用两个内部同步运行的独立请求线程满足要求, 预期比完全异步实现简单。也可因其他原因采用完全异步、事件驱动设计,协议完整支持。
同一服务器是否处理来自同一或多个客户端的多设备,是服务器实现细节。 套接字位置由实现决定。本版本不支持在同一套接字复用客户端、设备和服务器。
认证
AF_UNIX 依赖操作系统对套接字文件的强制访问控制,由管理层正确设置。 跨客户机或主机的套接字需要适当认证,具体机制留待未来版本。
命令并发
客户端可流水发送多个命令,无需等待此前回复。服务器按接收顺序处理。 因此先发送 No_reply 命令,再发送需回复命令时, 服务器回复后者之前,前者一定已处理。 使用流水时客户端必须了解设备的并发能力,例如多线程同时访问设备区域时, 仍需遵循设备语义。
例如帧缓冲可允许并发访问不同显存区域, 但并发访问命令或状态寄存器的行为可能不确定。
服务器向客户端的不相关消息可能插在客户端请求与回复之间,反之亦然。
某些命令可能具有无上限的延迟,例如 VFIO_USER_DEVICE_RESET 完成时间任意长, 客户端应避免不必要的阻塞。
套接字断开行为
任意一方可能有意或意外断开,双方都需知道如何处理。
服务器断开
服务器断开可能表示:
虚拟设备被重启,可能出于更新等有意操作,也可能因崩溃。
虚拟设备已关闭且不准备重启。
客户端无法判断故障是否暂时或无害、是否应重试,因此发现套接字断开后应复位 VFIO 设备。 错误恢复由客户机设备错误处理逻辑驱动。
客户端断开
客户端断开通常表示已退出。目前这意味着客户机关闭、设备不再需要,服务器可自动退出。 但下列情况不应因此退出:
一个服务器服务多个客户端。
多进程 QEMU 逐步自我升级,此功能尚未实现。
为保证向前兼容,服务器应这样处理:
取消映射并清理全部客户端内存区,关闭已传入的描述符。
关闭旧客户端传入的全部 IRQ 描述符。
保留其余设备状态。
预期客户端重连后重新建立 IRQ 和内存映射。
若客户端确实退出等,控制栈会获知并相应清理资源。
安全考虑
一般而言,双方不应相互信任。应使用标准工具和机制验证输入, 防止拒绝服务、缓冲区溢出等问题。
请求重试与响应超时
失败命令指已成功发送、收到错误码回复的命令。 无法发送命令(如套接字断开)属于前述另一类错误。
备注
QEMU VFIO 会重试部分失败操作。这对真实硬件合理,但是否适用于虚拟设备尚不确定。
重试与超时方案留待未来协议版本定义。
消息大小
部分请求有 argsz 字段,表示预期回复载荷上限, 至少应能容纳本文定义的固定回复载荷头。 请求自身载荷大小由普通头部 msg_size 定义,而不是 argsz。
回复中,服务器将 argsz 设为完整载荷所需大小,可能小于请求上限,也可能更大。 更大时回复不含完整载荷,但 argsz 告知所需大小, 让客户端分配更大缓冲区后重试。
此外,协商时双方可分别指定 max_data_xfer_size,见 Version。 它限制单个 VFIO_USER_DMA/VFIO_USER_REGION_READ/WRITE 的数据量,见 Read and Write Operations。
协议规范
为区别基本 VFIO 符号,所有协议符号使用 vfio_user 或 VFIO_USER 前缀。 当前全部数据采用主机字节序;未来双方运行在不同字节序主机时,可能放宽此限制。
除非另有说明,所有大小单位均为字节。
命令
下表列出 VFIO 消息命令 ID 与发送方向。
- ==============.. list-table::
- header-rows:
1
名称
命令
请求方向
VFIO_USER_VERSION1
客户端到服务器
VFIO_USER_DMA_MAP2
客户端到服务器
VFIO_USER_DMA_UNMAP3
客户端到服务器
VFIO_USER_DEVICE_GET_INFO4
客户端到服务器
VFIO_USER_DEVICE_GET_REGION_INFO5
客户端到服务器
VFIO_USER_DEVICE_GET_REGION_IO_FDS6
客户端到服务器
VFIO_USER_DEVICE_GET_IRQ_INFO7
客户端到服务器
VFIO_USER_DEVICE_SET_IRQS8
客户端到服务器
VFIO_USER_REGION_READ9
客户端到服务器
VFIO_USER_REGION_WRITE10
客户端到服务器
VFIO_USER_DMA_READ11
服务器到客户端
VFIO_USER_DMA_WRITE12
服务器到客户端
VFIO_USER_DEVICE_RESET13
客户端到服务器
VFIO_USER_REGION_WRITE_MULTI15
客户端到服务器
VFIO_USER_DEVICE_FEATURE16
客户端到服务器
VFIO_USER_MIG_DATA_READ17
客户端到服务器
VFIO_USER_MIG_DATA_WRITE18
客户端到服务器
消息头
所有命令与回复都以 16 字节消息头开始,包含基本信息, 随后为以下章节描述的专用数据。
名称 |
偏移 |
大小 |
||||||||
|---|---|---|---|---|---|---|---|---|---|---|
Message ID |
0 |
2 |
||||||||
Command |
2 |
2 |
||||||||
Message size |
4 |
4 |
||||||||
Flags |
8 |
4 |
||||||||
|
||||||||||
Error |
12 |
4 |
||||||||
<message data> |
16 |
可变 |
||||||||
Message ID 标识消息,回复会原样返回。ID 完全由发送方管理,可复用,甚至并发复用; 接收方不得假定唯一性。
Command 指定 Commands 所列命令,回复头也设置该值。
Message size 是含头部的整个消息大小。
Flags 表示消息属性:
Type 位表示类型。
Command(0x0):命令消息。
Reply(0x1):确认此前具有同一 ID 的命令。
命令的 No_reply 表示无需回复,常用于多命令仅要求最后一个确认的情况。
回复的 Error 表示被确认命令发生错误,此时 Error 字段有效。
回复中的 Error 为可选 UNIX errno。即使 Flags 的 Error 位设置,也可能为零。 命令中此字段保留。
Commands 中每个命令都必须回复,除非设置 No_Reply。 回复包含设置了 Reply 位的消息头,以及额外数据。
发生错误时,回复只能包含头部。
后续各命令定义省略标准头部,所列数据应追加在标准头后,偏移从标准头末尾算起。
VFIO_USER_VERSION
连接建立后客户端发送的首个消息,服务器回复使用相同格式。
客户端必须发送此消息,提出协议版本和能力;服务器与自身支持情况比较,按以下规则回复:
回复主版本必须与所提相同。若客户端不支持所提主版本,则关闭连接。
回复次版本必须小于或等于所提版本。
能力列表必须为提议的子集。服务器要求客户端未包含的能力时关闭连接。
只有修改消息格式等不兼容变更才修改主版本。 新增消息或能力等兼容变更可修改次版本。 双方必须支持所支持最大次版本以下的全部版本,例如支持 1.3 必须同时支持 1.0–1.2。
修改规范时,必须以“added in version X.Y”形式注明协议版本。
请求
名称 |
偏移 |
大小 |
|---|---|---|
version major |
0 |
2 |
version minor |
2 |
2 |
version data |
4 |
可变,含末尾 NUL,可选。 |
version data 是可选的 UTF-8 JSON 字节数组,格式如下:
名称 |
类型 |
描述 |
|---|---|---|
capabilities |
object |
发送方支持的通用能力,可选。 |
能力:
名称 |
类型 |
描述 |
|---|---|---|
max_msg_fds |
number |
发送方单条消息最多可接收的描述符数量。可选,未指定时接收方必须假定为 1。 |
max_data_xfer_size |
number |
数据传输消息最大 count,见 Read and Write Operations。可选,默认 1048576 字节。 |
max_dma_maps |
number |
同时有效的 VFIO_USER_DMA 映射窗口最大数量。可选,默认 65535(64k-1)。 |
pgsizes |
number |
VFIO_USER_DMA 映射支持的页大小按位或。可选,默认仅支持 4k 页。 |
twin_socket |
object |
双套接字模式参数,让服务器到客户端命令及回复使用独立套接字,可选。 |
write_multiple |
boolean |
为 true 时支持 VFIO_USER_REGION_WRITE_MULTI。 |
双套接字能力对象包含:
名称 |
类型 |
描述 |
|---|---|---|
supported |
boolean |
发送方是否支持双套接字,可选,默认 false。 |
fd_index |
number |
消息描述符数组中,用于服务器到客户端命令通道的套接字索引。 可选,仅在回复有效。 |
回复
服务器回复格式相同,语义如上。
当且仅当客户端将 twin_socket.supported 设为 true,服务器才可选择建立反向独立通道:
创建新的套接字对。
保留服务器端,将客户端端放入回复的描述符数组。
回复中将 twin_socket.supported 设为 true。
用 twin_socket.fd_index 标明索引,让客户端识别正确描述符。
请求双套接字的客户端必须检查回复:
supported 为 false、字段缺失或整个 twin_socket 缺失,表示服务器不支持或未启用。 客户端可选择继续单套接字运行,或关闭连接。
supported 为 true,fd_index 存在且指向有效描述符,则协商成功。 客户端监视该描述符接收服务器命令。
其他情况表示回复不一致,客户端可能无法接收服务器命令,必须中止并关闭连接。
该功能可选,但会发送反向命令的服务器强烈建议实现。
VFIO_USER_DMA_MAP
客户端发送此命令,告知服务器可访问的内存区域,必须在任何客户端 VFIO_USER_DMA 前发送。 通常紧随版本握手,也可在客户端添加内存或使用 vIOMMU 时发送。
请求
载荷结构:
名称 |
偏移 |
大小 |
||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||
flags |
4 |
4 |
||||||||||
|
||||||||||||
offset |
8 |
8 |
||||||||||
address |
16 |
8 |
||||||||||
size |
24 |
8 |
||||||||||
argsz 为上述结构大小。此消息无回复载荷,与其他类型不同。
flags 包含区域属性:
readable:可读。
writeable:可写。
access mode:服务器应采用的访问方式。
offset:区域在相关描述符文件中的偏移;不可映射时为零。
address:区域 VFIO_USER_DMA 基址。
size:区域大小。
结构长 32 字节,因此消息为 16 + 32 字节。
服务器有以下访问方式:
VFIO_USER_DMA_READ/WRITE 消息,见 Read and Write Operations。
mmap() 客户端描述符,直接访问底层内存。
对客户端描述符执行 pread()/pwrite() 等文件 I/O。
flags 的模式位决定方式。指定了需要描述符的方式却没有描述符时, 服务器必须以 EINVAL 拒绝。未设置模式位时,若提供描述符则使用 mmap(),否则用消息。
AF_UNIX 必须通过 SCM_RIGHTS 辅助数据传描述符。
覆盖已有区域的映射命令必须失败,回复 Error 为 EEXIST。
回复
无回复载荷。
VFIO_USER_DMA_UNMAP
客户端通知此前通过 VFIO_USER_DMA_MAP 提供的区域不再可用于 VFIO_USER_DMA, 通常发生于移除内存或使用 vIOMMU。结构如下。
请求
载荷格式:
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
flags |
4 |
4 |
address |
8 |
8 |
size |
16 |
8 |
argsz:回复载荷最大大小。
flags:本版本未使用。
address:VFIO_USER_DMA 区域基址。
size:区域大小。
取消映射的地址与大小必须精确匹配此前映射。
回复
若描述符已映射,服务器回复前必须释放该 VFIO_USER_DMA 区域全部引用, 可能包括在途 VFIO_USER_DMA 事务。
回复包含请求中的原始 VFIO_USER_DMA 条目。
VFIO_USER_DEVICE_GET_INFO
客户端查询设备基本信息。
请求
名称 |
偏移 |
大小 |
||||||
|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||
flags |
4 |
4 |
||||||
|
||||||||
num_regions |
8 |
4 |
||||||
num_irqs |
12 |
4 |
||||||
argsz:回复载荷最大大小。
其他字段必须为零。
回复
名称 |
偏移 |
大小 |
||||||
|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||
flags |
4 |
4 |
||||||
|
||||||||
num_regions |
8 |
4 |
||||||
num_irqs |
12 |
4 |
||||||
argsz:完整回复所需大小,当前为 16 字节。
flags 包含:
VFIO_DEVICE_FLAGS_RESET:支持 VFIO_USER_DEVICE_RESET。
VFIO_DEVICE_FLAGS_PCI:设备为 PCI 设备。
num_regions:暴露的内存区域数量。
num_irqs:支持的不同中断类型数量。
本版本仅支持 PCI,未来可能支持其他设备。
VFIO_USER_DEVICE_GET_REGION_INFO
客户端查询设备区域信息。VFIO 区域信息结构为 linux/vfio.h 的 struct vfio_region_info。
请求
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
flags |
4 |
4 |
index |
8 |
4 |
cap_offset |
12 |
4 |
size |
16 |
8 |
offset |
24 |
8 |
argsz:回复载荷最大大小。
index:待查询内存区域索引,是命令唯一必须设置的字段。
其他字段必须为零。
回复
名称 |
偏移 |
大小 |
||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||
flags |
4 |
4 |
||||||||||
|
||||||||||||
index |
8 |
4 |
||||||||||
cap_offset |
12 |
4 |
||||||||||
size |
16 |
8 |
||||||||||
offset |
24 |
8 |
||||||||||
argsz:完整回复所需大小,包括区域信息结构及全部能力。
flags:区域属性。
VFIO_REGION_INFO_FLAG_READ:允许客户端读取。
VFIO_REGION_INFO_FLAG_WRITE:允许客户端写入。
VFIO_REGION_INFO_FLAG_MMAP:允许 mmap();回复元数据附带描述符, AF_UNIX 通过 SCM_RIGHTS 辅助数据传递。
VFIO_REGION_INFO_FLAG_CAPS:回复含额外能力。
index:所查询区域的索引,是命令唯一必须设置的字段。
cap_offset:额外能力的位置,相对 VFIO 区域信息结构开头, 指向 linux/vfio.h 定义的 VFIO 能力头。
size:区域大小。
offset:可映射区域传给 mmap() 的偏移,也用作下述稀疏映射的基础偏移。
VFIO 区域能力
区域信息可包含能力列表,类似 PCI 能力链。 每项的公共头标识能力并给出下一项位置。 头部在 linux/vfio.h 的 struct vfio_info_cap_header 中定义。
VFIO 能力头格式
名称 |
偏移 |
大小 |
|---|---|---|
id |
0 |
2 |
version |
2 |
2 |
next |
4 |
4 |
id:能力标识。
version:能力专用版本号。
next:下一项偏移,相对 VFIO 区域信息结构开头。
VFIO 稀疏 mmap 能力头
名称 |
值 |
|---|---|
id |
VFIO_REGION_INFO_CAP_SPARSE_MMAP |
version |
0x1 |
next |
<next> |
sparse mmap info |
VFIO region info sparse mmap |
仅部分区域支持客户端 mmap() 直接访问时定义此能力。 结构位于 linux/vfio.h:struct vfio_region_sparse_mmap_area 和 struct vfio_region_info_cap_sparse_mmap。
VFIO 稀疏 mmap 区域信息能力
名称 |
偏移 |
大小 |
|---|---|---|
nr_areas |
0 |
4 |
reserved |
4 |
4 |
offset |
8 |
8 |
size |
16 |
8 |
... |
nr_areas:区域中稀疏 mmap 范围的数量。
offset 和 size:一个可映射范围,共有 nr_areas 对。 offset 加上 VFIO_USER_DEVICE_GET_REGION_INFO 的基础偏移,形成后续 mmap() 的偏移参数。
结构定义于 linux/vfio.h 的 struct vfio_region_info_cap_sparse_mmap。
VFIO_USER_DEVICE_GET_REGION_IO_FDS
客户端可通过 VFIO_USER_REGION_READ/WRITE 访问区域,也可 mmap() 服务器提供的描述符。
此命令提供另一种基于描述符的可选访问机制,用于提高性能。 KVM 等底层子系统可通过这些描述符与服务器直接通信,无需客户端往返。
服务器返回所查询区域的子区域数组,每项描述范围偏移、大小及所请求的通知机制。 各子区域可以使用不同方式,本规范支持 ioeventfd 和 ioregionfd。
服务器还在辅助数据中返回描述符,客户端应按所请求机制配置。 例如,可通过 KVM_IOEVENTFD ioctl 为 KVM 配置 ioeventfd。
请求
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
flags |
4 |
4 |
index |
8 |
4 |
count |
12 |
4 |
argsz:回复载荷最大大小。
index:待查询区域索引。
其他字段为零。
客户端必须将 flags 置零,并在 index 指定区域。
回复
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
flags |
4 |
4 |
index |
8 |
4 |
count |
12 |
4 |
sub-regions |
16 |
... |
argsz:区域 I/O FD 信息结构加全部子区域数组大小。 数组第 i 项偏移为 i * ((argsz - 32) / count)。 两种 I/O FD 当前每项均为 40 字节,但不得依赖这一事实。 与其他消息一样,此值表示完整回复所需载荷大小。
flags:必须为零。
index:区域索引。
count:子区域数量。
sub-regions:子区域 I/O FD 信息数组。
回复辅助数据还至少含一个描述符,多个子区域可共用一个描述符。
客户端负责验证所请求值,例如偏移不能超出区域边界。
根据 type 是 VFIO_USER_IO_FD_TYPE_IOEVENTFD 还是 VFIO_USER_IO_FD_TYPE_IOREGIONFD, 子区域采用以下两种结构之一。
子区域 I/O FD 信息格式(ioeventfd)
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
size |
8 |
8 |
fd_index |
16 |
4 |
type |
20 |
4 |
flags |
24 |
4 |
padding |
28 |
4 |
datamatch |
32 |
8 |
offset:子区域在所查询区域内的起始偏移,即物理地址偏移。
size:子区域长度。访问大小无关时可为零,以允许优化。
fd_index:辅助数据中用于 ioeventfd 通知的描述符索引,可共享。
type:VFIO_USER_IO_FD_TYPE_IOEVENTFD。
flags 可包含:
KVM_IOEVENTFD_FLAG_DATAMATCH
KVM_IOEVENTFD_FLAG_PIO
KVM_IOEVENTFD_FLAG_VIRTIO_CCW_NOTIFY(待确认:此处是否合理?)
datamatch:需要时的匹配值。
专用字段的背景见 https://www.kernel.org/doc/Documentation/virtual/kvm/api.txt, 第 4.59 节 KVM_IOEVENTFD。
子区域 I/O FD 信息格式(ioregionfd)
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
size |
8 |
8 |
fd_index |
16 |
4 |
type |
20 |
4 |
flags |
24 |
4 |
padding |
28 |
4 |
user_data |
32 |
8 |
offset:子区域在所查询区域内的起始偏移。
size:子区域长度。访问大小无关时可为零以允许优化, 此时 flags 必须设置 KVM_IOREGION_POSTED_WRITES。
fd_index:辅助数据中用于 ioregionfd 消息的描述符索引,可共享。
type:VFIO_USER_IO_FD_TYPE_IOREGIONFD。
flags 可包含:
KVM_IOREGION_PIO
KVM_IOREGION_POSTED_WRITES
user_data:不透明值,经描述符上的消息返回服务器。
专用字段详情见: https://lore.kernel.org/kvm/cover.1613828726.git.eafanasova@gmail.com/
待办:更新为最终 API 文档。
VFIO_USER_DEVICE_GET_IRQ_INFO
客户端查询设备中断类型信息。结构为 linux/vfio.h 的 struct vfio_irq_info。
请求
名称 |
偏移 |
大小 |
||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||
flags |
4 |
4 |
||||||||||
|
||||||||||||
index |
8 |
4 |
||||||||||
count |
12 |
4 |
||||||||||
argsz:回复载荷最大大小,当前 16 字节。
index:IRQ 类型索引,例如 VFIO_PCI_MSIX_IRQ_INDEX。
其他字段为零。
回复
名称 |
偏移 |
大小 |
||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||
flags |
4 |
4 |
||||||||||
|
||||||||||||
index |
8 |
4 |
||||||||||
count |
12 |
4 |
||||||||||
argsz:完整回复所需大小,当前 16 字节。
flags:IRQ 属性。
VFIO_IRQ_INFO_EVENTFD:可由服务器通过 eventfd 发信号。
VFIO_IRQ_INFO_MASKABLE:支持 VFIO_USER_SET_IRQS 的 MASK、UNMASK。
VFIO_IRQ_INFO_AUTOMASKED:触发后自动屏蔽,客户端需发 UNMASK 才能接收新中断。
VFIO_IRQ_INFO_NORESIZE:VFIO_USER_SET_IRQS 将中断作为整体设置, 不禁用整个类型就无法启用新子索引。
index:所查询 IRQ 类型索引。
count:该类型的中断数量。
VFIO_USER_DEVICE_SET_IRQS
客户端设置设备中断类型的动作,结构为 linux/vfio.h 的 struct vfio_irq_set。
请求
名称 |
偏移 |
大小 |
||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||||||
flags |
4 |
4 |
||||||||||||||
|
||||||||||||||||
index |
8 |
4 |
||||||||||||||
start |
12 |
4 |
||||||||||||||
count |
16 |
4 |
||||||||||||||
data |
20 |
可变 |
||||||||||||||
argsz:含 data 的 IRQ 设置请求载荷大小;此消息没有回复载荷,与其他类型不同。
flags:中断范围上的动作。DATA 组描述数据形式,ACTION 组描述操作,各组内部互斥。
VFIO_IRQ_SET_DATA_NONE:无数据字段,无条件执行。
VFIO_IRQ_SET_DATA_BOOL:数据是布尔字节数组,仅对应值为真时执行。
VFIO_IRQ_SET_DATA_EVENTFD:元数据传入事件描述符数组, ACTION 指定的动作发生时向其发信号。AF_UNIX 用 SCM_RIGHTS 传递。 未提供描述符则解除此前配置的指定中断。
VFIO_IRQ_SET_ACTION_MASK:屏蔽事件。配合 BOOL/NONE 直接屏蔽; 配合 EVENTFD 则在客户机屏蔽时生成事件。
VFIO_IRQ_SET_ACTION_UNMASK:取消屏蔽。配合 BOOL/NONE 直接取消; 配合 EVENTFD 则在客户机取消屏蔽时生成事件。
VFIO_IRQ_SET_ACTION_TRIGGER:触发事件。配合 BOOL/NONE 触发中断; 配合 EVENTFD 则在服务器触发中断时生成事件。
index:要设置的 IRQ 类型索引。
start:起始子索引。
count:子索引数量。特殊情况下 start 和 count 都为 0, 且使用 VFIO_IRQ_SET_DATA_NONE,则禁用该索引全部中断。
data:使用 BOOL 时的可选布尔数组,决定对应索引是否执行动作, 用于只操作指定范围中的子集。
并非每种中断都支持所有数据/动作组合。 发送 VFIO_USER_DEVICE_SET_IRQ 前,客户端必须了解设备及 IRQ 索引能力。
典型 IRQ 流程:
客户端发送 VFIO_USER_DEVICE_SET_IRQ, flags=(VFIO_IRQ_SET_DATA_EVENTFD|VFIO_IRQ_SET_ACTION_TRIGGER), 并附 eventfd,使服务器将 IRQ 与描述符关联。
可再发送同一消息, flags=(VFIO_IRQ_SET_DATA_EVENTFD|VFIO_IRQ_SET_ACTION_MASK/UNMASK), 附另一 eventfd,将其与服务器屏蔽/取消屏蔽状态关联。
服务器向 eventfd 写 1 即可触发 IRQ。
服务器屏蔽或取消 IRQ 时,如有相应 eventfd,则向其写 1。
客户端可自行触发设备 IRQ,发送同一消息, flags=(VFIO_IRQ_SET_DATA_NONE/BOOL|VFIO_IRQ_SET_ACTION_TRIGGER)。
客户端可屏蔽或取消 IRQ,发送同一消息, flags=(VFIO_IRQ_SET_DATA_NONE/BOOL|VFIO_IRQ_SET_ACTION_MASK/UNMASK)。
回复
没有载荷。
即使相应内存或设备区域已作为可映射区域共享, 客户端和/或服务器也必须支持下列全部操作。
无论读写,count 都不得超过对端的 max_data_xfer_size。
VFIO_USER_REGION_READ
区域不可映射时,客户端无法 mmap() 直接访问,可用此消息读取。
请求
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
region |
8 |
4 |
count |
12 |
4 |
offset:所访问区域内的偏移。
region:区域索引。
count:待传输大小。
回复
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
region |
8 |
4 |
count |
12 |
4 |
data |
16 |
可变 |
offset:已访问的偏移。
region:区域索引。
count:已传输大小。
data:从设备区域读取的数据。
VFIO_USER_REGION_WRITE
区域不可映射时,客户端无法 mmap() 直接访问,可用此消息写入。
请求
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
region |
8 |
4 |
count |
12 |
4 |
data |
16 |
可变 |
offset:区域内偏移。
region:区域索引。
count:待传输大小。
data:待写数据。
回复
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
region |
8 |
4 |
count |
12 |
4 |
offset:已访问的偏移。
region:区域索引。
count:已传输大小。
VFIO_USER_DMA_READ
客户端未共享可映射内存时,服务器用此消息读取客户机内存。 协商了双套接字时,此消息与回复使用独立的服务器到客户端通道。
请求
名称 |
偏移 |
大小 |
|---|---|---|
address |
0 |
8 |
count |
8 |
8 |
address:客户端 VFIO_USER_DMA 内存地址,必须已通过 VFIO_USER_DMA_MAP 导出。
count:待传输大小。
回复
名称 |
偏移 |
大小 |
|---|---|---|
address |
0 |
8 |
count |
8 |
8 |
data |
16 |
可变 |
address:所访问的 VFIO_USER_DMA 地址。
count:已传输大小。
data:读取的数据。
短读时返回错误还是仅调整 count,由客户端决定;服务器必须能处理两者。
VFIO_USER_DMA_WRITE
客户端未共享可映射内存时,服务器用此消息写入客户机内存。 协商了双套接字时,此消息与回复使用独立的服务器到客户端通道。
请求
名称 |
偏移 |
大小 |
|---|---|---|
address |
0 |
8 |
count |
8 |
8 |
data |
16 |
可变 |
address:客户端 VFIO_USER_DMA 地址,必须已通过 VFIO_USER_DMA_MAP 导出。
count:待传输大小。
data:待写数据。
回复
名称 |
偏移 |
大小 |
|---|---|---|
address |
0 |
8 |
count |
8 |
8 |
address:所访问的 VFIO_USER_DMA 地址。
count:已传输大小。
短写时返回错误还是仅调整 count,由客户端决定;服务器必须能处理两者。
VFIO_USER_DEVICE_RESET
客户端请求服务器复位设备。请求与回复均无载荷。
VFIO_USER_REGION_WRITE_MULTI
将多个设备写合并为一个消息,仅用于发送队列相对较满时的优化。
请求
名称 |
偏移 |
大小 |
|---|---|---|
wr_cnt |
0 |
8 |
wrs |
8 |
可变 |
wr_cnt:合并的设备写数量。
wrs:下述设备写数组。
单次设备写格式
名称 |
偏移 |
大小 |
|---|---|---|
offset |
0 |
8 |
region |
8 |
4 |
count |
12 |
4 |
data |
16 |
8 |
offset:区域内偏移。
region:区域索引。
count:待传输大小,此格式仅能描述不超过 8 字节的写。
data:待写数据。
回复
名称 |
偏移 |
大小 |
|---|---|---|
wr_cnt |
0 |
8 |
wr_cnt:已完成设备写数量。
VFIO_USER_DEVICE_FEATURE
类似 VFIO_DEVICE_FEATURE,用于获取、设置或探测设备功能数据。
请求
请求载荷格式:
名称 |
偏移 |
大小 |
||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
argsz |
0 |
4 |
||||||||||
flags |
4 |
4 |
||||||||||
|
||||||||||||
data |
8 |
可变 |
||||||||||
argsz:回复载荷最大大小。
flags:服务器应执行的操作及所针对功能。
最低 16 位为功能索引,可用 VFIO_DEVICE_FEATURE_MASK 提取。
VFIO_DEVICE_FEATURE_GET:获取功能数据。
VFIO_DEVICE_FEATURE_SET:将功能数据设置为载荷 data。
VFIO_DEVICE_FEATURE_PROBE:探测支持。如果同时设 GET 和/或 SET, 只有指定的全部方法都支持时才成功。
GET 与 SET 互斥,配合 PROBE 使用时除外。
data:具体功能的数据,探测时不用。
此请求部分对应 VFIO 的 struct vfio_device_feature。
回复
设置或探测时,回复载荷必须与请求相同。 获取时,将数据加入 data,并将其长度加到 argsz。
设备功能
当前仅支持迁移相关功能,是 Linux VFIO 所支持功能的子集,未来可能变化。
名称 |
功能索引 |
|---|---|
VFIO_DEVICE_FEATURE_MIGRATION |
1 |
VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE |
2 |
VFIO_DEVICE_FEATURE_DMA_LOGGING_START |
6 |
VFIO_DEVICE_FEATURE_DMA_LOGGING_STOP |
7 |
VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT |
8 |
VFIO_DEVICE_FEATURE_MIGRATION
表明设备可通过 VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE 支持迁移 API。 GET 成功时必定支持 RUNNING 和 ERROR,其他状态由 flags 表示, 至少必须设置 VFIO_MIGRATION_STOP_COPY。
请求没有 data。回复 data 格式:
名称 |
偏移 |
大小 |
||||||||
|---|---|---|---|---|---|---|---|---|---|---|
flags |
0 |
8 |
||||||||
|
||||||||||
标志解释与 VFIO 相同。
VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE
SET 请求执行设备迁移状态转换,新状态在 device_state 中提供,回复前必须完全完成转换。
回复及 SET 请求的 data 格式:
名称 |
偏移 |
大小 |
|---|---|---|
device_state |
0 |
4 |
data_fd |
4 |
4 |
device_state:GET 时为当前状态,SET 时为目标状态。 由 vfio_device_mig_state 枚举定义,是迁移有限状态机的状态。
名称 |
状态 |
描述 |
|---|---|---|
VFIO_DEVICE_STATE_ERROR |
0 |
设备失败,必须复位。 |
VFIO_DEVICE_STATE_STOP |
1 |
设备不改变内部或外部状态。 |
VFIO_DEVICE_STATE_RUNNING |
2 |
正常运行。 |
VFIO_DEVICE_STATE_STOP_COPY |
3 |
可读出内部状态。 |
VFIO_DEVICE_STATE_RESUMING |
4 |
设备已停止,正在加载新内部状态。 |
VFIO_DEVICE_STATE_RUNNING_P2P |
5 |
vfio-user 不使用。 |
VFIO_DEVICE_STATE_PRE_COPY |
6 |
正常运行,同时跟踪内部状态变化。 |
VFIO_DEVICE_STATE_PRE_COPY_P2P |
7 |
vfio-user 不使用。 |
data_fd:vfio-user 不使用,改用 VFIO_USER_MIG_DATA_READ/WRITE 传输迁移数据。
直接状态转换
迁移状态机是 Mealy 机,动作发生在状态间的边上。 服务器必须支持以下转换,是 linux/vfio.h 中 enum vfio_device_mig_state 的子集。
RUNNING -> STOP、STOP_COPY -> STOP:停止设备,后者还终止数据传输会话。
RESUMING -> STOP:终止传输会话,完成迁移数据处理,停止设备。 数据不完整、不一致或无效时令 SET 失败,可选择转入 ERROR。
PRE_COPY -> RUNNING:终止传输会话,设备恢复完全运行。
STOP -> RUNNING:启动设备。
RUNNING -> PRE_COPY、STOP -> STOP_COPY:开始保存设备状态。 设备运行状态不变,但开始传输。PRE_COPY 和 STOP_COPY 称为保存组状态。
PRE_COPY -> STOP_COPY:继续传输,但停止设备。
STOP -> RESUMING:开始恢复,可修改内部状态以准备接收数据。
明确禁止 STOP_COPY -> PRE_COPY,请求时应报错。
不能直接指定 ERROR。但若服务器既无法执行请求转换, 又无法将设备恢复到任何有效状态,可令请求失败并转入 ERROR。 恢复必须通过 VFIO_USER_DEVICE_RESET 回到 RUNNING。
不支持 PRE_COPY 时,移除与它相连的边。
复合状态转换
其他转换通过上述边组合实现。存在多条路径时按以下规则选择:
选择最短路径。
保存组状态只能作为起点或终点,不能作为中间状态。
VFIO_DEVICE_FEATURE_DMA_LOGGING_START / VFIO_DEVICE_FEATURE_DMA_LOGGING_STOP
SET 时启动或停止 VFIO_USER_DMA 日志,也可探测设备是否支持。
启动时提供待监视 IOVA 范围,设备可仅记录这些范围以优化。只记录 VFIO_USER_DMA 写入。
SET 请求 data 格式:
名称 |
偏移 |
大小 |
|---|---|---|
page_size |
0 |
8 |
num_ranges |
8 |
4 |
reserved |
12 |
4 |
ranges |
16 |
可变 |
page_size:期望跟踪粒度。设备不支持时,驱动自行选择支持的页大小,并在输出返回。
num_ranges:监视范围数量。零表示记录全部写入。
ranges:vfio_user_device_feature_dma_logging_range 数组:
名称 |
偏移 |
大小 |
|---|---|---|
iova |
0 |
8 |
length |
8 |
8 |
iova:I/O 虚拟基址。
length:记录范围长度。
成功时回复 data 与请求相同,只有页大小改变时相应更新。
VFIO_DEVICE_FEATURE_DMA_LOGGING_REPORT
GET 时返回指定 IOVA 范围的脏位图,仅在此前通过 VFIO_DEVICE_FEATURE_DMA_LOGGING_START 启动记录后有效。
请求 data 格式:
名称 |
偏移 |
大小 |
|---|---|---|
iova |
0 |
8 |
length |
8 |
8 |
page_size |
16 |
8 |
iova:I/O 虚拟基址。
length:范围长度。
page_size:位图粒度,必须为 2 的幂。不必匹配启动记录时的值, 驱动会将内部日志转换为报告粒度;内部页更小时,可能通过复制位实现。
响应 data 格式相同,但在偏移 24 追加位图。
位图是 u64 数组,每位表示一个 page_size 单位的 IOVA。 请求范围外的位必须为零。
IOVA 到位的映射:
bitmap[(addr - iova)/page_size] & (1ULL << (addr % 64))
VFIO_USER_MIG_DATA_READ
从处于 PRE_COPY 或 STOP_COPY 保存组状态的源迁移服务器读取数据。
它与 VFIO_USER_MIG_DATA_WRITE 取代 linux/vfio.h 的 struct vfio_device_feature_mig_state 中 data_fd, 使全部数据使用已建立的单一 UNIX 套接字传输。 迁移数据视为流,客户端必须持续读取直到全部读完。
请求
载荷格式:
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
size |
4 |
4 |
argsz:回复载荷最大大小。
size:希望读取的迁移数据大小。
回复
载荷格式:
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
size |
4 |
4 |
data |
8 |
可变 |
argsz:含 data 在内的结构大小。
size:返回数据大小。小于请求值时,表示没有更多数据。
data:迁移数据。
VFIO_USER_MIG_DATA_WRITE
向处于 RESUMING 状态的目标迁移服务器写入数据。
同上,取代 data_fd 传输,迁移数据视为流。
请求
载荷格式:
名称 |
偏移 |
大小 |
|---|---|---|
argsz |
0 |
4 |
size |
4 |
4 |
data |
8 |
可变 |
argsz:回复载荷最大大小。
size:待写入迁移数据大小。
data:迁移数据。
回复
无回复载荷。
附录
未使用的 VFIO ioctl() 命令
以下 VFIO 命令没有对应的 vfio-user 命令:
VFIO_GET_API_VERSION
VFIO_CHECK_EXTENSION
VFIO_SET_IOMMU
VFIO_GROUP_GET_STATUS
VFIO_GROUP_SET_CONTAINER
VFIO_GROUP_UNSET_CONTAINER
VFIO_GROUP_GET_DEVICE_FD
VFIO_IOMMU_GET_INFO
VFIO 设备热迁移支持最终确定后,部分命令可能需以对应 vfio-user 形式由客户端处理, 未来协议版本将解决。
VFIO 组与容器
现有 VFIO 使用组和容器描述设备与主机 IOMMU 的关系。 vfio-user 的 IOMMU 由客户端软件实现,对服务器不可见。 最简单的方式是为每个设备独立设置组和容器。
后端程序约定
基于 vhost-user 后端约定:
后端不得自行转入守护进程模式。
不得假定后端对系统具有何种访问权限。
描述符 0、1、2 必须存在,具有普通 stdin/stdout/stderr 语义,并可重定向。
必须响应 SIGTERM。
必须接受以下命令行选项:
--socket-path=PATH:UNIX 域套接字路径。--fd=FDNUM:UNIX 域套接字描述符,与 --socket-path 不兼容。
必须配有存放于 /usr/share/vfio-user 的 JSON 文件。
待办:添加类似 docs/interop/vhost-user.json 的模式定义。