Table of Contents
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