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_mem
、bus->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_read
,pci_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_read
,pci_dev->config_write
。这两个默认注册的就是pci_default_read_config
、pci_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);
}
wmask
、w1cmask
在设备初始化的时候根据设备注册的空间大小来的,这里模拟了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
下的没有什么本质的区别,读写模拟基本也是走的一样的逻辑,性能也不会更高。