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

88 篇文章 19 订阅
本文详细解析了QEMU中SeaBIOS如何设置PCI设备的BAR基址,涉及pci_bios_map_devices、pci_region结构、pci_region_align等函数,阐述了地址分配和配置空间写入的过程。
摘要由CSDN通过智能技术生成

接前一篇文章:

上一回讲到,在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中,代码如下:

  1. static int pci_bios_init_root_regions_mem(struct pci_bus *bus)
  2. {
  3. struct pci_region *r_end = &bus->r[PCI_REGION_TYPE_PREFMEM];
  4. struct pci_region *r_start = &bus->r[PCI_REGION_TYPE_MEM];
  5. if (pci_region_align(r_start) < pci_region_align(r_end)) {
  6. // Swap regions to improve alignment.
  7. r_end = r_start;
  8. r_start = &bus->r[PCI_REGION_TYPE_PREFMEM];
  9. }
  10. u64 sum = pci_region_sum(r_end);
  11. u64 align = pci_region_align(r_end);
  12. r_end->base = ALIGN_DOWN((pcimem_end - sum), align);
  13. sum = pci_region_sum(r_start);
  14. align = pci_region_align(r_start);
  15. r_start->base = ALIGN_DOWN((r_end->base - sum), align);
  16. if ((r_start->base < pcimem_start) ||
  17. (r_start->base > pcimem_end))
  18. // Memory range requested is larger than available.
  19. return -1;
  20. return 0;
  21. }

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

  1. struct pci_region {
  2. /* pci region assignments */
  3. u64 base;
  4. struct hlist_head list;
  5. };

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

  1. PCI: map device bdf=00:03.0 bar 1, addr 0000c000, size 00000040 [io]
  2. PCI: map device bdf=00:01.1 bar 4, addr 0000c040, size 00000010 [io]
  3. PCI: map device bdf=00:04.0 bar 0, addr fea00000, size 00100000 [mem]
  4. PCI: map device bdf=00:03.0 bar 6, addr feb00000, size 00040000 [mem]
  5. PCI: map device bdf=00:03.0 bar 0, addr feb40000, size 00020000 [mem]
  6. PCI: map device bdf=00:02.0 bar 6, addr feb60000, size 00100000 [mem]
  7. PCI: map device bdf=00:02.0 bar 2, addr feb70000, size 00001000 [mem]
  8. 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。

pci_region_sum函数在roms/seabios/src/fw/pciinit.c中,代码如下:

  1. static u64 pci_region_sum(struct pci_region *r)
  2. {
  3. u64 sum = 0;
  4. struct pci_region_entry *entry;
  5. hlist_for_each_entry(entry, &r->list, node) {
  6. sum += entry->size;
  7. }
  8. return sum;
  9. }

这里r_end表示的就是mem BAR,此例中所有PCI设备的mem BAR的sum为0x171000(上边所有[mem]项的和,0x00100000+0x00040000+0x00020000+0x00100000+0x00001000=0x171000),align为mem中最大的那一个值,此例中是0x00100000,所以r_end->base就是0xfec00000-0x171000之后与0x00100000进行与运算的结果,该值为0xfea00000。

r_start表示PREFMEM BAR,这里只有一个PREFMEM,大小为0x01000000,所以align为0x01000000,0xfea000000-0x01000000=0xfd000000,所以那个PREFMEM的起始在0xfd000000。

将每种BAR的基址计算出来后就好处理了,调用pci_region_map_entries函数设置每个BAR的基址。pci_region_map_entries函数在roms/seabios/src/fw/pciinit.c中,代码如下:

  1. static void pci_region_map_entries(struct pci_bus *busses, struct pci_region *r)
  2. {
  3. struct hlist_node *n;
  4. struct pci_region_entry *entry;
  5. hlist_for_each_entry_safe(entry, n, &r->list, node) {
  6. u64 addr = r->base;
  7. r->base += entry->size;
  8. if (entry->bar == -1)
  9. // Update bus base address if entry is a bridge region
  10. busses[entry->dev->secondary_bus].r[entry->type].base = addr;
  11. pci_region_map_one_entry(entry, addr);
  12. hlist_del(&entry->node);
  13. free(entry);
  14. }
  15. }

该函数调用pci_region_map_one_entry函数来完成每一项的设置。pci_region_map_one_entry函数也在roms/seabios/src/fw/pciinit.c中(就在上边),代码如下:

  1. static void
  2. pci_region_map_one_entry(struct pci_region_entry *entry, u64 addr)
  3. {
  4. if (entry->bar >= 0) {
  5. dprintf(1, "PCI: map device bdf=%pP"
  6. " bar %d, addr %08llx, size %08llx [%s]\n",
  7. entry->dev,
  8. entry->bar, addr, entry->size, region_type_name[entry->type]);
  9. pci_set_io_region_addr(entry->dev, entry->bar, addr, entry->is64);
  10. return;
  11. }
  12. u16 bdf = entry->dev->bdf;
  13. u64 limit = addr + entry->size - 1;
  14. if (entry->type == PCI_REGION_TYPE_IO) {
  15. pci_config_writeb(bdf, PCI_IO_BASE, addr >> PCI_IO_SHIFT);
  16. pci_config_writew(bdf, PCI_IO_BASE_UPPER16, 0);
  17. pci_config_writeb(bdf, PCI_IO_LIMIT, limit >> PCI_IO_SHIFT);
  18. pci_config_writew(bdf, PCI_IO_LIMIT_UPPER16, 0);
  19. }
  20. if (entry->type == PCI_REGION_TYPE_MEM) {
  21. pci_config_writew(bdf, PCI_MEMORY_BASE, addr >> PCI_MEMORY_SHIFT);
  22. pci_config_writew(bdf, PCI_MEMORY_LIMIT, limit >> PCI_MEMORY_SHIFT);
  23. }
  24. if (entry->type == PCI_REGION_TYPE_PREFMEM) {
  25. pci_config_writew(bdf, PCI_PREF_MEMORY_BASE, addr >> PCI_PREF_MEMORY_SHIFT);
  26. pci_config_writew(bdf, PCI_PREF_MEMORY_LIMIT, limit >> PCI_PREF_MEMORY_SHIFT);
  27. pci_config_writel(bdf, PCI_PREF_BASE_UPPER32, addr >> 32);
  28. pci_config_writel(bdf, PCI_PREF_LIMIT_UPPER32, limit >> 32);
  29. }
  30. }

pci_set_io_region_addr函数同样在roms/seabios/src/fw/pciinit.c中,代码如下:

  1. static void
  2. pci_set_io_region_addr(struct pci_device *pci, int bar, u64 addr, int is64)
  3. {
  4. u32 ofs = pci_bar(pci, bar);
  5. pci_config_writel(pci->bdf, ofs, addr);
  6. if (is64)
  7. pci_config_writel(pci->bdf, ofs + 4, addr >> 32);
  8. }

(1)pci_bar函数用来得到需要写入的BAR在PCI配置空间的地址。

pci_bar函数在hw/pci/pci.c中,代码如下:

  1. int pci_bar(PCIDevice *d, int reg)
  2. {
  3. uint8_t type;
  4. /* PCIe virtual functions do not have their own BARs */
  5. assert(!pci_is_vf(d));
  6. if (reg != PCI_ROM_SLOT)
  7. return PCI_BASE_ADDRESS_0 + reg * 4;
  8. type = d->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
  9. return type == PCI_HEADER_TYPE_BRIDGE ? PCI_ROM_ADDRESS1 : PCI_ROM_ADDRESS;
  10. }

(2)然后调用pci_cobfig_writel函数。

pci_config_writel函数在roms/qemu-palcode/pci.h中,代码如下:

  1. static inline void pci_config_writel(int bdf, uint8_t addr, uint32_t val)
  2. {
  3. *(volatile uint32_t *)(pci_conf_base + bdf * 256 + addr) = val;
  4. }
  5. static inline void pci_config_writew(int bdf, uint8_t addr, uint16_t val)
  6. {
  7. *(volatile uint16_t *)(pci_conf_base + bdf * 256 + addr) = val;
  8. }
  9. static inline void pci_config_writeb(int bdf, uint8_t addr, uint8_t val)
  10. {
  11. *(volatile uint8_t *)(pci_conf_base + bdf * 256 + addr) = val;
  12. }

通过上述过程,SeaBIOS就把所有设备的BAR基址赋值好了。

欲知后事如何,且看下回分解。

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他