qemu/hw/arm/arm_generic_fdt.c
<<
>>
Prefs
   1/*
   2 * Xilinx Zynq Baseboard System emulation.
   3 *
   4 * Copyright (c) 2012 Xilinx. Inc
   5 * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@xilinx.com)
   6 *
   7 * This program is free software; you can redistribute it and/or
   8 * modify it under the terms of the GNU General Public License
   9 * as published by the Free Software Foundation; either version
  10 * 2 of the License, or (at your option) any later version.
  11 *
  12 * You should have received a copy of the GNU General Public License along
  13 * with this program; if not, see <http://www.gnu.org/licenses/>.
  14 */
  15
  16#include "qemu/osdep.h"
  17#include "cpu.h"
  18#include "exec/cpu-all.h"
  19#include "qemu/config-file.h"
  20#include "exec/memory.h"
  21#include "exec/address-spaces.h"
  22#include "sysemu/sysemu.h"
  23#include "sysemu/blockdev.h"
  24#include "hw/sysbus.h"
  25#include "hw/boards.h"
  26#include "hw/loader.h"
  27#include "qapi/error.h"
  28#include "hw/block/flash.h"
  29#include "qemu/error-report.h"
  30
  31#include <libfdt.h>
  32#include "hw/fdt_generic_util.h"
  33#include "hw/fdt_generic_devices.h"
  34
  35#include "hw/arm/arm.h"
  36
  37#define MACHINE_NAME "arm-generic-fdt"
  38
  39#define MAX_CPUS 4
  40
  41#define ZYNQ7000_MPCORE_PERIPHBASE 0xF8F00000
  42
  43#define SMP_BOOT_ADDR 0xfffffff0
  44/* Meaningless, but keeps arm boot happy */
  45#define SMP_BOOTREG_ADDR 0xfffffffc
  46
  47/* Entry point for secondary CPU */
  48static uint32_t zynq_smpboot[] = {
  49    0xe320f003, /* wfi */
  50    0xeafffffd, /* beq     <wfi> */
  51};
  52
  53static void zynq_write_secondary_boot(ARMCPU *cpu,
  54                                      const struct arm_boot_info *info)
  55{
  56    int n;
  57
  58    for (n = 0; n < ARRAY_SIZE(zynq_smpboot); n++) {
  59        zynq_smpboot[n] = tswap32(zynq_smpboot[n]);
  60    }
  61    rom_add_blob_fixed("smpboot", zynq_smpboot, sizeof(zynq_smpboot),
  62                       SMP_BOOT_ADDR);
  63}
  64
  65static void zynq_ps7_usb_nuke_phy(void *fdt)
  66{
  67    char usb_node_path[DT_PATH_LENGTH];
  68
  69    int ret = qemu_devtree_node_by_compatible(fdt, usb_node_path,
  70                                              "xlnx,ps7-usb-1.00.a");
  71    if (!ret) {
  72        qemu_fdt_setprop_string(fdt, usb_node_path, "dr_mode", "host");
  73    }
  74}
  75
  76static int zynq_ps7_mdio_phy_connect(char *node_path, FDTMachineInfo *fdti,
  77                                     void *Opaque)
  78{
  79    Object *parent;
  80    char parent_node_path[DT_PATH_LENGTH];
  81
  82    /* Register MDIO obj instance to fdti, useful during child registration */
  83    fdt_init_set_opaque(fdti, node_path, Opaque);
  84    if (qemu_devtree_getparent(fdti->fdt, parent_node_path, node_path)) {
  85        abort();
  86    }
  87
  88    /* Wait for the parent to be created */
  89    while (!fdt_init_has_opaque(fdti, parent_node_path)) {
  90        fdt_init_yield(fdti);
  91    }
  92
  93    /* Get the parent obj (i.e gem object), which was registerd in fdti */
  94    parent = fdt_init_get_opaque(fdti, parent_node_path);
  95
  96    /* Add parent to mdio node */
  97    object_property_add_child(OBJECT(parent), "mdio_child", OBJECT(Opaque),
  98                              NULL);
  99
 100    /* Set mdio property of gem device */
 101    object_property_set_link(OBJECT(parent), OBJECT(Opaque), "mdio", NULL);
 102    return 0;
 103}
 104
 105static char *zynq_ps7_qspi_flash_node_clone(void *fdt)
 106{
 107    char qspi_node_path[DT_PATH_LENGTH];
 108    char qspi_new_node_path[DT_PATH_LENGTH];
 109    char *qspi_clone_name = NULL;
 110    uint32_t val[2];
 111
 112    /* clear node paths */
 113    memset(qspi_node_path, 0, sizeof(qspi_node_path));
 114    memset(qspi_new_node_path, 0, sizeof(qspi_new_node_path));
 115
 116    /* search for ps7 qspi node */
 117    int ret = qemu_devtree_node_by_compatible(fdt, qspi_node_path,
 118                                              "xlnx,zynq-qspi-1.0");
 119    if (ret == 0) {
 120        int qspi_is_dual = qemu_fdt_getprop_cell(fdt, qspi_node_path,
 121                                                 "is-dual", 0, false, NULL);
 122        /* Set bus-cells property to 1 */
 123        val[0] = cpu_to_be32(1);
 124        val[1] = 0;
 125        fdt_setprop(fdt, fdt_path_offset(fdt, qspi_node_path),
 126                    "#bus-cells", val, 4);
 127
 128        /* Generate dummy name */
 129        snprintf(qspi_new_node_path, DT_PATH_LENGTH, "%s/ps7-qspi-dummy@0",
 130                 qspi_node_path);
 131
 132        /* get the spi flash node to clone from (assume first child node) */
 133        int child_num = qemu_devtree_get_num_children(fdt, qspi_node_path, 1);
 134        char **child_flash = qemu_devtree_get_children(fdt, qspi_node_path, 1);
 135        if (child_num > 0) {
 136            char *compat_str = NULL;
 137            compat_str = qemu_fdt_getprop(fdt, child_flash[0],
 138                                          "compatible", NULL, false, NULL);
 139        /* Attach Default flash node to bus 1 */
 140        val[0] = 0;
 141        val[1] = 0;
 142        fdt_setprop(fdt, fdt_path_offset(fdt, child_flash[0]), "reg", val, 8);
 143
 144            /* Create the cloned node if the qspi controller is in dual spi mode
 145             * and the compatible string is avaliable */
 146            if (compat_str != NULL) {
 147                if (qspi_is_dual == 1) {
 148                    /* Clone first node, preserving only 'compatible' value */
 149                    qemu_fdt_add_subnode(fdt, qspi_new_node_path);
 150                    qemu_fdt_setprop_string(fdt, qspi_new_node_path,
 151                                             "compatible", compat_str);
 152                    qspi_clone_name = g_strdup(qspi_new_node_path);
 153
 154                    /* Attach Dummy flash node to bus 0 */
 155                    val[0] = 0;
 156                    val[1] = cpu_to_be32(1);
 157                    fdt_setprop(fdt, fdt_path_offset(fdt, qspi_new_node_path),
 158                                "reg", val, 8);
 159                }
 160                g_free(compat_str);
 161            }
 162        }
 163        g_free(child_flash);
 164    }
 165
 166    return qspi_clone_name;
 167}
 168
 169static struct arm_boot_info arm_generic_fdt_binfo = {};
 170
 171static void arm_generic_fdt_init(MachineState *machine)
 172{
 173    ram_addr_t ram_kernel_base = 0, ram_kernel_size = 0, start_addr;
 174
 175    void *fdt = NULL;
 176    void *sw_fdt = NULL;
 177    int fdt_size, sw_fdt_size, mem_offset = 0;
 178    const char *dtb_arg, *hw_dtb_arg;
 179    char node_path[DT_PATH_LENGTH];
 180    FDTMachineInfo *fdti;
 181    MemoryRegion *mem_area;
 182    char *qspi_clone_spi_flash_node_name = NULL;
 183
 184    dtb_arg = qemu_opt_get(qemu_get_machine_opts(), "dtb");
 185    hw_dtb_arg = qemu_opt_get(qemu_get_machine_opts(), "hw-dtb");
 186    if (!dtb_arg && !hw_dtb_arg) {
 187        fprintf(stderr, "hw_dtb_arg: %s\n", hw_dtb_arg);
 188        goto no_dtb_arg;
 189    }
 190
 191    /* Software dtb is always the -dtb arg */
 192    if (dtb_arg) {
 193        sw_fdt = load_device_tree(dtb_arg, &sw_fdt_size);
 194        if (!sw_fdt) {
 195            error_report("Error: Unable to load Device Tree %s\n", dtb_arg);
 196            exit(1);
 197        }
 198    }
 199
 200    /* If the user provided a -hw-dtb, use it as the hw description.  */
 201    if (hw_dtb_arg) {
 202        fdt = load_device_tree(hw_dtb_arg, &fdt_size);
 203        if (!fdt) {
 204            hw_error("Error: Unable to load Device Tree %s\n", hw_dtb_arg);
 205            return;
 206        }
 207    } else if (sw_fdt) {
 208        fdt = sw_fdt;
 209        fdt_size = sw_fdt_size;
 210    }
 211
 212
 213    /* If booting PetaLinux ARM (Zynq Machine)*/
 214    if (!strcmp(MACHINE_GET_CLASS(machine)->name, "arm-generic-fdt-plnx")) {
 215        int node_offset = 0;
 216
 217        /* Added a dummy flash node, if is-dual property is set to 1*/
 218        qspi_clone_spi_flash_node_name = zynq_ps7_qspi_flash_node_clone(fdt);
 219
 220        /* Ensure that an interrupt controller exists before disabling it */
 221        if (!qemu_devtree_get_node_by_name(fdt, node_path,
 222                                           "interrupt-controller")) {
 223            qemu_fdt_setprop_cells(fdt, node_path,
 224                                   "disable-linux-gic-init", true);
 225        }
 226
 227        /* The Zynq-7000 device tree doesn't contain information about the
 228         * Configuation Base Address Register (reset-cbar) but we need to set
 229         * it in order for Linux to find the SCU. So add it into the device
 230         * tree for every A9 CPU.
 231         */
 232        do {
 233            node_offset = fdt_node_offset_by_compatible(fdt, node_offset,
 234                                                        "arm,cortex-a9");
 235            if (node_offset > 0) {
 236                fdt_get_path(fdt, node_offset, node_path, DT_PATH_LENGTH);
 237                qemu_fdt_setprop_cells(fdt, node_path, "reset-cbar",
 238                                       ZYNQ7000_MPCORE_PERIPHBASE);
 239            }
 240        } while (node_offset > 0);
 241    }
 242
 243    /* find memory node or add new one if needed */
 244    while (qemu_devtree_get_node_by_name(fdt, node_path, "memory")) {
 245        qemu_fdt_add_subnode(fdt, "/memory@0");
 246        qemu_fdt_setprop_cells(fdt, "/memory@0", "reg", 0, machine->ram_size);
 247    }
 248
 249    if (!qemu_fdt_getprop(fdt, "/memory", "compatible", NULL, 0, NULL)) {
 250        qemu_fdt_setprop_string(fdt, "/memory", "compatible",
 251                                "qemu:memory-region");
 252        qemu_fdt_setprop_cells(fdt, "/memory", "qemu,ram", 1);
 253    }
 254
 255    /* Instantiate peripherals from the FDT.  */
 256    fdti = fdt_generic_create_machine(fdt, NULL);
 257
 258    mem_area = MEMORY_REGION(object_resolve_path(node_path, NULL));
 259    ram_kernel_base = object_property_get_int(OBJECT(mem_area), "addr", NULL);
 260    ram_kernel_size = object_property_get_int(OBJECT(mem_area), "size", NULL);
 261
 262    if (!strcmp(MACHINE_GET_CLASS(machine)->name, "arm-generic-fdt-plnx")) {
 263    do {
 264        mem_offset = fdt_node_offset_by_compatible(fdt, mem_offset,
 265                                                   "qemu:memory-region");
 266        if (mem_offset > 0) {
 267            fdt_get_path(fdt, mem_offset, node_path, DT_PATH_LENGTH);
 268            mem_area = MEMORY_REGION(object_resolve_path(node_path, NULL));
 269
 270            if (!memory_region_is_mapped(mem_area)) {
 271                start_addr =  object_property_get_int(OBJECT(mem_area),
 272                                                      "addr", NULL);
 273                memory_region_add_subregion(get_system_memory(), start_addr,
 274                                            mem_area);
 275            }
 276        }
 277    } while (mem_offset > 0);
 278    }
 279
 280    fdt_init_destroy_fdti(fdti);
 281
 282    arm_generic_fdt_binfo.fdt = sw_fdt;
 283    arm_generic_fdt_binfo.fdt_size = sw_fdt_size;
 284    arm_generic_fdt_binfo.ram_size = ram_kernel_size;
 285    arm_generic_fdt_binfo.kernel_filename = machine->kernel_filename;
 286    arm_generic_fdt_binfo.kernel_cmdline = machine->kernel_cmdline;
 287    arm_generic_fdt_binfo.initrd_filename = machine->initrd_filename;
 288    arm_generic_fdt_binfo.nb_cpus = fdt_generic_num_cpus;
 289    arm_generic_fdt_binfo.write_secondary_boot = zynq_write_secondary_boot;
 290    arm_generic_fdt_binfo.smp_loader_start = SMP_BOOT_ADDR;
 291    arm_generic_fdt_binfo.smp_bootreg_addr = SMP_BOOTREG_ADDR;
 292    arm_generic_fdt_binfo.board_id = 0xd32;
 293    arm_generic_fdt_binfo.loader_start = ram_kernel_base;
 294    arm_generic_fdt_binfo.secure_boot = true;
 295
 296    if (qspi_clone_spi_flash_node_name != NULL) {
 297        /* Remove cloned DTB node */
 298        int offset = fdt_path_offset(fdt, qspi_clone_spi_flash_node_name);
 299        fdt_del_node(fdt, offset);
 300        g_free(qspi_clone_spi_flash_node_name);
 301    }
 302
 303    /*
 304     * FIXME: Probably better implemented as a plnx-specific pre-boot dtb
 305     * modifier
 306     */
 307    zynq_ps7_usb_nuke_phy(fdt);
 308
 309    if (machine->kernel_filename) {
 310        arm_load_kernel(ARM_CPU(first_cpu), &arm_generic_fdt_binfo);
 311    }
 312    return;
 313
 314no_dtb_arg:
 315    hw_error("DTB must be specified for %s machine model\n", MACHINE_NAME);
 316    return;
 317}
 318
 319static void arm_generic_fdt_init_plnx(MachineState *machine)
 320{
 321    MemoryRegion *address_space_mem = get_system_memory();
 322    DeviceState *dev;
 323    SysBusDevice *busdev;
 324
 325    /*FIXME: Describe OCM in DTB and delete this */
 326    /* ZYNQ OCM: */
 327    {
 328        MemoryRegion *ocm_ram = g_new(MemoryRegion, 1);
 329        memory_region_init_ram(ocm_ram, NULL, "zynq.ocm_ram", 256 << 10,
 330                               &error_abort);
 331        vmstate_register_ram_global(ocm_ram);
 332        memory_region_add_subregion(address_space_mem, 0xFFFC0000, ocm_ram);
 333    }
 334
 335    /* FIXME: Descibe NAND in DTB and delete this */
 336    /* NAND: */
 337    dev = qdev_create(NULL, "arm.pl35x");
 338    /* FIXME: handle this somewhere central */
 339    object_property_add_child(container_get(qdev_get_machine(), "/unattached"),
 340                              "pl353", OBJECT(dev), NULL);
 341    qdev_prop_set_uint8(dev, "x", 3);
 342    {
 343        DriveInfo *dinfo = drive_get_next(IF_PFLASH);
 344        DeviceState *att_dev = nand_init(dinfo ? blk_by_legacy_dinfo(dinfo)
 345                                               : NULL,
 346                                         NAND_MFR_STMICRO, 0xaa);
 347
 348        object_property_set_link(OBJECT(dev), OBJECT(att_dev), "dev1",
 349                                 &error_abort);
 350    }
 351    qdev_init_nofail(dev);
 352    busdev = SYS_BUS_DEVICE(dev);
 353    sysbus_mmio_map(busdev, 0, 0xe000e000);
 354    sysbus_mmio_map(busdev, 2, 0xe1000000);
 355
 356    /* Mark the simple-bus as incompatible as it breaks the Zynq boot */
 357    add_to_compat_table(NULL, "compatible:simple-bus", NULL);
 358
 359    {
 360        DeviceState *dev = qdev_create(NULL, "mdio");
 361        qdev_init_nofail(dev);
 362        /* Add MDIO Connect Call back */
 363        add_to_inst_bind_table(zynq_ps7_mdio_phy_connect, "mdio", dev);
 364    }
 365
 366    arm_generic_fdt_init(machine);
 367
 368    /* FIXME: Descibe SCU in DTB and delete this */
 369    /* ZYNQ SCU: */
 370    {
 371        DeviceState *dev = qdev_create(NULL, "a9-scu");
 372        SysBusDevice *busdev = SYS_BUS_DEVICE(dev);
 373
 374        qdev_prop_set_uint32(dev, "num-cpu", fdt_generic_num_cpus);
 375        qdev_init_nofail(dev);
 376        sysbus_mmio_map(busdev, 0, ZYNQ7000_MPCORE_PERIPHBASE);
 377    }
 378
 379}
 380
 381int endian = 0;
 382
 383static void arm_generic_fdt_machine_init(MachineClass *mc)
 384{
 385    mc->desc = "ARM device tree driven machine model";
 386    mc->init = arm_generic_fdt_init;
 387    mc->max_cpus = MAX_CPUS;
 388}
 389
 390static void arm_generic_fdt_plnx_machine_init(MachineClass *mc)
 391{
 392    mc->desc = "ARM device tree driven machine model for PetaLinux Zynq";
 393    mc->init = arm_generic_fdt_init_plnx;
 394    mc->max_cpus = MAX_CPUS;
 395}
 396
 397fdt_register_compatibility_opaque(pflash_cfi01_fdt_init,
 398                                  "compatibile:cfi-flash", 0, &endian);
 399
 400DEFINE_MACHINE(MACHINE_NAME, arm_generic_fdt_machine_init)
 401DEFINE_MACHINE(MACHINE_NAME "-plnx", arm_generic_fdt_plnx_machine_init)
 402