接前一篇文章:
上一回讲到,在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。