qemu/hw/i386/microvm-dt.c
<<
>>
Prefs
   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
  46static bool debug;
  47
  48static 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
  63static 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
  88static 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
 105static 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
 151static 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
 188static 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
 209static 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
 225static 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
 255static 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
 317void 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