virtio-pci 驱动映射 virtio common_cfg resource 空间
virtio-pci 获取 comon_cfg 物理空间的函数调用如下:
mdev->common = vp_modern_map_capability(mdev, common,sizeof(struct virtio_pci_common_cfg), 4,0, sizeof(struct virtio_pci_common_cfg),NULL, NULL);
p_modern_map_capability 函数通过访问 pci 配置空间来获取 virtio 相关属性信息,然后执行 iomap 映射 virito resource 空间到内核虚拟地址中,核心代码如下:
pci_read_config_byte(dev, off + offsetof(struct virtio_pci_cap,bar),&bar);pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, offset),&offset);pci_read_config_dword(dev, off + offsetof(struct virtio_pci_cap, length),&length);..............................................p = pci_iomap_range(dev, bar, offset, length);if (!p)
virtio_net 驱动中调用的 virtio_config_ops 方法
virtio_net 驱动通过调用 virtio_config_ops 中实现的不同函数来读写 virtio 网卡 resource 空间,此结构的一个实例如下:
static const struct virtio_config_ops virtio_pci_config_ops = {.get = vp_get,.set = vp_set,.generation = vp_generation,.get_status = vp_get_status,.set_status = vp_set_status,.reset = vp_reset,.find_vqs = vp_modern_find_vqs,.del_vqs = vp_del_vqs,.synchronize_cbs = vp_synchronize_vectors,.get_features = vp_get_features,.finalize_features = vp_finalize_features,.bus_name = vp_bus_name,.set_vq_affinity = vp_set_vq_affinity,.get_vq_affinity = vp_get_vq_affinity,.get_shm_region = vp_get_shm_region,.disable_vq_and_reset = vp_modern_disable_vq_and_reset,.enable_vq_after_reset = vp_modern_enable_vq_after_reset,
};
以 vp_set_status 为例,它实际是对 vp_modern_set_status 函数的封装,vp_modern_set_status 函数的实现如下:
void vp_modern_set_status(struct virtio_pci_modern_device *mdev,u8 status)
{struct virtio_pci_common_cfg __iomem *cfg = mdev->common;/** Per memory-barriers.txt, wmb() is not needed to guarantee* that the cache coherent memory writes have completed* before writing to the MMIO region.*/vp_iowrite8(status, &cfg->device_status);
}
EXPORT_SYMBOL_GPL(vp_modern_set_status);
直接调用 iowrite 来读写 mdev 中 common 字段执行的虚拟内存来以 MMIO 方式写入网卡配置空间。
virtio_net 驱动奇怪的 pci_id_table
#define VIRTIO_ID_NET 1 /* virtio net */
#define VIRTIO_DEV_ANY_ID 0xffffffffstatic struct virtio_device_id id_table[] = {{ VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID },{ 0 },
};
此处并不是 virtio 的 device_id + vendor_id的形式,而是内部定义的值,这里的 vendor id 被设置为全 F,表明只通过 device id 来 match 驱动。
virtio-pci probe 时 virtio_config_ops 的绑定过程
virtio-pci 驱动的 probe 函数中会调用 legacy 与 modern 两种 virtio pci 的 probe 函数来注册 virtio_config_ops 到 virtio 设备中,相关代码如下:
if (force_legacy) {rc = virtio_pci_legacy_probe(vp_dev);/* Also try modern mode if we can't map BAR0 (no IO space). */if (rc == -ENODEV || rc == -ENOMEM)rc = virtio_pci_modern_probe(vp_dev);if (rc)goto err_probe;} else {rc = virtio_pci_modern_probe(vp_dev);if (rc == -ENODEV)rc = virtio_pci_legacy_probe(vp_dev);if (rc)goto err_probe;}
virtio-pci 与 virtio_net 如何协作?
virtio-pci 驱动与常规的 pci 驱动一样,使用 virtio 网卡的 vendor id + device id 匹配设备,匹配到后执行 virtio_pci_probe 函数。此函数核心流程如下:
-
创建一个 virtio_pci_device 结构并初始化。
-
调用 virtio_pci_modern_probe、virtio_pci_legacy_probe 解析 virtio 设备的 capabilities 并初始化相关数据结构,同时也会绑定一个 virtio_config_ops 到 virtio_device 上。新的 virtio_device 的 vendor_id 与 device_id 也会被设置为 virtio 总线内部使用的数据。
-
调用 register_virtio_device 向 virtio 总线注册一个 virtio 设备,此设备由 virtio_pci_device 中的 virtio_device 结构描述。
register_virtio_device 函数的关键过程如下:
- 为当前设备分配一个唯一的 id,并使用此 id 制作 virtioXX 的设备名并填充。
- 初始化必要的字段后执行 virtio_reset_device 函数 reset virtio 设备,reset 完成后继续调用 virtio_add_status 通知 qemu 后端。
- 调用 device_add 将新的设备添加到总线中,此过程会触发 virtio 总线 match 驱动。
-
如果设备是 virtio 网卡设备,virtio_net 驱动成功 match,此驱动完成类似网卡驱动初始化的过程,其中访问 virtio 网卡 resource 依赖 virtio-pci probe 中绑定的 virtio_config_ops 进行。
virtio 总线
virtio bus 与其它总线一样会在 /sys/bus 目录中生成相关内容,包含驱动、设备等属性。其下注册的驱动结构 sys 目录示例如下:
.
├── virtio_balloon
│ ├── bind
│ ├── module -> ../../../../module/virtio_balloon
│ ├── uevent
│ ├── unbind
│ └── virtio1 -> ../../../../devices/pci0000:00/0000:00:07.0/virtio1
├── virtio_console
│ ├── bind
│ ├── module -> ../../../../module/virtio_console
│ ├── uevent
│ ├── unbind
│ └── virtio0 -> ../../../../devices/pci0000:00/0000:00:06.0/virtio0
├── virtio_net
│ ├── bind
│ ├── module -> ../../../../module/virtio_net
│ ├── uevent
│ ├── unbind
│ └── virtio2 -> ../../../../devices/pci0000:00/0000:00:08.0/virtio2
├── virtio_rng
│ ├── bind
│ ├── uevent
│ └── unbind
└── virtio_rproc_serial├── bind├── module -> ../../../../module/virtio_console├── uevent└── unbind
virtio 驱动仍旧可以通过写入 bind、unbind 文件来绑定、解绑设备到总线上注册的驱动,只不过设备的标识并非 pci 号,而是 virtioXXX 这种内部的标识,毕竟 virtio bus 是一种独立的总线。
virtio-net 设备驱动绑定示例如下:
[root@openeuler virtio_net]# echo virtio3 > bind
[root@openeuler virtio_net]# ls
bind module uevent unbind virtio2 virtio3
[root@openeuler virtio_net]# echo virtio3 > unbind
[root@openeuler virtio_net]# ls
bind module uevent unbind virtio2
virtio 设备 sys 目录结构示例如下:
.
├── virtio0 -> ../../../devices/pci0000:00/0000:00:06.0/virtio0
├── virtio1 -> ../../../devices/pci0000:00/0000:00:07.0/virtio1
├── virtio2 -> ../../../devices/pci0000:00/0000:00:08.0/virtio2
└── virtio3 -> ../../../devices/pci0000:00/0000:00:09.0/virtio3
单个设备的内容示例如下:
[root@openeuler virtio3]# cat modalias
virtio:d00000001v00001AF4
[root@openeuler virtio3]# cat ./device
0x0001
[root@openeuler virtio3]# cat ./vendor
0x1af4
[root@openeuler virtio3]# cat ./uevent
MODALIAS=virtio:d00000001v00001AF4
[root@openeuler virtio3]# cat ./modalias
virtio:d00000001v00001AF4
[root@openeuler virtio3]# cat features
1110010111111111111101010000110010000000000000000000000000000000
[root@openeuler virtio3]# cat status
0x00000001
virtio 总线匹配 virtio 设备的一些特征
在内核 modules.alias 中查询到 virtio 与e1000e 设备的匹配模式如下:
alias pci:v00008086d000010BCsv*sd*bc*sc*i* e1000e
alias pci:v00008086d000010A4sv*sd*bc*sc*i* e1000e
alias pci:v00008086d0000105Fsv*sd*bc*sc*i* e1000e
alias pci:v00008086d0000105Esv*sd*bc*sc*i* e1000e
................................................
alias virtio:d00000005v* virtio_balloon
alias virtio:d00000012v* virtio_input
alias virtio:d00000003v* virtio_console
alias virtio:d00000010v* virtio_gpu
alias virtio:d00000002v* virtio_blk
alias virtio:d00000008v* virtio_scsi
alias virtio:d00000001v* virtio_net
alias virtio:d00000013v* vmw_vsock_virtio_transport
能够看到 virtio 的 alias 中的 v 并没有指定具体的值而是使用 *,表明总线匹配的时候只使用 device_id,而 e1000e 设备的 alias 中严格按照 vendor id + device id 匹配驱动。
对于 virtio 总线而言,virtio device 的 device id 非常重要,它是匹配 virtio 驱动的源数据,此值在 virtio-pci 这一层进行初始化。有如下几条规则:
- legacy virtio 设备的 device id 为相应 pci 设备的 subsystem_device 值。
- modern virtio 设备的 device id 在对应 pci 设备的 device_id 小于 0x1040 则设置为 pci 设备的 subsystem_device 值,否则为 pci 设备的 device id 减去 0x1040 的值。
virtio bus 匹配设备驱动的代码如下:
static inline int virtio_id_match(const struct virtio_device *dev,const struct virtio_device_id *id)
{if (id->device != dev->id.device && id->device != VIRTIO_DEV_ANY_ID)return 0;return id->vendor == VIRTIO_DEV_ANY_ID || id->vendor == dev->id.vendor;
}
如上文所述,virtio-net 当 device 相等时,virtio 上层驱动生命vendor 是否相等都会返回 true,符合上面的判断。
总结
使用 virtio 虚拟网卡时,virtio pci 设备会绑定到 virtio-pci 驱动上,virtio-pci 驱动负责构建新的 virtio 设备并挂入到总线中并 match 驱动,此时如果加载了 virtio_et 驱动并支持新创建的 virtio 设备,就会执行 virtio-net 驱动的 probe 完成网络设备的初始化。
virtio-pci 驱动可以看做是一个 virtio bus 底层的驱动,它对接 pci 总线,并创建新的 virtio 设备注入到 virtio 总线中,根据设备的类型 match 不同的 virtio 上层驱动以提供某一类服务。