qemu/hw/microblaze/microblaze_generic_fdt.c
<<
>>
Prefs
   1/*
   2 * Model of Petalogix linux reference design for all boards
   3 *
   4 * Copyright (c) 2009 Edgar E. Iglesias.
   5 * Copyright (c) 2009 Michal Simek.
   6 * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.croshtwaite@petalogix.com)
   7 * Copyright (c) 2012 Petalogix Pty Ltd.
   8 *
   9 * Permission is hereby granted, free of charge, to any person obtaining a copy
  10 * of this software and associated documentation files (the "Software"), to deal
  11 * in the Software without restriction, including without limitation the rights
  12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13 * copies of the Software, and to permit persons to whom the Software is
  14 * furnished to do so, subject to the following conditions:
  15 *
  16 * The above copyright notice and this permission notice shall be included in
  17 * all copies or substantial portions of the Software.
  18 *
  19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  22 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25 * THE SOFTWARE.
  26 */
  27
  28#include "qemu/osdep.h"
  29#include "cpu.h"
  30#include "hw/sysbus.h"
  31#include "qemu/log.h"
  32#include "net/net.h"
  33#include "hw/block/flash.h"
  34#include "sysemu/sysemu.h"
  35#include "hw/boards.h"
  36#include "sysemu/device_tree.h"
  37#include "exec/memory.h"
  38#include "exec/address-spaces.h"
  39#include "qemu/error-report.h"
  40#include "qapi/error.h"
  41#include "qemu/config-file.h"
  42
  43#include "hw/fdt_generic_util.h"
  44#include "hw/fdt_generic_devices.h"
  45
  46#include "boot.h"
  47
  48#define VAL(name) qemu_fdt_getprop_cell(fdt_g, node_path, name, 0, false, NULL)
  49
  50#define IS_PETALINUX_MACHINE \
  51    (!strcmp(MACHINE_GET_CLASS(machine)->name, MACHINE_NAME "-plnx"))
  52
  53/* FIXME: delete */
  54static void *fdt_g;
  55
  56static void
  57microblaze_generic_fdt_reset(MicroBlazeCPU *cpu)
  58{
  59    CPUMBState *env = &cpu->env;
  60
  61    char node_path[DT_PATH_LENGTH];
  62    qemu_devtree_get_node_by_name(fdt_g, node_path, "cpu");
  63    int t;
  64    int use_exc = 0;
  65
  66    env->pvr.regs[0] = 0;
  67    env->pvr.regs[2] = PVR2_D_OPB_MASK \
  68                        | PVR2_D_LMB_MASK \
  69                        | PVR2_I_OPB_MASK \
  70                        | PVR2_I_LMB_MASK \
  71                        | 0;
  72
  73    /* Even if we don't have PVR's, we fill out everything
  74       because QEMU will internally follow what the pvr regs
  75       state about the HW.  */
  76
  77    if (VAL("xlnx,use-barrel")) {
  78        env->pvr.regs[0] |= PVR0_USE_BARREL_MASK;
  79        env->pvr.regs[2] |= PVR2_USE_BARREL_MASK;
  80    }
  81
  82    if (VAL("xlnx,use-div")) {
  83        env->pvr.regs[0] |= PVR0_USE_DIV_MASK;
  84        env->pvr.regs[2] |= PVR2_USE_DIV_MASK;
  85    }
  86
  87    t = VAL("xlnx,use-hw-mul");
  88    if (t) {
  89        env->pvr.regs[0] |= PVR0_USE_HW_MUL_MASK;
  90        env->pvr.regs[2] |= PVR2_USE_HW_MUL_MASK;
  91        if (t >= 2) {
  92            env->pvr.regs[2] |= PVR2_USE_MUL64_MASK;
  93        }
  94    }
  95
  96    if (VAL("xlnx,use-msr-instr")) {
  97        env->pvr.regs[2] |= PVR2_USE_MSR_INSTR;
  98    }
  99
 100    if (VAL("xlnx,use-pcmp-instr")) {
 101        env->pvr.regs[2] |= PVR2_USE_PCMP_INSTR;
 102    }
 103
 104    if (VAL("xlnx,opcode-0x0-illegal")) {
 105        env->pvr.regs[2] |= PVR2_OPCODE_0x0_ILL_MASK;
 106    }
 107
 108    if (VAL("xlnx,unaligned-exceptions")) {
 109        env->pvr.regs[2] |= PVR2_UNALIGNED_EXC_MASK;
 110        use_exc = 1;
 111    }
 112
 113    if (VAL("xlnx,ill-opcode-exception")) {
 114        env->pvr.regs[2] |= PVR2_ILL_OPCODE_EXC_MASK;
 115        use_exc = 1;
 116    }
 117
 118    if (VAL("xlnx,iopb-bus-exception")) {
 119        env->pvr.regs[2] |= PVR2_IOPB_BUS_EXC_MASK;
 120        use_exc = 1;
 121    }
 122
 123    if (VAL("xlnx,dopb-bus-exception")) {
 124        env->pvr.regs[2] |= PVR2_DOPB_BUS_EXC_MASK;
 125        use_exc = 1;
 126    }
 127
 128    if (VAL("xlnx,div-zero-exception")) {
 129        env->pvr.regs[2] |= PVR2_DIV_ZERO_EXC_MASK;
 130        use_exc = 1;
 131    }
 132
 133    env->pvr.regs[0] |= VAL("xlnx,pvr-user1") & 0xff;
 134    env->pvr.regs[1] = VAL("xlnx,pvr-user2");
 135
 136    /* MMU regs.  */
 137    t = VAL("xlnx,use-mmu");
 138    if (use_exc || t) {
 139        env->pvr.regs[0] |= PVR0_USE_EXC_MASK ;
 140    }
 141
 142    env->pvr.regs[11] = t << 30;
 143    t = VAL("xlnx,mmu-zones");
 144    env->pvr.regs[11] |= t << 17;
 145    env->mmu.c_mmu_zones = t;
 146
 147    t = VAL("xlnx,mmu-tlb-access");
 148    env->mmu.c_mmu_tlb_access = t;
 149    env->pvr.regs[11] |= t << 22;
 150
 151    {
 152        char *str;
 153        const struct {
 154            const char *name;
 155            unsigned int arch;
 156        } arch_lookup[] = {
 157            {"virtex2", 0x4},
 158            {"virtex2pro", 0x5},
 159            {"spartan3", 0x6},
 160            {"virtex4", 0x7},
 161            {"virtex5", 0x8},
 162            {"spartan3e", 0x9},
 163            {"spartan3a", 0xa},
 164            {"spartan3an", 0xb},
 165            {"spartan3adsp", 0xc},
 166            {"spartan6", 0xd},
 167            {"virtex6", 0xe},
 168            {"virtex7", 0xf},
 169            {"kintex7", 0x10},
 170            {"artix7", 0x11},
 171            {"zynq7000", 0x12},
 172            {"spartan2", 0xf0},
 173            {NULL, 0},
 174        };
 175        unsigned int i = 0;
 176
 177        str = qemu_fdt_getprop(fdt_g, node_path, "xlnx,family", NULL, false, NULL);
 178        while (arch_lookup[i].name && str) {
 179            if (strcmp(arch_lookup[i].name, str) == 0) {
 180                break;
 181            }
 182            i++;
 183        }
 184        if (!str || !arch_lookup[i].arch) {
 185            env->pvr.regs[10] = 0x0c000000; /* spartan 3a dsp family.  */
 186        } else {
 187            env->pvr.regs[10] = arch_lookup[i].arch << 24;
 188        }
 189        g_free(str);
 190    }
 191
 192    {
 193        env->pvr.regs[4] = PVR4_USE_ICACHE_MASK
 194                           | (21 << 26) /* Tag size.  */
 195                           | (4 << 21)
 196                           | (11 << 16);
 197        env->pvr.regs[6] = VAL("d-cache-baseaddr");
 198        env->pvr.regs[7] = VAL("d-cache-highaddr");
 199        env->pvr.regs[5] = PVR5_USE_DCACHE_MASK
 200                           | (21 << 26) /* Tag size.  */
 201                           | (4 << 21)
 202                           | (11 << 16);
 203
 204        if (VAL("xlnx,dcache-use-writeback")) {
 205            env->pvr.regs[5] |= PVR5_DCACHE_WRITEBACK_MASK;
 206        }
 207
 208        env->pvr.regs[8] = VAL("i-cache-baseaddr");
 209        env->pvr.regs[9] = VAL("i-cache-highaddr");
 210    }
 211    if (VAL("qemu,halt")) {
 212        cpu_interrupt(ENV_GET_CPU(env), CPU_INTERRUPT_HALT);
 213    }
 214}
 215
 216static void secondary_cpu_reset(void *opaque)
 217{
 218    MicroBlazeCPU *cpu = MICROBLAZE_CPU(opaque);
 219
 220    /* Configure secondary cores.  */
 221    microblaze_generic_fdt_reset(cpu);
 222}
 223
 224#define LMB_BRAM_SIZE  (128 * 1024)
 225
 226#define MACHINE_NAME "microblaze-fdt"
 227
 228#ifdef TARGET_WORDS_BIGENDIAN
 229int endian = 1;
 230#else
 231int endian;
 232#endif
 233
 234static void
 235microblaze_generic_fdt_init(MachineState *machine)
 236{
 237    CPUState *cpu;
 238    ram_addr_t ram_kernel_base = 0, ram_kernel_size = 0;
 239    void *fdt = NULL;
 240    const char *dtb_arg, *hw_dtb_arg;
 241    QemuOpts *machine_opts;
 242    int fdt_size;
 243
 244    /* for memory node */
 245    char node_path[DT_PATH_LENGTH];
 246    FDTMachineInfo *fdti;
 247    MemoryRegion *main_mem;
 248
 249    /* For DMA node */
 250    char dma_path[DT_PATH_LENGTH] = { 0 };
 251    uint32_t memory_phandle;
 252
 253    /* For Ethernet nodes */
 254    char **eth_paths;
 255    char *phy_path;
 256    char *mdio_path;
 257    uint32_t n_eth;
 258    uint32_t prop_val;
 259
 260    machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0);
 261    if (!machine_opts) {
 262        goto no_dtb_arg;
 263    }
 264    dtb_arg = qemu_opt_get(machine_opts, "dtb");
 265    hw_dtb_arg = qemu_opt_get(machine_opts, "hw-dtb");
 266    if (!dtb_arg && !hw_dtb_arg) {
 267        goto no_dtb_arg;
 268    }
 269
 270    /* If the user only provided a -dtb, use it as the hw description.  */
 271    if (!hw_dtb_arg) {
 272        hw_dtb_arg = dtb_arg;
 273    }
 274
 275    fdt = load_device_tree(hw_dtb_arg, &fdt_size);
 276    if (!fdt) {
 277        hw_error("Error: Unable to load Device Tree %s\n", hw_dtb_arg);
 278        return;
 279    }
 280
 281    if (IS_PETALINUX_MACHINE) {
 282        /* Mark the simple-bus as incompatible as it breaks the Microblaze
 283         * PetaLinux boot
 284         */
 285        add_to_compat_table(NULL, "compatible:simple-bus", NULL);
 286    }
 287
 288    /* find memory node or add new one if needed */
 289    while (qemu_devtree_get_node_by_name(fdt, node_path, "memory")) {
 290        qemu_fdt_add_subnode(fdt, "/memory@0");
 291        qemu_fdt_setprop_cells(fdt, "/memory@0", "reg", 0, machine->ram_size);
 292    }
 293
 294    if (!qemu_fdt_getprop(fdt, "/memory", "compatible", NULL, 0, NULL)) {
 295        qemu_fdt_setprop_string(fdt, "/memory", "compatible",
 296                                "qemu:memory-region");
 297        qemu_fdt_setprop_cells(fdt, "/memory", "qemu,ram", 1);
 298    }
 299
 300    if (IS_PETALINUX_MACHINE) {
 301        /* If using a *-plnx machine, the AXI DMA memory links are not included
 302         * in the DTB by default. To avoid seg faults, add the links in here if
 303         * they have not already been added by the user
 304         */
 305        qemu_devtree_get_node_by_name(fdt, dma_path, "dma");
 306
 307        if (strcmp(dma_path, "") != 0) {
 308            memory_phandle = qemu_fdt_check_phandle(fdt, node_path);
 309
 310            if (!memory_phandle) {
 311                memory_phandle = qemu_fdt_alloc_phandle(fdt);
 312
 313                qemu_fdt_setprop_cells(fdt, "/memory", "linux,phandle",
 314                                       memory_phandle);
 315                qemu_fdt_setprop_cells(fdt, "/memory", "phandle",
 316                                       memory_phandle);
 317            }
 318
 319            if (!qemu_fdt_getprop(fdt, dma_path, "sg", NULL, 0, NULL)) {
 320                qemu_fdt_setprop_phandle(fdt, dma_path, "sg", node_path);
 321            }
 322
 323            if (!qemu_fdt_getprop(fdt, dma_path, "s2mm", NULL, 0, NULL)) {
 324                qemu_fdt_setprop_phandle(fdt, dma_path, "s2mm", node_path);
 325            }
 326
 327            if (!qemu_fdt_getprop(fdt, dma_path, "mm2s", NULL, 0, NULL)) {
 328                qemu_fdt_setprop_phandle(fdt, dma_path, "mm2s", node_path);
 329            }
 330        }
 331
 332        /* Copy phyaddr value from phy node reg property */
 333        n_eth = qemu_devtree_get_n_nodes_by_name(fdt, &eth_paths, "ethernet");
 334
 335        while (n_eth--) {
 336            mdio_path = qemu_devtree_get_child_by_name(fdt, eth_paths[n_eth],
 337                                                       "mdio");
 338            if (mdio_path) {
 339                phy_path = qemu_devtree_get_child_by_name(fdt, mdio_path,
 340                                                          "phy");
 341                if (phy_path) {
 342                    prop_val = qemu_fdt_getprop_cell(fdt, phy_path, "reg", 0,
 343                                                     NULL, &error_abort);
 344                    qemu_fdt_setprop_cell(fdt, eth_paths[n_eth], "xlnx,phyaddr",
 345                                          prop_val);
 346                    g_free(phy_path);
 347                } else {
 348                    qemu_log_mask(LOG_GUEST_ERROR, "phy not found in %s",
 349                                  mdio_path);
 350                }
 351                g_free(mdio_path);
 352            }
 353            g_free(eth_paths[n_eth]);
 354        }
 355        g_free(eth_paths);
 356    }
 357
 358    /* Instantiate peripherals from the FDT.  */
 359    fdti = fdt_generic_create_machine(fdt, NULL);
 360    main_mem = MEMORY_REGION(object_resolve_path(node_path, NULL));
 361
 362    ram_kernel_base = object_property_get_int(OBJECT(main_mem), "addr", NULL);
 363    ram_kernel_size = object_property_get_int(OBJECT(main_mem), "size", NULL);
 364
 365    if (!memory_region_is_mapped(main_mem)) {
 366        /* If the memory region is not mapped, map it here.
 367         * It has to be mapped somewhere, so guess that the base address
 368         * is where the kernel starts
 369         */
 370        memory_region_add_subregion(get_system_memory(), ram_kernel_base,
 371                                    main_mem);
 372
 373        if (ram_kernel_base && IS_PETALINUX_MACHINE) {
 374            /* If the memory added is at an offset from zero QEMU will error
 375             * when an ISR/exception is triggered. Add a small amount of hack
 376             * RAM to handle this.
 377             */
 378            MemoryRegion *hack_ram = g_new(MemoryRegion, 1);
 379            memory_region_init_ram(hack_ram, NULL, "hack_ram", 0x1000,
 380                                   &error_abort);
 381            vmstate_register_ram_global(hack_ram);
 382            memory_region_add_subregion(get_system_memory(), 0, hack_ram);
 383        }
 384    }
 385
 386    fdt_init_destroy_fdti(fdti);
 387
 388    fdt_g = fdt;
 389    microblaze_load_kernel(MICROBLAZE_CPU(first_cpu), ram_kernel_base,
 390                           ram_kernel_size, machine->initrd_filename, NULL,
 391                           microblaze_generic_fdt_reset, fdt, fdt_size);
 392
 393    /* Register FDT to prop mapper for secondary cores.  */
 394    cpu = CPU_NEXT(first_cpu);
 395    while (cpu) {
 396        qemu_register_reset(secondary_cpu_reset, cpu);
 397        cpu = CPU_NEXT(cpu);
 398    }
 399
 400    return;
 401no_dtb_arg:
 402    hw_error("DTB must be specified for %s machine model\n", MACHINE_NAME);
 403    return;
 404}
 405
 406static void microblaze_generic_fdt_machine_init(MachineClass *mc)
 407{
 408    mc->desc = "Microblaze device tree driven machine model";
 409    mc->init = microblaze_generic_fdt_init;
 410}
 411
 412static void microblaze_generic_fdt_plnx_machine_init(MachineClass *mc)
 413{
 414    mc->desc = "Microblaze device tree driven machine model for PetaLinux";
 415    mc->init = microblaze_generic_fdt_init;
 416}
 417
 418fdt_register_compatibility_opaque(pflash_cfi01_fdt_init, "compatible:cfi-flash",
 419                                  0, &endian);
 420
 421DEFINE_MACHINE(MACHINE_NAME, microblaze_generic_fdt_machine_init)
 422DEFINE_MACHINE(MACHINE_NAME "-plnx", microblaze_generic_fdt_plnx_machine_init)
 423