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

88 篇文章 19 订阅
本文深入解析QEMU中PCI设备模拟的实现,重点讲解pci_default_write_config函数如何设置BAR基址,并通过pci_update_mappings完成内存映射。通过这个过程,QEMU建立内存分派表,使得虚拟机能够正确处理MMIO访问。
摘要由CSDN通过智能技术生成

接前一篇文章:

通过前文,SeaBIOS就把所有设备的BAR基址赋值好了。接下来解析与BAR地址设置相关的代码。pci_default_write_config函数在hw/ pci /pci.c中,代码如下:

  1. void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val_in, int l)
  2. {
  3. int i, was_irq_disabled = pci_irq_disabled(d);
  4. uint32_t val = val_in;
  5. assert(addr + l <= pci_config_size(d));
  6. for (i = 0; i < l; val >>= 8, ++i) {
  7. uint8_t wmask = d->wmask[addr + i];
  8. uint8_t w1cmask = d->w1cmask[addr + i];
  9. assert(!(wmask & w1cmask));
  10. d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask);
  11. d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
  12. }
  13. if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) ||
  14. ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) ||
  15. ranges_overlap(addr, l, PCI_ROM_ADDRESS1, 4) ||
  16. range_covers_byte(addr, l, PCI_COMMAND))
  17. pci_update_mappings(d);
  18. if (ranges_overlap(addr, l, PCI_COMMAND, 2)) {
  19. pci_update_irq_disabled(d, was_irq_disabled);
  20. memory_region_set_enabled(&d->bus_master_enable_region,
  21. (pci_get_word(d->config + PCI_COMMAND)
  22. & PCI_COMMAND_MASTER) && d->has_power);
  23. }
  24. msi_write_config(d, addr, val_in, l);
  25. msix_write_config(d, addr, val_in, l);
  26. pcie_sriov_config_write(d, addr, val_in, l);
  27. }

pci_default_write_config函数首先将传过来的值写入PCI设备的配置空间,因为地址是BAR的地址,所以会调用pci_update_mapping s函数 。该函数也在hw/pci/pci.c中,代码如下:

  1. static void pci_update_mappings(PCIDevice *d)
  2. {
  3. PCIIORegion *r;
  4. int i;
  5. pcibus_t new_addr;
  6. for(i = 0; i < PCI_NUM_REGIONS; i++) {
  7. r = &d->io_regions[i];
  8. /* this region isn't registered */
  9. if (!r->size)
  10. continue;
  11. new_addr = pci_bar_address(d, i, r->type, r->size);
  12. if (!d->has_power) {
  13. new_addr = PCI_BAR_UNMAPPED;
  14. }
  15. /* This bar isn't changed */
  16. if (new_addr == r->addr)
  17. continue;
  18. /* now do the real mapping */
  19. if (r->addr != PCI_BAR_UNMAPPED) {
  20. trace_pci_update_mappings_del(d->name, pci_dev_bus_num(d),
  21. PCI_SLOT(d->devfn),
  22. PCI_FUNC(d->devfn),
  23. i, r->addr, r->size);
  24. memory_region_del_subregion(r->address_space, r->memory);
  25. }
  26. r->addr = new_addr;
  27. if (r->addr != PCI_BAR_UNMAPPED) {
  28. trace_pci_update_mappings_add(d->name, pci_dev_bus_num(d),
  29. PCI_SLOT(d->devfn),
  30. PCI_FUNC(d->devfn),
  31. i, r->addr, r->size);
  32. memory_region_add_subregion_overlap(r->address_space,
  33. r->addr, r->memory, 1);
  34. }
  35. }
  36. pci_update_vga(d);
  37. }

1)首先从pci_bar_address函数中得到BAR的地址。代码片段如下:

  1. new_addr = pci_bar_address(d, i, r->type, r->size);
  2. if (!d->has_power) {
  3. new_addr = PCI_BAR_UNMAPPED;
  4. }

2)然后 复制到 io_regions中的addr。代码片段如下:

  1. /* now do the real mapping */
  2. if (r->addr != PCI_BAR_UNMAPPED) {
  3. trace_pci_update_mappings_del(d->name, pci_dev_bus_num(d),
  4. PCI_SLOT(d->devfn),
  5. PCI_FUNC(d->devfn),
  6. i, r->addr, r->size);
  7. memory_region_del_subregion(r->address_space, r->memory);
  8. }
  9. r->addr = new_addr;

3)最后调用memory_region_add_subregion_overlap函数将该地址添加到PCI MemoryRegion,作为它的一个子MR。代码片段如下:

  1. if (r->addr != PCI_BAR_UNMAPPED) {
  2. trace_pci_update_mappings_add(d->name, pci_dev_bus_num(d),
  3. PCI_SLOT(d->devfn),
  4. PCI_FUNC(d->devfn),
  5. i, r->addr, r->size);
  6. memory_region_add_subregion_overlap(r->address_space,
  7. r->addr, r->memory, 1);
  8. }

4)再经过内存提交,就会在QEMU中建立起内存分派表。从而在 虚拟机 访问这块MMIO内存的时候,由其对应设备的回调函数来处理。

至此,PCI设备的模拟就解析完了。

举报

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