vfio-ccw: 基础架构¶
简介¶
这里我们描述了 Linux/s390 对 I/O 子通道(subchannel)设备的 vfio 支持。vfio-ccw 的动机是将子通道直通(passthrough)到虚拟机,而 vfio 是实现这一目标的手段。
与其他硬件架构不同,s390 定义了一种统一的 I/O 访问方法,即所谓的通道 I/O(Channel I/O)。它有其自己的访问模式
通道程序在一个独立的(协)处理器上异步运行。
通道子系统将直接访问通道程序中由调用者指定的任何内存,即其中不涉及 iommu。
因此,当我们为这些设备引入 vfio 支持时,我们通过中介设备(mediated device, mdev)实现来完成。vfio mdev 将被添加到一个 iommu 组中,从而使其能够被 vfio 框架管理。并且我们为特殊的 vfio I/O 区域添加了读/写回调,以便将通道程序从 mdev 传递到其父设备(真实的 I/O 子通道设备),以进行进一步的地址转换并执行 I/O 指令。
本文并不打算详细解释 s390 I/O 架构的每一个细节。更多信息/参考资料可以在这里找到
全面了解通道 I/O 的良好起点:https://en.wikipedia.org/wiki/Channel_I/O
s390 架构:s390 操作原理手册 (IBM Form. No. SA22-7832)
实现了一个简单的模拟通道子系统的现有 QEMU 代码也可以作为很好的参考。这使得跟踪流程更加容易。qemu/hw/s390x/css.c
关于 vfio 中介设备框架:- VFIO Mediated devices
vfio-ccw 的动机¶
通常,在 s390 上通过 QEMU/KVM 虚拟化的客户机只能通过“通道 I/O 上的 Virtio (virtio-ccw)”传输看到半虚拟化的 virtio 设备。这使得 virtio 设备可以通过处理通道设备的标准操作系统算法被发现。
然而这还不够。在 s390 上,对于使用标准基于通道 I/O 机制的大多数设备,我们还需要提供将它们直通给 QEMU 虚拟机的功能。这包括没有 virtio 对应设备的设备(例如磁带驱动器)或者具有客户机想要利用的特定特征的设备。
为了将设备传递给客户机,我们希望使用与其他所有人相同的接口,即 vfio。我们通过 vfio 中介设备框架和子通道设备驱动程序“vfio_ccw”来实现对通道设备的这种 vfio 支持。
CCW 设备的访问模式¶
s390 架构实现了一个所谓的通道子系统,它提供了物理连接到系统的设备的统一视图。尽管 s390 硬件平台了解各种各样的不同外围连接设备,如磁盘设备(又称 DASD)、磁带、通信控制器等。它们都可以通过定义良好的访问方法进行访问,并且它们以统一的方式呈现 I/O 完成情况:I/O 中断。
所有的 I/O 都需要使用通道命令字(CCW)。CCW 是发给专用 I/O 通道处理器的指令。通道程序是由 I/O 通道子系统执行的 CCW 序列。为了向通道子系统发出通道程序,需要构建一个操作请求块(ORB),它可用于向系统指出 CCW 的格式和其他控制信息。操作系统发信号给 I/O 通道子系统,通过 SSCH(启动子通道)指令开始执行通道程序。然后中央处理器可以自由继续执行非 I/O 指令,直到被中断。I/O 完成结果由中断处理程序以中断响应块(IRB)的形式接收。
回到 vfio-ccw,简而言之
ORB 和通道程序在客户机内核中构建(带有客户机物理地址)。
ORB 和通道程序被传递到宿主机内核。
宿主机内核将客户机物理地址转换为真实地址,并通过发出特权的通道 I/O 指令(例如 SSCH)来启动 I/O。
通道程序在一个独立处理器上异步运行。
I/O 完成将通过 I/O 中断向宿主机发出信号。并且它将被复制为 IRB 到用户空间以传回给客户机。
物理 vfio ccw 设备及其子 mdev¶
如上所述,我们通过 mdev 实现来实现 vfio-ccw。
通道 I/O 没有 IOMMU 硬件支持,因此物理 vfio-ccw 设备没有 IOMMU 级别的转换或隔离。
子通道 I/O 指令全部是特权指令。在处理 I/O 指令拦截时,vfio-ccw 具有软件监督和转换功能,决定了通道程序在发送到硬件之前是如何被编排的。
在此实现中,我们针对两种类型的设备有两个驱动程序
用于物理子通道设备的 vfio_ccw 驱动程序。这是一个针对真实子通道设备的 I/O 子通道驱动程序。它实现了一组回调函数,并作为父(物理)设备注册到 mdev 框架。因此,mdev 为 vfio_ccw 提供了一个通用接口(sysfs)来创建 mdev 设备。随后可以由 vfio_ccw 创建一个 vfio mdev 并将其添加到中介总线中。正是这个 vfio 设备被添加到了 IOMMU 组和 vfio 组中。vfio_ccw 还提供了一个 I/O 区域,用于接受来自用户空间的通道程序请求,并存储 I/O 中断结果供用户空间检索。为了通知用户空间 I/O 完成,它提供了一个接口来设置用于异步发信号的 eventfd 文件描述符(fd)。
用于中介 vfio ccw 设备的 vfio_mdev 驱动程序。这是由 mdev 框架提供的。它是针对由 vfio_ccw 创建的 mdev 的 vfio 设备驱动程序。它实现了一组 vfio 设备驱动程序回调,将自己添加到一个 vfio 组中,并作为 mdev 驱动程序注册到 mdev 框架。它使用了一个 vfio iommu 后端,该后端使用现有的 map 和 unmap ioctl,但它不是将它们编程到设备的 IOMMU 中,而是简单地存储转换以供后续请求使用。这意味着在虚拟机中用客户机物理地址编程的设备可以由 vfio 内核一步到位地将其地址转换为进程虚拟地址,固定页面(pin page)并用宿主机物理地址对硬件进行编程。对于 mdev,vfio iommu 后端不会在 VFIO_IOMMU_MAP_DMA ioctl 期间固定页面。Mdev 框架在此操作中只会维护一个 iova<->vaddr 映射的数据库。并且它们从 vfio iommu 后端导出了 vfio_pin_pages 和 vfio_unpin_pages 接口,供物理设备按需固定和取消固定页面。
以下是高级架构图
+-------------+
| |
| +---------+ | mdev_register_driver() +--------------+
| | Mdev | +<-----------------------+ |
| | bus | | | vfio_mdev.ko |
| | driver | +----------------------->+ |<-> VFIO user
| +---------+ | probe()/remove() +--------------+ APIs
| |
| MDEV CORE |
| MODULE |
| mdev.ko |
| +---------+ | mdev_register_parent() +--------------+
| |Physical | +<-----------------------+ |
| | device | | | vfio_ccw.ko |<-> subchannel
| |interface| +----------------------->+ | device
| +---------+ | callback +--------------+
+-------------+
这些组件协同工作的过程。
vfio_ccw.ko 驱动物理 I/O 子通道,并将物理设备(带回调)注册到 mdev 框架。当 vfio_ccw 探测子通道设备时,它将设备指针和回调注册到 mdev 框架。在 sysfs 中设备节点下的 mdev 相关文件节点将被创建,即 ‘mdev_create’、‘mdev_destroy’ 和 ‘mdev_supported_types’。
创建一个中介 vfio ccw 设备。使用 ‘mdev_create’ sysfs 文件,我们需要手动创建一个(在我们的情况下仅一个)中介设备。
vfio_mdev.ko 驱动中介 ccw 设备。vfio_mdev 也是 vfio 设备驱动程序。它将探测 mdev 并将其添加到 iommu_group 和 vfio_group 中。然后我们可以将该 mdev 直通给客户机。
VFIO-CCW 区域¶
vfio-ccw 驱动程序暴露了 MMIO 区域,以接受来自用户空间的请求并向其返回结果。
vfio-ccw I/O 区域¶
I/O 区域用于接受来自用户空间的通道程序请求,并存储 I/O 中断结果供用户空间检索。该区域的定义是
struct ccw_io_region {
#define ORB_AREA_SIZE 12
__u8 orb_area[ORB_AREA_SIZE];
#define SCSW_AREA_SIZE 12
__u8 scsw_area[SCSW_AREA_SIZE];
#define IRB_AREA_SIZE 96
__u8 irb_area[IRB_AREA_SIZE];
__u32 ret_code;
} __packed;
此区域始终可用。
在启动 I/O 请求时,orb_area 应填充客户机 ORB,scsw_area 应填充虚拟子通道的 SCSW。
irb_area 存储 I/O 结果。
ret_code 存储每次访问该区域的返回码。可能会出现以下值
0操作成功。
-EOPNOTSUPPORB 指定了传输模式,或者 SCSW 指定了启动函数之外的功能。
-EIO在设备未处于准备好接受请求的状态时发出了请求,或者发生了内部错误。
-EBUSY子通道处于状态挂起或忙碌状态,或者请求已经处于活动状态。
-EAGAIN请求正在处理中,调用者应当重试。
-EACCES发现用于 I/O 的通道路径不可操作。
-ENODEV发现设备不可操作。
-EINVALorb 指定的链长于 255 个 ccw,或者发生了内部错误。
vfio-ccw cmd 区域¶
vfio-ccw cmd 区域用于接受来自用户空间的异步指令
#define VFIO_CCW_ASYNC_CMD_HSCH (1 << 0)
#define VFIO_CCW_ASYNC_CMD_CSCH (1 << 1)
struct ccw_cmd_region {
__u32 command;
__u32 ret_code;
} __packed;
该区域通过区域类型 VFIO_REGION_SUBTYPE_CCW_ASYNC_CMD 暴露。
目前,CLEAR SUBCHANNEL(清除子通道)和 HALT SUBCHANNEL(暂停子通道)使用此区域。
command 指定要发出的命令;ret_code 存储每次访问该区域的返回码。可能会出现以下值
0操作成功。
-ENODEV发现设备不可操作。
-EINVAL指定了 halt 或 clear 之外的命令。
-EIO在设备未处于准备好接受请求的状态时发出了请求。
-EAGAIN请求正在处理中,调用者应当重试。
-EBUSY在处理暂停(halt)请求时,子通道处于状态挂起或忙碌状态。
vfio-ccw schib 区域¶
vfio-ccw schib 区域用于向用户空间返回子通道信息块(SCHIB)数据
struct ccw_schib_region {
#define SCHIB_AREA_SIZE 52
__u8 schib_area[SCHIB_AREA_SIZE];
} __packed;
该区域通过区域类型 VFIO_REGION_SUBTYPE_CCW_SCHIB 暴露。
读取此区域会触发向关联硬件发出 STORE SUBCHANNEL(存储子通道)。
vfio-ccw crw 区域¶
vfio-ccw crw 区域用于向用户空间返回通道报告字(CRW)数据
struct ccw_crw_region {
__u32 crw;
__u32 pad;
} __packed;
该区域通过区域类型 VFIO_REGION_SUBTYPE_CCW_CRW 暴露。
如果存在与此子通道相关的挂起 CRW(例如报告通道路径状态变化的 CRW),读取此区域将返回该 CRW,否则返回全零。如果多个 CRW 处于挂起状态(可能包括链式 CRW),再次读取此区域将返回下一个 CRW,直到没有挂起的 CRW 并返回全零。这类似于 STORE CHANNEL REPORT WORD 的工作方式。
vfio-ccw 操作细节¶
vfio-ccw 遵循 vfio-pci 在 s390 平台上的做法,使用 vfio-iommu-type1 作为 vfio iommu 后端。
CCW 转换 API 一组用于进行 CCW 转换的 API(以 cp_ 开头)。用户空间程序传入的 CCW 使用其客户机物理内存地址进行组织。这些 API 会将 CCW 复制到内核空间,并通过用相应的宿主机物理地址更新客户机物理地址,组装一个可运行的内核通道程序。注意,即使对于直接访问的 CCW,我们也必须使用 IDAL(间接数据地址列表),因为引用的内存可以位于任何地方,包括 2G 以上。
vfio_ccw 设备驱动程序 该驱动程序利用 CCW 转换 API 并引入了 vfio_ccw,它是您想要直通的 I/O 子通道设备的驱动程序。vfio_ccw 实现了以下 vfio ioctl
VFIO_DEVICE_GET_INFO VFIO_DEVICE_GET_IRQ_INFO VFIO_DEVICE_GET_REGION_INFO VFIO_DEVICE_RESET VFIO_DEVICE_SET_IRQS
这提供了一个 I/O 区域,以便用户空间程序可以将通道程序传递给内核,在将它们发出到真实设备之前进行进一步的 CCW 转换。这还提供了 SET_IRQ ioctl 来设置事件通知程序,以异步方式通知用户空间程序 I/O 完成。
vfio-ccw 的使用并不限于 QEMU,而 QEMU 绝对是理解这些补丁如何工作的良好示例。这里有关于 QEMU 客户机触发的 I/O 请求将如何被处理的更多详细信息(不包含错误处理)。
解释
Q1-Q7:QEMU 端处理流程。
K1-K5:内核端处理流程。
- Q1.
在初始化期间获取 I/O 区域信息。
- Q2.
设置事件通知程序和处理程序以处理 I/O 完成。
... ...
- Q3.
拦截 ssch 指令。
- Q4.
将客户机通道程序和 ORB 写入 I/O 区域。
- K1.
从客户机复制到内核。
- K2.
将客户机通道程序转换为宿主机内核空间通道程序,该程序对真实设备而言变为可运行的。
- K3.
使用 QEMU 传入的 orb 中包含的必要信息,将 ccwchain 发出到设备。
- K4.
返回 ssch 条件码 (CC code)。
- Q5.
将条件码返回给客户机。
... ...
- K5.
中断处理程序获取 I/O 结果并将结果写入 I/O 区域。
- K6.
向 QEMU 发出信号以检索结果。
- Q6.
获取信号,事件处理程序从 I/O 区域读出结果。
- Q7.
为客户机更新 irb。
限制¶
当前的 vfio-ccw 实现侧重于仅支持实现 DASD/ECKD 设备的块设备功能(读/写)所需的的基本命令。某些命令在将来可能需要特殊处理,例如与路径分组相关的任何内容。
DASD 是一种存储设备。而 ECKD 是一种数据记录格式。有关 DASD 和 ECKD 的更多信息,可以在这里找到:https://en.wikipedia.org/wiki/Direct-access_storage_device https://en.wikipedia.org/wiki/Count_key_data
结合 QEMU 中的相应工作,我们现在可以使直通的 DASD/ECKD 设备在客户机中上线,并将其用作块设备。
当前的代码允许客户机通过 START SUBCHANNEL 启动通道程序,并发出 HALT SUBCHANNEL、CLEAR SUBCHANNEL 和 STORE SUBCHANNEL。
目前,所有的通道程序都会被预取(prefetch),而不管 ORB 中的 p 位设置如何。因此,不支持自修改通道程序。出于这个原因,IPL(初始程序加载)必须由用户空间/客户机程序作为特殊情况处理;从 QEMU 4.1 开始,这已经在 QEMU 的 s390-ccw bios 中实现。
vfio-ccw 仅支持经典(命令模式)通道 I/O。不支持传输模式(HPF)。
当前不支持 QDIO 子通道。除 DASD/ECKD 之外的经典设备可能可以工作,但尚未经过测试。
参考资料¶
ESA/s390 操作原理手册 (IBM Form. No. SA22-7832)
ESA/390 公共 I/O 设备命令手册 (IBM Form. No. SA22-7204)