2 * microvm device tree support
4 * This generates an device tree for microvm and exports it via fw_cfg
5 * as "etc/fdt" to the firmware (edk2 specifically).
7 * The use case is to allow edk2 find the pcie ecam and the virtio
8 * devices, without adding an ACPI parser, reusing the fdt parser
9 * which is needed anyway for the arm platform.
11 * Note 1: The device tree is incomplete. CPUs and memory is missing
12 * for example, those can be detected using other fw_cfg files.
13 * Also pci ecam irq routing is not there, edk2 doesn't use
16 * Note 2: This is for firmware only. OSes should use the more
17 * complete ACPI tables for hardware discovery.
19 * ----------------------------------------------------------------------
21 * This program is free software; you can redistribute it and/or modify it
22 * under the terms and conditions of the GNU General Public License,
23 * version 2 or later, as published by the Free Software Foundation.
25 * This program is distributed in the hope it will be useful, but WITHOUT
26 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
27 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
30 * You should have received a copy of the GNU General Public License along with
31 * this program. If not, see <http://www.gnu.org/licenses/>.
33 #include "qemu/osdep.h"
34 #include "qemu/cutils.h"
35 #include "qapi/error.h"
36 #include "sysemu/device_tree.h"
37 #include "hw/char/serial.h"
38 #include "hw/i386/fw_cfg.h"
39 #include "hw/rtc/mc146818rtc.h"
40 #include "hw/sysbus.h"
41 #include "hw/virtio/virtio-mmio.h"
42 #include "hw/usb/xhci.h"
44 #include "microvm-dt.h"
48 static void dt_add_microvm_irq(MicrovmMachineState
*mms
,
49 const char *nodename
, uint32_t irq
)
53 if (irq
>= IO_APIC_SECONDARY_IRQBASE
) {
54 irq
-= IO_APIC_SECONDARY_IRQBASE
;
58 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "interrupt-parent",
59 mms
->ioapic_phandle
[index
]);
60 qemu_fdt_setprop_cells(mms
->fdt
, nodename
, "interrupts", irq
, 0);
63 static void dt_add_virtio(MicrovmMachineState
*mms
, VirtIOMMIOProxy
*mmio
)
65 SysBusDevice
*dev
= SYS_BUS_DEVICE(mmio
);
66 VirtioBusState
*mmio_virtio_bus
= &mmio
->bus
;
67 BusState
*mmio_bus
= &mmio_virtio_bus
->parent_obj
;
70 if (QTAILQ_EMPTY(&mmio_bus
->children
)) {
74 hwaddr base
= dev
->mmio
[0].addr
;
76 unsigned index
= (base
- VIRTIO_MMIO_BASE
) / size
;
77 uint32_t irq
= mms
->virtio_irq_base
+ index
;
79 nodename
= g_strdup_printf("/virtio_mmio@%" PRIx64
, base
);
80 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
81 qemu_fdt_setprop_string(mms
->fdt
, nodename
, "compatible", "virtio,mmio");
82 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
83 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
84 dt_add_microvm_irq(mms
, nodename
, irq
);
88 static void dt_add_xhci(MicrovmMachineState
*mms
)
90 const char compat
[] = "generic-xhci";
91 uint32_t irq
= MICROVM_XHCI_IRQ
;
92 hwaddr base
= MICROVM_XHCI_BASE
;
93 hwaddr size
= XHCI_LEN_REGS
;
96 nodename
= g_strdup_printf("/usb@%" PRIx64
, base
);
97 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
98 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
99 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
100 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
101 dt_add_microvm_irq(mms
, nodename
, irq
);
105 static void dt_add_pcie(MicrovmMachineState
*mms
)
107 hwaddr base
= PCIE_MMIO_BASE
;
111 nodename
= g_strdup_printf("/pcie@%" PRIx64
, base
);
112 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
113 qemu_fdt_setprop_string(mms
->fdt
, nodename
,
114 "compatible", "pci-host-ecam-generic");
115 qemu_fdt_setprop_string(mms
->fdt
, nodename
, "device_type", "pci");
116 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#address-cells", 3);
117 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#size-cells", 2);
118 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "linux,pci-domain", 0);
119 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
121 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg",
122 2, PCIE_ECAM_BASE
, 2, PCIE_ECAM_SIZE
);
123 if (mms
->gpex
.mmio64
.size
) {
124 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "ranges",
126 1, FDT_PCI_RANGE_MMIO
,
127 2, mms
->gpex
.mmio32
.base
,
128 2, mms
->gpex
.mmio32
.base
,
129 2, mms
->gpex
.mmio32
.size
,
131 1, FDT_PCI_RANGE_MMIO_64BIT
,
132 2, mms
->gpex
.mmio64
.base
,
133 2, mms
->gpex
.mmio64
.base
,
134 2, mms
->gpex
.mmio64
.size
);
136 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "ranges",
138 1, FDT_PCI_RANGE_MMIO
,
139 2, mms
->gpex
.mmio32
.base
,
140 2, mms
->gpex
.mmio32
.base
,
141 2, mms
->gpex
.mmio32
.size
);
144 nr_pcie_buses
= PCIE_ECAM_SIZE
/ PCIE_MMCFG_SIZE_MIN
;
145 qemu_fdt_setprop_cells(mms
->fdt
, nodename
, "bus-range", 0,
151 static void dt_add_ioapic(MicrovmMachineState
*mms
, SysBusDevice
*dev
)
153 hwaddr base
= dev
->mmio
[0].addr
;
159 case IO_APIC_DEFAULT_ADDRESS
:
162 case IO_APIC_SECONDARY_ADDRESS
:
166 fprintf(stderr
, "unknown ioapic @ %" PRIx64
"\n", base
);
170 nodename
= g_strdup_printf("/ioapic%d@%" PRIx64
, index
+ 1, base
);
171 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
172 qemu_fdt_setprop_string(mms
->fdt
, nodename
,
173 "compatible", "intel,ce4100-ioapic");
174 qemu_fdt_setprop(mms
->fdt
, nodename
, "interrupt-controller", NULL
, 0);
175 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#interrupt-cells", 0x2);
176 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#address-cells", 0x2);
177 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg",
180 ph
= qemu_fdt_alloc_phandle(mms
->fdt
);
181 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "phandle", ph
);
182 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "linux,phandle", ph
);
183 mms
->ioapic_phandle
[index
] = ph
;
188 static void dt_add_isa_serial(MicrovmMachineState
*mms
, ISADevice
*dev
)
190 const char compat
[] = "ns16550";
191 uint32_t irq
= object_property_get_int(OBJECT(dev
), "irq", &error_fatal
);
192 hwaddr base
= object_property_get_int(OBJECT(dev
), "iobase", &error_fatal
);
196 nodename
= g_strdup_printf("/serial@%" PRIx64
, base
);
197 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
198 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
199 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
200 dt_add_microvm_irq(mms
, nodename
, irq
);
202 if (base
== 0x3f8 /* com1 */) {
203 qemu_fdt_setprop_string(mms
->fdt
, "/chosen", "stdout-path", nodename
);
209 static void dt_add_isa_rtc(MicrovmMachineState
*mms
, ISADevice
*dev
)
211 const char compat
[] = "motorola,mc146818";
212 uint32_t irq
= object_property_get_uint(OBJECT(dev
), "irq", &error_fatal
);
213 hwaddr base
= object_property_get_uint(OBJECT(dev
), "iobase", &error_fatal
);
217 nodename
= g_strdup_printf("/rtc@%" PRIx64
, base
);
218 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
219 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
220 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
221 dt_add_microvm_irq(mms
, nodename
, irq
);
225 static void dt_setup_isa_bus(MicrovmMachineState
*mms
, DeviceState
*bridge
)
227 BusState
*bus
= qdev_get_child_bus(bridge
, "isa.0");
231 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
232 DeviceState
*dev
= kid
->child
;
235 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_ISA_SERIAL
);
237 dt_add_isa_serial(mms
, ISA_DEVICE(obj
));
242 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_MC146818_RTC
);
244 dt_add_isa_rtc(mms
, ISA_DEVICE(obj
));
249 fprintf(stderr
, "%s: unhandled: %s\n", __func__
,
250 object_get_typename(OBJECT(dev
)));
255 static void dt_setup_sys_bus(MicrovmMachineState
*mms
)
262 bus
= sysbus_get_default();
263 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
264 DeviceState
*dev
= kid
->child
;
267 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_IOAPIC
);
269 dt_add_ioapic(mms
, SYS_BUS_DEVICE(obj
));
274 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
275 DeviceState
*dev
= kid
->child
;
278 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_VIRTIO_MMIO
);
280 dt_add_virtio(mms
, VIRTIO_MMIO(obj
));
285 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_XHCI_SYSBUS
);
292 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_GPEX_HOST
);
299 obj
= object_dynamic_cast(OBJECT(dev
), "isabus-bridge");
301 dt_setup_isa_bus(mms
, DEVICE(obj
));
306 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_IOAPIC
);
308 /* ioapic already added in first pass */
311 fprintf(stderr
, "%s: unhandled: %s\n", __func__
,
312 object_get_typename(OBJECT(dev
)));
317 void dt_setup_microvm(MicrovmMachineState
*mms
)
319 X86MachineState
*x86ms
= X86_MACHINE(mms
);
322 mms
->fdt
= create_device_tree(&size
);
325 qemu_fdt_setprop_string(mms
->fdt
, "/", "compatible", "linux,microvm");
326 qemu_fdt_setprop_cell(mms
->fdt
, "/", "#address-cells", 0x2);
327 qemu_fdt_setprop_cell(mms
->fdt
, "/", "#size-cells", 0x2);
329 qemu_fdt_add_subnode(mms
->fdt
, "/chosen");
330 dt_setup_sys_bus(mms
);
334 fprintf(stderr
, "%s: add etc/fdt to fw_cfg\n", __func__
);
336 fw_cfg_add_file(x86ms
->fw_cfg
, "etc/fdt", mms
->fdt
, size
);
339 fprintf(stderr
, "%s: writing microvm.fdt\n", __func__
);
340 if (!g_file_set_contents("microvm.fdt", mms
->fdt
, size
, NULL
)) {
341 fprintf(stderr
, "%s: writing microvm.fdt failed\n", __func__
);
344 int ret
= system("dtc -I dtb -O dts microvm.fdt");
346 fprintf(stderr
, "%s: oops, dtc not installed?\n", __func__
);