xref: /qemu/hw/i386/microvm-dt.c (revision a976a99a)
1 /*
2  * microvm device tree support
3  *
4  * This generates an device tree for microvm and exports it via fw_cfg
5  * as "etc/fdt" to the firmware (edk2 specifically).
6  *
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.
10  *
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
14  *         interrupts.
15  *
16  * Note 2: This is for firmware only. OSes should use the more
17  *         complete ACPI tables for hardware discovery.
18  *
19  * ----------------------------------------------------------------------
20  *
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.
24  *
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
28  * more details.
29  *
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/>.
32  */
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"
43 
44 #include "microvm-dt.h"
45 
46 static bool debug;
47 
48 static void dt_add_microvm_irq(MicrovmMachineState *mms,
49                                const char *nodename, uint32_t irq)
50 {
51     int index = 0;
52 
53     if (irq >= IO_APIC_SECONDARY_IRQBASE) {
54         irq -= IO_APIC_SECONDARY_IRQBASE;
55         index++;
56     }
57 
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);
61 }
62 
63 static void dt_add_virtio(MicrovmMachineState *mms, VirtIOMMIOProxy *mmio)
64 {
65     SysBusDevice *dev = SYS_BUS_DEVICE(mmio);
66     VirtioBusState *mmio_virtio_bus = &mmio->bus;
67     BusState *mmio_bus = &mmio_virtio_bus->parent_obj;
68     char *nodename;
69 
70     if (QTAILQ_EMPTY(&mmio_bus->children)) {
71         return;
72     }
73 
74     hwaddr base = dev->mmio[0].addr;
75     hwaddr size = 512;
76     unsigned index = (base - VIRTIO_MMIO_BASE) / size;
77     uint32_t irq = mms->virtio_irq_base + index;
78 
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);
85     g_free(nodename);
86 }
87 
88 static void dt_add_xhci(MicrovmMachineState *mms)
89 {
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;
94     char *nodename;
95 
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);
102     g_free(nodename);
103 }
104 
105 static void dt_add_pcie(MicrovmMachineState *mms)
106 {
107     hwaddr base = PCIE_MMIO_BASE;
108     int nr_pcie_buses;
109     char *nodename;
110 
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);
120 
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",
125 
126                                      1, FDT_PCI_RANGE_MMIO,
127                                      2, mms->gpex.mmio32.base,
128                                      2, mms->gpex.mmio32.base,
129                                      2, mms->gpex.mmio32.size,
130 
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);
135     } else {
136         qemu_fdt_setprop_sized_cells(mms->fdt, nodename, "ranges",
137 
138                                      1, FDT_PCI_RANGE_MMIO,
139                                      2, mms->gpex.mmio32.base,
140                                      2, mms->gpex.mmio32.base,
141                                      2, mms->gpex.mmio32.size);
142     }
143 
144     nr_pcie_buses = PCIE_ECAM_SIZE / PCIE_MMCFG_SIZE_MIN;
145     qemu_fdt_setprop_cells(mms->fdt, nodename, "bus-range", 0,
146                            nr_pcie_buses - 1);
147 
148     g_free(nodename);
149 }
150 
151 static void dt_add_ioapic(MicrovmMachineState *mms, SysBusDevice *dev)
152 {
153     hwaddr base = dev->mmio[0].addr;
154     char *nodename;
155     uint32_t ph;
156     int index;
157 
158     switch (base) {
159     case IO_APIC_DEFAULT_ADDRESS:
160         index = 0;
161         break;
162     case IO_APIC_SECONDARY_ADDRESS:
163         index = 1;
164         break;
165     default:
166         fprintf(stderr, "unknown ioapic @ %" PRIx64 "\n", base);
167         return;
168     }
169 
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",
178                                  2, base, 2, 0x1000);
179 
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;
184 
185     g_free(nodename);
186 }
187 
188 static void dt_add_isa_serial(MicrovmMachineState *mms, ISADevice *dev)
189 {
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);
193     hwaddr size = 8;
194     char *nodename;
195 
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);
201 
202     if (base == 0x3f8 /* com1 */) {
203         qemu_fdt_setprop_string(mms->fdt, "/chosen", "stdout-path", nodename);
204     }
205 
206     g_free(nodename);
207 }
208 
209 static void dt_add_isa_rtc(MicrovmMachineState *mms, ISADevice *dev)
210 {
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);
214     hwaddr size = 8;
215     char *nodename;
216 
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);
222     g_free(nodename);
223 }
224 
225 static void dt_setup_isa_bus(MicrovmMachineState *mms, DeviceState *bridge)
226 {
227     BusState *bus = qdev_get_child_bus(bridge, "isa.0");
228     BusChild *kid;
229     Object *obj;
230 
231     QTAILQ_FOREACH(kid, &bus->children, sibling) {
232         DeviceState *dev = kid->child;
233 
234         /* serial */
235         obj = object_dynamic_cast(OBJECT(dev), TYPE_ISA_SERIAL);
236         if (obj) {
237             dt_add_isa_serial(mms, ISA_DEVICE(obj));
238             continue;
239         }
240 
241         /* rtc */
242         obj = object_dynamic_cast(OBJECT(dev), TYPE_MC146818_RTC);
243         if (obj) {
244             dt_add_isa_rtc(mms, ISA_DEVICE(obj));
245             continue;
246         }
247 
248         if (debug) {
249             fprintf(stderr, "%s: unhandled: %s\n", __func__,
250                     object_get_typename(OBJECT(dev)));
251         }
252     }
253 }
254 
255 static void dt_setup_sys_bus(MicrovmMachineState *mms)
256 {
257     BusState *bus;
258     BusChild *kid;
259     Object *obj;
260 
261     /* sysbus devices */
262     bus = sysbus_get_default();
263     QTAILQ_FOREACH(kid, &bus->children, sibling) {
264         DeviceState *dev = kid->child;
265 
266         /* ioapic */
267         obj = object_dynamic_cast(OBJECT(dev), TYPE_IOAPIC);
268         if (obj) {
269             dt_add_ioapic(mms, SYS_BUS_DEVICE(obj));
270             continue;
271         }
272     }
273 
274     QTAILQ_FOREACH(kid, &bus->children, sibling) {
275         DeviceState *dev = kid->child;
276 
277         /* virtio */
278         obj = object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MMIO);
279         if (obj) {
280             dt_add_virtio(mms, VIRTIO_MMIO(obj));
281             continue;
282         }
283 
284         /* xhci */
285         obj = object_dynamic_cast(OBJECT(dev), TYPE_XHCI_SYSBUS);
286         if (obj) {
287             dt_add_xhci(mms);
288             continue;
289         }
290 
291         /* pcie */
292         obj = object_dynamic_cast(OBJECT(dev), TYPE_GPEX_HOST);
293         if (obj) {
294             dt_add_pcie(mms);
295             continue;
296         }
297 
298         /* isa */
299         obj = object_dynamic_cast(OBJECT(dev), "isabus-bridge");
300         if (obj) {
301             dt_setup_isa_bus(mms, DEVICE(obj));
302             continue;
303         }
304 
305         if (debug) {
306             obj = object_dynamic_cast(OBJECT(dev), TYPE_IOAPIC);
307             if (obj) {
308                 /* ioapic already added in first pass */
309                 continue;
310             }
311             fprintf(stderr, "%s: unhandled: %s\n", __func__,
312                     object_get_typename(OBJECT(dev)));
313         }
314     }
315 }
316 
317 void dt_setup_microvm(MicrovmMachineState *mms)
318 {
319     X86MachineState *x86ms = X86_MACHINE(mms);
320     int size = 0;
321 
322     mms->fdt = create_device_tree(&size);
323 
324     /* root node */
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);
328 
329     qemu_fdt_add_subnode(mms->fdt, "/chosen");
330     dt_setup_sys_bus(mms);
331 
332     /* add to fw_cfg */
333     if (debug) {
334         fprintf(stderr, "%s: add etc/fdt to fw_cfg\n", __func__);
335     }
336     fw_cfg_add_file(x86ms->fw_cfg, "etc/fdt", mms->fdt, size);
337 
338     if (debug) {
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__);
342             return;
343         }
344         int ret = system("dtc -I dtb -O dts microvm.fdt");
345         if (ret != 0) {
346             fprintf(stderr, "%s: oops, dtc not installed?\n", __func__);
347         }
348     }
349 }
350