接前一篇文章:VirtIO实现原理之VirtIO-PCI(2)
本文内容参考:
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初始化
(1)枚举
本回详细解析发生在pci_subsys_init()中的枚举过程。先把整体流程列出来,如下所示:
pci_subsys_init
x86_init.pci.init => x86_default_pci_init
pci_legacy_init
pcibios_scan_root
x86_pci_root_bus_resources // 为Host bridge分配资源,通常情况下就是64K IO空间地址和内存空间地址就在这里划分
pci_scan_root_bus // 枚举总线树上的设备
pci_create_root_bus // 创建Host bridge
pci_scan_child_bus // 扫描总线树上所有设备,如果有pci桥,递归扫描下去
pci_scan_slot
pci_scan_single_device // 扫描设备,读取vendor id和device id
pci_scan_device
pci_setup_device
pci_read_bases
__pci_read_base // 读取bar空间大小
在原作中,就直接写读到bar空间之后的事了,这里不要一带而过,而是详细跟一下上边这个代码流程。
1)pci_subsys_init()调用x86_init.pci.init
对应上边流程中的:
pci_subsys_init函数前文已给出过代码,在arch/x86/pci/legacy.c中。代码如下:
static int __init pci_subsys_init(void)
{
/*
* The init function returns an non zero value when
* pci_legacy_init should be invoked.
*/
if (x86_init.pci.init()) {
if (pci_legacy_init()) {
pr_info("PCI: System does not support PCI\n");
return -ENODEV;
}
}
pcibios_fixup_peer_bridges();
x86_init.pci.init_irq();
pcibios_init();
return 0;
}
subsys_initcall(pci_subsys_init);
x86_init的定义在arch/x86/kernel/x86_init.c中,代码如下:
/*
* The platform setup functions are preset with the default functions
* for standard PC hardware.
*/
struct x86_init_ops x86_init __initdata = {
.resources = {
.probe_roms = probe_roms,
.reserve_resources = reserve_standard_io_resources,
.memory_setup = e820__memory_setup_default,
},
.mpparse = {
.setup_ioapic_ids = x86_init_noop,
.find_smp_config = default_find_smp_config,
.get_smp_config = default_get_smp_config,
},
.irqs = {
.pre_vector_init = init_ISA_irqs,
.intr_init = native_init_IRQ,
.intr_mode_select = apic_intr_mode_select,
.intr_mode_init = apic_intr_mode_init,
.create_pci_msi_domain = native_create_pci_msi_domain,
},
.oem = {
.arch_setup = x86_init_noop,
.banner = default_banner,
},
.paging = {
.pagetable_init = native_pagetable_init,
},
.timers = {
.setup_percpu_clockev = setup_boot_APIC_clock,
.timer_init = hpet_time_init,
.wallclock_init = x86_wallclock_init,
},
.iommu = {
.iommu_init = iommu_init_noop,
},
.pci = {
.init = x86_default_pci_init,
.init_irq = x86_default_pci_init_irq,
.fixup_irqs = x86_default_pci_fixup_irqs,
},
.hyper = {
.init_platform = x86_init_noop,
.guest_late_init = x86_init_noop,
.x2apic_available = bool_x86_init_noop,
.msi_ext_dest_id = bool_x86_init_noop,
.init_mem_mapping = x86_init_noop,
.init_after_bootmem = x86_init_noop,
},
.acpi = {
.set_root_pointer = x86_default_set_root_pointer,
.get_root_pointer = x86_default_get_root_pointer,
.reduced_hw_early_init = acpi_generic_reduced_hw_init,
},
};
x86_init.pci相关的片段为:
.pci = {
.init = x86_default_pci_init,
.init_irq = x86_default_pci_init_irq,
.fixup_irqs = x86_default_pci_fixup_irqs,
},
那么,pci_subsys_init函数中所调用的x86_init.pci.init()其实就是x86_default_pci_init()。
这个x86_default_pci_init实际上还是个宏定义,在arch/x86/include/asm/pci_x86.h中,如下:
#ifdef CONFIG_PCI
# ifdef CONFIG_ACPI
# define x86_default_pci_init pci_acpi_init
# else
# define x86_default_pci_init pci_legacy_init
# endif
# define x86_default_pci_init_irq pcibios_irq_init
# define x86_default_pci_fixup_irqs pcibios_fixup_irqs
#else
# define x86_default_pci_init NULL
# define x86_default_pci_init_irq NULL
# define x86_default_pci_fixup_irqs NULL
#endif
由代码可知,x86_default_pci_init根据不同宏的定义即选项使能的情况,指向了不同的函数。
如果CONFIG_PCI未被定义、也就是没有使能PCI,那么x86_default_pci_init为NULL;如果CONFIG_PCI定义了、但是CONFIG_ACPI未被定义,也就是没有使能ACPI(Advanced Configuration and Power Management Interface,高级配置和电源管理接口)的情况,那么x86_default_pci_init为pci_legacy_init;如果CONFIG_PCI和CONFIG_ACPI都被定义了,也就是既使能了PCI、又使能了ACPI,那么x86_default_pci_init为pci_acpi_init。
这里把pci_legacy_init和pci_acpi_init函数代码都贴出来,后续会重点讲解前者。
- pci_legacy_init函数
pci_legacy_init函数在arch/x86/pci/legacy.c中,代码如下:
int __init pci_legacy_init(void)
{
if (!raw_pci_ops)
return 1;
pr_info("PCI: Probing PCI hardware\n");
pcibios_scan_root(0);
return 0;
}
- pci_acpi_init函数
pci_acpi_init函数在arch/x86/pci/acpi.c中,代码如下:
int __init pci_acpi_init(void)
{
struct pci_dev *dev = NULL;
if (acpi_noirq)
return -ENODEV;
printk(KERN_INFO "PCI: Using ACPI for IRQ routing\n");
acpi_irq_penalty_init();
pcibios_enable_irq = acpi_pci_irq_enable;
pcibios_disable_irq = acpi_pci_irq_disable;
x86_init.pci.init_irq = x86_init_noop;
if (pci_routeirq) {
/*
* PCI IRQ routing is set up by pci_enable_device(), but we
* also do it here in case there are still broken drivers that
* don't use pci_enable_device().
*/
printk(KERN_INFO "PCI: Routing PCI interrupts for all devices because \"pci=routeirq\" specified\n");
for_each_pci_dev(dev)
acpi_pci_irq_enable(dev);
}
return 0;
}
下一回将按照(沿着)上边的流程,对于pci_legacy_init函数及其后流程进行深入解析。