QEMU源码全解析 —— virtio(16)

88 篇文章 19 订阅
本文详细介绍了QEMU中virtio_pci_device_plugged函数的工作流程,包括调用pci_register_bar注册内存区域,使用msix_init_exclusive_bar处理MSI中断,以及设置PCI配置空间的读写函数。这些步骤构成了QEMU为virtio balloon设备准备的基础。通过图表展示了virtio PCI代理设备与virtio设备的继承关系以及初始化过程中的关键函数。
摘要由CSDN通过智能技术生成

接前一篇文章:

上一回讲解了virtio_pci_device_plugged函数的第二部分的前两小部分,本回继续讲解virtio_pci_device_plugged函数的其余部分。为了便于理解,再次贴出virtio_pci_device_plugged函数源码,在hw/ virtio /virtio-pci.c中,如下:

  1. /* This is called by virtio-bus just after the device is plugged. */
  2. static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
  3. {
  4. VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
  5. VirtioBusState *bus = &proxy->bus;
  6. bool legacy = virtio_pci_legacy(proxy);
  7. bool modern;
  8. bool modern_pio = proxy->flags & VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY;
  9. uint8_t *config;
  10. uint32_t size;
  11. VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);
  12. /*
  13. * Virtio capabilities present without
  14. * VIRTIO_F_VERSION_1 confuses guests
  15. */
  16. if (!proxy->ignore_backend_features &&
  17. !virtio_has_feature(vdev->host_features, VIRTIO_F_VERSION_1)) {
  18. virtio_pci_disable_modern(proxy);
  19. if (!legacy) {
  20. error_setg(errp, "Device doesn't support modern mode, and legacy"
  21. " mode is disabled");
  22. error_append_hint(errp, "Set disable-legacy to off\n");
  23. return;
  24. }
  25. }
  26. modern = virtio_pci_modern(proxy);
  27. config = proxy->pci_dev.config;
  28. if (proxy->class_code) {
  29. pci_config_set_class(config, proxy->class_code);
  30. }
  31. if (legacy) {
  32. if (!virtio_legacy_allowed(vdev)) {
  33. /*
  34. * To avoid migration issues, we allow legacy mode when legacy
  35. * check is disabled in the old machine types (< 5.1).
  36. */
  37. if (virtio_legacy_check_disabled(vdev)) {
  38. warn_report("device is modern-only, but for backward "
  39. "compatibility legacy is allowed");
  40. } else {
  41. error_setg(errp,
  42. "device is modern-only, use disable-legacy=on");
  43. return;
  44. }
  45. }
  46. if (virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM)) {
  47. error_setg(errp, "VIRTIO_F_IOMMU_PLATFORM was supported by"
  48. " neither legacy nor transitional device");
  49. return;
  50. }
  51. /*
  52. * Legacy and transitional devices use specific subsystem IDs.
  53. * Note that the subsystem vendor ID (config + PCI_SUBSYSTEM_VENDOR_ID)
  54. * is set to PCI_SUBVENDOR_ID_REDHAT_QUMRANET by default.
  55. */
  56. pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));
  57. if (proxy->trans_devid) {
  58. pci_config_set_device_id(config, proxy->trans_devid);
  59. }
  60. } else {
  61. /* pure virtio-1.0 */
  62. pci_set_word(config + PCI_VENDOR_ID,
  63. PCI_VENDOR_ID_REDHAT_QUMRANET);
  64. pci_set_word(config + PCI_DEVICE_ID,
  65. PCI_DEVICE_ID_VIRTIO_10_BASE + virtio_bus_get_vdev_id(bus));
  66. pci_config_set_revision(config, 1);
  67. }
  68. config[PCI_INTERRUPT_PIN] = 1;
  69. if (modern) {
  70. struct virtio_pci_cap cap = {
  71. .cap_len = sizeof cap,
  72. };
  73. struct virtio_pci_notify_cap notify = {
  74. .cap.cap_len = sizeof notify,
  75. .notify_off_multiplier =
  76. cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),
  77. };
  78. struct virtio_pci_cfg_cap cfg = {
  79. .cap.cap_len = sizeof cfg,
  80. .cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,
  81. };
  82. struct virtio_pci_notify_cap notify_pio = {
  83. .cap.cap_len = sizeof notify,
  84. .notify_off_multiplier = cpu_to_le32(0x0),
  85. };
  86. struct virtio_pci_cfg_cap *cfg_mask;
  87. virtio_pci_modern_regions_init(proxy, vdev->name);
  88. virtio_pci_modern_mem_region_map(proxy, &proxy->common, &cap);
  89. virtio_pci_modern_mem_region_map(proxy, &proxy->isr, &cap);
  90. virtio_pci_modern_mem_region_map(proxy, &proxy->device, &cap);
  91. virtio_pci_modern_mem_region_map(proxy, &proxy->notify, &notify.cap);
  92. if (modern_pio) {
  93. memory_region_init(&proxy->io_bar, OBJECT(proxy),
  94. "virtio-pci-io", 0x4);
  95. pci_register_bar(&proxy->pci_dev, proxy->modern_io_bar_idx,
  96. PCI_BASE_ADDRESS_SPACE_IO, &proxy->io_bar);
  97. virtio_pci_modern_io_region_map(proxy, &proxy->notify_pio,
  98. &notify_pio.cap);
  99. }
  100. pci_register_bar(&proxy->pci_dev, proxy->modern_mem_bar_idx,
  101. PCI_BASE_ADDRESS_SPACE_MEMORY |
  102. PCI_BASE_ADDRESS_MEM_PREFETCH |
  103. PCI_BASE_ADDRESS_MEM_TYPE_64,
  104. &proxy->modern_bar);
  105. proxy->config_cap = virtio_pci_add_mem_cap(proxy, &cfg.cap);
  106. cfg_mask = (void *)(proxy->pci_dev.wmask + proxy->config_cap);
  107. pci_set_byte(&cfg_mask->cap.bar, ~0x0);
  108. pci_set_long((uint8_t *)&cfg_mask->cap.offset, ~0x0);
  109. pci_set_long((uint8_t *)&cfg_mask->cap.length, ~0x0);
  110. pci_set_long(cfg_mask->pci_cfg_data, ~0x0);
  111. }
  112. if (proxy->nvectors) {
  113. int err = msix_init_exclusive_bar(&proxy->pci_dev, proxy->nvectors,
  114. proxy->msix_bar_idx, NULL);
  115. if (err) {
  116. /* Notice when a system that supports MSIx can't initialize it */
  117. if (err != -ENOTSUP) {
  118. warn_report("unable to init msix vectors to %" PRIu32,
  119. proxy->nvectors);
  120. }
  121. proxy->nvectors = 0;
  122. }
  123. }
  124. proxy->pci_dev.config_write = virtio_write_config;
  125. proxy->pci_dev.config_read = virtio_read_config;
  126. if (legacy) {
  127. size = VIRTIO_PCI_REGION_SIZE(&proxy->pci_dev)
  128. + virtio_bus_get_vdev_config_len(bus);
  129. size = pow2ceil(size);
  130. memory_region_init_io(&proxy->bar, OBJECT(proxy),
  131. &virtio_pci_config_ops,
  132. proxy, "virtio-pci", size);
  133. pci_register_bar(&proxy->pci_dev, proxy->legacy_io_bar_idx,
  134. PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar);
  135. }
  136. }

(2.3)virtio_pci_device_plugged函数接着调用pci_register_bar函数,将VirtIOPCIProxy的modern_bar这一MemoryRegion注册到系统中。代码片段如下:

  1. pci_register_bar(&proxy->pci_dev, proxy->modern_mem_bar_idx,
  2. PCI_BASE_ADDRESS_SPACE_MEMORY |
  3. PCI_BASE_ADDRESS_MEM_PREFETCH |
  4. PCI_BASE_ADDRESS_MEM_TYPE_64,
  5. &proxy->modern_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. }

(3)接下来,调用msix_init_exclusive_bar函数注册与 msi 中断有关的数据。代码片段如下:

  1. if (proxy->nvectors) {
  2. int err = msix_init_exclusive_bar(&proxy->pci_dev, proxy->nvectors,
  3. proxy->msix_bar_idx, NULL);
  4. if (err) {
  5. /* Notice when a system that supports MSIx can't initialize it */
  6. if (err != -ENOTSUP) {
  7. warn_report("unable to init msix vectors to %" PRIu32,
  8. proxy->nvectors);
  9. }
  10. proxy->nvectors = 0;
  11. }
  12. }

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

  1. int msix_init_exclusive_bar(PCIDevice *dev, unsigned short nentries,
  2. uint8_t bar_nr, Error **errp)
  3. {
  4. int ret;
  5. char *name;
  6. uint32_t bar_size = 4096;
  7. uint32_t bar_pba_offset = bar_size / 2;
  8. uint32_t bar_pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;
  9. /*
  10. * Migration compatibility dictates that this remains a 4k
  11. * BAR with the vector table in the lower half and PBA in
  12. * the upper half for nentries which is lower or equal to 128.
  13. * No need to care about using more than 65 entries for legacy
  14. * machine types who has at most 64 queues.
  15. */
  16. if (nentries * PCI_MSIX_ENTRY_SIZE > bar_pba_offset) {
  17. bar_pba_offset = nentries * PCI_MSIX_ENTRY_SIZE;
  18. }
  19. if (bar_pba_offset + bar_pba_size > 4096) {
  20. bar_size = bar_pba_offset + bar_pba_size;
  21. }
  22. bar_size = pow2ceil(bar_size);
  23. name = g_strdup_printf("%s-msix", dev->name);
  24. memory_region_init(&dev->msix_exclusive_bar, OBJECT(dev), name, bar_size);
  25. g_free(name);
  26. ret = msix_init(dev, nentries, &dev->msix_exclusive_bar, bar_nr,
  27. 0, &dev->msix_exclusive_bar,
  28. bar_nr, bar_pba_offset,
  29. 0, errp);
  30. if (ret) {
  31. return ret;
  32. }
  33. pci_register_bar(dev, bar_nr, PCI_BASE_ADDRESS_SPACE_MEMORY,
  34. &dev->msix_exclusive_bar);
  35. return 0;
  36. }

(4)virtio_pci_device_plugged函数还会将VirtPCIProxy设备PCI配置空间的读写函数分别设置成virtio_write_config和virtio_read_config。代码片段如下:

  1. proxy->pci_dev.config_write = virtio_write_config;
  2. proxy->pci_dev.config_read = virtio_read_config;

virtio_pci_device_plugged函数经过一系列的函数调用,就在QEMU侧准备好了virtio balloon设备。其它设备与此类似。

通过图来整体总结一下:

  • virtio PCI代理设备与virtio设备的相关类型的继承关系如下图所示:

  • virtio设备初始化过程中涉及的相关函数及其所对应的类型如下图所示:

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

举报

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