/* * QEMU PowerPC 405 evaluation boards emulation * * Copyright (c) 2007 Jocelyn Mayer * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qemu/osdep.h" #include "qemu/units.h" #include "qapi/error.h" #include "qemu/datadir.h" #include "cpu.h" #include "hw/ppc/ppc.h" #include "hw/qdev-properties.h" #include "hw/sysbus.h" #include "ppc405.h" #include "hw/rtc/m48t59.h" #include "hw/block/flash.h" #include "sysemu/qtest.h" #include "sysemu/reset.h" #include "sysemu/block-backend.h" #include "hw/boards.h" #include "qemu/error-report.h" #include "hw/loader.h" #include "qemu/cutils.h" #include "elf.h" #define BIOS_FILENAME "ppc405_rom.bin" #define BIOS_SIZE (2 * MiB) #define KERNEL_LOAD_ADDR 0x01000000 #define INITRD_LOAD_ADDR 0x01800000 #define PPC405EP_SDRAM_BASE 0x00000000 #define PPC405EP_SRAM_BASE 0xFFF00000 #define PPC405EP_SRAM_SIZE (512 * KiB) #define USE_FLASH_BIOS #define TYPE_PPC405_MACHINE MACHINE_TYPE_NAME("ppc405") OBJECT_DECLARE_SIMPLE_TYPE(Ppc405MachineState, PPC405_MACHINE); struct Ppc405MachineState { /* Private */ MachineState parent_obj; /* Public */ Ppc405SoCState soc; }; /* CPU reset handler when booting directly from a loaded kernel */ static struct boot_info { uint32_t entry; uint32_t bdloc; uint32_t initrd_base; uint32_t initrd_size; uint32_t cmdline_base; uint32_t cmdline_size; } boot_info; static void main_cpu_reset(void *opaque) { PowerPCCPU *cpu = opaque; CPUPPCState *env = &cpu->env; struct boot_info *bi = env->load_info; cpu_reset(CPU(cpu)); /* stack: top of sram */ env->gpr[1] = PPC405EP_SRAM_BASE + PPC405EP_SRAM_SIZE - 8; /* Tune our boot state */ env->gpr[3] = bi->bdloc; env->gpr[4] = bi->initrd_base; env->gpr[5] = bi->initrd_base + bi->initrd_size; env->gpr[6] = bi->cmdline_base; env->gpr[7] = bi->cmdline_size; env->nip = bi->entry; } /* Bootinfo as set-up by u-boot */ typedef struct { uint32_t bi_memstart; uint32_t bi_memsize; uint32_t bi_flashstart; uint32_t bi_flashsize; uint32_t bi_flashoffset; /* 0x10 */ uint32_t bi_sramstart; uint32_t bi_sramsize; uint32_t bi_bootflags; uint32_t bi_ipaddr; /* 0x20 */ uint8_t bi_enetaddr[6]; uint16_t bi_ethspeed; uint32_t bi_intfreq; uint32_t bi_busfreq; /* 0x30 */ uint32_t bi_baudrate; uint8_t bi_s_version[4]; uint8_t bi_r_version[32]; uint32_t bi_procfreq; uint32_t bi_plb_busfreq; uint32_t bi_pci_busfreq; uint8_t bi_pci_enetaddr[6]; uint8_t bi_pci_enetaddr2[6]; /* PPC405EP specific */ uint32_t bi_opbfreq; uint32_t bi_iic_fast[2]; } ppc4xx_bd_info_t; static void ppc405_set_default_bootinfo(ppc4xx_bd_info_t *bd, ram_addr_t ram_size) { memset(bd, 0, sizeof(*bd)); bd->bi_memstart = PPC405EP_SDRAM_BASE; bd->bi_memsize = ram_size; bd->bi_sramstart = PPC405EP_SRAM_BASE; bd->bi_sramsize = PPC405EP_SRAM_SIZE; bd->bi_bootflags = 0; bd->bi_intfreq = 133333333; bd->bi_busfreq = 33333333; bd->bi_baudrate = 115200; bd->bi_s_version[0] = 'Q'; bd->bi_s_version[1] = 'M'; bd->bi_s_version[2] = 'U'; bd->bi_s_version[3] = '\0'; bd->bi_r_version[0] = 'Q'; bd->bi_r_version[1] = 'E'; bd->bi_r_version[2] = 'M'; bd->bi_r_version[3] = 'U'; bd->bi_r_version[4] = '\0'; bd->bi_procfreq = 133333333; bd->bi_plb_busfreq = 33333333; bd->bi_pci_busfreq = 33333333; bd->bi_opbfreq = 33333333; } static ram_addr_t __ppc405_set_bootinfo(CPUPPCState *env, ppc4xx_bd_info_t *bd) { CPUState *cs = env_cpu(env); ram_addr_t bdloc; int i, n; /* We put the bd structure at the top of memory */ if (bd->bi_memsize >= 0x01000000UL) { bdloc = 0x01000000UL - sizeof(ppc4xx_bd_info_t); } else { bdloc = bd->bi_memsize - sizeof(ppc4xx_bd_info_t); } stl_be_phys(cs->as, bdloc + 0x00, bd->bi_memstart); stl_be_phys(cs->as, bdloc + 0x04, bd->bi_memsize); stl_be_phys(cs->as, bdloc + 0x08, bd->bi_flashstart); stl_be_phys(cs->as, bdloc + 0x0C, bd->bi_flashsize); stl_be_phys(cs->as, bdloc + 0x10, bd->bi_flashoffset); stl_be_phys(cs->as, bdloc + 0x14, bd->bi_sramstart); stl_be_phys(cs->as, bdloc + 0x18, bd->bi_sramsize); stl_be_phys(cs->as, bdloc + 0x1C, bd->bi_bootflags); stl_be_phys(cs->as, bdloc + 0x20, bd->bi_ipaddr); for (i = 0; i < 6; i++) { stb_phys(cs->as, bdloc + 0x24 + i, bd->bi_enetaddr[i]); } stw_be_phys(cs->as, bdloc + 0x2A, bd->bi_ethspeed); stl_be_phys(cs->as, bdloc + 0x2C, bd->bi_intfreq); stl_be_phys(cs->as, bdloc + 0x30, bd->bi_busfreq); stl_be_phys(cs->as, bdloc + 0x34, bd->bi_baudrate); for (i = 0; i < 4; i++) { stb_phys(cs->as, bdloc + 0x38 + i, bd->bi_s_version[i]); } for (i = 0; i < 32; i++) { stb_phys(cs->as, bdloc + 0x3C + i, bd->bi_r_version[i]); } stl_be_phys(cs->as, bdloc + 0x5C, bd->bi_procfreq); stl_be_phys(cs->as, bdloc + 0x60, bd->bi_plb_busfreq); stl_be_phys(cs->as, bdloc + 0x64, bd->bi_pci_busfreq); for (i = 0; i < 6; i++) { stb_phys(cs->as, bdloc + 0x68 + i, bd->bi_pci_enetaddr[i]); } n = 0x70; /* includes 2 bytes hole */ for (i = 0; i < 6; i++) { stb_phys(cs->as, bdloc + n++, bd->bi_pci_enetaddr2[i]); } stl_be_phys(cs->as, bdloc + n, bd->bi_opbfreq); n += 4; for (i = 0; i < 2; i++) { stl_be_phys(cs->as, bdloc + n, bd->bi_iic_fast[i]); n += 4; } return bdloc; } static ram_addr_t ppc405_set_bootinfo(CPUPPCState *env, ram_addr_t ram_size) { ppc4xx_bd_info_t bd; memset(&bd, 0, sizeof(bd)); ppc405_set_default_bootinfo(&bd, ram_size); return __ppc405_set_bootinfo(env, &bd); } static void boot_from_kernel(MachineState *machine, PowerPCCPU *cpu) { CPUPPCState *env = &cpu->env; hwaddr boot_entry; hwaddr kernel_base; int kernel_size; hwaddr initrd_base; int initrd_size; ram_addr_t bdloc; int len; bdloc = ppc405_set_bootinfo(env, machine->ram_size); boot_info.bdloc = bdloc; kernel_size = load_elf(machine->kernel_filename, NULL, NULL, NULL, &boot_entry, &kernel_base, NULL, NULL, 1, PPC_ELF_MACHINE, 0, 0); if (kernel_size < 0) { error_report("Could not load kernel '%s' : %s", machine->kernel_filename, load_elf_strerror(kernel_size)); exit(1); } boot_info.entry = boot_entry; /* load initrd */ if (machine->initrd_filename) { initrd_base = INITRD_LOAD_ADDR; initrd_size = load_image_targphys(machine->initrd_filename, initrd_base, machine->ram_size - initrd_base); if (initrd_size < 0) { error_report("could not load initial ram disk '%s'", machine->initrd_filename); exit(1); } boot_info.initrd_base = initrd_base; boot_info.initrd_size = initrd_size; } if (machine->kernel_cmdline) { len = strlen(machine->kernel_cmdline); bdloc -= ((len + 255) & ~255); cpu_physical_memory_write(bdloc, machine->kernel_cmdline, len + 1); boot_info.cmdline_base = bdloc; boot_info.cmdline_size = bdloc + len; } /* Install our custom reset handler to start from Linux */ qemu_register_reset(main_cpu_reset, cpu); env->load_info = &boot_info; } static void ppc405_init(MachineState *machine) { Ppc405MachineState *ppc405 = PPC405_MACHINE(machine); const char *kernel_filename = machine->kernel_filename; MemoryRegion *sysmem = get_system_memory(); object_initialize_child(OBJECT(machine), "soc", &ppc405->soc, TYPE_PPC405_SOC); object_property_set_link(OBJECT(&ppc405->soc), "dram", OBJECT(machine->ram), &error_abort); object_property_set_uint(OBJECT(&ppc405->soc), "sys-clk", 33333333, &error_abort); qdev_realize(DEVICE(&ppc405->soc), NULL, &error_fatal); /* allocate and load BIOS */ if (machine->firmware) { MemoryRegion *bios = g_new(MemoryRegion, 1); g_autofree char *filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, machine->firmware); long bios_size; memory_region_init_rom(bios, NULL, "ef405ep.bios", BIOS_SIZE, &error_fatal); if (!filename) { error_report("Could not find firmware '%s'", machine->firmware); exit(1); } bios_size = load_image_size(filename, memory_region_get_ram_ptr(bios), BIOS_SIZE); if (bios_size < 0) { error_report("Could not load PowerPC BIOS '%s'", machine->firmware); exit(1); } bios_size = (bios_size + 0xfff) & ~0xfff; memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios); } /* Load kernel and initrd using U-Boot images */ if (kernel_filename && machine->firmware) { target_ulong kernel_base, initrd_base; long kernel_size, initrd_size; kernel_base = KERNEL_LOAD_ADDR; kernel_size = load_image_targphys(kernel_filename, kernel_base, machine->ram_size - kernel_base); if (kernel_size < 0) { error_report("could not load kernel '%s'", kernel_filename); exit(1); } /* load initrd */ if (machine->initrd_filename) { initrd_base = INITRD_LOAD_ADDR; initrd_size = load_image_targphys(machine->initrd_filename, initrd_base, machine->ram_size - initrd_base); if (initrd_size < 0) { error_report("could not load initial ram disk '%s'", machine->initrd_filename); exit(1); } } /* Load ELF kernel and rootfs.cpio */ } else if (kernel_filename && !machine->firmware) { ppc4xx_sdram_ddr_enable(&ppc405->soc.sdram); boot_from_kernel(machine, &ppc405->soc.cpu); } } static void ppc405_machine_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "PPC405 generic machine"; mc->init = ppc405_init; mc->default_ram_size = 128 * MiB; mc->default_ram_id = "ppc405.ram"; mc->deprecation_reason = "machine is old and unmaintained"; } static const TypeInfo ppc405_machine_type = { .name = TYPE_PPC405_MACHINE, .parent = TYPE_MACHINE, .instance_size = sizeof(Ppc405MachineState), .class_init = ppc405_machine_class_init, .abstract = true, }; /*****************************************************************************/ /* PPC405EP reference board (IBM) */ /* * Standalone board with: * - PowerPC 405EP CPU * - SDRAM (0x00000000) * - Flash (0xFFF80000) * - SRAM (0xFFF00000) * - NVRAM (0xF0000000) * - FPGA (0xF0300000) */ #define PPC405EP_NVRAM_BASE 0xF0000000 #define PPC405EP_FPGA_BASE 0xF0300000 #define PPC405EP_FLASH_BASE 0xFFF80000 #define TYPE_REF405EP_FPGA "ref405ep-fpga" OBJECT_DECLARE_SIMPLE_TYPE(Ref405epFpgaState, REF405EP_FPGA); struct Ref405epFpgaState { SysBusDevice parent_obj; MemoryRegion iomem; uint8_t reg0; uint8_t reg1; }; static uint64_t ref405ep_fpga_readb(void *opaque, hwaddr addr, unsigned size) { Ref405epFpgaState *fpga = opaque; uint32_t ret; switch (addr) { case 0x0: ret = fpga->reg0; break; case 0x1: ret = fpga->reg1; break; default: ret = 0; break; } return ret; } static void ref405ep_fpga_writeb(void *opaque, hwaddr addr, uint64_t value, unsigned size) { Ref405epFpgaState *fpga = opaque; switch (addr) { case 0x0: /* Read only */ break; case 0x1: fpga->reg1 = value; break; default: break; } } static const MemoryRegionOps ref405ep_fpga_ops = { .read = ref405ep_fpga_readb, .write = ref405ep_fpga_writeb, .impl.min_access_size = 1, .impl.max_access_size = 1, .valid.min_access_size = 1, .valid.max_access_size = 4, .endianness = DEVICE_BIG_ENDIAN, }; static void ref405ep_fpga_reset(DeviceState *dev) { Ref405epFpgaState *fpga = REF405EP_FPGA(dev); fpga->reg0 = 0x00; fpga->reg1 = 0x0F; } static void ref405ep_fpga_realize(DeviceState *dev, Error **errp) { Ref405epFpgaState *s = REF405EP_FPGA(dev); memory_region_init_io(&s->iomem, OBJECT(s), &ref405ep_fpga_ops, s, "fpga", 0x00000100); sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); } static void ref405ep_fpga_class_init(ObjectClass *oc, void *data) { DeviceClass *dc = DEVICE_CLASS(oc); dc->realize = ref405ep_fpga_realize; device_class_set_legacy_reset(dc, ref405ep_fpga_reset); /* Reason: only works as part of a ppc405 board */ dc->user_creatable = false; } static const TypeInfo ref405ep_fpga_type = { .name = TYPE_REF405EP_FPGA, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Ref405epFpgaState), .class_init = ref405ep_fpga_class_init, }; static void ref405ep_init(MachineState *machine) { DeviceState *dev; SysBusDevice *s; MemoryRegion *sram = g_new(MemoryRegion, 1); ppc405_init(machine); /* allocate SRAM */ memory_region_init_ram(sram, NULL, "ref405ep.sram", PPC405EP_SRAM_SIZE, &error_fatal); memory_region_add_subregion(get_system_memory(), PPC405EP_SRAM_BASE, sram); /* Register FPGA */ dev = qdev_new(TYPE_REF405EP_FPGA); object_property_add_child(OBJECT(machine), "fpga", OBJECT(dev)); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, PPC405EP_FPGA_BASE); /* Register NVRAM */ dev = qdev_new("sysbus-m48t08"); qdev_prop_set_int32(dev, "base-year", 1968); s = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(s, &error_fatal); sysbus_mmio_map(s, 0, PPC405EP_NVRAM_BASE); } static void ref405ep_class_init(ObjectClass *oc, void *data) { MachineClass *mc = MACHINE_CLASS(oc); mc->desc = "ref405ep"; mc->init = ref405ep_init; } static const TypeInfo ref405ep_type = { .name = MACHINE_TYPE_NAME("ref405ep"), .parent = TYPE_PPC405_MACHINE, .class_init = ref405ep_class_init, }; static void ppc405_machine_init(void) { type_register_static(&ppc405_machine_type); type_register_static(&ref405ep_type); type_register_static(&ref405ep_fpga_type); } type_init(ppc405_machine_init)