| #include "hw/sysbus.h" |
| #include "hw/devices.h" |
| #include "hw/mips/mips.h" |
| #include "hw/mips/cpudevs.h" |
| #include "hw/mips/bios.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" |
| |
| #ifdef CONFIG_ANDROID |
| #include "android/android.h" |
| #endif // CONFIG_ANDROID |
| |
| #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 RESET_ADDRESS 0x1fc00000 |
| |
| #define RANCHU_BIOS_SIZE 0x100000 /* 1024 * 1024 = 1 MB */ |
| |
| /* Store the environment arguments table in the second page from the top |
| * of the bios memory. Two pages are required since the environment |
| * arguments table is 4160 bytes in size. |
| */ |
| #define ENVP_ADDR PHYS_TO_VIRT(RESET_ADDRESS + RANCHU_BIOS_SIZE - 2*TARGET_PAGE_SIZE) |
| #define ENVP_NB_ENTRIES 16 |
| #define ENVP_ENTRY_SIZE 256 |
| |
| #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_GF_PIC, |
| RANCHU_GF_TTY, |
| RANCHU_GF_TIMER, |
| RANCHU_GF_RTC, |
| RANCHU_GF_BATTERY, |
| RANCHU_GF_FB, |
| RANCHU_GF_EVDEV, |
| RANCHU_ANDROID_PIPE, |
| RANCHU_GF_AUDIO, |
| RANCHU_GF_RESET, |
| 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_GF_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_GF_TTY] = { GOLDFISH_IO_SPACE + 0x02000, 0x1000, 2, |
| "goldfish_tty", "goldfish_tty", "generic,goldfish-tty" }, |
| /* repeats for a total of MAX_GF_TTYS */ |
| [RANCHU_GF_TIMER] = { GOLDFISH_IO_SPACE + 0x05000, 0x1000, 5, |
| "goldfish_timer", "goldfish_timer", "generic,goldfish-timer" }, |
| [RANCHU_GF_RTC] = { GOLDFISH_IO_SPACE + 0x06000, 0x1000, 6, |
| "goldfish_rtc", "goldfish_rtc", "generic,goldfish-rtc" }, |
| [RANCHU_GF_BATTERY] = { GOLDFISH_IO_SPACE + 0x07000, 0x1000, 7, |
| "goldfish_battery", "goldfish_battery", "generic,goldfish-battery" }, |
| [RANCHU_GF_FB] = { GOLDFISH_IO_SPACE + 0x08000, 0x0100, 8, |
| "goldfish_fb", "goldfish_fb", "generic,goldfish-fb" }, |
| [RANCHU_GF_EVDEV] = { GOLDFISH_IO_SPACE + 0x09000, 0x1000, 9, |
| "goldfish-events", "goldfish_events", "generic,goldfish-events-keypad" }, |
| [RANCHU_ANDROID_PIPE] = { GOLDFISH_IO_SPACE + 0x0A000, 0x2000, 10, |
| "android_pipe", "android_pipe", "generic,android-pipe" }, |
| [RANCHU_GF_AUDIO] = { GOLDFISH_IO_SPACE + 0x0C000, 0x0100, 11, |
| "goldfish_audio", "goldfish_audio", "generic,goldfish-audio" }, |
| [RANCHU_GF_RESET] = { GOLDFISH_IO_SPACE + 0x0D000, 0x0100, 12, |
| "goldfish_reset", "goldfish_reset", "generic,goldfish-reset" }, |
| [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 mips_boot_info { |
| const char* kernel_filename; |
| const char* kernel_cmdline; |
| const char* initrd_filename; |
| unsigned ram_size; |
| unsigned highmem_size; |
| }; |
| |
| typedef struct machine_params { |
| struct mips_boot_info bootinfo; |
| MIPSCPU *cpu; |
| int fdt_size; |
| void *fdt; |
| } 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)); |
| } |
| |
| /* ROM and pseudo bootloader |
| |
| The following code implements a very simple bootloader. It first |
| loads the registers a0, a1, a2 and a3 to the values expected by the kernel, |
| and then jumps at the kernel entry address. |
| |
| The registers a0 to a3 should contain the following values: |
| a0 - 32-bit address of the command line |
| a1 - RAM size in bytes (ram_size) |
| a2 - RAM size in bytes (highmen_size) |
| a3 - 0 |
| */ |
| static void write_bootloader(uint8_t *base, int64_t run_addr, |
| int64_t kernel_entry, ranchu_params *rp) |
| { |
| uint32_t *p; |
| |
| /* Small bootloader */ |
| p = (uint32_t *)base; |
| |
| stl_p(p++, 0x08000000 | ((run_addr + 0x040) & 0x0fffffff) >> 2); /* j 0x1fc00040 */ |
| stl_p(p++, 0x00000000); /* nop */ |
| |
| /* Second part of the bootloader */ |
| p = (uint32_t *) (base + 0x040); |
| stl_p(p++, 0x3c040000 | (((ENVP_ADDR + 64) >> 16) & 0xffff)); /* lui a0, high(ENVP_ADDR + 64) */ |
| stl_p(p++, 0x34840000 | ((ENVP_ADDR + 64) & 0xffff)); /* ori a0, a0, low(ENVP_ADDR + 64) */ |
| stl_p(p++, 0x3c050000 | (rp->bootinfo.ram_size >> 16)); /* lui a1, high(ram_size) */ |
| stl_p(p++, 0x34a50000 | (rp->bootinfo.ram_size & 0xffff)); /* ori a1, a1, low(ram_size) */ |
| stl_p(p++, 0x3c060000 | (rp->bootinfo.highmem_size >> 16)); /* lui a2, high(highmem_size) */ |
| stl_p(p++, 0x34c60000 | (rp->bootinfo.highmem_size & 0xffff)); /* ori a2, a2, low(highmem_size) */ |
| stl_p(p++, 0x24070000); /* addiu a3, zero, 0 */ |
| |
| /* Jump to kernel code */ |
| stl_p(p++, 0x3c1f0000 | ((kernel_entry >> 16) & 0xffff)); /* lui ra, high(kernel_entry) */ |
| stl_p(p++, 0x37ff0000 | (kernel_entry & 0xffff)); /* ori ra, ra, low(kernel_entry) */ |
| stl_p(p++, 0x03e00009); /* jalr ra */ |
| stl_p(p++, 0x00000000); /* nop */ |
| } |
| |
| static void GCC_FMT_ATTR(3, 4) prom_set(uint32_t* prom_buf, int index, |
| const char *string, ...) |
| { |
| va_list ap; |
| int32_t table_addr; |
| |
| if (index >= ENVP_NB_ENTRIES) |
| return; |
| |
| if (string == NULL) { |
| prom_buf[index] = 0; |
| return; |
| } |
| |
| table_addr = sizeof(int32_t) * ENVP_NB_ENTRIES + index * ENVP_ENTRY_SIZE; |
| prom_buf[index] = tswap32(ENVP_ADDR + table_addr); |
| |
| va_start(ap, string); |
| vsnprintf((char *)prom_buf + table_addr, ENVP_ENTRY_SIZE, string, ap); |
| va_end(ap); |
| } |
| |
| static int64_t android_load_kernel(ranchu_params *rp) |
| { |
| int initrd_size; |
| ram_addr_t initrd_offset; |
| uint64_t kernel_entry, kernel_low, kernel_high; |
| uint32_t *prom_buf; |
| long prom_size; |
| int prom_index = 0; |
| |
| /* Load the kernel. */ |
| if (!rp->bootinfo.kernel_filename) { |
| fprintf(stderr, "Kernel image must be specified\n"); |
| exit(1); |
| } |
| |
| if (load_elf(rp->bootinfo.kernel_filename, cpu_mips_kseg0_to_phys, NULL, |
| (uint64_t *)&kernel_entry, (uint64_t *)&kernel_low, |
| (uint64_t *)&kernel_high, 0, ELF_MACHINE, 1) < 0) { |
| fprintf(stderr, "qemu: could not load kernel '%s'\n", |
| rp->bootinfo.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 (rp->fdt) { |
| cpu_physical_memory_write(kernel_high, rp->fdt, rp->fdt_size); |
| kernel_high += rp->fdt_size; |
| } |
| |
| /* load initrd */ |
| initrd_size = 0; |
| initrd_offset = 0; |
| if (rp->bootinfo.initrd_filename) { |
| initrd_size = get_image_size (rp->bootinfo.initrd_filename); |
| if (initrd_size > 0) { |
| initrd_offset = (kernel_high + ~TARGET_PAGE_MASK) & TARGET_PAGE_MASK; |
| if (initrd_offset + initrd_size > rp->bootinfo.ram_size) { |
| fprintf(stderr, |
| "qemu: memory too small for initial ram disk '%s'\n", |
| rp->bootinfo.initrd_filename); |
| exit(1); |
| } |
| initrd_size = load_image_targphys(rp->bootinfo.initrd_filename, |
| initrd_offset, |
| rp->bootinfo.ram_size - initrd_offset); |
| } |
| |
| if (initrd_size == (target_ulong) -1) { |
| fprintf(stderr, |
| "qemu: could not load initial ram disk '%s'\n", |
| rp->bootinfo.initrd_filename); |
| exit(1); |
| } |
| } |
| |
| char kernel_cmd[1024]; |
| if (initrd_size > 0) |
| sprintf (kernel_cmd, "%s rd_start=0x%" HWADDR_PRIx " rd_size=%li", |
| rp->bootinfo.kernel_cmdline, |
| (hwaddr)PHYS_TO_VIRT(initrd_offset), |
| (long int)initrd_size); |
| else |
| strcpy (kernel_cmd, rp->bootinfo.kernel_cmdline); |
| |
| /* Setup Highmem */ |
| char kernel_cmd2[1024]; |
| if (rp->bootinfo.highmem_size) { |
| sprintf (kernel_cmd2, "%s mem=%um@0x0 mem=%um@0x%x", |
| kernel_cmd, |
| GOLDFISH_IO_SPACE / (1024 * 1024), |
| rp->bootinfo.highmem_size / (1024 * 1024), |
| HIGHMEM_OFFSET); |
| } else { |
| strcpy (kernel_cmd2, kernel_cmd); |
| } |
| |
| /* Prepare the environment arguments table */ |
| prom_size = ENVP_NB_ENTRIES * (sizeof(int32_t) + ENVP_ENTRY_SIZE); |
| prom_buf = g_malloc(prom_size); |
| |
| prom_set(prom_buf, prom_index++, "%s", kernel_cmd2); |
| prom_set(prom_buf, prom_index++, NULL); |
| |
| rom_add_blob_fixed("prom", prom_buf, prom_size, |
| cpu_mips_kseg0_to_phys(NULL, ENVP_ADDR)); |
| |
| return kernel_entry; |
| } |
| |
| static void goldfish_reset_io_write(void *opaque, hwaddr addr, |
| uint64_t val, unsigned size) |
| { |
| switch (val) { |
| case 0x42: |
| qemu_system_reset_request(); |
| break; |
| case 0x43: |
| qemu_system_shutdown_request(); |
| break; |
| default: |
| fprintf(stdout, "%s: %d: Unknown command %lx\n", |
| __func__, __LINE__, val); |
| break; |
| } |
| } |
| |
| static uint64_t goldfish_reset_io_read(void *opaque, hwaddr addr, |
| unsigned size) |
| { |
| return 0; |
| } |
| |
| static const MemoryRegionOps goldfish_reset_io_ops = { |
| .read = goldfish_reset_io_read, |
| .write = goldfish_reset_io_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| }; |
| |
| /* 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); |
| } |
| } |
| |
| /* create_pm_device(void* fdt, DevMapEntry* dev) |
| * |
| * Create power management DT node which will be used by kernel |
| * in order to attach PM routines for reset/halt/shoutdown |
| * |
| * @fdt - Place where DT node will be stored |
| * @dev - Device information (base, irq, name) |
| */ |
| static void create_pm_device(void* fdt, DevMapEntry* dev) |
| { |
| hwaddr base = dev->base; |
| |
| 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); |
| g_free(nodename); |
| } |
| |
| static void ranchu_init(MachineState *machine) |
| { |
| MIPSCPU *cpu; |
| CPUMIPSState *env; |
| qemu_irq *goldfish_pic; |
| int i, fdt_size; |
| void *fdt; |
| ranchu_params *rp; |
| int64_t kernel_entry; |
| |
| 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); |
| MemoryRegion *iomem = g_new(MemoryRegion, 1); |
| MemoryRegion *bios = 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); |
| } |
| |
| rp = g_new0(ranchu_params, 1); |
| |
| env = &cpu->env; |
| |
| register_savevm(NULL, |
| "cpu", |
| 0, |
| MIPS_CPU_SAVE_VERSION, |
| cpu_save, |
| cpu_load, |
| env); |
| |
| qemu_register_reset(main_cpu_reset, rp); |
| |
| 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); |
| |
| memory_region_init_io(iomem, NULL, &goldfish_reset_io_ops, NULL, "goldfish-reset", 0x0100); |
| memory_region_add_subregion(get_system_memory(), devmap[RANCHU_GF_RESET].base, iomem); |
| |
| memory_region_init_ram(bios, NULL, "ranchu.bios", RANCHU_BIOS_SIZE, |
| &error_abort); |
| vmstate_register_ram_global(bios); |
| memory_region_set_readonly(bios, true); |
| memory_region_add_subregion(get_system_memory(), RESET_ADDRESS, bios); |
| |
| /* 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); |
| rp->bootinfo.highmem_size = ram_size - GOLDFISH_IO_SPACE; |
| } |
| |
| rp->bootinfo.kernel_filename = strdup(machine->kernel_filename); |
| rp->bootinfo.kernel_cmdline = strdup(machine->kernel_cmdline); |
| rp->bootinfo.initrd_filename = strdup(machine->initrd_filename); |
| rp->bootinfo.ram_size = MIN(ram_size, GOLDFISH_IO_SPACE); |
| rp->fdt = fdt; |
| rp->fdt_size = fdt_size; |
| rp->cpu = cpu; |
| |
| cpu_mips_irq_init_cpu(env); |
| cpu_mips_clock_init(env); |
| |
| /* Initialize Goldfish PIC */ |
| s->gfpic = goldfish_pic = goldfish_interrupt_init(devmap[RANCHU_GF_PIC].base, |
| env->irq[2], env->irq[3]); |
| /* Alocate dt handle (label) for interrupt-parent */ |
| devmap[RANCHU_GF_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_GF_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_GF_PIC], NULL, 1, 0); |
| |
| /* Create 4 Goldfish TTYs */ |
| create_device(fdt, &devmap[RANCHU_GF_TTY], goldfish_pic, MAX_GF_TTYS, 0); |
| |
| /* Other Goldfish Platform devices */ |
| for (i = RANCHU_GF_AUDIO; i >= RANCHU_GF_TIMER ; i--) { |
| create_device(fdt, &devmap[i], goldfish_pic, 1, 0); |
| } |
| |
| create_pm_device(fdt, &devmap[RANCHU_GF_RESET]); |
| |
| /* Virtio MMIO devices */ |
| create_device(fdt, &devmap[RANCHU_MMIO], goldfish_pic, VIRTIO_TRANSPORTS, 1); |
| |
| qemu_fdt_dumpdtb(fdt, fdt_size); |
| |
| kernel_entry = android_load_kernel(rp); |
| |
| write_bootloader(memory_region_get_ram_ptr(bios), |
| PHYS_TO_VIRT(RESET_ADDRESS), kernel_entry, rp); |
| } |
| |
| 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 QEMUMachine ranchu_machine = { |
| .name = "ranchu", |
| .desc = "Ranchu Virtual Machine for Android Emulator", |
| .init = ranchu_init, |
| .max_cpus = 1, |
| }; |
| |
| static void mips_ranchu_register_types(void) |
| { |
| type_register_static(&mips_ranchu_device); |
| } |
| |
| static void ranchu_machine_init(void) |
| { |
| qemu_register_machine(&ranchu_machine); |
| } |
| |
| type_init(mips_ranchu_register_types) |
| machine_init(ranchu_machine_init); |