接前一篇文章:VirtIO实现原理之VirtIO-PCI(1)
本文内容参考:
VirtIO实现原理——PCI基础_virtio-pci-CSDN博客
QEMU源码全解析 —— virtio(3)_qemu virtio block bus-CSDN博客
特此致谢!
一、VirtIO-PCI初始化
virtio设备首先需要创建一个PCI设备,叫作virtio PCI代理设备,这个代理设备挂到PCI总线上。
接着,virtio代理设备再创建一条virtio总线,这样virtio设备就可以挂到这条virtio总线上了。
1. PCI初始化
PCI驱动框架初始化在内核中有两个入口,分别如下:
- arch_initcall(pci_arch_init)
- subsys_initcall(pci_subsys_init)
(1)枚举
枚举的前提该PCI设备可以访问,PCI规范规定,设备在还没有配置地址前,CPU往两个IO端口(CONFIG_ADDRESS:0xCF8、CONFIG_DATA:0xCFC)分别写入地址和数据,实现对PCI设备的读写。如下图所示,这两个IO端口对应的是两个Host桥上的寄存器,它们可以直接通过io指令访问。
这两个寄存器位于主桥(Host Bridge)上,翻看Host Bridge(Intel 5000X MCH 3.5章节)的手册,可以找到寄存器各字段具体含义,如下图(表)所示。
当cpu要访问某个pci设备时,先往0xCF8写入4字节的"bus/slot/function"地址,然后通过0xCFC的IO空间读取或者写入数据。地址空间0xCF8的初始化发生在pci_arch_init()里面。
/* arch_initcall has too random ordering, so call the initializers
in the right sequence from here. */
static __init int pci_arch_init(void)
{
int type, pcbios = 1;
type = pci_direct_probe();
if (!(pci_probe & PCI_PROBE_NOEARLY))
pci_mmcfg_early_init();
if (x86_init.pci.arch_init)
pcbios = x86_init.pci.arch_init();
/*
* Must happen after x86_init.pci.arch_init(). Xen sets up the
* x86_init.irqs.create_pci_msi_domain there.
*/
x86_create_pci_msi_domain();
if (!pcbios)
return 0;
pci_pcbios_init();
/*
* don't check for raw_pci_ops here because we want pcbios as last
* fallback, yet it's needed to run first to set pcibios_last_bus
* in case legacy PCI probing is used. otherwise detecting peer busses
* fails.
*/
pci_direct_init(type);
if (!raw_pci_ops && !raw_pci_ext_ops)
printk(KERN_ERR
"PCI: Fatal: No config space access function found\n");
dmi_check_pciprobe();
dmi_check_skip_isa_align();
return 0;
}
arch_initcall(pci_arch_init);
有了读写pci设备寄存器的方法,cpu就可以读取任意pci总线上任意设备配置空间的任意寄存器。读取slot设备配置空间的vendor id,如果返回全1表示没有设备;如果返回具体值表示slot上存在function,继续判断是否为multi function。通过这样的方式逐总线搜索下去,枚举每一个slot上存在的pci设备,直到遍历完总线树。
其中两处涉及到配置空间寄存器的读写,一处是识别设备的时候需要读取vendor id和device id;另一处是读取bar空间大小时,需要读写bar寄存器。枚举发生在pci_subsys_init()中。详细流程的解析请看下回。