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() 权限,因此仅在信任服务器不会破坏客户机内存时, 才应导出描述符。

见 Read and Write Operations。

客户端与服务器交互

套接字

一个服务器可服务:

  1. 一个或多个客户端;和/或

  2. 属于一个或多个客户端的一个或多个虚拟设备。

本版本要求每个连接使用专用套接字。客户端向服务器的命令及回复使用主通信套接字。 反向命令及回复可选用协商时建立的另一套接字,AF_UNIX 服务器直接传递描述符。

每个命令通道独立使用套接字,避免人为引入通道同步点, 也无需将传入消息拆分为命令与回复、交错处理,简化实现。 但等待另一通道回复时,仍不能无限期停止处理命令或回复,否则可能死锁。 由于消息在不同套接字上到达,可用两个内部同步运行的独立请求线程满足要求, 预期比完全异步实现简单。也可因其他原因采用完全异步、事件驱动设计,协议完整支持。

同一服务器是否处理来自同一或多个客户端的多设备,是服务器实现细节。 套接字位置由实现决定。本版本不支持在同一套接字复用客户端、设备和服务器。

认证

AF_UNIX 依赖操作系统对套接字文件的强制访问控制,由管理层正确设置。 跨客户机或主机的套接字需要适当认证,具体机制留待未来版本。

命令并发

客户端可流水发送多个命令,无需等待此前回复。服务器按接收顺序处理。 因此先发送 No_reply 命令,再发送需回复命令时, 服务器回复后者之前,前者一定已处理。 使用流水时客户端必须了解设备的并发能力,例如多线程同时访问设备区域时, 仍需遵循设备语义。

例如帧缓冲可允许并发访问不同显存区域, 但并发访问命令或状态寄存器的行为可能不确定。

服务器向客户端的不相关消息可能插在客户端请求与回复之间,反之亦然。

某些命令可能具有无上限的延迟,例如 VFIO_USER_DEVICE_RESET 完成时间任意长, 客户端应避免不必要的阻塞。

套接字断开行为

任意一方可能有意或意外断开,双方都需知道如何处理。

服务器断开

服务器断开可能表示:

  1. 虚拟设备被重启,可能出于更新等有意操作,也可能因崩溃。

  2. 虚拟设备已关闭且不准备重启。

客户端无法判断故障是否暂时或无害、是否应重试,因此发现套接字断开后应复位 VFIO 设备。 错误恢复由客户机设备错误处理逻辑驱动。

客户端断开

客户端断开通常表示已退出。目前这意味着客户机关闭、设备不再需要,服务器可自动退出。 但下列情况不应因此退出:

  1. 一个服务器服务多个客户端。

  2. 多进程 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_VERSION

    • 1

    • 客户端到服务器

    • VFIO_USER_DMA_MAP

    • 2

    • 客户端到服务器

    • VFIO_USER_DMA_UNMAP

    • 3

    • 客户端到服务器

    • VFIO_USER_DEVICE_GET_INFO

    • 4

    • 客户端到服务器

    • VFIO_USER_DEVICE_GET_REGION_INFO

    • 5

    • 客户端到服务器

    • VFIO_USER_DEVICE_GET_REGION_IO_FDS

    • 6

    • 客户端到服务器

    • VFIO_USER_DEVICE_GET_IRQ_INFO

    • 7

    • 客户端到服务器

    • VFIO_USER_DEVICE_SET_IRQS

    • 8

    • 客户端到服务器

    • VFIO_USER_REGION_READ

    • 9

    • 客户端到服务器

    • VFIO_USER_REGION_WRITE

    • 10

    • 客户端到服务器

    • VFIO_USER_DMA_READ

    • 11

    • 服务器到客户端

    • VFIO_USER_DMA_WRITE

    • 12

    • 服务器到客户端

    • VFIO_USER_DEVICE_RESET

    • 13

    • 客户端到服务器

    • VFIO_USER_REGION_WRITE_MULTI

    • 15

    • 客户端到服务器

    • VFIO_USER_DEVICE_FEATURE

    • 16

    • 客户端到服务器

    • VFIO_USER_MIG_DATA_READ

    • 17

    • 客户端到服务器

    • VFIO_USER_MIG_DATA_WRITE

    • 18

    • 客户端到服务器

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

位

定义

0

readable

1

writeable

2

access mode: mmap

3

access mode: file I/O

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

位

定义

0

VFIO_DEVICE_FLAGS_RESET

1

VFIO_DEVICE_FLAGS_PCI

num_regions

8

4

num_irqs

12

4

  • argsz:回复载荷最大大小。

  • 其他字段必须为零。

回复

名称

偏移

大小

argsz

0

4

flags

4

4

位

定义

0

VFIO_DEVICE_FLAGS_RESET

1

VFIO_DEVICE_FLAGS_PCI

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

位

定义

0

VFIO_REGION_INFO_FLAG_READ

1

VFIO_REGION_INFO_FLAG_WRITE

2

VFIO_REGION_INFO_FLAG_MMAP

3

VFIO_REGION_INFO_FLAG_CAPS

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

位

定义

0

VFIO_IRQ_INFO_EVENTFD

1

VFIO_IRQ_INFO_MASKABLE

2

VFIO_IRQ_INFO_AUTOMASKED

3

VFIO_IRQ_INFO_NORESIZE

index

8

4

count

12

4

  • argsz:回复载荷最大大小,当前 16 字节。

  • index:IRQ 类型索引,例如 VFIO_PCI_MSIX_IRQ_INDEX。

  • 其他字段为零。

回复

名称

偏移

大小

argsz

0

4

flags

4

4

位

定义

0

VFIO_IRQ_INFO_EVENTFD

1

VFIO_IRQ_INFO_MASKABLE

2

VFIO_IRQ_INFO_AUTOMASKED

3

VFIO_IRQ_INFO_NORESIZE

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

位

定义

0

VFIO_IRQ_SET_DATA_NONE

1

VFIO_IRQ_SET_DATA_BOOL

2

VFIO_IRQ_SET_DATA_EVENTFD

3

VFIO_IRQ_SET_ACTION_MASK

4

VFIO_IRQ_SET_ACTION_UNMASK

5

VFIO_IRQ_SET_ACTION_TRIGGER

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 流程:

  1. 客户端发送 VFIO_USER_DEVICE_SET_IRQ, flags=(VFIO_IRQ_SET_DATA_EVENTFD|VFIO_IRQ_SET_ACTION_TRIGGER), 并附 eventfd,使服务器将 IRQ 与描述符关联。

  2. 可再发送同一消息, flags=(VFIO_IRQ_SET_DATA_EVENTFD|VFIO_IRQ_SET_ACTION_MASK/UNMASK), 附另一 eventfd,将其与服务器屏蔽/取消屏蔽状态关联。

  3. 服务器向 eventfd 写 1 即可触发 IRQ。

  4. 服务器屏蔽或取消 IRQ 时,如有相应 eventfd,则向其写 1。

  1. 客户端可自行触发设备 IRQ,发送同一消息, flags=(VFIO_IRQ_SET_DATA_NONE/BOOL|VFIO_IRQ_SET_ACTION_TRIGGER)。

  2. 客户端可屏蔽或取消 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

位

定义

0 到 15

Feature index

16

VFIO_DEVICE_FEATURE_GET

17

VFIO_DEVICE_FEATURE_SET

18

VFIO_DEVICE_FEATURE_PROBE

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

位

定义

0

VFIO_MIGRATION_STOP_COPY

1

VFIO_MIGRATION_P2P

2

VFIO_MIGRATION_PRE_COPY

标志解释与 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 的模式定义。