blob: 9b405ac2f23bcd0b60ea7f508d72424a2c7d9fc9 [file] [log] [blame]
#include "qemu/osdep.h"
#include "cpu.h"
#include "elf.h"
#include "exec/address-spaces.h"
#include "hw/boards.h"
#include "hw/devices.h"
#include "hw/intc/goldfish_pic.h"
#include "hw/irq.h"
#include "hw/loader.h"
#include "hw/mips/bios.h"
#include "hw/mips/cpudevs.h"
#include "hw/mips/cps.h"
#include "hw/mips/mips.h"
#include "hw/sysbus.h"
#include "monitor/monitor.h"
#include "net/net.h"
#include "qapi/error.h"
#include "qemu/bitops.h"
#include "qemu/config-file.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
#include "chardev/char.h"
#include "sysemu/device_tree.h"
#include "sysemu/kvm.h"
#include "sysemu/ranchu.h"
#include "sysemu/sysemu.h"
#define FATAL(fmt, ...) \
do { \
fprintf(stderr, "%s:%d : " fmt, __func__, __LINE__, ##__VA_ARGS__); \
exit(-errno); \
} while (0)
#define LOGI(fmt, ...) \
do { \
fprintf(stdout, "%s() : " fmt, __func__, ##__VA_ARGS__); \
} while (0)
#define MIPS_CPU_SAVE_VERSION 1
#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 7232 bytes in size.
*/
#define ENVP_ADDR cpu_mips_phys_to_kseg0(NULL, \
RESET_ADDRESS + \
RANCHU_BIOS_SIZE - \
(2 * TARGET_PAGE_SIZE))
#define ENVP_NB_ENTRIES 16
#define ENVP_ENTRY_SIZE 448
#define VIRTIO_TRANSPORTS 16
#define MAX_GF_TTYS 3
#define HIGH(addr) ((addr >> 16) & 0xFFFF)
#define LOW(addr) (addr & 0xFFFF)
#define TYPE_MIPS_RANCHU "mips-ranchu"
#define MIPS_RANCHU(obj) OBJECT_CHECK(RanchuState, (obj), TYPE_MIPS_RANCHU)
#define MIPS_RANCHU_REV "1"
struct mips_boot_info {
const char *kernel_filename;
const char *kernel_cmdline;
const char *initrd_filename;
uint64_t ram_size;
uint64_t highmem_size;
bool use_uhi;
};
typedef struct machine_params {
struct mips_boot_info bootinfo;
MIPSCPU *cpu;
int fdt_size;
void *fdt;
hwaddr fdt_addr;
} ranchu_params;
typedef struct {
SysBusDevice parent_obj;
MIPSCPSState *cps;
qemu_irq *gfpic;
ranchu_params *rp;
} RanchuState;
typedef qemu_irq (*get_qemu_irq_cb)(RanchuState *s, int irq);
get_qemu_irq_cb get_irq;
#define MAX_RAM_SIZE_MB 4079UL
enum {
RANCHU_MIPS_CPU_INTC,
RANCHU_MIPS_GIC,
RANCHU_MIPS_CPC,
RANCHU_GOLDFISH_PIC,
RANCHU_GOLDFISH_TTY,
RANCHU_GOLDFISH_RTC,
RANCHU_GOLDFISH_BATTERY,
RANCHU_GOLDFISH_FB,
RANCHU_GOLDFISH_EVDEV,
RANCHU_GOLDFISH_PIPE,
RANCHU_GOLDFISH_AUDIO,
RANCHU_GOLDFISH_SYNC,
RANCHU_GOLDFISH_RESET,
RANCHU_GOLDFISH_PSTORE,
RANCHU_GOLDFISH_TIMER,
RANCHU_MMIO,
};
/* Power Management Unit commands */
enum {
GOLDFISH_PM_CMD_GORESET = 0x42,
GOLDFISH_PM_CMD_GOSHUTDOWN = 0x43
};
typedef struct DevMapEntry {
hwaddr base;
hwaddr size;
int irq;
const char *qemu_name;
const char *dt_name;
const char *dt_compatible;
int dt_num_compatible;
int dt_phandle;
} DevMapEntry;
static DevMapEntry devmap[] = {
[RANCHU_MIPS_CPU_INTC] = {
0, 0, -1, "cpuintc", "cpuintc",
"mti,cpu-interrupt-controller", 1
},
[RANCHU_GOLDFISH_PIC] = {
GOLDFISH_IO_SPACE, 0x1000, 2,
"goldfish_pic", "goldfish_pic",
"google,goldfish-pic\0generic,goldfish-pic", 2
},
[RANCHU_MIPS_GIC] = {
GOLDFISH_IO_SPACE, 0x20000, -1,
"gic", "gic", "mti,gic", 1
},
/* The following region of 64K is reserved for Goldfish pstore device */
[RANCHU_GOLDFISH_PSTORE] = {
GOLDFISH_IO_SPACE + 0x20000, 0x10000, -1,
"goldfish_pstore", "goldfish_pstore",
"google,goldfish-pstore\0generic,goldfish-pstore", 2
},
/* 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.
* Repeats for a total of MAX_GF_TTYS
*/
[RANCHU_GOLDFISH_TTY] = {
GOLDFISH_IO_SPACE + 0x30000, 0x1000, 2,
"goldfish_tty", "goldfish_tty",
"google,goldfish-tty\0generic,goldfish-tty", 2
},
/* This is just a fake node to fool kernels < v4.4.
* It's required during a call to estimate_cpu_frequency().
* Essentially it will be using Goldfish RTC device.
* This should be removed once we officially switch to v4.4.
*/
[RANCHU_GOLDFISH_TIMER] = {
GOLDFISH_IO_SPACE + 0x33000, 0x1000, 5,
"goldfish_timer", "goldfish_timer",
"google,goldfish-timer\0generic,goldfish-timer", 2
},
[RANCHU_GOLDFISH_RTC] = {
GOLDFISH_IO_SPACE + 0x33000, 0x1000, 5,
"goldfish_rtc", "goldfish_rtc",
"google,goldfish-rtc\0generic,goldfish-rtc", 2
},
[RANCHU_GOLDFISH_BATTERY] = {
GOLDFISH_IO_SPACE + 0x34000, 0x1000, 6,
"goldfish_battery", "goldfish_battery",
"google,goldfish-battery\0generic,goldfish-battery", 2
},
[RANCHU_GOLDFISH_FB] = {
GOLDFISH_IO_SPACE + 0x35000, 0x0100, 7,
"goldfish_fb", "goldfish_fb",
"google,goldfish-fb\0generic,goldfish-fb", 2
},
[RANCHU_GOLDFISH_EVDEV] = {
GOLDFISH_IO_SPACE + 0x36000, 0x1000, 8,
"goldfish-events", "goldfish_events",
"google,goldfish-events-keypad\0generic,goldfish-events-keypad", 2
},
[RANCHU_GOLDFISH_PIPE] = {
GOLDFISH_IO_SPACE + 0x37000, 0x2000, 9,
"goldfish_pipe", "android_pipe",
"google,android-pipe\0generic,android-pipe", 2
},
[RANCHU_GOLDFISH_AUDIO] = {
GOLDFISH_IO_SPACE + 0x39000, 0x0100, 10,
"goldfish_audio", "goldfish_audio",
"google,goldfish-audio\0generic,goldfish-audio", 2
},
[RANCHU_GOLDFISH_SYNC] = {
GOLDFISH_IO_SPACE + 0x3A000, 0x2000, 11,
"goldfish_sync", "goldfish_sync",
"google,goldfish-sync\0generic,goldfish-sync", 2
},
[RANCHU_GOLDFISH_RESET] = {
GOLDFISH_IO_SPACE + 0x3C000, 0x0100, -1,
"goldfish_reset", "goldfish_reset",
"google,goldfish-reset\0generic,goldfish-reset\0syscon\0simple-mfd", 4
},
/* ...repeating for a total of VIRTIO_TRANSPORTS, each of that size */
[RANCHU_MMIO] = {
GOLDFISH_IO_SPACE + 0x3D000, 0x0200, 16,
"virtio-mmio", "virtio_mmio", "virtio,mmio", 1
},
[RANCHU_MIPS_CPC] = {
GOLDFISH_IO_SPACE + 0x40000, 0x8000, -1,
"cpc", "cpc", "mti,mips-cpc", 1
},
};
#ifdef _WIN32
/*
* Returns a pointer to the first occurrence of |needle| in |haystack|, or a
* NULL pointer if |needle| is not part of |haystack|.
* Intentionally in global namespace. This is already provided by the system
* C library on Linux and OS X.
*/
static void *memmem(const void *haystack, size_t haystacklen,
const void *needle, size_t needlelen)
{
register char *cur, *last;
const char *cl = (const char *)haystack;
const char *cs = (const char *)needle;
/* we need something to compare */
if (haystacklen == 0 || needlelen == 0)
return NULL;
/* "needle" must be smaller or equal to "haystack" */
if (haystacklen < needlelen)
return NULL;
/* special case where needlelen == 1 */
if (needlelen == 1)
return memchr(haystack, (int)*cs, haystacklen);
/* the last position where its possible to find "needle" in "haystack" */
last = (char *)cl + haystacklen - needlelen;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, needlelen) == 0)
return cur;
return NULL;
}
#endif
static QemuDeviceTreeSetupFunc device_tree_setup_func;
void qemu_device_tree_setup_callback(QemuDeviceTreeSetupFunc setup_func)
{
device_tree_setup_func = setup_func;
}
typedef enum {
KERNEL_VERSION_4_4_0 = 0x040400,
} KernelVersion;
const char kLinuxVersionStringPrefix[] = "Linux version ";
const size_t kLinuxVersionStringPrefixLen =
sizeof(kLinuxVersionStringPrefix) - 1U;
static void parse_vmlinux_version_string(const char *versionString,
KernelVersion *kernelVersion) {
uint32_t parsedVersion;
int i, err = 0;
if (strncmp(versionString, kLinuxVersionStringPrefix,
kLinuxVersionStringPrefixLen)) {
FATAL("Unsupported kernel version string: %s\n", versionString);
}
/* skip the linux prefix string to start parsing the version number */
versionString += kLinuxVersionStringPrefixLen;
parsedVersion = 0;
for (i = 0; i < 3; i++) {
const char *end;
unsigned long number;
/* skip '.' delimeters */
while (i > 0 && *versionString == '.') {
versionString++;
}
err = qemu_strtoul(versionString, &end, 10, &number);
if (end == versionString || number > 0xff || err) {
FATAL("Unsupported kernel version string: %s\n", versionString);
}
parsedVersion <<= 8;
parsedVersion |= number;
versionString = end;
}
*kernelVersion = (KernelVersion)parsedVersion;
}
static void probe_vmlinux_version_string(const char *kernelFilePath, char *dst,
size_t dstLen) {
const uint8_t *uncompressedKernel = NULL;
size_t uncompressedKernelLen = 0;
int ret = 0;
long kernelFileSize;
uint8_t *kernelFileData;
FILE *f = fopen(kernelFilePath, "rb");
if (!f) {
FATAL("Failed to fopen() kernel file %s\n", kernelFilePath);
}
do {
if (fseek(f, 0, SEEK_END) < 0) {
ret = -errno;
break;
}
kernelFileSize = ftell(f);
if (kernelFileSize < 0) {
ret = -errno;
break;
}
if (fseek(f, 0, SEEK_SET) < 0) {
ret = -errno;
break;
}
kernelFileData = malloc((size_t)kernelFileSize);
if (!kernelFileData) {
ret = -errno;
break;
}
size_t readLen = fread(kernelFileData, 1, (size_t)kernelFileSize, f);
if (readLen != (size_t)kernelFileSize) {
if (feof(f)) {
ret = -EIO;
} else {
ret = -ferror(f);
}
free(kernelFileData);
break;
}
} while (0);
fclose(f);
if (ret) {
FATAL("Failed to load kernel file %s errno %d\n", kernelFilePath, ret);
}
const char kElfHeader[] = { 0x7f, 'E', 'L', 'F' };
if (kernelFileSize < sizeof(kElfHeader)) {
FATAL("Kernel image too short\n");
}
const char *versionStringStart = NULL;
if (0 == memcmp(kElfHeader, kernelFileData, sizeof(kElfHeader))) {
/* Ranchu uses uncompressed kernel ELF file */
uncompressedKernel = kernelFileData;
uncompressedKernelLen = kernelFileSize;
}
if (!versionStringStart) {
versionStringStart = (const char *)memmem(
uncompressedKernel,
uncompressedKernelLen,
kLinuxVersionStringPrefix,
kLinuxVersionStringPrefixLen);
if (!versionStringStart) {
FATAL("Could not find 'Linux version' in kernel!\n");
}
}
snprintf(dst, dstLen, "%s", versionStringStart);
}
static void boot_protocol_detect(ranchu_params *rp, const char *kernelFilePath)
{
char version_string[256];
KernelVersion kernelVersion = 0;
probe_vmlinux_version_string(kernelFilePath, version_string,
sizeof(version_string));
parse_vmlinux_version_string(version_string, &kernelVersion);
if (kernelVersion >= KERNEL_VERSION_4_4_0) {
LOGI("Using UHI boot protocol!\n");
rp->bootinfo.use_uhi = true;
} else {
LOGI("Using legacy boot protocol!\n");
rp->bootinfo.use_uhi = false;
}
}
static void ranchu_mips_config(MIPSCPU *cpu)
{
CPUMIPSState *env = &cpu->env;
CPUState *cs = CPU(cpu);
env->mvp->CP0_MVPConf0 |= ((smp_cpus - 1) << CP0MVPC0_PVPE) |
((smp_cpus * cs->nr_threads - 1) << CP0MVPC0_PTC);
}
static void main_cpu_reset(void *opaque)
{
MIPSCPU *cpu = opaque;
cpu_reset(CPU(cpu));
ranchu_mips_config(cpu);
}
static qemu_irq gfpic_get_irq(RanchuState *s, int irq)
{
return s->gfpic[irq];
}
static qemu_irq gic_get_irq(RanchuState *s, int irq)
{
return get_cps_irq(s->cps, irq);
}
static void create_cpu_without_cps(const char *cpu_model,
qemu_irq *gfpic_irq)
{
CPUMIPSState *env;
MIPSCPU *cpu;
int i;
const char* cpu_type = parse_cpu_model(cpu_model);
for (i = 0; i < smp_cpus; i++) {
cpu = MIPS_CPU(cpu_create(cpu_type));
if (cpu == NULL) {
FATAL("Unable to find CPU definition\n");
}
/* Init internal devices */
cpu_mips_irq_init_cpu(cpu);
cpu_mips_clock_init(cpu);
qemu_register_reset(main_cpu_reset, cpu);
}
cpu = MIPS_CPU(first_cpu);
env = &cpu->env;
*gfpic_irq = env->irq[2];
}
static void create_cps(RanchuState *s, const char *cpu_model)
{
Error *err = NULL;
s->cps = g_new0(MIPSCPSState, 1);
object_initialize(s->cps, sizeof(MIPSCPSState), TYPE_MIPS_CPS);
qdev_set_parent_bus(DEVICE(s->cps), sysbus_get_default());
object_property_set_str(OBJECT(s->cps), cpu_model, "cpu-model", &err);
object_property_set_int(OBJECT(s->cps), smp_cpus, "num-vp", &err);
object_property_set_bool(OBJECT(s->cps), true, "realized", &err);
if (err != NULL) {
error_report("%s", error_get_pretty(err));
exit(1);
}
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(s->cps), 0, 0, 1);
}
static void create_cpu(RanchuState *s, const char *cpu_model)
{
qemu_irq gfpic_out_irq;
if (cpu_model == NULL) {
#ifdef TARGET_MIPS64
cpu_model = "MIPS64R6-generic";
#else
cpu_model = "74Kf";
#endif
}
if (s->rp->bootinfo.use_uhi && cpu_supports_cps_smp(cpu_model)) {
create_cps(s, cpu_model);
get_irq = &gic_get_irq;
s->gfpic = NULL;
} else {
create_cpu_without_cps(cpu_model, &gfpic_out_irq);
/* Initialize Goldfish PIC */
s->gfpic = goldfish_pic_init(devmap[RANCHU_GOLDFISH_PIC].base,
gfpic_out_irq);
get_irq = &gfpic_get_irq;
s->cps = NULL;
}
}
/*
* write_bootloader() - Writes a small boot loader into ROM
*
* @base - ROM base address
* @run_addr - Second boot loader address
* @kernel_entry - Kernel entry address
* @rp - Ranchu machine parameters
*
* Legacy boot loader arguments:
*
* a0 = 32-bit address of the command line
* a1 = RAM size in bytes (ram_size)
* a2 = RAM size in bytes (highmen_size)
* a3 = 0
*
* UHI boot loader - DTB passover mode
*
* a0 = -2
* a1 = DTB address
*/
static void write_bootloader(uint8_t *base, int64_t run_addr,
int64_t kernel_entry, ranchu_params *rp)
{
uint32_t addr, addr_hi, addr_low;
uint32_t *p;
/* Small bootloader */
p = (uint32_t *)base;
addr = ((run_addr + 0x040) & 0x0fffffff) >> 2;
stl_p(p++, 0x08000000 | addr); /* j 0x1fc00040 */
stl_p(p++, 0x00000000); /* nop */
/* Second part of the bootloader */
p = (uint32_t *) (base + 0x040);
if (rp->bootinfo.use_uhi) {
/* UHI boot protocol, device tree handover mode */
addr = cpu_mips_phys_to_kseg0(NULL, rp->fdt_addr);
stl_p(p++, 0x2404fffe); /* addiu a0, zero, -2 */
stl_p(p++, 0x3c050000 | HIGH(addr)); /* lui a1, HI(fdt_addr) */
stl_p(p++, 0x34a50000 | LOW(addr)); /* ori a1, a1, LO(fdt_addr) */
} else {
/* legacy boot protocol */
addr = ENVP_ADDR + 64;
stl_p(p++, 0x3c040000 | HIGH(addr)); /* lui a0, HI(envp_addr) */
stl_p(p++, 0x34840000 | LOW(addr)); /* ori a0, a0, LO(envp_addr) */
addr = rp->bootinfo.ram_size;
stl_p(p++, 0x3c050000 | HIGH(addr)); /* lui a1, HI(ram_size) */
stl_p(p++, 0x34a50000 | LOW(addr)); /* ori a1, a1, LO(ram_size) */
addr = rp->bootinfo.highmem_size;
stl_p(p++, 0x3c060000 | HIGH(addr)); /* lui a2, HI(highmem) */
stl_p(p++, 0x34c60000 | LOW(addr)); /* ori a2, a2, LO(highmem) */
stl_p(p++, 0x24070000); /* addiu a3, zero, 0 */
}
/* Jump to kernel code */
addr = kernel_entry;
stl_p(p++, 0x3c1f0000 | HIGH(addr)); /* lui ra, HI(kentry) */
stl_p(p++, 0x37ff0000 | LOW(addr)); /* ori ra, ra, LO(kentry) */
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);
}
#define KERNEL_CMD_MAX_LEN 1024
#define STDOUT_PATH_MAX_LEN 64
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;
GString *kernel_cmd;
char stdout_path[STDOUT_PATH_MAX_LEN];
int rc, prom_index = 0;
/* Load the kernel. */
if (!rp->bootinfo.kernel_filename) {
FATAL("Kernel image must be specified\n");
}
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, EM_MIPS, 1, 0) < 0) {
FATAL("qemu: could not load kernel '%s'\n",
rp->bootinfo.kernel_filename);
}
rp->fdt_addr = kernel_high;
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) {
FATAL("qemu: memory too small for initial ram disk '%s'\n",
rp->bootinfo.initrd_filename);
}
initrd_size = load_image_targphys(rp->bootinfo.initrd_filename,
initrd_offset,
rp->bootinfo.ram_size -
initrd_offset);
}
if (initrd_size == (target_ulong) -1) {
FATAL("qemu: could not load initial ram disk '%s'\n",
rp->bootinfo.initrd_filename);
}
}
snprintf(stdout_path, STDOUT_PATH_MAX_LEN, "/%s@%" HWADDR_PRIx,
devmap[RANCHU_GOLDFISH_TTY].qemu_name,
devmap[RANCHU_GOLDFISH_TTY].base +
devmap[RANCHU_GOLDFISH_TTY].size *
(MAX_GF_TTYS - 1));
qemu_fdt_add_subnode(rp->fdt, "/chosen");
rc = qemu_fdt_setprop_string(rp->fdt, "/chosen", "stdout-path",
stdout_path);
if (rc < 0) {
FATAL("couldn't set /chosen/stdout-path\n");
}
kernel_cmd = g_string_new(rp->bootinfo.kernel_cmdline);
g_string_append(kernel_cmd, " ieee754=relaxed");
if (rp->bootinfo.use_uhi) {
g_string_append(kernel_cmd, " mipsr2emu");
rc = qemu_fdt_setprop_string(rp->fdt, "/chosen", "bootargs",
kernel_cmd->str);
if (rc < 0) {
FATAL("couldn't set /chosen/bootargs\n");
}
rc = qemu_fdt_setprop_cell(rp->fdt, "/chosen", "linux,initrd-start",
initrd_offset);
if (rc < 0) {
FATAL("couldn't set /chosen/linux,initrd-start\n");
}
rc = qemu_fdt_setprop_cell(rp->fdt, "/chosen", "linux,initrd-end",
initrd_offset + (long int)initrd_size);
if (rc < 0) {
FATAL("couldn't set /chosen/linux,initrd-end\n");
}
} else {
g_string_append(kernel_cmd, " earlycon");
if (initrd_size > 0) {
g_string_append_printf(kernel_cmd,
" rd_start=0x%" HWADDR_PRIx " rd_size=%li",
(hwaddr)cpu_mips_phys_to_kseg0(NULL, initrd_offset),
(long int)initrd_size);
}
/* Setup Highmem */
if (rp->bootinfo.highmem_size) {
g_string_append_printf(kernel_cmd,
" mem=%um@0x0 mem=%" PRIu64 "m@0x%x",
GOLDFISH_IO_SPACE / (1024 * 1024),
rp->bootinfo.highmem_size / (1024 * 1024),
HIGHMEM_OFFSET);
}
/* 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_cmd->str);
prom_set(prom_buf, prom_index++, NULL);
rom_add_blob_fixed("prom", prom_buf, prom_size,
cpu_mips_kseg0_to_phys(NULL, ENVP_ADDR));
}
/* Put the DTB into the memory map as a ROM image: this will ensure
* the DTB is copied again upon reset, even if addr points into RAM.
*/
rom_add_blob_fixed("dtb", rp->fdt, rp->fdt_size, rp->fdt_addr);
qemu_fdt_dumpdtb(rp->fdt, rp->fdt_size);
g_string_free(kernel_cmd, true);
return kernel_entry;
}
static void goldfish_reset_io_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
switch (val) {
case GOLDFISH_PM_CMD_GORESET:
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
break;
case GOLDFISH_PM_CMD_GOSHUTDOWN:
qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
break;
default:
LOGI("Unknown command %llx\n", (unsigned long long)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, bool is_virtio, bool to_attach)
*
* In case of interrupt controller dev->irq stores
* dt handle previously referenced as interrupt-parent
*
* @s - RanchuState pointer
* @fdt - Place where DT nodes will be stored
* @dev - Device information (base, irq, name)
* @num_devices - If you want to allocate multiple continuous device mappings
* @is_virtio - Virtio devices should be added in revers order
* @to_attach - Controls whether we should try to attach this device to a sysbus
*/
static void create_device(RanchuState *s, void *fdt, DevMapEntry *dev,
int num_devices, bool is_virtio, bool to_attach)
{
int i, j;
for (i = 0; i < num_devices; i++) {
hwaddr base = dev->base + i * dev->size;
int dt_compat_sz = 0;
char *nodename = g_strdup_printf("/%s@%" PRIx64, dev->dt_name, base);
qemu_fdt_add_subnode(fdt, nodename);
dt_compat_sz = 0;
for (j = 0; j < dev->dt_num_compatible; j++) {
dt_compat_sz += strlen(dev->dt_compatible + dt_compat_sz) + 1;
}
qemu_fdt_setprop(fdt, nodename, "compatible", dev->dt_compatible,
dt_compat_sz);
qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", 1, base, 2,
dev->size);
if (dev->irq > 0) {
if (s->cps == NULL) {
qemu_fdt_setprop_cells(fdt, nodename, "interrupts",
dev->irq + i);
} else {
qemu_fdt_setprop_cells(fdt, nodename, "interrupts", 0,
dev->irq + i, 4);
}
if (s->cps == NULL) {
qemu_fdt_setprop_cell(fdt, nodename, "interrupt-parent",
devmap[RANCHU_GOLDFISH_PIC].dt_phandle);
} else {
qemu_fdt_setprop_cell(fdt, nodename, "interrupt-parent",
devmap[RANCHU_MIPS_GIC].dt_phandle);
}
}
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.
*/
if (to_attach) {
sysbus_create_simple(dev->qemu_name,
dev->base + (num_devices - i - 1) * dev->size,
get_irq(s, dev->irq + num_devices - i - 1));
}
} else if (to_attach) {
sysbus_create_simple(dev->qemu_name, base,
get_irq(s, dev->irq + i));
}
g_free(nodename);
}
}
/* create_intc_device(void *fdt, DevMapEntry *dev, DevMapEntry *parent)
*
* Create interrupt device DT node
*
* @fdt - Place where DT node will be stored
* @dev - Interrupt Device information (base, irq, name)
* @parent - If not NULL, it represents a parent Interrupt controller
*/
static void create_intc_device(void *fdt, DevMapEntry *dev,
DevMapEntry *parent)
{
int i, dt_compat_sz = 0;
char *nodename;
if (dev->base != 0) {
nodename = g_strdup_printf("/%s@%" PRIx64, dev->dt_name, dev->base);
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", 1, dev->base, 2,
dev->size);
} else {
nodename = g_strdup_printf("/%s", dev->dt_name);
qemu_fdt_add_subnode(fdt, nodename);
}
qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0);
qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 0x1);
dev->dt_phandle = qemu_fdt_alloc_phandle(fdt);
qemu_fdt_setprop_cell(fdt, nodename, "phandle", dev->dt_phandle);
for (i = 0; i < dev->dt_num_compatible; i++) {
dt_compat_sz += strlen(dev->dt_compatible + dt_compat_sz) + 1;
}
qemu_fdt_setprop(fdt, nodename, "compatible",
dev->dt_compatible, dt_compat_sz);
if (parent) {
qemu_fdt_setprop_cell(fdt, nodename, "interrupt-parent",
parent->dt_phandle);
}
if (dev->irq > 0) {
qemu_fdt_setprop_cells(fdt, nodename, "interrupts", dev->irq);
}
g_free(nodename);
}
/* create_gic_device(void *fdt, DevMapEntry *dev)
*
* Create GIC device DT node
*
* @fdt - Place where DT node will be stored
* @dev - Interrupt Device information (base, irq, name)
*/
static void create_gic_device(void *fdt, DevMapEntry *dev)
{
int i, dt_compat_sz = 0;
char *nodename;
nodename = g_strdup_printf("/%s@%" PRIx64, dev->dt_name, dev->base);
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", 1,
dev->base, 2, dev->size);
qemu_fdt_setprop(fdt, nodename, "interrupt-controller", NULL, 0);
qemu_fdt_setprop_cell(fdt, nodename, "#interrupt-cells", 0x3);
dev->dt_phandle = qemu_fdt_alloc_phandle(fdt);
qemu_fdt_setprop_cell(fdt, nodename, "phandle", dev->dt_phandle);
for (i = 0; i < dev->dt_num_compatible; i++) {
dt_compat_sz += strlen(dev->dt_compatible + dt_compat_sz) + 1;
}
qemu_fdt_setprop(fdt, nodename, "compatible",
dev->dt_compatible, dt_compat_sz);
g_free(nodename);
/* GIC Timer node */
nodename = g_strdup_printf("/%s@%" PRIx64 "/timer",
dev->dt_name, dev->base);
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop(fdt, nodename, "compatible",
"mti,gic-timer", strlen("mti,gic-timer") + 1);
qemu_fdt_setprop_cells(fdt, nodename, "interrupts", 1, 1, 0);
qemu_fdt_setprop_cells(fdt, nodename, "clock-frequency", 100000000);
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/shutdown
*
* @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;
int i, dt_compat_sz = 0;
uint32_t pm_regs;
char *nodename;
nodename = g_strdup_printf("/%s@%" PRIx64, dev->dt_name, base);
qemu_fdt_add_subnode(fdt, nodename);
pm_regs = qemu_fdt_alloc_phandle(fdt);
qemu_fdt_setprop_cell(fdt, nodename, "phandle", pm_regs);
for (i = 0; i < dev->dt_num_compatible; i++) {
dt_compat_sz += strlen(dev->dt_compatible + dt_compat_sz) + 1;
}
qemu_fdt_setprop(fdt, nodename, "compatible",
dev->dt_compatible, dt_compat_sz);
qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", 1, base, 2, dev->size);
g_free(nodename);
nodename = g_strdup_printf("/%s/reboot", dev->dt_name);
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop_string(fdt, nodename, "compatible", "syscon-reboot");
qemu_fdt_setprop_cell(fdt, nodename, "regmap", pm_regs);
qemu_fdt_setprop_sized_cells(fdt, nodename, "offset", 1, 0x00);
qemu_fdt_setprop_sized_cells(fdt, nodename, "mask", 1,
GOLDFISH_PM_CMD_GORESET);
g_free(nodename);
nodename = g_strdup_printf("/%s/poweroff", dev->dt_name);
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop_string(fdt, nodename, "compatible", "syscon-poweroff");
qemu_fdt_setprop_cell(fdt, nodename, "regmap", pm_regs);
qemu_fdt_setprop_sized_cells(fdt, nodename, "offset", 1, 0x00);
qemu_fdt_setprop_sized_cells(fdt, nodename, "mask", 1,
GOLDFISH_PM_CMD_GOSHUTDOWN);
g_free(nodename);
}
static void mips_ranchu_init(MachineState *machine)
{
int i, fdt_size;
void *fdt;
int64_t kernel_entry;
char *nodename;
DeviceState *dev = qdev_create(NULL, TYPE_MIPS_RANCHU);
RanchuState *s = MIPS_RANCHU(dev);
ranchu_params *rp = NULL;
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);
rp = g_new0(ranchu_params, 1);
s->rp = rp;
rp->bootinfo.kernel_filename = strdup(machine->kernel_filename);
rp->bootinfo.kernel_cmdline = strdup(machine->kernel_cmdline);
rp->bootinfo.initrd_filename = strdup(machine->initrd_filename);
/* Are we using UHI or legacy boot protocol? */
boot_protocol_detect(rp, rp->bootinfo.kernel_filename);
/* init CPUs */
create_cpu(s, machine->cpu_type);
rp->cpu = MIPS_CPU(first_cpu);
#if !defined(TARGET_MIPS64)
if (ram_size > (MAX_RAM_SIZE_MB << 20)) {
LOGI("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;
}
#endif
fdt = create_device_tree(&fdt_size);
if (!fdt) {
FATAL("create_device_tree() failed");
}
rp->fdt = fdt;
rp->fdt_size = fdt_size;
memory_region_init_ram_nomigrate(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,
devmap[RANCHU_GOLDFISH_RESET].qemu_name,
devmap[RANCHU_GOLDFISH_RESET].size);
memory_region_add_subregion(get_system_memory(),
devmap[RANCHU_GOLDFISH_RESET].base, iomem);
memory_region_init_ram_nomigrate(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_nomigrate(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.ram_size = MIN(ram_size, GOLDFISH_IO_SPACE);
qemu_fdt_setprop_string(fdt, "/", "model", "ranchu");
if (!rp->bootinfo.use_uhi) {
qemu_fdt_setprop_string(fdt, "/", "compatible", "mti,goldfish");
} else {
qemu_fdt_setprop_string(fdt, "/", "compatible", "mti,ranchu");
}
qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x1);
qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2);
/* Firmware node */
qemu_fdt_add_subnode(fdt, "/firmware");
nodename = g_strdup_printf("/firmware/android");
qemu_fdt_add_subnode(fdt, nodename);
qemu_fdt_setprop_string(fdt, nodename, "compatible", "android,firmware");
qemu_fdt_setprop_string(fdt, nodename, "hardware", "ranchu");
qemu_fdt_setprop_string(fdt, nodename, "revision", MIPS_RANCHU_REV);
g_free(nodename);
/* fstab node */
if (device_tree_setup_func) {
device_tree_setup_func(fdt);
}
/* 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);
}
if (s->gfpic) {
/* Create CPU Interrupt controller node in dt */
create_intc_device(fdt, &devmap[RANCHU_MIPS_CPU_INTC], NULL);
/* Create goldfish_pic controller node in dt */
create_intc_device(fdt, &devmap[RANCHU_GOLDFISH_PIC],
&devmap[RANCHU_MIPS_CPU_INTC]);
} else {
/* Create MIPS Global Interrupt Controller node in dt */
create_gic_device(fdt, &devmap[RANCHU_MIPS_GIC]);
/* Create CPC node in DT */
create_device(s, fdt, &devmap[RANCHU_MIPS_CPC], 1, true, false);
}
/* Make sure the first 3 serial ports are associated with a device. */
for (i = 0; i < 3; i++) {
if (!serial_hds[i]) {
char label[32];
snprintf(label, sizeof(label), "serial%d", i);
serial_hds[i] = qemu_chr_new(label, "null");
}
}
/* Create 3 Goldfish TTYs */
create_device(s, fdt, &devmap[RANCHU_GOLDFISH_TTY], MAX_GF_TTYS,
true, true);
/* Other Goldfish Platform devices */
for (i = RANCHU_GOLDFISH_SYNC; i >= RANCHU_GOLDFISH_RTC ; i--) {
create_device(s, fdt, &devmap[i], 1, false, true);
}
create_pm_device(fdt, &devmap[RANCHU_GOLDFISH_RESET]);
/* Virtio MMIO devices */
create_device(s, fdt, &devmap[RANCHU_MMIO], VIRTIO_TRANSPORTS, true, true);
if (!rp->bootinfo.use_uhi) {
/* Create a fake Goldfish Timer node
* This is required for kernels < v4.4
*/
create_device(s, fdt, &devmap[RANCHU_GOLDFISH_TIMER], 1, true, false);
}
kernel_entry = android_load_kernel(rp);
write_bootloader(memory_region_get_ram_ptr(bios),
cpu_mips_phys_to_kseg0(NULL, 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 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)