qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

microvm-dt.c (11713B)


      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 }