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

发布时间:2024年01月11日

接前一篇文章:

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(),讲解了pci_qdev_realize函数所完成的三个任务中的第一个,本回接着往下讲。为了便于理解,再次贴出该函数源码,在hw/pci/pci.c中,如下:

static void pci_qdev_realize(DeviceState *qdev, Error **errp)
{
    PCIDevice *pci_dev = (PCIDevice *)qdev;
    PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(pci_dev);
    ObjectClass *klass = OBJECT_CLASS(pc);
    Error *local_err = NULL;
    bool is_default_rom;
    uint16_t class_id;
 
    /*
     * capped by systemd (see: udev-builtin-net_id.c)
     * as it's the only known user honor it to avoid users
     * misconfigure QEMU and then wonder why acpi-index doesn't work
     */
    if (pci_dev->acpi_index > ONBOARD_INDEX_MAX) {
        error_setg(errp, "acpi-index should be less or equal to %u",
                   ONBOARD_INDEX_MAX);
        return;
    }
 
    /*
     * make sure that acpi-index is unique across all present PCI devices
     */
    if (pci_dev->acpi_index) {
        GSequence *used_indexes = pci_acpi_index_list();
 
        if (g_sequence_lookup(used_indexes,
                              GINT_TO_POINTER(pci_dev->acpi_index),
                              g_cmp_uint32, NULL)) {
            error_setg(errp, "a PCI device with acpi-index = %" PRIu32
                       " already exist", pci_dev->acpi_index);
            return;
        }
        g_sequence_insert_sorted(used_indexes,
                                 GINT_TO_POINTER(pci_dev->acpi_index),
                                 g_cmp_uint32, NULL);
    }
 
    if (pci_dev->romsize != -1 && !is_power_of_2(pci_dev->romsize)) {
        error_setg(errp, "ROM size %u is not a power of two", pci_dev->romsize);
        return;
    }
 
    /* initialize cap_present for pci_is_express() and pci_config_size(),
     * Note that hybrid PCIs are not set automatically and need to manage
     * QEMU_PCI_CAP_EXPRESS manually */
    if (object_class_dynamic_cast(klass, INTERFACE_PCIE_DEVICE) &&
       !object_class_dynamic_cast(klass, INTERFACE_CONVENTIONAL_PCI_DEVICE)) {
        pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS;
    }
 
    if (object_class_dynamic_cast(klass, INTERFACE_CXL_DEVICE)) {
        pci_dev->cap_present |= QEMU_PCIE_CAP_CXL;
    }
 
    pci_dev = do_pci_register_device(pci_dev,
                                     object_get_typename(OBJECT(qdev)),
                                     pci_dev->devfn, errp);
    if (pci_dev == NULL)
        return;
 
    if (pc->realize) {
        pc->realize(pci_dev, &local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            do_pci_unregister_device(pci_dev);
            return;
        }
    }
 
    /*
     * A PCIe Downstream Port that do not have ARI Forwarding enabled must
     * associate only Device 0 with the device attached to the bus
     * representing the Link from the Port (PCIe base spec rev 4.0 ver 0.3,
     * sec 7.3.1).
     * With ARI, PCI_SLOT() can return non-zero value as the traditional
     * 5-bit Device Number and 3-bit Function Number fields in its associated
     * Routing IDs, Requester IDs and Completer IDs are interpreted as a
     * single 8-bit Function Number. Hence, ignore ARI capable devices.
     */
    if (pci_is_express(pci_dev) &&
        !pcie_find_capability(pci_dev, PCI_EXT_CAP_ID_ARI) &&
        pcie_has_upstream_port(pci_dev) &&
        PCI_SLOT(pci_dev->devfn)) {
        warn_report("PCI: slot %d is not valid for %s,"
                    " parent device only allows plugging into slot 0.",
                    PCI_SLOT(pci_dev->devfn), pci_dev->name);
    }
 
    if (pci_dev->failover_pair_id) {
        if (!pci_bus_is_express(pci_get_bus(pci_dev))) {
            error_setg(errp, "failover primary device must be on "
                             "PCIExpress bus");
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        class_id = pci_get_word(pci_dev->config + PCI_CLASS_DEVICE);
        if (class_id != PCI_CLASS_NETWORK_ETHERNET) {
            error_setg(errp, "failover primary device is not an "
                             "Ethernet device");
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        if ((pci_dev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION)
            || (PCI_FUNC(pci_dev->devfn) != 0)) {
            error_setg(errp, "failover: primary device must be in its own "
                              "PCI slot");
            pci_qdev_unrealize(DEVICE(pci_dev));
            return;
        }
        qdev->allow_unplug_during_migration = true;
    }
 
    /* rom loading */
    is_default_rom = false;
    if (pci_dev->romfile == NULL && pc->romfile != NULL) {
        pci_dev->romfile = g_strdup(pc->romfile);
        is_default_rom = true;
    }
 
    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;
    }
 
    pci_set_power(pci_dev, true);
 
    pci_dev->msi_trigger = pci_msi_trigger;
}

(2)其次,pci_qdev_realize函数调用PCI设备所属的class的realize函数,即pc->realize函数。

代码片段如下:

    if (pc->realize) {
        pc->realize(pci_dev, &local_err);
        if (local_err) {
            error_propagate(errp, local_err);
            do_pci_unregister_device(pci_dev);
            return;
        }
    }

pc的是PCIDeviceClass结构类型的指针,PCIDeviceClass的定义在include/hw/pci/pci_device.h中,代码如下:

#define TYPE_PCI_DEVICE "pci-device"
typedef struct PCIDeviceClass PCIDeviceClass;
DECLARE_OBJ_CHECKERS(PCIDevice, PCIDeviceClass,
                     PCI_DEVICE, TYPE_PCI_DEVICE)

而struct PCIDeviceClass的定义也在include/hw/pci/pci_device.h中,代码如下:

struct PCIDeviceClass {
    DeviceClass parent_class;

    void (*realize)(PCIDevice *dev, Error **errp);
    PCIUnregisterFunc *exit;
    PCIConfigReadFunc *config_read;
    PCIConfigWriteFunc *config_write;

    uint16_t vendor_id;
    uint16_t device_id;
    uint8_t revision;
    uint16_t class_id;
    uint16_t subsystem_vendor_id;       /* only for header type = 0 */
    uint16_t subsystem_id;              /* only for header type = 0 */

    const char *romfile;                /* rom bar */
};

PCI_DEVICE_GET_CLASS是一个宏定义,但是在QEMU源码中搜素怎么都搜不到。笔者开始以为是在Linux内核源码中的定义,后来在Linux源码中搜索却并未搜到。后来几经周折,终于弄清楚了其具体细节。

要弄清楚PCI_DEVICE_GET_CLASS,得先从DECLARE_OBJ_CHECKERS讲起。DECLARE_OBJ_CHECKERS的相关代码在include/hw/pci/pci_device.h中:

#define TYPE_PCI_DEVICE "pci-device"
typedef struct PCIDeviceClass PCIDeviceClass;
DECLARE_OBJ_CHECKERS(PCIDevice, PCIDeviceClass,
                     PCI_DEVICE, TYPE_PCI_DEVICE)

DECLARE_OBJ_CHECKERS的定义在include/qom/object.h中,代码如下:

/**
 * DECLARE_OBJ_CHECKERS:
 * @InstanceType: instance struct name
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the three standard type cast functions for a
 * QOM type.
 */
#define DECLARE_OBJ_CHECKERS(InstanceType, ClassType, OBJ_NAME, TYPENAME) \
    DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    \
    DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME)

代入实际值并展开,得到:

    DECLARE_INSTANCE_CHECKER(PCIDevice, PCI_DEVICE, TYPE_PCI_DEVICE)
    
    DECLARE_CLASS_CHECKERS(PCIDeviceClass, PCI_DEVICE, TYPE_PCI_DEVICE)

DECLARE_INSTANCE_CHECKER宏定义也在include/qom/object.h中,代码如下:

/**
 * DECLARE_INSTANCE_CHECKER:
 * @InstanceType: instance struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the instance type cast functions for a
 * QOM type.
 */
#define DECLARE_INSTANCE_CHECKER(InstanceType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED InstanceType * \
    OBJ_NAME(const void *obj) \
    { return OBJECT_CHECK(InstanceType, obj, TYPENAME); }

代入实际值并展开得:

static inline G_GNUC_UNUSED PCIDevice* PCI_DEVICE(const void *obj)
{
    return OBJECT_CHECK(PCIDevice, obj, TYPE_PCI_DEVICE);
}

DECLARE_CLASS_CHECKERS宏定义也在include/qom/object.h中,代码如下:

/**
 * DECLARE_CLASS_CHECKERS:
 * @ClassType: class struct name
 * @OBJ_NAME: the object name in uppercase with underscore separators
 * @TYPENAME: type name
 *
 * Direct usage of this macro should be avoided, and the complete
 * OBJECT_DECLARE_TYPE macro is recommended instead.
 *
 * This macro will provide the class type cast functions for a
 * QOM type.
 */
#define DECLARE_CLASS_CHECKERS(ClassType, OBJ_NAME, TYPENAME) \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_GET_CLASS(const void *obj) \
    { return OBJECT_GET_CLASS(ClassType, obj, TYPENAME); } \
    \
    static inline G_GNUC_UNUSED ClassType * \
    OBJ_NAME##_CLASS(const void *klass) \
    { return OBJECT_CLASS_CHECK(ClassType, klass, TYPENAME); }

代入实际值并展开得:

static inline G_GNUC_UNUSED PCIDeviceClass* PCI_DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(PCIDeviceClass, obj, TYPE_PCI_DEVICE);
}

static inline G_GNUC_UNUSED PCIDeviceClass* PCI_DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(PCIDeviceClass, klass, TYPE_PCI_DEVICE);
}

综合两式,最终得到:

static inline G_GNUC_UNUSED PCIDevice* PCI_DEVICE(const void *obj)
{
    return OBJECT_CHECK(PCIDevice, obj, TYPE_PCI_DEVICE);
}

static inline G_GNUC_UNUSED PCIDeviceClass* PCI_DEVICE_GET_CLASS(const void *obj)
{
    return OBJECT_GET_CLASS(PCIDeviceClass, obj, TYPE_PCI_DEVICE);
}

static inline G_GNUC_UNUSED PCIDeviceClass* PCI_DEVICE_CLASS(const void *klass)
{
    return OBJECT_CLASS_CHECK(PCIDeviceClass, klass, TYPE_PCI_DEVICE);
}

这样,最终就得到了也可以说生成出了PCI_DEVICE_GET_CLASS。

再深入跟紧一点,OBJECT_CHECK、OBJECT_GET_CLASS、OBJECT_CLASS_CHECK三个宏的定义都在include/qom/object.h中,代码如下:

/**
 * OBJECT_CHECK:
 * @type: The C type to use for the return value.
 * @obj: A derivative of @type to cast.
 * @name: The QOM typename of @type
 *
 * A type safe version of @object_dynamic_cast_assert.  Typically each class
 * will define a macro based on this type to perform type safe dynamic_casts to
 * this object type.
 *
 * If an invalid object is passed to this function, a run time assert will be
 * generated.
 */
#define OBJECT_CHECK(type, obj, name) \
    ((type *)object_dynamic_cast_assert(OBJECT(obj), (name), \
                                        __FILE__, __LINE__, __func__))

/**
 * OBJECT_CLASS_CHECK:
 * @class_type: The C type to use for the return value.
 * @class: A derivative class of @class_type to cast.
 * @name: the QOM typename of @class_type.
 *
 * A type safe version of @object_class_dynamic_cast_assert.  This macro is
 * typically wrapped by each type to perform type safe casts of a class to a
 * specific class type.
 */
#define OBJECT_CLASS_CHECK(class_type, class, name) \
    ((class_type *)object_class_dynamic_cast_assert(OBJECT_CLASS(class), (name), \
                                               __FILE__, __LINE__, __func__))

/**
 * OBJECT_GET_CLASS:
 * @class: The C type to use for the return value.
 * @obj: The object to obtain the class for.
 * @name: The QOM typename of @obj.
 *
 * This function will return a specific class for a given object.  Its generally
 * used by each type to provide a type safe macro to get a specific class type
 * from an object.
 */
#define OBJECT_GET_CLASS(class, obj, name) \
    OBJECT_CLASS_CHECK(class, object_get_class(OBJECT(obj)), name)

至此,pci_qdev_realize函数所做的第二方面工作即所调用的第2个函数也就是PCI设备所属的class的realize函数,即pc->realize函数就解析完了。

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

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