接前一篇文章:
上一回介绍了虚拟机如何通过北桥的MMIO来读写PCI设备的配置空间。PCI设备的配置空间中有MMIO的地址,也就是BAR信息,里面存放有BAR的基址,虚拟机可以通过读写这些BAR来与设备通信。然而,QEMU在设备初始化、具现化的过程中并没有设置基址,那么这些地址是怎么设置的呢?这实际上是在运行时设置的,下面从BIOS的角度来分析地址是怎么设置的。以QEMU命令行为例(并非笔者环境,而是《QEMU/KVM源码解析与应用》中的参考例程):
/home/test/qemu/x86_64-softmmu/qemu-system-x86_64 -m 1024 -smp 4 -hda /home/test/test.img --enable-kvm -vnc :0 -device edu -debugcon file:/home/test/1.txt -global isa-debugcon.iobase=0x402 -bios /home/test/seabios/out/bios.bin
上述虚拟机对应的PCI设备如下所示:
$ lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI(rev 03)
00:02.0 VGA compatible controller: Device 1234: 1111(rev 02)
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)
00:04.0 Unclassified device [00ff]: Device 1234: 11e8(rev 10)
读取/proc/iomem,可以看到设备的MMIO地址分布,如下所示:
40000000-febfffff : PCI Bus 0000:00
fd000000-fdffffff : 0000:00:02.0
fd000000-fd15ffff : efifb
fea00000-feafffff : 0000:00:04.0
feb00000-feb3ffff : 0000:00:03.0
feb40000-feb5ffff : 0000:00:03.0
feb40000-feb5ffff : e1000
feb70000-feb70fff : 0000:00.02.0
查看SeaBIOS的日志文件1.txt,可以看到上述PCI设备的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]
从上述日志可以看出:
- IDE控制器有1个I/O BAR
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
PCI: map device bdf=00:01.1 bar 4, addr 0000c040, size 00000010 [io]
- VGA有3个mem BAR
00:02.0 VGA compatible controller: Device 1234: 1111(rev 02)
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]
- 网卡有2个mem BAR以及1个I/O BAR
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 03)
PCI: map device bdf=00:03.0 bar 1, addr 0000c000, size 00000040 [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]
- edu设备有1个mem BAR
00:04.0 Unclassified device [00ff]: Device 1234: 11e8(rev 10)
PCI: map device bdf=00:01.1 bar 4, addr 0000c040, size 00000010 [io]
SeaBIOS日志也打印出了设备BAR的地址。
下面,从代码层分析这一过程是如何完成的。
在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函数。
对于该函数的解析,请看下回。