qemu虚拟化-pci框架

pci大体上比较类似sysbus,理解的时候最好类比。pci主要的工作是帮助pci设备管理configure的配置、模拟configure中的内存映射事件。老规矩,框架的解说从注册开始

1.pci总线的注册

设备的注册最早从pci_register_root_bus开始,一般是由具体的架构初始化的时候调用。下面看一下具体流程
hw/pci/pci.c

->pci_register_root_bus
	->pci_root_bus_new
		->qbus_create
		->pci_root_bus_init
			->bus->address_space_mem = address_space_mem;
			->bus->address_space_io = address_space_io;
			->pci_host_bus_register

pci总线的注册跟sysbus区别不大,主要是注册了两个新的地址空间bus->address_space_membus->address_space_io

看一下设备的注册

2.pci设备的注册

pci的注册逻辑很大一部分在父类PCIDevice中,需要先来看看父类的逻辑,看一下父类注册的类型
hw/pci/pci.c

static const TypeInfo pci_device_type_info = {
    .name = TYPE_PCI_DEVICE,
    .parent = TYPE_DEVICE,
    .instance_size = sizeof(PCIDevice),
    .abstract = true,
    .class_size = sizeof(PCIDeviceClass),
    .class_init = pci_device_class_init,
    .class_base_init = pci_device_class_base_init,
};

hw/pci/pci.c

static void pci_device_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *k = DEVICE_CLASS(klass);

    k->realize = pci_qdev_realize;
    k->unrealize = pci_qdev_unrealize;
    k->bus_type = TYPE_PCI_BUS;
    k->props = pci_props;
}

父类的初始化函数主要是pci_qdev_realize,看一下流程
hw/pci/pci.c

->pci_qdev_realize
	->do_pci_register_device
		->memory_region_init
		->address_space_init
		->pci_config_set_vendor_id
		->pci_config_set_device_id
		->pci_config_set_revision
		->pci_config_set_class
		->pci_init_cmask
		->pci_init_wmask
		->pci_init_w1cmask
		->pci_init_mask_bridge
		->pci_init_multifunction
		->pci_dev->config_read = config_read;
		->pci_dev->config_write = config_write;
	->pc->realize

pci_dev->config_readpci_dev->config_write注册了configure的读写逻辑,后面详细分析。
最后调用子设备的pc->realize函数完成子类中的初始化逻辑。

父类初始化完了,看一下设备的注册。设备的注册入口是pci_register_bar

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);

    assert(region_num >= 0);
    assert(region_num < PCI_NUM_REGIONS);
    if (size & (size-1)) {
        error_report("ERROR: PCI region size must be pow2 "
                    "type=0x%x, size=0x%"FMT_PCIBUS"", type, size);
        exit(1);
    }

    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);
    }
}

设备的注册逻辑比较简单,主要是设置一些剩下的参数。

3.pci设备的configure的配置模拟

pci设备的configure的配置模拟如果没有自己定义逻辑的话,使用pci.c中写好的即可,也就是上面说到的pci_dev->config_readpci_dev->config_write。这两个默认注册的就是pci_default_read_configpci_default_write_config。read操作没什么模拟的逻辑,主要看一下write的模拟逻辑。

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;

    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 (range_covers_byte(addr, l, PCI_COMMAND)) {
        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);
    }

    msi_write_config(d, addr, val_in, l);
    msix_write_config(d, addr, val_in, l);
}

wmaskw1cmask在设备初始化的时候根据设备注册的空间大小来的,这里模拟了pci设备mmio地址空间大小的探测,即写入全部的1,只有可配置的部分可以写入成功,不可写入的部分就是mmio的大小。具体pci驱动初始化细节这里不多讲了。
接上面地址配置好了之后pci_update_mappings会重新映射设备内存块,实现了guest的pci mmio映射模拟,看一下映射逻辑
hw/pci/pci.c

->pci_update_mappings
	->memory_region_del_subregion
	->memory_region_add_subregion_overlap

memory.c

->memory_region_add_subregion_overlap
	->memory_region_add_subregion_common
		->subregion->addr = offset;
		->memory_region_update_container_subregions
		->QTAILQ_INSERT_TAIL(&mr->subregions, subregion, subregions_link);

最后将内存插到子区域里面了subregions,在sysbus发生io与mmio的时候如果有子区域会先匹配子区域内存,这样就实现了另一个地址空间的模拟。到此就基本结束了,pci设备的模拟总的来说跟普通设备,也就是挂在sysbus下的没有什么本质的区别,读写模拟基本也是走的一样的逻辑,性能也不会更高。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值