QEMU源码全解析 —— PCI设备模拟(10)

接前一篇文章:

上一回讲到,在SeaBIOS的调用链dopost->maininit->platform_hardware_setup->qemu_platform_setup->pci_setup->pci_bios_map_devices过程中,最后这个函数负责完成PCI设备BAR的设置。

其中包括I/O、MEM以及PREFMEM三种BAR的设置,MEM和PREFMEM是一起的,这里以上述命令行为例,讨论SeaBIOS如何给PCI设备设置BAR基址。

pci_bios_map_devices首先调用pci_bios_init_root_regions_io和pci_bios_init_root_regions_mem函数做初始化的工作,以mem为例,调用的是src/fw/pcinit.c中的pci_bios_init_root_regions_mem函数。

该函数在QEMU源码根目录/roms/seabios/src/fw/pciinit.c中,代码如下:

static int pci_bios_init_root_regions_mem(struct pci_bus *bus)
{
    struct pci_region *r_end = &bus->r[PCI_REGION_TYPE_PREFMEM];
    struct pci_region *r_start = &bus->r[PCI_REGION_TYPE_MEM];

    if (pci_region_align(r_start) < pci_region_align(r_end)) {
        // Swap regions to improve alignment.
        r_end = r_start;
        r_start = &bus->r[PCI_REGION_TYPE_PREFMEM];
    }
    u64 sum = pci_region_sum(r_end);
    u64 align = pci_region_align(r_end);
    r_end->base = ALIGN_DOWN((pcimem_end - sum), align);
    sum = pci_region_sum(r_start);
    align = pci_region_align(r_start);
    r_start->base = ALIGN_DOWN((r_end->base - sum), align);

    if ((r_start->base < pcimem_start) ||
         (r_start->base > pcimem_end))
        // Memory range requested is larger than available.
        return -1;
    return 0;
}

pci_region结构表示该虚拟机所有设备的某一类BAR(如PCI_REGION_TYPE_MEM表示mem BAR)。其定义也在roms/seabios/src/fw/pciinit.c中,如下:

struct pci_region {
    /* pci region assignments */
    u64 base;
    struct hlist_head list;
};

struct pci_region的base成员表示这类BAR的起始地址;list成员用来链接所有这类BAR的设备。

    PCI: map device bdf=00:03.0 bar 1, addr 0000c000, size 00000040 [io]
    PCI: map device bdf=00:01.1 bar 4, addr 0000c040, size 00000010 [io]
    PCI: map device bdf=00:04.0 bar 0, addr fea00000, size 00100000 [mem]
    PCI: map device bdf=00:03.0 bar 6, addr feb00000, size 00040000 [mem]
    PCI: map device bdf=00:03.0 bar 0, addr feb40000, size 00020000 [mem]
    PCI: map device bdf=00:02.0 bar 6, addr feb60000, size 00100000 [mem]
    PCI: map device bdf=00:02.0 bar 2, addr feb70000, size 00001000 [mem]
    PCI: map device bdf=00:02.0 bar 0, addr fd000000, size 01000000 [prefmem]

pci_region_align函数会返回最大的align值,每个设备的BAR地址的alignment就是其大小。这里首先比较align,大的尽量往前面放。在命令行启动的虚拟机中最大的是VGA的16MB ROM区域,所以会把PREFMEM放在更前面,也就是其地址比较低。pci_region_sum函数返回某一类BAR的所有空间和,将pcimem_end设置为0xfec00000。

这里r_end表示的就是mem BAR,此例中所有PCI设备的mem BAR的sum为0x171000,align为mem中最大的那一个值,此例中是0x00100000,所以r_end->base就是0xfec00000-0x171000之后与0x100000进行与运算的结果,该值为0xfea00000。

你可能感兴趣的:(QEMU,KVM,QEMU,KVM,PCI)