
Linux/s390 vfio-ccw 深度解析基于 vfio 中介设备框架的 Channel I/O 子通道直通方案【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxvfio-ccw 是 Linux 内核为 s390 架构实现的一套基于 vfio 与 vfio 中介设备mdev框架的 I/O 子通道直通基础设施它让 QEMU/KVM 客户机能够把真实的 Channel I/O 设备如 DASD/ECKD 磁带、磁盘直接透传给虚拟机使用。本文以内核仓库中的 vfio-ccw.rst 为骨架结合 drivers/s390/cio 下的源码实现完整讲解 vfio-ccw 的动机、CCW 访问模型、mdev 架构、四大 MMIO Region 的布局与返回码语义、通道程序翻译与 I/O 全流程以及当前实现的边界与限制帮助你理解并上手这套虚拟化透传方案。引言为什么 s390 需要 vfio-ccwLinux/s390 的 vfio 支持面向 I/O 子通道设备I/O subchannel devices展开其核心动机是把子通道透传给虚拟机而 vfio 是完成这种透传的手段。与其他硬件架构不同s390 定义了一套统一的 I/O 访问方法——Channel I/O它有两大独特访问模式通道程序在独立的协处理器上异步执行。CPU 发起 I/O 后即可继续执行非 I/O 指令直到被 I/O 中断打断。通道子系统会直接访问通道程序channel program中由调用方指定的任意内存即整个过程中没有 IOMMU 参与。正是因为没有硬件 IOMMUvfio-ccw 选择用中介设备mdev实现来落地将 vfio mdev 加入一个 IOMMU group从而使其能够被 vfio 框架管理同时为特殊的 vfio I/O region 提供读/写回调把通道程序从 mdev 传递给其父设备真实的 I/O 子通道设备由父设备完成进一步地址翻译并执行 I/O 指令。本文不打算逐一解释 s390 I/O 架构的每个细节相关权威参考资料包括s390 架构规范s390 Principles of Operation手册IBM 表单号 SA22-7832QEMU 中模拟通道子系统的现有实现qemu/hw/s390x/css.c可作为理解整体流程的辅助参考vfio 中介设备框架说明Documentation/driver-api/vfio-mediated-device.rst动机virtio-ccw 之外的直通需求在 s390 平台上经 QEMU/KVM 虚拟化的客户机通常只能看到通过 Virtio Over Channel I/Ovirtio-ccw 传输机制暴露的半虚拟化 virtio 设备。virtio-ccw 使得 virtio 设备能够通过操作系统处理通道设备的标准算法被发现。但这远远不够。对于绝大多数采用标准 Channel I/O 机制的 s390 设备我们同样需要把它们直通pass-through给 QEMU 虚拟机这包括没有 virtio 对应物的设备例如磁带机 tape drives具有客户机希望利用的特殊特性的设备。为了把设备透传给客户机vfio-ccw 选择与其它平台一致的统一接口——vfio。它通过 vfio 中介设备框架以及子通道设备驱动vfio_ccw实现针对通道设备的 vfio 支持。CCW 设备的访问模式从 CCW 到 SSCHs390 架构实现了所谓的通道子系统channel subsystem为物理挂载的设备提供统一视图。尽管 s390 硬件平台支持种类繁多的外设磁盘 DASD、磁带、通信控制器等它们都能通过定义良好的访问方法访问并且以统一的方式呈现 I/O 完成——I/O 中断I/O interruptions。所有 I/O 都必须使用通道命令字Channel Command WordCCW。CCW 是发给专用 I/O 通道处理器的一条指令通道程序channel program是由 I/O 通道子系统执行的一串 CCW 序列。要向外发出一个通道程序需要构造操作请求块ORBOperation Request Block用于向系统指明 CCW 的格式及其它控制信息。操作系统通过SSCHStart Sub-channel启动子通道指令通知 I/O 通道子系统开始执行通道程序此后中央处理器可自由执行非 I/O 指令直到被中断。I/O 完成结果由中断处理器以中断响应块IRBInterrupt Response Block的形式接收。把视角切回 vfio-ccw整个流程可概括为ORB 与通道程序在客户机内核中构建使用客户机物理地址。ORB 与通道程序传递给宿主机内核。宿主机内核把客户机物理地址翻译为真实地址并通过发出特权 Channel I/O 指令如 SSCH启动 I/O。通道程序在独立的处理器上异步执行。I/O 完成以 I/O 中断方式通知宿主机结果以 IRB 形式拷贝到用户空间再交回客户机。物理 vfio-ccw 设备与其子 mdev双层驱动架构vfio-ccw 通过 mdev 实现落地。由于Channel I/O 没有 IOMMU 硬件支持物理 vfio-ccw 设备没有 IOMMU 级别的地址翻译与隔离。子通道 I/O 指令全部是特权指令在处理 I/O 指令拦截interception时vfio-ccw 在通道程序被发送到硬件之前通过软件方式对通道程序的编程方式进行策略检查与翻译。该实现包含两类设备的两个驱动vfio_ccw驱动——物理子通道设备驱动它是真实子通道设备的 I/O 子通道驱动实现一组回调并作为父物理设备注册到 mdev 框架。mdev 框架由此为 vfio_ccw 提供创建 mdev 设备的通用 sysfs 接口。vfio_ccw 随后创建 vfio mdev 并加入中介总线该 vfio 设备会被加入 IOMMU group 与 vfio group。vfio_ccw 还提供一个 I/O region 接收来自用户空间的通道程序请求、保存 I/O 中断结果供用户空间读取并通过 eventfd 接口提供异步的 I/O 完成通知。vfio_mdev驱动——中介 vfio ccw 设备驱动由 mdev 框架提供是 vfio_ccw 所创建 mdev 的 vfio 设备驱动。它实现一组 vfio 设备驱动回调把自身加入 vfio group并注册到 mdev 框架作为 mdev 驱动。它使用一个 vfio iommu 后端复用现有的 map/unmap ioctl但并不把翻译结果编程进设备对应的 IOMMU而是仅保存翻译供后续请求使用。这意味着在 VM 中用客户机物理地址编程的设备可以让 vfio 内核一次性完成把该地址转换为进程虚拟地址 → 钉住页面 → 以主机物理地址编程硬件。对 mdev 而言vfio iommu 后端在VFIO_IOMMU_MAP_DMAioctl 期间不会钉住页面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 框架。在探测子通道设备时它把设备指针与回调注册给 mdev 框架为该子通道设备在 sysfs 下创建 mdev 相关文件节点mdev_create、mdev_destroy与mdev_supported_types。创建中介 vfio ccw 设备。使用mdev_createsysfs 文件手工创建一个就 vfio-ccw 场景而言且仅创建一个中介设备。vfio_mdev.ko驱动中介 ccw 设备。vfio_mdev 同时也是 vfio 设备驱动它会探测该 mdev 并将其加入iommu_group与vfio_group之后即可把该 mdev 透传给客户机。在源码中这三步分别对应 drivers/s390/cio/vfio_ccw_drv.c 中的vfio_ccw_sch_probe()注册struct vfio_ccw_parent并调用mdev_register_parent()、mdev 的 sysfs 创建流程以及vfio_ccw_mdev_probe()中的vfio_register_emulated_iommu_dev()调用。值得注意的是vfio_ccw_drv.c 的探测逻辑会检查pmcw-qf一旦发现 QDIO 子通道qf置位会直接返回-ENODEV并拒绝绑定这与文档QDIO 子通道目前不支持的限制完全一致。VFIO-CCW Regions四大 MMIO 区域详解vfio-ccw 驱动对外暴露 MMIO region用于接收用户空间请求并返回结果。所有 region 结构体的权威定义位于 include/uapi/linux/vfio_ccw.h设备侧的数据结构与回调在 drivers/s390/cio/vfio_ccw_private.h 中由struct vfio_ccw_region与struct vfio_ccw_regops描述region 的注册、偏移计算与读写分发则在 drivers/s390/cio/vfio_ccw_ops.c 中实现。vfio-ccw I/O region主 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需填入客户机的 ORBscsw_area需填入虚拟子通道Virtual Subchannel的 SCSWirb_area保存 I/O 结果ret_code保存每次访问该区域的返回码。该区域始终可用vfio_ccw_mdev_ioctl_get_region_info()对VFIO_CCW_CONFIG_REGION_INDEX返回sizeof(struct ccw_io_region)且READ | WRITE标志见 vfio_ccw_ops.c。在 vfio_ccw_ops.c 的写路径中用户空间写入 ORB/SCSW 后会触发VFIO_CCW_EVENT_IO_REQ事件进入设备状态机随后把region-ret_code返回给调用者。ret_code可能出现以下取值返回码含义0操作成功。-EOPNOTSUPPORB 指定了传输模式transport mode或 SCSW 指定的功能不是启动功能start function。-EIO在设备尚未处于可接收请求状态时发出了请求或发生内部错误。-EBUSY子通道处于 status pending 或忙状态或已有请求正在进行。-EAGAIN请求正在处理中调用方应重试。-EACCES用于 I/O 的通道路径被判定为不可操作。-ENODEV设备被判定为不可操作。-EINVALORB 指定的链chain超过 255 个 CCW或发生内部错误。vfio-ccw cmd region异步指令区域该区域用于接收来自用户空间的异步指令通过VFIO_REGION_SUBTYPE_CCW_ASYNC_CMD类型暴露。目前CLEAR SUBCHANNEL清子通道与HALT SUBCHANNEL停子通道使用该区域#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;command指定要执行的指令ret_code保存每次访问该区域的返回码返回码含义0操作成功。-ENODEV设备被判定为不可操作。-EINVAL指定了 halt/clear 之外的命令。-EIO在设备尚未处于可接收请求状态时发出了请求。-EAGAIN请求正在处理中调用方应重试。-EBUSY处理 halt 请求时子通道处于 status pending 或忙状态。在 vfio_ccw_async.c 的写路径中写入command后触发VFIO_CCW_EVENT_ASYNC_REQ事件状态机在 vfio_ccw_fsm.c 中根据command分发到fsm_do_halt()执行hsch()或fsm_do_clear()执行csch()并把 SSCH/HSCH/CSCH 指令的 CC code0/1/2/3映射为对应的负错误码。例如fsm_io_helper()中CC 0 置SCSW_ACTL_START_PEND并进入VFIO_CCW_STATE_CP_PENDINGCC 1/2 返回-EBUSYCC 3 根据路径状态返回-EACCES或-ENODEV。vfio-ccw schib region子通道信息区域该区域用于向用户空间返回子通道信息块Subchannel-Information BlockSCHIB数据通过VFIO_REGION_SUBTYPE_CCW_SCHIB类型暴露struct ccw_schib_region { #define SCHIB_AREA_SIZE 52 __u8 schib_area[SCHIB_AREA_SIZE]; } __packed;读取该区域会触发向关联硬件发出 STORE SUBCHANNEL存储子通道指令。用户空间可通过读取此区域获取子通道当前状态例如用于查询 I/O 是否完成、路径状态等。vfio-ccw crw region通道报告字区域该区域用于向用户空间返回通道报告字Channel Report WordCRW数据通过VFIO_REGION_SUBTYPE_CCW_CRW类型暴露struct ccw_crw_region { __u32 crw; __u32 pad; } __packed;读取该区域时若存在与该子通道相关的待处理 CRW例如报告通道路径状态变化的 CRW则返回一个 CRW若无则返回全零如果多个 CRW 待处理包括可能的链式 CRW再次读取会返回下一个直到没有更多 CRW 待处理并返回零。这与 STORE CHANNEL REPORT WORD 的工作方式类似。在 vfio_ccw_chp.c 中实现了该区域的读写而 vfio_ccw_drv.c 的vfio_ccw_queue_crw()会在通道路径事件如CHP_OFFLINE报告CRW_ERC_PERRN、CHP_ONLINE报告CRW_ERC_INIT时构造 CRW 加入链表并调度crw_workvfio_ccw_crw_todo()在链表非空且注册了crw_triggereventfd 时发送信号通知用户空间。区域的能力通告机制I/O region 之外的三个区域async cmd、schib、crw都通过 vfio 的 capability chain 机制通告给用户空间vfio_ccw_mdev_ioctl_get_region_info()会为每个额外区域填充vfio_region_info_cap_typeVFIO_REGION_INFO_CAP_TYPE设置type VFIO_REGION_TYPE_CCW与对应的subtype并通过vfio_info_add_capability()附加见 vfio_ccw_ops.c。这些区域并非始终存在而是在 mdev 打开vfio_ccw_mdev_open_device()时按需注册见 vfio_ccw_ops.c关闭时统一释放。vfio-ccw 操作细节通道程序翻译与 I/O 全流程vfio-ccw 遵循 vfio-pci 在 s390 平台上的做法使用vfio-iommu-type1作为 vfio iommu 后端。CCW 翻译 APIcp_系列一组以cp_为前缀的 API 负责 CCW 翻译实现在 drivers/s390/cio/vfio_ccw_cp.c接口声明见 drivers/s390/cio/vfio_ccw_cp.h用户空间程序传入的 CCW 以客户机物理内存地址组织这些 API 将 CCW 拷贝进内核空间通过把客户机物理地址更新为对应的主机物理地址组装出可在内核运行、可交给真实设备执行的通道程序关键约束即使是直接访问direct-access类 CCW也必须使用 IDAL间接数据地址列表因为被引用的内存可能位于任意位置包括 2G 以上。struct channel_program管理整个翻译过程其内部维护ccwchain_list翻译后的链式 CCW 列表、orb、guest_cp客户机通道程序的副本等字段链长上限为CCWCHAIN_LEN_MAX256链数量上限为CCWCHAIN_COUNT_MAX16。翻译涉及按需调用vfio_pin_pages()钉住客户机页面并在 I/O 完成后由vfio_ccw_sch_io_todo()通过cp_free()释放。vfio_ccw 设备驱动与 vfio ioctlvfio_ccw驱动利用 CCW 翻译 API是被透传 I/O 子通道设备的驱动实现了以下 vfio ioctl见 vfio_ccw_ops.c 的vfio_ccw_mdev_ioctl()VFIO_DEVICE_GET_INFO VFIO_DEVICE_GET_IRQ_INFO VFIO_DEVICE_GET_REGION_INFO VFIO_DEVICE_RESET VFIO_DEVICE_SET_IRQSVFIO_DEVICE_GET_INFO返回VFIO_DEVICE_FLAGS_CCW | VFIO_DEVICE_FLAGS_RESET标志、region 数量VFIO_CCW_NUM_REGIONS num_regions与 IRQ 数量VFIO_CCW_NUM_IRQS通过 I/O region用户空间程序可以把通道程序传给内核在内核完成 CCW 翻译后再下发给真实设备通过VFIO_DEVICE_SET_IRQS可设置事件通知器event notifier以异步方式通知用户空间程序 I/O 完成。该实现支持三个 IRQ 索引——VFIO_CCW_IO_IRQ_INDEXI/O 完成、VFIO_CCW_CRW_IRQ_INDEXCRW 事件、VFIO_CCW_REQ_IRQ_INDEX设备请求/移除每个都支持 eventfd 绑定VFIO_IRQ_SET_DATA_EVENTFD见 vfio_ccw_ops.c。一次 I/O 请求的完整旅程QEMU 视角vfio-ccw 的使用并不局限于 QEMU但 QEMU 是理解这些补丁如何协同工作的绝佳示例。下面是一次由 QEMU 客户机触发的 I/O 请求的处理过程不含错误处理路径其中 Q1–Q7 为 QEMU 侧流程K1–K6 为内核侧流程Q1. 初始化时获取 I/O region 信息。 Q2. 设置事件通知器event notifier及其处理器用于处理 I/O 完成。 Q3. 拦截intercept一条 ssch 指令。 Q4. 把客户机通道程序与 ORB 写入 I/O region。 K1. 从客户机拷贝到内核copy_from_user 到 io_region触发 IO_REQ 事件。 K2. 把客户机通道程序翻译为宿主机内核空间、可被真实设备执行的通道程序cp_* 系列 API 页面钉住。 K3. 利用 QEMU 传入的 ORB 中所含必要信息把 ccw 链下发给设备fsm_io_helper 执行 ssch()。 K4. 返回 ssch 的 CC code。 Q5. 把 CC code 返回给客户机。 K5. 中断处理器取得 I/O 结果并写入 I/O regionirb_area若为最终中断则释放通道程序。 K6. 信号通知 QEMU 取回结果eventfd_signal(io_trigger)。 Q6. 收到信号事件处理器从 I/O region 读出结果。 Q7. 为客户机更新 irb。内核侧关键实现点vfio_ccw_mdev_write_io_region()vfio_ccw_ops.c完成 K1通过copy_from_user把用户空间数据写入ccw_io_region并以mutex_trylock保证并发安全拿不到锁返回-EAGAIN随后触发VFIO_CCW_EVENT_IO_REQfsm_io_helper()vfio_ccw_fsm.c完成 K2–K4先cp_get_orb()取得翻译后的 ORB再执行ssch(sch-schid, orb)并按 CC code 映射返回码vfio_ccw_sch_io_todo()vfio_ccw_drv.c完成 K5–K6在 workqueue 中把irb拷入io_region-irb_area在通道程序最终结束时把状态机恢复为VFIO_CCW_STATE_IDLE最后eventfd_signal(private-io_trigger)唤醒 QEMU状态机本身由 vfio_ccw_fsm.c 中的跳转表jumptable驱动状态包括NOT_OPER / STANDBY / IDLE / CP_PROCESSING / CP_PENDING事件包括NOT_OPER / IO_REQ / INTERRUPT / ASYNC_REQ / OPEN / CLOSE枚举定义见 vfio_ccw_private.h。当前实现边界与限制理解以下限制有助于判断 vfio-ccw 适用的场景仅支持实现块设备功能读/写所需的 DASD/ECKD 设备基本命令。部分命令未来可能需要特殊处理例如任何与路径分组path grouping相关的内容。DASD 是一种存储设备ECKD 是数据记录格式。配合 QEMU 侧的对应工作目前可以把透传的 DASD/ECKD 设备在客户机中上线online并作为块设备使用。当前代码允许客户机通过START SUBCHANNEL启动通道程序并可发出HALT SUBCHANNEL、CLEAR SUBCHANNEL与STORE SUBCHANNEL。所有通道程序都会被预取prefetch无论 ORB 中的 p-bit 如何设置。因此不支持自修改通道程序self modifying channel programs。基于此IPL初始程序加载必须由用户空间/客户机程序作为特殊情况处理——自 QEMU 4.1 起这一逻辑已在 QEMU 的 s390-ccw BIOS 中实现。vfio-ccw 仅支持经典命令模式command modeChannel I/O不支持传输模式transport mode即 HPF。QDIO 子通道目前不受支持。DASD/ECKD 之外的经典设备可能可以工作但尚未经过测试。源码中vfio_ccw_sch_probe()对 QDIO 子通道直接返回-ENODEV见 vfio_ccw_drv.c。配置与构建提示vfio-ccw 由CONFIG_VFIO_CCW编译选项控制在 drivers/s390/cio/Makefile 中以obj-$(CONFIG_VFIO_CCW) vfio_ccw.o方式构建。启用该选项会得到vfio_ccw子通道驱动与vfio_ccw_mdevmdev 驱动同时依赖 vfio 与 vfio 中介设备mdev框架。模块加载后运维流程为确认物理 I/O 子通道被vfio_ccw驱动绑定可通过 sysfs 的驱动绑定接口将子通道从原 cio 驱动解绑并绑定到 vfio_ccw在子通道设备节点下查看mdev_supported_types确认存在名为ioI/O subchannel (Non-QDIO)的受支持类型该类型名与显示名定义于 vfio_ccw_drv.c通过mdev_createsysfs 文件手工创建且仅创建一个中介设备在 QEMU 侧把该 mdev位于/dev/vfio/下的设备节点作为透传设备附加到客户机。参考ESA/s390 Principles of Operation手册IBM 表单号 SA22-7832ESA/390 Common I/O Device Commands手册IBM 表单号 SA22-7204Documentation/arch/s390/cds.rsts390 通道子系统相关文档Documentation/driver-api/vfio.rstvfio 框架说明Documentation/driver-api/vfio-mediated-device.rstvfio 中介设备框架说明include/uapi/linux/vfio_ccw.hvfio-ccw 用户空间 ABI 定义drivers/s390/cio/vfio_ccw_drv.c、drivers/s390/cio/vfio_ccw_ops.c、drivers/s390/cio/vfio_ccw_fsm.c、drivers/s390/cio/vfio_ccw_cp.c、drivers/s390/cio/vfio_ccw_async.c、drivers/s390/cio/vfio_ccw_chp.c核心实现【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考