在使用lspci -vvs XX:XX.X 查看virtio设备信息的时候,经常会看到下面的capability
本篇介绍下virtio的这类capability初始化的过程。
下面是软件定义的virtio capability的通用的结构体。
上面看到的各个virtio的capability,比如CommonCfg、Notify、ISR、DeviceCfg其结构都是下面这样。
/* This is the PCI capability header: */
struct virtio_pci_cap {
__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
__u8 cap_next; /* Generic PCI field: next ptr. */
__u8 cap_len; /* Generic PCI field: capability length */
__u8 cfg_type; /* Identifies the structure. */
__u8 bar; /* Where to find it. */
__u8 padding[3]; /* Pad to full dword. */
__le32 offset; /* Offset within bar. */
__le32 length; /* Length of the structure, in bytes. */
};
查看一个virtio_net设备的pci配置空间(前256字节),当做示例,已经用红圈圈出来对应上面结构各个成员的字节内容。
可以更直观的看到,作为一个pci设备,当前virtio设备的一个capability在pci配置空间里的内容。
上面这个图里,capability的起始查找位置为0x34,指向0x40的capability。0x40的capability的第二个字节为指向下一个cap的指针,这里是0x70。0x70的下一个cap是0xb0,0xb0的下一个cap是0x48,即指向上面cap。
这里的capability内容,只是一个概述,简单的描述了一些信息。该capability的具体内容,则是根据这个概述信息,在BAR空间有一段属于该capability的对应空间,里面存有该capability的具体各项内容。比如CommonCfg的内容如下。
/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
struct virtio_pci_common_cfg {
/* About the whole device. */
__le32 device_feature_select; /* read-write */
__le32 device_feature; /* read-only */
__le32 guest_feature_select; /* read-write */
__le32 guest_feature; /* read-write */
__le16 msix_config; /* read-write */
__le16 num_queues; /* read-only */
__u8 device_status; /* read-write */
__u8 config_generation; /* read-only */
/* About a specific virtqueue. */
__le16 queue_select; /* read-write */
__le16 queue_size; /* read-write, power of 2. */
__le16 queue_msix_vector; /* read-write */
__le16 queue_enable; /* read-write */
__le16 queue_notify_off; /* read-only */
__le32 queue_desc_lo; /* read-write */
__le32 queue_desc_hi; /* read-write */
__le32 queue_avail_lo; /* read-write */
__le32 queue_avail_hi; /* read-write */
__le32 queue_used_lo; /* read-write */
__le32 queue_used_hi; /* read-write */
};
下面是virtio驱动代码初始化capability的部分代码和过程
下面的函数,迭代读取pci配置空间中各个capability,根据VIRTIO_PCI_CAP_COMMON_CFG查找commoncfg的capability是否存在,并返回在pci配置空间中的偏移
common = virtio_pci_find_capability(pci_dev, VIRTIO_PCI_CAP_COMMON_CFG,
IORESOURCE_IO | IORESOURCE_MEM,
&vp_dev->modern_bars);
这里返回的common为commoncfg capability的偏移,实际为0x48。对应下面lspci -vvs看到的偏移[48],即上面cap开始的位置
Capabilities: [48] Vendor Specific Information: VirtIO: CommonCfg
下面函数根据上面的capability偏移,从pci配置空间中对应位置,读取该capability的offset和length。即上面virtio_pci_cap中的offset和length
vp_dev->common = map_capability(pci_dev, common, // common 0x48, 即下面的off
sizeof(struct virtio_pci_common_cfg), 4,
0, sizeof(struct virtio_pci_common_cfg),
NULL);
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);
根据读取到的offset和length,从该capability所在的bar映射的空间,进行相应偏移,映射length长度的内存空间给p,并返回p给vp_dev->common
vp_dev->common即驱动定义的commoncfg结构的起始地址,驱动直接访问该结构,便是直接访问硬件后端bar空间相应commoncfg内容
p = pci_iomap_range(dev, bar, offset, length);
virtio_pci_find_capability迭代查找cap的的具体流程
virtio_pci_find_capability
{
int pos;
for (pos = pci_find_capability(dev, PCI_CAP_ID_VNDR);
// __pci_bus_find_cap_start
// return PCI_CAPABILITY_LIST; 0x34
// pos = __pci_find_next_cap(dev->bus, dev->devfn, pos, cap); 找到0x34指向的下一个cap的pci配置空间的偏移
pos > 0;
pos = pci_find_next_capability(dev, pos, PCI_CAP_ID_VNDR)) { // 迭代找当前cap指向的下一个cap在配置空间的偏移
u8 type, bar;
获取当前cap的type
pci_read_config_byte(dev, pos + offsetof(struct virtio_pci_cap,
cfg_type),
&type);
pci_read_config_byte(dev, pos + offsetof(struct virtio_pci_cap,
bar),
&bar);
/* Ignore structures with reserved BAR values */
if (bar > 0x5)
continue;
if (type == cfg_type) {
if (pci_resource_len(dev, bar) &&
pci_resource_flags(dev, bar) & ioresource_types) {
*bars |= (1 << bar);
return pos;
}
}
}
return 0;
}