接前一篇文章:
前边就几回讲解了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函数的详细解析,请看下回。