| 1 | /* |
| 2 | * Model of Xilinx Virtex5 ML507 PPC-440 refdesign. |
| 3 | * |
| 4 | * Copyright (c) 2010 Edgar E. Iglesias. |
| 5 | * |
| 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | * of this software and associated documentation files (the "Software"), to deal |
| 8 | * in the Software without restriction, including without limitation the rights |
| 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | * copies of the Software, and to permit persons to whom the Software is |
| 11 | * furnished to do so, subject to the following conditions: |
| 12 | * |
| 13 | * The above copyright notice and this permission notice shall be included in |
| 14 | * all copies or substantial portions of the Software. |
| 15 | * |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| 19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 22 | * THE SOFTWARE. |
| 23 | */ |
| 24 | |
| 25 | #include "qemu/osdep.h" |
| 26 | #include "qemu/datadir.h" |
| 27 | #include "qemu/units.h" |
| 28 | #include "exec/page-protection.h" |
| 29 | #include "target/ppc/cpu.h" |
| 30 | #include "hw/core/sysbus.h" |
| 31 | #include "hw/char/serial-mm.h" |
| 32 | #include "hw/block/flash.h" |
| 33 | #include "system/physmem.h" |
| 34 | #include "system/system.h" |
| 35 | #include "system/reset.h" |
| 36 | #include "hw/core/boards.h" |
| 37 | #include "system/device_tree.h" |
| 38 | #include "hw/core/loader.h" |
| 39 | #include "elf.h" |
| 40 | #include "qapi/error.h" |
| 41 | #include "qemu/error-report.h" |
| 42 | #include "qemu/option.h" |
| 43 | |
| 44 | #include "hw/intc/ppc-uic.h" |
| 45 | #include "hw/ppc/ppc.h" |
| 46 | #include "hw/ppc/ppc4xx.h" |
| 47 | #include "hw/core/qdev-properties.h" |
| 48 | #include "exec/cpu-common.h" |
| 49 | |
| 50 | #include <libfdt.h> |
| 51 | |
| 52 | #define EPAPR_MAGIC (0x45504150) |
| 53 | #define FLASH_SIZE (16 * MiB) |
| 54 | |
| 55 | #define INTC_BASEADDR 0x81800000 |
| 56 | #define UART16550_BASEADDR 0x83e01003 |
| 57 | #define TIMER_BASEADDR 0x83c00000 |
| 58 | #define PFLASH_BASEADDR 0xfc000000 |
| 59 | |
| 60 | #define TIMER_IRQ 3 |
| 61 | #define UART16550_IRQ 9 |
| 62 | |
| 63 | static struct boot_info |
| 64 | { |
| 65 | uint32_t bootstrap_pc; |
| 66 | uint32_t cmdline; |
| 67 | uint32_t fdt; |
| 68 | uint32_t ima_size; |
| 69 | void *vfdt; |
| 70 | } boot_info; |
| 71 | |
| 72 | static PowerPCCPU *ppc440_init_xilinx(const char *cpu_type, uint32_t sysclk) |
| 73 | { |
| 74 | PowerPCCPU *cpu; |
| 75 | CPUPPCState *env; |
| 76 | DeviceState *uicdev; |
| 77 | SysBusDevice *uicsbd; |
| 78 | |
| 79 | cpu = POWERPC_CPU(cpu_create(cpu_type)); |
| 80 | env = &cpu->env; |
| 81 | |
| 82 | ppc_booke_timers_init(cpu, sysclk, 0/* no flags */); |
| 83 | |
| 84 | ppc_dcr_init(env, NULL, NULL); |
| 85 | |
| 86 | /* interrupt controller */ |
| 87 | uicdev = qdev_new(TYPE_PPC_UIC); |
| 88 | ppc4xx_dcr_realize(PPC4xx_DCR_DEVICE(uicdev), cpu, &error_fatal); |
| 89 | object_unref(OBJECT(uicdev)); |
| 90 | uicsbd = SYS_BUS_DEVICE(uicdev); |
| 91 | sysbus_connect_irq(uicsbd, PPCUIC_OUTPUT_INT, |
| 92 | qdev_get_gpio_in(DEVICE(cpu), PPC40x_INPUT_INT)); |
| 93 | sysbus_connect_irq(uicsbd, PPCUIC_OUTPUT_CINT, |
| 94 | qdev_get_gpio_in(DEVICE(cpu), PPC40x_INPUT_CINT)); |
| 95 | |
| 96 | /* This board doesn't wire anything up to the inputs of the UIC. */ |
| 97 | return cpu; |
| 98 | } |
| 99 | |
| 100 | static void main_cpu_reset(void *opaque) |
| 101 | { |
| 102 | PowerPCCPU *cpu = opaque; |
| 103 | CPUPPCState *env = &cpu->env; |
| 104 | struct boot_info *bi = env->load_info; |
| 105 | |
| 106 | cpu_reset(CPU(cpu)); |
| 107 | /* Linux Kernel Parameters (passing device tree): |
| 108 | * r3: pointer to the fdt |
| 109 | * r4: 0 |
| 110 | * r5: 0 |
| 111 | * r6: epapr magic |
| 112 | * r7: size of IMA in bytes |
| 113 | * r8: 0 |
| 114 | * r9: 0 |
| 115 | */ |
| 116 | env->gpr[1] = (16 * MiB) - 8; |
| 117 | /* Provide a device-tree. */ |
| 118 | env->gpr[3] = bi->fdt; |
| 119 | env->nip = bi->bootstrap_pc; |
| 120 | |
| 121 | /* Create a mapping spanning the 32bit addr space. */ |
| 122 | booke_set_tlb(&env->tlb.tlbe[0], 0, 0, 1U << 31); |
| 123 | booke_set_tlb(&env->tlb.tlbe[1], 0x80000000, 0x80000000, 1U << 31); |
| 124 | env->gpr[6] = EPAPR_MAGIC; |
| 125 | env->gpr[7] = bi->ima_size; |
| 126 | } |
| 127 | |
| 128 | #define BINARY_DEVICE_TREE_FILE "virtex-ml507.dtb" |
| 129 | static int xilinx_load_device_tree(MachineState *machine, |
| 130 | hwaddr addr, |
| 131 | hwaddr initrd_base, |
| 132 | hwaddr initrd_size) |
| 133 | { |
| 134 | char *path; |
| 135 | int fdt_size; |
| 136 | void *fdt = NULL; |
| 137 | int r; |
| 138 | const char *dtb_filename; |
| 139 | |
| 140 | dtb_filename = machine->dtb; |
| 141 | if (dtb_filename) { |
| 142 | fdt = load_device_tree(dtb_filename, &fdt_size); |
| 143 | if (!fdt) { |
| 144 | error_report("Error while loading device tree file '%s'", |
| 145 | dtb_filename); |
| 146 | } |
| 147 | } else { |
| 148 | /* Try the local "ppc.dtb" override. */ |
| 149 | fdt = load_device_tree("ppc.dtb", &fdt_size); |
| 150 | if (!fdt) { |
| 151 | path = qemu_find_file(QEMU_FILE_TYPE_DTB, BINARY_DEVICE_TREE_FILE); |
| 152 | if (path) { |
| 153 | fdt = load_device_tree(path, &fdt_size); |
| 154 | g_free(path); |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | if (!fdt) { |
| 159 | return 0; |
| 160 | } |
| 161 | |
| 162 | r = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start", |
| 163 | initrd_base); |
| 164 | if (r < 0) { |
| 165 | error_report("couldn't set /chosen/linux,initrd-start"); |
| 166 | } |
| 167 | |
| 168 | r = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end", |
| 169 | (initrd_base + initrd_size)); |
| 170 | if (r < 0) { |
| 171 | error_report("couldn't set /chosen/linux,initrd-end"); |
| 172 | } |
| 173 | |
| 174 | r = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", |
| 175 | machine->kernel_cmdline); |
| 176 | if (r < 0) |
| 177 | fprintf(stderr, "couldn't set /chosen/bootargs\n"); |
| 178 | physical_memory_write(addr, fdt, fdt_size); |
| 179 | |
| 180 | /* Set machine->fdt for 'dumpdtb' QMP/HMP command */ |
| 181 | machine->fdt = fdt; |
| 182 | |
| 183 | return fdt_size; |
| 184 | } |
| 185 | |
| 186 | static void virtex_init(MachineState *machine) |
| 187 | { |
| 188 | const char *kernel_filename = machine->kernel_filename; |
| 189 | hwaddr initrd_base = 0; |
| 190 | int initrd_size = 0; |
| 191 | MemoryRegion *address_space_mem = get_system_memory(); |
| 192 | DeviceState *dev; |
| 193 | PowerPCCPU *cpu; |
| 194 | CPUPPCState *env; |
| 195 | hwaddr ram_base = 0; |
| 196 | DriveInfo *dinfo; |
| 197 | qemu_irq irq[32], cpu_irq; |
| 198 | int kernel_size; |
| 199 | int i; |
| 200 | |
| 201 | /* init CPUs */ |
| 202 | cpu = ppc440_init_xilinx(machine->cpu_type, 400000000); |
| 203 | env = &cpu->env; |
| 204 | |
| 205 | if (env->mmu_model != POWERPC_MMU_BOOKE) { |
| 206 | error_report("MMU model %i not supported by this machine", |
| 207 | env->mmu_model); |
| 208 | exit(1); |
| 209 | } |
| 210 | |
| 211 | qemu_register_reset(main_cpu_reset, cpu); |
| 212 | |
| 213 | memory_region_add_subregion(address_space_mem, ram_base, machine->ram); |
| 214 | |
| 215 | dinfo = drive_get(IF_PFLASH, 0, 0); |
| 216 | pflash_cfi01_register(PFLASH_BASEADDR, "virtex.flash", FLASH_SIZE, |
| 217 | dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, |
| 218 | 64 * KiB, 1, 0x89, 0x18, 0x0000, 0x0, 1); |
| 219 | |
| 220 | cpu_irq = qdev_get_gpio_in(DEVICE(cpu), PPC40x_INPUT_INT); |
| 221 | dev = qdev_new("xlnx.xps-intc"); |
| 222 | qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_BIG); |
| 223 | qdev_prop_set_uint32(dev, "kind-of-intr", 0); |
| 224 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); |
| 225 | sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, INTC_BASEADDR); |
| 226 | sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, cpu_irq); |
| 227 | for (i = 0; i < 32; i++) { |
| 228 | irq[i] = qdev_get_gpio_in(dev, i); |
| 229 | } |
| 230 | |
| 231 | serial_mm_init(address_space_mem, UART16550_BASEADDR, 2, irq[UART16550_IRQ], |
| 232 | 115200, serial_hd(0), DEVICE_LITTLE_ENDIAN); |
| 233 | |
| 234 | /* 2 timers at irq 2 @ 62 Mhz. */ |
| 235 | dev = qdev_new("xlnx.xps-timer"); |
| 236 | qdev_prop_set_enum(dev, "endianness", ENDIAN_MODE_BIG); |
| 237 | qdev_prop_set_uint32(dev, "one-timer-only", 0); |
| 238 | qdev_prop_set_uint32(dev, "clock-frequency", 62 * 1000000); |
| 239 | sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); |
| 240 | sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, TIMER_BASEADDR); |
| 241 | sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, irq[TIMER_IRQ]); |
| 242 | |
| 243 | if (kernel_filename) { |
| 244 | uint64_t entry, high; |
| 245 | hwaddr boot_offset; |
| 246 | |
| 247 | /* Boots a kernel elf binary. */ |
| 248 | kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, |
| 249 | &entry, NULL, &high, NULL, |
| 250 | ELFDATA2MSB, PPC_ELF_MACHINE, 0, 0); |
| 251 | boot_info.bootstrap_pc = entry & 0x00ffffff; |
| 252 | |
| 253 | if (kernel_size < 0) { |
| 254 | boot_offset = 0x1200000; |
| 255 | /* If we failed loading ELF's try a raw image. */ |
| 256 | kernel_size = load_image_targphys(kernel_filename, |
| 257 | boot_offset, |
| 258 | machine->ram_size, &error_fatal); |
| 259 | boot_info.bootstrap_pc = boot_offset; |
| 260 | high = boot_info.bootstrap_pc + kernel_size + 8192; |
| 261 | } |
| 262 | |
| 263 | boot_info.ima_size = kernel_size; |
| 264 | |
| 265 | /* Load initrd. */ |
| 266 | if (machine->initrd_filename) { |
| 267 | initrd_base = high = ROUND_UP(high, 4); |
| 268 | initrd_size = load_image_targphys(machine->initrd_filename, |
| 269 | high, machine->ram_size - high, |
| 270 | &error_fatal); |
| 271 | high = ROUND_UP(high + initrd_size, 4); |
| 272 | } |
| 273 | |
| 274 | /* Provide a device-tree. */ |
| 275 | boot_info.fdt = high + (8192 * 2); |
| 276 | boot_info.fdt &= ~8191; |
| 277 | |
| 278 | xilinx_load_device_tree(machine, boot_info.fdt, |
| 279 | initrd_base, initrd_size); |
| 280 | } |
| 281 | env->load_info = &boot_info; |
| 282 | } |
| 283 | |
| 284 | static void virtex_machine_init(MachineClass *mc) |
| 285 | { |
| 286 | mc->desc = "Xilinx Virtex ML507 reference design"; |
| 287 | mc->init = virtex_init; |
| 288 | mc->default_cpu_type = POWERPC_CPU_TYPE_NAME("440-xilinx"); |
| 289 | mc->default_ram_id = "ram"; |
| 290 | } |
| 291 | |
| 292 | DEFINE_MACHINE("virtex-ml507", virtex_machine_init) |