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

88 篇文章 19 订阅
本文深入解析QEMU中PCI设备模拟,特别是北桥的PCI部分,包括PCIHostState结构体,配置地址及数据寄存器的工作原理,以及相关内存区域的初始化和管理。内容涵盖配置地址寄存器CONFADDR、配置数据寄存器CONFDATA的读写过程,以及PCI设备寻址和配置空间访问机制。
摘要由CSDN通过智能技术生成

接前一篇文章:

上一回介绍了普通设备的模拟,这里介绍一个特殊的设备——北桥的I/O模拟。

北桥的PCI部分由结构体PCIHostState表示 。北桥的PCI部分有 两个I/O寄存器 :其中 一个是配置地址寄存器,叫作CONFGADDR ,其对应的MemoryRegion保存在PCIHostState的 conf_mem 成员中,该 寄存器 的作用是选择PCI设备。 另一个寄存器是配置数据寄存器,叫作CONFDATA ,其对应的MemoryRegion保存在PCIHostState的 data_mem 成员中, 当其CONFADDR寄存器最高位为1时,这个寄存器用来对CONFADDR中指定的设备进行配置

这两段MMIO地址是在北桥的instance_init函数i440fx_pcihost_initfn中初始化的。i440fx_pcihost_initfn函数在hw/pci-host/i440fx.c中,代码如下:

  1. static void i440fx_pcihost_initfn(Object *obj)
  2. {
  3. I440FXState *s = I440FX_PCI_HOST_BRIDGE(obj);
  4. PCIHostState *phb = PCI_HOST_BRIDGE(obj);
  5. memory_region_init_io(&phb->conf_mem, obj, &pci_host_conf_le_ops, phb,
  6. "pci-conf-idx", 4);
  7. memory_region_init_io(&phb->data_mem, obj, &pci_host_data_le_ops, phb,
  8. "pci-conf-data", 4);
  9. object_property_add_link(obj, PCI_HOST_PROP_RAM_MEM, TYPE_MEMORY_REGION,
  10. (Object **) &s->ram_memory,
  11. qdev_prop_allow_set_link_before_realize, 0);
  12. object_property_add_link(obj, PCI_HOST_PROP_PCI_MEM, TYPE_MEMORY_REGION,
  13. (Object **) &s->pci_address_space,
  14. qdev_prop_allow_set_link_before_realize, 0);
  15. object_property_add_link(obj, PCI_HOST_PROP_SYSTEM_MEM, TYPE_MEMORY_REGION,
  16. (Object **) &s->system_memory,
  17. qdev_prop_allow_set_link_before_realize, 0);
  18. object_property_add_link(obj, PCI_HOST_PROP_IO_MEM, TYPE_MEMORY_REGION,
  19. (Object **) &s->io_memory,
  20. qdev_prop_allow_set_link_before_realize, 0);
  21. }

其对应的MemoryRegionOps分别是pci_host_conf_le_ops和pci_host_data_le_ops。I/O地址的注册是在北桥的具现化函数i440fx_pcihost_realize中完成的。该函数也在hw/pci-host/i440fx.c中(就在下边),代码如下:

  1. static void i440fx_pcihost_realize(DeviceState *dev, Error **errp)
  2. {
  3. ERRP_GUARD();
  4. I440FXState *s = I440FX_PCI_HOST_BRIDGE(dev);
  5. PCIHostState *phb = PCI_HOST_BRIDGE(dev);
  6. SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
  7. PCIBus *b;
  8. PCIDevice *d;
  9. PCII440FXState *f;
  10. unsigned i;
  11. memory_region_add_subregion(s->io_memory, 0xcf8, &phb->conf_mem);
  12. sysbus_init_ioports(sbd, 0xcf8, 4);
  13. memory_region_add_subregion(s->io_memory, 0xcfc, &phb->data_mem);
  14. sysbus_init_ioports(sbd, 0xcfc, 4);
  15. /* register i440fx 0xcf8 port as coalesced pio */
  16. memory_region_set_flush_coalesced(&phb->data_mem);
  17. memory_region_add_coalescing(&phb->conf_mem, 0, 4);
  18. b = pci_root_bus_new(dev, NULL, s->pci_address_space,
  19. s->io_memory, 0, TYPE_PCI_BUS);
  20. phb->bus = b;
  21. d = pci_create_simple(b, 0, s->pci_type);
  22. f = I440FX_PCI_DEVICE(d);
  23. range_set_bounds(&s->pci_hole, s->below_4g_mem_size,
  24. IO_APIC_DEFAULT_ADDRESS - 1);
  25. /* setup pci memory mapping */
  26. pc_pci_as_mapping_init(s->system_memory, s->pci_address_space);
  27. /* if *disabled* show SMRAM to all CPUs */
  28. memory_region_init_alias(&f->smram_region, OBJECT(d), "smram-region",
  29. s->pci_address_space, SMRAM_C_BASE, SMRAM_C_SIZE);
  30. memory_region_add_subregion_overlap(s->system_memory, SMRAM_C_BASE,
  31. &f->smram_region, 1);
  32. memory_region_set_enabled(&f->smram_region, true);
  33. /* smram, as seen by SMM CPUs */
  34. memory_region_init(&f->smram, OBJECT(d), "smram", 4 * GiB);
  35. memory_region_set_enabled(&f->smram, true);
  36. memory_region_init_alias(&f->low_smram, OBJECT(d), "smram-low",
  37. s->ram_memory, SMRAM_C_BASE, SMRAM_C_SIZE);
  38. memory_region_set_enabled(&f->low_smram, true);
  39. memory_region_add_subregion(&f->smram, SMRAM_C_BASE, &f->low_smram);
  40. object_property_add_const_link(qdev_get_machine(), "smram",
  41. OBJECT(&f->smram));
  42. init_pam(&f->pam_regions[0], OBJECT(d), s->ram_memory, s->system_memory,
  43. s->pci_address_space, PAM_BIOS_BASE, PAM_BIOS_SIZE);
  44. for (i = 0; i < ARRAY_SIZE(f->pam_regions) - 1; ++i) {
  45. init_pam(&f->pam_regions[i + 1], OBJECT(d), s->ram_memory,
  46. s->system_memory, s->pci_address_space,
  47. PAM_EXPAN_BASE + i * PAM_EXPAN_SIZE, PAM_EXPAN_SIZE);
  48. }
  49. ram_addr_t ram_size = s->below_4g_mem_size + s->above_4g_mem_size;
  50. ram_size = ram_size / 8 / 1024 / 1024;
  51. if (ram_size > 255) {
  52. ram_size = 255;
  53. }
  54. d->config[I440FX_COREBOOT_RAM_SIZE] = ram_size;
  55. i440fx_update_memory_mappings(f);
  56. }

memory_region_add_subregion函数会将指定MemoryRegion设置为系统I/O地址空间的子MemoryRegion。sysbus_init_ioports会对SysBusDevice中的PIO端口数组初始化。i440fx_pcihost_realize函数将北桥的CONFADDR和CONFDATA两个寄存器地址加入到系统I/O地址空间中。其中,CONFADDR使用从端口0xcf8开始的4个端口,CONFDATA使用从0xcfc开始的4个端口。

这部分相关知识参见笔者博文 《PCI Express体系结构导读》随记 —— 第I篇 第2章 PCI总线的桥与配置(10)

写CONFADDR的行为会设置配置寄存器的值,指定选择的PCI设备,用于随后的数据访问。pci_host_config_write函数在hw/pci/pci_host.c中,代码如下:

  1. static void pci_host_config_write(void *opaque, hwaddr addr,
  2. uint64_t val, unsigned len)
  3. {
  4. PCIHostState *s = opaque;
  5. PCI_DPRINTF("%s addr " HWADDR_FMT_plx " len %d val %"PRIx64"\n",
  6. __func__, addr, len, val);
  7. if (addr != 0 || len != 4) {
  8. return;
  9. }
  10. s->config_reg = val;
  11. }

pci_host_config_write函数将 虚拟机 选中的PCI设备地址保存在了PCIHostState的config_reg寄存器中。CONFADDR必须通过4个字节访问。从Intel 440FX PCIset手册中(见上图)可知写入CONFADDR寄存器的数据的含义。 CONFADDR寄存器的第31位表示是否使能PCI设备的配置功能 如果要想读写PCI设备的配置空间,需要将该位设置为1 ;第24位到第30位为保留位; 第16位到23位表示设置PCI总线号 第11位到15位表示设置选择的总线上面的PCI设备号 第8位到第10位表示选择总线上面设备号对应PCI设备的功能号 第2位到第7位表示选定的总线、设备、功能号对应的PCI设备的寄存器值 ;第0位到第1位为保留位。

综上,CONFADDR寄存器指定了PCI设备的地址,当选定了PCI设备之后就可以向PC配置空间写数据了。下面是写CONFDATA寄存器的值。

pci_host_data_write函数也在hw/pci/pci_host.c中,代码如下:

  1. static void pci_host_data_write(void *opaque, hwaddr addr,
  2. uint64_t val, unsigned len)
  3. {
  4. PCIHostState *s = opaque;
  5. if (s->config_reg & (1u << 31))
  6. pci_data_write(s->bus, s->config_reg | (addr & 3), val, len);
  7. }

首先判断配置寄存器的值中的第31位是否为1(使能),使能的情况下调用pci_data_write函数开始写设备的配置空间。

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

  1. void pci_data_write(PCIBus *s, uint32_t addr, uint32_t val, unsigned len)
  2. {
  3. PCIDevice *pci_dev = pci_dev_find_by_addr(s, addr);
  4. uint32_t config_addr = addr & (PCI_CONFIG_SPACE_SIZE - 1);
  5. if (!pci_dev) {
  6. trace_pci_cfg_write("empty", extract32(addr, 16, 8),
  7. extract32(addr, 11, 5), extract32(addr, 8, 3),
  8. config_addr, val);
  9. return;
  10. }
  11. pci_host_config_write_common(pci_dev, config_addr, PCI_CONFIG_SPACE_SIZE,
  12. val, len);
  13. }

pci_data_write函数首先通过CONFADDR中的值,调用pci_dev_find_by_addr函数找到需要访问的PCI设备;然后再调用pci_host_config_write_common函数读写该设备的PCI配置空间。值得注意的是,addr和(PCI_CONIG_SPACE_SIZE-1)进行与操作,将addr限制在了PCI配置空间的大小256字节以内。

PCI_CONIG_SPACE_SIZE的定义在include/hw/pci/pci.h中,如下:

  1. /* Size of the standard PCI config space */
  2. #define PCI_CONFIG_SPACE_SIZE 0x100

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

  1. void pci_host_config_write_common(PCIDevice *pci_dev, uint32_t addr,
  2. uint32_t limit, uint32_t val, uint32_t len)
  3. {
  4. pci_adjust_config_limit(pci_get_bus(pci_dev), &limit);
  5. if (limit <= addr) {
  6. return;
  7. }
  8. assert(len <= 4);
  9. /* non-zero functions are only exposed when function 0 is present,
  10. * allowing direct removal of unexposed functions.
  11. */
  12. if ((pci_dev->qdev.hotplugged && !pci_get_function_0(pci_dev)) ||
  13. !pci_dev->has_power || is_pci_dev_ejected(pci_dev)) {
  14. return;
  15. }
  16. trace_pci_cfg_write(pci_dev->name, pci_dev_bus_num(pci_dev),
  17. PCI_SLOT(pci_dev->devfn),
  18. PCI_FUNC(pci_dev->devfn), addr, val);
  19. pci_dev->config_write(pci_dev, addr, val, MIN(len, limit - addr));
  20. }

pci_host_config_write_common函数在做一些基本的检查之后,调用了设备自己的config_write回调函数。

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

举报

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