vfio-ccw:基本基础设施¶
引言¶
在此,我们描述 Linux/s390 的 I/O 子通道设备的 vfio 支持。vfio-ccw 的动机是将子通道传递给虚拟机,而 vfio 是实现这一目标的方式。
与其它硬件架构不同,s390 定义了一种统一的 I/O 访问方法,即所谓的通道 I/O。它有自己的访问模式。
通道程序在单独的(协)处理器上异步运行。
通道子系统将直接访问通道程序中调用者指定的任何内存,即不涉及 iommu。
因此,当我们为这些设备引入 vfio 支持时,我们通过中介设备 (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 表格编号 SA22-7832)
实现简单模拟通道子系统的现有 QEMU 代码也可以作为很好的参考。它可以更容易地跟踪流程。qemu/hw/s390x/css.c
对于 vfio 中介设备框架: - VFIO 中介设备
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,并将其添加到中介总线。它是添加到 IOMMU 组和 vfio 组的 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 内核将该地址转换为进程虚拟地址,固定页面并在一步中将主机物理地址编程到硬件中。对于 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 填充 orb_area,并应使用虚拟子通道的 SCSW 填充 scsw_area。
irb_area 存储 I/O 结果。
ret_code 存储每次访问该区域的返回代码。可能出现以下值
0
操作成功。
-EOPNOTSUPP
ORB 指定的传输模式或 SCSW 指定的功能不是启动功能。
-EIO
发出请求时,设备未处于准备好接受请求的状态,或者发生了内部错误。
-EBUSY
子通道处于挂起或忙碌状态,或者请求已处于活动状态。
-EAGAIN
正在处理请求,调用者应重试。
-EACCES
发现用于 I/O 的通道路径未运行。
-ENODEV
发现设备未运行。
-EINVAL
orb 指定的链长度超过 255 个 ccw,或者发生了内部错误。
vfio-ccw 命令区域¶
vfio-ccw 命令区域用于接受来自用户空间的异步指令。
#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 请求时,子通道的状态为 pending 或 busy。
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 代码。
- Q5.
将 CC 代码返回给客户机。
... ...
- 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。
目前,所有通道程序都被预取,而不管 ORB 中的 p 位设置如何。因此,不支持自修改通道程序。因此,IPL 必须由用户空间/客户机程序作为特殊情况处理;这已在 QEMU 4.1 的 QEMU s390-ccw bios 中实现。
vfio-ccw 仅支持经典(命令模式)通道 I/O。不支持传输模式 (HPF)。
目前不支持 QDIO 子通道。DASD/ECKD 以外的经典设备可能会工作,但尚未经过测试。
参考¶
ESA/s390 操作原理手册(IBM 表格编号 SA22-7832)
ESA/390 通用 I/O 设备命令手册(IBM 表格编号 SA22-7204)