QEMU源码全解析 —— PCI设备模拟(4)

发布时间:2024年01月12日

接前一篇文章:

2. PCI设备的模拟

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函数也就解析完了。

欲知后事如何,且看下回分解。

文章来源:https://blog.csdn.net/phmatthaus/article/details/135538846
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。