| #include "qemu/osdep.h" |
| #include "cpu.h" |
| #include "hw/sysbus.h" |
| #include "hw/devices.h" |
| #include "hw/mips/mips.h" |
| #include "hw/mips/cpudevs.h" |
| #include "net/net.h" |
| #include "sysemu/device_tree.h" |
| #include "sysemu/sysemu.h" |
| #include "sysemu/kvm.h" |
| #include "hw/boards.h" |
| #include "exec/address-spaces.h" |
| #include "qemu/bitops.h" |
| #include "qemu/error-report.h" |
| #include "qemu/config-file.h" |
| #include "sysemu/char.h" |
| #include "monitor/monitor.h" |
| #include "hw/loader.h" |
| #include "elf.h" |
| #include "hw/intc/goldfish_pic.h" |
| #include "hw/irq.h" |
| #include "qapi/error.h" |
| |
| #define PHYS_TO_VIRT(x) ((x) | ~(target_ulong)0x7fffffff) |
| |
| #define MIPS_CPU_SAVE_VERSION 1 |
| #define MIPS_CPU_IRQ_BASE 8 |
| |
| #define HIGHMEM_OFFSET 0x20000000 |
| #define GOLDFISH_IO_SPACE 0x1f000000 |
| |
| #define VIRTIO_TRANSPORTS 16 |
| #define MAX_GF_TTYS 3 |
| |
| #define TYPE_MIPS_RANCHU "mips-ranchu" |
| #define MIPS_RANCHU(obj) OBJECT_CHECK(RanchuState, (obj), TYPE_MIPS_RANCHU) |
| #define MIPS_RANCHU_REV "1" |
| |
| typedef struct { |
| SysBusDevice parent_obj; |
| |
| qemu_irq *gfpic; |
| } RanchuState; |
| |
| #define MAX_RAM_SIZE_MB 4079UL |
| |
| enum { |
| RANCHU_GOLDFISH_PIC, |
| RANCHU_GOLDFISH_TTY, |
| RANCHU_GOLDFISH_TIMER, |
| RANCHU_GOLDFISH_RTC, |
| RANCHU_GOLDFISH_BATTERY, |
| RANCHU_GOLDFISH_FB, |
| RANCHU_GOLDFISH_EVDEV, |
| RANCHU_GOLDFISH_PIPE, |
| RANCHU_GOLDFISH_AUDIO, |
| RANCHU_GOLDFISH_SYNC, |
| RANCHU_MMIO, |
| }; |
| |
| typedef struct DevMapEntry { |
| hwaddr base; |
| hwaddr size; |
| int irq; |
| const char* qemu_name; |
| const char* dt_name; |
| const char* dt_compatible; |
| } DevMapEntry; |
| |
| static DevMapEntry devmap[] = { |
| [RANCHU_GOLDFISH_PIC] = { GOLDFISH_IO_SPACE, 0x1000, 0, |
| NULL, "goldfish_pic", "generic,goldfish-pic" }, |
| /* ttyGF0 base address remains hardcoded in the kernel. |
| * Early printing (prom_putchar()) relies on finding device |
| * mapped on this address. DT can not be used at that early stage |
| * for acquiring the base address of the device in the kernel. |
| */ |
| [RANCHU_GOLDFISH_TTY] = { GOLDFISH_IO_SPACE + 0x02000, 0x1000, 2, |
| "goldfish_tty", "goldfish_tty", "generic,goldfish-tty" }, |
| /* repeats for a total of MAX_GF_TTYS */ |
| [RANCHU_GOLDFISH_TIMER] = { GOLDFISH_IO_SPACE + 0x05000, 0x1000, 5, |
| "goldfish_timer", "goldfish_timer", "generic,goldfish-timer" }, |
| [RANCHU_GOLDFISH_RTC] = { GOLDFISH_IO_SPACE + 0x06000, 0x1000, 6, |
| "goldfish_rtc", "goldfish_rtc", "generic,goldfish-rtc" }, |
| [RANCHU_GOLDFISH_BATTERY] = { GOLDFISH_IO_SPACE + 0x07000, 0x1000, 7, |
| "goldfish_battery", "goldfish_battery", "generic,goldfish-battery" }, |
| [RANCHU_GOLDFISH_FB] = { GOLDFISH_IO_SPACE + 0x08000, 0x0100, 8, |
| "goldfish_fb", "goldfish_fb", "generic,goldfish-fb" }, |
| [RANCHU_GOLDFISH_EVDEV] = { GOLDFISH_IO_SPACE + 0x09000, 0x1000, 9, |
| "goldfish-events", "goldfish_events", "generic,goldfish-events-keypad" }, |
| [RANCHU_GOLDFISH_PIPE] = { GOLDFISH_IO_SPACE + 0x0A000, 0x2000, 10, |
| "goldfish_pipe", "goldfish_pipe", "generic,android-pipe" }, |
| [RANCHU_GOLDFISH_AUDIO] = { GOLDFISH_IO_SPACE + 0x0C000, 0x0100, 11, |
| "goldfish_audio", "goldfish_audio", "generic,goldfish-audio" }, |
| [RANCHU_GOLDFISH_SYNC] = { GOLDFISH_IO_SPACE + 0x0D000, 0x0100, 11, |
| "goldfish_sync", "goldfish_sync", "generic,goldfish-audio" }, |
| [RANCHU_MMIO] = { GOLDFISH_IO_SPACE + 0x10000, 0x0200, 16, |
| "virtio-mmio", "virtio_mmio", "virtio,mmio" }, |
| /* ...repeating for a total of VIRTIO_TRANSPORTS, each of that size */ |
| }; |
| |
| struct machine_params { |
| uint64_t kernel_entry; |
| uint64_t cmdline_ptr; |
| unsigned ram_size; |
| unsigned highmem_size; |
| MIPSCPU *cpu; |
| } ranchu_params; |
| |
| static void main_cpu_reset(void* opaque1) |
| { |
| struct machine_params *opaque = (struct machine_params *)opaque1; |
| MIPSCPU *cpu = opaque->cpu; |
| |
| cpu_reset(CPU(cpu)); |
| |
| cpu->env.active_tc.PC = opaque->kernel_entry; |
| cpu->env.active_tc.gpr[4] = opaque->cmdline_ptr; /* a0 */ |
| cpu->env.active_tc.gpr[5] = opaque->ram_size; /* a1 */ |
| cpu->env.active_tc.gpr[6] = opaque->highmem_size;/* a2 */ |
| cpu->env.active_tc.gpr[7] = 0; /* a3 */ |
| |
| } |
| |
| static void android_load_kernel(CPUMIPSState *env, int ram_size, |
| const char *kernel_filename, |
| const char *kernel_cmdline, |
| const char *initrd_filename, |
| void* fdt, int fdt_size) |
| { |
| int initrd_size; |
| ram_addr_t initrd_offset; |
| uint64_t kernel_entry, kernel_low, kernel_high; |
| unsigned int cmdline; |
| |
| /* Load the kernel. */ |
| if (!kernel_filename) { |
| fprintf(stderr, "Kernel image must be specified\n"); |
| exit(1); |
| } |
| |
| if (load_elf(kernel_filename, cpu_mips_kseg0_to_phys, NULL, |
| (uint64_t *)&kernel_entry, (uint64_t *)&kernel_low, |
| (uint64_t *)&kernel_high, 0, EM_MIPS, 1, 0) < 0) { |
| fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename); |
| exit(1); |
| } |
| |
| /* Load the DTB at the kernel_high address, that is the place where |
| * kernel with Appended DT support enabled will look for it |
| */ |
| if (fdt) { |
| cpu_physical_memory_write(kernel_high, fdt, fdt_size); |
| kernel_high += fdt_size; |
| } |
| |
| ranchu_params.kernel_entry = kernel_entry; |
| |
| /* load initrd */ |
| initrd_size = 0; |
| initrd_offset = 0; |
| if (initrd_filename) { |
| initrd_size = get_image_size (initrd_filename); |
| if (initrd_size > 0) { |
| initrd_offset = (kernel_high + ~TARGET_PAGE_MASK) & TARGET_PAGE_MASK; |
| if (initrd_offset + initrd_size > ram_size) { |
| fprintf(stderr, |
| "qemu: memory too small for initial ram disk '%s'\n", |
| initrd_filename); |
| exit(1); |
| } |
| initrd_size = load_image_targphys(initrd_filename, |
| initrd_offset, |
| ram_size - initrd_offset); |
| } |
| |
| if (initrd_size == (target_ulong) -1) { |
| fprintf(stderr, |
| "qemu: could not load initial ram disk '%s'\n", |
| initrd_filename); |
| exit(1); |
| } |
| } |
| |
| /* Store command line in top page of memory |
| * kernel will copy the command line to a local buffer |
| */ |
| cmdline = ram_size - TARGET_PAGE_SIZE; |
| char kernel_cmd[1024]; |
| if (initrd_size > 0) |
| sprintf (kernel_cmd, "%s rd_start=0x%" HWADDR_PRIx " rd_size=%li", |
| kernel_cmdline, |
| (hwaddr)PHYS_TO_VIRT(initrd_offset), |
| (long int)initrd_size); |
| else |
| strcpy (kernel_cmd, kernel_cmdline); |
| |
| /* Setup Highmem */ |
| char kernel_cmd2[1024]; |
| if (ranchu_params.highmem_size) { |
| sprintf (kernel_cmd2, "%s mem=%um@0x0 mem=%um@0x%x", |
| kernel_cmd, |
| GOLDFISH_IO_SPACE / (1024 * 1024), |
| ranchu_params.highmem_size / (1024 * 1024), |
| HIGHMEM_OFFSET); |
| } else { |
| strcpy (kernel_cmd2, kernel_cmd); |
| } |
| |
| cpu_physical_memory_write(ram_size - TARGET_PAGE_SIZE, |
| (void *)kernel_cmd2, |
| strlen(kernel_cmd2) + 1); |
| |
| ranchu_params.cmdline_ptr = PHYS_TO_VIRT(cmdline); |
| } |
| |
| /* create_device(void* fdt, DevMapEntry* dev, qemu_irq* pic, |
| * int num_devices, int is_virtio) |
| * |
| * In case of interrupt controller dev->irq stores |
| * dt handle previously referenced as interrupt-parent |
| * |
| * @fdt - Place where DT nodes will be stored |
| * @dev - Device information (base, irq, name) |
| * @pic - Interrupt controller parent. If NULL, 'intc' node assumed. |
| * @num_devices - If you want to allocate multiple continuous device mappings |
| */ |
| static void create_device(void* fdt, DevMapEntry* dev, qemu_irq* pic, |
| int num_devices, int is_virtio) |
| { |
| int i; |
| |
| for (i = 0; i < num_devices; i++) { |
| hwaddr base = dev->base + i * dev->size; |
| |
| char* nodename = g_strdup_printf("/%s@%" PRIx64, dev->dt_name, base); |
| qemu_fdt_add_subnode(fdt, nodename); |
| qemu_fdt_setprop(fdt, nodename, "compatible", |
| dev->dt_compatible, |
| strlen(dev->dt_compatible) + 1); |
| qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", 1, base, 2, dev->size); |
| |
| if (pic == NULL) { |
| qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0); |
| qemu_fdt_setprop_cell(fdt, nodename, "phandle", dev->irq); |
| qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 0x1); |
| } else { |
| qemu_fdt_setprop_cells(fdt, nodename, "interrupts", |
| dev->irq + i + MIPS_CPU_IRQ_BASE); |
| if (is_virtio) { |
| /* Note that we have to create the transports in forwards order |
| * so that command line devices are inserted lowest address first, |
| * and then add dtb nodes in reverse order so that they appear in |
| * the finished device tree lowest address first. |
| */ |
| sysbus_create_simple(dev->qemu_name, |
| dev->base + (num_devices - i - 1) * dev->size, |
| pic[dev->irq + num_devices - i - 1]); |
| } else { |
| sysbus_create_simple(dev->qemu_name, base, pic[dev->irq + i]); |
| } |
| } |
| g_free(nodename); |
| } |
| } |
| |
| static void mips_ranchu_init(MachineState *machine) |
| { |
| MIPSCPU *cpu; |
| CPUMIPSState *env; |
| qemu_irq *goldfish_pic; |
| int i, fdt_size; |
| void *fdt; |
| |
| DeviceState *dev = qdev_create(NULL, TYPE_MIPS_RANCHU); |
| RanchuState *s = MIPS_RANCHU(dev); |
| |
| MemoryRegion *ram_lo = g_new(MemoryRegion, 1); |
| MemoryRegion *ram_hi = g_new(MemoryRegion, 1); |
| |
| /* init CPUs */ |
| if (machine->cpu_model == NULL) { |
| #ifdef TARGET_MIPS64 |
| machine->cpu_model = "MIPS64R2-generic"; |
| #else |
| machine->cpu_model = "74Kf"; |
| #endif |
| } |
| cpu = cpu_mips_init(machine->cpu_model); |
| if (cpu == NULL) { |
| fprintf(stderr, "Unable to find CPU definition\n"); |
| exit(1); |
| } |
| |
| env = &cpu->env; |
| |
| qemu_register_reset(main_cpu_reset, &ranchu_params); |
| |
| if (ram_size > (MAX_RAM_SIZE_MB << 20)) { |
| fprintf(stderr, "qemu: Too much memory for this machine. " |
| "RAM size reduced to %lu MB\n", MAX_RAM_SIZE_MB); |
| ram_size = MAX_RAM_SIZE_MB << 20; |
| } |
| |
| fdt = create_device_tree(&fdt_size); |
| |
| if (!fdt) { |
| error_report("create_device_tree() failed"); |
| exit(1); |
| } |
| |
| memory_region_init_ram(ram_lo, NULL, "ranchu_low.ram", |
| MIN(ram_size, GOLDFISH_IO_SPACE), &error_abort); |
| vmstate_register_ram_global(ram_lo); |
| memory_region_add_subregion(get_system_memory(), 0, ram_lo); |
| |
| ranchu_params.ram_size = MIN(ram_size, GOLDFISH_IO_SPACE); |
| |
| /* post IO hole, if there is enough RAM */ |
| if (ram_size > GOLDFISH_IO_SPACE) { |
| memory_region_init_ram(ram_hi, NULL, "ranchu_high.ram", |
| ram_size - GOLDFISH_IO_SPACE, &error_abort); |
| vmstate_register_ram_global(ram_hi); |
| memory_region_add_subregion(get_system_memory(), HIGHMEM_OFFSET, ram_hi); |
| ranchu_params.highmem_size = ram_size - GOLDFISH_IO_SPACE; |
| } |
| |
| ranchu_params.cpu = cpu; |
| |
| cpu_mips_irq_init_cpu(cpu); |
| cpu_mips_clock_init(cpu); |
| |
| /* Initialize Goldfish PIC */ |
| s->gfpic = goldfish_pic = goldfish_interrupt_init(devmap[RANCHU_GOLDFISH_PIC].base, |
| env->irq[2], env->irq[3]); |
| /* Alocate dt handle (label) for interrupt-parent */ |
| devmap[RANCHU_GOLDFISH_PIC].irq = qemu_fdt_alloc_phandle(fdt); |
| |
| qemu_fdt_setprop_string(fdt, "/", "model", "ranchu"); |
| qemu_fdt_setprop_string(fdt, "/", "compatible", "mti,goldfish"); |
| qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x1); |
| qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2); |
| qemu_fdt_setprop_cell(fdt, "/", "interrupt-parent", devmap[RANCHU_GOLDFISH_PIC].irq); |
| |
| /* Firmware node */ |
| qemu_fdt_add_subnode(fdt, "/firmware"); |
| qemu_fdt_add_subnode(fdt, "/firmware/android"); |
| qemu_fdt_setprop_string(fdt, "/firmware/android", "compatible", "android,firmware"); |
| qemu_fdt_setprop_string(fdt, "/firmware/android", "hardware", "ranchu"); |
| qemu_fdt_setprop_string(fdt, "/firmware/android", "revision", MIPS_RANCHU_REV); |
| |
| /* CPU node */ |
| qemu_fdt_add_subnode(fdt, "/cpus"); |
| qemu_fdt_add_subnode(fdt, "/cpus/cpu@0"); |
| qemu_fdt_setprop_string(fdt, "/cpus/cpu@0", "device_type", "cpu"); |
| qemu_fdt_setprop_string(fdt, "/cpus/cpu@0", "compatible", "mti,5KEf"); |
| |
| /* Memory node */ |
| qemu_fdt_add_subnode(fdt, "/memory"); |
| qemu_fdt_setprop_string(fdt, "/memory", "device_type", "memory"); |
| if (ram_size > GOLDFISH_IO_SPACE) { |
| qemu_fdt_setprop_sized_cells(fdt, "/memory", "reg", |
| 1, 0, 2, GOLDFISH_IO_SPACE, |
| 1, HIGHMEM_OFFSET, 2, ram_size - GOLDFISH_IO_SPACE); |
| } else { |
| qemu_fdt_setprop_sized_cells(fdt, "/memory", "reg", 1, 0, 2, ram_size); |
| } |
| |
| /* Create goldfish_pic controller node in dt */ |
| create_device(fdt, &devmap[RANCHU_GOLDFISH_PIC], NULL, 1, 0); |
| |
| /* Create 4 Goldfish TTYs */ |
| create_device(fdt, &devmap[RANCHU_GOLDFISH_TTY], goldfish_pic, MAX_GF_TTYS, 0); |
| |
| /* Other Goldfish Platform devices */ |
| for (i = RANCHU_GOLDFISH_AUDIO; i >= RANCHU_GOLDFISH_TIMER ; i--) { |
| create_device(fdt, &devmap[i], goldfish_pic, 1, 0); |
| } |
| |
| /* Virtio MMIO devices */ |
| create_device(fdt, &devmap[RANCHU_MMIO], goldfish_pic, VIRTIO_TRANSPORTS, 1); |
| |
| qemu_fdt_dumpdtb(fdt, fdt_size); |
| |
| android_load_kernel(env, MIN(ram_size, GOLDFISH_IO_SPACE), |
| machine->kernel_filename, |
| machine->kernel_cmdline, |
| machine->initrd_filename, |
| fdt, fdt_size); |
| } |
| |
| static int mips_ranchu_sysbus_device_init(SysBusDevice *sysbusdev) |
| { |
| return 0; |
| } |
| |
| static void mips_ranchu_class_init(ObjectClass *klass, void *data) |
| { |
| SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); |
| |
| k->init = mips_ranchu_sysbus_device_init; |
| } |
| |
| static const TypeInfo mips_ranchu_device = { |
| .name = TYPE_MIPS_RANCHU, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(RanchuState), |
| .class_init = mips_ranchu_class_init, |
| }; |
| |
| static void mips_ranchu_machine_init(MachineClass *mc) |
| { |
| mc->desc = "Android/MIPS ranchu"; |
| mc->init = mips_ranchu_init; |
| mc->max_cpus = 16; |
| mc->is_default = 1; |
| } |
| |
| DEFINE_MACHINE("ranchu", mips_ranchu_machine_init) |
| |
| static void mips_ranchu_register_types(void) |
| { |
| type_register_static(&mips_ranchu_device); |
| } |
| |
| type_init(mips_ranchu_register_types) |