blob: 294ce48cd45b40e29d066d4687ef26347ae7bdbe [file] [log] [blame]
#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);