diff options
Diffstat (limited to 'hw/sparc')
-rw-r--r-- | hw/sparc/Kconfig | 29 | ||||
-rw-r--r-- | hw/sparc/leon3.c | 390 | ||||
-rw-r--r-- | hw/sparc/meson.build | 6 | ||||
-rw-r--r-- | hw/sparc/sun4m.c | 1494 | ||||
-rw-r--r-- | hw/sparc/sun4m_iommu.c | 412 | ||||
-rw-r--r-- | hw/sparc/trace-events | 21 | ||||
-rw-r--r-- | hw/sparc/trace.h | 1 |
7 files changed, 2353 insertions, 0 deletions
diff --git a/hw/sparc/Kconfig b/hw/sparc/Kconfig new file mode 100644 index 000000000..79d58beb7 --- /dev/null +++ b/hw/sparc/Kconfig @@ -0,0 +1,29 @@ +config SUN4M + bool + imply TCX + imply CG3 + select CS4231 + select ECCMEMCTL + select EMPTY_SLOT + select UNIMP + select ESCC + select ESP + select FDC_SYSBUS + select SLAVIO + select LANCE + select M48T59 + select STP2000 + select CHRP_NVRAM + select OR_IRQ + +config LEON3 + bool + select GRLIB + +config GRLIB + bool + select PTIMER + +config SLAVIO + bool + select PTIMER diff --git a/hw/sparc/leon3.c b/hw/sparc/leon3.c new file mode 100644 index 000000000..7b4dec172 --- /dev/null +++ b/hw/sparc/leon3.c @@ -0,0 +1,390 @@ +/* + * 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 "qemu/datadir.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 "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 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; +} + +static void leon3_cache_control_int(CPUSPARCState *env) +{ + uint32_t state = 0; + + if (env->cache_control & CACHE_CTRL_IF) { + /* Instruction cache state */ + state = env->cache_control & CACHE_STATE_MASK; + if (state == CACHE_ENABLED) { + state = CACHE_FROZEN; + trace_int_helper_icache_freeze(); + } + + env->cache_control &= ~CACHE_STATE_MASK; + env->cache_control |= state; + } + + if (env->cache_control & CACHE_CTRL_DF) { + /* Data cache state */ + state = (env->cache_control >> 2) & CACHE_STATE_MASK; + if (state == CACHE_ENABLED) { + state = CACHE_FROZEN; + trace_int_helper_dcache_freeze(); + } + + env->cache_control &= ~(CACHE_STATE_MASK << 2); + env->cache_control |= (state << 2); + } +} + +static 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_irq_manager(CPUSPARCState *env, void *irq_manager, int intno) +{ + leon3_irq_ack(irq_manager, intno); + leon3_cache_control_int(env); +} + +static void leon3_generic_hw_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *bios_name = machine->firmware ?: LEON3_PROM_FILENAME; + 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; + int bios_size; + int prom_size; + ResetData *reset_info; + DeviceState *dev, *irqmpdev; + 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(qdev_new(TYPE_GRLIB_AHB_PNP)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(ahb_pnp), &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(qdev_new(TYPE_GRLIB_APB_PNP)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(apb_pnp), &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 */ + irqmpdev = qdev_new(TYPE_GRLIB_IRQMP); + qdev_init_gpio_in_named_with_opaque(DEVICE(cpu), leon3_set_pil_in, + env, "pil", 1); + qdev_connect_gpio_out_named(irqmpdev, "grlib-irq", 0, + qdev_get_gpio_in_named(DEVICE(cpu), "pil", 0)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(irqmpdev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(irqmpdev), 0, LEON3_IRQMP_OFFSET); + env->irq_manager = irqmpdev; + env->qemu_irq_ack = leon3_irq_manager; + 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_rom(prom, NULL, "Leon3.bios", prom_size, &error_fatal); + memory_region_add_subregion(address_space_mem, LEON3_PROM_OFFSET, prom); + + /* Load boot prom */ + 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_new(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); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + + 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, + qdev_get_gpio_in(irqmpdev, 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 */ + dev = qdev_new(TYPE_GRLIB_APB_UART); + qdev_prop_set_chr(dev, "chrdev", serial_hd(0)); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, LEON3_UART_OFFSET); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, + qdev_get_gpio_in(irqmpdev, 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) diff --git a/hw/sparc/meson.build b/hw/sparc/meson.build new file mode 100644 index 000000000..19c442c90 --- /dev/null +++ b/hw/sparc/meson.build @@ -0,0 +1,6 @@ +sparc_ss = ss.source_set() +sparc_ss.add(when: 'CONFIG_LEON3', if_true: files('leon3.c')) +sparc_ss.add(when: 'CONFIG_SUN4M', if_true: files('sun4m.c')) +sparc_ss.add(when: 'CONFIG_SUN4M', if_true: files('sun4m_iommu.c')) + +hw_arch += {'sparc': sparc_ss} diff --git a/hw/sparc/sun4m.c b/hw/sparc/sun4m.c new file mode 100644 index 000000000..7f3a7c002 --- /dev/null +++ b/hw/sparc/sun4m.c @@ -0,0 +1,1494 @@ +/* + * QEMU Sun4m & Sun4d & Sun4c System Emulator + * + * Copyright (c) 2003-2005 Fabrice Bellard + * + * 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 "qapi/error.h" +#include "qemu/datadir.h" +#include "qemu-common.h" +#include "cpu.h" +#include "hw/sysbus.h" +#include "qemu/error-report.h" +#include "qemu/timer.h" +#include "hw/sparc/sun4m_iommu.h" +#include "hw/rtc/m48t59.h" +#include "migration/vmstate.h" +#include "hw/sparc/sparc32_dma.h" +#include "hw/block/fdc.h" +#include "sysemu/reset.h" +#include "sysemu/runstate.h" +#include "sysemu/sysemu.h" +#include "net/net.h" +#include "hw/boards.h" +#include "hw/scsi/esp.h" +#include "hw/nvram/sun_nvram.h" +#include "hw/qdev-properties.h" +#include "hw/nvram/chrp_nvram.h" +#include "hw/nvram/fw_cfg.h" +#include "hw/char/escc.h" +#include "hw/misc/empty_slot.h" +#include "hw/misc/unimp.h" +#include "hw/irq.h" +#include "hw/or-irq.h" +#include "hw/loader.h" +#include "elf.h" +#include "trace.h" +#include "qom/object.h" + +/* + * Sun4m architecture was used in the following machines: + * + * SPARCserver 6xxMP/xx + * SPARCclassic (SPARCclassic Server)(SPARCstation LC) (4/15), + * SPARCclassic X (4/10) + * SPARCstation LX/ZX (4/30) + * SPARCstation Voyager + * SPARCstation 10/xx, SPARCserver 10/xx + * SPARCstation 5, SPARCserver 5 + * SPARCstation 20/xx, SPARCserver 20 + * SPARCstation 4 + * + * See for example: http://www.sunhelp.org/faq/sunref1.html + */ + +#define KERNEL_LOAD_ADDR 0x00004000 +#define CMDLINE_ADDR 0x007ff000 +#define INITRD_LOAD_ADDR 0x00800000 +#define PROM_SIZE_MAX (1 * MiB) +#define PROM_VADDR 0xffd00000 +#define PROM_FILENAME "openbios-sparc32" +#define CFG_ADDR 0xd00000510ULL +#define FW_CFG_SUN4M_DEPTH (FW_CFG_ARCH_LOCAL + 0x00) +#define FW_CFG_SUN4M_WIDTH (FW_CFG_ARCH_LOCAL + 0x01) +#define FW_CFG_SUN4M_HEIGHT (FW_CFG_ARCH_LOCAL + 0x02) + +#define MAX_CPUS 16 +#define MAX_PILS 16 +#define MAX_VSIMMS 4 + +#define ESCC_CLOCK 4915200 + +struct sun4m_hwdef { + hwaddr iommu_base, iommu_pad_base, iommu_pad_len, slavio_base; + hwaddr intctl_base, counter_base, nvram_base, ms_kb_base; + hwaddr serial_base, fd_base; + hwaddr afx_base, idreg_base, dma_base, esp_base, le_base; + hwaddr tcx_base, cs_base, apc_base, aux1_base, aux2_base; + hwaddr bpp_base, dbri_base, sx_base; + struct { + hwaddr reg_base, vram_base; + } vsimm[MAX_VSIMMS]; + hwaddr ecc_base; + uint64_t max_mem; + uint32_t ecc_version; + uint32_t iommu_version; + uint16_t machine_id; + uint8_t nvram_machine_id; +}; + +struct Sun4mMachineClass { + /*< private >*/ + MachineClass parent_obj; + /*< public >*/ + const struct sun4m_hwdef *hwdef; +}; +typedef struct Sun4mMachineClass Sun4mMachineClass; + +#define TYPE_SUN4M_MACHINE MACHINE_TYPE_NAME("sun4m-common") +DECLARE_CLASS_CHECKERS(Sun4mMachineClass, SUN4M_MACHINE, TYPE_SUN4M_MACHINE) + +const char *fw_cfg_arch_key_name(uint16_t key) +{ + static const struct { + uint16_t key; + const char *name; + } fw_cfg_arch_wellknown_keys[] = { + {FW_CFG_SUN4M_DEPTH, "depth"}, + {FW_CFG_SUN4M_WIDTH, "width"}, + {FW_CFG_SUN4M_HEIGHT, "height"}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(fw_cfg_arch_wellknown_keys); i++) { + if (fw_cfg_arch_wellknown_keys[i].key == key) { + return fw_cfg_arch_wellknown_keys[i].name; + } + } + return NULL; +} + +static void fw_cfg_boot_set(void *opaque, const char *boot_device, + Error **errp) +{ + fw_cfg_modify_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); +} + +static void nvram_init(Nvram *nvram, uint8_t *macaddr, + const char *cmdline, const char *boot_devices, + ram_addr_t RAM_size, uint32_t kernel_size, + int width, int height, int depth, + int nvram_machine_id, const char *arch) +{ + unsigned int i; + int sysp_end; + uint8_t image[0x1ff0]; + NvramClass *k = NVRAM_GET_CLASS(nvram); + + memset(image, '\0', sizeof(image)); + + /* OpenBIOS nvram variables partition */ + sysp_end = chrp_nvram_create_system_partition(image, 0, 0x1fd0); + + /* Free space partition */ + chrp_nvram_create_free_partition(&image[sysp_end], 0x1fd0 - sysp_end); + + Sun_init_header((struct Sun_nvram *)&image[0x1fd8], macaddr, + nvram_machine_id); + + for (i = 0; i < sizeof(image); i++) { + (k->write)(nvram, i, image[i]); + } +} + +static void cpu_kick_irq(SPARCCPU *cpu) +{ + CPUSPARCState *env = &cpu->env; + CPUState *cs = CPU(cpu); + + cs->halted = 0; + cpu_check_irqs(env); + qemu_cpu_kick(cs); +} + +static void cpu_set_irq(void *opaque, int irq, int level) +{ + SPARCCPU *cpu = opaque; + CPUSPARCState *env = &cpu->env; + + if (level) { + trace_sun4m_cpu_set_irq_raise(irq); + env->pil_in |= 1 << irq; + cpu_kick_irq(cpu); + } else { + trace_sun4m_cpu_set_irq_lower(irq); + env->pil_in &= ~(1 << irq); + cpu_check_irqs(env); + } +} + +static void dummy_cpu_set_irq(void *opaque, int irq, int level) +{ +} + +static void sun4m_cpu_reset(void *opaque) +{ + SPARCCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + + cpu_reset(cs); +} + +static void cpu_halt_signal(void *opaque, int irq, int level) +{ + if (level && current_cpu) { + cpu_interrupt(current_cpu, CPU_INTERRUPT_HALT); + } +} + +static uint64_t translate_kernel_address(void *opaque, uint64_t addr) +{ + return addr - 0xf0000000ULL; +} + +static unsigned long sun4m_load_kernel(const char *kernel_filename, + const char *initrd_filename, + ram_addr_t RAM_size, + uint32_t *initrd_size) +{ + int linux_boot; + unsigned int i; + long kernel_size; + uint8_t *ptr; + + linux_boot = (kernel_filename != NULL); + + kernel_size = 0; + if (linux_boot) { + int bswap_needed; + +#ifdef BSWAP_NEEDED + bswap_needed = 1; +#else + bswap_needed = 0; +#endif + kernel_size = load_elf(kernel_filename, NULL, + translate_kernel_address, NULL, + NULL, NULL, NULL, NULL, 1, EM_SPARC, 0, 0); + if (kernel_size < 0) + kernel_size = load_aout(kernel_filename, KERNEL_LOAD_ADDR, + RAM_size - KERNEL_LOAD_ADDR, bswap_needed, + TARGET_PAGE_SIZE); + if (kernel_size < 0) + kernel_size = load_image_targphys(kernel_filename, + KERNEL_LOAD_ADDR, + RAM_size - KERNEL_LOAD_ADDR); + if (kernel_size < 0) { + error_report("could not load kernel '%s'", kernel_filename); + exit(1); + } + + /* load initrd */ + *initrd_size = 0; + if (initrd_filename) { + *initrd_size = load_image_targphys(initrd_filename, + INITRD_LOAD_ADDR, + RAM_size - INITRD_LOAD_ADDR); + if ((int)*initrd_size < 0) { + error_report("could not load initial ram disk '%s'", + initrd_filename); + exit(1); + } + } + if (*initrd_size > 0) { + for (i = 0; i < 64 * TARGET_PAGE_SIZE; i += TARGET_PAGE_SIZE) { + ptr = rom_ptr(KERNEL_LOAD_ADDR + i, 24); + if (ptr && ldl_p(ptr) == 0x48647253) { /* HdrS */ + stl_p(ptr + 16, INITRD_LOAD_ADDR); + stl_p(ptr + 20, *initrd_size); + break; + } + } + } + } + return kernel_size; +} + +static void *iommu_init(hwaddr addr, uint32_t version, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_SUN4M_IOMMU); + qdev_prop_set_uint32(dev, "version", version); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_connect_irq(s, 0, irq); + sysbus_mmio_map(s, 0, addr); + + return s; +} + +static void *sparc32_dma_init(hwaddr dma_base, + hwaddr esp_base, qemu_irq espdma_irq, + hwaddr le_base, qemu_irq ledma_irq, NICInfo *nd) +{ + DeviceState *dma; + ESPDMADeviceState *espdma; + LEDMADeviceState *ledma; + SysBusESPState *esp; + SysBusPCNetState *lance; + + dma = qdev_new(TYPE_SPARC32_DMA); + espdma = SPARC32_ESPDMA_DEVICE(object_resolve_path_component( + OBJECT(dma), "espdma")); + sysbus_connect_irq(SYS_BUS_DEVICE(espdma), 0, espdma_irq); + + esp = SYSBUS_ESP(object_resolve_path_component(OBJECT(espdma), "esp")); + + ledma = SPARC32_LEDMA_DEVICE(object_resolve_path_component( + OBJECT(dma), "ledma")); + sysbus_connect_irq(SYS_BUS_DEVICE(ledma), 0, ledma_irq); + + lance = SYSBUS_PCNET(object_resolve_path_component( + OBJECT(ledma), "lance")); + qdev_set_nic_properties(DEVICE(lance), nd); + + sysbus_realize_and_unref(SYS_BUS_DEVICE(dma), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dma), 0, dma_base); + + sysbus_mmio_map(SYS_BUS_DEVICE(esp), 0, esp_base); + scsi_bus_legacy_handle_cmdline(&esp->esp.bus); + + sysbus_mmio_map(SYS_BUS_DEVICE(lance), 0, le_base); + + return dma; +} + +static DeviceState *slavio_intctl_init(hwaddr addr, + hwaddr addrg, + qemu_irq **parent_irq) +{ + DeviceState *dev; + SysBusDevice *s; + unsigned int i, j; + + dev = qdev_new("slavio_intctl"); + + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + for (i = 0; i < MAX_CPUS; i++) { + for (j = 0; j < MAX_PILS; j++) { + sysbus_connect_irq(s, i * MAX_PILS + j, parent_irq[i][j]); + } + } + sysbus_mmio_map(s, 0, addrg); + for (i = 0; i < MAX_CPUS; i++) { + sysbus_mmio_map(s, i + 1, addr + i * TARGET_PAGE_SIZE); + } + + return dev; +} + +#define SYS_TIMER_OFFSET 0x10000ULL +#define CPU_TIMER_OFFSET(cpu) (0x1000ULL * cpu) + +static void slavio_timer_init_all(hwaddr addr, qemu_irq master_irq, + qemu_irq *cpu_irqs, unsigned int num_cpus) +{ + DeviceState *dev; + SysBusDevice *s; + unsigned int i; + + dev = qdev_new("slavio_timer"); + qdev_prop_set_uint32(dev, "num_cpus", num_cpus); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_connect_irq(s, 0, master_irq); + sysbus_mmio_map(s, 0, addr + SYS_TIMER_OFFSET); + + for (i = 0; i < MAX_CPUS; i++) { + sysbus_mmio_map(s, i + 1, addr + (hwaddr)CPU_TIMER_OFFSET(i)); + sysbus_connect_irq(s, i + 1, cpu_irqs[i]); + } +} + +static qemu_irq slavio_system_powerdown; + +static void slavio_powerdown_req(Notifier *n, void *opaque) +{ + qemu_irq_raise(slavio_system_powerdown); +} + +static Notifier slavio_system_powerdown_notifier = { + .notify = slavio_powerdown_req +}; + +#define MISC_LEDS 0x01600000 +#define MISC_CFG 0x01800000 +#define MISC_DIAG 0x01a00000 +#define MISC_MDM 0x01b00000 +#define MISC_SYS 0x01f00000 + +static void slavio_misc_init(hwaddr base, + hwaddr aux1_base, + hwaddr aux2_base, qemu_irq irq, + qemu_irq fdc_tc) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new("slavio_misc"); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + if (base) { + /* 8 bit registers */ + /* Slavio control */ + sysbus_mmio_map(s, 0, base + MISC_CFG); + /* Diagnostics */ + sysbus_mmio_map(s, 1, base + MISC_DIAG); + /* Modem control */ + sysbus_mmio_map(s, 2, base + MISC_MDM); + /* 16 bit registers */ + /* ss600mp diag LEDs */ + sysbus_mmio_map(s, 3, base + MISC_LEDS); + /* 32 bit registers */ + /* System control */ + sysbus_mmio_map(s, 4, base + MISC_SYS); + } + if (aux1_base) { + /* AUX 1 (Misc System Functions) */ + sysbus_mmio_map(s, 5, aux1_base); + } + if (aux2_base) { + /* AUX 2 (Software Powerdown Control) */ + sysbus_mmio_map(s, 6, aux2_base); + } + sysbus_connect_irq(s, 0, irq); + sysbus_connect_irq(s, 1, fdc_tc); + slavio_system_powerdown = qdev_get_gpio_in(dev, 0); + qemu_register_powerdown_notifier(&slavio_system_powerdown_notifier); +} + +static void ecc_init(hwaddr base, qemu_irq irq, uint32_t version) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new("eccmemctl"); + qdev_prop_set_uint32(dev, "version", version); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_connect_irq(s, 0, irq); + sysbus_mmio_map(s, 0, base); + if (version == 0) { // SS-600MP only + sysbus_mmio_map(s, 1, base + 0x1000); + } +} + +static void apc_init(hwaddr power_base, qemu_irq cpu_halt) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new("apc"); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + /* Power management (APC) XXX: not a Slavio device */ + sysbus_mmio_map(s, 0, power_base); + sysbus_connect_irq(s, 0, cpu_halt); +} + +static void tcx_init(hwaddr addr, qemu_irq irq, int vram_size, int width, + int height, int depth) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new("sun-tcx"); + qdev_prop_set_uint32(dev, "vram_size", vram_size); + qdev_prop_set_uint16(dev, "width", width); + qdev_prop_set_uint16(dev, "height", height); + qdev_prop_set_uint16(dev, "depth", depth); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + /* 10/ROM : FCode ROM */ + sysbus_mmio_map(s, 0, addr); + /* 2/STIP : Stipple */ + sysbus_mmio_map(s, 1, addr + 0x04000000ULL); + /* 3/BLIT : Blitter */ + sysbus_mmio_map(s, 2, addr + 0x06000000ULL); + /* 5/RSTIP : Raw Stipple */ + sysbus_mmio_map(s, 3, addr + 0x0c000000ULL); + /* 6/RBLIT : Raw Blitter */ + sysbus_mmio_map(s, 4, addr + 0x0e000000ULL); + /* 7/TEC : Transform Engine */ + sysbus_mmio_map(s, 5, addr + 0x00700000ULL); + /* 8/CMAP : DAC */ + sysbus_mmio_map(s, 6, addr + 0x00200000ULL); + /* 9/THC : */ + if (depth == 8) { + sysbus_mmio_map(s, 7, addr + 0x00300000ULL); + } else { + sysbus_mmio_map(s, 7, addr + 0x00301000ULL); + } + /* 11/DHC : */ + sysbus_mmio_map(s, 8, addr + 0x00240000ULL); + /* 12/ALT : */ + sysbus_mmio_map(s, 9, addr + 0x00280000ULL); + /* 0/DFB8 : 8-bit plane */ + sysbus_mmio_map(s, 10, addr + 0x00800000ULL); + /* 1/DFB24 : 24bit plane */ + sysbus_mmio_map(s, 11, addr + 0x02000000ULL); + /* 4/RDFB32: Raw framebuffer. Control plane */ + sysbus_mmio_map(s, 12, addr + 0x0a000000ULL); + /* 9/THC24bits : NetBSD writes here even with 8-bit display: dummy */ + if (depth == 8) { + sysbus_mmio_map(s, 13, addr + 0x00301000ULL); + } + + sysbus_connect_irq(s, 0, irq); +} + +static void cg3_init(hwaddr addr, qemu_irq irq, int vram_size, int width, + int height, int depth) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new("cgthree"); + qdev_prop_set_uint32(dev, "vram-size", vram_size); + qdev_prop_set_uint16(dev, "width", width); + qdev_prop_set_uint16(dev, "height", height); + qdev_prop_set_uint16(dev, "depth", depth); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + /* FCode ROM */ + sysbus_mmio_map(s, 0, addr); + /* DAC */ + sysbus_mmio_map(s, 1, addr + 0x400000ULL); + /* 8-bit plane */ + sysbus_mmio_map(s, 2, addr + 0x800000ULL); + + sysbus_connect_irq(s, 0, irq); +} + +/* NCR89C100/MACIO Internal ID register */ + +#define TYPE_MACIO_ID_REGISTER "macio_idreg" + +static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 }; + +static void idreg_init(hwaddr addr) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_MACIO_ID_REGISTER); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + sysbus_mmio_map(s, 0, addr); + address_space_write_rom(&address_space_memory, addr, + MEMTXATTRS_UNSPECIFIED, + idreg_data, sizeof(idreg_data)); +} + +OBJECT_DECLARE_SIMPLE_TYPE(IDRegState, MACIO_ID_REGISTER) + +struct IDRegState { + SysBusDevice parent_obj; + + MemoryRegion mem; +}; + +static void idreg_realize(DeviceState *ds, Error **errp) +{ + IDRegState *s = MACIO_ID_REGISTER(ds); + SysBusDevice *dev = SYS_BUS_DEVICE(ds); + Error *local_err = NULL; + + memory_region_init_ram_nomigrate(&s->mem, OBJECT(ds), "sun4m.idreg", + sizeof(idreg_data), &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + vmstate_register_ram_global(&s->mem); + memory_region_set_readonly(&s->mem, true); + sysbus_init_mmio(dev, &s->mem); +} + +static void idreg_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = idreg_realize; +} + +static const TypeInfo idreg_info = { + .name = TYPE_MACIO_ID_REGISTER, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IDRegState), + .class_init = idreg_class_init, +}; + +#define TYPE_TCX_AFX "tcx_afx" +OBJECT_DECLARE_SIMPLE_TYPE(AFXState, TCX_AFX) + +struct AFXState { + SysBusDevice parent_obj; + + MemoryRegion mem; +}; + +/* SS-5 TCX AFX register */ +static void afx_init(hwaddr addr) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_new(TYPE_TCX_AFX); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + sysbus_mmio_map(s, 0, addr); +} + +static void afx_realize(DeviceState *ds, Error **errp) +{ + AFXState *s = TCX_AFX(ds); + SysBusDevice *dev = SYS_BUS_DEVICE(ds); + Error *local_err = NULL; + + memory_region_init_ram_nomigrate(&s->mem, OBJECT(ds), "sun4m.afx", 4, + &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + vmstate_register_ram_global(&s->mem); + sysbus_init_mmio(dev, &s->mem); +} + +static void afx_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = afx_realize; +} + +static const TypeInfo afx_info = { + .name = TYPE_TCX_AFX, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AFXState), + .class_init = afx_class_init, +}; + +#define TYPE_OPENPROM "openprom" +typedef struct PROMState PROMState; +DECLARE_INSTANCE_CHECKER(PROMState, OPENPROM, + TYPE_OPENPROM) + +struct PROMState { + SysBusDevice parent_obj; + + MemoryRegion prom; +}; + +/* Boot PROM (OpenBIOS) */ +static uint64_t translate_prom_address(void *opaque, uint64_t addr) +{ + hwaddr *base_addr = (hwaddr *)opaque; + return addr + *base_addr - PROM_VADDR; +} + +static void prom_init(hwaddr addr, const char *bios_name) +{ + DeviceState *dev; + SysBusDevice *s; + char *filename; + int ret; + + dev = qdev_new(TYPE_OPENPROM); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + + sysbus_mmio_map(s, 0, addr); + + /* load boot prom */ + if (bios_name == NULL) { + bios_name = PROM_FILENAME; + } + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (filename) { + ret = load_elf(filename, NULL, + translate_prom_address, &addr, NULL, + NULL, NULL, NULL, 1, EM_SPARC, 0, 0); + if (ret < 0 || ret > PROM_SIZE_MAX) { + ret = load_image_targphys(filename, addr, PROM_SIZE_MAX); + } + g_free(filename); + } else { + ret = -1; + } + if (ret < 0 || ret > PROM_SIZE_MAX) { + error_report("could not load prom '%s'", bios_name); + exit(1); + } +} + +static void prom_realize(DeviceState *ds, Error **errp) +{ + PROMState *s = OPENPROM(ds); + SysBusDevice *dev = SYS_BUS_DEVICE(ds); + Error *local_err = NULL; + + memory_region_init_ram_nomigrate(&s->prom, OBJECT(ds), "sun4m.prom", + PROM_SIZE_MAX, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + vmstate_register_ram_global(&s->prom); + memory_region_set_readonly(&s->prom, true); + sysbus_init_mmio(dev, &s->prom); +} + +static Property prom_properties[] = { + {/* end of property list */}, +}; + +static void prom_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, prom_properties); + dc->realize = prom_realize; +} + +static const TypeInfo prom_info = { + .name = TYPE_OPENPROM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PROMState), + .class_init = prom_class_init, +}; + +#define TYPE_SUN4M_MEMORY "memory" +typedef struct RamDevice RamDevice; +DECLARE_INSTANCE_CHECKER(RamDevice, SUN4M_RAM, + TYPE_SUN4M_MEMORY) + +struct RamDevice { + SysBusDevice parent_obj; + HostMemoryBackend *memdev; +}; + +/* System RAM */ +static void ram_realize(DeviceState *dev, Error **errp) +{ + RamDevice *d = SUN4M_RAM(dev); + MemoryRegion *ram = host_memory_backend_get_memory(d->memdev); + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), ram); +} + +static void ram_initfn(Object *obj) +{ + RamDevice *d = SUN4M_RAM(obj); + object_property_add_link(obj, "memdev", TYPE_MEMORY_BACKEND, + (Object **)&d->memdev, + object_property_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_property_set_description(obj, "memdev", "Set RAM backend" + "Valid value is ID of a hostmem backend"); +} + +static void ram_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = ram_realize; +} + +static const TypeInfo ram_info = { + .name = TYPE_SUN4M_MEMORY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RamDevice), + .instance_init = ram_initfn, + .class_init = ram_class_init, +}; + +static void cpu_devinit(const char *cpu_type, unsigned int id, + uint64_t prom_addr, qemu_irq **cpu_irqs) +{ + SPARCCPU *cpu; + CPUSPARCState *env; + + cpu = SPARC_CPU(object_new(cpu_type)); + env = &cpu->env; + + qemu_register_reset(sun4m_cpu_reset, cpu); + object_property_set_bool(OBJECT(cpu), "start-powered-off", id != 0, + &error_fatal); + qdev_realize_and_unref(DEVICE(cpu), NULL, &error_fatal); + cpu_sparc_set_id(env, id); + *cpu_irqs = qemu_allocate_irqs(cpu_set_irq, cpu, MAX_PILS); + env->prom_addr = prom_addr; +} + +static void dummy_fdc_tc(void *opaque, int irq, int level) +{ +} + +static void sun4m_hw_init(MachineState *machine) +{ + const struct sun4m_hwdef *hwdef = SUN4M_MACHINE_GET_CLASS(machine)->hwdef; + DeviceState *slavio_intctl; + unsigned int i; + Nvram *nvram; + qemu_irq *cpu_irqs[MAX_CPUS], slavio_irq[32], slavio_cpu_irq[MAX_CPUS]; + qemu_irq fdc_tc; + unsigned long kernel_size; + uint32_t initrd_size; + DriveInfo *fd[MAX_FD]; + FWCfgState *fw_cfg; + DeviceState *dev, *ms_kb_orgate, *serial_orgate; + SysBusDevice *s; + unsigned int smp_cpus = machine->smp.cpus; + unsigned int max_cpus = machine->smp.max_cpus; + Object *ram_memdev = object_resolve_path_type(machine->ram_memdev_id, + TYPE_MEMORY_BACKEND, NULL); + NICInfo *nd = &nd_table[0]; + + if (machine->ram_size > hwdef->max_mem) { + error_report("Too much memory for this machine: %" PRId64 "," + " maximum %" PRId64, + machine->ram_size / MiB, hwdef->max_mem / MiB); + exit(1); + } + + /* init CPUs */ + for(i = 0; i < smp_cpus; i++) { + cpu_devinit(machine->cpu_type, i, hwdef->slavio_base, &cpu_irqs[i]); + } + + for (i = smp_cpus; i < MAX_CPUS; i++) + cpu_irqs[i] = qemu_allocate_irqs(dummy_cpu_set_irq, NULL, MAX_PILS); + + /* Create and map RAM frontend */ + dev = qdev_new("memory"); + object_property_set_link(OBJECT(dev), "memdev", ram_memdev, &error_fatal); + sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0); + + /* models without ECC don't trap when missing ram is accessed */ + if (!hwdef->ecc_base) { + empty_slot_init("ecc", machine->ram_size, + hwdef->max_mem - machine->ram_size); + } + + prom_init(hwdef->slavio_base, machine->firmware); + + slavio_intctl = slavio_intctl_init(hwdef->intctl_base, + hwdef->intctl_base + 0x10000ULL, + cpu_irqs); + + for (i = 0; i < 32; i++) { + slavio_irq[i] = qdev_get_gpio_in(slavio_intctl, i); + } + for (i = 0; i < MAX_CPUS; i++) { + slavio_cpu_irq[i] = qdev_get_gpio_in(slavio_intctl, 32 + i); + } + + if (hwdef->idreg_base) { + idreg_init(hwdef->idreg_base); + } + + if (hwdef->afx_base) { + afx_init(hwdef->afx_base); + } + + iommu_init(hwdef->iommu_base, hwdef->iommu_version, slavio_irq[30]); + + if (hwdef->iommu_pad_base) { + /* On the real hardware (SS-5, LX) the MMU is not padded, but aliased. + Software shouldn't use aliased addresses, neither should it crash + when does. Using empty_slot instead of aliasing can help with + debugging such accesses */ + empty_slot_init("iommu.alias", + hwdef->iommu_pad_base, hwdef->iommu_pad_len); + } + + qemu_check_nic_model(nd, TYPE_LANCE); + sparc32_dma_init(hwdef->dma_base, + hwdef->esp_base, slavio_irq[18], + hwdef->le_base, slavio_irq[16], nd); + + if (graphic_depth != 8 && graphic_depth != 24) { + error_report("Unsupported depth: %d", graphic_depth); + exit (1); + } + if (vga_interface_type != VGA_NONE) { + if (vga_interface_type == VGA_CG3) { + if (graphic_depth != 8) { + error_report("Unsupported depth: %d", graphic_depth); + exit(1); + } + + if (!(graphic_width == 1024 && graphic_height == 768) && + !(graphic_width == 1152 && graphic_height == 900)) { + error_report("Unsupported resolution: %d x %d", graphic_width, + graphic_height); + exit(1); + } + + /* sbus irq 5 */ + cg3_init(hwdef->tcx_base, slavio_irq[11], 0x00100000, + graphic_width, graphic_height, graphic_depth); + } else { + /* If no display specified, default to TCX */ + if (graphic_depth != 8 && graphic_depth != 24) { + error_report("Unsupported depth: %d", graphic_depth); + exit(1); + } + + if (!(graphic_width == 1024 && graphic_height == 768)) { + error_report("Unsupported resolution: %d x %d", + graphic_width, graphic_height); + exit(1); + } + + tcx_init(hwdef->tcx_base, slavio_irq[11], 0x00100000, + graphic_width, graphic_height, graphic_depth); + } + } + + for (i = 0; i < MAX_VSIMMS; i++) { + /* vsimm registers probed by OBP */ + if (hwdef->vsimm[i].reg_base) { + char *name = g_strdup_printf("vsimm[%d]", i); + empty_slot_init(name, hwdef->vsimm[i].reg_base, 0x2000); + g_free(name); + } + } + + if (hwdef->sx_base) { + create_unimplemented_device("sun-sx", hwdef->sx_base, 0x2000); + } + + dev = qdev_new("sysbus-m48t08"); + qdev_prop_set_int32(dev, "base-year", 1968); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_connect_irq(s, 0, slavio_irq[0]); + sysbus_mmio_map(s, 0, hwdef->nvram_base); + nvram = NVRAM(dev); + + slavio_timer_init_all(hwdef->counter_base, slavio_irq[19], slavio_cpu_irq, smp_cpus); + + /* Slavio TTYA (base+4, Linux ttyS0) is the first QEMU serial device + Slavio TTYB (base+0, Linux ttyS1) is the second QEMU serial device */ + dev = qdev_new(TYPE_ESCC); + qdev_prop_set_uint32(dev, "disabled", !machine->enable_graphics); + qdev_prop_set_uint32(dev, "frequency", ESCC_CLOCK); + qdev_prop_set_uint32(dev, "it_shift", 1); + qdev_prop_set_chr(dev, "chrB", NULL); + qdev_prop_set_chr(dev, "chrA", NULL); + qdev_prop_set_uint32(dev, "chnBtype", escc_mouse); + qdev_prop_set_uint32(dev, "chnAtype", escc_kbd); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, hwdef->ms_kb_base); + + /* Logically OR both its IRQs together */ + ms_kb_orgate = DEVICE(object_new(TYPE_OR_IRQ)); + object_property_set_int(OBJECT(ms_kb_orgate), "num-lines", 2, &error_fatal); + qdev_realize_and_unref(ms_kb_orgate, NULL, &error_fatal); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(ms_kb_orgate, 0)); + sysbus_connect_irq(s, 1, qdev_get_gpio_in(ms_kb_orgate, 1)); + qdev_connect_gpio_out(DEVICE(ms_kb_orgate), 0, slavio_irq[14]); + + dev = qdev_new(TYPE_ESCC); + qdev_prop_set_uint32(dev, "disabled", 0); + qdev_prop_set_uint32(dev, "frequency", ESCC_CLOCK); + qdev_prop_set_uint32(dev, "it_shift", 1); + qdev_prop_set_chr(dev, "chrB", serial_hd(1)); + qdev_prop_set_chr(dev, "chrA", serial_hd(0)); + qdev_prop_set_uint32(dev, "chnBtype", escc_serial); + qdev_prop_set_uint32(dev, "chnAtype", escc_serial); + + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, hwdef->serial_base); + + /* Logically OR both its IRQs together */ + serial_orgate = DEVICE(object_new(TYPE_OR_IRQ)); + object_property_set_int(OBJECT(serial_orgate), "num-lines", 2, + &error_fatal); + qdev_realize_and_unref(serial_orgate, NULL, &error_fatal); + sysbus_connect_irq(s, 0, qdev_get_gpio_in(serial_orgate, 0)); + sysbus_connect_irq(s, 1, qdev_get_gpio_in(serial_orgate, 1)); + qdev_connect_gpio_out(DEVICE(serial_orgate), 0, slavio_irq[15]); + + if (hwdef->apc_base) { + apc_init(hwdef->apc_base, qemu_allocate_irq(cpu_halt_signal, NULL, 0)); + } + + if (hwdef->fd_base) { + /* there is zero or one floppy drive */ + memset(fd, 0, sizeof(fd)); + fd[0] = drive_get(IF_FLOPPY, 0, 0); + sun4m_fdctrl_init(slavio_irq[22], hwdef->fd_base, fd, + &fdc_tc); + } else { + fdc_tc = qemu_allocate_irq(dummy_fdc_tc, NULL, 0); + } + + slavio_misc_init(hwdef->slavio_base, hwdef->aux1_base, hwdef->aux2_base, + slavio_irq[30], fdc_tc); + + if (hwdef->cs_base) { + sysbus_create_simple("sun-CS4231", hwdef->cs_base, + slavio_irq[5]); + } + + if (hwdef->dbri_base) { + /* ISDN chip with attached CS4215 audio codec */ + /* prom space */ + create_unimplemented_device("sun-DBRI.prom", + hwdef->dbri_base + 0x1000, 0x30); + /* reg space */ + create_unimplemented_device("sun-DBRI", + hwdef->dbri_base + 0x10000, 0x100); + } + + if (hwdef->bpp_base) { + /* parallel port */ + create_unimplemented_device("sun-bpp", hwdef->bpp_base, 0x20); + } + + initrd_size = 0; + kernel_size = sun4m_load_kernel(machine->kernel_filename, + machine->initrd_filename, + machine->ram_size, &initrd_size); + + nvram_init(nvram, (uint8_t *)&nd->macaddr, machine->kernel_cmdline, + machine->boot_order, machine->ram_size, kernel_size, + graphic_width, graphic_height, graphic_depth, + hwdef->nvram_machine_id, "Sun4m"); + + if (hwdef->ecc_base) + ecc_init(hwdef->ecc_base, slavio_irq[28], + hwdef->ecc_version); + + dev = qdev_new(TYPE_FW_CFG_MEM); + fw_cfg = FW_CFG(dev); + qdev_prop_set_uint32(dev, "data_width", 1); + qdev_prop_set_bit(dev, "dma_enabled", false); + object_property_add_child(OBJECT(qdev_get_machine()), TYPE_FW_CFG, + OBJECT(fw_cfg)); + s = SYS_BUS_DEVICE(dev); + sysbus_realize_and_unref(s, &error_fatal); + sysbus_mmio_map(s, 0, CFG_ADDR); + sysbus_mmio_map(s, 1, CFG_ADDR + 2); + + fw_cfg_add_i16(fw_cfg, FW_CFG_NB_CPUS, (uint16_t)smp_cpus); + fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)max_cpus); + fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)machine->ram_size); + fw_cfg_add_i16(fw_cfg, FW_CFG_MACHINE_ID, hwdef->machine_id); + fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_DEPTH, graphic_depth); + fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_WIDTH, graphic_width); + fw_cfg_add_i16(fw_cfg, FW_CFG_SUN4M_HEIGHT, graphic_height); + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR); + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size); + if (machine->kernel_cmdline) { + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, CMDLINE_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, TARGET_PAGE_SIZE, + machine->kernel_cmdline); + fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, machine->kernel_cmdline); + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(machine->kernel_cmdline) + 1); + } else { + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 0); + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, 0); + } + fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_ADDR, INITRD_LOAD_ADDR); + fw_cfg_add_i32(fw_cfg, FW_CFG_INITRD_SIZE, initrd_size); + fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, machine->boot_order[0]); + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +enum { + ss5_id = 32, + vger_id, + lx_id, + ss4_id, + scls_id, + sbook_id, + ss10_id = 64, + ss20_id, + ss600mp_id, +}; + +static void sun4m_machine_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->init = sun4m_hw_init; + mc->block_default_type = IF_SCSI; + mc->default_boot_order = "c"; + mc->default_display = "tcx"; + mc->default_ram_id = "sun4m.ram"; +} + +static void ss5_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss5_hwdef = { + .iommu_base = 0x10000000, + .iommu_pad_base = 0x10004000, + .iommu_pad_len = 0x0fffb000, + .tcx_base = 0x50000000, + .cs_base = 0x6c000000, + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .apc_base = 0x6a000000, + .afx_base = 0x6e000000, + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = ss5_id, + .iommu_version = 0x05000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCstation 5"; + mc->is_default = true; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Fujitsu-MB86904"); + smc->hwdef = &ss5_hwdef; +} + +static void ss10_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss10_hwdef = { + .iommu_base = 0xfe0000000ULL, + .tcx_base = 0xe20000000ULL, + .slavio_base = 0xff0000000ULL, + .ms_kb_base = 0xff1000000ULL, + .serial_base = 0xff1100000ULL, + .nvram_base = 0xff1200000ULL, + .fd_base = 0xff1700000ULL, + .counter_base = 0xff1300000ULL, + .intctl_base = 0xff1400000ULL, + .idreg_base = 0xef0000000ULL, + .dma_base = 0xef0400000ULL, + .esp_base = 0xef0800000ULL, + .le_base = 0xef0c00000ULL, + .apc_base = 0xefa000000ULL, /* XXX should not exist */ + .aux1_base = 0xff1800000ULL, + .aux2_base = 0xff1a01000ULL, + .ecc_base = 0xf00000000ULL, + .ecc_version = 0x10000000, /* version 0, implementation 1 */ + .nvram_machine_id = 0x72, + .machine_id = ss10_id, + .iommu_version = 0x03000000, + .max_mem = 0xf00000000ULL, + }; + + mc->desc = "Sun4m platform, SPARCstation 10"; + mc->max_cpus = 4; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-SuperSparc-II"); + smc->hwdef = &ss10_hwdef; +} + +static void ss600mp_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss600mp_hwdef = { + .iommu_base = 0xfe0000000ULL, + .tcx_base = 0xe20000000ULL, + .slavio_base = 0xff0000000ULL, + .ms_kb_base = 0xff1000000ULL, + .serial_base = 0xff1100000ULL, + .nvram_base = 0xff1200000ULL, + .counter_base = 0xff1300000ULL, + .intctl_base = 0xff1400000ULL, + .dma_base = 0xef0081000ULL, + .esp_base = 0xef0080000ULL, + .le_base = 0xef0060000ULL, + .apc_base = 0xefa000000ULL, /* XXX should not exist */ + .aux1_base = 0xff1800000ULL, + .aux2_base = 0xff1a01000ULL, /* XXX should not exist */ + .ecc_base = 0xf00000000ULL, + .ecc_version = 0x00000000, /* version 0, implementation 0 */ + .nvram_machine_id = 0x71, + .machine_id = ss600mp_id, + .iommu_version = 0x01000000, + .max_mem = 0xf00000000ULL, + }; + + mc->desc = "Sun4m platform, SPARCserver 600MP"; + mc->max_cpus = 4; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-SuperSparc-II"); + smc->hwdef = &ss600mp_hwdef; +} + +static void ss20_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss20_hwdef = { + .iommu_base = 0xfe0000000ULL, + .tcx_base = 0xe20000000ULL, + .slavio_base = 0xff0000000ULL, + .ms_kb_base = 0xff1000000ULL, + .serial_base = 0xff1100000ULL, + .nvram_base = 0xff1200000ULL, + .fd_base = 0xff1700000ULL, + .counter_base = 0xff1300000ULL, + .intctl_base = 0xff1400000ULL, + .idreg_base = 0xef0000000ULL, + .dma_base = 0xef0400000ULL, + .esp_base = 0xef0800000ULL, + .le_base = 0xef0c00000ULL, + .bpp_base = 0xef4800000ULL, + .apc_base = 0xefa000000ULL, /* XXX should not exist */ + .aux1_base = 0xff1800000ULL, + .aux2_base = 0xff1a01000ULL, + .dbri_base = 0xee0000000ULL, + .sx_base = 0xf80000000ULL, + .vsimm = { + { + .reg_base = 0x9c000000ULL, + .vram_base = 0xfc000000ULL + }, { + .reg_base = 0x90000000ULL, + .vram_base = 0xf0000000ULL + }, { + .reg_base = 0x94000000ULL + }, { + .reg_base = 0x98000000ULL + } + }, + .ecc_base = 0xf00000000ULL, + .ecc_version = 0x20000000, /* version 0, implementation 2 */ + .nvram_machine_id = 0x72, + .machine_id = ss20_id, + .iommu_version = 0x13000000, + .max_mem = 0xf00000000ULL, + }; + + mc->desc = "Sun4m platform, SPARCstation 20"; + mc->max_cpus = 4; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-SuperSparc-II"); + smc->hwdef = &ss20_hwdef; +} + +static void voyager_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef voyager_hwdef = { + .iommu_base = 0x10000000, + .tcx_base = 0x50000000, + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .apc_base = 0x71300000, /* pmc */ + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = vger_id, + .iommu_version = 0x05000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCstation Voyager"; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Fujitsu-MB86904"); + smc->hwdef = &voyager_hwdef; +} + +static void ss_lx_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss_lx_hwdef = { + .iommu_base = 0x10000000, + .iommu_pad_base = 0x10004000, + .iommu_pad_len = 0x0fffb000, + .tcx_base = 0x50000000, + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = lx_id, + .iommu_version = 0x04000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCstation LX"; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-MicroSparc-I"); + smc->hwdef = &ss_lx_hwdef; +} + +static void ss4_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef ss4_hwdef = { + .iommu_base = 0x10000000, + .tcx_base = 0x50000000, + .cs_base = 0x6c000000, + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .apc_base = 0x6a000000, + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = ss4_id, + .iommu_version = 0x05000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCstation 4"; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("Fujitsu-MB86904"); + smc->hwdef = &ss4_hwdef; +} + +static void scls_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef scls_hwdef = { + .iommu_base = 0x10000000, + .tcx_base = 0x50000000, + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .apc_base = 0x6a000000, + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = scls_id, + .iommu_version = 0x05000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCClassic"; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-MicroSparc-I"); + smc->hwdef = &scls_hwdef; +} + +static void sbook_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + Sun4mMachineClass *smc = SUN4M_MACHINE_CLASS(mc); + static const struct sun4m_hwdef sbook_hwdef = { + .iommu_base = 0x10000000, + .tcx_base = 0x50000000, /* XXX */ + .slavio_base = 0x70000000, + .ms_kb_base = 0x71000000, + .serial_base = 0x71100000, + .nvram_base = 0x71200000, + .fd_base = 0x71400000, + .counter_base = 0x71d00000, + .intctl_base = 0x71e00000, + .idreg_base = 0x78000000, + .dma_base = 0x78400000, + .esp_base = 0x78800000, + .le_base = 0x78c00000, + .apc_base = 0x6a000000, + .aux1_base = 0x71900000, + .aux2_base = 0x71910000, + .nvram_machine_id = 0x80, + .machine_id = sbook_id, + .iommu_version = 0x05000000, + .max_mem = 0x10000000, + }; + + mc->desc = "Sun4m platform, SPARCbook"; + mc->default_cpu_type = SPARC_CPU_TYPE_NAME("TI-MicroSparc-I"); + smc->hwdef = &sbook_hwdef; +} + +static const TypeInfo sun4m_machine_types[] = { + { + .name = MACHINE_TYPE_NAME("SS-5"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss5_class_init, + }, { + .name = MACHINE_TYPE_NAME("SS-10"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss10_class_init, + }, { + .name = MACHINE_TYPE_NAME("SS-600MP"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss600mp_class_init, + }, { + .name = MACHINE_TYPE_NAME("SS-20"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss20_class_init, + }, { + .name = MACHINE_TYPE_NAME("Voyager"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = voyager_class_init, + }, { + .name = MACHINE_TYPE_NAME("LX"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss_lx_class_init, + }, { + .name = MACHINE_TYPE_NAME("SS-4"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = ss4_class_init, + }, { + .name = MACHINE_TYPE_NAME("SPARCClassic"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = scls_class_init, + }, { + .name = MACHINE_TYPE_NAME("SPARCbook"), + .parent = TYPE_SUN4M_MACHINE, + .class_init = sbook_class_init, + }, { + .name = TYPE_SUN4M_MACHINE, + .parent = TYPE_MACHINE, + .class_size = sizeof(Sun4mMachineClass), + .class_init = sun4m_machine_class_init, + .abstract = true, + } +}; + +DEFINE_TYPES(sun4m_machine_types) + +static void sun4m_register_types(void) +{ + type_register_static(&idreg_info); + type_register_static(&afx_info); + type_register_static(&prom_info); + type_register_static(&ram_info); +} + +type_init(sun4m_register_types) diff --git a/hw/sparc/sun4m_iommu.c b/hw/sparc/sun4m_iommu.c new file mode 100644 index 000000000..71f546524 --- /dev/null +++ b/hw/sparc/sun4m_iommu.c @@ -0,0 +1,412 @@ +/* + * QEMU Sun4m iommu emulation + * + * Copyright (c) 2003-2005 Fabrice Bellard + * + * 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 "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/sparc/sun4m_iommu.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "exec/address-spaces.h" +#include "trace.h" + +/* + * I/O MMU used by Sun4m systems + * + * Chipset docs: + * "Sun-4M System Architecture (revision 2.0) by Chuck Narad", 950-1373-01, + * http://mediacast.sun.com/users/Barton808/media/Sun4M_SystemArchitecture_edited2.pdf + */ + +#define IOMMU_CTRL (0x0000 >> 2) +#define IOMMU_CTRL_IMPL 0xf0000000 /* Implementation */ +#define IOMMU_CTRL_VERS 0x0f000000 /* Version */ +#define IOMMU_CTRL_RNGE 0x0000001c /* Mapping RANGE */ +#define IOMMU_RNGE_16MB 0x00000000 /* 0xff000000 -> 0xffffffff */ +#define IOMMU_RNGE_32MB 0x00000004 /* 0xfe000000 -> 0xffffffff */ +#define IOMMU_RNGE_64MB 0x00000008 /* 0xfc000000 -> 0xffffffff */ +#define IOMMU_RNGE_128MB 0x0000000c /* 0xf8000000 -> 0xffffffff */ +#define IOMMU_RNGE_256MB 0x00000010 /* 0xf0000000 -> 0xffffffff */ +#define IOMMU_RNGE_512MB 0x00000014 /* 0xe0000000 -> 0xffffffff */ +#define IOMMU_RNGE_1GB 0x00000018 /* 0xc0000000 -> 0xffffffff */ +#define IOMMU_RNGE_2GB 0x0000001c /* 0x80000000 -> 0xffffffff */ +#define IOMMU_CTRL_ENAB 0x00000001 /* IOMMU Enable */ +#define IOMMU_CTRL_MASK 0x0000001d + +#define IOMMU_BASE (0x0004 >> 2) +#define IOMMU_BASE_MASK 0x07fffc00 + +#define IOMMU_TLBFLUSH (0x0014 >> 2) +#define IOMMU_TLBFLUSH_MASK 0xffffffff + +#define IOMMU_PGFLUSH (0x0018 >> 2) +#define IOMMU_PGFLUSH_MASK 0xffffffff + +#define IOMMU_AFSR (0x1000 >> 2) +#define IOMMU_AFSR_ERR 0x80000000 /* LE, TO, or BE asserted */ +#define IOMMU_AFSR_LE 0x40000000 /* SBUS reports error after + transaction */ +#define IOMMU_AFSR_TO 0x20000000 /* Write access took more than + 12.8 us. */ +#define IOMMU_AFSR_BE 0x10000000 /* Write access received error + acknowledge */ +#define IOMMU_AFSR_SIZE 0x0e000000 /* Size of transaction causing error */ +#define IOMMU_AFSR_S 0x01000000 /* Sparc was in supervisor mode */ +#define IOMMU_AFSR_RESV 0x00800000 /* Reserved, forced to 0x8 by + hardware */ +#define IOMMU_AFSR_ME 0x00080000 /* Multiple errors occurred */ +#define IOMMU_AFSR_RD 0x00040000 /* A read operation was in progress */ +#define IOMMU_AFSR_FAV 0x00020000 /* IOMMU afar has valid contents */ +#define IOMMU_AFSR_MASK 0xff0fffff + +#define IOMMU_AFAR (0x1004 >> 2) + +#define IOMMU_AER (0x1008 >> 2) /* Arbiter Enable Register */ +#define IOMMU_AER_EN_P0_ARB 0x00000001 /* MBus master 0x8 (Always 1) */ +#define IOMMU_AER_EN_P1_ARB 0x00000002 /* MBus master 0x9 */ +#define IOMMU_AER_EN_P2_ARB 0x00000004 /* MBus master 0xa */ +#define IOMMU_AER_EN_P3_ARB 0x00000008 /* MBus master 0xb */ +#define IOMMU_AER_EN_0 0x00010000 /* SBus slot 0 */ +#define IOMMU_AER_EN_1 0x00020000 /* SBus slot 1 */ +#define IOMMU_AER_EN_2 0x00040000 /* SBus slot 2 */ +#define IOMMU_AER_EN_3 0x00080000 /* SBus slot 3 */ +#define IOMMU_AER_EN_F 0x00100000 /* SBus on-board */ +#define IOMMU_AER_SBW 0x80000000 /* S-to-M asynchronous writes */ +#define IOMMU_AER_MASK 0x801f000f + +#define IOMMU_SBCFG0 (0x1010 >> 2) /* SBUS configration per-slot */ +#define IOMMU_SBCFG1 (0x1014 >> 2) /* SBUS configration per-slot */ +#define IOMMU_SBCFG2 (0x1018 >> 2) /* SBUS configration per-slot */ +#define IOMMU_SBCFG3 (0x101c >> 2) /* SBUS configration per-slot */ +#define IOMMU_SBCFG_SAB30 0x00010000 /* Phys-address bit 30 when + bypass enabled */ +#define IOMMU_SBCFG_BA16 0x00000004 /* Slave supports 16 byte bursts */ +#define IOMMU_SBCFG_BA8 0x00000002 /* Slave supports 8 byte bursts */ +#define IOMMU_SBCFG_BYPASS 0x00000001 /* Bypass IOMMU, treat all addresses + produced by this device as pure + physical. */ +#define IOMMU_SBCFG_MASK 0x00010003 + +#define IOMMU_ARBEN (0x2000 >> 2) /* SBUS arbitration enable */ +#define IOMMU_ARBEN_MASK 0x001f0000 +#define IOMMU_MID 0x00000008 + +#define IOMMU_MASK_ID (0x3018 >> 2) /* Mask ID */ +#define IOMMU_MASK_ID_MASK 0x00ffffff + +#define IOMMU_MSII_MASK 0x26000000 /* microSPARC II mask number */ +#define IOMMU_TS_MASK 0x23000000 /* turboSPARC mask number */ + +/* The format of an iopte in the page tables */ +#define IOPTE_PAGE 0xffffff00 /* Physical page number (PA[35:12]) */ +#define IOPTE_CACHE 0x00000080 /* Cached (in vme IOCACHE or + Viking/MXCC) */ +#define IOPTE_WRITE 0x00000004 /* Writable */ +#define IOPTE_VALID 0x00000002 /* IOPTE is valid */ +#define IOPTE_WAZ 0x00000001 /* Write as zeros */ + +#define IOMMU_PAGE_SHIFT 12 +#define IOMMU_PAGE_SIZE (1 << IOMMU_PAGE_SHIFT) +#define IOMMU_PAGE_MASK (~(IOMMU_PAGE_SIZE - 1)) + +static uint64_t iommu_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + IOMMUState *s = opaque; + hwaddr saddr; + uint32_t ret; + + saddr = addr >> 2; + switch (saddr) { + default: + ret = s->regs[saddr]; + break; + case IOMMU_AFAR: + case IOMMU_AFSR: + ret = s->regs[saddr]; + qemu_irq_lower(s->irq); + break; + } + trace_sun4m_iommu_mem_readl(saddr, ret); + return ret; +} + +static void iommu_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + IOMMUState *s = opaque; + hwaddr saddr; + + saddr = addr >> 2; + trace_sun4m_iommu_mem_writel(saddr, val); + switch (saddr) { + case IOMMU_CTRL: + switch (val & IOMMU_CTRL_RNGE) { + case IOMMU_RNGE_16MB: + s->iostart = 0xffffffffff000000ULL; + break; + case IOMMU_RNGE_32MB: + s->iostart = 0xfffffffffe000000ULL; + break; + case IOMMU_RNGE_64MB: + s->iostart = 0xfffffffffc000000ULL; + break; + case IOMMU_RNGE_128MB: + s->iostart = 0xfffffffff8000000ULL; + break; + case IOMMU_RNGE_256MB: + s->iostart = 0xfffffffff0000000ULL; + break; + case IOMMU_RNGE_512MB: + s->iostart = 0xffffffffe0000000ULL; + break; + case IOMMU_RNGE_1GB: + s->iostart = 0xffffffffc0000000ULL; + break; + default: + case IOMMU_RNGE_2GB: + s->iostart = 0xffffffff80000000ULL; + break; + } + trace_sun4m_iommu_mem_writel_ctrl(s->iostart); + s->regs[saddr] = ((val & IOMMU_CTRL_MASK) | s->version); + break; + case IOMMU_BASE: + s->regs[saddr] = val & IOMMU_BASE_MASK; + break; + case IOMMU_TLBFLUSH: + trace_sun4m_iommu_mem_writel_tlbflush(val); + s->regs[saddr] = val & IOMMU_TLBFLUSH_MASK; + break; + case IOMMU_PGFLUSH: + trace_sun4m_iommu_mem_writel_pgflush(val); + s->regs[saddr] = val & IOMMU_PGFLUSH_MASK; + break; + case IOMMU_AFAR: + s->regs[saddr] = val; + qemu_irq_lower(s->irq); + break; + case IOMMU_AER: + s->regs[saddr] = (val & IOMMU_AER_MASK) | IOMMU_AER_EN_P0_ARB; + break; + case IOMMU_AFSR: + s->regs[saddr] = (val & IOMMU_AFSR_MASK) | IOMMU_AFSR_RESV; + qemu_irq_lower(s->irq); + break; + case IOMMU_SBCFG0: + case IOMMU_SBCFG1: + case IOMMU_SBCFG2: + case IOMMU_SBCFG3: + s->regs[saddr] = val & IOMMU_SBCFG_MASK; + break; + case IOMMU_ARBEN: + /* XXX implement SBus probing: fault when reading unmapped + addresses, fault cause and address stored to MMU/IOMMU */ + s->regs[saddr] = (val & IOMMU_ARBEN_MASK) | IOMMU_MID; + break; + case IOMMU_MASK_ID: + s->regs[saddr] |= val & IOMMU_MASK_ID_MASK; + break; + default: + s->regs[saddr] = val; + break; + } +} + +static const MemoryRegionOps iommu_mem_ops = { + .read = iommu_mem_read, + .write = iommu_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static uint32_t iommu_page_get_flags(IOMMUState *s, hwaddr addr) +{ + uint32_t ret; + hwaddr iopte; + hwaddr pa = addr; + + iopte = s->regs[IOMMU_BASE] << 4; + addr &= ~s->iostart; + iopte += (addr >> (IOMMU_PAGE_SHIFT - 2)) & ~3; + ret = address_space_ldl_be(&address_space_memory, iopte, + MEMTXATTRS_UNSPECIFIED, NULL); + trace_sun4m_iommu_page_get_flags(pa, iopte, ret); + return ret; +} + +static hwaddr iommu_translate_pa(hwaddr addr, + uint32_t pte) +{ + hwaddr pa; + + pa = ((pte & IOPTE_PAGE) << 4) + (addr & ~IOMMU_PAGE_MASK); + trace_sun4m_iommu_translate_pa(addr, pa, pte); + return pa; +} + +static void iommu_bad_addr(IOMMUState *s, hwaddr addr, + int is_write) +{ + trace_sun4m_iommu_bad_addr(addr); + s->regs[IOMMU_AFSR] = IOMMU_AFSR_ERR | IOMMU_AFSR_LE | IOMMU_AFSR_RESV | + IOMMU_AFSR_FAV; + if (!is_write) { + s->regs[IOMMU_AFSR] |= IOMMU_AFSR_RD; + } + s->regs[IOMMU_AFAR] = addr; + qemu_irq_raise(s->irq); +} + +/* Called from RCU critical section */ +static IOMMUTLBEntry sun4m_translate_iommu(IOMMUMemoryRegion *iommu, + hwaddr addr, + IOMMUAccessFlags flags, + int iommu_idx) +{ + IOMMUState *is = container_of(iommu, IOMMUState, iommu); + hwaddr page, pa; + int is_write = (flags & IOMMU_WO) ? 1 : 0; + uint32_t pte; + IOMMUTLBEntry ret = { + .target_as = &address_space_memory, + .iova = 0, + .translated_addr = 0, + .addr_mask = ~(hwaddr)0, + .perm = IOMMU_NONE, + }; + + page = addr & IOMMU_PAGE_MASK; + pte = iommu_page_get_flags(is, page); + if (!(pte & IOPTE_VALID)) { + iommu_bad_addr(is, page, is_write); + return ret; + } + + pa = iommu_translate_pa(addr, pte); + if (is_write && !(pte & IOPTE_WRITE)) { + iommu_bad_addr(is, page, is_write); + return ret; + } + + if (pte & IOPTE_WRITE) { + ret.perm = IOMMU_RW; + } else { + ret.perm = IOMMU_RO; + } + + ret.iova = page; + ret.translated_addr = pa; + ret.addr_mask = ~IOMMU_PAGE_MASK; + + return ret; +} + +static const VMStateDescription vmstate_iommu = { + .name = "iommu", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IOMMUState, IOMMU_NREGS), + VMSTATE_UINT64(iostart, IOMMUState), + VMSTATE_END_OF_LIST() + } +}; + +static void iommu_reset(DeviceState *d) +{ + IOMMUState *s = SUN4M_IOMMU(d); + + memset(s->regs, 0, IOMMU_NREGS * 4); + s->iostart = 0; + s->regs[IOMMU_CTRL] = s->version; + s->regs[IOMMU_ARBEN] = IOMMU_MID; + s->regs[IOMMU_AFSR] = IOMMU_AFSR_RESV; + s->regs[IOMMU_AER] = IOMMU_AER_EN_P0_ARB | IOMMU_AER_EN_P1_ARB; + s->regs[IOMMU_MASK_ID] = IOMMU_TS_MASK; +} + +static void iommu_init(Object *obj) +{ + IOMMUState *s = SUN4M_IOMMU(obj); + SysBusDevice *dev = SYS_BUS_DEVICE(obj); + + memory_region_init_iommu(&s->iommu, sizeof(s->iommu), + TYPE_SUN4M_IOMMU_MEMORY_REGION, OBJECT(dev), + "iommu-sun4m", UINT64_MAX); + address_space_init(&s->iommu_as, MEMORY_REGION(&s->iommu), "iommu-as"); + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->iomem, obj, &iommu_mem_ops, s, "iommu", + IOMMU_NREGS * sizeof(uint32_t)); + sysbus_init_mmio(dev, &s->iomem); +} + +static Property iommu_properties[] = { + DEFINE_PROP_UINT32("version", IOMMUState, version, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void iommu_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = iommu_reset; + dc->vmsd = &vmstate_iommu; + device_class_set_props(dc, iommu_properties); +} + +static const TypeInfo iommu_info = { + .name = TYPE_SUN4M_IOMMU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IOMMUState), + .instance_init = iommu_init, + .class_init = iommu_class_init, +}; + +static void sun4m_iommu_memory_region_class_init(ObjectClass *klass, void *data) +{ + IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass); + + imrc->translate = sun4m_translate_iommu; +} + +static const TypeInfo sun4m_iommu_memory_region_info = { + .parent = TYPE_IOMMU_MEMORY_REGION, + .name = TYPE_SUN4M_IOMMU_MEMORY_REGION, + .class_init = sun4m_iommu_memory_region_class_init, +}; + +static void iommu_register_types(void) +{ + type_register_static(&iommu_info); + type_register_static(&sun4m_iommu_memory_region_info); +} + +type_init(iommu_register_types) diff --git a/hw/sparc/trace-events b/hw/sparc/trace-events new file mode 100644 index 000000000..00b0212c3 --- /dev/null +++ b/hw/sparc/trace-events @@ -0,0 +1,21 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# sun4m.c +sun4m_cpu_set_irq_raise(int level) "Raise CPU IRQ %d" +sun4m_cpu_set_irq_lower(int level) "Lower CPU IRQ %d" + +# sun4m_iommu.c +sun4m_iommu_mem_readl(uint64_t addr, uint32_t ret) "read reg[0x%"PRIx64"] = 0x%x" +sun4m_iommu_mem_writel(uint64_t addr, uint32_t val) "write reg[0x%"PRIx64"] = 0x%x" +sun4m_iommu_mem_writel_ctrl(uint64_t iostart) "iostart = 0x%"PRIx64 +sun4m_iommu_mem_writel_tlbflush(uint32_t val) "tlb flush 0x%x" +sun4m_iommu_mem_writel_pgflush(uint32_t val) "page flush 0x%x" +sun4m_iommu_page_get_flags(uint64_t pa, uint64_t iopte, uint32_t ret) "get flags addr 0x%"PRIx64" => pte 0x%"PRIx64", *pte = 0x%x" +sun4m_iommu_translate_pa(uint64_t addr, uint64_t pa, uint32_t iopte) "xlate dva 0x%"PRIx64" => pa 0x%"PRIx64" iopte = 0x%x" +sun4m_iommu_bad_addr(uint64_t addr) "bad addr 0x%"PRIx64 + +# leon3.c +leon3_set_irq(int intno) "Set CPU IRQ %d" +leon3_reset_irq(int intno) "Reset CPU IRQ %d" +int_helper_icache_freeze(void) "Instruction cache: freeze" +int_helper_dcache_freeze(void) "Data cache: freeze" diff --git a/hw/sparc/trace.h b/hw/sparc/trace.h new file mode 100644 index 000000000..625d60ca7 --- /dev/null +++ b/hw/sparc/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_sparc.h" |