软件环境:qemu,linux kernel
硬件环境:x86 PC
目标:在host ubuntu20.04上通过qemu运行linux虚拟机
0. 目标
看virtio代码时,会发现virtio_pci驱动,virtio总线,pci总线,pci设备,virtio驱动,virtio设备,它们到底是一个什么样的关系,
这篇文章尝试理清。
1. VIRTIO框架
-
从前后端代码角度来看,virtio框架如下
1. 除了前端驱动(在客户机操作系统中实现)和后端驱动(在虚拟机 hypervisor 中实现)之外,virtio 还定义了2个层次来支持客户机到 hypervisor 的通信。 2. 在顶层(称为 virtio 层)是虚拟队列接口,它在概念上将前端驱动程序附加到后端驱动,驱动可以根据需要使用零个或多个队列。例如,virtio 网络驱动使用两个虚拟队列(一个用于接收,一个用于发送),而 virtio 块驱动只使用一个队列。虚拟队列是虚拟的,它通过 ring 实现,以用于遍历客户机到 hypervisor 的信息。这可以以任何方式来实现,只要 Guest 和 hypervisor 通过相同的方式来实现。 3. 列出了五个前端驱动程序,分别用于块设备(例如磁盘)、网络设备、PCI 模拟、balloon 驱动(用于动态管理客户机内存使用)和控制台驱动。每个前端驱动在 hypervisor 中都有一个对应的后端驱动。
-
从驱动与设备匹配角度来看,Virtio框架如下
1.1 virtio_pci
1. virtio_pci分为modern和legacy,当前用modern
2. virtio_pci是一种pci_driver,模块初始化为virtio_pci_driver_init,其向内核注册pci driver,与pci dev配对后进行virtio_pci_probe
-
virtio_pci_probe
virtio_pci_driver_init -> ... -> virtio_pci_probe -> virtio_pci_modern_probe 1.创建virtio_pci设备 2.virtio_pci_modern_probe 填充virtio_pci设备结构体 3.register_virtio_device注册virtio_pci设备,后续virtio_driver就可以probe了
-
virtio_pci_modern_probe
1.设置pci设备中的device vendor id到virtio_device,用于register_virtio_device注册设备 2.映射common isr notify_base device区 3.注册virtio_pci_config_ops, setup_vq,del_vq
-
virtio_pci_config_ops
实现virtio_config_ops定义的回调,由virtblk驱动调用
-
IO region
Virtio规定IO区域:Common, ISR, Device, Notify virtio_pci_common_cfg 映射PCI设备的各个IO配置空间,在virtio_pci_modern中对其映射
1.2 IO Region
1.2.1 设备类型
基于PCI总线发现,virtio设备有着专属的Vendor ID(0x1AF4)和特定的Device ID区间(0x1000~0x107F)其中0x1000~0x103F用于传统模式设备,0x1040~0x107F用于现代模式设备。通过Vendor ID可以识别出virtio pci设备,而Device ID则可以指示该virtio pci设备支持的virtio设备类型。
如modern模式中,DEVICE_ID采用0x1000/0x1001+0x40,分别表示net和block设备
其实device id沿用pci_dev->subsystem_device,与legacy一致
如device = 1代表virtio-net设备,vendor = 6900(0x1af4)代表virtio设备
(gdb) p vp_dev->vdev.id
$6 = {device = 1, vendor = 6900}
1.2.2 Modern Virtio-pci设备配置空间
现代virtio-pci设备通过标准的PCI配置空间中的能力列表(capability list),可以指定配置信息的存储位置(使用哪个BAR,从BAR空间开始的偏移地址等)。1.0规范中定义了4种配置信息:通用配置、中断配置、通知配置、设备专属配置。每种配置信息都对应一个capabiility,如果设备不需要某种类型的配置信息,则不提供相应的capability即可。配置信息分别是:
1)common config,通用的配置信息,包括队列的初始化、feature bit、status,对应legacy模式下的common config,必须支持;
2)isr config,中断配置信息;
3)notify config,也是中断相关的配置信息,isr和notify至少需要支持一种;
4)device config,设备自有的配置信息,对应virtio0.95中提到的net/block设备配置信息,有些设备不需要单独的配置信息,则不提供相应的capability;
//qemu相关的实现代码,注册四种配置信息的mem_region并创建相应的capability
virtio_pci_modern_mem_region_map(proxy, &proxy->common, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->isr, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->device, &cap);
virtio_pci_modern_mem_region_map(proxy, &proxy->notify, ¬ify.cap);
1.2.3 Bars
从modern_bars(0)读取是否支持common,isr,notify
/* check for a common config: if not, use legacy mode (bar 0). */
common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&vp_dev->modern_bars);
/* If common is there, these should be too... */
isr = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_ISR_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&vp_dev->modern_bars);
notify = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_NOTIFY_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&vp_dev->modern_bars);
1.2.4 Virtio common
Qemu中common结构体
/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
/* About the whole device. */
uint32_t device_feature_select; /* read-write */
uint32_t device_feature; /* read-only */
uint32_t guest_feature_select; /* read-write */
uint32_t guest_feature; /* read-write */
uint16_t msix_config; /* read-write */
uint16_t num_queues; /* read-only */
uint8_t device_status; /* read-write */
uint8_t config_generation; /* read-only */
/* About a specific virtqueue. */
uint16_t queue_select; /* read-write */
uint16_t queue_size; /* read-write, power of 2. */
uint16_t queue_msix_vector; /* read-write */
uint16_t queue_enable; /* read-write */
uint16_t queue_notify_off; /* read-only */
uint32_t queue_desc_lo; /* read-write */
uint32_t queue_desc_hi; /* read-write */
uint32_t queue_avail_lo; /* read-write */
uint32_t queue_avail_hi; /* read-write */
uint32_t queue_used_lo; /* read-write */
uint32_t queue_used_hi; /* read-write */
};
Virtio_pci_modern_probe映射common后成员
在setup_vq中会对common进行读写设置
1.2.5 Isr
在Virtio_pci_modern_probe映射isr成员
setup_vq中将其计算赋值给vq->priv
1.2.6 Notify_base
pci_read_config_dword从pci config space读notify_off_multiplier,notify_length,notify_offset
在Virtio_pci_modern_probe映射notify_base成员
setup_vq将notify_base映射对应的vq
vq->priv = (void __force *)vp_dev->notify_base +
off * vp_dev->notify_offset_multiplier;
1.2.7 Device
在Virtio_pci_modern_probe映射device成员
vp_get从device根据offset读取
vp_set向device的offset设置
1.3 virtio_mmio
qemu_system_aarch64 启动的vm看起来有virtio_mmio节点,实际上没有走virtio_mmio,实际上走的virtio_pci_common
-
virtio_mmio_driver
virtio_mmio是一种platform_driver,向platform_driver_register
static struct platform_driver virtio_mmio_driver = {
.probe = virtio_mmio_probe,
.remove = virtio_mmio_remove,
.driver = {
.name = "virtio-mmio",
.of_match_table = virtio_mmio_match, //”virtio,mmio”
.acpi_match_table = ACPI_PTR(virtio_mmio_acpi_match),
},
};
-
virtio_mmio_probe
register_virtio_device //将子设备注册到virtio总线
-
virtio_mmio_config_ops
1.4 virtio
-
bus_register
Virtio 注册virtio-bus(drivers/virtio/virtio.c)
static struct bus_type virtio_bus = {
.name = "virtio",
.match = virtio_dev_match, //该总线dev和dri匹配规则
.dev_groups = virtio_dev_groups,
.uevent = virtio_uevent,
.probe = virtio_dev_probe, //match成功后内核走总线probe,dri->probe
.remove = virtio_dev_remove,
};
-
register_virtio_driver
定义register_virtio_driver,用于virtio设备注册,相比于普通drive_register,仅都一个driver.bus成员
virtio_bus
1.5 virtio_ring
定义了virtio_queue的一系列操作
1.6 virtio_blk
此处以virtio_blk为例,对于virtio其他驱动来说也大致相似
-
virtio_blk driver
register_virtio_driver(&virtio_blk) //首先virtio_blk是一个virtio_driver,由virtio.h定义
static struct virtio_driver virtio_blk = {
.feature_table = features,
.feature_table_size = ARRAY_SIZE(features),
.feature_table_legacy = features_legacy,
.feature_table_size_legacy = ARRAY_SIZE(features_legacy),
.driver.name = KBUILD_MODNAME,
.driver.owner = THIS_MODULE,
.id_table = id_table,
.probe = virtblk_probe,
.remove = virtblk_remove,
.config_changed = virtblk_config_changed,
#ifdef CONFIG_PM_SLEEP
.freeze = virtblk_freeze,
.restore = virtblk_restore,
#endif
};
-
virtblk_probe
1.Virtio_blk初始化init调用Register_virtio_driver,走driver的匹配流程,成功先走virtio_bus的probe(virtio_dev_probe),然后走blk驱动的probe(virtblk_probe)virtio_dev_probe -> virtblk_probe 2.virtio_find_vqs 实现有3种:virtio_pci_modern,virtio_mmio,virtio_pci_legacy
-
virtio_mq_ops
块设备queue操作
static const struct blk_mq_ops virtio_mq_ops = {
.queue_rq = virtio_queue_rq,
.commit_rqs = virtio_commit_rqs,
.complete = virtblk_request_done,
.init_request = virtblk_init_request,
.map_queues = virtblk_map_queues,
};