mips_ranchu.c: Implement reboot and power-off sequences
In order to implement functional reboot sequence for MIPS Ranchu emulator,
small bootloader was written and it is placed in a read-only memory
region represented as BIOS. Its responsibility is to prepare the boot
arguments a0-a3 with appropriate values before jumping to the kernel entry point.
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
The virtual power management device is located at 0x1f00d000 and it
is responsible for responding to kernel reboot and power-off triggers.
In case of a reboot sys_req() kernel will trigger an event coded
with value 0x42 into pm device register, which in turn should trigger
qemu_system_reset_request() following by a cpu reset callback.
Afterwards, bootloader is executed from the default address 0xbfc00000.
In case of a shutdown request (it can be issued with "reboot -p" from
the guest) kernel will send command coded with 0x43 which will result
in a call to qemu_system_shutdown_request() thus effectively exiting
the emulator.
Change-Id: Iab6694794ddfd829a9825b4374a2901d4d113036
Signed-off-by: Goran Ferenc <goran.ferenc@imgtec.com>
Signed-off-by: Miodrag Dinic <miodrag.dinic@imgtec.com>
Fix Mingw build.
The compiler was complaining about the printf() parameter type.
Change-Id: Ia1759a2d52f275acf62bf30d126fb3b0ab1050ea
diff --git a/hw/mips/mips_ranchu.c b/hw/mips/mips_ranchu.c
index 02d1efd..75cabd6 100644
--- a/hw/mips/mips_ranchu.c
+++ b/hw/mips/mips_ranchu.c
@@ -4,6 +4,7 @@
#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"
@@ -28,6 +29,17 @@
#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
@@ -55,6 +67,7 @@
RANCHU_GOLDFISH_PIPE,
RANCHU_GOLDFISH_AUDIO,
RANCHU_GOLDFISH_SYNC,
+ RANCHU_GOLDFISH_RESET,
RANCHU_MMIO,
};
@@ -94,17 +107,26 @@
"goldfish_audio", "goldfish_audio", "generic,goldfish-audio" },
[RANCHU_GOLDFISH_SYNC] = { GOLDFISH_IO_SPACE + 0x0D000, 0x0100, 11,
"goldfish_sync", "goldfish_sync", "generic,goldfish-audio" },
+ [RANCHU_GOLDFISH_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 machine_params {
- uint64_t kernel_entry;
- uint64_t cmdline_ptr;
+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)
@@ -113,107 +135,190 @@
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)
+/* 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;
- unsigned int cmdline;
+ uint32_t *prom_buf;
+ long prom_size;
+ int prom_index = 0;
/* Load the kernel. */
- if (!kernel_filename) {
+ if (!rp->bootinfo.kernel_filename) {
fprintf(stderr, "Kernel image must be specified\n");
exit(1);
}
- if (load_elf(kernel_filename, cpu_mips_kseg0_to_phys, NULL,
+ 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) {
- fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename);
+ 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 (fdt) {
- cpu_physical_memory_write(kernel_high, fdt, fdt_size);
- kernel_high += fdt_size;
+ if (rp->fdt) {
+ cpu_physical_memory_write(kernel_high, rp->fdt, rp->fdt_size);
+ kernel_high += rp->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 (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 > ram_size) {
+ if (initrd_offset + initrd_size > rp->bootinfo.ram_size) {
fprintf(stderr,
"qemu: memory too small for initial ram disk '%s'\n",
- initrd_filename);
+ rp->bootinfo.initrd_filename);
exit(1);
}
- initrd_size = load_image_targphys(initrd_filename,
- initrd_offset,
- ram_size - initrd_offset);
+ 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",
- initrd_filename);
+ rp->bootinfo.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,
+ rp->bootinfo.kernel_cmdline,
(hwaddr)PHYS_TO_VIRT(initrd_offset),
(long int)initrd_size);
else
- strcpy (kernel_cmd, kernel_cmdline);
+ strcpy (kernel_cmd, rp->bootinfo.kernel_cmdline);
/* Setup Highmem */
char kernel_cmd2[1024];
- if (ranchu_params.highmem_size) {
+ if (rp->bootinfo.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),
+ rp->bootinfo.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);
+ /* Prepare the environment arguments table */
+ prom_size = ENVP_NB_ENTRIES * (sizeof(int32_t) + ENVP_ENTRY_SIZE);
+ prom_buf = g_malloc(prom_size);
- ranchu_params.cmdline_ptr = PHYS_TO_VIRT(cmdline);
+ 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 %llx\n",
+ __func__, __LINE__, (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, int is_virtio)
*
@@ -264,6 +369,27 @@
}
}
+/* 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 mips_ranchu_init(MachineState *machine)
{
MIPSCPU *cpu;
@@ -271,12 +397,16 @@
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) {
@@ -292,9 +422,11 @@
exit(1);
}
+ rp = g_new0(ranchu_params, 1);
+
env = &cpu->env;
- qemu_register_reset(main_cpu_reset, &ranchu_params);
+ qemu_register_reset(main_cpu_reset, rp);
if (ram_size > (MAX_RAM_SIZE_MB << 20)) {
fprintf(stderr, "qemu: Too much memory for this machine. "
@@ -314,7 +446,14 @@
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);
+ memory_region_init_io(iomem, NULL, &goldfish_reset_io_ops, NULL, "goldfish-reset", 0x0100);
+ memory_region_add_subregion(get_system_memory(), devmap[RANCHU_GOLDFISH_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) {
@@ -322,10 +461,16 @@
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;
+ rp->bootinfo.highmem_size = ram_size - GOLDFISH_IO_SPACE;
}
- ranchu_params.cpu = cpu;
+ 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(cpu);
cpu_mips_clock_init(cpu);
@@ -377,16 +522,17 @@
create_device(fdt, &devmap[i], goldfish_pic, 1, 0);
}
+ create_pm_device(fdt, &devmap[RANCHU_GOLDFISH_RESET]);
+
/* 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);
+ 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)