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

88 篇文章 19 订阅
本文深入剖析QEMU PCI设备模拟,重点讲解配置空间的初始化,包括设置中断引脚、MSI中断配置,以及通过memory_region_init_io创建MMIO区域并使用pci_register_bar注册为设备的BAR0。通过对pci_edu_realize函数的解析,展示了PCI设备模拟的具体实现细节。
摘要由CSDN通过智能技术生成

接前一篇文章:

前边就几回讲解了 PCI 设备的具现化函数pci_qdev_realize(),主要解析了其所完成的三个任务。

在PCI设备的模拟中, 不需要关注与电气相关的部分,只需要关注与操作系统的接口部分 。设备与 操作系统 接口主要包括PCI设备的配置空间以及PCI设备的寄存器基址 所有PCI设备的基类都是TYPE_PCI_DEVICE 所有PCI设备在初始化时都会分配一块PCI配置空间 ,保存在PCIDevice的config中,do_pci_register_device函数会初始化PCI配置空间的基本数据。在具体设备进行具现化的函数中,会初始化PCI配置空间的一些其它数据,还会分配设备需要的其它资源。具体设备的具现化函数囊括在用于调用 具体PCI设备类的具现化函数 中,也就是上述pci_qdev_realize函数过程中的第二部分(其次,pci_qdev_realize函数调用 PCI设备所属的class的realize函数 ,即pc->realize函数)。

下面以EDU类设备对应的具现化函数pci_edu_realize()为例,查看相关的配置。pci_edu_realize函数在hw/misc/edu.c中,代码如下:

  1. static void pci_edu_realize(PCIDevice *pdev, Error **errp)
  2. {
  3. EduState *edu = EDU(pdev);
  4. uint8_t *pci_conf = pdev->config;
  5. pci_config_set_interrupt_pin(pci_conf, 1);
  6. if (msi_init(pdev, 0, 1, true, false, errp)) {
  7. return;
  8. }
  9. timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
  10. qemu_mutex_init(&edu->thr_mutex);
  11. qemu_cond_init(&edu->thr_cond);
  12. qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
  13. edu, QEMU_THREAD_JOINABLE);
  14. memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
  15. "edu-mmio", 1 * MiB);
  16. pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
  17. }

其被赋值的地方也在hw/misc/edu.c中,代码如下:

  1. static void edu_class_init(ObjectClass *class, void *data)
  2. {
  3. DeviceClass *dc = DEVICE_CLASS(class);
  4. PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
  5. k->realize = pci_edu_realize;
  6. k->exit = pci_edu_uninit;
  7. k->vendor_id = PCI_VENDOR_ID_QEMU;
  8. k->device_id = 0x11e8;
  9. k->revision = 0x10;
  10. k->class_id = PCI_CLASS_OTHERS;
  11. set_bit(DEVICE_CATEGORY_MISC, dc->categories);
  12. }

下面开始对于pci_edu_realize函数的解析。

(1)pci_config_set_interrupt_pin设置了PCI设备配置空间的PCI_INTERRUPT_PIN字节。

代码片段如下:

    pci_config_set_interrupt_pin(pci_conf, 1);

pci_config_set_interrupt_pin函数在include/hw/pci/pci.h中,代码如下:

  1. static inline void
  2. pci_config_set_interrupt_pin(uint8_t *pci_config, uint8_t val)
  3. {
  4. pci_set_byte(&pci_config[PCI_INTERRUPT_PIN], val);
  5. }

(2)msi_init函数会设置PCI配置空间与MSI中断相关的数据。

代码片段如下:

  1. if (msi_init(pdev, 0, 1, true, false, errp)) {
  2. return;
  3. }

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

  1. /*
  2. * Make PCI device @dev MSI-capable.
  3. * Non-zero @offset puts capability MSI at that offset in PCI config
  4. * space.
  5. * @nr_vectors is the number of MSI vectors (1, 2, 4, 8, 16 or 32).
  6. * If @msi64bit, make the device capable of sending a 64-bit message
  7. * address.
  8. * If @msi_per_vector_mask, make the device support per-vector masking.
  9. * @errp is for returning errors.
  10. * Return 0 on success; set @errp and return -errno on error.
  11. *
  12. * -ENOTSUP means lacking msi support for a msi-capable platform.
  13. * -EINVAL means capability overlap, happens when @offset is non-zero,
  14. * also means a programming error, except device assignment, which can check
  15. * if a real HW is broken.
  16. */
  17. int msi_init(struct PCIDevice *dev, uint8_t offset,
  18. unsigned int nr_vectors, bool msi64bit,
  19. bool msi_per_vector_mask, Error **errp)
  20. {
  21. unsigned int vectors_order;
  22. uint16_t flags;
  23. uint8_t cap_size;
  24. int config_offset;
  25. if (!msi_nonbroken) {
  26. error_setg(errp, "MSI is not supported by interrupt controller");
  27. return -ENOTSUP;
  28. }
  29. MSI_DEV_PRINTF(dev,
  30. "init offset: 0x%"PRIx8" vector: %"PRId8
  31. " 64bit %d mask %d\n",
  32. offset, nr_vectors, msi64bit, msi_per_vector_mask);
  33. assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
  34. assert(nr_vectors > 0);
  35. assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
  36. /* the nr of MSI vectors is up to 32 */
  37. vectors_order = ctz32(nr_vectors);
  38. flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
  39. if (msi64bit) {
  40. flags |= PCI_MSI_FLAGS_64BIT;
  41. }
  42. if (msi_per_vector_mask) {
  43. flags |= PCI_MSI_FLAGS_MASKBIT;
  44. }
  45. cap_size = msi_cap_sizeof(flags);
  46. config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset,
  47. cap_size, errp);
  48. if (config_offset < 0) {
  49. return config_offset;
  50. }
  51. dev->msi_cap = config_offset;
  52. dev->cap_present |= QEMU_PCI_CAP_MSI;
  53. pci_set_word(dev->config + msi_flags_off(dev), flags);
  54. pci_set_word(dev->wmask + msi_flags_off(dev),
  55. PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
  56. pci_set_long(dev->wmask + msi_address_lo_off(dev),
  57. PCI_MSI_ADDRESS_LO_MASK);
  58. if (msi64bit) {
  59. pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
  60. }
  61. pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
  62. if (msi_per_vector_mask) {
  63. /* Make mask bits 0 to nr_vectors - 1 writable. */
  64. pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
  65. 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors));
  66. }
  67. dev->msi_prepare_message = msi_prepare_message;
  68. return 0;
  69. }

(3)memory_region_init_io函数初始化了一个edu->mmio,表示的是该设备的MMIO,其大小为1MB。

代码片段如下:

  1. memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
  2. "edu-mmio", 1 * MiB);

memory_region_init_io函数在softmmu/memory.c中,代码如下:

  1. void memory_region_init_io(MemoryRegion *mr,
  2. Object *owner,
  3. const MemoryRegionOps *ops,
  4. void *opaque,
  5. const char *name,
  6. uint64_t size)
  7. {
  8. memory_region_init(mr, owner, name, size);
  9. mr->ops = ops ? ops : &unassigned_mem_ops;
  10. mr->opaque = opaque;
  11. mr->terminates = true;
  12. }

(4)最后调用pci_register_bar函数,将该MMIO注册为设备的第0号BAR。

代码片段如下:

    pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);

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. }

对于pci_register_bar函数的详细解析,请看下回。

举报

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