QEMU(4) q35

 

Table of Contents

架構

root

q35 machine

select_machine

main-system-bus系統總線

q35-pcihost

q35_host_class_init

q35_host_props

q35_host_initfn

q35_host_realize

pcie.0

New device

New bus

參考


QEMU啓動的命令帶有參數“-M q35 ”,它表示虛擬機的machine和chipset爲q35。

架構

QEMU支持的chipset很少。1996年誕生的I440FX由於不支持PCIe等原因,被2012年Jason Baron引入的Q35 chipset所替代。

I440FX架構圖如下。支持的總線都比較古老。

Q35架構圖如下。分爲南北橋,北橋MCH通過前端總線FSB與CPU連接,提供PCIe接口和DDR內存接口等。南橋ICH9通過DMI總線連接到北區MCH,提供USB2.0, SATA等各種低速外設。

通過“info qtree”命令可以查看QEMU中系統設備架構信息。以下是刪減過dev細節的示例,其中q35-pcihost會整個Q35 chipset的根節點。

[root@localhost kvm]# x86_64-softmmu/qemu-system-x86_64 -M q35 -m 1024 -enable-kvm -monitor stdio
QEMU 4.1.0 monitor - type 'help' for more information
(qemu) VNC server running on ::1:5901

(qemu) info qtree
bus: main-system-bus
  type System
  dev: hpet, id ""
  dev: kvm-ioapic, id ""
  dev: q35-pcihost, id ""
    bus: pcie.0
      type PCIE
      dev: e1000e, id ""
      dev: VGA, id ""
      dev: ICH9 SMB, id ""
        bus: i2c
          type i2c-bus
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
          dev: smbus-eeprom, id ""
      dev: ich9-ahci, id ""
        bus: ide.5
          type IDE
        bus: ide.4
          type IDE
        bus: ide.3
          type IDE
        bus: ide.2
          type IDE
          dev: ide-cd, id ""
        bus: ide.1
          type IDE
        bus: ide.0
          type IDE
      dev: ICH9-LPC, id ""
        bus: isa.0
          type ISA
          dev: port92, id ""
          dev: vmmouse, id ""
          dev: vmport, id ""
          dev: i8042, id ""
          dev: isa-parallel, id ""
          dev: isa-serial, id ""
          dev: i8257, id ""
          dev: i8257, id ""
          dev: isa-pcspk, id ""
          dev: kvm-pit, id ""
          dev: mc146818rtc, id ""
          dev: kvm-i8259, id ""
          dev: kvm-i8259, id ""
      dev: mch, id ""
  dev: fw_cfg_io, id ""
  dev: kvmclock, id ""
  dev: kvmvapic, id ""
(qemu)

簡化示意圖如下。可以清楚的看到這個設備樹是總線和設備交替連接的。每個設備都是掛載在一個總線上的,設備又可以生成新的總線,如此往復。

這種bus和device交替的關係,也體現在設備和總線對象結構的定義上

struct DeviceState {
...
    BusState *parent_bus;               //設備device的parent是總線bus
    QLIST_HEAD(, BusState) child_bus;   //設備device的child是總線bus
    int num_child_bus;
...
};

struct BusState {
...
    DeviceState *parent;               //總線bus的parent是設備device
    int num_children;
    QTAILQ_HEAD(, BusChild) children;  //總線bus的child是BusChild結構中定義的設備device
    QLIST_ENTRY(BusState) sibling;     //總線bus的sibling是總線bus
...
};

typedef struct BusChild {
    DeviceState *child;
    int index;
    QTAILQ_ENTRY(BusChild) sibling;
} BusChild;

main-system-bus不是根設備對象,main-system-bus之前還有root和machine兩級對象。

root

root設備對象爲一個虛擬的"container"基對象。它的TypeInfo類型信息內容如下。

static const TypeInfo container_info = {
    .name          = "container",
    //沒有定義.class_size, 父類型TYPE_OBJECT也沒有,那怎麼建立對象類?
    .instance_size = sizeof(Object),       //對象大小爲基對象Object
    .parent        = TYPE_OBJECT,          //父了下爲TYPE_OBJECT基類型
};

type_init(container_register_types)

object_get_root負責新建root "container"對象。

Object *object_get_root(void)
{
    static Object *root;

    if (!root) {
        root = object_new("container");
    }

    return root;
}

q35 machine

Q35構造函數如下,代碼中其實定義了很多組DEFINE_Q35_MACHIN宏定義實現,但是隻有一組的m->alias是有值的,其餘的都是不可用的。每組定義都有一個後綴(QEMU的版本號),例如下面的“4_1”。

static void pc_q35_4_1_machine_options(MachineClass *m)     //被Q35對象類的class_init函數調用
{
    PCMachineClass *pcmc = PC_MACHINE_CLASS(m);
    pc_q35_machine_options(m);
    m->alias = "q35";                                      //alias name,通過這個名字和-M q35參數關聯上
    pcmc->default_cpu_version = 1;
}

DEFINE_Q35_MACHINE(v4_1, "pc-q35-4.1", NULL,              //宏定義了Q35 machine
                   pc_q35_4_1_machine_options);

DEFINE_Q35_MACHINE宏定義如下。

#define DEFINE_Q35_MACHINE(suffix, name, compatfn, optionfn) \
    static void pc_init_##suffix(MachineState *machine) \           //對象類的init函數
    { \
        void (*compat)(MachineState *m) = (compatfn); \
        if (compat) { \
            compat(machine); \
        } \
        pc_q35_init(machine); \
    } \
    DEFINE_PC_MACHINE(suffix, name, pc_init_##suffix, optionfn)

DEFINE_PC_MACHINE宏定義如下。

#define DEFINE_PC_MACHINE(suffix, namestr, initfn, optsfn) \
    static void pc_machine_##suffix##_class_init(ObjectClass *oc, void *data) \   //對象類的class_init函數
    { \
        MachineClass *mc = MACHINE_CLASS(oc); \                  //Q35沒有定義派生對象類,繼承父對象類MachineClass
        optsfn(mc); \                                            //調用pc_q35_4_1_machine_options
        mc->init = initfn; \                                     //設置init函數
    } \
    //沒有定義instant_size和class_size,對象和對象類大小都繼承自父類型 \
    static const TypeInfo pc_machine_type_##suffix = { \         
        .name       = namestr TYPE_MACHINE_SUFFIX, \             //"pc-q35-4.1 -machine"
        .parent     = TYPE_PC_MACHINE, \
        //沒有定義.class_size, 繼承自父類型TYPE_PC_MACHINE,等於sizeof(PCMachineClass)
        //沒有定義.instance_size,繼承自父類型TYPE_PC_MACHINE,等於sizeof(PCMachineState)
        .class_init = pc_machine_##suffix##_class_init, \
    }; \
    static void pc_machine_init_##suffix(void) \
    { \
        type_register(&pc_machine_type_##suffix); \
    } \
    type_init(pc_machine_init_##suffix)

#define TYPE_MACHINE_SUFFIX "-machine"
#define TYPE_MACHINE "machine"

在QEMU的main函數中,通過select_machine選擇合適的machine對象類,新建machine對象,並添加machine對象爲root對象的child屬性。

MachineState *current_machine;

int main(int argc, char **argv, char **envp)
{
    MachineClass *machine_class;
...
    machine_class = select_machine();                            //根據qemu參數input選擇MachineClass類
    current_machine = MACHINE(object_new(object_class_get_name(
                          OBJECT_CLASS(machine_class))));        //新建對象PCMachineState,cast到父對象MachineState,對象名爲machine名

    //參數1 object_get_root新建root對象“container
    //參數2 “machine”爲child屬性的名字
    //參數3 新建的machine對象,cast到基對象object
    //object_property_add_child的作用是給root “container”對象,添加一個child屬性,
    //屬性名稱爲“maichine”,屬性get方法爲object_get_child_property(), 
    //屬性set方法爲NULL,屬性release方法爲object_finalize_child_property(),
    //屬性opaque參數指向child對象current_machine。
    object_property_add_child(object_get_root(), "machine",          ”
                              OBJECT(current_machine), &error_abort);
...
}

select_machine

select_machine挑選匹配的machine class。一圈查找下來,找到q35 machine的machine class對象類。

static MachineClass *select_machine(void)
{
    //獲取所有的machine class的哈希表。
    //object_class_get_list在loop的過程中新建各個TYPE_MACHINE的對象類,然後把list添加到哈希表返回。
    GSList *machines = object_class_get_list(TYPE_MACHINE, false);  
    //先獲取一個default machine class,x86的default machine還是pc_i440fx(參考pc_i440fx_4_1_machine_options)
    MachineClass *machine_class = find_default_machine(machines);   
    const char *optarg;
    QemuOpts *opts;
    Location loc;

    loc_push_none(&loc);

    opts = qemu_get_machine_opts();    //得到-M 參數信息的QemuOpts指針
    qemu_opts_loc_restore(opts);

    //-M q35在參數解析時,由於沒有參數opt名字,會取QemuOptsList的implied_opt_name作爲名字,
    //對於qemu_machine_opts就是“type”,
    //這邊qemu_opt_get取machine opts裏面的“type”opt,就是取"-M q35”參數的值“q35”
    optarg = qemu_opt_get(opts, "type");   
    if (optarg) {
        //machine_parse調用find_machine,查找machine class的hash表
        //find_machine比較machine class的name或者alias,任一個和optarg相同就表示找到了。
        //對於q35 machine來說就是匹配的alias名字(參考pc_q35_4_1_machine_options)
        machine_class = machine_parse(optarg, machines);  
    }

    if (!machine_class) {
        error_report("No machine specified, and there is no default");
        error_printf("Use -machine help to list supported machines\n");
        exit(1);
    }

    loc_pop(&loc);
    g_slist_free(machines);
    return machine_class;
}

main-system-bus系統總線

所有設備都是掛載在main-system-bus是系統總線的後面。它是個虛擬的總線。

它的type_info定義如下,

static const TypeInfo system_bus_info = {
    .name = TYPE_SYSTEM_BUS,
    .parent = TYPE_BUS,
    .instance_size = sizeof(BusState),
    .class_init = system_bus_class_init,
};

sysbus_get_default調用main_system_bus_create生成main_system_bus對象。

/* This is a nasty hack to allow passing a NULL bus to qdev_create.  */
static BusState *main_system_bus;                     #全局變量

static void main_system_bus_create(void)
{
    /* assign main_system_bus before qbus_create_inplace()
     * in order to make "if (bus != sysbus_get_default())" work */
    main_system_bus = g_malloc0(system_bus_info.instance_size);
    qbus_create_inplace(main_system_bus, system_bus_info.instance_size,
                        TYPE_SYSTEM_BUS, NULL, "main-system-bus");
    OBJECT(main_system_bus)->free = g_free;
}

BusState *sysbus_get_default(void)
{
    if (!main_system_bus) {
        main_system_bus_create();
    }
    return main_system_bus;
}

在qemu的main函數中,main-system-bus對象作爲child屬性添加到current_mainchine對象,current_mainchine對象則是root設備的child屬性。

int main(int argc, char **argv, char **envp)
{
... 
    //參數1,新建child對象“containner”,並添加爲current maichine對象的child屬性,
            child屬性名稱爲“unattached”(“/”會被去掉)
    //參數2,“sysbus”爲child屬性名稱
    //參數3,由sysbus_get_default調用main_system_bus_create建立main_system_bus對象
    //在current main對象的child屬性“unattached”對應的child對象“container”上,
    //添加child屬性“sysbus”,child對象爲main_system_bus對象
    object_property_add_child(container_get(OBJECT(current_machine),"/unattached"),
                              "sysbus", 
                              OBJECT(sysbus_get_default()),
                              NULL);
...
}

q35-pcihost

q35的module init與編譯函數爲q35_register。它會調用type_register_static函數註冊兩個TypeInfo結構mch_info和q35_host_info。mch_info在這裏先不展開。

static void q35_register(void)
{
    type_register_static(&mch_info);          //先忽略
    type_register_static(&q35_host_info);     //註冊q35-pcihost TypeInfo
}

type_init(q35_register);

q35的TypeInfo結構q35_host_info定義如下。它的繼承關係是TYPE_OBJECT -> TYPE_DEVICE -> TYPE_SYS_BUS_DEVICE -> TYPE_PCI_HOST_BRIDGE -> TYPE_PCIE_HOST_BRIDGE -> TYPE_Q35_HOST_DEVICE。

static const TypeInfo q35_host_info = {
    .name       = TYPE_Q35_HOST_DEVICE,     //#define TYPE_Q35_HOST_DEVICE "q35-pcihost"
    .parent     = TYPE_PCIE_HOST_BRIDGE,    //#define TYPE_PCIE_HOST_BRIDGE "pcie-host-bridge"
    .instance_size = sizeof(Q35PCIHost),    //對象爲Q35PCIHost,object_initialize_with_type()中申請對象空間
    .instance_init = q35_host_initfn,       //object_init_with_type()執行父類往下所有的.instance_init
    //沒有定義.class_size, 從TYPE_PCI_HOST_BRIDGE繼承對象類PCIHostBridgeClass
    .class_init = q35_host_class_init,      //type_initialize()中申請對象類空間,執行父類往下所有的.class_init
};

 

q35_host_class_init

對象類初始化函數q35_host_class_init如下,在type_initialize函數中,會申請對象類的空間,並且調用class_init進行初始化。

static void q35_host_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    PCIHostBridgeClass *hc = PCI_HOST_BRIDGE_CLASS(klass);

    hc->root_bus_path = q35_host_root_bus_path;         //設置父對象類PCIHostBridgeClass的root_bus_path
    dc->realize = q35_host_realize;                     //設置父對象類DeviceClass的realize函數
    dc->props = q35_host_props;                         //設置父對象類DeviceClass的props
    /* Reason: needs to be wired up by pc_q35_init */
    dc->user_creatable = false;
    set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
    dc->fw_name = "pci";
}

q35_host_props

q35_host_props爲q35的靜態對象類屬性Property。

static Property q35_host_props[] = {
    DEFINE_PROP_UINT64(PCIE_HOST_MCFG_BASE, Q35PCIHost, parent_obj.base_addr,  //mcfg基地址
                        MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT),
    DEFINE_PROP_SIZE(PCI_HOST_PROP_PCI_HOLE64_SIZE, Q35PCIHost,
                     mch.pci_hole64_size, Q35_PCI_HOST_HOLE64_SIZE_DEFAULT),
    DEFINE_PROP_UINT32("short_root_bus", Q35PCIHost, mch.short_root_bus, 0),
    DEFINE_PROP_SIZE(PCI_HOST_BELOW_4G_MEM_SIZE, Q35PCIHost,
                     mch.below_4g_mem_size, 0),
    DEFINE_PROP_SIZE(PCI_HOST_ABOVE_4G_MEM_SIZE, Q35PCIHost,
                     mch.above_4g_mem_size, 0),
    DEFINE_PROP_BOOL("x-pci-hole64-fix", Q35PCIHost, pci_hole64_fix, true),
    DEFINE_PROP_END_OF_LIST(),
};

靜態的對象類屬性在對象類DeviceClass的.instance_init函數device_initfn中會把對象類屬性轉換爲對象屬性ObjectProperty

static void device_initfn(Object *obj)
{
    DeviceState *dev = DEVICE(obj);
    ObjectClass *class;
    Property *prop;
...
    class = object_get_class(OBJECT(dev));
    do {
        for (prop = DEVICE_CLASS(class)->props; prop && prop->name; prop++) {
            qdev_property_add_legacy(dev, prop, &error_abort); //名爲"legacy-%接口名%"的字串屬性
            qdev_property_add_static(dev, prop, &error_abort); //以Property信息爲input
        }
        class = object_class_get_parent(class);
    } while (class != object_class_by_name(TYPE_DEVICE));
...
}

q35_host_initfn

q35_host_initfn定義如下,在object_initialize_with_type中被調用,mch相關初始化代碼被過濾掉了,動態添加各種對象屬性。

static void q35_host_initfn(Object *obj)
{
    Q35PCIHost *s = Q35_HOST_DEVICE(obj);               //從基對象cast到派生對象
    PCIHostState *phb = PCI_HOST_BRIDGE(obj);

...
    qdev_prop_set_uint64(DEVICE(s), PCI_HOST_PROP_PCI_HOLE64_SIZE,
                         Q35_PCI_HOST_HOLE64_SIZE_DEFAULT);
    object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_START, "uint32",
                        q35_host_get_pci_hole_start,
                        NULL, NULL, NULL, NULL);

    object_property_add(obj, PCI_HOST_PROP_PCI_HOLE_END, "uint32",
                        q35_host_get_pci_hole_end,
                        NULL, NULL, NULL, NULL);

    object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_START, "uint64",
                        q35_host_get_pci_hole64_start,
                        NULL, NULL, NULL, NULL);

    object_property_add(obj, PCI_HOST_PROP_PCI_HOLE64_END, "uint64",
                        q35_host_get_pci_hole64_end,
                        NULL, NULL, NULL, NULL);

    object_property_add(obj, PCIE_HOST_MCFG_SIZE, "uint64",
                        q35_host_get_mmcfg_size,
                        NULL, NULL, NULL, NULL);

    object_property_add_link(obj, MCH_HOST_PROP_RAM_MEM, TYPE_MEMORY_REGION,
                             (Object **) &s->mch.ram_memory,
                             qdev_prop_allow_set_link_before_realize, 0, NULL);

    object_property_add_link(obj, MCH_HOST_PROP_PCI_MEM, TYPE_MEMORY_REGION,
                             (Object **) &s->mch.pci_address_space,
                             qdev_prop_allow_set_link_before_realize, 0, NULL);

    object_property_add_link(obj, MCH_HOST_PROP_SYSTEM_MEM, TYPE_MEMORY_REGION,
                             (Object **) &s->mch.system_memory,
                             qdev_prop_allow_set_link_before_realize, 0, NULL);

    object_property_add_link(obj, MCH_HOST_PROP_IO_MEM, TYPE_MEMORY_REGION,
                             (Object **) &s->mch.address_space_io,
                             qdev_prop_allow_set_link_before_realize, 0, NULL);
}

q35_host_realize

device_set_realized會調用設備的realize屬性的set方法。q35的realize屬性的set方法爲q35_host_realize。

static void q35_host_realize(DeviceState *dev, Error **errp)
{
    PCIHostState *pci = PCI_HOST_BRIDGE(dev);
    Q35PCIHost *s = Q35_HOST_DEVICE(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);

    sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, &pci->conf_mem);
    sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_ADDR, 4);

    sysbus_add_io(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, &pci->data_mem);
    sysbus_init_ioports(sbd, MCH_HOST_BRIDGE_CONFIG_DATA, 4);

    /* register q35 0xcf8 port as coalesced pio */
    memory_region_set_flush_coalesced(&pci->data_mem);
    memory_region_add_coalescing(&pci->conf_mem, 0, 4);

    //建立root bus “pcie.0”
    pci->bus = pci_root_bus_new(DEVICE(s), "pcie.0",
                                s->mch.pci_address_space,
                                s->mch.address_space_io,
                                0, TYPE_PCIE_BUS);
    PC_MACHINE(qdev_get_machine())->bus = pci->bus;
    qdev_set_parent_bus(DEVICE(&s->mch), BUS(pci->bus));
    qdev_init_nofail(DEVICE(&s->mch));
}

pcie.0

q35_host_realize調用pci_root_bus_new創建pcie.0 root bus。

static void q35_host_realize(DeviceState *dev, Error **errp)
{
...
   PCIHostState *pci = PCI_HOST_BRIDGE(dev);
   pci->bus = pci_root_bus_new(DEVICE(s), "pcie.0",
                                s->mch.pci_address_space,
                                s->mch.address_space_io,
                                0, TYPE_PCIE_BUS);      //新建root bus對象,並保存到pci host bridge對象
   PC_MACHINE(qdev_get_machine())->bus = pci->bus;      //保存root bus對象到machine對象
...
}

PCIBus *pci_root_bus_new(DeviceState *parent, const char *name,
                         MemoryRegion *address_space_mem,
                         MemoryRegion *address_space_io,
                         uint8_t devfn_min, const char *typename)
{
    PCIBus *bus;

    bus = PCI_BUS(qbus_create(typename, parent, name)); //調用qbus_create創建bus
    pci_root_bus_init(bus, parent, address_space_mem, address_space_io,
                      devfn_min);                       //初始化新bus
    return bus;
}

pci_root_bus_init定義如下

static void pci_root_bus_init(PCIBus *bus, DeviceState *parent,
                              MemoryRegion *address_space_mem,
                              MemoryRegion *address_space_io,
                              uint8_t devfn_min)
{
    assert(PCI_FUNC(devfn_min) == 0);
    bus->devfn_min = devfn_min;
    bus->slot_reserved_mask = 0x0;
    bus->address_space_mem = address_space_mem; //root bus的mmio空間
    bus->address_space_io = address_space_io;  //root bus的io space空間
    bus->flags |= PCI_BUS_IS_ROOT;      // root flag

    /* host bridge */
    QLIST_INIT(&bus->child);           //初始化child隊列
 
    pci_host_bus_register(parent);     //添加到pcie.0總線的parent設備(Q35PCIHost)到pci host bridge隊列
}

pci_host_bus_register定義如下

static QLIST_HEAD(, PCIHostState) pci_host_bridges;  //全局pci host bridge隊列頭

static void pci_host_bus_register(DeviceState *host)
{
    PCIHostState *host_bridge = PCI_HOST_BRIDGE(host);

    QLIST_INSERT_HEAD(&pci_host_bridges, host_bridge, next);  //添加host到pci host bridge隊列
}

New device

qdev_create函數新建一個新的設備對象,隨後的函數qdev_set_parent_bus負責把device對象加到父bus對象上。

New bus

新bus對象的建立有兩個接口,qbus_create會新建對象空間,qbus_create_inplace的bus對象空間在調用進來時已經建立好了,這邊是執行具體的對象初始化。隨後的qbus_realize函數會把新的bus對象添加到父設備對象的child_bus隊列和child屬性中。

 

參考

https://www.linux-kvm.org/images/0/06/2012-forum-Q35.pdf

https://github.com/GiantVM/doc/blob/master/q35.md

https://remimin.github.io/2019/07/09/qemu_machine_type/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章