memory_region_allocate_system_memory() API is going away, so replace it with memdev allocated MemoryRegion. The later is initialized by generic code, so board only needs to opt in to memdev scheme by providing MachineClass::default_ram_id and using MachineState::ram instead of manually initializing RAM memory region. Signed-off-by: Igor Mammedov <imammedo@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-Id: <20200219160953.13771-70-imammedo@redhat.com>
364 lines
12 KiB
C
364 lines
12 KiB
C
/*
|
|
* QEMU Leon3 System Emulator
|
|
*
|
|
* Copyright (c) 2010-2019 AdaCore
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/units.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu-common.h"
|
|
#include "cpu.h"
|
|
#include "hw/irq.h"
|
|
#include "qemu/timer.h"
|
|
#include "hw/ptimer.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "sysemu/qtest.h"
|
|
#include "sysemu/reset.h"
|
|
#include "hw/boards.h"
|
|
#include "hw/loader.h"
|
|
#include "elf.h"
|
|
#include "trace.h"
|
|
#include "exec/address-spaces.h"
|
|
|
|
#include "hw/sparc/grlib.h"
|
|
#include "hw/misc/grlib_ahb_apb_pnp.h"
|
|
|
|
/* Default system clock. */
|
|
#define CPU_CLK (40 * 1000 * 1000)
|
|
|
|
#define LEON3_PROM_FILENAME "u-boot.bin"
|
|
#define LEON3_PROM_OFFSET (0x00000000)
|
|
#define LEON3_RAM_OFFSET (0x40000000)
|
|
|
|
#define MAX_PILS 16
|
|
|
|
#define LEON3_UART_OFFSET (0x80000100)
|
|
#define LEON3_UART_IRQ (3)
|
|
|
|
#define LEON3_IRQMP_OFFSET (0x80000200)
|
|
|
|
#define LEON3_TIMER_OFFSET (0x80000300)
|
|
#define LEON3_TIMER_IRQ (6)
|
|
#define LEON3_TIMER_COUNT (2)
|
|
|
|
#define LEON3_APB_PNP_OFFSET (0x800FF000)
|
|
#define LEON3_AHB_PNP_OFFSET (0xFFFFF000)
|
|
|
|
typedef struct ResetData {
|
|
SPARCCPU *cpu;
|
|
uint32_t entry; /* save kernel entry in case of reset */
|
|
target_ulong sp; /* initial stack pointer */
|
|
} ResetData;
|
|
|
|
static uint32_t *gen_store_u32(uint32_t *code, hwaddr addr, uint32_t val)
|
|
{
|
|
stl_p(code++, 0x82100000); /* mov %g0, %g1 */
|
|
stl_p(code++, 0x84100000); /* mov %g0, %g2 */
|
|
stl_p(code++, 0x03000000 +
|
|
extract32(addr, 10, 22));
|
|
/* sethi %hi(addr), %g1 */
|
|
stl_p(code++, 0x82106000 +
|
|
extract32(addr, 0, 10));
|
|
/* or %g1, addr, %g1 */
|
|
stl_p(code++, 0x05000000 +
|
|
extract32(val, 10, 22));
|
|
/* sethi %hi(val), %g2 */
|
|
stl_p(code++, 0x8410a000 +
|
|
extract32(val, 0, 10));
|
|
/* or %g2, val, %g2 */
|
|
stl_p(code++, 0xc4204000); /* st %g2, [ %g1 ] */
|
|
|
|
return code;
|
|
}
|
|
|
|
/*
|
|
* When loading a kernel in RAM the machine is expected to be in a different
|
|
* state (eg: initialized by the bootloader). This little code reproduces
|
|
* this behavior.
|
|
*/
|
|
static void write_bootloader(CPUSPARCState *env, uint8_t *base,
|
|
hwaddr kernel_addr)
|
|
{
|
|
uint32_t *p = (uint32_t *) base;
|
|
|
|
/* Initialize the UARTs */
|
|
/* *UART_CONTROL = UART_RECEIVE_ENABLE | UART_TRANSMIT_ENABLE; */
|
|
p = gen_store_u32(p, 0x80000108, 3);
|
|
|
|
/* Initialize the TIMER 0 */
|
|
/* *GPTIMER_SCALER_RELOAD = 40 - 1; */
|
|
p = gen_store_u32(p, 0x80000304, 39);
|
|
/* *GPTIMER0_COUNTER_RELOAD = 0xFFFE; */
|
|
p = gen_store_u32(p, 0x80000314, 0xFFFFFFFE);
|
|
/* *GPTIMER0_CONFIG = GPTIMER_ENABLE | GPTIMER_RESTART; */
|
|
p = gen_store_u32(p, 0x80000318, 3);
|
|
|
|
/* JUMP to the entry point */
|
|
stl_p(p++, 0x82100000); /* mov %g0, %g1 */
|
|
stl_p(p++, 0x03000000 + extract32(kernel_addr, 10, 22));
|
|
/* sethi %hi(kernel_addr), %g1 */
|
|
stl_p(p++, 0x82106000 + extract32(kernel_addr, 0, 10));
|
|
/* or kernel_addr, %g1 */
|
|
stl_p(p++, 0x81c04000); /* jmp %g1 */
|
|
stl_p(p++, 0x01000000); /* nop */
|
|
}
|
|
|
|
static void main_cpu_reset(void *opaque)
|
|
{
|
|
ResetData *s = (ResetData *)opaque;
|
|
CPUState *cpu = CPU(s->cpu);
|
|
CPUSPARCState *env = &s->cpu->env;
|
|
|
|
cpu_reset(cpu);
|
|
|
|
cpu->halted = 0;
|
|
env->pc = s->entry;
|
|
env->npc = s->entry + 4;
|
|
env->regbase[6] = s->sp;
|
|
}
|
|
|
|
void leon3_irq_ack(void *irq_manager, int intno)
|
|
{
|
|
grlib_irqmp_ack((DeviceState *)irq_manager, intno);
|
|
}
|
|
|
|
/*
|
|
* This device assumes that the incoming 'level' value on the
|
|
* qemu_irq is the interrupt number, not just a simple 0/1 level.
|
|
*/
|
|
static void leon3_set_pil_in(void *opaque, int n, int level)
|
|
{
|
|
CPUSPARCState *env = opaque;
|
|
uint32_t pil_in = level;
|
|
CPUState *cs;
|
|
|
|
assert(env != NULL);
|
|
|
|
env->pil_in = pil_in;
|
|
|
|
if (env->pil_in && (env->interrupt_index == 0 ||
|
|
(env->interrupt_index & ~15) == TT_EXTINT)) {
|
|
unsigned int i;
|
|
|
|
for (i = 15; i > 0; i--) {
|
|
if (env->pil_in & (1 << i)) {
|
|
int old_interrupt = env->interrupt_index;
|
|
|
|
env->interrupt_index = TT_EXTINT | i;
|
|
if (old_interrupt != env->interrupt_index) {
|
|
cs = env_cpu(env);
|
|
trace_leon3_set_irq(i);
|
|
cpu_interrupt(cs, CPU_INTERRUPT_HARD);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} else if (!env->pil_in && (env->interrupt_index & ~15) == TT_EXTINT) {
|
|
cs = env_cpu(env);
|
|
trace_leon3_reset_irq(env->interrupt_index & 15);
|
|
env->interrupt_index = 0;
|
|
cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
|
|
}
|
|
}
|
|
|
|
static void leon3_generic_hw_init(MachineState *machine)
|
|
{
|
|
ram_addr_t ram_size = machine->ram_size;
|
|
const char *kernel_filename = machine->kernel_filename;
|
|
SPARCCPU *cpu;
|
|
CPUSPARCState *env;
|
|
MemoryRegion *address_space_mem = get_system_memory();
|
|
MemoryRegion *prom = g_new(MemoryRegion, 1);
|
|
int ret;
|
|
char *filename;
|
|
qemu_irq *cpu_irqs = NULL;
|
|
int bios_size;
|
|
int prom_size;
|
|
ResetData *reset_info;
|
|
DeviceState *dev;
|
|
int i;
|
|
AHBPnp *ahb_pnp;
|
|
APBPnp *apb_pnp;
|
|
|
|
/* Init CPU */
|
|
cpu = SPARC_CPU(cpu_create(machine->cpu_type));
|
|
env = &cpu->env;
|
|
|
|
cpu_sparc_set_id(env, 0);
|
|
|
|
/* Reset data */
|
|
reset_info = g_malloc0(sizeof(ResetData));
|
|
reset_info->cpu = cpu;
|
|
reset_info->sp = LEON3_RAM_OFFSET + ram_size;
|
|
qemu_register_reset(main_cpu_reset, reset_info);
|
|
|
|
ahb_pnp = GRLIB_AHB_PNP(object_new(TYPE_GRLIB_AHB_PNP));
|
|
object_property_set_bool(OBJECT(ahb_pnp), true, "realized", &error_fatal);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(ahb_pnp), 0, LEON3_AHB_PNP_OFFSET);
|
|
grlib_ahb_pnp_add_entry(ahb_pnp, 0, 0, GRLIB_VENDOR_GAISLER,
|
|
GRLIB_LEON3_DEV, GRLIB_AHB_MASTER,
|
|
GRLIB_CPU_AREA);
|
|
|
|
apb_pnp = GRLIB_APB_PNP(object_new(TYPE_GRLIB_APB_PNP));
|
|
object_property_set_bool(OBJECT(apb_pnp), true, "realized", &error_fatal);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(apb_pnp), 0, LEON3_APB_PNP_OFFSET);
|
|
grlib_ahb_pnp_add_entry(ahb_pnp, LEON3_APB_PNP_OFFSET, 0xFFF,
|
|
GRLIB_VENDOR_GAISLER, GRLIB_APBMST_DEV,
|
|
GRLIB_AHB_SLAVE, GRLIB_AHBMEM_AREA);
|
|
|
|
/* Allocate IRQ manager */
|
|
dev = qdev_create(NULL, TYPE_GRLIB_IRQMP);
|
|
qdev_init_gpio_in_named_with_opaque(DEVICE(cpu), leon3_set_pil_in,
|
|
env, "pil", 1);
|
|
qdev_connect_gpio_out_named(dev, "grlib-irq", 0,
|
|
qdev_get_gpio_in_named(DEVICE(cpu), "pil", 0));
|
|
qdev_init_nofail(dev);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, LEON3_IRQMP_OFFSET);
|
|
env->irq_manager = dev;
|
|
env->qemu_irq_ack = leon3_irq_manager;
|
|
cpu_irqs = qemu_allocate_irqs(grlib_irqmp_set_irq, dev, MAX_PILS);
|
|
grlib_apb_pnp_add_entry(apb_pnp, LEON3_IRQMP_OFFSET, 0xFFF,
|
|
GRLIB_VENDOR_GAISLER, GRLIB_IRQMP_DEV,
|
|
2, 0, GRLIB_APBIO_AREA);
|
|
|
|
/* Allocate RAM */
|
|
if (ram_size > 1 * GiB) {
|
|
error_report("Too much memory for this machine: %" PRId64 "MB,"
|
|
" maximum 1G",
|
|
ram_size / MiB);
|
|
exit(1);
|
|
}
|
|
|
|
memory_region_add_subregion(address_space_mem, LEON3_RAM_OFFSET,
|
|
machine->ram);
|
|
|
|
/* Allocate BIOS */
|
|
prom_size = 8 * MiB;
|
|
memory_region_init_ram(prom, NULL, "Leon3.bios", prom_size, &error_fatal);
|
|
memory_region_set_readonly(prom, true);
|
|
memory_region_add_subregion(address_space_mem, LEON3_PROM_OFFSET, prom);
|
|
|
|
/* Load boot prom */
|
|
if (bios_name == NULL) {
|
|
bios_name = LEON3_PROM_FILENAME;
|
|
}
|
|
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
|
|
|
|
if (filename) {
|
|
bios_size = get_image_size(filename);
|
|
} else {
|
|
bios_size = -1;
|
|
}
|
|
|
|
if (bios_size > prom_size) {
|
|
error_report("could not load prom '%s': file too big", filename);
|
|
exit(1);
|
|
}
|
|
|
|
if (bios_size > 0) {
|
|
ret = load_image_targphys(filename, LEON3_PROM_OFFSET, bios_size);
|
|
if (ret < 0 || ret > prom_size) {
|
|
error_report("could not load prom '%s'", filename);
|
|
exit(1);
|
|
}
|
|
} else if (kernel_filename == NULL && !qtest_enabled()) {
|
|
error_report("Can't read bios image '%s'", filename
|
|
? filename
|
|
: LEON3_PROM_FILENAME);
|
|
exit(1);
|
|
}
|
|
g_free(filename);
|
|
|
|
/* Can directly load an application. */
|
|
if (kernel_filename != NULL) {
|
|
long kernel_size;
|
|
uint64_t entry;
|
|
|
|
kernel_size = load_elf(kernel_filename, NULL, NULL, NULL,
|
|
&entry, NULL, NULL, NULL,
|
|
1 /* big endian */, EM_SPARC, 0, 0);
|
|
if (kernel_size < 0) {
|
|
kernel_size = load_uimage(kernel_filename, NULL, &entry,
|
|
NULL, NULL, NULL);
|
|
}
|
|
if (kernel_size < 0) {
|
|
error_report("could not load kernel '%s'", kernel_filename);
|
|
exit(1);
|
|
}
|
|
if (bios_size <= 0) {
|
|
/*
|
|
* If there is no bios/monitor just start the application but put
|
|
* the machine in an initialized state through a little
|
|
* bootloader.
|
|
*/
|
|
uint8_t *bootloader_entry;
|
|
|
|
bootloader_entry = memory_region_get_ram_ptr(prom);
|
|
write_bootloader(env, bootloader_entry, entry);
|
|
env->pc = LEON3_PROM_OFFSET;
|
|
env->npc = LEON3_PROM_OFFSET + 4;
|
|
reset_info->entry = LEON3_PROM_OFFSET;
|
|
}
|
|
}
|
|
|
|
/* Allocate timers */
|
|
dev = qdev_create(NULL, TYPE_GRLIB_GPTIMER);
|
|
qdev_prop_set_uint32(dev, "nr-timers", LEON3_TIMER_COUNT);
|
|
qdev_prop_set_uint32(dev, "frequency", CPU_CLK);
|
|
qdev_prop_set_uint32(dev, "irq-line", LEON3_TIMER_IRQ);
|
|
qdev_init_nofail(dev);
|
|
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, LEON3_TIMER_OFFSET);
|
|
for (i = 0; i < LEON3_TIMER_COUNT; i++) {
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(dev), i,
|
|
cpu_irqs[LEON3_TIMER_IRQ + i]);
|
|
}
|
|
|
|
grlib_apb_pnp_add_entry(apb_pnp, LEON3_TIMER_OFFSET, 0xFFF,
|
|
GRLIB_VENDOR_GAISLER, GRLIB_GPTIMER_DEV,
|
|
0, LEON3_TIMER_IRQ, GRLIB_APBIO_AREA);
|
|
|
|
/* Allocate uart */
|
|
if (serial_hd(0)) {
|
|
dev = qdev_create(NULL, TYPE_GRLIB_APB_UART);
|
|
qdev_prop_set_chr(dev, "chrdev", serial_hd(0));
|
|
qdev_init_nofail(dev);
|
|
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, LEON3_UART_OFFSET);
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, cpu_irqs[LEON3_UART_IRQ]);
|
|
grlib_apb_pnp_add_entry(apb_pnp, LEON3_UART_OFFSET, 0xFFF,
|
|
GRLIB_VENDOR_GAISLER, GRLIB_APBUART_DEV, 1,
|
|
LEON3_UART_IRQ, GRLIB_APBIO_AREA);
|
|
}
|
|
}
|
|
|
|
static void leon3_generic_machine_init(MachineClass *mc)
|
|
{
|
|
mc->desc = "Leon-3 generic";
|
|
mc->init = leon3_generic_hw_init;
|
|
mc->default_cpu_type = SPARC_CPU_TYPE_NAME("LEON3");
|
|
mc->default_ram_id = "leon3.ram";
|
|
}
|
|
|
|
DEFINE_MACHINE("leon3_generic", leon3_generic_machine_init)
|