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 区域添加读/写回调,以将通道程序从中介设备传递到其父设备(真正的 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 中介设备
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 设备及其子中介设备¶
如上所述,我们通过中介设备实现 vfio-ccw。
通道 I/O 没有 IOMMU 硬件支持,因此物理 vfio-ccw 设备没有 IOMMU 级别的转换或隔离。
子通道 I/O 指令都是特权指令。 在处理 I/O 指令拦截时,vfio-ccw 具有软件策略和转换,通道程序在发送到硬件之前是如何编程的。
在此实现中,我们有两个驱动程序用于两种类型的设备
物理子通道设备的 vfio_ccw 驱动程序。 这是真实子通道设备的 I/O 子通道驱动程序。 它实现了一组回调并注册到中介设备框架作为父(物理)设备。 因此,中介设备为 vfio_ccw 提供了一个通用接口 (sysfs) 来创建中介设备。 然后可以通过 vfio_ccw 创建 vfio 中介设备并添加到中介总线。 它是添加到 IOMMU 组和 vfio 组的 vfio 设备。 vfio_ccw 还提供了一个 I/O 区域来接受来自用户空间的通道程序请求,并存储 I/O 中断结果供用户空间检索。 为了通知用户空间 I/O 完成,它提供了一个接口来设置 eventfd fd 以进行异步信号传递。
中介 vfio ccw 设备的 vfio_mdev 驱动程序。 这是由中介设备框架提供的。 它是 vfio_ccw 创建的中介设备的 vfio 设备驱动程序。 它实现了一组 vfio 设备驱动程序回调,将其自身添加到 vfio 组,并将其自身注册到中介设备框架作为中介设备驱动程序。 它使用 vfio iommu 后端,该后端使用现有的 map 和 unmap ioctl,但不是将它们编程到设备的 IOMMU 中,而是简单地存储转换以供以后的请求使用。 这意味着在 VM 中使用访客物理地址编程的设备可以使 vfio 内核将该地址转换为进程虚拟地址,固定页面并在一个步骤中用主机物理地址编程硬件。 对于中介设备,vfio iommu 后端不会在 VFIO_IOMMU_MAP_DMA ioctl 期间固定页面。 中介设备框架将仅在此操作中维护 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 子通道,并将物理设备(具有回调)注册到中介设备框架。 当 vfio_ccw 探测子通道设备时,它将设备指针和回调注册到中介设备框架。 将在 sysfs 中设备节点下为子通道设备创建与中介设备相关的文件节点,即“mdev_create”、“mdev_destroy”和“mdev_supported_types”。
创建一个中介 vfio ccw 设备。 使用“mdev_create”sysfs 文件,我们需要手动创建一个(并且在我们这里只有一个)中介设备。
vfio_mdev.ko 驱动中介 ccw 设备。 vfio_mdev 也是 vfio 设备驱动程序。 它将探测中介设备并将其添加到 iommu_group 和 vfio_group。 然后我们可以将中介设备传递给访客。
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
操作成功。
-EOPNOTSUPP
ORB 指定的传输模式或 SCSW 指定的函数不是启动函数。
-EIO
当设备未处于接受请求的就绪状态时发出请求,或者发生内部错误。
-EBUSY
子通道状态挂起或忙碌,或者请求已处于活动状态。
-EAGAIN
请求正在处理中,调用者应该重试。
-EACCES
发现用于 I/O 的通道路径无法运行。
-ENODEV
发现设备无法运行。
-EINVAL
orb 指定的链长于 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 一组 API(以 cp_ 开头)用于进行 CCW 转换。 用户空间程序传入的 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-bit 的设置如何。 因此,不支持自修改通道程序。 因此,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)