接前一篇文章:

上一回讲到了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 config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;assert(hdr_type != PCI_HEADER_TYPE_BRIDGE || region_num 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读写函数。