接前一篇文章:
上一回讲到了pci_edu_realize函数中的pci_register_bar函数,本回对于其进行详细解析。
再次贴出pci_register_bar函数源码,在hw/ pci /pci.c中,代码如下:
- void pci_register_bar(PCIDevice *pci_dev, int region_num,
- uint8_t type, MemoryRegion *memory)
- {
- PCIIORegion *r;
- uint32_t addr; /* offset in pci config space */
- uint64_t wmask;
- pcibus_t size = memory_region_size(memory);
- uint8_t hdr_type;
-
- assert(!pci_is_vf(pci_dev)); /* VFs must use pcie_sriov_vf_register_bar */
- assert(region_num >= 0);
- assert(region_num < PCI_NUM_REGIONS);
- assert(is_power_of_2(size));
-
- /* A PCI bridge device (with Type 1 header) may only have at most 2 BARs */
- hdr_type =
- pci_dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
- assert(hdr_type != PCI_HEADER_TYPE_BRIDGE || region_num < 2);
-
- r = &pci_dev->io_regions[region_num];
- r->addr = PCI_BAR_UNMAPPED;
- r->size = size;
- r->type = type;
- r->memory = memory;
- r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
- ? pci_get_bus(pci_dev)->address_space_io
- : pci_get_bus(pci_dev)->address_space_mem;
-
- wmask = ~(size - 1);
- if (region_num == PCI_ROM_SLOT) {
- /* ROM enable bit is writable */
- wmask |= PCI_ROM_ADDRESS_ENABLE;
- }
-
- addr = pci_bar(pci_dev, region_num);
- pci_set_long(pci_dev->config + addr, type);
-
- if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
- r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
- pci_set_quad(pci_dev->wmask + addr, wmask);
- pci_set_quad(pci_dev->cmask + addr, ~0ULL);
- } else {
- pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
- pci_set_long(pci_dev->cmask + addr, 0xffffffff);
- }
- }
(1)首先根据region_num找到PCIDevice->io_regions数组中对应的项。PCI设备的MMIO存放在PCIIORegion 结构体 中,结构体中保存了MMIO的地址、大小、类型等信息。
代码片段如下:
r = &pci_dev->io_regions[region_num];
(2)得到region_num表示的PCIIORegion之后,进行一些 初始化 设置。
- r->addr = PCI_BAR_UNMAPPED;
- r->size = size;
- r->type = type;
- r->memory = memory;
- r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
- ? pci_get_bus(pci_dev)->address_space_io
- : pci_get_bus(pci_dev)->address_space_mem;
(3)然后将该region的type写到相应PCI配置空间对应BAR的地址处。代码片段如下:
- r->address_space = type & PCI_BASE_ADDRESS_SPACE_IO
- ? pci_get_bus(pci_dev)->address_space_io
- : pci_get_bus(pci_dev)->address_space_mem;
(4)最后设置PCI Device中wmask和cmask的值。代码片段如下:
- wmask = ~(size - 1);
- if (region_num == PCI_ROM_SLOT) {
- /* ROM enable bit is writable */
- wmask |= PCI_ROM_ADDRESS_ENABLE;
- }
-
- addr = pci_bar(pci_dev, region_num);
- pci_set_long(pci_dev->config + addr, type);
-
- if (!(r->type & PCI_BASE_ADDRESS_SPACE_IO) &&
- r->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
- pci_set_quad(pci_dev->wmask + addr, wmask);
- pci_set_quad(pci_dev->cmask + addr, ~0ULL);
- } else {
- pci_set_long(pci_dev->wmask + addr, wmask & 0xffffffff);
- pci_set_long(pci_dev->cmask + addr, 0xffffffff);
- }
操作系统与PCI设备交互的主要方式是PIO和MMIO。MMIO虽然是一段内存,但是其没有EPT映射,在 虚拟机 访问设备的MMIO时,会产生VM Exit;KVM识别此MMIO访问并且将该访问分派到应用层QEMU中;QEMU根据内存虚拟化的步骤进行分派,找到设备注册的MMIO读写回调函数;设备的MMIO读写回调函数根据设备的功能进行模拟,完成模拟之后可能会发送中断到虚拟机中,从而完成一些MMIO访问。
下一回将开始解析edu设备的MMIO读写函数。