本文发自 http://www./blog/qemu-note-of-Q35-machine/,转载请注明出处。
QEMU支持的架构非常少,在Q35出现之前,就只有诞生于1996年的i440FX + PIIX一个架构在苦苦支撑。一方面是Intel不断推出新的芯片组,搞出了PCIe、AHCI等等新东西。i440FX已经无法满足需求,为此在 KVM Forum 2012 上Jason Baron带来了PPT:A New Chipset For Qemu - Intel's Q35。Q35是Intel在2007年6月推出的芯片组,最吸引人的就是其支持PCI-e。
根据Intel Q35文档,Q35的拓扑结构如图所示:
可见其北桥为MCH,南桥为ICH9。CPU 通过 前端总线(FSB) 连接到 北桥(MCH),北桥为内存、PCIE等提供接入,同时连接到南桥(ICH9)。南桥为 USB / PCIE / SATA 等提供接入。
Q35 拓扑结构
那么在QEMU中实现的Q35拓扑结构是否真的如上图所示呢?我们在 QEMU 中通过 info qtree 查询,简化后的结构为:
| (qemu) info qtree | | bus: main-system-bus | | dev: hpet, id "" | | gpio-in "" 2 | | gpio-out "" 1 | | gpio-out "sysbus-irq" 32 | | dev: ioapic, id "" | | gpio-in "" 24 | | version = 32 (0x20) | | dev: q35-pcihost, id "" | | bus: pcie.0 | | type PCIE | | dev: e1000, id "" | | dev: VGA, id "" | | dev: ICH9 SMB, 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: ide-hd, id "" | | drive = "ide0-hd0" | | dev: ICH9-LPC, id "" | | gpio-out "gsi" 24 | | bus: isa.0 | | type ISA | | dev: i8257, id "" | | dev: i8257, id "" | | dev: port92, id "" | | gpio-out "a20" 1 | | dev: vmmouse, id "" | | dev: vmport, id "" | | dev: i8042, id "" | | gpio-out "a20" 1 | | dev: isa-parallel, id "" | | dev: isa-serial, id "" | | dev: isa-pcspk, id "" | | iobase = 97 (0x61) | | dev: isa-pit, id "" | | gpio-in "" 1 | | gpio-out "" 1 | | dev: mc146818rtc, id "" | | gpio-out "" 1 | | dev: isa-i8259, id "" | | gpio-in "" 8 | | gpio-out "" 1 | | master = false | | dev: isa-i8259, id "" | | gpio-in "" 8 | | gpio-out "" 1 | | master = true | | dev: mch, id "" | | dev: fw_cfg_io, id "" | | dev: kvmclock, id "" | | dev: kvmvapic, id "" |
注意dev和bus是交替出现的,更加简化的设备图如下:
| bus dev bus dev bus dev | | main-system-bus - ioapic | | - q35-pcihost - pcie.0 - mch | | - ICH9-LPC - isa.0 - isa-i8259 | | - ICH9 SMB | | - ich9-ahci | | - VGA | | - e1000 | | - ... |
由于对硬件和架构的不熟悉,这里反反复复研究了一天才有点眉目。我的理解如下:
main-system-bus 就是系统总线,ioapic直接连到系统总线上,符合我们对IOAPIC的认知,在q35架构图CPU为 Core 和 Pentium Pentium E2000 系列,而根据文档,Intel自从Pentium4/Xeon后就取消了APIC bus,换用系统总线。
但让我纠结的是,mch为什么会连到 pcie.0 上?按照定义,mch是Intel对北桥的称呼。但mch的全称其实是memory controller hub,在这里它指的就真的是内存控制器这个hub。
既然 mch 不是北桥,那么 q35-pcihost 自然就是了,一方面host bridge也是北桥的称呼,另一方面它上连系统总线,下连pcie总线。再看其定义:
| typedef struct Q35PCIHost { | | /*< private >*/ | | PCIExpressHost parent_obj; | | /*< public >*/ | |
| | MCHPCIState mch; | | } Q35PCIHost; | |
| | typedef struct MCHPCIState { | | /*< private >*/ | | PCIDevice parent_obj; | | /*< public >*/ | |
| | MemoryRegion *ram_memory; | | MemoryRegion *pci_address_space; | | MemoryRegion *system_memory; | | MemoryRegion *address_space_io; | | PAMMemoryRegion pam_regions[13]; | | MemoryRegion smram_region, open_high_smram; | | MemoryRegion smram, low_smram, high_smram; | | MemoryRegion tseg_blackhole, tseg_window; | | Range pci_hole; | | uint64_t below_4g_mem_size; | | uint64_t above_4g_mem_size; | | uint64_t pci_hole64_size; | | uint32_t short_root_bus; | | } MCHPCIState; |
和更加验证了我的想法。然后内存控制器又是用pci统一编号的,因此接在pcie.0上还算合理。
同理 ICH9-LPC 是 PCI/ISA bridge,也就是我们通常所说的南桥。然后上面接了条ISA总线,用来连接传统的(Legacy)ISA设备,比如PIC。
结合虚拟机内部的lshw输出以帮助理解:
| binss@ubuntu:~$ lshw -businfo | | WARNING: you should run this program as super-user. | | Bus info Device Class Description | | ==================================================== | | system Computer | | bus Motherboard | | memory 1999MiB System memory | | cpu@0 processor Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20G | | cpu@1 processor Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20G | | cpu@2 processor Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20G | | cpu@3 processor Intel(R) Xeon(R) CPU E5-2650 v4 @ 2.20G | | pci@0000:00:00.0 bridge 82G33/G31/P35/P31 Express DRAM Controll | | pci@0000:00:01.0 display VGA compatible controller | | pci@0000:00:02.0 enp0s2 network 82540EM Gigabit Ethernet Controller | | pci@0000:00:1f.0 bridge 82801IB (ICH9) LPC Interface Controller | | pci@0000:00:1f.2 storage 82801IR/IO/IH (ICH9R/DO/DH) 6 port SATA | | pci@0000:00:1f.3 bus 82801I (ICH9 Family) SMBus Controller | | scsi2 storage | | scsi@2:0.0.0 /dev/cdrom disk DVD reader |
设备关系
个人认为有两种关系,一种是 挂载关系 ,通过设置类对象成员的形式来定义设备之间的关系。另一种是 组合树+关联关系 ,通过设置设备的属性来定义QOM之间的关系。
挂载关系
设备会挂载到bus上。创建设备对象的调用链如下:
| qdev_create => qdev_try_create => 如果传入参数bus为NULL,则 bus = sysbus_get_default() => 如果全局变量 main_system_bus 不存在,调用 main_system_bus_create 创建并返回 | | => qdev_set_parent_bus => 设置 dev->parent_bus |
因此一个device挂载在哪个bus上,是由其 DeviceState 的 parent_bus 成员指定的。
在这里,main_system_bus 即 main-system-bus ,创建时不传入bus参数的设备都将挂载在该bus上,比如 ioapic 和 q35-pcihost
同理 ICH9-LPC 等设备在 qdev_create 时会传入bus参数,其指向 pcie.0 ,因此表示被挂载在 pcie.0 这个 PCIBus 上
除此之外,对于 PCI 设备 PCIDevice ,虽然其"父类" DeviceState 的 parent_bus 已经指定挂载在哪个bus上,但其还自己维护了一个 bus 成员,指向 PCIBus 。该成员在 do_pci_register_device 时设置。
以上定义了下游device和bus的关系,而上游device和bus的关系由 device 的 bus 成员来指向。比如
q35-pcihost 的 PCIExpressHost-PCIHostState 有 PCIBus 类型的 bus 成员,其在 q35_host_realize 时设置为 pcie.0
ICH9-LPC 的 ICH9LPCState 有 ISABus 类型的 isa_bus 成员, 其在 ich9_lpc_realize 时设置为 isa.0 (名字是在qbus_realize中拼凑出来的)
组合树关系
我们知道,无论是 bus 和 device ,本质上都是 QOM 对象。这些对象以路径的形式联系在一起,构成一棵组合树。
将 info qom-tree 精简(去除irq和memory-region)后如下:
| /machine (pc-q35-2.8-machine) | | /unattached (container) | | /device[2] (host-x86_64-cpu) | | /lapic (kvm-apic) | | /device[6] (ICH9-LPC) | | /isa.0 (ISA) | | /device[15] (i8042) | | /device[19] (i8257) | | /device[3] (host-x86_64-cpu) | | /lapic (kvm-apic) | | /device[7] (isa-i8259) | | /device[20] (i8257) | | /device[24] (ICH9 SMB) | | /i2c (i2c-bus) | | /device[8] (isa-i8259) | | /device[17] (vmmouse) | | /device[21] (ich9-ahci) | | /ide.4 (IDE) | | /ide.5 (IDE) | | /ide.0 (IDE) | | /ide.1 (IDE) | | /ide.2 (IDE) | | /ide.3 (IDE) | | /sysbus (System) | | /device[33] (VGA) | | /q35 (q35-pcihost) | | /pcie.0 (PCIE) | | /ioapic (ioapic) | | /mch (mch) | | /peripheral-anon (container) | | /peripheral (container) |
这样的关系是通过child属性来维系的。
main_system_bus_create 创建了 main-system-bus ,同时
| object_property_add_child(container_get(qdev_get_machine(), "/unattached"), "sysbus", OBJECT(main_system_bus), NULL); |
而 qdev_get_machine 定义如下:
| Object *qdev_get_machine(void) | | { | | static Object *dev; | |
| | if (dev == NULL) { | | dev = container_get(object_get_root(), "/machine"); | | } | |
| | return dev; | | } | |
| | Object *object_get_root(void) | | { | | static Object *root; | |
| | if (!root) { | | root = object_new("container"); | | } | |
| | return root; | | } | |
| | Object *container_get(Object *root, const char *path) | | { | | Object *obj, *child; | | gchar **parts; | | int i; | |
| | // 切分路径 | | parts = g_strsplit(path, "/", 0); | | assert(parts != NULL && parts[0] != NULL && !parts[0][0]); | | obj = root; | |
| | // 逐级访问,如果发现某一级不存在,则创建一个container补上 | | for (i = 1; parts[i] != NULL; i++, obj = child) { | | child = object_resolve_path_component(obj, parts[i]); | | if (!child) { | | child = object_new("container"); | | object_property_add_child(obj, parts[i], child, NULL); | | } | | } | |
| | g_strfreev(parts); | |
| | return obj; | | } |
首先 qdev_get_machine 调用了 container_get(object_get_root(), "/machine") ,而 object_get_root 负责返回根级对象。如果未创建则会创建一个再返回。我们可以看到它是一个container,根据其定义,可以发现其继承了Object,但没有定义新的成员,相当于alias:
| static const TypeInfo container_info = { | | .name = "container", | | .instance_size = sizeof(Object), | | .parent = TYPE_OBJECT, | | }; |
然后 container_get 负责从传入的第一个参数(root)出发,返回相对路径为第二个参数(path)的对象。它将相对路径按"/"进行切分,对每一级调用 object_resolve_path_component
| Object *object_resolve_path_component(Object *parent, const gchar *part) | | { | | ObjectProperty *prop = object_property_find(parent, part, NULL); | | if (prop == NULL) { | | return NULL; | | } | |
| | if (prop->resolve) { | | return prop->resolve(parent, prop->opaque, part); | | } else { | | return NULL; | | } | | } |
本质上就是查找名为 路径名 的属性,然后将其属性值返回。该属性值就是当前级对象在路径上的的下一级对象,然后再从下一级对象继续调用 object_resolve_path_component 找下下级对象。
如此迭代,直到相对路径切分出来的所有级别都被访问到,然后将最后一级对象,也就是目标对象返回。
如果某一级对象不存在,则创建一个container,将其作为当前级的child属性,然后继续下一级。
因此 main-system-bus 的路径为 /machine/unattached/sysbus ,符合 qom-tree。我们可以通过 object_resolve_path 根据路径找到设备对象:
| (gdb) p object_resolve_path("/machine/unattached/sysbus", 0) | | $92 = (Object *) 0x555556769d20 | | (gdb) p object_resolve_path("/machine/unattached/sysbus", 0)->class->type->name | | $93 = 0x555556683760 "System" |
同理,经过:
| object_property_add_child(qdev_get_machine(), "q35", OBJECT(q35_host), NULL); | | object_property_add_child(object_resolve_path(parent_name, NULL), "ioapic", OBJECT(dev), NULL); |
于是 ioapic 的路径为 /machine/q35/ioapic ,回想上文提到 ioapic 和 q35-pcihost 一起挂在 main-system-bus 上,属于平级关系,显然挂载关系和组合树关系有不一致之处。
对于bus来说,在 qbus_realize => object_property_add_child(OBJECT(bus->parent), bus->name, OBJECT(bus), NULL) 中被添加为上一级对象的child属性,比如 pcie.0 被作为 q35-pcihost 的pcie.0属性,于是路径为 /machine/q35/pcie.0
对于memory region来说,在 memory_region_init => object_property_add_child 中被添加为上一级对象的child属性。
关联关系
在组合树关系中,我们可以看到,设备之间通过child属性,构成了一棵设备树,以machine为根,分门别类地向下展开。通过路径,我们能够从根设备出发,一级一级地向下迭代,找到目标设备。
但组合树还不能完全表达设备之间的关系,在child关系中,父节点掌控了子节点的生命周期。如果我们不需要这么强的关系,而仅仅是需要表示一种设备和另一种设备有关联,应该如何表示呢?
为此QOM定义了backlink关系。通过设置设备的link属性,表示一个设备引用了另外一个设备,在设置后,一个设备可以通过它的link属性访问到另一个设备,这也模拟了在硬件上两个设备之间直接连起来的场景。
一种最典型的backlink就是 bus 和插在它上面的child device。child device会有指向bus的link属性,比如:
pc_q35_init => qdev_create(NULL, TYPE_Q35_HOST_DEVICE) => qdev_try_create => object_new 会调用其父类的实例函数,即 device_initfn ,则:
| static void device_initfn(Object *obj) | | { | | ... | | object_property_add_link(OBJECT(dev), "parent_bus", TYPE_BUS, | | (Object **)&dev->parent_bus, NULL, 0, | | &error_abort); | | } |
将 main-system-bus 设置为 q35-pcihost 的link属性,名为 parent_bus 。注意这里的 dev->parent_bus 还是空的,因为 parent_bus 还没设置,直到:
pc_q35_init => qdev_create(NULL, TYPE_Q35_HOST_DEVICE) => qdev_try_create => qdev_set_parent_bus 的 dev->parent_bus = bus; 才会填上。因此属性值存的不是对象成员的值,而是对象成员的指针。当然由于 parent_bus 成员本来就是指针,于是这里是指针的指针(Object **)。
相反,bus会有指向child device的link属性:
qdev_set_parent_bus 中, dev->parent_bus = bus; 的下一行就是 bus_add_child :
| static void bus_add_child(BusState *bus, DeviceState *child) | | { | | char name[32]; | | BusChild *kid = g_malloc0(sizeof(*kid)); | |
| | kid->index = bus->max_index++; | | kid->child = child; | | object_ref(OBJECT(kid->child)); | |
| | QTAILQ_INSERT_HEAD(&bus->children, kid, sibling); | |
| | /* This transfers ownership of kid->child to the property. */ | | snprintf(name, sizeof(name), "child[%d]", kid->index); | | object_property_add_link(OBJECT(bus), name, | | object_get_typename(OBJECT(child)), | | (Object **)&kid->child, | | NULL, /* read-only property */ | | 0, /* return ownership on prop deletion */ | | NULL); | | } |
它将 q35-pcihost device设置为 main-system-bus 的link属性,名为 child[3] (注:前三个是kvmvapic、kvmclock和fw_cfg_io),值为 bus->children中的对应child的地址(指针的指针)。
于是 bus 和 device 之间的关系就通过两个 link 属性关联起来了。
| /machine (pc-q35-2.8-machine) | | /unattached (container) | | /sysbus (System) <------------------ | | ------- | | | | child[3] | parent_bus | | <------ | | | /q35 (q35-pcihost) ------------------- | | /pcie.0 (PCIE) | | /ioapic (ioapic) | | /mch (mch) |
如果说设备之间的关系是一棵树,那么多了backlink后设备之间的关系就成为了一幅有向图。
于是我们可以通过路径 /machine/unattached/sysbus/child[3] 访问 q35-pcihost :
| (gdb) p object_resolve_path("/machine/unattached/sysbus/child[3]", 0) | | $95 = (Object *) 0x55555694ec00 | | (gdb) p object_resolve_path("/machine/unattached/sysbus/child[3]", 0)->class->type->name | | $96 = 0x555556690ae0 "q35-pcihost" |
而路径是可以回绕的,于是有这样的玩法:
| (gdb) p object_resolve_path("/machine/unattached/sysbus", 0) | | $97 = (Object *) 0x555556769d20 | | (gdb) p object_resolve_path("/machine/unattached/sysbus/child[3]/parent_bus", 0) | | $98 = (Object *) 0x555556769d20 | | (gdb) p object_resolve_path("/machine/unattached/sysbus/child[3]/parent_bus/child[3]/parent_bus", 0) | | $99 = (Object *) 0x555556769d20 | | (gdb) p object_resolve_path("/machine/unattached/sysbus/child[3]/parent_bus/child[3]/parent_bus/child[3]/parent_bus", 0) | | $100 = (Object *) 0x555556769d20 | | ... |
总结
本文大致介绍了QEMU中"最新"q35架构的组成,同时对设备之间的关系也进行了分析。
接下来将研究QEMU中设备的模拟,包括中断控制器PIC、IOAPIC,以及中断是如何在设备之间传递和路由的。
|