virtio_net 与 virtio-pci 驱动关联浅析

22 篇文章 1 订阅
5 篇文章 1 订阅

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	0xffffffff

static 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 函数。此函数核心流程如下:

  1. 创建一个 virtio_pci_device 结构并初始化。

  2. 调用 virtio_pci_modern_probevirtio_pci_legacy_probe 解析 virtio 设备的 capabilities 并初始化相关数据结构,同时也会绑定一个 virtio_config_opsvirtio_device 上。新的 virtio_device 的 vendor_id 与 device_id 也会被设置为 virtio 总线内部使用的数据。

  3. 调用 register_virtio_device 向 virtio 总线注册一个 virtio 设备,此设备由 virtio_pci_device 中的 virtio_device 结构描述。

    register_virtio_device 函数的关键过程如下:

    1. 为当前设备分配一个唯一的 id,并使用此 id 制作 virtioXX 的设备名并填充。
    2. 初始化必要的字段后执行 virtio_reset_device 函数 reset virtio 设备,reset 完成后继续调用 virtio_add_status 通知 qemu 后端。
    3. 调用 device_add 将新的设备添加到总线中,此过程会触发 virtio 总线 match 驱动。
  4. 如果设备是 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 这一层进行初始化。有如下几条规则:

  1. legacy virtio 设备的 device id 为相应 pci 设备的 subsystem_device 值。
  2. 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 上层驱动以提供某一类服务。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值