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

88 篇文章 19 订阅
本文继续讲解QEMU模拟PCI设备的细节,主要涉及PCI设备的ROM加载过程。通过pci_add_option_rom函数,根据设备类设置加载默认或指定的ROM,并详细阐述了加载流程,包括检查(pdev->rom_bar),获取ROM文件,加载到内存以及注册到PCI BAR。
摘要由CSDN通过智能技术生成

接前一篇文章:

2. PCI 设备的模拟

QEMU 模拟的设备很多都是PCI设备,本节介绍PCI设备的模拟。与所有设备类似,PCI设备的父设备也是TYPE_DEVICE,其定义在QEMU源码根目录/hw/pci/pci.c中,代码如下:

  1. static const TypeInfo pci_device_type_info = {
  2. .name = TYPE_PCI_DEVICE,
  3. .parent = TYPE_DEVICE,
  4. .instance_size = sizeof(PCIDevice),
  5. .abstract = true,
  6. .class_size = sizeof(PCIDeviceClass),
  7. .class_init = pci_device_class_init,
  8. .class_base_init = pci_device_class_base_init,
  9. };
  10. static void pci_register_types(void)
  11. {
  12. type_register_static(&pci_bus_info);
  13. type_register_static(&pcie_bus_info);
  14. type_register_static(&cxl_bus_info);
  15. type_register_static(&conventional_pci_interface_info);
  16. type_register_static(&cxl_interface_info);
  17. type_register_static(&pcie_interface_info);
  18. type_register_static(&pci_device_type_info);
  19. }
  20. type_init(pci_register_types)

上一回讲解了PCI设备的具现化函数pci_qdev_realize()所完成的三个任务中的第二个,本回接着往下讲。

(3)最后,调用pci_add_option_rom函数加载PCI设备的ROM。

代码片段如下:

  1. /* rom loading */
  2. is_default_rom = false;
  3. if (pci_dev->romfile == NULL && pc->romfile != NULL) {
  4. pci_dev->romfile = g_strdup(pc->romfile);
  5. is_default_rom = true;
  6. }
  7. pci_add_option_rom(pci_dev, is_default_rom, &local_err);
  8. if (local_err) {
  9. error_propagate(errp, local_err);
  10. pci_qdev_unrealize(DEVICE(pci_dev));
  11. return;
  12. }

有的设备有自己的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中,代码如下:

  1. /* Add an option rom for the device */
  2. static void pci_add_option_rom(PCIDevice *pdev, bool is_default_rom,
  3. Error **errp)
  4. {
  5. int64_t size = 0;
  6. g_autofree char *path = NULL;
  7. char name[32];
  8. const VMStateDescription *vmsd;
  9. /*
  10. * In case of incoming migration ROM will come with migration stream, no
  11. * reason to load the file. Neither we want to fail if local ROM file
  12. * mismatches with specified romsize.
  13. */
  14. bool load_file = !runstate_check(RUN_STATE_INMIGRATE);
  15. if (!pdev->romfile || !strlen(pdev->romfile)) {
  16. return;
  17. }
  18. if (!pdev->rom_bar) {
  19. /*
  20. * Load rom via fw_cfg instead of creating a rom bar,
  21. * for 0.11 compatibility.
  22. */
  23. int class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
  24. /*
  25. * Hot-plugged devices can't use the option ROM
  26. * if the rom bar is disabled.
  27. */
  28. if (DEVICE(pdev)->hotplugged) {
  29. error_setg(errp, "Hot-plugged device without ROM bar"
  30. " can't have an option ROM");
  31. return;
  32. }
  33. if (class == 0x0300) {
  34. rom_add_vga(pdev->romfile);
  35. } else {
  36. rom_add_option(pdev->romfile, -1);
  37. }
  38. return;
  39. }
  40. if (load_file || pdev->romsize == -1) {
  41. path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile);
  42. if (path == NULL) {
  43. path = g_strdup(pdev->romfile);
  44. }
  45. size = get_image_size(path);
  46. if (size < 0) {
  47. error_setg(errp, "failed to find romfile \"%s\"", pdev->romfile);
  48. return;
  49. } else if (size == 0) {
  50. error_setg(errp, "romfile \"%s\" is empty", pdev->romfile);
  51. return;
  52. } else if (size > 2 * GiB) {
  53. error_setg(errp,
  54. "romfile \"%s\" too large (size cannot exceed 2 GiB)",
  55. pdev->romfile);
  56. return;
  57. }
  58. if (pdev->romsize != -1) {
  59. if (size > pdev->romsize) {
  60. error_setg(errp, "romfile \"%s\" (%u bytes) "
  61. "is too large for ROM size %u",
  62. pdev->romfile, (uint32_t)size, pdev->romsize);
  63. return;
  64. }
  65. } else {
  66. pdev->romsize = pow2ceil(size);
  67. }
  68. }
  69. vmsd = qdev_get_vmsd(DEVICE(pdev));
  70. snprintf(name, sizeof(name), "%s.rom",
  71. vmsd ? vmsd->name : object_get_typename(OBJECT(pdev)));
  72. pdev->has_rom = true;
  73. memory_region_init_rom(&pdev->rom, OBJECT(pdev), name, pdev->romsize,
  74. &error_fatal);
  75. if (load_file) {
  76. void *ptr = memory_region_get_ram_ptr(&pdev->rom);
  77. if (load_image_size(path, ptr, size) < 0) {
  78. error_setg(errp, "failed to load romfile \"%s\"", pdev->romfile);
  79. return;
  80. }
  81. if (is_default_rom) {
  82. /* Only the default rom images will be patched (if needed). */
  83. pci_patch_ids(pdev, ptr, size);
  84. }
  85. }
  86. pci_register_bar(pdev, PCI_ROM_SLOT, 0, &pdev->rom);
  87. }

1)pci_add_option_rom函数按照pdev->rom_bar是否为true,来决定是使用fw_cfg中的文件还是创建一个rom_bar。代码片段如下:

  1. if (!pdev->rom_bar) {
  2. /*
  3. * Load rom via fw_cfg instead of creating a rom bar,
  4. * for 0.11 compatibility.
  5. */
  6. int class = pci_get_word(pdev->config + PCI_CLASS_DEVICE);
  7. /*
  8. * Hot-plugged devices can't use the option ROM
  9. * if the rom bar is disabled.
  10. */
  11. if (DEVICE(pdev)->hotplugged) {
  12. error_setg(errp, "Hot-plugged device without ROM bar"
  13. " can't have an option ROM");
  14. return;
  15. }
  16. if (class == 0x0300) {
  17. rom_add_vga(pdev->romfile);
  18. } else {
  19. rom_add_option(pdev->romfile, -1);
  20. }
  21. return;
  22. }

pdev->rom_bar默认为true。

2)接着是得到ROM文件的路径和大小。代码片段如下:

  1. if (load_file || pdev->romsize == -1) {
  2. path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile);
  3. if (path == NULL) {
  4. path = g_strdup(pdev->romfile);
  5. }
  6. size = get_image_size(path);
  7. if (size < 0) {
  8. error_setg(errp, "failed to find romfile \"%s\"", pdev->romfile);
  9. return;
  10. } else if (size == 0) {
  11. error_setg(errp, "romfile \"%s\" is empty", pdev->romfile);
  12. return;
  13. } else if (size > 2 * GiB) {
  14. error_setg(errp,
  15. "romfile \"%s\" too large (size cannot exceed 2 GiB)",
  16. pdev->romfile);
  17. return;
  18. }
  19. if (pdev->romsize != -1) {
  20. if (size > pdev->romsize) {
  21. error_setg(errp, "romfile \"%s\" (%u bytes) "
  22. "is too large for ROM size %u",
  23. pdev->romfile, (uint32_t)size, pdev->romsize);
  24. return;
  25. }
  26. } else {
  27. pdev->romsize = pow2ceil(size);
  28. }
  29. }

3)然后,调用load_image_size函数将文件加载到指定的ROM中。代码片段如下:

  1. if (load_file) {
  2. void *ptr = memory_region_get_ram_ptr(&pdev->rom);
  3. if (load_image_size(path, ptr, size) < 0) {
  4. error_setg(errp, "failed to load romfile \"%s\"", pdev->romfile);
  5. return;
  6. }
  7. if (is_default_rom) {
  8. /* Only the default rom images will be patched (if needed). */
  9. pci_patch_ids(pdev, ptr, size);
  10. }
  11. }

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

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

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他