Virtio 系列一框架概览Virtual I/O Device (VIRTIO) Version 1.1概述上一篇从整体框架解释了 virtio 的三列结构Linux guest 侧、virtio interface、VMM/device 侧。本文进入协议控制面说明一个 virtio 设备从被发现到可用driver 与 device 必须完成哪些协议动作以及这些动作在 Linux 与 QEMU 代码中大致对应到哪里。本文以 VIRTIO 1.1 modern device 为主不展开 legacy 设备差异。代码示例主要使用 MMIO transport因为 MMIO 的寄存器路径最直观PCI、CCW 的承载方式不同但背后的 status、feature、config、virtqueue 初始化语义是一致的。本文只讲初始化和控制面。virtqueue 的 descriptor table、available ring、used ring以及完整数据路径放到下一篇展开。1. 初始化要解决什么问题virtio 设备初始化的目标不是“加载一个驱动”这么简单而是让 driver 和 device 对以下信息达成一致这个设备是什么类型双方共同支持哪些 feature设备处于哪个 status 阶段device-specific config 如何读取virtqueue 有几条、每条 queue 如何分配和启用什么时候可以正式开始收发 buffer。这些内容构成 virtio 的控制面。数据面虽然依赖 virtqueue但在初始化阶段只需要完成 queue 的发现、分配和 ready 标记不需要立即理解 ring 内部结构。2. Linux guest 侧从 transport probe 到 frontend probe本节配套关系图virtio-2-guest-probe-flow.drawio。在 Linux guest 侧virtio 初始化不是从具体前端驱动直接开始的。这里有两条线最终汇合到virtio_bustransport driver 发现 guest 可见的 virtio 设备并注册virtio_devicefrontend driver 注册自己支持的virtio_driverLinux driver core 在virtio_bus上完成二者匹配。platform bus detects virtio-mmio device - virtio_mmio_probe() - register_virtio_device() - device_add() frontend module init - register_virtio_driver() - driver_register() virtio device virtio driver on virtio_bus - virtio_bus.match - virtio_bus.probe virtio_dev_probe() - drv-probe(dev) - frontend probe, such as virtcons_probe()virtio_mmio_probe()属于 transport driver。它发现的是 guest 可见的 MMIO transport endpoint并构造struct virtio_device。/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticintvirtio_mmio_probe(structplatform_device*pdev){structvirtio_mmio_device*vm_dev;vm_devkzalloc(sizeof(*vm_dev),GFP_KERNEL);...vm_dev-vdev.dev.parentpdev-dev;vm_dev-vdev.configvirtio_mmio_config_ops;vm_dev-basedevm_platform_ioremap_resource(pdev,0);vm_dev-vdev.id.devicereadl(vm_dev-baseVIRTIO_MMIO_DEVICE_ID);vm_dev-vdev.id.vendorreadl(vm_dev-baseVIRTIO_MMIO_VENDOR_ID);rcregister_virtio_device(vm_dev-vdev);...}以 Linux 6.5.3 中的 DTS 为例下面每个virtio...节点都会生成一个 platform device并触发一次virtio_mmio_probe()。/* Linux 6.5.3: arch/arc/boot/dts/haps_hs.dts节选 */ virtio0: virtiof0100000 { compatible virtio,mmio; reg 0xf0100000 0x2000; interrupts 31; }; virtio1: virtiof0102000 { compatible virtio,mmio; reg 0xf0102000 0x2000; interrupts 32; };如果 VMM 暴露两个独立的 virtio-console/virtio-serial 设备通常会有两个独立的 transport endpoint因此会触发两次virtio_mmio_probe()并注册两个virtio_device。这里的“VMM 暴露”指 VMM 把某个 virtio 设备放进 guest 可见的虚拟硬件拓扑中guest OS 启动后再去发现它。但如果是一个 virtio-serial 设备启用了 multiport capability在同一个 virtio 设备下提供多个 port则通常只有一个 transport endpoint、一次virtio_mmio_probe()多个 port 是该前端驱动内部再根据 config/control queue 建立出来的。register_virtio_device()会把设备挂到virtio_bus上。真正触发 virtio driver 匹配的是device_add()。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */intregister_virtio_device(structvirtio_device*dev){dev-dev.busvirtio_bus;device_initialize(dev-dev);...virtio_reset_device(dev);virtio_add_status(dev,VIRTIO_CONFIG_S_ACKNOWLEDGE);/* * device_add() causes the bus infrastructure to look for a matching * driver. */errdevice_add(dev-dev);...}另一侧具体前端驱动会注册struct virtio_driver。以 virtio-console 为例它声明支持的 device id、feature table 和自己的业务 probe然后通过register_virtio_driver()挂到同一个virtio_bus。/* Linux 6.5.3: drivers/char/virtio_console.c关键路径摘录 */staticconststructvirtio_device_idid_table[]{{VIRTIO_ID_CONSOLE,VIRTIO_DEV_ANY_ID},{0},};staticstructvirtio_drivervirtio_console{.feature_tablefeatures,.feature_table_sizeARRAY_SIZE(features),.driver.nameKBUILD_MODNAME,.id_tableid_table,.probevirtcons_probe,.removevirtcons_remove,.config_changedconfig_intr,};staticint__initvirtio_console_init(void){...errregister_virtio_driver(virtio_console);...}register_virtio_driver()的关键动作是把该 driver 绑定到virtio_bus然后交给 Linux driver core 注册。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */intregister_virtio_driver(structvirtio_driver*driver){driver-driver.busvirtio_bus;returndriver_register(driver-driver);}virtio_device和virtio_driver的注册顺序并不固定。前端驱动通过register_virtio_driver()进入 driver core 的virtio_bustransport 侧随后通过device_add()把virtio_device挂到同一个 bus 上时driver core 会用virtio_bus.match查找已有 driver。反过来如果 device 先注册后续 driver 注册时也会触发同样的匹配逻辑。匹配依据是virtio_device_id。匹配成功后Linux driver core 调用.probe virtio_dev_probe。这个 probe 是 virtio core 的通用 probe它完成 feature negotiation、status 更新等通用流程最后才调用具体前端驱动的drv-probe(dev)。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */staticstructbus_typevirtio_bus{.namevirtio,.matchvirtio_dev_match,.probevirtio_dev_probe,.removevirtio_dev_remove,};因此本文后面讨论的virtio_dev_probe()不是 transport probe也不是某个具体设备的业务 probe而是 Linux virtio core 在virtio bus匹配成功后执行的通用初始化入口。3. 协议中的几个核心对象协议对象作用Device ID标识设备类型例如 net、blk、consoleDevice statusdriver 与 device 之间的初始化状态机Feature bits双方能力协商决定后续行为Device config spacedevice-specific 配置区例如 MAC、capacity、console portsVirtqueue configqueue 数量、大小、地址、ready 状态Notificationqueue 可用或状态变化时的事件提示其中最关键的是status和feature negotiation。如果这两步没有完成driver 不能假设 device 已经进入可用状态device 也不能随意消费 queue 中的 buffer。4. Device status初始化状态机virtio 规范定义了一组 device status bit。它们不是普通日志状态而是 driver 和 device 共同观察的协议状态。Status bit含义ACKNOWLEDGEdriver 已经发现该 virtio deviceDRIVERdriver 知道如何驱动该 deviceFEATURES_OKdriver 已完成 feature 协商DRIVER_OKdriver 完成初始化device 可以正常工作DEVICE_NEEDS_RESETdevice 需要 resetFAILEDdriver 初始化失败典型初始化顺序如下reset device - ACKNOWLEDGE - DRIVER - read device features - write accepted driver features - FEATURES_OK - setup virtqueues - device-specific setup - DRIVER_OK在 Linux 6.5.3 中ACKNOWLEDGE已经在register_virtio_device()中设置当virtio_device和virtio_driver在virtio_bus上匹配成功后driver core 进入virtio_dev_probe()。这个函数是 virtio bus 的共享 probe负责继续推进DRIVER、feature negotiation、FEATURES_OK和DRIVER_OK等通用初始化步骤。status 的具体读写不由前端驱动直接操作寄存器而是通过dev-config间接进入 transport driver。以virtio-mmio为例最终会落到VIRTIO_MMIO_STATUS等 MMIO 寄存器。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */staticintvirtio_dev_probe(structdevice*_d){interr,i;structvirtio_device*devdev_to_virtio(_d);structvirtio_driver*drvdrv_to_virtio(dev-dev.driver);u64 device_features;u64 driver_features;virtio_add_status(dev,VIRTIO_CONFIG_S_DRIVER);/* 读取 device features并与 driver 支持的 features 做交集 */device_featuresdev-config-get_features(dev);...dev-featuresdriver_featuresdevice_features;errdev-config-finalize_features(dev);if(err)gotoerr;/* FEATURES_OK 是协议确认点device 可以拒绝不支持的组合 */errvirtio_features_ok(dev);if(err)gotoerr;/* 调用匹配到的具体前端驱动 probe例如 virtcons_probe() */errdrv-probe(dev);if(err)gotoerr;/* probe 成功后进入可用状态 */if(!(dev-config-get_status(dev)VIRTIO_CONFIG_S_DRIVER_OK))virtio_device_ready(dev);...}这段代码对应第 2 节中的virtio_bus.probe virtio_dev_probe()。它不是 transport probe也不是具体设备的业务 probe它是 virtio core 在 bus match 之后执行的通用初始化入口。具体前端驱动的初始化入口由drv-probe(dev)触发例如 virtio-console 对应virtcons_probe()。5. Feature negotiation不是单方面声明能力feature negotiation 的核心规则是device 暴露自己支持的 feature bitsdriver 从中选择自己理解且愿意启用的子集driver 把选择结果写回 devicedriver 设置FEATURES_OKdriver 再读回 status确认 device 是否接受该组合。这不是“driver 支持什么就打开什么”而是双方共同确认。feature 决定后续协议行为例如是否启用 modern layout、是否支持某个 device-specific 能力、是否支持 packed queue 等。Linux 的通用流程在 virtio core 中具体 feature bit 的读写由 transport driver 实现。以 MMIO 为例transport 需要从DEVICE_FEATURES相关寄存器读取 device features并把 driver features 写回DRIVER_FEATURES相关寄存器。/* Linux: drivers/virtio/virtio_mmio.c关键路径摘录 */staticu64vm_get_features(structvirtio_device*vdev){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vdev);/* 通过 MMIO 寄存器读取 device 暴露的 feature bits */writel(0,vm_dev-baseVIRTIO_MMIO_DEVICE_FEATURES_SEL);featuresreadl(vm_dev-baseVIRTIO_MMIO_DEVICE_FEATURES);...}staticintvm_finalize_features(structvirtio_device*vdev){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vdev);/* 把 driver 接受的 feature bits 写回 device */writel(0,vm_dev-baseVIRTIO_MMIO_DRIVER_FEATURES_SEL);writel((u32)vdev-features,vm_dev-baseVIRTIO_MMIO_DRIVER_FEATURES);...}QEMU 侧的 MMIO endpoint 则接收这些寄存器访问并更新 VMM 中的 virtio device 状态。/* QEMU: hw/virtio/virtio-mmio.c关键路径摘录 */staticvoidvirtio_mmio_write(void*opaque,hwaddr offset,uint64_tvalue,unsignedsize){VirtIOMMIOProxy*proxyopaque;VirtIODevice*vdevvirtio_bus_get_device(proxy-bus);switch(offset){caseVIRTIO_MMIO_DRIVER_FEATURES_SEL:proxy-guest_features_selvalue;break;caseVIRTIO_MMIO_DRIVER_FEATURES:/* 保存 driver 接受的 features */virtio_set_features(vdev,value);break;caseVIRTIO_MMIO_STATUS:/* status 写入会驱动 device 状态机变化 */virtio_set_status(vdev,value);break;...}}前端驱动声明自己支持的 featureLinux virtio core 负责计算双方交集并推进协商流程transport driver 再把 feature 读写转换为 MMIO、PCI 或 CCW 访问。VMM 侧收到这些 guest-visible register/config 访问后更新自己的 virtio device 状态。frontend driver provides feature_table - Linux virtio core selects accepted features - transport driver reads/writes guest-visible registers - VMM transport endpoint - VMM virtio core records features/status6. Device config space设备类型相关的控制信息device config space 保存 device-specific 配置。它不是每个字段都重新发明一套访问 API也不是从前端驱动视角看到的一堆散乱寄存器更准确地说它是一块由 virtio 协议按设备类型定义布局的配置区。virtio-net 的 MAC、MTU、queue pair 数virtio-blk 的 capacity、block sizevirtio-console / virtio-serial 的端口能力其他设备自己的配置结构。以 virtio-console 为例guest 前端和 VMM/QEMU 后端都使用同一份协议布局。Linux 侧定义在 UAPI 头文件中/* Linux 6.5.3: include/uapi/linux/virtio_console.h节选 */structvirtio_console_config{/* colums of the screens */__virtio16 cols;/* rows of the screens */__virtio16 rows;/* max. number of ports this device can hold */__virtio32 max_nr_ports;/* emergency write register */__virtio32 emerg_wr;}__attribute__((packed));QEMU 侧也有对应的 packed layout/* QEMU: include/standard-headers/linux/virtio_console.h节选 */structvirtio_console_config{__virtio16 cols;__virtio16 rows;__virtio32 max_nr_ports;__virtio32 emerg_wr;}QEMU_PACKED;因此config space 首先是协议定义的结构体布局不同设备的 config 结构不同字段是否有效还可能受 feature bit 约束。前端驱动通过virtio_cread*()这类 helper 按字段 offset 读取 device config。PCI、MMIO、CCW 只是这块 config space 的不同承载形式不改变字段布局本身。/* Linux 6.5.3: drivers/net/virtio_net.c关键路径摘录 */staticintvirtnet_probe(structvirtio_device*vdev){...if(virtio_has_feature(vdev,VIRTIO_NET_F_MAC))virtio_cread_bytes(vdev,offsetof(structvirtio_net_config,mac),addr,ETH_ALEN);...}这说明 config space 属于协议控制面但具体读取动作通常出现在前端驱动初始化逻辑中。前端驱动负责解释字段语义transport driver 负责屏蔽底层访问方式。7. Virtqueue setup初始化阶段只建立通道virtqueue 是数据路径核心但初始化阶段只做三类事情选择 queue分配并写入 ring 的物理地址标记 queue ready。以 MMIO modern device 为例driver 会通过 queue 相关寄存器告诉 device当前配置哪条 queuequeue size 是多少descriptor table 地址在哪里driver/available area 与 device/used area 地址在哪里这条 queue 是否 ready。Linux MMIO transport 的 queue 初始化大致如下/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticstructvirtqueue*vm_setup_vq(structvirtio_device*vdev,unsignedintindex,void(*callback)(structvirtqueue*vq),constchar*name,bool ctx){...writel(index,vm_dev-baseVIRTIO_MMIO_QUEUE_SEL);/* 分配 vring并把地址写入 MMIO queue config 寄存器 */vqvring_create_virtqueue(index,num,VIRTIO_MMIO_VRING_ALIGN,vdev,true,true,ctx,notify,callback,name);addrvirtqueue_get_desc_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_DESC_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_DESC_HIGH);addrvirtqueue_get_avail_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_AVAIL_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_AVAIL_HIGH);addrvirtqueue_get_used_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_USED_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_USED_HIGH);writel(1,vm_dev-baseVIRTIO_MMIO_QUEUE_READY);...}本节只关注初始化阶段的 queue 配置driver 把 queue 的地址和 ready 状态交给 device。descriptor table、available ring、used ring 的内部格式以及 buffer 如何放入和取出 queue将在下一篇 virtqueue 数据路径中展开。8. Notification初始化结束后的事件入口当DRIVER_OK设置完成后设备进入可用状态。此后数据路径的典型动作是driver 把 buffer 描述放入 virtqueue - driver notify device - device/backend 处理 queue - device interrupt drivernotification 本身不携带数据数据仍然在 virtqueue 中。对 MMIO transport 来说notify 通常表现为向QUEUE_NOTIFY寄存器写入 queue index。/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticboolvm_notify(structvirtqueue*vq){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vq-vdev);/* We write the queues selector into the notification register to * signal the other end */writel(vq-index,vm_dev-baseVIRTIO_MMIO_QUEUE_NOTIFY);returntrue;}QEMU 的 MMIO endpoint 收到该写入后会转入通用 virtio queue dispatch再由具体 backend 处理。/* QEMU: hw/virtio/virtio-mmio.c关键路径摘录 */staticvoidvirtio_mmio_write(void*opaque,hwaddr offset,uint64_tvalue,unsignedsize){...switch(offset){caseVIRTIO_MMIO_QUEUE_NOTIFY:/* value 是 guest 通知的 queue index */virtio_queue_notify(vdev,value);break;...}}因此notify 是 transport-specific 的入口但它触发的是 virtio 通用 queue 处理逻辑。真正的 buffer 格式和 ring 更新规则属于下一篇的数据路径主题。9. 小结virtio 初始化可以概括为一条控制面流水线discover device - reset/status - feature negotiation - config space read/write - virtqueue setup - DRIVER_OK - notify/interrupt driven data path从源码角度看这条流水线分布在三类代码中Linux virtio core 组织通用状态机和 feature 协商Linux transport driver 完成 PCI/MMIO/CCW 的具体寄存器或配置访问VMM transport endpoint 和 virtio core 保存状态并把 queue 事件分发给具体 device backend。理解这一层后再进入 virtqueue 数据结构就会更自然descriptor table、available ring、used ring 不是孤立结构而是初始化完成后由双方共同使用的数据通道。下一篇将继续讨论 virtqueue 与数据路径包括 split virtqueue 的 descriptor table、avail ring、used ring、Linuxvirtqueue_add_*()/virtqueue_kick()路径以及 QEMU backend 如何取出并完成请求。
Virtio(二):协议基础与设备初始化
发布时间:2026/7/12 3:38:23
Virtio 系列一框架概览Virtual I/O Device (VIRTIO) Version 1.1概述上一篇从整体框架解释了 virtio 的三列结构Linux guest 侧、virtio interface、VMM/device 侧。本文进入协议控制面说明一个 virtio 设备从被发现到可用driver 与 device 必须完成哪些协议动作以及这些动作在 Linux 与 QEMU 代码中大致对应到哪里。本文以 VIRTIO 1.1 modern device 为主不展开 legacy 设备差异。代码示例主要使用 MMIO transport因为 MMIO 的寄存器路径最直观PCI、CCW 的承载方式不同但背后的 status、feature、config、virtqueue 初始化语义是一致的。本文只讲初始化和控制面。virtqueue 的 descriptor table、available ring、used ring以及完整数据路径放到下一篇展开。1. 初始化要解决什么问题virtio 设备初始化的目标不是“加载一个驱动”这么简单而是让 driver 和 device 对以下信息达成一致这个设备是什么类型双方共同支持哪些 feature设备处于哪个 status 阶段device-specific config 如何读取virtqueue 有几条、每条 queue 如何分配和启用什么时候可以正式开始收发 buffer。这些内容构成 virtio 的控制面。数据面虽然依赖 virtqueue但在初始化阶段只需要完成 queue 的发现、分配和 ready 标记不需要立即理解 ring 内部结构。2. Linux guest 侧从 transport probe 到 frontend probe本节配套关系图virtio-2-guest-probe-flow.drawio。在 Linux guest 侧virtio 初始化不是从具体前端驱动直接开始的。这里有两条线最终汇合到virtio_bustransport driver 发现 guest 可见的 virtio 设备并注册virtio_devicefrontend driver 注册自己支持的virtio_driverLinux driver core 在virtio_bus上完成二者匹配。platform bus detects virtio-mmio device - virtio_mmio_probe() - register_virtio_device() - device_add() frontend module init - register_virtio_driver() - driver_register() virtio device virtio driver on virtio_bus - virtio_bus.match - virtio_bus.probe virtio_dev_probe() - drv-probe(dev) - frontend probe, such as virtcons_probe()virtio_mmio_probe()属于 transport driver。它发现的是 guest 可见的 MMIO transport endpoint并构造struct virtio_device。/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticintvirtio_mmio_probe(structplatform_device*pdev){structvirtio_mmio_device*vm_dev;vm_devkzalloc(sizeof(*vm_dev),GFP_KERNEL);...vm_dev-vdev.dev.parentpdev-dev;vm_dev-vdev.configvirtio_mmio_config_ops;vm_dev-basedevm_platform_ioremap_resource(pdev,0);vm_dev-vdev.id.devicereadl(vm_dev-baseVIRTIO_MMIO_DEVICE_ID);vm_dev-vdev.id.vendorreadl(vm_dev-baseVIRTIO_MMIO_VENDOR_ID);rcregister_virtio_device(vm_dev-vdev);...}以 Linux 6.5.3 中的 DTS 为例下面每个virtio...节点都会生成一个 platform device并触发一次virtio_mmio_probe()。/* Linux 6.5.3: arch/arc/boot/dts/haps_hs.dts节选 */ virtio0: virtiof0100000 { compatible virtio,mmio; reg 0xf0100000 0x2000; interrupts 31; }; virtio1: virtiof0102000 { compatible virtio,mmio; reg 0xf0102000 0x2000; interrupts 32; };如果 VMM 暴露两个独立的 virtio-console/virtio-serial 设备通常会有两个独立的 transport endpoint因此会触发两次virtio_mmio_probe()并注册两个virtio_device。这里的“VMM 暴露”指 VMM 把某个 virtio 设备放进 guest 可见的虚拟硬件拓扑中guest OS 启动后再去发现它。但如果是一个 virtio-serial 设备启用了 multiport capability在同一个 virtio 设备下提供多个 port则通常只有一个 transport endpoint、一次virtio_mmio_probe()多个 port 是该前端驱动内部再根据 config/control queue 建立出来的。register_virtio_device()会把设备挂到virtio_bus上。真正触发 virtio driver 匹配的是device_add()。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */intregister_virtio_device(structvirtio_device*dev){dev-dev.busvirtio_bus;device_initialize(dev-dev);...virtio_reset_device(dev);virtio_add_status(dev,VIRTIO_CONFIG_S_ACKNOWLEDGE);/* * device_add() causes the bus infrastructure to look for a matching * driver. */errdevice_add(dev-dev);...}另一侧具体前端驱动会注册struct virtio_driver。以 virtio-console 为例它声明支持的 device id、feature table 和自己的业务 probe然后通过register_virtio_driver()挂到同一个virtio_bus。/* Linux 6.5.3: drivers/char/virtio_console.c关键路径摘录 */staticconststructvirtio_device_idid_table[]{{VIRTIO_ID_CONSOLE,VIRTIO_DEV_ANY_ID},{0},};staticstructvirtio_drivervirtio_console{.feature_tablefeatures,.feature_table_sizeARRAY_SIZE(features),.driver.nameKBUILD_MODNAME,.id_tableid_table,.probevirtcons_probe,.removevirtcons_remove,.config_changedconfig_intr,};staticint__initvirtio_console_init(void){...errregister_virtio_driver(virtio_console);...}register_virtio_driver()的关键动作是把该 driver 绑定到virtio_bus然后交给 Linux driver core 注册。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */intregister_virtio_driver(structvirtio_driver*driver){driver-driver.busvirtio_bus;returndriver_register(driver-driver);}virtio_device和virtio_driver的注册顺序并不固定。前端驱动通过register_virtio_driver()进入 driver core 的virtio_bustransport 侧随后通过device_add()把virtio_device挂到同一个 bus 上时driver core 会用virtio_bus.match查找已有 driver。反过来如果 device 先注册后续 driver 注册时也会触发同样的匹配逻辑。匹配依据是virtio_device_id。匹配成功后Linux driver core 调用.probe virtio_dev_probe。这个 probe 是 virtio core 的通用 probe它完成 feature negotiation、status 更新等通用流程最后才调用具体前端驱动的drv-probe(dev)。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */staticstructbus_typevirtio_bus{.namevirtio,.matchvirtio_dev_match,.probevirtio_dev_probe,.removevirtio_dev_remove,};因此本文后面讨论的virtio_dev_probe()不是 transport probe也不是某个具体设备的业务 probe而是 Linux virtio core 在virtio bus匹配成功后执行的通用初始化入口。3. 协议中的几个核心对象协议对象作用Device ID标识设备类型例如 net、blk、consoleDevice statusdriver 与 device 之间的初始化状态机Feature bits双方能力协商决定后续行为Device config spacedevice-specific 配置区例如 MAC、capacity、console portsVirtqueue configqueue 数量、大小、地址、ready 状态Notificationqueue 可用或状态变化时的事件提示其中最关键的是status和feature negotiation。如果这两步没有完成driver 不能假设 device 已经进入可用状态device 也不能随意消费 queue 中的 buffer。4. Device status初始化状态机virtio 规范定义了一组 device status bit。它们不是普通日志状态而是 driver 和 device 共同观察的协议状态。Status bit含义ACKNOWLEDGEdriver 已经发现该 virtio deviceDRIVERdriver 知道如何驱动该 deviceFEATURES_OKdriver 已完成 feature 协商DRIVER_OKdriver 完成初始化device 可以正常工作DEVICE_NEEDS_RESETdevice 需要 resetFAILEDdriver 初始化失败典型初始化顺序如下reset device - ACKNOWLEDGE - DRIVER - read device features - write accepted driver features - FEATURES_OK - setup virtqueues - device-specific setup - DRIVER_OK在 Linux 6.5.3 中ACKNOWLEDGE已经在register_virtio_device()中设置当virtio_device和virtio_driver在virtio_bus上匹配成功后driver core 进入virtio_dev_probe()。这个函数是 virtio bus 的共享 probe负责继续推进DRIVER、feature negotiation、FEATURES_OK和DRIVER_OK等通用初始化步骤。status 的具体读写不由前端驱动直接操作寄存器而是通过dev-config间接进入 transport driver。以virtio-mmio为例最终会落到VIRTIO_MMIO_STATUS等 MMIO 寄存器。/* Linux 6.5.3: drivers/virtio/virtio.c关键路径摘录 */staticintvirtio_dev_probe(structdevice*_d){interr,i;structvirtio_device*devdev_to_virtio(_d);structvirtio_driver*drvdrv_to_virtio(dev-dev.driver);u64 device_features;u64 driver_features;virtio_add_status(dev,VIRTIO_CONFIG_S_DRIVER);/* 读取 device features并与 driver 支持的 features 做交集 */device_featuresdev-config-get_features(dev);...dev-featuresdriver_featuresdevice_features;errdev-config-finalize_features(dev);if(err)gotoerr;/* FEATURES_OK 是协议确认点device 可以拒绝不支持的组合 */errvirtio_features_ok(dev);if(err)gotoerr;/* 调用匹配到的具体前端驱动 probe例如 virtcons_probe() */errdrv-probe(dev);if(err)gotoerr;/* probe 成功后进入可用状态 */if(!(dev-config-get_status(dev)VIRTIO_CONFIG_S_DRIVER_OK))virtio_device_ready(dev);...}这段代码对应第 2 节中的virtio_bus.probe virtio_dev_probe()。它不是 transport probe也不是具体设备的业务 probe它是 virtio core 在 bus match 之后执行的通用初始化入口。具体前端驱动的初始化入口由drv-probe(dev)触发例如 virtio-console 对应virtcons_probe()。5. Feature negotiation不是单方面声明能力feature negotiation 的核心规则是device 暴露自己支持的 feature bitsdriver 从中选择自己理解且愿意启用的子集driver 把选择结果写回 devicedriver 设置FEATURES_OKdriver 再读回 status确认 device 是否接受该组合。这不是“driver 支持什么就打开什么”而是双方共同确认。feature 决定后续协议行为例如是否启用 modern layout、是否支持某个 device-specific 能力、是否支持 packed queue 等。Linux 的通用流程在 virtio core 中具体 feature bit 的读写由 transport driver 实现。以 MMIO 为例transport 需要从DEVICE_FEATURES相关寄存器读取 device features并把 driver features 写回DRIVER_FEATURES相关寄存器。/* Linux: drivers/virtio/virtio_mmio.c关键路径摘录 */staticu64vm_get_features(structvirtio_device*vdev){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vdev);/* 通过 MMIO 寄存器读取 device 暴露的 feature bits */writel(0,vm_dev-baseVIRTIO_MMIO_DEVICE_FEATURES_SEL);featuresreadl(vm_dev-baseVIRTIO_MMIO_DEVICE_FEATURES);...}staticintvm_finalize_features(structvirtio_device*vdev){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vdev);/* 把 driver 接受的 feature bits 写回 device */writel(0,vm_dev-baseVIRTIO_MMIO_DRIVER_FEATURES_SEL);writel((u32)vdev-features,vm_dev-baseVIRTIO_MMIO_DRIVER_FEATURES);...}QEMU 侧的 MMIO endpoint 则接收这些寄存器访问并更新 VMM 中的 virtio device 状态。/* QEMU: hw/virtio/virtio-mmio.c关键路径摘录 */staticvoidvirtio_mmio_write(void*opaque,hwaddr offset,uint64_tvalue,unsignedsize){VirtIOMMIOProxy*proxyopaque;VirtIODevice*vdevvirtio_bus_get_device(proxy-bus);switch(offset){caseVIRTIO_MMIO_DRIVER_FEATURES_SEL:proxy-guest_features_selvalue;break;caseVIRTIO_MMIO_DRIVER_FEATURES:/* 保存 driver 接受的 features */virtio_set_features(vdev,value);break;caseVIRTIO_MMIO_STATUS:/* status 写入会驱动 device 状态机变化 */virtio_set_status(vdev,value);break;...}}前端驱动声明自己支持的 featureLinux virtio core 负责计算双方交集并推进协商流程transport driver 再把 feature 读写转换为 MMIO、PCI 或 CCW 访问。VMM 侧收到这些 guest-visible register/config 访问后更新自己的 virtio device 状态。frontend driver provides feature_table - Linux virtio core selects accepted features - transport driver reads/writes guest-visible registers - VMM transport endpoint - VMM virtio core records features/status6. Device config space设备类型相关的控制信息device config space 保存 device-specific 配置。它不是每个字段都重新发明一套访问 API也不是从前端驱动视角看到的一堆散乱寄存器更准确地说它是一块由 virtio 协议按设备类型定义布局的配置区。virtio-net 的 MAC、MTU、queue pair 数virtio-blk 的 capacity、block sizevirtio-console / virtio-serial 的端口能力其他设备自己的配置结构。以 virtio-console 为例guest 前端和 VMM/QEMU 后端都使用同一份协议布局。Linux 侧定义在 UAPI 头文件中/* Linux 6.5.3: include/uapi/linux/virtio_console.h节选 */structvirtio_console_config{/* colums of the screens */__virtio16 cols;/* rows of the screens */__virtio16 rows;/* max. number of ports this device can hold */__virtio32 max_nr_ports;/* emergency write register */__virtio32 emerg_wr;}__attribute__((packed));QEMU 侧也有对应的 packed layout/* QEMU: include/standard-headers/linux/virtio_console.h节选 */structvirtio_console_config{__virtio16 cols;__virtio16 rows;__virtio32 max_nr_ports;__virtio32 emerg_wr;}QEMU_PACKED;因此config space 首先是协议定义的结构体布局不同设备的 config 结构不同字段是否有效还可能受 feature bit 约束。前端驱动通过virtio_cread*()这类 helper 按字段 offset 读取 device config。PCI、MMIO、CCW 只是这块 config space 的不同承载形式不改变字段布局本身。/* Linux 6.5.3: drivers/net/virtio_net.c关键路径摘录 */staticintvirtnet_probe(structvirtio_device*vdev){...if(virtio_has_feature(vdev,VIRTIO_NET_F_MAC))virtio_cread_bytes(vdev,offsetof(structvirtio_net_config,mac),addr,ETH_ALEN);...}这说明 config space 属于协议控制面但具体读取动作通常出现在前端驱动初始化逻辑中。前端驱动负责解释字段语义transport driver 负责屏蔽底层访问方式。7. Virtqueue setup初始化阶段只建立通道virtqueue 是数据路径核心但初始化阶段只做三类事情选择 queue分配并写入 ring 的物理地址标记 queue ready。以 MMIO modern device 为例driver 会通过 queue 相关寄存器告诉 device当前配置哪条 queuequeue size 是多少descriptor table 地址在哪里driver/available area 与 device/used area 地址在哪里这条 queue 是否 ready。Linux MMIO transport 的 queue 初始化大致如下/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticstructvirtqueue*vm_setup_vq(structvirtio_device*vdev,unsignedintindex,void(*callback)(structvirtqueue*vq),constchar*name,bool ctx){...writel(index,vm_dev-baseVIRTIO_MMIO_QUEUE_SEL);/* 分配 vring并把地址写入 MMIO queue config 寄存器 */vqvring_create_virtqueue(index,num,VIRTIO_MMIO_VRING_ALIGN,vdev,true,true,ctx,notify,callback,name);addrvirtqueue_get_desc_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_DESC_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_DESC_HIGH);addrvirtqueue_get_avail_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_AVAIL_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_AVAIL_HIGH);addrvirtqueue_get_used_addr(vq);writel((u32)addr,vm_dev-baseVIRTIO_MMIO_QUEUE_USED_LOW);writel((u32)(addr32),vm_dev-baseVIRTIO_MMIO_QUEUE_USED_HIGH);writel(1,vm_dev-baseVIRTIO_MMIO_QUEUE_READY);...}本节只关注初始化阶段的 queue 配置driver 把 queue 的地址和 ready 状态交给 device。descriptor table、available ring、used ring 的内部格式以及 buffer 如何放入和取出 queue将在下一篇 virtqueue 数据路径中展开。8. Notification初始化结束后的事件入口当DRIVER_OK设置完成后设备进入可用状态。此后数据路径的典型动作是driver 把 buffer 描述放入 virtqueue - driver notify device - device/backend 处理 queue - device interrupt drivernotification 本身不携带数据数据仍然在 virtqueue 中。对 MMIO transport 来说notify 通常表现为向QUEUE_NOTIFY寄存器写入 queue index。/* Linux 6.5.3: drivers/virtio/virtio_mmio.c关键路径摘录 */staticboolvm_notify(structvirtqueue*vq){structvirtio_mmio_device*vm_devto_virtio_mmio_device(vq-vdev);/* We write the queues selector into the notification register to * signal the other end */writel(vq-index,vm_dev-baseVIRTIO_MMIO_QUEUE_NOTIFY);returntrue;}QEMU 的 MMIO endpoint 收到该写入后会转入通用 virtio queue dispatch再由具体 backend 处理。/* QEMU: hw/virtio/virtio-mmio.c关键路径摘录 */staticvoidvirtio_mmio_write(void*opaque,hwaddr offset,uint64_tvalue,unsignedsize){...switch(offset){caseVIRTIO_MMIO_QUEUE_NOTIFY:/* value 是 guest 通知的 queue index */virtio_queue_notify(vdev,value);break;...}}因此notify 是 transport-specific 的入口但它触发的是 virtio 通用 queue 处理逻辑。真正的 buffer 格式和 ring 更新规则属于下一篇的数据路径主题。9. 小结virtio 初始化可以概括为一条控制面流水线discover device - reset/status - feature negotiation - config space read/write - virtqueue setup - DRIVER_OK - notify/interrupt driven data path从源码角度看这条流水线分布在三类代码中Linux virtio core 组织通用状态机和 feature 协商Linux transport driver 完成 PCI/MMIO/CCW 的具体寄存器或配置访问VMM transport endpoint 和 virtio core 保存状态并把 queue 事件分发给具体 device backend。理解这一层后再进入 virtqueue 数据结构就会更自然descriptor table、available ring、used ring 不是孤立结构而是初始化完成后由双方共同使用的数据通道。下一篇将继续讨论 virtqueue 与数据路径包括 split virtqueue 的 descriptor table、avail ring、used ring、Linuxvirtqueue_add_*()/virtqueue_kick()路径以及 QEMU backend 如何取出并完成请求。