接前一篇文章:
QEMU模拟的设备很多都是PCI设备,本节介绍PCI设备的模拟。与所有设备类似,PCI设备的父设备也是TYPE_DEVICE,其定义在QEMU源码根目录/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,
};
static void pci_register_types(void)
{
type_register_static(&pci_bus_info);
type_register_static(&pcie_bus_info);
type_register_static(&cxl_bus_info);
type_register_static(&conventional_pci_interface_info);
type_register_static(&cxl_interface_info);
type_register_static(&pcie_interface_info);
type_register_static(&pci_device_type_info);
}
type_init(pci_register_types)
上一回讲解了PCI设备的具现化函数pci_qdev_realize()所完成的三个任务中的第二个,本回接着往下讲。
(3)最后,调用pci_add_option_rom函数加载PCI设备的ROM。
代码片段如下:
pci_add_option_rom(pci_dev, is_default_rom, &local_err);
if (local_err) {
error_propagate(errp, local_err);
pci_qdev_unrealize(DEVICE(pci_dev));
return;
}
有的设备有自己的ROM(有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置,如显卡、键盘和硬盘等设备。为了实现这个“预先执行”的功能,PCI设备需要提供一段ROM程序),如果QEMU命令行没有指定ROM,但是设备的class指定了ROM,那就使用默认的ROM。这种情况下,pci_add_option_rom函数的参数is_default_rom设置为true;否则使用QEMU命令行传过来的romfile文件。
pci_add_option_rom函数同样在hw/pci/pci.c中,代码如下:
/* Add an option rom for the device */
static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom,
Error **errp)
{
int64_t size = 0;
g_autofree char *path = NULL;
char name[32];
const VMStateDescription *vmsd;
/*
* In case of incoming migration ROM will come with migration stream, no
* reason to load the file. Neither we want to fail if local ROM file
* mismatches with specified romsize.
*/
bool load_file = !runstate_check(RUN_STATE_INMIGRATE);
if (!pdev->romfile || !strlen(pdev->romfile)) {
return;
}
if (!pdev->rom_bar) {
/*
* Load rom via fw_cfg instead of creating a rom bar,
* for 0.11 compatibility.
*/
int class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
/*
* Hot-plugged devices can't use the option ROM
* if the rom bar is disabled.
*/
if (DEVICE(pdev)->hotplugged) {
error_setg(errp, "Hot-plugged device without ROM bar"
" can't have an option ROM");
return;
}
if (class == 0x0300) {
rom_add_vga(pdev->romfile);
} else {
rom_add_option(pdev->romfile, -1);
}
return;
}
if (load_file || pdev->romsize == -1) {
path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile);
if (path == NULL) {
path = g_strdup(pdev->romfile);
}
size = get_image_size(path);
if (size < 0) {
error_setg(errp, "failed to find romfile \"%s\"", pdev->romfile);
return;
} else if (size == 0) {
error_setg(errp, "romfile \"%s\" is empty", pdev->romfile);
return;
} else if (size > 2 * GiB) {
error_setg(errp,
"romfile \"%s\" too large (size cannot exceed 2 GiB)",
pdev->romfile);
return;
}
if (pdev->romsize != -1) {
if (size > pdev->romsize) {
error_setg(errp, "romfile \"%s\" (%u bytes) "
"is too large for ROM size %u",
pdev->romfile, (uint32_t)size, pdev->romsize);
return;
}
} else {
pdev->romsize = pow2ceil(size);
}
}
vmsd = qdev_get_vmsd(DEVICE(pdev));
snprintf(name, sizeof(name), "%s.rom",
vmsd ? vmsd->name : object_get_typename(OBJECT(pdev)));
pdev->has_rom = true;
memory_region_init_rom(&pdev->rom, OBJECT(pdev), name, pdev->romsize,
&error_fatal);
if (load_file) {
void *ptr = memory_region_get_ram_ptr(&pdev->rom);
if (load_image_size(path, ptr, size) < 0) {
error_setg(errp, "failed to load romfile \"%s\"", pdev->romfile);
return;
}
if (is_default_rom) {
/* Only the default rom images will be patched (if needed). */
pci_patch_ids(pdev, ptr, size);
}
}
pci_register_bar(pdev, PCI_ROM_SLOT, 0, &pdev->rom);
}
1)pci_add_option_rom函数按照pdev->rom_bar是否为true,来决定是使用fw_cfg中的文件还是创建一个rom_bar。代码片段如下:
if (!pdev->rom_bar) {
/*
* Load rom via fw_cfg instead of creating a rom bar,
* for 0.11 compatibility.
*/
int class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
/*
* Hot-plugged devices can't use the option ROM
* if the rom bar is disabled.
*/
if (DEVICE(pdev)->hotplugged) {
error_setg(errp, "Hot-plugged device without ROM bar"
" can't have an option ROM");
return;
}
if (class == 0x0300) {
rom_add_vga(pdev->romfile);
} else {
rom_add_option(pdev->romfile, -1);
}
return;
}
pdev->rom_bar默认为true。
2)接着是得到ROM文件的路径和大小。代码片段如下:
if (load_file || pdev->romsize == -1) {
path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile);
if (path == NULL) {
path = g_strdup(pdev->romfile);
}
size = get_image_size(path);
if (size < 0) {
error_setg(errp, "failed to find romfile \"%s\"", pdev->romfile);
return;
} else if (size == 0) {
error_setg(errp, "romfile \"%s\" is empty", pdev->romfile);
return;
} else if (size > 2 * GiB) {
error_setg(errp,
"romfile \"%s\" too large (size cannot exceed 2 GiB)",
pdev->romfile);
return;
}
if (pdev->romsize != -1) {
if (size > pdev->romsize) {
error_setg(errp, "romfile \"%s\" (%u bytes) "
"is too large for ROM size %u",
pdev->romfile, (uint32_t)size, pdev->romsize);
return;
}
} else {
pdev->romsize = pow2ceil(size);
}
}
3)然后,调用load_image_size函数将文件加载到指定的ROM中。代码片段如下:
if (load_file) {
void *ptr = memory_region_get_ram_ptr(&pdev->rom);
if (load_image_size(path, ptr, size) < 0) {
error_setg(errp, "failed to load romfile \"%s\"", pdev->romfile);
return;
}
if (is_default_rom) {
/* Only the default rom images will be patched (if needed). */
pci_patch_ids(pdev, ptr, size);
}
}
4)最后,调用pci_register_bar函数将该ROM注册到设备的PCI bar中。代码片段如下:
pci_register_bar(pdev, PCI_ROM_SLOT, 0, &pdev->rom);
至此,PCI设备的具现化函数pci_qdev_realize()所完成的三个任务中的第3个也就是最后一个函数pci_add_option_rom()就讲解完了。那么,整个pci_qdev_realize函数也就解析完了。
欲知后事如何,且看下回分解。