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

88 篇文章 19 订阅
本文深入解析QEMU中PCI设备模拟的pci_register_bar函数,讲解PCIIORegion结构体的作用,以及MMIO在虚拟机中的处理流程。内容涉及MMIO的地址、大小、类型初始化,配置空间的设置,以及MMIO访问如何触发VM Exit并由QEMU进行模拟处理。
摘要由CSDN通过智能技术生成

接前一篇文章:

上一回讲到了pci_edu_realize函数中的pci_register_bar函数,本回对于其进行详细解析。

再次贴出pci_register_bar函数源码,在hw/ pci /pci.c中,代码如下:

  1. void pci_register_bar(PCIDevice *pci_dev, int region_num,
  2. uint8_t type, MemoryRegion *memory)
  3. {
  4. PCIIORegion *r;
  5. uint32_t addr; /* offset in pci config space */
  6. uint64_t wmask;
  7. pcibus_t size = memory_region_size(memory);
  8. uint8_t hdr_type;
  9. assert(!pci_is_vf(pci_dev)); /* VFs must use pcie_sriov_vf_register_bar */
  10. assert(region_num >= 0);
  11. assert(region_num < PCI_NUM_REGIONS);
  12. assert(is_power_of_2(size));
  13. /* A PCI bridge device (with Type 1 header) may only have at most 2 BARs */
  14. hdr_type =
  15. pci_dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
  16. assert(hdr_type != PCI_HEADER_TYPE_BRIDGE || region_num < 2);
  17. r = &pci_dev->io_regions[region_num];
  18. r->addr = PCI_BAR_UNMAPPED;
  19. r->size = size;
  20. r->type = type;
  21. r->memory = memory;
  22. r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
  23. ? pci_get_bus(pci_dev)->address_space_io
  24. : pci_get_bus(pci_dev)->address_space_mem;
  25. wmask = ~(size - 1);
  26. if (region_num == PCI_ROM_SLOT) {
  27. /* ROM enable bit is writable */
  28. wmask |= PCI_ROM_ADDRESS_ENABLE;
  29. }
  30. addr = pci_bar(pci_dev, region_num);
  31. pci_set_long(pci_dev->config + addr, type);
  32. if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
  33. r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
  34. pci_set_quad(pci_dev->wmask + addr, wmask);
  35. pci_set_quad(pci_dev->cmask + addr, ~0ULL);
  36. } else {
  37. pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
  38. pci_set_long(pci_dev->cmask + addr, 0xffffffff);
  39. }
  40. }

(1)首先根据region_num找到PCIDevice->io_regions数组中对应的项。PCI设备的MMIO存放在PCIIORegion 结构体 中,结构体中保存了MMIO的地址、大小、类型等信息。

代码片段如下:

    r = &pci_dev->io_regions[region_num];

(2)得到region_num表示的PCIIORegion之后,进行一些 初始化 设置。

  1. r->addr = PCI_BAR_UNMAPPED;
  2. r->size = size;
  3. r->type = type;
  4. r->memory = memory;
  5. r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
  6. ? pci_get_bus(pci_dev)->address_space_io
  7. : pci_get_bus(pci_dev)->address_space_mem;

(3)然后将该region的type写到相应PCI配置空间对应BAR的地址处。代码片段如下:

  1. r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
  2. ? pci_get_bus(pci_dev)->address_space_io
  3. : pci_get_bus(pci_dev)->address_space_mem;

(4)最后设置PCI Device中wmask和cmask的值。代码片段如下:

  1. wmask = ~(size - 1);
  2. if (region_num == PCI_ROM_SLOT) {
  3. /* ROM enable bit is writable */
  4. wmask |= PCI_ROM_ADDRESS_ENABLE;
  5. }
  6. addr = pci_bar(pci_dev, region_num);
  7. pci_set_long(pci_dev->config + addr, type);
  8. if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
  9. r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
  10. pci_set_quad(pci_dev->wmask + addr, wmask);
  11. pci_set_quad(pci_dev->cmask + addr, ~0ULL);
  12. } else {
  13. pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
  14. pci_set_long(pci_dev->cmask + addr, 0xffffffff);
  15. }

操作系统与PCI设备交互的主要方式是PIO和MMIO。MMIO虽然是一段内存,但是其没有EPT映射,在 虚拟机 访问设备的MMIO时,会产生VM Exit;KVM识别此MMIO访问并且将该访问分派到应用层QEMU中;QEMU根据内存虚拟化的步骤进行分派,找到设备注册的MMIO读写回调函数;设备的MMIO读写回调函数根据设备的功能进行模拟,完成模拟之后可能会发送中断到虚拟机中,从而完成一些MMIO访问。

下一回将开始解析edu设备的MMIO读写函数。

举报

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