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 "sysemu/device_tree.h"
36 #include "hw/char/serial.h"
37 #include "hw/i386/fw_cfg.h"
38 #include "hw/rtc/mc146818rtc.h"
39 #include "hw/sysbus.h"
40 #include "hw/virtio/virtio-mmio.h"
41 #include "hw/usb/xhci.h"
43 #include "microvm-dt.h"
47 static void dt_add_microvm_irq(MicrovmMachineState
*mms
,
48 const char *nodename
, uint32_t irq
)
52 if (irq
>= IO_APIC_SECONDARY_IRQBASE
) {
53 irq
-= IO_APIC_SECONDARY_IRQBASE
;
57 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "interrupt-parent",
58 mms
->ioapic_phandle
[index
]);
59 qemu_fdt_setprop_cells(mms
->fdt
, nodename
, "interrupts", irq
, 0);
62 static void dt_add_virtio(MicrovmMachineState
*mms
, VirtIOMMIOProxy
*mmio
)
64 SysBusDevice
*dev
= SYS_BUS_DEVICE(mmio
);
65 VirtioBusState
*mmio_virtio_bus
= &mmio
->bus
;
66 BusState
*mmio_bus
= &mmio_virtio_bus
->parent_obj
;
69 if (QTAILQ_EMPTY(&mmio_bus
->children
)) {
73 hwaddr base
= dev
->mmio
[0].addr
;
75 unsigned index
= (base
- VIRTIO_MMIO_BASE
) / size
;
76 uint32_t irq
= mms
->virtio_irq_base
+ index
;
78 nodename
= g_strdup_printf("/virtio_mmio@%" PRIx64
, base
);
79 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
80 qemu_fdt_setprop_string(mms
->fdt
, nodename
, "compatible", "virtio,mmio");
81 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
82 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
83 dt_add_microvm_irq(mms
, nodename
, irq
);
87 static void dt_add_xhci(MicrovmMachineState
*mms
)
89 const char compat
[] = "generic-xhci";
90 uint32_t irq
= MICROVM_XHCI_IRQ
;
91 hwaddr base
= MICROVM_XHCI_BASE
;
92 hwaddr size
= XHCI_LEN_REGS
;
95 nodename
= g_strdup_printf("/usb@%" PRIx64
, base
);
96 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
97 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
98 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
99 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
100 dt_add_microvm_irq(mms
, nodename
, irq
);
104 static void dt_add_pcie(MicrovmMachineState
*mms
)
106 hwaddr base
= PCIE_MMIO_BASE
;
110 nodename
= g_strdup_printf("/pcie@%" PRIx64
, base
);
111 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
112 qemu_fdt_setprop_string(mms
->fdt
, nodename
,
113 "compatible", "pci-host-ecam-generic");
114 qemu_fdt_setprop_string(mms
->fdt
, nodename
, "device_type", "pci");
115 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#address-cells", 3);
116 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#size-cells", 2);
117 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "linux,pci-domain", 0);
118 qemu_fdt_setprop(mms
->fdt
, nodename
, "dma-coherent", NULL
, 0);
120 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg",
121 2, PCIE_ECAM_BASE
, 2, PCIE_ECAM_SIZE
);
122 if (mms
->gpex
.mmio64
.size
) {
123 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "ranges",
125 1, FDT_PCI_RANGE_MMIO
,
126 2, mms
->gpex
.mmio32
.base
,
127 2, mms
->gpex
.mmio32
.base
,
128 2, mms
->gpex
.mmio32
.size
,
130 1, FDT_PCI_RANGE_MMIO_64BIT
,
131 2, mms
->gpex
.mmio64
.base
,
132 2, mms
->gpex
.mmio64
.base
,
133 2, mms
->gpex
.mmio64
.size
);
135 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "ranges",
137 1, FDT_PCI_RANGE_MMIO
,
138 2, mms
->gpex
.mmio32
.base
,
139 2, mms
->gpex
.mmio32
.base
,
140 2, mms
->gpex
.mmio32
.size
);
143 nr_pcie_buses
= PCIE_ECAM_SIZE
/ PCIE_MMCFG_SIZE_MIN
;
144 qemu_fdt_setprop_cells(mms
->fdt
, nodename
, "bus-range", 0,
150 static void dt_add_ioapic(MicrovmMachineState
*mms
, SysBusDevice
*dev
)
152 hwaddr base
= dev
->mmio
[0].addr
;
158 case IO_APIC_DEFAULT_ADDRESS
:
161 case IO_APIC_SECONDARY_ADDRESS
:
165 fprintf(stderr
, "unknown ioapic @ %" PRIx64
"\n", base
);
169 nodename
= g_strdup_printf("/ioapic%d@%" PRIx64
, index
+ 1, base
);
170 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
171 qemu_fdt_setprop_string(mms
->fdt
, nodename
,
172 "compatible", "intel,ce4100-ioapic");
173 qemu_fdt_setprop(mms
->fdt
, nodename
, "interrupt-controller", NULL
, 0);
174 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#interrupt-cells", 0x2);
175 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "#address-cells", 0x2);
176 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg",
179 ph
= qemu_fdt_alloc_phandle(mms
->fdt
);
180 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "phandle", ph
);
181 qemu_fdt_setprop_cell(mms
->fdt
, nodename
, "linux,phandle", ph
);
182 mms
->ioapic_phandle
[index
] = ph
;
187 static void dt_add_isa_serial(MicrovmMachineState
*mms
, ISADevice
*dev
)
189 const char compat
[] = "ns16550";
190 uint32_t irq
= object_property_get_int(OBJECT(dev
), "irq", NULL
);
191 hwaddr base
= object_property_get_int(OBJECT(dev
), "iobase", NULL
);
195 nodename
= g_strdup_printf("/serial@%" PRIx64
, base
);
196 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
197 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
198 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
199 dt_add_microvm_irq(mms
, nodename
, irq
);
201 if (base
== 0x3f8 /* com1 */) {
202 qemu_fdt_setprop_string(mms
->fdt
, "/chosen", "stdout-path", nodename
);
208 static void dt_add_isa_rtc(MicrovmMachineState
*mms
, ISADevice
*dev
)
210 const char compat
[] = "motorola,mc146818";
211 uint32_t irq
= RTC_ISA_IRQ
;
212 hwaddr base
= RTC_ISA_BASE
;
216 nodename
= g_strdup_printf("/rtc@%" PRIx64
, base
);
217 qemu_fdt_add_subnode(mms
->fdt
, nodename
);
218 qemu_fdt_setprop(mms
->fdt
, nodename
, "compatible", compat
, sizeof(compat
));
219 qemu_fdt_setprop_sized_cells(mms
->fdt
, nodename
, "reg", 2, base
, 2, size
);
220 dt_add_microvm_irq(mms
, nodename
, irq
);
224 static void dt_setup_isa_bus(MicrovmMachineState
*mms
, DeviceState
*bridge
)
226 BusState
*bus
= qdev_get_child_bus(bridge
, "isa.0");
230 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
231 DeviceState
*dev
= kid
->child
;
234 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_ISA_SERIAL
);
236 dt_add_isa_serial(mms
, ISA_DEVICE(obj
));
241 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_MC146818_RTC
);
243 dt_add_isa_rtc(mms
, ISA_DEVICE(obj
));
248 fprintf(stderr
, "%s: unhandled: %s\n", __func__
,
249 object_get_typename(OBJECT(dev
)));
254 static void dt_setup_sys_bus(MicrovmMachineState
*mms
)
261 bus
= sysbus_get_default();
262 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
263 DeviceState
*dev
= kid
->child
;
266 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_IOAPIC
);
268 dt_add_ioapic(mms
, SYS_BUS_DEVICE(obj
));
273 QTAILQ_FOREACH(kid
, &bus
->children
, sibling
) {
274 DeviceState
*dev
= kid
->child
;
277 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_VIRTIO_MMIO
);
279 dt_add_virtio(mms
, VIRTIO_MMIO(obj
));
284 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_XHCI_SYSBUS
);
291 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_GPEX_HOST
);
298 obj
= object_dynamic_cast(OBJECT(dev
), "isabus-bridge");
300 dt_setup_isa_bus(mms
, DEVICE(obj
));
305 obj
= object_dynamic_cast(OBJECT(dev
), TYPE_IOAPIC
);
307 /* ioapic already added in first pass */
310 fprintf(stderr
, "%s: unhandled: %s\n", __func__
,
311 object_get_typename(OBJECT(dev
)));
316 void dt_setup_microvm(MicrovmMachineState
*mms
)
318 X86MachineState
*x86ms
= X86_MACHINE(mms
);
321 mms
->fdt
= create_device_tree(&size
);
324 qemu_fdt_setprop_string(mms
->fdt
, "/", "compatible", "linux,microvm");
325 qemu_fdt_setprop_cell(mms
->fdt
, "/", "#address-cells", 0x2);
326 qemu_fdt_setprop_cell(mms
->fdt
, "/", "#size-cells", 0x2);
328 qemu_fdt_add_subnode(mms
->fdt
, "/chosen");
329 dt_setup_sys_bus(mms
);
333 fprintf(stderr
, "%s: add etc/fdt to fw_cfg\n", __func__
);
335 fw_cfg_add_file(x86ms
->fw_cfg
, "etc/fdt", mms
->fdt
, size
);
338 fprintf(stderr
, "%s: writing microvm.fdt\n", __func__
);
339 if (!g_file_set_contents("microvm.fdt", mms
->fdt
, size
, NULL
)) {
340 fprintf(stderr
, "%s: writing microvm.fdt failed\n", __func__
);
343 int ret
= system("dtc -I dtb -O dts microvm.fdt");
345 fprintf(stderr
, "%s: oops, dtc not installed?\n", __func__
);