接前一篇文章:
通过前文,SeaBIOS就把所有设备的BAR基址赋值好了。接下来解析与BAR地址设置相关的代码。pci_default_write_config函数在hw/ pci /pci.c中,代码如下:
- void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val_in, int l)
- {
- int i, was_irq_disabled = pci_irq_disabled(d);
- uint32_t val = val_in;
-
- assert(addr + l <= pci_config_size(d));
-
- for (i = 0; i < l; val >>= 8, ++i) {
- uint8_t wmask = d->wmask[addr + i];
- uint8_t w1cmask = d->w1cmask[addr + i];
- assert(!(wmask & w1cmask));
- d->config[addr + i] = (d->config[addr + i] & ~wmask) | (val & wmask);
- d->config[addr + i] &= ~(val & w1cmask); /* W1C: Write 1 to Clear */
- }
- if (ranges_overlap(addr, l, PCI_BASE_ADDRESS_0, 24) ||
- ranges_overlap(addr, l, PCI_ROM_ADDRESS, 4) ||
- ranges_overlap(addr, l, PCI_ROM_ADDRESS1, 4) ||
- range_covers_byte(addr, l, PCI_COMMAND))
- pci_update_mappings(d);
-
- if (ranges_overlap(addr, l, PCI_COMMAND, 2)) {
- pci_update_irq_disabled(d, was_irq_disabled);
- memory_region_set_enabled(&d->bus_master_enable_region,
- (pci_get_word(d->config + PCI_COMMAND)
- & PCI_COMMAND_MASTER) && d->has_power);
- }
-
- msi_write_config(d, addr, val_in, l);
- msix_write_config(d, addr, val_in, l);
- pcie_sriov_config_write(d, addr, val_in, l);
- }
pci_default_write_config函数首先将传过来的值写入PCI设备的配置空间,因为地址是BAR的地址,所以会调用pci_update_mapping s函数 。该函数也在hw/pci/pci.c中,代码如下:
- static void pci_update_mappings(PCIDevice *d)
- {
- PCIIORegion *r;
- int i;
- pcibus_t new_addr;
-
- for(i = 0; i < PCI_NUM_REGIONS; i++) {
- r = &d->io_regions[i];
-
- /* this region isn't registered */
- if (!r->size)
- continue;
-
- new_addr = pci_bar_address(d, i, r->type, r->size);
- if (!d->has_power) {
- new_addr = PCI_BAR_UNMAPPED;
- }
-
- /* This bar isn't changed */
- if (new_addr == r->addr)
- continue;
-
- /* now do the real mapping */
- if (r->addr != PCI_BAR_UNMAPPED) {
- trace_pci_update_mappings_del(d->name, pci_dev_bus_num(d),
- PCI_SLOT(d->devfn),
- PCI_FUNC(d->devfn),
- i, r->addr, r->size);
- memory_region_del_subregion(r->address_space, r->memory);
- }
- r->addr = new_addr;
- if (r->addr != PCI_BAR_UNMAPPED) {
- trace_pci_update_mappings_add(d->name, pci_dev_bus_num(d),
- PCI_SLOT(d->devfn),
- PCI_FUNC(d->devfn),
- i, r->addr, r->size);
- memory_region_add_subregion_overlap(r->address_space,
- r->addr, r->memory, 1);
- }
- }
-
- pci_update_vga(d);
- }
1)首先从pci_bar_address函数中得到BAR的地址。代码片段如下:
- new_addr = pci_bar_address(d, i, r->type, r->size);
- if (!d->has_power) {
- new_addr = PCI_BAR_UNMAPPED;
- }
2)然后 复制到 io_regions中的addr。代码片段如下:
- /* now do the real mapping */
- if (r->addr != PCI_BAR_UNMAPPED) {
- trace_pci_update_mappings_del(d->name, pci_dev_bus_num(d),
- PCI_SLOT(d->devfn),
- PCI_FUNC(d->devfn),
- i, r->addr, r->size);
- memory_region_del_subregion(r->address_space, r->memory);
- }
- r->addr = new_addr;
3)最后调用memory_region_add_subregion_overlap函数将该地址添加到PCI MemoryRegion,作为它的一个子MR。代码片段如下:
- if (r->addr != PCI_BAR_UNMAPPED) {
- trace_pci_update_mappings_add(d->name, pci_dev_bus_num(d),
- PCI_SLOT(d->devfn),
- PCI_FUNC(d->devfn),
- i, r->addr, r->size);
- memory_region_add_subregion_overlap(r->address_space,
- r->addr, r->memory, 1);
- }
4)再经过内存提交,就会在QEMU中建立起内存分派表。从而在 虚拟机 访问这块MMIO内存的时候,由其对应设备的回调函数来处理。
至此,PCI设备的模拟就解析完了。