接前一篇文章:
上一回讲解了virtio_pci_device_plugged函数的第3、4两部分,本回继续讲解virtio_pci_device_plugged函数的其余部分。为了便于理解,再次贴出virtio_pci_device_plugged函数源码,在hw/virtio/virtio-pci.c中,如下:
/* This is called by virtio-bus just after the device is plugged. */
static void virtio_pci_device_plugged(DeviceState *d, Error **errp)
{VirtIOPCIProxy *proxy = VIRTIO_PCI(d);VirtioBusState *bus = &proxy->bus;bool legacy = virtio_pci_legacy(proxy);bool modern;bool modern_pio = proxy->flags & VIRTIO_PCI_FLAG_MODERN_PIO_NOTIFY;uint8_t *config;uint32_t size;VirtIODevice *vdev = virtio_bus_get_device(&proxy->bus);/** Virtio capabilities present without* VIRTIO_F_VERSION_1 confuses guests*/if (!proxy->ignore_backend_features &&!virtio_has_feature(vdev->host_features, VIRTIO_F_VERSION_1)) {virtio_pci_disable_modern(proxy);if (!legacy) {error_setg(errp, "Device doesn't support modern mode, and legacy"" mode is disabled");error_append_hint(errp, "Set disable-legacy to off\n");return;}}modern = virtio_pci_modern(proxy);config = proxy->pci_dev.config;if (proxy->class_code) {pci_config_set_class(config, proxy->class_code);}if (legacy) {if (!virtio_legacy_allowed(vdev)) {/** To avoid migration issues, we allow legacy mode when legacy* check is disabled in the old machine types (< 5.1).*/if (virtio_legacy_check_disabled(vdev)) {warn_report("device is modern-only, but for backward ""compatibility legacy is allowed");} else {error_setg(errp,"device is modern-only, use disable-legacy=on");return;}}if (virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM)) {error_setg(errp, "VIRTIO_F_IOMMU_PLATFORM was supported by"" neither legacy nor transitional device");return;}/** Legacy and transitional devices use specific subsystem IDs.* Note that the subsystem vendor ID (config + PCI_SUBSYSTEM_VENDOR_ID)* is set to PCI_SUBVENDOR_ID_REDHAT_QUMRANET by default.*/pci_set_word(config + PCI_SUBSYSTEM_ID, virtio_bus_get_vdev_id(bus));if (proxy->trans_devid) {pci_config_set_device_id(config, proxy->trans_devid);}} else {/* pure virtio-1.0 */pci_set_word(config + PCI_VENDOR_ID,PCI_VENDOR_ID_REDHAT_QUMRANET);pci_set_word(config + PCI_DEVICE_ID,PCI_DEVICE_ID_VIRTIO_10_BASE + virtio_bus_get_vdev_id(bus));pci_config_set_revision(config, 1);}config[PCI_INTERRUPT_PIN] = 1;if (modern) {struct virtio_pci_cap cap = {.cap_len = sizeof cap,};struct virtio_pci_notify_cap notify = {.cap.cap_len = sizeof notify,.notify_off_multiplier =cpu_to_le32(virtio_pci_queue_mem_mult(proxy)),};struct virtio_pci_cfg_cap cfg = {.cap.cap_len = sizeof cfg,.cap.cfg_type = VIRTIO_PCI_CAP_PCI_CFG,};struct virtio_pci_notify_cap notify_pio = {.cap.cap_len = sizeof notify,.notify_off_multiplier = cpu_to_le32(0x0),};struct virtio_pci_cfg_cap *cfg_mask;virtio_pci_modern_regions_init(proxy, vdev->name);virtio_pci_modern_mem_region_map(proxy, &proxy->common, &cap);virtio_pci_modern_mem_region_map(proxy, &proxy->isr, &cap);virtio_pci_modern_mem_region_map(proxy, &proxy->device, &cap);virtio_pci_modern_mem_region_map(proxy, &proxy->notify, ¬ify.cap);if (modern_pio) {memory_region_init(&proxy->io_bar, OBJECT(proxy),"virtio-pci-io", 0x4);pci_register_bar(&proxy->pci_dev, proxy->modern_io_bar_idx,PCI_BASE_ADDRESS_SPACE_IO, &proxy->io_bar);virtio_pci_modern_io_region_map(proxy, &proxy->notify_pio,¬ify_pio.cap);}pci_register_bar(&proxy->pci_dev, proxy->modern_mem_bar_idx,PCI_BASE_ADDRESS_SPACE_MEMORY |PCI_BASE_ADDRESS_MEM_PREFETCH |PCI_BASE_ADDRESS_MEM_TYPE_64,&proxy->modern_bar);proxy->config_cap = virtio_pci_add_mem_cap(proxy, &cfg.cap);cfg_mask = (void *)(proxy->pci_dev.wmask + proxy->config_cap);pci_set_byte(&cfg_mask->cap.bar, ~0x0);pci_set_long((uint8_t *)&cfg_mask->cap.offset, ~0x0);pci_set_long((uint8_t *)&cfg_mask->cap.length, ~0x0);pci_set_long(cfg_mask->pci_cfg_data, ~0x0);}if (proxy->nvectors) {int err = msix_init_exclusive_bar(&proxy->pci_dev, proxy->nvectors,proxy->msix_bar_idx, NULL);if (err) {/* Notice when a system that supports MSIx can't initialize it */if (err != -ENOTSUP) {warn_report("unable to init msix vectors to %" PRIu32,proxy->nvectors);}proxy->nvectors = 0;}}proxy->pci_dev.config_write = virtio_write_config;proxy->pci_dev.config_read = virtio_read_config;if (legacy) {size = VIRTIO_PCI_REGION_SIZE(&proxy->pci_dev)+ virtio_bus_get_vdev_config_len(bus);size = pow2ceil(size);memory_region_init_io(&proxy->bar, OBJECT(proxy),&virtio_pci_config_ops,proxy, "virtio-pci", size);pci_register_bar(&proxy->pci_dev, proxy->legacy_io_bar_idx,PCI_BASE_ADDRESS_SPACE_IO, &proxy->bar);}
}
(5)virtio_pci_device_plugged函数接着调用pci_register_bar函数,将VirtIOPCIProxy的modern_bar这一MemoryRegion注册到系统中。代码片段如下:
pci_register_bar(&proxy->pci_dev, proxy->modern_mem_bar_idx,PCI_BASE_ADDRESS_SPACE_MEMORY |PCI_BASE_ADDRESS_MEM_PREFETCH |PCI_BASE_ADDRESS_MEM_TYPE_64,&proxy->modern_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);}
}
(6)接下来,调用msix_init_exclusive_bar函数注册与msi中断有关的数据。代码片段如下:
if (proxy->nvectors) {int err = msix_init_exclusive_bar(&proxy->pci_dev, proxy->nvectors,proxy->msix_bar_idx, NULL);if (err) {/* Notice when a system that supports MSIx can't initialize it */if (err != -ENOTSUP) {warn_report("unable to init msix vectors to %" PRIu32,proxy->nvectors);}proxy->nvectors = 0;}}
msix_init_exclusive_bar函数在hw/pci/msix.c中,代码如下:
int msix_init_exclusive_bar(PCIDevice *dev, unsigned short nentries,uint8_t bar_nr, Error **errp)
{int ret;char *name;uint32_t bar_size = 4096;uint32_t bar_pba_offset = bar_size / 2;uint32_t bar_pba_size = QEMU_ALIGN_UP(nentries, 64) / 8;/** Migration compatibility dictates that this remains a 4k* BAR with the vector table in the lower half and PBA in* the upper half for nentries which is lower or equal to 128.* No need to care about using more than 65 entries for legacy* machine types who has at most 64 queues.*/if (nentries * PCI_MSIX_ENTRY_SIZE > bar_pba_offset) {bar_pba_offset = nentries * PCI_MSIX_ENTRY_SIZE;}if (bar_pba_offset + bar_pba_size > 4096) {bar_size = bar_pba_offset + bar_pba_size;}bar_size = pow2ceil(bar_size);name = g_strdup_printf("%s-msix", dev->name);memory_region_init(&dev->msix_exclusive_bar, OBJECT(dev), name, bar_size);g_free(name);ret = msix_init(dev, nentries, &dev->msix_exclusive_bar, bar_nr,0, &dev->msix_exclusive_bar,bar_nr, bar_pba_offset,0, errp);if (ret) {return ret;}pci_register_bar(dev, bar_nr, PCI_BASE_ADDRESS_SPACE_MEMORY,&dev->msix_exclusive_bar);return 0;
}
(7)virtio_pci_device_plugged函数还会将VirtPCIProxy设备PCI配置空间的读写函数分别设置成virtio_write_config和virtio_read_config。代码片段如下:
proxy->pci_dev.config_write = virtio_write_config;proxy->pci_dev.config_read = virtio_read_config;
virtio_pci_device_plugged函数经过一系列的函数调用,就在QEMU侧准备好了virtio balloon设备。其它设备与此类似。
通过图来整体总结一下:
- virtio PCI代理设备与virtio设备的相关类型的继承关系如下图所示:
- virtio设备初始化过程中涉及的相关函数及其所对应的类型如下图所示:
预知后事如何,且看下回分解。