接前一篇文章:
前边就几回讲解了 PCI 设备的具现化函数pci_qdev_realize(),主要解析了其所完成的三个任务。
在PCI设备的模拟中, 不需要关注与电气相关的部分,只需要关注与操作系统的接口部分 。设备与 操作系统 的 接口主要包括PCI设备的配置空间以及PCI设备的寄存器基址 。 所有PCI设备的基类都是TYPE_PCI_DEVICE , 所有PCI设备在初始化时都会分配一块PCI配置空间 ,保存在PCIDevice的config中,do_pci_register_device函数会初始化PCI配置空间的基本数据。在具体设备进行具现化的函数中,会初始化PCI配置空间的一些其它数据,还会分配设备需要的其它资源。具体设备的具现化函数囊括在用于调用 具体PCI设备类的具现化函数 中,也就是上述pci_qdev_realize函数过程中的第二部分(其次,pci_qdev_realize函数调用 PCI设备所属的class的realize函数 ,即pc->realize函数)。
下面以EDU类设备对应的具现化函数pci_edu_realize()为例,查看相关的配置。pci_edu_realize函数在hw/misc/edu.c中,代码如下:
- static void pci_edu_realize(PCIDevice *pdev, Error **errp)
- {
- EduState *edu = EDU(pdev);
- uint8_t *pci_conf = pdev->config;
-
- pci_config_set_interrupt_pin(pci_conf, 1);
-
- if (msi_init(pdev, 0, 1, true, false, errp)) {
- return;
- }
-
- timer_init_ms(&edu->dma_timer, QEMU_CLOCK_VIRTUAL, edu_dma_timer, edu);
-
- qemu_mutex_init(&edu->thr_mutex);
- qemu_cond_init(&edu->thr_cond);
- qemu_thread_create(&edu->thread, "edu", edu_fact_thread,
- edu, QEMU_THREAD_JOINABLE);
-
- memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
- "edu-mmio", 1 * MiB);
- pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
- }
其被赋值的地方也在hw/misc/edu.c中,代码如下:
- static void edu_class_init(ObjectClass *class, void *data)
- {
- DeviceClass *dc = DEVICE_CLASS(class);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(class);
-
- k->realize = pci_edu_realize;
- k->exit = pci_edu_uninit;
- k->vendor_id = PCI_VENDOR_ID_QEMU;
- k->device_id = 0x11e8;
- k->revision = 0x10;
- k->class_id = PCI_CLASS_OTHERS;
- set_bit(DEVICE_CATEGORY_MISC, dc->categories);
- }
下面开始对于pci_edu_realize函数的解析。
(1)pci_config_set_interrupt_pin设置了PCI设备配置空间的PCI_INTERRUPT_PIN字节。
代码片段如下:
pci_config_set_interrupt_pin(pci_conf, 1);
pci_config_set_interrupt_pin函数在include/hw/pci/pci.h中,代码如下:
- static inline void
- pci_config_set_interrupt_pin(uint8_t *pci_config, uint8_t val)
- {
- pci_set_byte(&pci_config[PCI_INTERRUPT_PIN], val);
- }
(2)msi_init函数会设置PCI配置空间与MSI中断相关的数据。
代码片段如下:
- if (msi_init(pdev, 0, 1, true, false, errp)) {
- return;
- }
msi_init函数在hw/pci/msi.c中,代码如下:
- /*
- * Make PCI device @dev MSI-capable.
- * Non-zero @offset puts capability MSI at that offset in PCI config
- * space.
- * @nr_vectors is the number of MSI vectors (1, 2, 4, 8, 16 or 32).
- * If @msi64bit, make the device capable of sending a 64-bit message
- * address.
- * If @msi_per_vector_mask, make the device support per-vector masking.
- * @errp is for returning errors.
- * Return 0 on success; set @errp and return -errno on error.
- *
- * -ENOTSUP means lacking msi support for a msi-capable platform.
- * -EINVAL means capability overlap, happens when @offset is non-zero,
- * also means a programming error, except device assignment, which can check
- * if a real HW is broken.
- */
- int msi_init(struct PCIDevice *dev, uint8_t offset,
- unsigned int nr_vectors, bool msi64bit,
- bool msi_per_vector_mask, Error **errp)
- {
- unsigned int vectors_order;
- uint16_t flags;
- uint8_t cap_size;
- int config_offset;
-
- if (!msi_nonbroken) {
- error_setg(errp, "MSI is not supported by interrupt controller");
- return -ENOTSUP;
- }
-
- MSI_DEV_PRINTF(dev,
- "init offset: 0x%"PRIx8" vector: %"PRId8
- " 64bit %d mask %d\n",
- offset, nr_vectors, msi64bit, msi_per_vector_mask);
-
- assert(!(nr_vectors & (nr_vectors - 1))); /* power of 2 */
- assert(nr_vectors > 0);
- assert(nr_vectors <= PCI_MSI_VECTORS_MAX);
- /* the nr of MSI vectors is up to 32 */
- vectors_order = ctz32(nr_vectors);
-
- flags = vectors_order << ctz32(PCI_MSI_FLAGS_QMASK);
- if (msi64bit) {
- flags |= PCI_MSI_FLAGS_64BIT;
- }
- if (msi_per_vector_mask) {
- flags |= PCI_MSI_FLAGS_MASKBIT;
- }
-
- cap_size = msi_cap_sizeof(flags);
- config_offset = pci_add_capability(dev, PCI_CAP_ID_MSI, offset,
- cap_size, errp);
- if (config_offset < 0) {
- return config_offset;
- }
-
- dev->msi_cap = config_offset;
- dev->cap_present |= QEMU_PCI_CAP_MSI;
-
- pci_set_word(dev->config + msi_flags_off(dev), flags);
- pci_set_word(dev->wmask + msi_flags_off(dev),
- PCI_MSI_FLAGS_QSIZE | PCI_MSI_FLAGS_ENABLE);
- pci_set_long(dev->wmask + msi_address_lo_off(dev),
- PCI_MSI_ADDRESS_LO_MASK);
- if (msi64bit) {
- pci_set_long(dev->wmask + msi_address_hi_off(dev), 0xffffffff);
- }
- pci_set_word(dev->wmask + msi_data_off(dev, msi64bit), 0xffff);
-
- if (msi_per_vector_mask) {
- /* Make mask bits 0 to nr_vectors - 1 writable. */
- pci_set_long(dev->wmask + msi_mask_off(dev, msi64bit),
- 0xffffffff >> (PCI_MSI_VECTORS_MAX - nr_vectors));
- }
-
- dev->msi_prepare_message = msi_prepare_message;
-
- return 0;
- }
(3)memory_region_init_io函数初始化了一个edu->mmio,表示的是该设备的MMIO,其大小为1MB。
代码片段如下:
- memory_region_init_io(&edu->mmio, OBJECT(edu), &edu_mmio_ops, edu,
- "edu-mmio", 1 * MiB);
memory_region_init_io函数在softmmu/memory.c中,代码如下:
- void memory_region_init_io(MemoryRegion *mr,
- Object *owner,
- const MemoryRegionOps *ops,
- void *opaque,
- const char *name,
- uint64_t size)
- {
- memory_region_init(mr, owner, name, size);
- mr->ops = ops ? ops : &unassigned_mem_ops;
- mr->opaque = opaque;
- mr->terminates = true;
- }
(4)最后调用pci_register_bar函数,将该MMIO注册为设备的第0号BAR。
代码片段如下:
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &edu->mmio);
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);
- }
- }
对于pci_register_bar函数的详细解析,请看下回。