aboutsummaryrefslogtreecommitdiff
path: root/hw/ppc
diff options
context:
space:
mode:
authorAnthony Liguori <aliguori@us.ibm.com>2013-03-10 19:39:07 -0500
committerAnthony Liguori <aliguori@us.ibm.com>2013-03-10 19:56:35 -0500
commit6e72a00f909dcd093fbdd1faa2b3c8caa1697a6c (patch)
tree8f275496c83f840e00daf4af97beac94b3a3e0b3 /hw/ppc
parent6a245c666d02210b8461952eccc17379fdeedd9f (diff)
parent7a2771d1541ec9a0c585e9b853e5f4dc036919ad (diff)
downloadqemu-6e72a00f909dcd093fbdd1faa2b3c8caa1697a6c.zip
qemu-6e72a00f909dcd093fbdd1faa2b3c8caa1697a6c.tar.gz
qemu-6e72a00f909dcd093fbdd1faa2b3c8caa1697a6c.tar.bz2
Merge remote-tracking branch 'bonzini/hw-dirs' into staging
* bonzini/hw-dirs: sh: move files referencing CPU to hw/sh4/ ppc: move more files to hw/ppc ppc: move files referencing CPU to hw/ppc/ m68k: move files referencing CPU to hw/m68k/ i386: move files referencing CPU to hw/i386/ arm: move files referencing CPU to hw/arm/ hw: move boards and other isolated files to hw/ARCH ppc: express FDT dependency of pSeries and e500 boards via default-configs/ build: always link device_tree.o into emulators if libfdt available hw: include hw header files with full paths ppc: do not use ../ in include files vt82c686: vt82c686 is not a PCI host bridge virtio-9p: remove PCI dependencies from hw/9pfs/ virtio-9p: use CONFIG_VIRTFS, not CONFIG_LINUX hw: move device-hotplug.o to toplevel, compile it once hw: move qdev-monitor.o to toplevel directory hw: move fifo.[ch] to libqemuutil hw: move char backends to backends/ Conflicts: backends/baum.c backends/msmouse.c hw/a15mpcore.c hw/arm/Makefile.objs hw/arm/pic_cpu.c hw/dataplane/event-poll.c hw/dataplane/virtio-blk.c include/char/baum.h include/char/msmouse.h qemu-char.c vl.c Resolve conflicts caused by header movements. Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'hw/ppc')
-rw-r--r--hw/ppc/Makefile.objs29
-rw-r--r--hw/ppc/e500-ccsr.h2
-rw-r--r--hw/ppc/e500plat.c2
-rw-r--r--hw/ppc/mpc8544_guts.c143
-rw-r--r--hw/ppc/mpc8544ds.c2
-rw-r--r--hw/ppc/ppc.c1356
-rw-r--r--hw/ppc/ppc405_boards.c662
-rw-r--r--hw/ppc/ppc405_uc.c2548
-rw-r--r--hw/ppc/ppc440_bamboo.c306
-rw-r--r--hw/ppc/ppc4xx_devs.c721
-rw-r--r--hw/ppc/ppc_booke.c273
-rw-r--r--hw/ppc/ppce500_spin.c222
-rw-r--r--hw/ppc/spapr.c963
-rw-r--r--hw/ppc/spapr_events.c321
-rw-r--r--hw/ppc/spapr_hcall.c741
-rw-r--r--hw/ppc/spapr_iommu.c293
-rw-r--r--hw/ppc/spapr_rtas.c334
-rw-r--r--hw/ppc/spapr_vio.c649
-rw-r--r--hw/ppc/virtex_ml507.c274
-rw-r--r--hw/ppc/xics.c588
20 files changed, 10412 insertions, 17 deletions
diff --git a/hw/ppc/Makefile.objs b/hw/ppc/Makefile.objs
index f762050..4de0209 100644
--- a/hw/ppc/Makefile.objs
+++ b/hw/ppc/Makefile.objs
@@ -1,28 +1,26 @@
-# shared objects
-obj-y = ppc.o ppc_booke.o
# PREP target
obj-y += mc146818rtc.o
# IBM pSeries (sPAPR)
-obj-$(CONFIG_PSERIES) += spapr.o spapr_hcall.o spapr_rtas.o spapr_vio.o
-obj-$(CONFIG_PSERIES) += xics.o spapr_vty.o spapr_llan.o spapr_vscsi.o
-obj-$(CONFIG_PSERIES) += spapr_pci.o pci/pci-hotplug.o spapr_iommu.o
-obj-$(CONFIG_PSERIES) += spapr_events.o spapr_nvram.o
+obj-$(CONFIG_PSERIES) += spapr_vty.o spapr_llan.o spapr_vscsi.o
+obj-$(CONFIG_PSERIES) += spapr_pci.o pci/pci-hotplug.o
+obj-$(CONFIG_PSERIES) += spapr_nvram.o
# PowerPC 4xx boards
-obj-y += ppc4xx_devs.o ppc4xx_pci.o ppc405_uc.o ppc405_boards.o
-obj-y += ppc440_bamboo.o
-# PowerPC E500 boards
-obj-$(CONFIG_FDT) += mpc8544_guts.o ppce500_spin.o
-# PowerPC 440 Xilinx ML507 reference board.
-obj-y += virtex_ml507.o
+obj-y += ppc4xx_pci.o
# PowerPC OpenPIC
obj-y += openpic.o
-obj-$(CONFIG_FDT) += ../device_tree.o
# Xilinx PPC peripherals
obj-y += xilinx_ethlite.o
obj-y := $(addprefix ../,$(obj-y))
+# shared objects
+obj-y += ppc.o ppc_booke.o
+# IBM pSeries (sPAPR)
+obj-$(CONFIG_PSERIES) += spapr.o xics.o spapr_vio.o spapr_events.o
+obj-$(CONFIG_PSERIES) += spapr_hcall.o spapr_iommu.o spapr_rtas.o
+# PowerPC 4xx boards
+obj-y += ppc405_boards.o ppc4xx_devs.o ppc405_uc.o ppc440_bamboo.o
# PReP
obj-y += prep.o
# OldWorld PowerMac
@@ -30,4 +28,7 @@ obj-y += mac_oldworld.o
# NewWorld PowerMac
obj-y += mac_newworld.o
# e500
-obj-$(CONFIG_FDT) += e500.o mpc8544ds.o e500plat.o
+obj-$(CONFIG_E500) += e500.o mpc8544ds.o e500plat.o
+obj-$(CONFIG_E500) += mpc8544_guts.o ppce500_spin.o
+# PowerPC 440 Xilinx ML507 reference board.
+obj-y += virtex_ml507.o
diff --git a/hw/ppc/e500-ccsr.h b/hw/ppc/e500-ccsr.h
index f20f51b..12a2ba4 100644
--- a/hw/ppc/e500-ccsr.h
+++ b/hw/ppc/e500-ccsr.h
@@ -1,7 +1,7 @@
#ifndef E500_CCSR_H
#define E500_CCSR_H
-#include "../sysbus.h"
+#include "hw/sysbus.h"
typedef struct PPCE500CCSRState {
/*< private >*/
diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c
index 25ac4b1..4b30575 100644
--- a/hw/ppc/e500plat.c
+++ b/hw/ppc/e500plat.c
@@ -12,7 +12,7 @@
#include "config.h"
#include "qemu-common.h"
#include "e500.h"
-#include "../boards.h"
+#include "hw/boards.h"
#include "sysemu/device_tree.h"
#include "hw/pci/pci.h"
#include "hw/openpic.h"
diff --git a/hw/ppc/mpc8544_guts.c b/hw/ppc/mpc8544_guts.c
new file mode 100644
index 0000000..193beab
--- /dev/null
+++ b/hw/ppc/mpc8544_guts.c
@@ -0,0 +1,143 @@
+/*
+ * QEMU PowerPC MPC8544 global util pseudo-device
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <alex@csgraf.de>
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * *****************************************************************
+ *
+ * The documentation for this device is noted in the MPC8544 documentation,
+ * file name "MPC8544ERM.pdf". You can easily find it on the web.
+ *
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+
+#define MPC8544_GUTS_MMIO_SIZE 0x1000
+#define MPC8544_GUTS_RSTCR_RESET 0x02
+
+#define MPC8544_GUTS_ADDR_PORPLLSR 0x00
+#define MPC8544_GUTS_ADDR_PORBMSR 0x04
+#define MPC8544_GUTS_ADDR_PORIMPSCR 0x08
+#define MPC8544_GUTS_ADDR_PORDEVSR 0x0C
+#define MPC8544_GUTS_ADDR_PORDBGMSR 0x10
+#define MPC8544_GUTS_ADDR_PORDEVSR2 0x14
+#define MPC8544_GUTS_ADDR_GPPORCR 0x20
+#define MPC8544_GUTS_ADDR_GPIOCR 0x30
+#define MPC8544_GUTS_ADDR_GPOUTDR 0x40
+#define MPC8544_GUTS_ADDR_GPINDR 0x50
+#define MPC8544_GUTS_ADDR_PMUXCR 0x60
+#define MPC8544_GUTS_ADDR_DEVDISR 0x70
+#define MPC8544_GUTS_ADDR_POWMGTCSR 0x80
+#define MPC8544_GUTS_ADDR_MCPSUMR 0x90
+#define MPC8544_GUTS_ADDR_RSTRSCR 0x94
+#define MPC8544_GUTS_ADDR_PVR 0xA0
+#define MPC8544_GUTS_ADDR_SVR 0xA4
+#define MPC8544_GUTS_ADDR_RSTCR 0xB0
+#define MPC8544_GUTS_ADDR_IOVSELSR 0xC0
+#define MPC8544_GUTS_ADDR_DDRCSR 0xB20
+#define MPC8544_GUTS_ADDR_DDRCDR 0xB24
+#define MPC8544_GUTS_ADDR_DDRCLKDR 0xB28
+#define MPC8544_GUTS_ADDR_CLKOCR 0xE00
+#define MPC8544_GUTS_ADDR_SRDS1CR1 0xF04
+#define MPC8544_GUTS_ADDR_SRDS2CR1 0xF10
+#define MPC8544_GUTS_ADDR_SRDS2CR3 0xF18
+
+struct GutsState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+};
+
+typedef struct GutsState GutsState;
+
+static uint64_t mpc8544_guts_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t value = 0;
+ CPUPPCState *env = cpu_single_env;
+
+ addr &= MPC8544_GUTS_MMIO_SIZE - 1;
+ switch (addr) {
+ case MPC8544_GUTS_ADDR_PVR:
+ value = env->spr[SPR_PVR];
+ break;
+ case MPC8544_GUTS_ADDR_SVR:
+ value = env->spr[SPR_E500_SVR];
+ break;
+ default:
+ fprintf(stderr, "guts: Unknown register read: %x\n", (int)addr);
+ break;
+ }
+
+ return value;
+}
+
+static void mpc8544_guts_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ addr &= MPC8544_GUTS_MMIO_SIZE - 1;
+
+ switch (addr) {
+ case MPC8544_GUTS_ADDR_RSTCR:
+ if (value & MPC8544_GUTS_RSTCR_RESET) {
+ qemu_system_reset_request();
+ }
+ break;
+ default:
+ fprintf(stderr, "guts: Unknown register write: %x = %x\n",
+ (int)addr, (unsigned)value);
+ break;
+ }
+}
+
+static const MemoryRegionOps mpc8544_guts_ops = {
+ .read = mpc8544_guts_read,
+ .write = mpc8544_guts_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static int mpc8544_guts_initfn(SysBusDevice *dev)
+{
+ GutsState *s;
+
+ s = FROM_SYSBUS(GutsState, SYS_BUS_DEVICE(dev));
+
+ memory_region_init_io(&s->iomem, &mpc8544_guts_ops, s,
+ "mpc6544.guts", MPC8544_GUTS_MMIO_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ return 0;
+}
+
+static void mpc8544_guts_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = mpc8544_guts_initfn;
+}
+
+static const TypeInfo mpc8544_guts_info = {
+ .name = "mpc8544-guts",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(GutsState),
+ .class_init = mpc8544_guts_class_init,
+};
+
+static void mpc8544_guts_register_types(void)
+{
+ type_register_static(&mpc8544_guts_info);
+}
+
+type_init(mpc8544_guts_register_types)
diff --git a/hw/ppc/mpc8544ds.c b/hw/ppc/mpc8544ds.c
index e25c70b..cf29788 100644
--- a/hw/ppc/mpc8544ds.c
+++ b/hw/ppc/mpc8544ds.c
@@ -12,7 +12,7 @@
#include "config.h"
#include "qemu-common.h"
#include "e500.h"
-#include "../boards.h"
+#include "hw/boards.h"
#include "sysemu/device_tree.h"
#include "hw/openpic.h"
diff --git a/hw/ppc/ppc.c b/hw/ppc/ppc.c
new file mode 100644
index 0000000..c9437fc
--- /dev/null
+++ b/hw/ppc/ppc.c
@@ -0,0 +1,1356 @@
+/*
+ * QEMU generic PowerPC hardware System Emulator
+ *
+ * Copyright (c) 2003-2007 Jocelyn Mayer
+ *
+ * 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 "hw/hw.h"
+#include "hw/ppc.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/nvram.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+
+//#define PPC_DEBUG_IRQ
+//#define PPC_DEBUG_TB
+
+#ifdef PPC_DEBUG_IRQ
+# define LOG_IRQ(...) qemu_log_mask(CPU_LOG_INT, ## __VA_ARGS__)
+#else
+# define LOG_IRQ(...) do { } while (0)
+#endif
+
+
+#ifdef PPC_DEBUG_TB
+# define LOG_TB(...) qemu_log(__VA_ARGS__)
+#else
+# define LOG_TB(...) do { } while (0)
+#endif
+
+static void cpu_ppc_tb_stop (CPUPPCState *env);
+static void cpu_ppc_tb_start (CPUPPCState *env);
+
+void ppc_set_irq(PowerPCCPU *cpu, int n_IRQ, int level)
+{
+ CPUPPCState *env = &cpu->env;
+ unsigned int old_pending = env->pending_interrupts;
+
+ if (level) {
+ env->pending_interrupts |= 1 << n_IRQ;
+ cpu_interrupt(env, CPU_INTERRUPT_HARD);
+ } else {
+ env->pending_interrupts &= ~(1 << n_IRQ);
+ if (env->pending_interrupts == 0)
+ cpu_reset_interrupt(env, CPU_INTERRUPT_HARD);
+ }
+
+ if (old_pending != env->pending_interrupts) {
+#ifdef CONFIG_KVM
+ kvmppc_set_interrupt(cpu, n_IRQ, level);
+#endif
+ }
+
+ LOG_IRQ("%s: %p n_IRQ %d level %d => pending %08" PRIx32
+ "req %08x\n", __func__, env, n_IRQ, level,
+ env->pending_interrupts, env->interrupt_request);
+}
+
+/* PowerPC 6xx / 7xx internal IRQ controller */
+static void ppc6xx_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ switch (pin) {
+ case PPC6xx_INPUT_TBEN:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: %s the time base\n",
+ __func__, level ? "start" : "stop");
+ if (level) {
+ cpu_ppc_tb_start(env);
+ } else {
+ cpu_ppc_tb_stop(env);
+ }
+ case PPC6xx_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC6xx_INPUT_SMI:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the SMI IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_SMI, level);
+ break;
+ case PPC6xx_INPUT_MCP:
+ /* Negative edge sensitive */
+ /* XXX: TODO: actual reaction may depends on HID0 status
+ * 603/604/740/750: check HID0[EMCP]
+ */
+ if (cur_level == 1 && level == 0) {
+ LOG_IRQ("%s: raise machine check state\n",
+ __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, 1);
+ }
+ break;
+ case PPC6xx_INPUT_CKSTP_IN:
+ /* Level sensitive - active low */
+ /* XXX: TODO: relay the signal to CKSTP_OUT pin */
+ /* XXX: Note that the only way to restart the CPU is to reset it */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ env->halted = 1;
+ }
+ break;
+ case PPC6xx_INPUT_HRESET:
+ /* Level sensitive - active low */
+ if (level) {
+ LOG_IRQ("%s: reset the CPU\n", __func__);
+ cpu_interrupt(env, CPU_INTERRUPT_RESET);
+ }
+ break;
+ case PPC6xx_INPUT_SRESET:
+ LOG_IRQ("%s: set the RESET IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_RESET, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc6xx_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc6xx_set_irq, cpu,
+ PPC6xx_INPUT_NB);
+}
+
+#if defined(TARGET_PPC64)
+/* PowerPC 970 internal IRQ controller */
+static void ppc970_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ switch (pin) {
+ case PPC970_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC970_INPUT_THINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the SMI IRQ state to %d\n", __func__,
+ level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_THERM, level);
+ break;
+ case PPC970_INPUT_MCP:
+ /* Negative edge sensitive */
+ /* XXX: TODO: actual reaction may depends on HID0 status
+ * 603/604/740/750: check HID0[EMCP]
+ */
+ if (cur_level == 1 && level == 0) {
+ LOG_IRQ("%s: raise machine check state\n",
+ __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, 1);
+ }
+ break;
+ case PPC970_INPUT_CKSTP:
+ /* Level sensitive - active low */
+ /* XXX: TODO: relay the signal to CKSTP_OUT pin */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ env->halted = 1;
+ } else {
+ LOG_IRQ("%s: restart the CPU\n", __func__);
+ env->halted = 0;
+ qemu_cpu_kick(CPU(cpu));
+ }
+ break;
+ case PPC970_INPUT_HRESET:
+ /* Level sensitive - active low */
+ if (level) {
+ cpu_interrupt(env, CPU_INTERRUPT_RESET);
+ }
+ break;
+ case PPC970_INPUT_SRESET:
+ LOG_IRQ("%s: set the RESET IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_RESET, level);
+ break;
+ case PPC970_INPUT_TBEN:
+ LOG_IRQ("%s: set the TBEN state to %d\n", __func__,
+ level);
+ /* XXX: TODO */
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc970_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc970_set_irq, cpu,
+ PPC970_INPUT_NB);
+}
+
+/* POWER7 internal IRQ controller */
+static void power7_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+
+ switch (pin) {
+ case POWER7_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level) {
+ env->irq_input_state |= 1 << pin;
+ } else {
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppcPOWER7_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&power7_set_irq, cpu,
+ POWER7_INPUT_NB);
+}
+#endif /* defined(TARGET_PPC64) */
+
+/* PowerPC 40x internal IRQ controller */
+static void ppc40x_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ switch (pin) {
+ case PPC40x_INPUT_RESET_SYS:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC system\n",
+ __func__);
+ ppc40x_system_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_RESET_CHIP:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC chip\n", __func__);
+ ppc40x_chip_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_RESET_CORE:
+ /* XXX: TODO: update DBSR[MRR] */
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC core\n", __func__);
+ ppc40x_core_reset(cpu);
+ }
+ break;
+ case PPC40x_INPUT_CINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the critical IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_CEXT, level);
+ break;
+ case PPC40x_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the external IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPC40x_INPUT_HALT:
+ /* Level sensitive - active low */
+ if (level) {
+ LOG_IRQ("%s: stop the CPU\n", __func__);
+ env->halted = 1;
+ } else {
+ LOG_IRQ("%s: restart the CPU\n", __func__);
+ env->halted = 0;
+ qemu_cpu_kick(CPU(cpu));
+ }
+ break;
+ case PPC40x_INPUT_DEBUG:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the debug pin state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_DEBUG, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppc40x_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppc40x_set_irq,
+ cpu, PPC40x_INPUT_NB);
+}
+
+/* PowerPC E500 internal IRQ controller */
+static void ppce500_set_irq(void *opaque, int pin, int level)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ int cur_level;
+
+ LOG_IRQ("%s: env %p pin %d level %d\n", __func__,
+ env, pin, level);
+ cur_level = (env->irq_input_state >> pin) & 1;
+ /* Don't generate spurious events */
+ if ((cur_level == 1 && level == 0) || (cur_level == 0 && level != 0)) {
+ switch (pin) {
+ case PPCE500_INPUT_MCK:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC system\n",
+ __func__);
+ qemu_system_reset_request();
+ }
+ break;
+ case PPCE500_INPUT_RESET_CORE:
+ if (level) {
+ LOG_IRQ("%s: reset the PowerPC core\n", __func__);
+ ppc_set_irq(cpu, PPC_INTERRUPT_MCK, level);
+ }
+ break;
+ case PPCE500_INPUT_CINT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the critical IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_CEXT, level);
+ break;
+ case PPCE500_INPUT_INT:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the core IRQ state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_EXT, level);
+ break;
+ case PPCE500_INPUT_DEBUG:
+ /* Level sensitive - active high */
+ LOG_IRQ("%s: set the debug pin state to %d\n",
+ __func__, level);
+ ppc_set_irq(cpu, PPC_INTERRUPT_DEBUG, level);
+ break;
+ default:
+ /* Unknown pin - do nothing */
+ LOG_IRQ("%s: unknown IRQ pin %d\n", __func__, pin);
+ return;
+ }
+ if (level)
+ env->irq_input_state |= 1 << pin;
+ else
+ env->irq_input_state &= ~(1 << pin);
+ }
+}
+
+void ppce500_irq_init(CPUPPCState *env)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->irq_inputs = (void **)qemu_allocate_irqs(&ppce500_set_irq,
+ cpu, PPCE500_INPUT_NB);
+}
+
+/* Enable or Disable the E500 EPR capability */
+void ppce500_set_mpic_proxy(bool enabled)
+{
+ CPUPPCState *env;
+
+ for (env = first_cpu; env != NULL; env = env->next_cpu) {
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ CPUState *cs = CPU(cpu);
+
+ env->mpic_proxy = enabled;
+ if (kvm_enabled()) {
+ kvmppc_set_mpic_proxy(POWERPC_CPU(cs), enabled);
+ }
+ }
+}
+
+/*****************************************************************************/
+/* PowerPC time base and decrementer emulation */
+
+uint64_t cpu_ppc_get_tb(ppc_tb_t *tb_env, uint64_t vmclk, int64_t tb_offset)
+{
+ /* TB time in tb periods */
+ return muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec()) + tb_offset;
+}
+
+uint64_t cpu_ppc_load_tbl (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ if (kvm_enabled()) {
+ return env->spr[SPR_TBL];
+ }
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->tb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb;
+}
+
+static inline uint32_t _cpu_ppc_load_tbu(CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->tb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb >> 32;
+}
+
+uint32_t cpu_ppc_load_tbu (CPUPPCState *env)
+{
+ if (kvm_enabled()) {
+ return env->spr[SPR_TBU];
+ }
+
+ return _cpu_ppc_load_tbu(env);
+}
+
+static inline void cpu_ppc_store_tb(ppc_tb_t *tb_env, uint64_t vmclk,
+ int64_t *tb_offsetp, uint64_t value)
+{
+ *tb_offsetp = value - muldiv64(vmclk, tb_env->tb_freq, get_ticks_per_sec());
+ LOG_TB("%s: tb %016" PRIx64 " offset %08" PRIx64 "\n",
+ __func__, value, *tb_offsetp);
+}
+
+void cpu_ppc_store_tbl (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->tb_offset);
+ tb &= 0xFFFFFFFF00000000ULL;
+ cpu_ppc_store_tb(tb_env, qemu_get_clock_ns(vm_clock),
+ &tb_env->tb_offset, tb | (uint64_t)value);
+}
+
+static inline void _cpu_ppc_store_tbu(CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->tb_offset);
+ tb &= 0x00000000FFFFFFFFULL;
+ cpu_ppc_store_tb(tb_env, qemu_get_clock_ns(vm_clock),
+ &tb_env->tb_offset, ((uint64_t)value << 32) | tb);
+}
+
+void cpu_ppc_store_tbu (CPUPPCState *env, uint32_t value)
+{
+ _cpu_ppc_store_tbu(env, value);
+}
+
+uint64_t cpu_ppc_load_atbl (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->atb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb;
+}
+
+uint32_t cpu_ppc_load_atbu (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->atb_offset);
+ LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb);
+
+ return tb >> 32;
+}
+
+void cpu_ppc_store_atbl (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->atb_offset);
+ tb &= 0xFFFFFFFF00000000ULL;
+ cpu_ppc_store_tb(tb_env, qemu_get_clock_ns(vm_clock),
+ &tb_env->atb_offset, tb | (uint64_t)value);
+}
+
+void cpu_ppc_store_atbu (CPUPPCState *env, uint32_t value)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb;
+
+ tb = cpu_ppc_get_tb(tb_env, qemu_get_clock_ns(vm_clock), tb_env->atb_offset);
+ tb &= 0x00000000FFFFFFFFULL;
+ cpu_ppc_store_tb(tb_env, qemu_get_clock_ns(vm_clock),
+ &tb_env->atb_offset, ((uint64_t)value << 32) | tb);
+}
+
+static void cpu_ppc_tb_stop (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb, atb, vmclk;
+
+ /* If the time base is already frozen, do nothing */
+ if (tb_env->tb_freq != 0) {
+ vmclk = qemu_get_clock_ns(vm_clock);
+ /* Get the time base */
+ tb = cpu_ppc_get_tb(tb_env, vmclk, tb_env->tb_offset);
+ /* Get the alternate time base */
+ atb = cpu_ppc_get_tb(tb_env, vmclk, tb_env->atb_offset);
+ /* Store the time base value (ie compute the current offset) */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->tb_offset, tb);
+ /* Store the alternate time base value (compute the current offset) */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->atb_offset, atb);
+ /* Set the time base frequency to zero */
+ tb_env->tb_freq = 0;
+ /* Now, the time bases are frozen to tb_offset / atb_offset value */
+ }
+}
+
+static void cpu_ppc_tb_start (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t tb, atb, vmclk;
+
+ /* If the time base is not frozen, do nothing */
+ if (tb_env->tb_freq == 0) {
+ vmclk = qemu_get_clock_ns(vm_clock);
+ /* Get the time base from tb_offset */
+ tb = tb_env->tb_offset;
+ /* Get the alternate time base from atb_offset */
+ atb = tb_env->atb_offset;
+ /* Restore the tb frequency from the decrementer frequency */
+ tb_env->tb_freq = tb_env->decr_freq;
+ /* Store the time base value */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->tb_offset, tb);
+ /* Store the alternate time base value */
+ cpu_ppc_store_tb(tb_env, vmclk, &tb_env->atb_offset, atb);
+ }
+}
+
+static inline uint32_t _cpu_ppc_load_decr(CPUPPCState *env, uint64_t next)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint32_t decr;
+ int64_t diff;
+
+ diff = next - qemu_get_clock_ns(vm_clock);
+ if (diff >= 0) {
+ decr = muldiv64(diff, tb_env->decr_freq, get_ticks_per_sec());
+ } else if (tb_env->flags & PPC_TIMER_BOOKE) {
+ decr = 0;
+ } else {
+ decr = -muldiv64(-diff, tb_env->decr_freq, get_ticks_per_sec());
+ }
+ LOG_TB("%s: %08" PRIx32 "\n", __func__, decr);
+
+ return decr;
+}
+
+uint32_t cpu_ppc_load_decr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+
+ if (kvm_enabled()) {
+ return env->spr[SPR_DECR];
+ }
+
+ return _cpu_ppc_load_decr(env, tb_env->decr_next);
+}
+
+uint32_t cpu_ppc_load_hdecr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+
+ return _cpu_ppc_load_decr(env, tb_env->hdecr_next);
+}
+
+uint64_t cpu_ppc_load_purr (CPUPPCState *env)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t diff;
+
+ diff = qemu_get_clock_ns(vm_clock) - tb_env->purr_start;
+
+ return tb_env->purr_load + muldiv64(diff, tb_env->tb_freq, get_ticks_per_sec());
+}
+
+/* When decrementer expires,
+ * all we need to do is generate or queue a CPU exception
+ */
+static inline void cpu_ppc_decr_excp(PowerPCCPU *cpu)
+{
+ /* Raise it */
+ LOG_TB("raise decrementer exception\n");
+ ppc_set_irq(cpu, PPC_INTERRUPT_DECR, 1);
+}
+
+static inline void cpu_ppc_hdecr_excp(PowerPCCPU *cpu)
+{
+ /* Raise it */
+ LOG_TB("raise decrementer exception\n");
+ ppc_set_irq(cpu, PPC_INTERRUPT_HDECR, 1);
+}
+
+static void __cpu_ppc_store_decr(PowerPCCPU *cpu, uint64_t *nextp,
+ struct QEMUTimer *timer,
+ void (*raise_excp)(PowerPCCPU *),
+ uint32_t decr, uint32_t value,
+ int is_excp)
+{
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t now, next;
+
+ LOG_TB("%s: %08" PRIx32 " => %08" PRIx32 "\n", __func__,
+ decr, value);
+
+ if (kvm_enabled()) {
+ /* KVM handles decrementer exceptions, we don't need our own timer */
+ return;
+ }
+
+ now = qemu_get_clock_ns(vm_clock);
+ next = now + muldiv64(value, get_ticks_per_sec(), tb_env->decr_freq);
+ if (is_excp) {
+ next += *nextp - now;
+ }
+ if (next == now) {
+ next++;
+ }
+ *nextp = next;
+ /* Adjust timer */
+ qemu_mod_timer(timer, next);
+
+ /* If we set a negative value and the decrementer was positive, raise an
+ * exception.
+ */
+ if ((tb_env->flags & PPC_DECR_UNDERFLOW_TRIGGERED)
+ && (value & 0x80000000)
+ && !(decr & 0x80000000)) {
+ (*raise_excp)(cpu);
+ }
+}
+
+static inline void _cpu_ppc_store_decr(PowerPCCPU *cpu, uint32_t decr,
+ uint32_t value, int is_excp)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ __cpu_ppc_store_decr(cpu, &tb_env->decr_next, tb_env->decr_timer,
+ &cpu_ppc_decr_excp, decr, value, is_excp);
+}
+
+void cpu_ppc_store_decr (CPUPPCState *env, uint32_t value)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ _cpu_ppc_store_decr(cpu, cpu_ppc_load_decr(env), value, 0);
+}
+
+static void cpu_ppc_decr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ _cpu_ppc_store_decr(cpu, 0x00000000, 0xFFFFFFFF, 1);
+}
+
+static inline void _cpu_ppc_store_hdecr(PowerPCCPU *cpu, uint32_t hdecr,
+ uint32_t value, int is_excp)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ if (tb_env->hdecr_timer != NULL) {
+ __cpu_ppc_store_decr(cpu, &tb_env->hdecr_next, tb_env->hdecr_timer,
+ &cpu_ppc_hdecr_excp, hdecr, value, is_excp);
+ }
+}
+
+void cpu_ppc_store_hdecr (CPUPPCState *env, uint32_t value)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ _cpu_ppc_store_hdecr(cpu, cpu_ppc_load_hdecr(env), value, 0);
+}
+
+static void cpu_ppc_hdecr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ _cpu_ppc_store_hdecr(cpu, 0x00000000, 0xFFFFFFFF, 1);
+}
+
+static void cpu_ppc_store_purr(PowerPCCPU *cpu, uint64_t value)
+{
+ ppc_tb_t *tb_env = cpu->env.tb_env;
+
+ tb_env->purr_load = value;
+ tb_env->purr_start = qemu_get_clock_ns(vm_clock);
+}
+
+static void cpu_ppc_set_tb_clk (void *opaque, uint32_t freq)
+{
+ CPUPPCState *env = opaque;
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env = env->tb_env;
+
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ /* There is a bug in Linux 2.4 kernels:
+ * if a decrementer exception is pending when it enables msr_ee at startup,
+ * it's not ready to handle it...
+ */
+ _cpu_ppc_store_decr(cpu, 0xFFFFFFFF, 0xFFFFFFFF, 0);
+ _cpu_ppc_store_hdecr(cpu, 0xFFFFFFFF, 0xFFFFFFFF, 0);
+ cpu_ppc_store_purr(cpu, 0x0000000000000000ULL);
+}
+
+/* Set up (once) timebase frequency (in Hz) */
+clk_setup_cb cpu_ppc_tb_init (CPUPPCState *env, uint32_t freq)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ env->tb_env = tb_env;
+ tb_env->flags = PPC_DECR_UNDERFLOW_TRIGGERED;
+ /* Create new timer */
+ tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &cpu_ppc_decr_cb, cpu);
+ if (0) {
+ /* XXX: find a suitable condition to enable the hypervisor decrementer
+ */
+ tb_env->hdecr_timer = qemu_new_timer_ns(vm_clock, &cpu_ppc_hdecr_cb,
+ cpu);
+ } else {
+ tb_env->hdecr_timer = NULL;
+ }
+ cpu_ppc_set_tb_clk(env, freq);
+
+ return &cpu_ppc_set_tb_clk;
+}
+
+/* Specific helpers for POWER & PowerPC 601 RTC */
+#if 0
+static clk_setup_cb cpu_ppc601_rtc_init (CPUPPCState *env)
+{
+ return cpu_ppc_tb_init(env, 7812500);
+}
+#endif
+
+void cpu_ppc601_store_rtcu (CPUPPCState *env, uint32_t value)
+{
+ _cpu_ppc_store_tbu(env, value);
+}
+
+uint32_t cpu_ppc601_load_rtcu (CPUPPCState *env)
+{
+ return _cpu_ppc_load_tbu(env);
+}
+
+void cpu_ppc601_store_rtcl (CPUPPCState *env, uint32_t value)
+{
+ cpu_ppc_store_tbl(env, value & 0x3FFFFF80);
+}
+
+uint32_t cpu_ppc601_load_rtcl (CPUPPCState *env)
+{
+ return cpu_ppc_load_tbl(env) & 0x3FFFFF80;
+}
+
+/*****************************************************************************/
+/* PowerPC 40x timers */
+
+/* PIT, FIT & WDT */
+typedef struct ppc40x_timer_t ppc40x_timer_t;
+struct ppc40x_timer_t {
+ uint64_t pit_reload; /* PIT auto-reload value */
+ uint64_t fit_next; /* Tick for next FIT interrupt */
+ struct QEMUTimer *fit_timer;
+ uint64_t wdt_next; /* Tick for next WDT interrupt */
+ struct QEMUTimer *wdt_timer;
+
+ /* 405 have the PIT, 440 have a DECR. */
+ unsigned int decr_excp;
+};
+
+/* Fixed interval timer */
+static void cpu_4xx_fit_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ now = qemu_get_clock_ns(vm_clock);
+ switch ((env->spr[SPR_40x_TCR] >> 24) & 0x3) {
+ case 0:
+ next = 1 << 9;
+ break;
+ case 1:
+ next = 1 << 13;
+ break;
+ case 2:
+ next = 1 << 17;
+ break;
+ case 3:
+ next = 1 << 21;
+ break;
+ default:
+ /* Cannot occur, but makes gcc happy */
+ return;
+ }
+ next = now + muldiv64(next, get_ticks_per_sec(), tb_env->tb_freq);
+ if (next == now)
+ next++;
+ qemu_mod_timer(ppc40x_timer->fit_timer, next);
+ env->spr[SPR_40x_TSR] |= 1 << 26;
+ if ((env->spr[SPR_40x_TCR] >> 23) & 0x1) {
+ ppc_set_irq(cpu, PPC_INTERRUPT_FIT, 1);
+ }
+ LOG_TB("%s: ir %d TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx "\n", __func__,
+ (int)((env->spr[SPR_40x_TCR] >> 23) & 0x1),
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR]);
+}
+
+/* Programmable interval timer */
+static void start_stop_pit (CPUPPCState *env, ppc_tb_t *tb_env, int is_excp)
+{
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ ppc40x_timer = tb_env->opaque;
+ if (ppc40x_timer->pit_reload <= 1 ||
+ !((env->spr[SPR_40x_TCR] >> 26) & 0x1) ||
+ (is_excp && !((env->spr[SPR_40x_TCR] >> 22) & 0x1))) {
+ /* Stop PIT */
+ LOG_TB("%s: stop PIT\n", __func__);
+ qemu_del_timer(tb_env->decr_timer);
+ } else {
+ LOG_TB("%s: start PIT %016" PRIx64 "\n",
+ __func__, ppc40x_timer->pit_reload);
+ now = qemu_get_clock_ns(vm_clock);
+ next = now + muldiv64(ppc40x_timer->pit_reload,
+ get_ticks_per_sec(), tb_env->decr_freq);
+ if (is_excp)
+ next += tb_env->decr_next - now;
+ if (next == now)
+ next++;
+ qemu_mod_timer(tb_env->decr_timer, next);
+ tb_env->decr_next = next;
+ }
+}
+
+static void cpu_4xx_pit_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ env->spr[SPR_40x_TSR] |= 1 << 27;
+ if ((env->spr[SPR_40x_TCR] >> 26) & 0x1) {
+ ppc_set_irq(cpu, ppc40x_timer->decr_excp, 1);
+ }
+ start_stop_pit(env, tb_env, 1);
+ LOG_TB("%s: ar %d ir %d TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx " "
+ "%016" PRIx64 "\n", __func__,
+ (int)((env->spr[SPR_40x_TCR] >> 22) & 0x1),
+ (int)((env->spr[SPR_40x_TCR] >> 26) & 0x1),
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR],
+ ppc40x_timer->pit_reload);
+}
+
+/* Watchdog timer */
+static void cpu_4xx_wdt_cb (void *opaque)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+ uint64_t now, next;
+
+ env = opaque;
+ cpu = ppc_env_get_cpu(env);
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ now = qemu_get_clock_ns(vm_clock);
+ switch ((env->spr[SPR_40x_TCR] >> 30) & 0x3) {
+ case 0:
+ next = 1 << 17;
+ break;
+ case 1:
+ next = 1 << 21;
+ break;
+ case 2:
+ next = 1 << 25;
+ break;
+ case 3:
+ next = 1 << 29;
+ break;
+ default:
+ /* Cannot occur, but makes gcc happy */
+ return;
+ }
+ next = now + muldiv64(next, get_ticks_per_sec(), tb_env->decr_freq);
+ if (next == now)
+ next++;
+ LOG_TB("%s: TCR " TARGET_FMT_lx " TSR " TARGET_FMT_lx "\n", __func__,
+ env->spr[SPR_40x_TCR], env->spr[SPR_40x_TSR]);
+ switch ((env->spr[SPR_40x_TSR] >> 30) & 0x3) {
+ case 0x0:
+ case 0x1:
+ qemu_mod_timer(ppc40x_timer->wdt_timer, next);
+ ppc40x_timer->wdt_next = next;
+ env->spr[SPR_40x_TSR] |= 1 << 31;
+ break;
+ case 0x2:
+ qemu_mod_timer(ppc40x_timer->wdt_timer, next);
+ ppc40x_timer->wdt_next = next;
+ env->spr[SPR_40x_TSR] |= 1 << 30;
+ if ((env->spr[SPR_40x_TCR] >> 27) & 0x1) {
+ ppc_set_irq(cpu, PPC_INTERRUPT_WDT, 1);
+ }
+ break;
+ case 0x3:
+ env->spr[SPR_40x_TSR] &= ~0x30000000;
+ env->spr[SPR_40x_TSR] |= env->spr[SPR_40x_TCR] & 0x30000000;
+ switch ((env->spr[SPR_40x_TCR] >> 28) & 0x3) {
+ case 0x0:
+ /* No reset */
+ break;
+ case 0x1: /* Core reset */
+ ppc40x_core_reset(cpu);
+ break;
+ case 0x2: /* Chip reset */
+ ppc40x_chip_reset(cpu);
+ break;
+ case 0x3: /* System reset */
+ ppc40x_system_reset(cpu);
+ break;
+ }
+ }
+}
+
+void store_40x_pit (CPUPPCState *env, target_ulong val)
+{
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ tb_env = env->tb_env;
+ ppc40x_timer = tb_env->opaque;
+ LOG_TB("%s val" TARGET_FMT_lx "\n", __func__, val);
+ ppc40x_timer->pit_reload = val;
+ start_stop_pit(env, tb_env, 0);
+}
+
+target_ulong load_40x_pit (CPUPPCState *env)
+{
+ return cpu_ppc_load_decr(env);
+}
+
+static void ppc_40x_set_tb_clk (void *opaque, uint32_t freq)
+{
+ CPUPPCState *env = opaque;
+ ppc_tb_t *tb_env = env->tb_env;
+
+ LOG_TB("%s set new frequency to %" PRIu32 "\n", __func__,
+ freq);
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ /* XXX: we should also update all timers */
+}
+
+clk_setup_cb ppc_40x_timers_init (CPUPPCState *env, uint32_t freq,
+ unsigned int decr_excp)
+{
+ ppc_tb_t *tb_env;
+ ppc40x_timer_t *ppc40x_timer;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ env->tb_env = tb_env;
+ tb_env->flags = PPC_DECR_UNDERFLOW_TRIGGERED;
+ ppc40x_timer = g_malloc0(sizeof(ppc40x_timer_t));
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ tb_env->opaque = ppc40x_timer;
+ LOG_TB("%s freq %" PRIu32 "\n", __func__, freq);
+ if (ppc40x_timer != NULL) {
+ /* We use decr timer for PIT */
+ tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &cpu_4xx_pit_cb, env);
+ ppc40x_timer->fit_timer =
+ qemu_new_timer_ns(vm_clock, &cpu_4xx_fit_cb, env);
+ ppc40x_timer->wdt_timer =
+ qemu_new_timer_ns(vm_clock, &cpu_4xx_wdt_cb, env);
+ ppc40x_timer->decr_excp = decr_excp;
+ }
+
+ return &ppc_40x_set_tb_clk;
+}
+
+/*****************************************************************************/
+/* Embedded PowerPC Device Control Registers */
+typedef struct ppc_dcrn_t ppc_dcrn_t;
+struct ppc_dcrn_t {
+ dcr_read_cb dcr_read;
+ dcr_write_cb dcr_write;
+ void *opaque;
+};
+
+/* XXX: on 460, DCR addresses are 32 bits wide,
+ * using DCRIPR to get the 22 upper bits of the DCR address
+ */
+#define DCRN_NB 1024
+struct ppc_dcr_t {
+ ppc_dcrn_t dcrn[DCRN_NB];
+ int (*read_error)(int dcrn);
+ int (*write_error)(int dcrn);
+};
+
+int ppc_dcr_read (ppc_dcr_t *dcr_env, int dcrn, uint32_t *valp)
+{
+ ppc_dcrn_t *dcr;
+
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ goto error;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->dcr_read == NULL)
+ goto error;
+ *valp = (*dcr->dcr_read)(dcr->opaque, dcrn);
+
+ return 0;
+
+ error:
+ if (dcr_env->read_error != NULL)
+ return (*dcr_env->read_error)(dcrn);
+
+ return -1;
+}
+
+int ppc_dcr_write (ppc_dcr_t *dcr_env, int dcrn, uint32_t val)
+{
+ ppc_dcrn_t *dcr;
+
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ goto error;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->dcr_write == NULL)
+ goto error;
+ (*dcr->dcr_write)(dcr->opaque, dcrn, val);
+
+ return 0;
+
+ error:
+ if (dcr_env->write_error != NULL)
+ return (*dcr_env->write_error)(dcrn);
+
+ return -1;
+}
+
+int ppc_dcr_register (CPUPPCState *env, int dcrn, void *opaque,
+ dcr_read_cb dcr_read, dcr_write_cb dcr_write)
+{
+ ppc_dcr_t *dcr_env;
+ ppc_dcrn_t *dcr;
+
+ dcr_env = env->dcr_env;
+ if (dcr_env == NULL)
+ return -1;
+ if (dcrn < 0 || dcrn >= DCRN_NB)
+ return -1;
+ dcr = &dcr_env->dcrn[dcrn];
+ if (dcr->opaque != NULL ||
+ dcr->dcr_read != NULL ||
+ dcr->dcr_write != NULL)
+ return -1;
+ dcr->opaque = opaque;
+ dcr->dcr_read = dcr_read;
+ dcr->dcr_write = dcr_write;
+
+ return 0;
+}
+
+int ppc_dcr_init (CPUPPCState *env, int (*read_error)(int dcrn),
+ int (*write_error)(int dcrn))
+{
+ ppc_dcr_t *dcr_env;
+
+ dcr_env = g_malloc0(sizeof(ppc_dcr_t));
+ dcr_env->read_error = read_error;
+ dcr_env->write_error = write_error;
+ env->dcr_env = dcr_env;
+
+ return 0;
+}
+
+/*****************************************************************************/
+/* Debug port */
+void PPC_debug_write (void *opaque, uint32_t addr, uint32_t val)
+{
+ addr &= 0xF;
+ switch (addr) {
+ case 0:
+ printf("%c", val);
+ break;
+ case 1:
+ printf("\n");
+ fflush(stdout);
+ break;
+ case 2:
+ printf("Set loglevel to %04" PRIx32 "\n", val);
+ qemu_set_log(val | 0x100);
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* NVRAM helpers */
+static inline uint32_t nvram_read (nvram_t *nvram, uint32_t addr)
+{
+ return (*nvram->read_fn)(nvram->opaque, addr);
+}
+
+static inline void nvram_write (nvram_t *nvram, uint32_t addr, uint32_t val)
+{
+ (*nvram->write_fn)(nvram->opaque, addr, val);
+}
+
+static void NVRAM_set_byte(nvram_t *nvram, uint32_t addr, uint8_t value)
+{
+ nvram_write(nvram, addr, value);
+}
+
+static uint8_t NVRAM_get_byte(nvram_t *nvram, uint32_t addr)
+{
+ return nvram_read(nvram, addr);
+}
+
+static void NVRAM_set_word(nvram_t *nvram, uint32_t addr, uint16_t value)
+{
+ nvram_write(nvram, addr, value >> 8);
+ nvram_write(nvram, addr + 1, value & 0xFF);
+}
+
+static uint16_t NVRAM_get_word(nvram_t *nvram, uint32_t addr)
+{
+ uint16_t tmp;
+
+ tmp = nvram_read(nvram, addr) << 8;
+ tmp |= nvram_read(nvram, addr + 1);
+
+ return tmp;
+}
+
+static void NVRAM_set_lword(nvram_t *nvram, uint32_t addr, uint32_t value)
+{
+ nvram_write(nvram, addr, value >> 24);
+ nvram_write(nvram, addr + 1, (value >> 16) & 0xFF);
+ nvram_write(nvram, addr + 2, (value >> 8) & 0xFF);
+ nvram_write(nvram, addr + 3, value & 0xFF);
+}
+
+uint32_t NVRAM_get_lword (nvram_t *nvram, uint32_t addr)
+{
+ uint32_t tmp;
+
+ tmp = nvram_read(nvram, addr) << 24;
+ tmp |= nvram_read(nvram, addr + 1) << 16;
+ tmp |= nvram_read(nvram, addr + 2) << 8;
+ tmp |= nvram_read(nvram, addr + 3);
+
+ return tmp;
+}
+
+static void NVRAM_set_string(nvram_t *nvram, uint32_t addr, const char *str,
+ uint32_t max)
+{
+ int i;
+
+ for (i = 0; i < max && str[i] != '\0'; i++) {
+ nvram_write(nvram, addr + i, str[i]);
+ }
+ nvram_write(nvram, addr + i, str[i]);
+ nvram_write(nvram, addr + max - 1, '\0');
+}
+
+int NVRAM_get_string (nvram_t *nvram, uint8_t *dst, uint16_t addr, int max)
+{
+ int i;
+
+ memset(dst, 0, max);
+ for (i = 0; i < max; i++) {
+ dst[i] = NVRAM_get_byte(nvram, addr + i);
+ if (dst[i] == '\0')
+ break;
+ }
+
+ return i;
+}
+
+static uint16_t NVRAM_crc_update (uint16_t prev, uint16_t value)
+{
+ uint16_t tmp;
+ uint16_t pd, pd1, pd2;
+
+ tmp = prev >> 8;
+ pd = prev ^ value;
+ pd1 = pd & 0x000F;
+ pd2 = ((pd >> 4) & 0x000F) ^ pd1;
+ tmp ^= (pd1 << 3) | (pd1 << 8);
+ tmp ^= pd2 | (pd2 << 7) | (pd2 << 12);
+
+ return tmp;
+}
+
+static uint16_t NVRAM_compute_crc (nvram_t *nvram, uint32_t start, uint32_t count)
+{
+ uint32_t i;
+ uint16_t crc = 0xFFFF;
+ int odd;
+
+ odd = count & 1;
+ count &= ~1;
+ for (i = 0; i != count; i++) {
+ crc = NVRAM_crc_update(crc, NVRAM_get_word(nvram, start + i));
+ }
+ if (odd) {
+ crc = NVRAM_crc_update(crc, NVRAM_get_byte(nvram, start + i) << 8);
+ }
+
+ return crc;
+}
+
+#define CMDLINE_ADDR 0x017ff000
+
+int PPC_NVRAM_set_params (nvram_t *nvram, uint16_t NVRAM_size,
+ const char *arch,
+ uint32_t RAM_size, int boot_device,
+ uint32_t kernel_image, uint32_t kernel_size,
+ const char *cmdline,
+ uint32_t initrd_image, uint32_t initrd_size,
+ uint32_t NVRAM_image,
+ int width, int height, int depth)
+{
+ uint16_t crc;
+
+ /* Set parameters for Open Hack'Ware BIOS */
+ NVRAM_set_string(nvram, 0x00, "QEMU_BIOS", 16);
+ NVRAM_set_lword(nvram, 0x10, 0x00000002); /* structure v2 */
+ NVRAM_set_word(nvram, 0x14, NVRAM_size);
+ NVRAM_set_string(nvram, 0x20, arch, 16);
+ NVRAM_set_lword(nvram, 0x30, RAM_size);
+ NVRAM_set_byte(nvram, 0x34, boot_device);
+ NVRAM_set_lword(nvram, 0x38, kernel_image);
+ NVRAM_set_lword(nvram, 0x3C, kernel_size);
+ if (cmdline) {
+ /* XXX: put the cmdline in NVRAM too ? */
+ pstrcpy_targphys("cmdline", CMDLINE_ADDR, RAM_size - CMDLINE_ADDR, cmdline);
+ NVRAM_set_lword(nvram, 0x40, CMDLINE_ADDR);
+ NVRAM_set_lword(nvram, 0x44, strlen(cmdline));
+ } else {
+ NVRAM_set_lword(nvram, 0x40, 0);
+ NVRAM_set_lword(nvram, 0x44, 0);
+ }
+ NVRAM_set_lword(nvram, 0x48, initrd_image);
+ NVRAM_set_lword(nvram, 0x4C, initrd_size);
+ NVRAM_set_lword(nvram, 0x50, NVRAM_image);
+
+ NVRAM_set_word(nvram, 0x54, width);
+ NVRAM_set_word(nvram, 0x56, height);
+ NVRAM_set_word(nvram, 0x58, depth);
+ crc = NVRAM_compute_crc(nvram, 0x00, 0xF8);
+ NVRAM_set_word(nvram, 0xFC, crc);
+
+ return 0;
+}
diff --git a/hw/ppc/ppc405_boards.c b/hw/ppc/ppc405_boards.c
new file mode 100644
index 0000000..ba443cf
--- /dev/null
+++ b/hw/ppc/ppc405_boards.c
@@ -0,0 +1,662 @@
+/*
+ * QEMU PowerPC 405 evaluation boards emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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 "hw/hw.h"
+#include "hw/ppc.h"
+#include "hw/ppc405.h"
+#include "hw/nvram.h"
+#include "hw/flash.h"
+#include "sysemu/sysemu.h"
+#include "block/block.h"
+#include "hw/boards.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+#include "sysemu/blockdev.h"
+#include "exec/address-spaces.h"
+
+#define BIOS_FILENAME "ppc405_rom.bin"
+#define BIOS_SIZE (2048 * 1024)
+
+#define KERNEL_LOAD_ADDR 0x00000000
+#define INITRD_LOAD_ADDR 0x01800000
+
+#define USE_FLASH_BIOS
+
+#define DEBUG_BOARD_INIT
+
+/*****************************************************************************/
+/* PPC405EP reference board (IBM) */
+/* Standalone board with:
+ * - PowerPC 405EP CPU
+ * - SDRAM (0x00000000)
+ * - Flash (0xFFF80000)
+ * - SRAM (0xFFF00000)
+ * - NVRAM (0xF0000000)
+ * - FPGA (0xF0300000)
+ */
+typedef struct ref405ep_fpga_t ref405ep_fpga_t;
+struct ref405ep_fpga_t {
+ uint8_t reg0;
+ uint8_t reg1;
+};
+
+static uint32_t ref405ep_fpga_readb (void *opaque, hwaddr addr)
+{
+ ref405ep_fpga_t *fpga;
+ uint32_t ret;
+
+ fpga = opaque;
+ switch (addr) {
+ case 0x0:
+ ret = fpga->reg0;
+ break;
+ case 0x1:
+ ret = fpga->reg1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void ref405ep_fpga_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_t *fpga;
+
+ fpga = opaque;
+ switch (addr) {
+ case 0x0:
+ /* Read only */
+ break;
+ case 0x1:
+ fpga->reg1 = value;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t ref405ep_fpga_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = ref405ep_fpga_readb(opaque, addr) << 8;
+ ret |= ref405ep_fpga_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void ref405ep_fpga_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_writeb(opaque, addr, (value >> 8) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 1, value & 0xFF);
+}
+
+static uint32_t ref405ep_fpga_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = ref405ep_fpga_readb(opaque, addr) << 24;
+ ret |= ref405ep_fpga_readb(opaque, addr + 1) << 16;
+ ret |= ref405ep_fpga_readb(opaque, addr + 2) << 8;
+ ret |= ref405ep_fpga_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void ref405ep_fpga_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ref405ep_fpga_writeb(opaque, addr, (value >> 24) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 1, (value >> 16) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 2, (value >> 8) & 0xFF);
+ ref405ep_fpga_writeb(opaque, addr + 3, value & 0xFF);
+}
+
+static const MemoryRegionOps ref405ep_fpga_ops = {
+ .old_mmio = {
+ .read = {
+ ref405ep_fpga_readb, ref405ep_fpga_readw, ref405ep_fpga_readl,
+ },
+ .write = {
+ ref405ep_fpga_writeb, ref405ep_fpga_writew, ref405ep_fpga_writel,
+ },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ref405ep_fpga_reset (void *opaque)
+{
+ ref405ep_fpga_t *fpga;
+
+ fpga = opaque;
+ fpga->reg0 = 0x00;
+ fpga->reg1 = 0x0F;
+}
+
+static void ref405ep_fpga_init(MemoryRegion *sysmem, uint32_t base)
+{
+ ref405ep_fpga_t *fpga;
+ MemoryRegion *fpga_memory = g_new(MemoryRegion, 1);
+
+ fpga = g_malloc0(sizeof(ref405ep_fpga_t));
+ memory_region_init_io(fpga_memory, &ref405ep_fpga_ops, fpga,
+ "fpga", 0x00000100);
+ memory_region_add_subregion(sysmem, base, fpga_memory);
+ qemu_register_reset(&ref405ep_fpga_reset, fpga);
+}
+
+static void ref405ep_init(QEMUMachineInitArgs *args)
+{
+ ram_addr_t ram_size = args->ram_size;
+ const char *kernel_filename = args->kernel_filename;
+ const char *kernel_cmdline = args->kernel_cmdline;
+ const char *initrd_filename = args->initrd_filename;
+ char *filename;
+ ppc4xx_bd_info_t bd;
+ CPUPPCState *env;
+ qemu_irq *pic;
+ MemoryRegion *bios;
+ MemoryRegion *sram = g_new(MemoryRegion, 1);
+ ram_addr_t bdloc;
+ MemoryRegion *ram_memories = g_malloc(2 * sizeof(*ram_memories));
+ hwaddr ram_bases[2], ram_sizes[2];
+ target_ulong sram_size;
+ long bios_size;
+ //int phy_addr = 0;
+ //static int phy_addr = 1;
+ target_ulong kernel_base, initrd_base;
+ long kernel_size, initrd_size;
+ int linux_boot;
+ int fl_idx, fl_sectors, len;
+ DriveInfo *dinfo;
+ MemoryRegion *sysmem = get_system_memory();
+
+ /* XXX: fix this */
+ memory_region_init_ram(&ram_memories[0], "ef405ep.ram", 0x08000000);
+ vmstate_register_ram_global(&ram_memories[0]);
+ ram_bases[0] = 0;
+ ram_sizes[0] = 0x08000000;
+ memory_region_init(&ram_memories[1], "ef405ep.ram1", 0);
+ ram_bases[1] = 0x00000000;
+ ram_sizes[1] = 0x00000000;
+ ram_size = 128 * 1024 * 1024;
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register cpu\n", __func__);
+#endif
+ env = ppc405ep_init(sysmem, ram_memories, ram_bases, ram_sizes,
+ 33333333, &pic, kernel_filename == NULL ? 0 : 1);
+ /* allocate SRAM */
+ sram_size = 512 * 1024;
+ memory_region_init_ram(sram, "ef405ep.sram", sram_size);
+ vmstate_register_ram_global(sram);
+ memory_region_add_subregion(sysmem, 0xFFF00000, sram);
+ /* allocate and load BIOS */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register BIOS\n", __func__);
+#endif
+ fl_idx = 0;
+#ifdef USE_FLASH_BIOS
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ bios_size = bdrv_getlength(dinfo->bdrv);
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr %lx '%s' %d\n",
+ fl_idx, bios_size, -bios_size,
+ bdrv_get_device_name(dinfo->bdrv), fl_sectors);
+#endif
+ pflash_cfi02_register((uint32_t)(-bios_size),
+ NULL, "ef405ep.bios", bios_size,
+ dinfo->bdrv, 65536, fl_sectors, 1,
+ 2, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ } else
+#endif
+ {
+#ifdef DEBUG_BOARD_INIT
+ printf("Load BIOS from file\n");
+#endif
+ bios = g_new(MemoryRegion, 1);
+ memory_region_init_ram(bios, "ef405ep.bios", BIOS_SIZE);
+ vmstate_register_ram_global(bios);
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image(filename, memory_region_get_ram_ptr(bios));
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ fprintf(stderr, "qemu: could not load PowerPC bios '%s'\n",
+ bios_name);
+ exit(1);
+ }
+ bios_size = (bios_size + 0xfff) & ~0xfff;
+ memory_region_set_readonly(bios, true);
+ memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios);
+ }
+ /* Register FPGA */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register FPGA\n", __func__);
+#endif
+ ref405ep_fpga_init(sysmem, 0xF0300000);
+ /* Register NVRAM */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register NVRAM\n", __func__);
+#endif
+ m48t59_init(NULL, 0xF0000000, 0, 8192, 8);
+ /* Load kernel */
+ linux_boot = (kernel_filename != NULL);
+ if (linux_boot) {
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: load kernel\n", __func__);
+#endif
+ memset(&bd, 0, sizeof(bd));
+ bd.bi_memstart = 0x00000000;
+ bd.bi_memsize = ram_size;
+ bd.bi_flashstart = -bios_size;
+ bd.bi_flashsize = -bios_size;
+ bd.bi_flashoffset = 0;
+ bd.bi_sramstart = 0xFFF00000;
+ bd.bi_sramsize = sram_size;
+ bd.bi_bootflags = 0;
+ bd.bi_intfreq = 133333333;
+ bd.bi_busfreq = 33333333;
+ bd.bi_baudrate = 115200;
+ bd.bi_s_version[0] = 'Q';
+ bd.bi_s_version[1] = 'M';
+ bd.bi_s_version[2] = 'U';
+ bd.bi_s_version[3] = '\0';
+ bd.bi_r_version[0] = 'Q';
+ bd.bi_r_version[1] = 'E';
+ bd.bi_r_version[2] = 'M';
+ bd.bi_r_version[3] = 'U';
+ bd.bi_r_version[4] = '\0';
+ bd.bi_procfreq = 133333333;
+ bd.bi_plb_busfreq = 33333333;
+ bd.bi_pci_busfreq = 33333333;
+ bd.bi_opbfreq = 33333333;
+ bdloc = ppc405_set_bootinfo(env, &bd, 0x00000001);
+ env->gpr[3] = bdloc;
+ kernel_base = KERNEL_LOAD_ADDR;
+ /* now we can load the kernel */
+ kernel_size = load_image_targphys(kernel_filename, kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ printf("Load kernel size %ld at " TARGET_FMT_lx,
+ kernel_size, kernel_base);
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = INITRD_LOAD_ADDR;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ env->gpr[4] = initrd_base;
+ env->gpr[5] = initrd_size;
+ if (kernel_cmdline != NULL) {
+ len = strlen(kernel_cmdline);
+ bdloc -= ((len + 255) & ~255);
+ cpu_physical_memory_write(bdloc, (void *)kernel_cmdline, len + 1);
+ env->gpr[6] = bdloc;
+ env->gpr[7] = bdloc + len;
+ } else {
+ env->gpr[6] = 0;
+ env->gpr[7] = 0;
+ }
+ env->nip = KERNEL_LOAD_ADDR;
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ bdloc = 0;
+ }
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: Done\n", __func__);
+#endif
+ printf("bdloc " RAM_ADDR_FMT "\n", bdloc);
+}
+
+static QEMUMachine ref405ep_machine = {
+ .name = "ref405ep",
+ .desc = "ref405ep",
+ .init = ref405ep_init,
+ DEFAULT_MACHINE_OPTIONS,
+};
+
+/*****************************************************************************/
+/* AMCC Taihu evaluation board */
+/* - PowerPC 405EP processor
+ * - SDRAM 128 MB at 0x00000000
+ * - Boot flash 2 MB at 0xFFE00000
+ * - Application flash 32 MB at 0xFC000000
+ * - 2 serial ports
+ * - 2 ethernet PHY
+ * - 1 USB 1.1 device 0x50000000
+ * - 1 LCD display 0x50100000
+ * - 1 CPLD 0x50100000
+ * - 1 I2C EEPROM
+ * - 1 I2C thermal sensor
+ * - a set of LEDs
+ * - bit-bang SPI port using GPIOs
+ * - 1 EBC interface connector 0 0x50200000
+ * - 1 cardbus controller + expansion slot.
+ * - 1 PCI expansion slot.
+ */
+typedef struct taihu_cpld_t taihu_cpld_t;
+struct taihu_cpld_t {
+ uint8_t reg0;
+ uint8_t reg1;
+};
+
+static uint32_t taihu_cpld_readb (void *opaque, hwaddr addr)
+{
+ taihu_cpld_t *cpld;
+ uint32_t ret;
+
+ cpld = opaque;
+ switch (addr) {
+ case 0x0:
+ ret = cpld->reg0;
+ break;
+ case 0x1:
+ ret = cpld->reg1;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void taihu_cpld_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_t *cpld;
+
+ cpld = opaque;
+ switch (addr) {
+ case 0x0:
+ /* Read only */
+ break;
+ case 0x1:
+ cpld->reg1 = value;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t taihu_cpld_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = taihu_cpld_readb(opaque, addr) << 8;
+ ret |= taihu_cpld_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void taihu_cpld_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_writeb(opaque, addr, (value >> 8) & 0xFF);
+ taihu_cpld_writeb(opaque, addr + 1, value & 0xFF);
+}
+
+static uint32_t taihu_cpld_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+ ret = taihu_cpld_readb(opaque, addr) << 24;
+ ret |= taihu_cpld_readb(opaque, addr + 1) << 16;
+ ret |= taihu_cpld_readb(opaque, addr + 2) << 8;
+ ret |= taihu_cpld_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void taihu_cpld_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ taihu_cpld_writel(opaque, addr, (value >> 24) & 0xFF);
+ taihu_cpld_writel(opaque, addr + 1, (value >> 16) & 0xFF);
+ taihu_cpld_writel(opaque, addr + 2, (value >> 8) & 0xFF);
+ taihu_cpld_writeb(opaque, addr + 3, value & 0xFF);
+}
+
+static const MemoryRegionOps taihu_cpld_ops = {
+ .old_mmio = {
+ .read = { taihu_cpld_readb, taihu_cpld_readw, taihu_cpld_readl, },
+ .write = { taihu_cpld_writeb, taihu_cpld_writew, taihu_cpld_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void taihu_cpld_reset (void *opaque)
+{
+ taihu_cpld_t *cpld;
+
+ cpld = opaque;
+ cpld->reg0 = 0x01;
+ cpld->reg1 = 0x80;
+}
+
+static void taihu_cpld_init(MemoryRegion *sysmem, uint32_t base)
+{
+ taihu_cpld_t *cpld;
+ MemoryRegion *cpld_memory = g_new(MemoryRegion, 1);
+
+ cpld = g_malloc0(sizeof(taihu_cpld_t));
+ memory_region_init_io(cpld_memory, &taihu_cpld_ops, cpld, "cpld", 0x100);
+ memory_region_add_subregion(sysmem, base, cpld_memory);
+ qemu_register_reset(&taihu_cpld_reset, cpld);
+}
+
+static void taihu_405ep_init(QEMUMachineInitArgs *args)
+{
+ ram_addr_t ram_size = args->ram_size;
+ const char *kernel_filename = args->kernel_filename;
+ const char *initrd_filename = args->initrd_filename;
+ char *filename;
+ qemu_irq *pic;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *bios;
+ MemoryRegion *ram_memories = g_malloc(2 * sizeof(*ram_memories));
+ hwaddr ram_bases[2], ram_sizes[2];
+ long bios_size;
+ target_ulong kernel_base, initrd_base;
+ long kernel_size, initrd_size;
+ int linux_boot;
+ int fl_idx, fl_sectors;
+ DriveInfo *dinfo;
+
+ /* RAM is soldered to the board so the size cannot be changed */
+ memory_region_init_ram(&ram_memories[0],
+ "taihu_405ep.ram-0", 0x04000000);
+ vmstate_register_ram_global(&ram_memories[0]);
+ ram_bases[0] = 0;
+ ram_sizes[0] = 0x04000000;
+ memory_region_init_ram(&ram_memories[1],
+ "taihu_405ep.ram-1", 0x04000000);
+ vmstate_register_ram_global(&ram_memories[1]);
+ ram_bases[1] = 0x04000000;
+ ram_sizes[1] = 0x04000000;
+ ram_size = 0x08000000;
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register cpu\n", __func__);
+#endif
+ ppc405ep_init(sysmem, ram_memories, ram_bases, ram_sizes,
+ 33333333, &pic, kernel_filename == NULL ? 0 : 1);
+ /* allocate and load BIOS */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register BIOS\n", __func__);
+#endif
+ fl_idx = 0;
+#if defined(USE_FLASH_BIOS)
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ bios_size = bdrv_getlength(dinfo->bdrv);
+ /* XXX: should check that size is 2MB */
+ // bios_size = 2 * 1024 * 1024;
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr %lx '%s' %d\n",
+ fl_idx, bios_size, -bios_size,
+ bdrv_get_device_name(dinfo->bdrv), fl_sectors);
+#endif
+ pflash_cfi02_register((uint32_t)(-bios_size),
+ NULL, "taihu_405ep.bios", bios_size,
+ dinfo->bdrv, 65536, fl_sectors, 1,
+ 4, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ } else
+#endif
+ {
+#ifdef DEBUG_BOARD_INIT
+ printf("Load BIOS from file\n");
+#endif
+ if (bios_name == NULL)
+ bios_name = BIOS_FILENAME;
+ bios = g_new(MemoryRegion, 1);
+ memory_region_init_ram(bios, "taihu_405ep.bios", BIOS_SIZE);
+ vmstate_register_ram_global(bios);
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
+ if (filename) {
+ bios_size = load_image(filename, memory_region_get_ram_ptr(bios));
+ g_free(filename);
+ } else {
+ bios_size = -1;
+ }
+ if (bios_size < 0 || bios_size > BIOS_SIZE) {
+ fprintf(stderr, "qemu: could not load PowerPC bios '%s'\n",
+ bios_name);
+ exit(1);
+ }
+ bios_size = (bios_size + 0xfff) & ~0xfff;
+ memory_region_set_readonly(bios, true);
+ memory_region_add_subregion(sysmem, (uint32_t)(-bios_size), bios);
+ }
+ /* Register Linux flash */
+ dinfo = drive_get(IF_PFLASH, 0, fl_idx);
+ if (dinfo) {
+ bios_size = bdrv_getlength(dinfo->bdrv);
+ /* XXX: should check that size is 32MB */
+ bios_size = 32 * 1024 * 1024;
+ fl_sectors = (bios_size + 65535) >> 16;
+#ifdef DEBUG_BOARD_INIT
+ printf("Register parallel flash %d size %lx"
+ " at addr " TARGET_FMT_lx " '%s'\n",
+ fl_idx, bios_size, (target_ulong)0xfc000000,
+ bdrv_get_device_name(dinfo->bdrv));
+#endif
+ pflash_cfi02_register(0xfc000000, NULL, "taihu_405ep.flash", bios_size,
+ dinfo->bdrv, 65536, fl_sectors, 1,
+ 4, 0x0001, 0x22DA, 0x0000, 0x0000, 0x555, 0x2AA,
+ 1);
+ fl_idx++;
+ }
+ /* Register CLPD & LCD display */
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: register CPLD\n", __func__);
+#endif
+ taihu_cpld_init(sysmem, 0x50100000);
+ /* Load kernel */
+ linux_boot = (kernel_filename != NULL);
+ if (linux_boot) {
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: load kernel\n", __func__);
+#endif
+ kernel_base = KERNEL_LOAD_ADDR;
+ /* now we can load the kernel */
+ kernel_size = load_image_targphys(kernel_filename, kernel_base,
+ ram_size - kernel_base);
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ /* load initrd */
+ if (initrd_filename) {
+ initrd_base = INITRD_LOAD_ADDR;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ ram_size - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr,
+ "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ } else {
+ kernel_base = 0;
+ kernel_size = 0;
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+#ifdef DEBUG_BOARD_INIT
+ printf("%s: Done\n", __func__);
+#endif
+}
+
+static QEMUMachine taihu_machine = {
+ .name = "taihu",
+ .desc = "taihu",
+ .init = taihu_405ep_init,
+ DEFAULT_MACHINE_OPTIONS,
+};
+
+static void ppc405_machine_init(void)
+{
+ qemu_register_machine(&ref405ep_machine);
+ qemu_register_machine(&taihu_machine);
+}
+
+machine_init(ppc405_machine_init);
diff --git a/hw/ppc/ppc405_uc.c b/hw/ppc/ppc405_uc.c
new file mode 100644
index 0000000..8465f6d
--- /dev/null
+++ b/hw/ppc/ppc405_uc.c
@@ -0,0 +1,2548 @@
+/*
+ * QEMU PowerPC 405 embedded processors emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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 "hw/hw.h"
+#include "hw/ppc.h"
+#include "hw/ppc405.h"
+#include "hw/serial.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+#define DEBUG_OPBA
+#define DEBUG_SDRAM
+#define DEBUG_GPIO
+#define DEBUG_SERIAL
+#define DEBUG_OCM
+//#define DEBUG_I2C
+#define DEBUG_GPT
+#define DEBUG_MAL
+#define DEBUG_CLOCKS
+//#define DEBUG_CLOCKS_LL
+
+ram_addr_t ppc405_set_bootinfo (CPUPPCState *env, ppc4xx_bd_info_t *bd,
+ uint32_t flags)
+{
+ ram_addr_t bdloc;
+ int i, n;
+
+ /* We put the bd structure at the top of memory */
+ if (bd->bi_memsize >= 0x01000000UL)
+ bdloc = 0x01000000UL - sizeof(struct ppc4xx_bd_info_t);
+ else
+ bdloc = bd->bi_memsize - sizeof(struct ppc4xx_bd_info_t);
+ stl_be_phys(bdloc + 0x00, bd->bi_memstart);
+ stl_be_phys(bdloc + 0x04, bd->bi_memsize);
+ stl_be_phys(bdloc + 0x08, bd->bi_flashstart);
+ stl_be_phys(bdloc + 0x0C, bd->bi_flashsize);
+ stl_be_phys(bdloc + 0x10, bd->bi_flashoffset);
+ stl_be_phys(bdloc + 0x14, bd->bi_sramstart);
+ stl_be_phys(bdloc + 0x18, bd->bi_sramsize);
+ stl_be_phys(bdloc + 0x1C, bd->bi_bootflags);
+ stl_be_phys(bdloc + 0x20, bd->bi_ipaddr);
+ for (i = 0; i < 6; i++) {
+ stb_phys(bdloc + 0x24 + i, bd->bi_enetaddr[i]);
+ }
+ stw_be_phys(bdloc + 0x2A, bd->bi_ethspeed);
+ stl_be_phys(bdloc + 0x2C, bd->bi_intfreq);
+ stl_be_phys(bdloc + 0x30, bd->bi_busfreq);
+ stl_be_phys(bdloc + 0x34, bd->bi_baudrate);
+ for (i = 0; i < 4; i++) {
+ stb_phys(bdloc + 0x38 + i, bd->bi_s_version[i]);
+ }
+ for (i = 0; i < 32; i++) {
+ stb_phys(bdloc + 0x3C + i, bd->bi_r_version[i]);
+ }
+ stl_be_phys(bdloc + 0x5C, bd->bi_plb_busfreq);
+ stl_be_phys(bdloc + 0x60, bd->bi_pci_busfreq);
+ for (i = 0; i < 6; i++) {
+ stb_phys(bdloc + 0x64 + i, bd->bi_pci_enetaddr[i]);
+ }
+ n = 0x6A;
+ if (flags & 0x00000001) {
+ for (i = 0; i < 6; i++)
+ stb_phys(bdloc + n++, bd->bi_pci_enetaddr2[i]);
+ }
+ stl_be_phys(bdloc + n, bd->bi_opbfreq);
+ n += 4;
+ for (i = 0; i < 2; i++) {
+ stl_be_phys(bdloc + n, bd->bi_iic_fast[i]);
+ n += 4;
+ }
+
+ return bdloc;
+}
+
+/*****************************************************************************/
+/* Shared peripherals */
+
+/*****************************************************************************/
+/* Peripheral local bus arbitrer */
+enum {
+ PLB0_BESR = 0x084,
+ PLB0_BEAR = 0x086,
+ PLB0_ACR = 0x087,
+};
+
+typedef struct ppc4xx_plb_t ppc4xx_plb_t;
+struct ppc4xx_plb_t {
+ uint32_t acr;
+ uint32_t bear;
+ uint32_t besr;
+};
+
+static uint32_t dcr_read_plb (void *opaque, int dcrn)
+{
+ ppc4xx_plb_t *plb;
+ uint32_t ret;
+
+ plb = opaque;
+ switch (dcrn) {
+ case PLB0_ACR:
+ ret = plb->acr;
+ break;
+ case PLB0_BEAR:
+ ret = plb->bear;
+ break;
+ case PLB0_BESR:
+ ret = plb->besr;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_plb (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = opaque;
+ switch (dcrn) {
+ case PLB0_ACR:
+ /* We don't care about the actual parameters written as
+ * we don't manage any priorities on the bus
+ */
+ plb->acr = val & 0xF8000000;
+ break;
+ case PLB0_BEAR:
+ /* Read only */
+ break;
+ case PLB0_BESR:
+ /* Write-clear */
+ plb->besr &= ~val;
+ break;
+ }
+}
+
+static void ppc4xx_plb_reset (void *opaque)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = opaque;
+ plb->acr = 0x00000000;
+ plb->bear = 0x00000000;
+ plb->besr = 0x00000000;
+}
+
+static void ppc4xx_plb_init(CPUPPCState *env)
+{
+ ppc4xx_plb_t *plb;
+
+ plb = g_malloc0(sizeof(ppc4xx_plb_t));
+ ppc_dcr_register(env, PLB0_ACR, plb, &dcr_read_plb, &dcr_write_plb);
+ ppc_dcr_register(env, PLB0_BEAR, plb, &dcr_read_plb, &dcr_write_plb);
+ ppc_dcr_register(env, PLB0_BESR, plb, &dcr_read_plb, &dcr_write_plb);
+ qemu_register_reset(ppc4xx_plb_reset, plb);
+}
+
+/*****************************************************************************/
+/* PLB to OPB bridge */
+enum {
+ POB0_BESR0 = 0x0A0,
+ POB0_BESR1 = 0x0A2,
+ POB0_BEAR = 0x0A4,
+};
+
+typedef struct ppc4xx_pob_t ppc4xx_pob_t;
+struct ppc4xx_pob_t {
+ uint32_t bear;
+ uint32_t besr0;
+ uint32_t besr1;
+};
+
+static uint32_t dcr_read_pob (void *opaque, int dcrn)
+{
+ ppc4xx_pob_t *pob;
+ uint32_t ret;
+
+ pob = opaque;
+ switch (dcrn) {
+ case POB0_BEAR:
+ ret = pob->bear;
+ break;
+ case POB0_BESR0:
+ ret = pob->besr0;
+ break;
+ case POB0_BESR1:
+ ret = pob->besr1;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_pob (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = opaque;
+ switch (dcrn) {
+ case POB0_BEAR:
+ /* Read only */
+ break;
+ case POB0_BESR0:
+ /* Write-clear */
+ pob->besr0 &= ~val;
+ break;
+ case POB0_BESR1:
+ /* Write-clear */
+ pob->besr1 &= ~val;
+ break;
+ }
+}
+
+static void ppc4xx_pob_reset (void *opaque)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = opaque;
+ /* No error */
+ pob->bear = 0x00000000;
+ pob->besr0 = 0x0000000;
+ pob->besr1 = 0x0000000;
+}
+
+static void ppc4xx_pob_init(CPUPPCState *env)
+{
+ ppc4xx_pob_t *pob;
+
+ pob = g_malloc0(sizeof(ppc4xx_pob_t));
+ ppc_dcr_register(env, POB0_BEAR, pob, &dcr_read_pob, &dcr_write_pob);
+ ppc_dcr_register(env, POB0_BESR0, pob, &dcr_read_pob, &dcr_write_pob);
+ ppc_dcr_register(env, POB0_BESR1, pob, &dcr_read_pob, &dcr_write_pob);
+ qemu_register_reset(ppc4xx_pob_reset, pob);
+}
+
+/*****************************************************************************/
+/* OPB arbitrer */
+typedef struct ppc4xx_opba_t ppc4xx_opba_t;
+struct ppc4xx_opba_t {
+ MemoryRegion io;
+ uint8_t cr;
+ uint8_t pr;
+};
+
+static uint32_t opba_readb (void *opaque, hwaddr addr)
+{
+ ppc4xx_opba_t *opba;
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ opba = opaque;
+ switch (addr) {
+ case 0x00:
+ ret = opba->cr;
+ break;
+ case 0x01:
+ ret = opba->pr;
+ break;
+ default:
+ ret = 0x00;
+ break;
+ }
+
+ return ret;
+}
+
+static void opba_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_opba_t *opba;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba = opaque;
+ switch (addr) {
+ case 0x00:
+ opba->cr = value & 0xF8;
+ break;
+ case 0x01:
+ opba->pr = value & 0xFF;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t opba_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = opba_readb(opaque, addr) << 8;
+ ret |= opba_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void opba_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba_writeb(opaque, addr, value >> 8);
+ opba_writeb(opaque, addr + 1, value);
+}
+
+static uint32_t opba_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = opba_readb(opaque, addr) << 24;
+ ret |= opba_readb(opaque, addr + 1) << 16;
+
+ return ret;
+}
+
+static void opba_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_OPBA
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ opba_writeb(opaque, addr, value >> 24);
+ opba_writeb(opaque, addr + 1, value >> 16);
+}
+
+static const MemoryRegionOps opba_ops = {
+ .old_mmio = {
+ .read = { opba_readb, opba_readw, opba_readl, },
+ .write = { opba_writeb, opba_writew, opba_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_opba_reset (void *opaque)
+{
+ ppc4xx_opba_t *opba;
+
+ opba = opaque;
+ opba->cr = 0x00; /* No dynamic priorities - park disabled */
+ opba->pr = 0x11;
+}
+
+static void ppc4xx_opba_init(hwaddr base)
+{
+ ppc4xx_opba_t *opba;
+
+ opba = g_malloc0(sizeof(ppc4xx_opba_t));
+#ifdef DEBUG_OPBA
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&opba->io, &opba_ops, opba, "opba", 0x002);
+ memory_region_add_subregion(get_system_memory(), base, &opba->io);
+ qemu_register_reset(ppc4xx_opba_reset, opba);
+}
+
+/*****************************************************************************/
+/* Code decompression controller */
+/* XXX: TODO */
+
+/*****************************************************************************/
+/* Peripheral controller */
+typedef struct ppc4xx_ebc_t ppc4xx_ebc_t;
+struct ppc4xx_ebc_t {
+ uint32_t addr;
+ uint32_t bcr[8];
+ uint32_t bap[8];
+ uint32_t bear;
+ uint32_t besr0;
+ uint32_t besr1;
+ uint32_t cfg;
+};
+
+enum {
+ EBC0_CFGADDR = 0x012,
+ EBC0_CFGDATA = 0x013,
+};
+
+static uint32_t dcr_read_ebc (void *opaque, int dcrn)
+{
+ ppc4xx_ebc_t *ebc;
+ uint32_t ret;
+
+ ebc = opaque;
+ switch (dcrn) {
+ case EBC0_CFGADDR:
+ ret = ebc->addr;
+ break;
+ case EBC0_CFGDATA:
+ switch (ebc->addr) {
+ case 0x00: /* B0CR */
+ ret = ebc->bcr[0];
+ break;
+ case 0x01: /* B1CR */
+ ret = ebc->bcr[1];
+ break;
+ case 0x02: /* B2CR */
+ ret = ebc->bcr[2];
+ break;
+ case 0x03: /* B3CR */
+ ret = ebc->bcr[3];
+ break;
+ case 0x04: /* B4CR */
+ ret = ebc->bcr[4];
+ break;
+ case 0x05: /* B5CR */
+ ret = ebc->bcr[5];
+ break;
+ case 0x06: /* B6CR */
+ ret = ebc->bcr[6];
+ break;
+ case 0x07: /* B7CR */
+ ret = ebc->bcr[7];
+ break;
+ case 0x10: /* B0AP */
+ ret = ebc->bap[0];
+ break;
+ case 0x11: /* B1AP */
+ ret = ebc->bap[1];
+ break;
+ case 0x12: /* B2AP */
+ ret = ebc->bap[2];
+ break;
+ case 0x13: /* B3AP */
+ ret = ebc->bap[3];
+ break;
+ case 0x14: /* B4AP */
+ ret = ebc->bap[4];
+ break;
+ case 0x15: /* B5AP */
+ ret = ebc->bap[5];
+ break;
+ case 0x16: /* B6AP */
+ ret = ebc->bap[6];
+ break;
+ case 0x17: /* B7AP */
+ ret = ebc->bap[7];
+ break;
+ case 0x20: /* BEAR */
+ ret = ebc->bear;
+ break;
+ case 0x21: /* BESR0 */
+ ret = ebc->besr0;
+ break;
+ case 0x22: /* BESR1 */
+ ret = ebc->besr1;
+ break;
+ case 0x23: /* CFG */
+ ret = ebc->cfg;
+ break;
+ default:
+ ret = 0x00000000;
+ break;
+ }
+ break;
+ default:
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_ebc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_ebc_t *ebc;
+
+ ebc = opaque;
+ switch (dcrn) {
+ case EBC0_CFGADDR:
+ ebc->addr = val;
+ break;
+ case EBC0_CFGDATA:
+ switch (ebc->addr) {
+ case 0x00: /* B0CR */
+ break;
+ case 0x01: /* B1CR */
+ break;
+ case 0x02: /* B2CR */
+ break;
+ case 0x03: /* B3CR */
+ break;
+ case 0x04: /* B4CR */
+ break;
+ case 0x05: /* B5CR */
+ break;
+ case 0x06: /* B6CR */
+ break;
+ case 0x07: /* B7CR */
+ break;
+ case 0x10: /* B0AP */
+ break;
+ case 0x11: /* B1AP */
+ break;
+ case 0x12: /* B2AP */
+ break;
+ case 0x13: /* B3AP */
+ break;
+ case 0x14: /* B4AP */
+ break;
+ case 0x15: /* B5AP */
+ break;
+ case 0x16: /* B6AP */
+ break;
+ case 0x17: /* B7AP */
+ break;
+ case 0x20: /* BEAR */
+ break;
+ case 0x21: /* BESR0 */
+ break;
+ case 0x22: /* BESR1 */
+ break;
+ case 0x23: /* CFG */
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void ebc_reset (void *opaque)
+{
+ ppc4xx_ebc_t *ebc;
+ int i;
+
+ ebc = opaque;
+ ebc->addr = 0x00000000;
+ ebc->bap[0] = 0x7F8FFE80;
+ ebc->bcr[0] = 0xFFE28000;
+ for (i = 0; i < 8; i++) {
+ ebc->bap[i] = 0x00000000;
+ ebc->bcr[i] = 0x00000000;
+ }
+ ebc->besr0 = 0x00000000;
+ ebc->besr1 = 0x00000000;
+ ebc->cfg = 0x80400000;
+}
+
+static void ppc405_ebc_init(CPUPPCState *env)
+{
+ ppc4xx_ebc_t *ebc;
+
+ ebc = g_malloc0(sizeof(ppc4xx_ebc_t));
+ qemu_register_reset(&ebc_reset, ebc);
+ ppc_dcr_register(env, EBC0_CFGADDR,
+ ebc, &dcr_read_ebc, &dcr_write_ebc);
+ ppc_dcr_register(env, EBC0_CFGDATA,
+ ebc, &dcr_read_ebc, &dcr_write_ebc);
+}
+
+/*****************************************************************************/
+/* DMA controller */
+enum {
+ DMA0_CR0 = 0x100,
+ DMA0_CT0 = 0x101,
+ DMA0_DA0 = 0x102,
+ DMA0_SA0 = 0x103,
+ DMA0_SG0 = 0x104,
+ DMA0_CR1 = 0x108,
+ DMA0_CT1 = 0x109,
+ DMA0_DA1 = 0x10A,
+ DMA0_SA1 = 0x10B,
+ DMA0_SG1 = 0x10C,
+ DMA0_CR2 = 0x110,
+ DMA0_CT2 = 0x111,
+ DMA0_DA2 = 0x112,
+ DMA0_SA2 = 0x113,
+ DMA0_SG2 = 0x114,
+ DMA0_CR3 = 0x118,
+ DMA0_CT3 = 0x119,
+ DMA0_DA3 = 0x11A,
+ DMA0_SA3 = 0x11B,
+ DMA0_SG3 = 0x11C,
+ DMA0_SR = 0x120,
+ DMA0_SGC = 0x123,
+ DMA0_SLP = 0x125,
+ DMA0_POL = 0x126,
+};
+
+typedef struct ppc405_dma_t ppc405_dma_t;
+struct ppc405_dma_t {
+ qemu_irq irqs[4];
+ uint32_t cr[4];
+ uint32_t ct[4];
+ uint32_t da[4];
+ uint32_t sa[4];
+ uint32_t sg[4];
+ uint32_t sr;
+ uint32_t sgc;
+ uint32_t slp;
+ uint32_t pol;
+};
+
+static uint32_t dcr_read_dma (void *opaque, int dcrn)
+{
+ return 0;
+}
+
+static void dcr_write_dma (void *opaque, int dcrn, uint32_t val)
+{
+}
+
+static void ppc405_dma_reset (void *opaque)
+{
+ ppc405_dma_t *dma;
+ int i;
+
+ dma = opaque;
+ for (i = 0; i < 4; i++) {
+ dma->cr[i] = 0x00000000;
+ dma->ct[i] = 0x00000000;
+ dma->da[i] = 0x00000000;
+ dma->sa[i] = 0x00000000;
+ dma->sg[i] = 0x00000000;
+ }
+ dma->sr = 0x00000000;
+ dma->sgc = 0x00000000;
+ dma->slp = 0x7C000000;
+ dma->pol = 0x00000000;
+}
+
+static void ppc405_dma_init(CPUPPCState *env, qemu_irq irqs[4])
+{
+ ppc405_dma_t *dma;
+
+ dma = g_malloc0(sizeof(ppc405_dma_t));
+ memcpy(dma->irqs, irqs, 4 * sizeof(qemu_irq));
+ qemu_register_reset(&ppc405_dma_reset, dma);
+ ppc_dcr_register(env, DMA0_CR0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG0,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG1,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG2,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CR3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_CT3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_DA3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SA3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SG3,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SR,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SGC,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_SLP,
+ dma, &dcr_read_dma, &dcr_write_dma);
+ ppc_dcr_register(env, DMA0_POL,
+ dma, &dcr_read_dma, &dcr_write_dma);
+}
+
+/*****************************************************************************/
+/* GPIO */
+typedef struct ppc405_gpio_t ppc405_gpio_t;
+struct ppc405_gpio_t {
+ MemoryRegion io;
+ uint32_t or;
+ uint32_t tcr;
+ uint32_t osrh;
+ uint32_t osrl;
+ uint32_t tsrh;
+ uint32_t tsrl;
+ uint32_t odr;
+ uint32_t ir;
+ uint32_t rr1;
+ uint32_t isr1h;
+ uint32_t isr1l;
+};
+
+static uint32_t ppc405_gpio_readb (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static uint32_t ppc405_gpio_readw (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static uint32_t ppc405_gpio_readl (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+
+ return 0;
+}
+
+static void ppc405_gpio_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_GPIO
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+}
+
+static const MemoryRegionOps ppc405_gpio_ops = {
+ .old_mmio = {
+ .read = { ppc405_gpio_readb, ppc405_gpio_readw, ppc405_gpio_readl, },
+ .write = { ppc405_gpio_writeb, ppc405_gpio_writew, ppc405_gpio_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc405_gpio_reset (void *opaque)
+{
+}
+
+static void ppc405_gpio_init(hwaddr base)
+{
+ ppc405_gpio_t *gpio;
+
+ gpio = g_malloc0(sizeof(ppc405_gpio_t));
+#ifdef DEBUG_GPIO
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&gpio->io, &ppc405_gpio_ops, gpio, "pgio", 0x038);
+ memory_region_add_subregion(get_system_memory(), base, &gpio->io);
+ qemu_register_reset(&ppc405_gpio_reset, gpio);
+}
+
+/*****************************************************************************/
+/* On Chip Memory */
+enum {
+ OCM0_ISARC = 0x018,
+ OCM0_ISACNTL = 0x019,
+ OCM0_DSARC = 0x01A,
+ OCM0_DSACNTL = 0x01B,
+};
+
+typedef struct ppc405_ocm_t ppc405_ocm_t;
+struct ppc405_ocm_t {
+ MemoryRegion ram;
+ MemoryRegion isarc_ram;
+ MemoryRegion dsarc_ram;
+ uint32_t isarc;
+ uint32_t isacntl;
+ uint32_t dsarc;
+ uint32_t dsacntl;
+};
+
+static void ocm_update_mappings (ppc405_ocm_t *ocm,
+ uint32_t isarc, uint32_t isacntl,
+ uint32_t dsarc, uint32_t dsacntl)
+{
+#ifdef DEBUG_OCM
+ printf("OCM update ISA %08" PRIx32 " %08" PRIx32 " (%08" PRIx32
+ " %08" PRIx32 ") DSA %08" PRIx32 " %08" PRIx32
+ " (%08" PRIx32 " %08" PRIx32 ")\n",
+ isarc, isacntl, dsarc, dsacntl,
+ ocm->isarc, ocm->isacntl, ocm->dsarc, ocm->dsacntl);
+#endif
+ if (ocm->isarc != isarc ||
+ (ocm->isacntl & 0x80000000) != (isacntl & 0x80000000)) {
+ if (ocm->isacntl & 0x80000000) {
+ /* Unmap previously assigned memory region */
+ printf("OCM unmap ISA %08" PRIx32 "\n", ocm->isarc);
+ memory_region_del_subregion(get_system_memory(), &ocm->isarc_ram);
+ }
+ if (isacntl & 0x80000000) {
+ /* Map new instruction memory region */
+#ifdef DEBUG_OCM
+ printf("OCM map ISA %08" PRIx32 "\n", isarc);
+#endif
+ memory_region_add_subregion(get_system_memory(), isarc,
+ &ocm->isarc_ram);
+ }
+ }
+ if (ocm->dsarc != dsarc ||
+ (ocm->dsacntl & 0x80000000) != (dsacntl & 0x80000000)) {
+ if (ocm->dsacntl & 0x80000000) {
+ /* Beware not to unmap the region we just mapped */
+ if (!(isacntl & 0x80000000) || ocm->dsarc != isarc) {
+ /* Unmap previously assigned memory region */
+#ifdef DEBUG_OCM
+ printf("OCM unmap DSA %08" PRIx32 "\n", ocm->dsarc);
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &ocm->dsarc_ram);
+ }
+ }
+ if (dsacntl & 0x80000000) {
+ /* Beware not to remap the region we just mapped */
+ if (!(isacntl & 0x80000000) || dsarc != isarc) {
+ /* Map new data memory region */
+#ifdef DEBUG_OCM
+ printf("OCM map DSA %08" PRIx32 "\n", dsarc);
+#endif
+ memory_region_add_subregion(get_system_memory(), dsarc,
+ &ocm->dsarc_ram);
+ }
+ }
+ }
+}
+
+static uint32_t dcr_read_ocm (void *opaque, int dcrn)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t ret;
+
+ ocm = opaque;
+ switch (dcrn) {
+ case OCM0_ISARC:
+ ret = ocm->isarc;
+ break;
+ case OCM0_ISACNTL:
+ ret = ocm->isacntl;
+ break;
+ case OCM0_DSARC:
+ ret = ocm->dsarc;
+ break;
+ case OCM0_DSACNTL:
+ ret = ocm->dsacntl;
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_ocm (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t isarc, dsarc, isacntl, dsacntl;
+
+ ocm = opaque;
+ isarc = ocm->isarc;
+ dsarc = ocm->dsarc;
+ isacntl = ocm->isacntl;
+ dsacntl = ocm->dsacntl;
+ switch (dcrn) {
+ case OCM0_ISARC:
+ isarc = val & 0xFC000000;
+ break;
+ case OCM0_ISACNTL:
+ isacntl = val & 0xC0000000;
+ break;
+ case OCM0_DSARC:
+ isarc = val & 0xFC000000;
+ break;
+ case OCM0_DSACNTL:
+ isacntl = val & 0xC0000000;
+ break;
+ }
+ ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl);
+ ocm->isarc = isarc;
+ ocm->dsarc = dsarc;
+ ocm->isacntl = isacntl;
+ ocm->dsacntl = dsacntl;
+}
+
+static void ocm_reset (void *opaque)
+{
+ ppc405_ocm_t *ocm;
+ uint32_t isarc, dsarc, isacntl, dsacntl;
+
+ ocm = opaque;
+ isarc = 0x00000000;
+ isacntl = 0x00000000;
+ dsarc = 0x00000000;
+ dsacntl = 0x00000000;
+ ocm_update_mappings(ocm, isarc, isacntl, dsarc, dsacntl);
+ ocm->isarc = isarc;
+ ocm->dsarc = dsarc;
+ ocm->isacntl = isacntl;
+ ocm->dsacntl = dsacntl;
+}
+
+static void ppc405_ocm_init(CPUPPCState *env)
+{
+ ppc405_ocm_t *ocm;
+
+ ocm = g_malloc0(sizeof(ppc405_ocm_t));
+ /* XXX: Size is 4096 or 0x04000000 */
+ memory_region_init_ram(&ocm->isarc_ram, "ppc405.ocm", 4096);
+ vmstate_register_ram_global(&ocm->isarc_ram);
+ memory_region_init_alias(&ocm->dsarc_ram, "ppc405.dsarc", &ocm->isarc_ram,
+ 0, 4096);
+ qemu_register_reset(&ocm_reset, ocm);
+ ppc_dcr_register(env, OCM0_ISARC,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_ISACNTL,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_DSARC,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+ ppc_dcr_register(env, OCM0_DSACNTL,
+ ocm, &dcr_read_ocm, &dcr_write_ocm);
+}
+
+/*****************************************************************************/
+/* I2C controller */
+typedef struct ppc4xx_i2c_t ppc4xx_i2c_t;
+struct ppc4xx_i2c_t {
+ qemu_irq irq;
+ MemoryRegion iomem;
+ uint8_t mdata;
+ uint8_t lmadr;
+ uint8_t hmadr;
+ uint8_t cntl;
+ uint8_t mdcntl;
+ uint8_t sts;
+ uint8_t extsts;
+ uint8_t sdata;
+ uint8_t lsadr;
+ uint8_t hsadr;
+ uint8_t clkdiv;
+ uint8_t intrmsk;
+ uint8_t xfrcnt;
+ uint8_t xtcntlss;
+ uint8_t directcntl;
+};
+
+static uint32_t ppc4xx_i2c_readb (void *opaque, hwaddr addr)
+{
+ ppc4xx_i2c_t *i2c;
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ i2c = opaque;
+ switch (addr) {
+ case 0x00:
+ // i2c_readbyte(&i2c->mdata);
+ ret = i2c->mdata;
+ break;
+ case 0x02:
+ ret = i2c->sdata;
+ break;
+ case 0x04:
+ ret = i2c->lmadr;
+ break;
+ case 0x05:
+ ret = i2c->hmadr;
+ break;
+ case 0x06:
+ ret = i2c->cntl;
+ break;
+ case 0x07:
+ ret = i2c->mdcntl;
+ break;
+ case 0x08:
+ ret = i2c->sts;
+ break;
+ case 0x09:
+ ret = i2c->extsts;
+ break;
+ case 0x0A:
+ ret = i2c->lsadr;
+ break;
+ case 0x0B:
+ ret = i2c->hsadr;
+ break;
+ case 0x0C:
+ ret = i2c->clkdiv;
+ break;
+ case 0x0D:
+ ret = i2c->intrmsk;
+ break;
+ case 0x0E:
+ ret = i2c->xfrcnt;
+ break;
+ case 0x0F:
+ ret = i2c->xtcntlss;
+ break;
+ case 0x10:
+ ret = i2c->directcntl;
+ break;
+ default:
+ ret = 0x00;
+ break;
+ }
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " %02" PRIx32 "\n", __func__, addr, ret);
+#endif
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_i2c_t *i2c;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ i2c = opaque;
+ switch (addr) {
+ case 0x00:
+ i2c->mdata = value;
+ // i2c_sendbyte(&i2c->mdata);
+ break;
+ case 0x02:
+ i2c->sdata = value;
+ break;
+ case 0x04:
+ i2c->lmadr = value;
+ break;
+ case 0x05:
+ i2c->hmadr = value;
+ break;
+ case 0x06:
+ i2c->cntl = value;
+ break;
+ case 0x07:
+ i2c->mdcntl = value & 0xDF;
+ break;
+ case 0x08:
+ i2c->sts &= ~(value & 0x0A);
+ break;
+ case 0x09:
+ i2c->extsts &= ~(value & 0x8F);
+ break;
+ case 0x0A:
+ i2c->lsadr = value;
+ break;
+ case 0x0B:
+ i2c->hsadr = value;
+ break;
+ case 0x0C:
+ i2c->clkdiv = value;
+ break;
+ case 0x0D:
+ i2c->intrmsk = value;
+ break;
+ case 0x0E:
+ i2c->xfrcnt = value & 0x77;
+ break;
+ case 0x0F:
+ i2c->xtcntlss = value;
+ break;
+ case 0x10:
+ i2c->directcntl = value & 0x7;
+ break;
+ }
+}
+
+static uint32_t ppc4xx_i2c_readw (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = ppc4xx_i2c_readb(opaque, addr) << 8;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 1);
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ ppc4xx_i2c_writeb(opaque, addr, value >> 8);
+ ppc4xx_i2c_writeb(opaque, addr + 1, value);
+}
+
+static uint32_t ppc4xx_i2c_readl (void *opaque, hwaddr addr)
+{
+ uint32_t ret;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ ret = ppc4xx_i2c_readb(opaque, addr) << 24;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 1) << 16;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 2) << 8;
+ ret |= ppc4xx_i2c_readb(opaque, addr + 3);
+
+ return ret;
+}
+
+static void ppc4xx_i2c_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ ppc4xx_i2c_writeb(opaque, addr, value >> 24);
+ ppc4xx_i2c_writeb(opaque, addr + 1, value >> 16);
+ ppc4xx_i2c_writeb(opaque, addr + 2, value >> 8);
+ ppc4xx_i2c_writeb(opaque, addr + 3, value);
+}
+
+static const MemoryRegionOps i2c_ops = {
+ .old_mmio = {
+ .read = { ppc4xx_i2c_readb, ppc4xx_i2c_readw, ppc4xx_i2c_readl, },
+ .write = { ppc4xx_i2c_writeb, ppc4xx_i2c_writew, ppc4xx_i2c_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_i2c_reset (void *opaque)
+{
+ ppc4xx_i2c_t *i2c;
+
+ i2c = opaque;
+ i2c->mdata = 0x00;
+ i2c->sdata = 0x00;
+ i2c->cntl = 0x00;
+ i2c->mdcntl = 0x00;
+ i2c->sts = 0x00;
+ i2c->extsts = 0x00;
+ i2c->clkdiv = 0x00;
+ i2c->xfrcnt = 0x00;
+ i2c->directcntl = 0x0F;
+}
+
+static void ppc405_i2c_init(hwaddr base, qemu_irq irq)
+{
+ ppc4xx_i2c_t *i2c;
+
+ i2c = g_malloc0(sizeof(ppc4xx_i2c_t));
+ i2c->irq = irq;
+#ifdef DEBUG_I2C
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&i2c->iomem, &i2c_ops, i2c, "i2c", 0x011);
+ memory_region_add_subregion(get_system_memory(), base, &i2c->iomem);
+ qemu_register_reset(ppc4xx_i2c_reset, i2c);
+}
+
+/*****************************************************************************/
+/* General purpose timers */
+typedef struct ppc4xx_gpt_t ppc4xx_gpt_t;
+struct ppc4xx_gpt_t {
+ MemoryRegion iomem;
+ int64_t tb_offset;
+ uint32_t tb_freq;
+ struct QEMUTimer *timer;
+ qemu_irq irqs[5];
+ uint32_t oe;
+ uint32_t ol;
+ uint32_t im;
+ uint32_t is;
+ uint32_t ie;
+ uint32_t comp[5];
+ uint32_t mask[5];
+};
+
+static uint32_t ppc4xx_gpt_readb (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ /* XXX: generate a bus fault */
+ return -1;
+}
+
+static void ppc4xx_gpt_writeb (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ /* XXX: generate a bus fault */
+}
+
+static uint32_t ppc4xx_gpt_readw (void *opaque, hwaddr addr)
+{
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ /* XXX: generate a bus fault */
+ return -1;
+}
+
+static void ppc4xx_gpt_writew (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ /* XXX: generate a bus fault */
+}
+
+static int ppc4xx_gpt_compare (ppc4xx_gpt_t *gpt, int n)
+{
+ /* XXX: TODO */
+ return 0;
+}
+
+static void ppc4xx_gpt_set_output (ppc4xx_gpt_t *gpt, int n, int level)
+{
+ /* XXX: TODO */
+}
+
+static void ppc4xx_gpt_set_outputs (ppc4xx_gpt_t *gpt)
+{
+ uint32_t mask;
+ int i;
+
+ mask = 0x80000000;
+ for (i = 0; i < 5; i++) {
+ if (gpt->oe & mask) {
+ /* Output is enabled */
+ if (ppc4xx_gpt_compare(gpt, i)) {
+ /* Comparison is OK */
+ ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask);
+ } else {
+ /* Comparison is KO */
+ ppc4xx_gpt_set_output(gpt, i, gpt->ol & mask ? 0 : 1);
+ }
+ }
+ mask = mask >> 1;
+ }
+}
+
+static void ppc4xx_gpt_set_irqs (ppc4xx_gpt_t *gpt)
+{
+ uint32_t mask;
+ int i;
+
+ mask = 0x00008000;
+ for (i = 0; i < 5; i++) {
+ if (gpt->is & gpt->im & mask)
+ qemu_irq_raise(gpt->irqs[i]);
+ else
+ qemu_irq_lower(gpt->irqs[i]);
+ mask = mask >> 1;
+ }
+}
+
+static void ppc4xx_gpt_compute_timer (ppc4xx_gpt_t *gpt)
+{
+ /* XXX: TODO */
+}
+
+static uint32_t ppc4xx_gpt_readl (void *opaque, hwaddr addr)
+{
+ ppc4xx_gpt_t *gpt;
+ uint32_t ret;
+ int idx;
+
+#ifdef DEBUG_GPT
+ printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr);
+#endif
+ gpt = opaque;
+ switch (addr) {
+ case 0x00:
+ /* Time base counter */
+ ret = muldiv64(qemu_get_clock_ns(vm_clock) + gpt->tb_offset,
+ gpt->tb_freq, get_ticks_per_sec());
+ break;
+ case 0x10:
+ /* Output enable */
+ ret = gpt->oe;
+ break;
+ case 0x14:
+ /* Output level */
+ ret = gpt->ol;
+ break;
+ case 0x18:
+ /* Interrupt mask */
+ ret = gpt->im;
+ break;
+ case 0x1C:
+ case 0x20:
+ /* Interrupt status */
+ ret = gpt->is;
+ break;
+ case 0x24:
+ /* Interrupt enable */
+ ret = gpt->ie;
+ break;
+ case 0x80 ... 0x90:
+ /* Compare timer */
+ idx = (addr - 0x80) >> 2;
+ ret = gpt->comp[idx];
+ break;
+ case 0xC0 ... 0xD0:
+ /* Compare mask */
+ idx = (addr - 0xC0) >> 2;
+ ret = gpt->mask[idx];
+ break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static void ppc4xx_gpt_writel (void *opaque,
+ hwaddr addr, uint32_t value)
+{
+ ppc4xx_gpt_t *gpt;
+ int idx;
+
+#ifdef DEBUG_I2C
+ printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr,
+ value);
+#endif
+ gpt = opaque;
+ switch (addr) {
+ case 0x00:
+ /* Time base counter */
+ gpt->tb_offset = muldiv64(value, get_ticks_per_sec(), gpt->tb_freq)
+ - qemu_get_clock_ns(vm_clock);
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ case 0x10:
+ /* Output enable */
+ gpt->oe = value & 0xF8000000;
+ ppc4xx_gpt_set_outputs(gpt);
+ break;
+ case 0x14:
+ /* Output level */
+ gpt->ol = value & 0xF8000000;
+ ppc4xx_gpt_set_outputs(gpt);
+ break;
+ case 0x18:
+ /* Interrupt mask */
+ gpt->im = value & 0x0000F800;
+ break;
+ case 0x1C:
+ /* Interrupt status set */
+ gpt->is |= value & 0x0000F800;
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x20:
+ /* Interrupt status clear */
+ gpt->is &= ~(value & 0x0000F800);
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x24:
+ /* Interrupt enable */
+ gpt->ie = value & 0x0000F800;
+ ppc4xx_gpt_set_irqs(gpt);
+ break;
+ case 0x80 ... 0x90:
+ /* Compare timer */
+ idx = (addr - 0x80) >> 2;
+ gpt->comp[idx] = value & 0xF8000000;
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ case 0xC0 ... 0xD0:
+ /* Compare mask */
+ idx = (addr - 0xC0) >> 2;
+ gpt->mask[idx] = value & 0xF8000000;
+ ppc4xx_gpt_compute_timer(gpt);
+ break;
+ }
+}
+
+static const MemoryRegionOps gpt_ops = {
+ .old_mmio = {
+ .read = { ppc4xx_gpt_readb, ppc4xx_gpt_readw, ppc4xx_gpt_readl, },
+ .write = { ppc4xx_gpt_writeb, ppc4xx_gpt_writew, ppc4xx_gpt_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void ppc4xx_gpt_cb (void *opaque)
+{
+ ppc4xx_gpt_t *gpt;
+
+ gpt = opaque;
+ ppc4xx_gpt_set_irqs(gpt);
+ ppc4xx_gpt_set_outputs(gpt);
+ ppc4xx_gpt_compute_timer(gpt);
+}
+
+static void ppc4xx_gpt_reset (void *opaque)
+{
+ ppc4xx_gpt_t *gpt;
+ int i;
+
+ gpt = opaque;
+ qemu_del_timer(gpt->timer);
+ gpt->oe = 0x00000000;
+ gpt->ol = 0x00000000;
+ gpt->im = 0x00000000;
+ gpt->is = 0x00000000;
+ gpt->ie = 0x00000000;
+ for (i = 0; i < 5; i++) {
+ gpt->comp[i] = 0x00000000;
+ gpt->mask[i] = 0x00000000;
+ }
+}
+
+static void ppc4xx_gpt_init(hwaddr base, qemu_irq irqs[5])
+{
+ ppc4xx_gpt_t *gpt;
+ int i;
+
+ gpt = g_malloc0(sizeof(ppc4xx_gpt_t));
+ for (i = 0; i < 5; i++) {
+ gpt->irqs[i] = irqs[i];
+ }
+ gpt->timer = qemu_new_timer_ns(vm_clock, &ppc4xx_gpt_cb, gpt);
+#ifdef DEBUG_GPT
+ printf("%s: offset " TARGET_FMT_plx "\n", __func__, base);
+#endif
+ memory_region_init_io(&gpt->iomem, &gpt_ops, gpt, "gpt", 0x0d4);
+ memory_region_add_subregion(get_system_memory(), base, &gpt->iomem);
+ qemu_register_reset(ppc4xx_gpt_reset, gpt);
+}
+
+/*****************************************************************************/
+/* MAL */
+enum {
+ MAL0_CFG = 0x180,
+ MAL0_ESR = 0x181,
+ MAL0_IER = 0x182,
+ MAL0_TXCASR = 0x184,
+ MAL0_TXCARR = 0x185,
+ MAL0_TXEOBISR = 0x186,
+ MAL0_TXDEIR = 0x187,
+ MAL0_RXCASR = 0x190,
+ MAL0_RXCARR = 0x191,
+ MAL0_RXEOBISR = 0x192,
+ MAL0_RXDEIR = 0x193,
+ MAL0_TXCTP0R = 0x1A0,
+ MAL0_TXCTP1R = 0x1A1,
+ MAL0_TXCTP2R = 0x1A2,
+ MAL0_TXCTP3R = 0x1A3,
+ MAL0_RXCTP0R = 0x1C0,
+ MAL0_RXCTP1R = 0x1C1,
+ MAL0_RCBS0 = 0x1E0,
+ MAL0_RCBS1 = 0x1E1,
+};
+
+typedef struct ppc40x_mal_t ppc40x_mal_t;
+struct ppc40x_mal_t {
+ qemu_irq irqs[4];
+ uint32_t cfg;
+ uint32_t esr;
+ uint32_t ier;
+ uint32_t txcasr;
+ uint32_t txcarr;
+ uint32_t txeobisr;
+ uint32_t txdeir;
+ uint32_t rxcasr;
+ uint32_t rxcarr;
+ uint32_t rxeobisr;
+ uint32_t rxdeir;
+ uint32_t txctpr[4];
+ uint32_t rxctpr[2];
+ uint32_t rcbs[2];
+};
+
+static void ppc40x_mal_reset (void *opaque);
+
+static uint32_t dcr_read_mal (void *opaque, int dcrn)
+{
+ ppc40x_mal_t *mal;
+ uint32_t ret;
+
+ mal = opaque;
+ switch (dcrn) {
+ case MAL0_CFG:
+ ret = mal->cfg;
+ break;
+ case MAL0_ESR:
+ ret = mal->esr;
+ break;
+ case MAL0_IER:
+ ret = mal->ier;
+ break;
+ case MAL0_TXCASR:
+ ret = mal->txcasr;
+ break;
+ case MAL0_TXCARR:
+ ret = mal->txcarr;
+ break;
+ case MAL0_TXEOBISR:
+ ret = mal->txeobisr;
+ break;
+ case MAL0_TXDEIR:
+ ret = mal->txdeir;
+ break;
+ case MAL0_RXCASR:
+ ret = mal->rxcasr;
+ break;
+ case MAL0_RXCARR:
+ ret = mal->rxcarr;
+ break;
+ case MAL0_RXEOBISR:
+ ret = mal->rxeobisr;
+ break;
+ case MAL0_RXDEIR:
+ ret = mal->rxdeir;
+ break;
+ case MAL0_TXCTP0R:
+ ret = mal->txctpr[0];
+ break;
+ case MAL0_TXCTP1R:
+ ret = mal->txctpr[1];
+ break;
+ case MAL0_TXCTP2R:
+ ret = mal->txctpr[2];
+ break;
+ case MAL0_TXCTP3R:
+ ret = mal->txctpr[3];
+ break;
+ case MAL0_RXCTP0R:
+ ret = mal->rxctpr[0];
+ break;
+ case MAL0_RXCTP1R:
+ ret = mal->rxctpr[1];
+ break;
+ case MAL0_RCBS0:
+ ret = mal->rcbs[0];
+ break;
+ case MAL0_RCBS1:
+ ret = mal->rcbs[1];
+ break;
+ default:
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_mal (void *opaque, int dcrn, uint32_t val)
+{
+ ppc40x_mal_t *mal;
+ int idx;
+
+ mal = opaque;
+ switch (dcrn) {
+ case MAL0_CFG:
+ if (val & 0x80000000)
+ ppc40x_mal_reset(mal);
+ mal->cfg = val & 0x00FFC087;
+ break;
+ case MAL0_ESR:
+ /* Read/clear */
+ mal->esr &= ~val;
+ break;
+ case MAL0_IER:
+ mal->ier = val & 0x0000001F;
+ break;
+ case MAL0_TXCASR:
+ mal->txcasr = val & 0xF0000000;
+ break;
+ case MAL0_TXCARR:
+ mal->txcarr = val & 0xF0000000;
+ break;
+ case MAL0_TXEOBISR:
+ /* Read/clear */
+ mal->txeobisr &= ~val;
+ break;
+ case MAL0_TXDEIR:
+ /* Read/clear */
+ mal->txdeir &= ~val;
+ break;
+ case MAL0_RXCASR:
+ mal->rxcasr = val & 0xC0000000;
+ break;
+ case MAL0_RXCARR:
+ mal->rxcarr = val & 0xC0000000;
+ break;
+ case MAL0_RXEOBISR:
+ /* Read/clear */
+ mal->rxeobisr &= ~val;
+ break;
+ case MAL0_RXDEIR:
+ /* Read/clear */
+ mal->rxdeir &= ~val;
+ break;
+ case MAL0_TXCTP0R:
+ idx = 0;
+ goto update_tx_ptr;
+ case MAL0_TXCTP1R:
+ idx = 1;
+ goto update_tx_ptr;
+ case MAL0_TXCTP2R:
+ idx = 2;
+ goto update_tx_ptr;
+ case MAL0_TXCTP3R:
+ idx = 3;
+ update_tx_ptr:
+ mal->txctpr[idx] = val;
+ break;
+ case MAL0_RXCTP0R:
+ idx = 0;
+ goto update_rx_ptr;
+ case MAL0_RXCTP1R:
+ idx = 1;
+ update_rx_ptr:
+ mal->rxctpr[idx] = val;
+ break;
+ case MAL0_RCBS0:
+ idx = 0;
+ goto update_rx_size;
+ case MAL0_RCBS1:
+ idx = 1;
+ update_rx_size:
+ mal->rcbs[idx] = val & 0x000000FF;
+ break;
+ }
+}
+
+static void ppc40x_mal_reset (void *opaque)
+{
+ ppc40x_mal_t *mal;
+
+ mal = opaque;
+ mal->cfg = 0x0007C000;
+ mal->esr = 0x00000000;
+ mal->ier = 0x00000000;
+ mal->rxcasr = 0x00000000;
+ mal->rxdeir = 0x00000000;
+ mal->rxeobisr = 0x00000000;
+ mal->txcasr = 0x00000000;
+ mal->txdeir = 0x00000000;
+ mal->txeobisr = 0x00000000;
+}
+
+static void ppc405_mal_init(CPUPPCState *env, qemu_irq irqs[4])
+{
+ ppc40x_mal_t *mal;
+ int i;
+
+ mal = g_malloc0(sizeof(ppc40x_mal_t));
+ for (i = 0; i < 4; i++)
+ mal->irqs[i] = irqs[i];
+ qemu_register_reset(&ppc40x_mal_reset, mal);
+ ppc_dcr_register(env, MAL0_CFG,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_ESR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_IER,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCASR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCARR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXEOBISR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXDEIR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCASR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCARR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXEOBISR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXDEIR,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP0R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP1R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP2R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_TXCTP3R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCTP0R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RXCTP1R,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RCBS0,
+ mal, &dcr_read_mal, &dcr_write_mal);
+ ppc_dcr_register(env, MAL0_RCBS1,
+ mal, &dcr_read_mal, &dcr_write_mal);
+}
+
+/*****************************************************************************/
+/* SPR */
+void ppc40x_core_reset(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong dbsr;
+
+ printf("Reset PowerPC core\n");
+ cpu_interrupt(env, CPU_INTERRUPT_RESET);
+ dbsr = env->spr[SPR_40x_DBSR];
+ dbsr &= ~0x00000300;
+ dbsr |= 0x00000100;
+ env->spr[SPR_40x_DBSR] = dbsr;
+}
+
+void ppc40x_chip_reset(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong dbsr;
+
+ printf("Reset PowerPC chip\n");
+ cpu_interrupt(env, CPU_INTERRUPT_RESET);
+ /* XXX: TODO reset all internal peripherals */
+ dbsr = env->spr[SPR_40x_DBSR];
+ dbsr &= ~0x00000300;
+ dbsr |= 0x00000200;
+ env->spr[SPR_40x_DBSR] = dbsr;
+}
+
+void ppc40x_system_reset(PowerPCCPU *cpu)
+{
+ printf("Reset PowerPC system\n");
+ qemu_system_reset_request();
+}
+
+void store_40x_dbcr0 (CPUPPCState *env, uint32_t val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ switch ((val >> 28) & 0x3) {
+ case 0x0:
+ /* No action */
+ break;
+ case 0x1:
+ /* Core reset */
+ ppc40x_core_reset(cpu);
+ break;
+ case 0x2:
+ /* Chip reset */
+ ppc40x_chip_reset(cpu);
+ break;
+ case 0x3:
+ /* System reset */
+ ppc40x_system_reset(cpu);
+ break;
+ }
+}
+
+/*****************************************************************************/
+/* PowerPC 405CR */
+enum {
+ PPC405CR_CPC0_PLLMR = 0x0B0,
+ PPC405CR_CPC0_CR0 = 0x0B1,
+ PPC405CR_CPC0_CR1 = 0x0B2,
+ PPC405CR_CPC0_PSR = 0x0B4,
+ PPC405CR_CPC0_JTAGID = 0x0B5,
+ PPC405CR_CPC0_ER = 0x0B9,
+ PPC405CR_CPC0_FR = 0x0BA,
+ PPC405CR_CPC0_SR = 0x0BB,
+};
+
+enum {
+ PPC405CR_CPU_CLK = 0,
+ PPC405CR_TMR_CLK = 1,
+ PPC405CR_PLB_CLK = 2,
+ PPC405CR_SDRAM_CLK = 3,
+ PPC405CR_OPB_CLK = 4,
+ PPC405CR_EXT_CLK = 5,
+ PPC405CR_UART_CLK = 6,
+ PPC405CR_CLK_NB = 7,
+};
+
+typedef struct ppc405cr_cpc_t ppc405cr_cpc_t;
+struct ppc405cr_cpc_t {
+ clk_setup_t clk_setup[PPC405CR_CLK_NB];
+ uint32_t sysclk;
+ uint32_t psr;
+ uint32_t cr0;
+ uint32_t cr1;
+ uint32_t jtagid;
+ uint32_t pllmr;
+ uint32_t er;
+ uint32_t fr;
+};
+
+static void ppc405cr_clk_setup (ppc405cr_cpc_t *cpc)
+{
+ uint64_t VCO_out, PLL_out;
+ uint32_t CPU_clk, TMR_clk, SDRAM_clk, PLB_clk, OPB_clk, EXT_clk, UART_clk;
+ int M, D0, D1, D2;
+
+ D0 = ((cpc->pllmr >> 26) & 0x3) + 1; /* CBDV */
+ if (cpc->pllmr & 0x80000000) {
+ D1 = (((cpc->pllmr >> 20) - 1) & 0xF) + 1; /* FBDV */
+ D2 = 8 - ((cpc->pllmr >> 16) & 0x7); /* FWDVA */
+ M = D0 * D1 * D2;
+ VCO_out = cpc->sysclk * M;
+ if (VCO_out < 400000000 || VCO_out > 800000000) {
+ /* PLL cannot lock */
+ cpc->pllmr &= ~0x80000000;
+ goto bypass_pll;
+ }
+ PLL_out = VCO_out / D2;
+ } else {
+ /* Bypass PLL */
+ bypass_pll:
+ M = D0;
+ PLL_out = cpc->sysclk * M;
+ }
+ CPU_clk = PLL_out;
+ if (cpc->cr1 & 0x00800000)
+ TMR_clk = cpc->sysclk; /* Should have a separate clock */
+ else
+ TMR_clk = CPU_clk;
+ PLB_clk = CPU_clk / D0;
+ SDRAM_clk = PLB_clk;
+ D0 = ((cpc->pllmr >> 10) & 0x3) + 1;
+ OPB_clk = PLB_clk / D0;
+ D0 = ((cpc->pllmr >> 24) & 0x3) + 2;
+ EXT_clk = PLB_clk / D0;
+ D0 = ((cpc->cr0 >> 1) & 0x1F) + 1;
+ UART_clk = CPU_clk / D0;
+ /* Setup CPU clocks */
+ clk_setup(&cpc->clk_setup[PPC405CR_CPU_CLK], CPU_clk);
+ /* Setup time-base clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_TMR_CLK], TMR_clk);
+ /* Setup PLB clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_PLB_CLK], PLB_clk);
+ /* Setup SDRAM clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_SDRAM_CLK], SDRAM_clk);
+ /* Setup OPB clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_OPB_CLK], OPB_clk);
+ /* Setup external clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_EXT_CLK], EXT_clk);
+ /* Setup UART clock */
+ clk_setup(&cpc->clk_setup[PPC405CR_UART_CLK], UART_clk);
+}
+
+static uint32_t dcr_read_crcpc (void *opaque, int dcrn)
+{
+ ppc405cr_cpc_t *cpc;
+ uint32_t ret;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405CR_CPC0_PLLMR:
+ ret = cpc->pllmr;
+ break;
+ case PPC405CR_CPC0_CR0:
+ ret = cpc->cr0;
+ break;
+ case PPC405CR_CPC0_CR1:
+ ret = cpc->cr1;
+ break;
+ case PPC405CR_CPC0_PSR:
+ ret = cpc->psr;
+ break;
+ case PPC405CR_CPC0_JTAGID:
+ ret = cpc->jtagid;
+ break;
+ case PPC405CR_CPC0_ER:
+ ret = cpc->er;
+ break;
+ case PPC405CR_CPC0_FR:
+ ret = cpc->fr;
+ break;
+ case PPC405CR_CPC0_SR:
+ ret = ~(cpc->er | cpc->fr) & 0xFFFF0000;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_crcpc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405cr_cpc_t *cpc;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405CR_CPC0_PLLMR:
+ cpc->pllmr = val & 0xFFF77C3F;
+ break;
+ case PPC405CR_CPC0_CR0:
+ cpc->cr0 = val & 0x0FFFFFFE;
+ break;
+ case PPC405CR_CPC0_CR1:
+ cpc->cr1 = val & 0x00800000;
+ break;
+ case PPC405CR_CPC0_PSR:
+ /* Read-only */
+ break;
+ case PPC405CR_CPC0_JTAGID:
+ /* Read-only */
+ break;
+ case PPC405CR_CPC0_ER:
+ cpc->er = val & 0xBFFC0000;
+ break;
+ case PPC405CR_CPC0_FR:
+ cpc->fr = val & 0xBFFC0000;
+ break;
+ case PPC405CR_CPC0_SR:
+ /* Read-only */
+ break;
+ }
+}
+
+static void ppc405cr_cpc_reset (void *opaque)
+{
+ ppc405cr_cpc_t *cpc;
+ int D;
+
+ cpc = opaque;
+ /* Compute PLLMR value from PSR settings */
+ cpc->pllmr = 0x80000000;
+ /* PFWD */
+ switch ((cpc->psr >> 30) & 3) {
+ case 0:
+ /* Bypass */
+ cpc->pllmr &= ~0x80000000;
+ break;
+ case 1:
+ /* Divide by 3 */
+ cpc->pllmr |= 5 << 16;
+ break;
+ case 2:
+ /* Divide by 4 */
+ cpc->pllmr |= 4 << 16;
+ break;
+ case 3:
+ /* Divide by 6 */
+ cpc->pllmr |= 2 << 16;
+ break;
+ }
+ /* PFBD */
+ D = (cpc->psr >> 28) & 3;
+ cpc->pllmr |= (D + 1) << 20;
+ /* PT */
+ D = (cpc->psr >> 25) & 7;
+ switch (D) {
+ case 0x2:
+ cpc->pllmr |= 0x13;
+ break;
+ case 0x4:
+ cpc->pllmr |= 0x15;
+ break;
+ case 0x5:
+ cpc->pllmr |= 0x16;
+ break;
+ default:
+ break;
+ }
+ /* PDC */
+ D = (cpc->psr >> 23) & 3;
+ cpc->pllmr |= D << 26;
+ /* ODP */
+ D = (cpc->psr >> 21) & 3;
+ cpc->pllmr |= D << 10;
+ /* EBPD */
+ D = (cpc->psr >> 17) & 3;
+ cpc->pllmr |= D << 24;
+ cpc->cr0 = 0x0000003C;
+ cpc->cr1 = 0x2B0D8800;
+ cpc->er = 0x00000000;
+ cpc->fr = 0x00000000;
+ ppc405cr_clk_setup(cpc);
+}
+
+static void ppc405cr_clk_init (ppc405cr_cpc_t *cpc)
+{
+ int D;
+
+ /* XXX: this should be read from IO pins */
+ cpc->psr = 0x00000000; /* 8 bits ROM */
+ /* PFWD */
+ D = 0x2; /* Divide by 4 */
+ cpc->psr |= D << 30;
+ /* PFBD */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 28;
+ /* PDC */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 23;
+ /* PT */
+ D = 0x5; /* M = 16 */
+ cpc->psr |= D << 25;
+ /* ODP */
+ D = 0x1; /* Divide by 2 */
+ cpc->psr |= D << 21;
+ /* EBDP */
+ D = 0x2; /* Divide by 4 */
+ cpc->psr |= D << 17;
+}
+
+static void ppc405cr_cpc_init (CPUPPCState *env, clk_setup_t clk_setup[7],
+ uint32_t sysclk)
+{
+ ppc405cr_cpc_t *cpc;
+
+ cpc = g_malloc0(sizeof(ppc405cr_cpc_t));
+ memcpy(cpc->clk_setup, clk_setup,
+ PPC405CR_CLK_NB * sizeof(clk_setup_t));
+ cpc->sysclk = sysclk;
+ cpc->jtagid = 0x42051049;
+ ppc_dcr_register(env, PPC405CR_CPC0_PSR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_CR0, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_CR1, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_JTAGID, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_PLLMR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_ER, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_FR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc_dcr_register(env, PPC405CR_CPC0_SR, cpc,
+ &dcr_read_crcpc, &dcr_write_crcpc);
+ ppc405cr_clk_init(cpc);
+ qemu_register_reset(ppc405cr_cpc_reset, cpc);
+}
+
+CPUPPCState *ppc405cr_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[4],
+ hwaddr ram_bases[4],
+ hwaddr ram_sizes[4],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init)
+{
+ clk_setup_t clk_setup[PPC405CR_CLK_NB];
+ qemu_irq dma_irqs[4];
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *pic, *irqs;
+
+ memset(clk_setup, 0, sizeof(clk_setup));
+ cpu = ppc4xx_init("405cr", &clk_setup[PPC405CR_CPU_CLK],
+ &clk_setup[PPC405CR_TMR_CLK], sysclk);
+ env = &cpu->env;
+ /* Memory mapped devices registers */
+ /* PLB arbitrer */
+ ppc4xx_plb_init(env);
+ /* PLB to OPB bridge */
+ ppc4xx_pob_init(env);
+ /* OBP arbitrer */
+ ppc4xx_opba_init(0xef600600);
+ /* Universal interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ *picp = pic;
+ /* SDRAM controller */
+ ppc4xx_sdram_init(env, pic[14], 1, ram_memories,
+ ram_bases, ram_sizes, do_init);
+ /* External bus controller */
+ ppc405_ebc_init(env);
+ /* DMA controller */
+ dma_irqs[0] = pic[26];
+ dma_irqs[1] = pic[25];
+ dma_irqs[2] = pic[24];
+ dma_irqs[3] = pic[23];
+ ppc405_dma_init(env, dma_irqs);
+ /* Serial ports */
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+ /* IIC controller */
+ ppc405_i2c_init(0xef600500, pic[2]);
+ /* GPIO */
+ ppc405_gpio_init(0xef600700);
+ /* CPU control */
+ ppc405cr_cpc_init(env, clk_setup, sysclk);
+
+ return env;
+}
+
+/*****************************************************************************/
+/* PowerPC 405EP */
+/* CPU control */
+enum {
+ PPC405EP_CPC0_PLLMR0 = 0x0F0,
+ PPC405EP_CPC0_BOOT = 0x0F1,
+ PPC405EP_CPC0_EPCTL = 0x0F3,
+ PPC405EP_CPC0_PLLMR1 = 0x0F4,
+ PPC405EP_CPC0_UCR = 0x0F5,
+ PPC405EP_CPC0_SRR = 0x0F6,
+ PPC405EP_CPC0_JTAGID = 0x0F7,
+ PPC405EP_CPC0_PCI = 0x0F9,
+#if 0
+ PPC405EP_CPC0_ER = xxx,
+ PPC405EP_CPC0_FR = xxx,
+ PPC405EP_CPC0_SR = xxx,
+#endif
+};
+
+enum {
+ PPC405EP_CPU_CLK = 0,
+ PPC405EP_PLB_CLK = 1,
+ PPC405EP_OPB_CLK = 2,
+ PPC405EP_EBC_CLK = 3,
+ PPC405EP_MAL_CLK = 4,
+ PPC405EP_PCI_CLK = 5,
+ PPC405EP_UART0_CLK = 6,
+ PPC405EP_UART1_CLK = 7,
+ PPC405EP_CLK_NB = 8,
+};
+
+typedef struct ppc405ep_cpc_t ppc405ep_cpc_t;
+struct ppc405ep_cpc_t {
+ uint32_t sysclk;
+ clk_setup_t clk_setup[PPC405EP_CLK_NB];
+ uint32_t boot;
+ uint32_t epctl;
+ uint32_t pllmr[2];
+ uint32_t ucr;
+ uint32_t srr;
+ uint32_t jtagid;
+ uint32_t pci;
+ /* Clock and power management */
+ uint32_t er;
+ uint32_t fr;
+ uint32_t sr;
+};
+
+static void ppc405ep_compute_clocks (ppc405ep_cpc_t *cpc)
+{
+ uint32_t CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk;
+ uint32_t UART0_clk, UART1_clk;
+ uint64_t VCO_out, PLL_out;
+ int M, D;
+
+ VCO_out = 0;
+ if ((cpc->pllmr[1] & 0x80000000) && !(cpc->pllmr[1] & 0x40000000)) {
+ M = (((cpc->pllmr[1] >> 20) - 1) & 0xF) + 1; /* FBMUL */
+#ifdef DEBUG_CLOCKS_LL
+ printf("FBMUL %01" PRIx32 " %d\n", (cpc->pllmr[1] >> 20) & 0xF, M);
+#endif
+ D = 8 - ((cpc->pllmr[1] >> 16) & 0x7); /* FWDA */
+#ifdef DEBUG_CLOCKS_LL
+ printf("FWDA %01" PRIx32 " %d\n", (cpc->pllmr[1] >> 16) & 0x7, D);
+#endif
+ VCO_out = cpc->sysclk * M * D;
+ if (VCO_out < 500000000UL || VCO_out > 1000000000UL) {
+ /* Error - unlock the PLL */
+ printf("VCO out of range %" PRIu64 "\n", VCO_out);
+#if 0
+ cpc->pllmr[1] &= ~0x80000000;
+ goto pll_bypass;
+#endif
+ }
+ PLL_out = VCO_out / D;
+ /* Pretend the PLL is locked */
+ cpc->boot |= 0x00000001;
+ } else {
+#if 0
+ pll_bypass:
+#endif
+ PLL_out = cpc->sysclk;
+ if (cpc->pllmr[1] & 0x40000000) {
+ /* Pretend the PLL is not locked */
+ cpc->boot &= ~0x00000001;
+ }
+ }
+ /* Now, compute all other clocks */
+ D = ((cpc->pllmr[0] >> 20) & 0x3) + 1; /* CCDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("CCDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 20) & 0x3, D);
+#endif
+ CPU_clk = PLL_out / D;
+ D = ((cpc->pllmr[0] >> 16) & 0x3) + 1; /* CBDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("CBDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 16) & 0x3, D);
+#endif
+ PLB_clk = CPU_clk / D;
+ D = ((cpc->pllmr[0] >> 12) & 0x3) + 1; /* OPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("OPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 12) & 0x3, D);
+#endif
+ OPB_clk = PLB_clk / D;
+ D = ((cpc->pllmr[0] >> 8) & 0x3) + 2; /* EPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("EPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 8) & 0x3, D);
+#endif
+ EBC_clk = PLB_clk / D;
+ D = ((cpc->pllmr[0] >> 4) & 0x3) + 1; /* MPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("MPDV %01" PRIx32 " %d\n", (cpc->pllmr[0] >> 4) & 0x3, D);
+#endif
+ MAL_clk = PLB_clk / D;
+ D = (cpc->pllmr[0] & 0x3) + 1; /* PPDV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("PPDV %01" PRIx32 " %d\n", cpc->pllmr[0] & 0x3, D);
+#endif
+ PCI_clk = PLB_clk / D;
+ D = ((cpc->ucr - 1) & 0x7F) + 1; /* U0DIV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("U0DIV %01" PRIx32 " %d\n", cpc->ucr & 0x7F, D);
+#endif
+ UART0_clk = PLL_out / D;
+ D = (((cpc->ucr >> 8) - 1) & 0x7F) + 1; /* U1DIV */
+#ifdef DEBUG_CLOCKS_LL
+ printf("U1DIV %01" PRIx32 " %d\n", (cpc->ucr >> 8) & 0x7F, D);
+#endif
+ UART1_clk = PLL_out / D;
+#ifdef DEBUG_CLOCKS
+ printf("Setup PPC405EP clocks - sysclk %" PRIu32 " VCO %" PRIu64
+ " PLL out %" PRIu64 " Hz\n", cpc->sysclk, VCO_out, PLL_out);
+ printf("CPU %" PRIu32 " PLB %" PRIu32 " OPB %" PRIu32 " EBC %" PRIu32
+ " MAL %" PRIu32 " PCI %" PRIu32 " UART0 %" PRIu32
+ " UART1 %" PRIu32 "\n",
+ CPU_clk, PLB_clk, OPB_clk, EBC_clk, MAL_clk, PCI_clk,
+ UART0_clk, UART1_clk);
+#endif
+ /* Setup CPU clocks */
+ clk_setup(&cpc->clk_setup[PPC405EP_CPU_CLK], CPU_clk);
+ /* Setup PLB clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_PLB_CLK], PLB_clk);
+ /* Setup OPB clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_OPB_CLK], OPB_clk);
+ /* Setup external clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_EBC_CLK], EBC_clk);
+ /* Setup MAL clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_MAL_CLK], MAL_clk);
+ /* Setup PCI clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_PCI_CLK], PCI_clk);
+ /* Setup UART0 clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_UART0_CLK], UART0_clk);
+ /* Setup UART1 clock */
+ clk_setup(&cpc->clk_setup[PPC405EP_UART1_CLK], UART1_clk);
+}
+
+static uint32_t dcr_read_epcpc (void *opaque, int dcrn)
+{
+ ppc405ep_cpc_t *cpc;
+ uint32_t ret;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405EP_CPC0_BOOT:
+ ret = cpc->boot;
+ break;
+ case PPC405EP_CPC0_EPCTL:
+ ret = cpc->epctl;
+ break;
+ case PPC405EP_CPC0_PLLMR0:
+ ret = cpc->pllmr[0];
+ break;
+ case PPC405EP_CPC0_PLLMR1:
+ ret = cpc->pllmr[1];
+ break;
+ case PPC405EP_CPC0_UCR:
+ ret = cpc->ucr;
+ break;
+ case PPC405EP_CPC0_SRR:
+ ret = cpc->srr;
+ break;
+ case PPC405EP_CPC0_JTAGID:
+ ret = cpc->jtagid;
+ break;
+ case PPC405EP_CPC0_PCI:
+ ret = cpc->pci;
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_epcpc (void *opaque, int dcrn, uint32_t val)
+{
+ ppc405ep_cpc_t *cpc;
+
+ cpc = opaque;
+ switch (dcrn) {
+ case PPC405EP_CPC0_BOOT:
+ /* Read-only register */
+ break;
+ case PPC405EP_CPC0_EPCTL:
+ /* Don't care for now */
+ cpc->epctl = val & 0xC00000F3;
+ break;
+ case PPC405EP_CPC0_PLLMR0:
+ cpc->pllmr[0] = val & 0x00633333;
+ ppc405ep_compute_clocks(cpc);
+ break;
+ case PPC405EP_CPC0_PLLMR1:
+ cpc->pllmr[1] = val & 0xC0F73FFF;
+ ppc405ep_compute_clocks(cpc);
+ break;
+ case PPC405EP_CPC0_UCR:
+ /* UART control - don't care for now */
+ cpc->ucr = val & 0x003F7F7F;
+ break;
+ case PPC405EP_CPC0_SRR:
+ cpc->srr = val;
+ break;
+ case PPC405EP_CPC0_JTAGID:
+ /* Read-only */
+ break;
+ case PPC405EP_CPC0_PCI:
+ cpc->pci = val;
+ break;
+ }
+}
+
+static void ppc405ep_cpc_reset (void *opaque)
+{
+ ppc405ep_cpc_t *cpc = opaque;
+
+ cpc->boot = 0x00000010; /* Boot from PCI - IIC EEPROM disabled */
+ cpc->epctl = 0x00000000;
+ cpc->pllmr[0] = 0x00011010;
+ cpc->pllmr[1] = 0x40000000;
+ cpc->ucr = 0x00000000;
+ cpc->srr = 0x00040000;
+ cpc->pci = 0x00000000;
+ cpc->er = 0x00000000;
+ cpc->fr = 0x00000000;
+ cpc->sr = 0x00000000;
+ ppc405ep_compute_clocks(cpc);
+}
+
+/* XXX: sysclk should be between 25 and 100 MHz */
+static void ppc405ep_cpc_init (CPUPPCState *env, clk_setup_t clk_setup[8],
+ uint32_t sysclk)
+{
+ ppc405ep_cpc_t *cpc;
+
+ cpc = g_malloc0(sizeof(ppc405ep_cpc_t));
+ memcpy(cpc->clk_setup, clk_setup,
+ PPC405EP_CLK_NB * sizeof(clk_setup_t));
+ cpc->jtagid = 0x20267049;
+ cpc->sysclk = sysclk;
+ qemu_register_reset(&ppc405ep_cpc_reset, cpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_BOOT, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_EPCTL, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PLLMR0, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PLLMR1, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_UCR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_SRR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_JTAGID, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_PCI, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+#if 0
+ ppc_dcr_register(env, PPC405EP_CPC0_ER, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_FR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+ ppc_dcr_register(env, PPC405EP_CPC0_SR, cpc,
+ &dcr_read_epcpc, &dcr_write_epcpc);
+#endif
+}
+
+CPUPPCState *ppc405ep_init(MemoryRegion *address_space_mem,
+ MemoryRegion ram_memories[2],
+ hwaddr ram_bases[2],
+ hwaddr ram_sizes[2],
+ uint32_t sysclk, qemu_irq **picp,
+ int do_init)
+{
+ clk_setup_t clk_setup[PPC405EP_CLK_NB], tlb_clk_setup;
+ qemu_irq dma_irqs[4], gpt_irqs[5], mal_irqs[4];
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *pic, *irqs;
+
+ memset(clk_setup, 0, sizeof(clk_setup));
+ /* init CPUs */
+ cpu = ppc4xx_init("405ep", &clk_setup[PPC405EP_CPU_CLK],
+ &tlb_clk_setup, sysclk);
+ env = &cpu->env;
+ clk_setup[PPC405EP_CPU_CLK].cb = tlb_clk_setup.cb;
+ clk_setup[PPC405EP_CPU_CLK].opaque = tlb_clk_setup.opaque;
+ /* Internal devices init */
+ /* Memory mapped devices registers */
+ /* PLB arbitrer */
+ ppc4xx_plb_init(env);
+ /* PLB to OPB bridge */
+ ppc4xx_pob_init(env);
+ /* OBP arbitrer */
+ ppc4xx_opba_init(0xef600600);
+ /* Initialize timers */
+ ppc_booke_timers_init(cpu, sysclk, 0);
+ /* Universal interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] =
+ ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ *picp = pic;
+ /* SDRAM controller */
+ /* XXX 405EP has no ECC interrupt */
+ ppc4xx_sdram_init(env, pic[17], 2, ram_memories,
+ ram_bases, ram_sizes, do_init);
+ /* External bus controller */
+ ppc405_ebc_init(env);
+ /* DMA controller */
+ dma_irqs[0] = pic[5];
+ dma_irqs[1] = pic[6];
+ dma_irqs[2] = pic[7];
+ dma_irqs[3] = pic[8];
+ ppc405_dma_init(env, dma_irqs);
+ /* IIC controller */
+ ppc405_i2c_init(0xef600500, pic[2]);
+ /* GPIO */
+ ppc405_gpio_init(0xef600700);
+ /* Serial ports */
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+ /* OCM */
+ ppc405_ocm_init(env);
+ /* GPT */
+ gpt_irqs[0] = pic[19];
+ gpt_irqs[1] = pic[20];
+ gpt_irqs[2] = pic[21];
+ gpt_irqs[3] = pic[22];
+ gpt_irqs[4] = pic[23];
+ ppc4xx_gpt_init(0xef600000, gpt_irqs);
+ /* PCI */
+ /* Uses pic[3], pic[16], pic[18] */
+ /* MAL */
+ mal_irqs[0] = pic[11];
+ mal_irqs[1] = pic[12];
+ mal_irqs[2] = pic[13];
+ mal_irqs[3] = pic[14];
+ ppc405_mal_init(env, mal_irqs);
+ /* Ethernet */
+ /* Uses pic[9], pic[15], pic[17] */
+ /* CPU control */
+ ppc405ep_cpc_init(env, clk_setup, sysclk);
+
+ return env;
+}
diff --git a/hw/ppc/ppc440_bamboo.c b/hw/ppc/ppc440_bamboo.c
new file mode 100644
index 0000000..66911b5
--- /dev/null
+++ b/hw/ppc/ppc440_bamboo.c
@@ -0,0 +1,306 @@
+/*
+ * QEMU PowerPC 440 Bamboo board emulation
+ *
+ * Copyright 2007 IBM Corporation.
+ * Authors:
+ * Jerone Young <jyoung5@us.ibm.com>
+ * Christian Ehrhardt <ehrhardt@linux.vnet.ibm.com>
+ * Hollis Blanchard <hollisb@us.ibm.com>
+ *
+ * This work is licensed under the GNU GPL license version 2 or later.
+ *
+ */
+
+#include "config.h"
+#include "qemu-common.h"
+#include "net/net.h"
+#include "hw/hw.h"
+#include "hw/pci/pci.h"
+#include "hw/boards.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "sysemu/device_tree.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "exec/address-spaces.h"
+#include "hw/serial.h"
+#include "hw/ppc.h"
+#include "hw/ppc405.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+
+#define BINARY_DEVICE_TREE_FILE "bamboo.dtb"
+
+/* from u-boot */
+#define KERNEL_ADDR 0x1000000
+#define FDT_ADDR 0x1800000
+#define RAMDISK_ADDR 0x1900000
+
+#define PPC440EP_PCI_CONFIG 0xeec00000
+#define PPC440EP_PCI_INTACK 0xeed00000
+#define PPC440EP_PCI_SPECIAL 0xeed00000
+#define PPC440EP_PCI_REGS 0xef400000
+#define PPC440EP_PCI_IO 0xe8000000
+#define PPC440EP_PCI_IOLEN 0x00010000
+
+#define PPC440EP_SDRAM_NR_BANKS 4
+
+static const unsigned int ppc440ep_sdram_bank_sizes[] = {
+ 256<<20, 128<<20, 64<<20, 32<<20, 16<<20, 8<<20, 0
+};
+
+static hwaddr entry;
+
+static int bamboo_load_device_tree(hwaddr addr,
+ uint32_t ramsize,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ const char *kernel_cmdline)
+{
+ int ret = -1;
+#ifdef CONFIG_FDT
+ uint32_t mem_reg_property[] = { 0, 0, cpu_to_be32(ramsize) };
+ char *filename;
+ int fdt_size;
+ void *fdt;
+ uint32_t tb_freq = 400000000;
+ uint32_t clock_freq = 400000000;
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE);
+ if (!filename) {
+ goto out;
+ }
+ fdt = load_device_tree(filename, &fdt_size);
+ g_free(filename);
+ if (fdt == NULL) {
+ goto out;
+ }
+
+ /* Manipulate device tree in memory. */
+
+ ret = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property,
+ sizeof(mem_reg_property));
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /memory/reg\n");
+
+ ret = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+ initrd_base);
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
+
+ ret = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+ (initrd_base + initrd_size));
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
+
+ ret = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs",
+ kernel_cmdline);
+ if (ret < 0)
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+
+ /* Copy data from the host device tree into the guest. Since the guest can
+ * directly access the timebase without host involvement, we must expose
+ * the correct frequencies. */
+ if (kvm_enabled()) {
+ tb_freq = kvmppc_get_tbfreq();
+ clock_freq = kvmppc_get_clockfreq();
+ }
+
+ qemu_devtree_setprop_cell(fdt, "/cpus/cpu@0", "clock-frequency",
+ clock_freq);
+ qemu_devtree_setprop_cell(fdt, "/cpus/cpu@0", "timebase-frequency",
+ tb_freq);
+
+ ret = rom_add_blob_fixed(BINARY_DEVICE_TREE_FILE, fdt, fdt_size, addr);
+ g_free(fdt);
+
+out:
+#endif
+
+ return ret;
+}
+
+/* Create reset TLB entries for BookE, spanning the 32bit addr space. */
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa)
+{
+ ppcemb_tlb_t *tlb = &env->tlb.tlbe[0];
+
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1 << 31; /* up to 0x80000000 */
+ tlb->EPN = va & TARGET_PAGE_MASK;
+ tlb->RPN = pa & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+
+ tlb = &env->tlb.tlbe[1];
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1 << 31; /* up to 0xffffffff */
+ tlb->EPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->RPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+ env->gpr[1] = (16<<20) - 8;
+ env->gpr[3] = FDT_ADDR;
+ env->nip = entry;
+
+ /* Create a mapping for the kernel. */
+ mmubooke_create_initial_mapping(env, 0, 0);
+}
+
+static void bamboo_init(QEMUMachineInitArgs *args)
+{
+ ram_addr_t ram_size = args->ram_size;
+ const char *cpu_model = args->cpu_model;
+ const char *kernel_filename = args->kernel_filename;
+ const char *kernel_cmdline = args->kernel_cmdline;
+ const char *initrd_filename = args->initrd_filename;
+ unsigned int pci_irq_nrs[4] = { 28, 27, 26, 25 };
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ram_memories
+ = g_malloc(PPC440EP_SDRAM_NR_BANKS * sizeof(*ram_memories));
+ hwaddr ram_bases[PPC440EP_SDRAM_NR_BANKS];
+ hwaddr ram_sizes[PPC440EP_SDRAM_NR_BANKS];
+ qemu_irq *pic;
+ qemu_irq *irqs;
+ PCIBus *pcibus;
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ uint64_t elf_entry;
+ uint64_t elf_lowaddr;
+ hwaddr loadaddr = 0;
+ target_long initrd_size = 0;
+ DeviceState *dev;
+ int success;
+ int i;
+
+ /* Setup CPU. */
+ if (cpu_model == NULL) {
+ cpu_model = "440EP";
+ }
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to initialize CPU!\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ qemu_register_reset(main_cpu_reset, cpu);
+ ppc_booke_timers_init(cpu, 400000000, 0);
+ ppc_dcr_init(env, NULL, NULL);
+
+ /* interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ pic = ppcuic_init(env, irqs, 0x0C0, 0, 1);
+
+ /* SDRAM controller */
+ memset(ram_bases, 0, sizeof(ram_bases));
+ memset(ram_sizes, 0, sizeof(ram_sizes));
+ ram_size = ppc4xx_sdram_adjust(ram_size, PPC440EP_SDRAM_NR_BANKS,
+ ram_memories,
+ ram_bases, ram_sizes,
+ ppc440ep_sdram_bank_sizes);
+ /* XXX 440EP's ECC interrupts are on UIC1, but we've only created UIC0. */
+ ppc4xx_sdram_init(env, pic[14], PPC440EP_SDRAM_NR_BANKS, ram_memories,
+ ram_bases, ram_sizes, 1);
+
+ /* PCI */
+ dev = sysbus_create_varargs(TYPE_PPC4xx_PCI_HOST_BRIDGE,
+ PPC440EP_PCI_CONFIG,
+ pic[pci_irq_nrs[0]], pic[pci_irq_nrs[1]],
+ pic[pci_irq_nrs[2]], pic[pci_irq_nrs[3]],
+ NULL);
+ pcibus = (PCIBus *)qdev_get_child_bus(dev, "pci.0");
+ if (!pcibus) {
+ fprintf(stderr, "couldn't create PCI controller!\n");
+ exit(1);
+ }
+
+ isa_mmio_init(PPC440EP_PCI_IO, PPC440EP_PCI_IOLEN);
+
+ if (serial_hds[0] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600300, 0, pic[0],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[0],
+ DEVICE_BIG_ENDIAN);
+ }
+ if (serial_hds[1] != NULL) {
+ serial_mm_init(address_space_mem, 0xef600400, 0, pic[1],
+ PPC_SERIAL_MM_BAUDBASE, serial_hds[1],
+ DEVICE_BIG_ENDIAN);
+ }
+
+ if (pcibus) {
+ /* Register network interfaces. */
+ for (i = 0; i < nb_nics; i++) {
+ /* There are no PCI NICs on the Bamboo board, but there are
+ * PCI slots, so we can pick whatever default model we want. */
+ pci_nic_init_nofail(&nd_table[i], "e1000", NULL);
+ }
+ }
+
+ /* Load kernel. */
+ if (kernel_filename) {
+ success = load_uimage(kernel_filename, &entry, &loadaddr, NULL);
+ if (success < 0) {
+ success = load_elf(kernel_filename, NULL, NULL, &elf_entry,
+ &elf_lowaddr, NULL, 1, ELF_MACHINE, 0);
+ entry = elf_entry;
+ loadaddr = elf_lowaddr;
+ }
+ /* XXX try again as binary */
+ if (success < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+ }
+
+ /* Load initrd. */
+ if (initrd_filename) {
+ initrd_size = load_image_targphys(initrd_filename, RAMDISK_ADDR,
+ ram_size - RAMDISK_ADDR);
+
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load ram disk '%s' at %x\n",
+ initrd_filename, RAMDISK_ADDR);
+ exit(1);
+ }
+ }
+
+ /* If we're loading a kernel directly, we must load the device tree too. */
+ if (kernel_filename) {
+ if (bamboo_load_device_tree(FDT_ADDR, ram_size, RAMDISK_ADDR,
+ initrd_size, kernel_cmdline) < 0) {
+ fprintf(stderr, "couldn't load device tree\n");
+ exit(1);
+ }
+ }
+
+ if (kvm_enabled())
+ kvmppc_init();
+}
+
+static QEMUMachine bamboo_machine = {
+ .name = "bamboo",
+ .desc = "bamboo",
+ .init = bamboo_init,
+ DEFAULT_MACHINE_OPTIONS,
+};
+
+static void bamboo_machine_init(void)
+{
+ qemu_register_machine(&bamboo_machine);
+}
+
+machine_init(bamboo_machine_init);
diff --git a/hw/ppc/ppc4xx_devs.c b/hw/ppc/ppc4xx_devs.c
new file mode 100644
index 0000000..49ec728
--- /dev/null
+++ b/hw/ppc/ppc4xx_devs.c
@@ -0,0 +1,721 @@
+/*
+ * QEMU PowerPC 4xx embedded processors shared devices emulation
+ *
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * 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 "hw/hw.h"
+#include "hw/ppc.h"
+#include "hw/ppc4xx.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_MMIO
+//#define DEBUG_UNASSIGNED
+#define DEBUG_UIC
+
+
+#ifdef DEBUG_UIC
+# define LOG_UIC(...) qemu_log_mask(CPU_LOG_INT, ## __VA_ARGS__)
+#else
+# define LOG_UIC(...) do { } while (0)
+#endif
+
+static void ppc4xx_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+
+ cpu_reset(CPU(cpu));
+}
+
+/*****************************************************************************/
+/* Generic PowerPC 4xx processor instantiation */
+PowerPCCPU *ppc4xx_init(const char *cpu_model,
+ clk_setup_t *cpu_clk, clk_setup_t *tb_clk,
+ uint32_t sysclk)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+
+ /* init CPUs */
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC %s CPU definition\n",
+ cpu_model);
+ exit(1);
+ }
+ env = &cpu->env;
+
+ cpu_clk->cb = NULL; /* We don't care about CPU clock frequency changes */
+ cpu_clk->opaque = env;
+ /* Set time-base frequency to sysclk */
+ tb_clk->cb = ppc_40x_timers_init(env, sysclk, PPC_INTERRUPT_PIT);
+ tb_clk->opaque = env;
+ ppc_dcr_init(env, NULL, NULL);
+ /* Register qemu callbacks */
+ qemu_register_reset(ppc4xx_reset, cpu);
+
+ return cpu;
+}
+
+/*****************************************************************************/
+/* "Universal" Interrupt controller */
+enum {
+ DCR_UICSR = 0x000,
+ DCR_UICSRS = 0x001,
+ DCR_UICER = 0x002,
+ DCR_UICCR = 0x003,
+ DCR_UICPR = 0x004,
+ DCR_UICTR = 0x005,
+ DCR_UICMSR = 0x006,
+ DCR_UICVR = 0x007,
+ DCR_UICVCR = 0x008,
+ DCR_UICMAX = 0x009,
+};
+
+#define UIC_MAX_IRQ 32
+typedef struct ppcuic_t ppcuic_t;
+struct ppcuic_t {
+ uint32_t dcr_base;
+ int use_vectors;
+ uint32_t level; /* Remembers the state of level-triggered interrupts. */
+ uint32_t uicsr; /* Status register */
+ uint32_t uicer; /* Enable register */
+ uint32_t uiccr; /* Critical register */
+ uint32_t uicpr; /* Polarity register */
+ uint32_t uictr; /* Triggering register */
+ uint32_t uicvcr; /* Vector configuration register */
+ uint32_t uicvr;
+ qemu_irq *irqs;
+};
+
+static void ppcuic_trigger_irq (ppcuic_t *uic)
+{
+ uint32_t ir, cr;
+ int start, end, inc, i;
+
+ /* Trigger interrupt if any is pending */
+ ir = uic->uicsr & uic->uicer & (~uic->uiccr);
+ cr = uic->uicsr & uic->uicer & uic->uiccr;
+ LOG_UIC("%s: uicsr %08" PRIx32 " uicer %08" PRIx32
+ " uiccr %08" PRIx32 "\n"
+ " %08" PRIx32 " ir %08" PRIx32 " cr %08" PRIx32 "\n",
+ __func__, uic->uicsr, uic->uicer, uic->uiccr,
+ uic->uicsr & uic->uicer, ir, cr);
+ if (ir != 0x0000000) {
+ LOG_UIC("Raise UIC interrupt\n");
+ qemu_irq_raise(uic->irqs[PPCUIC_OUTPUT_INT]);
+ } else {
+ LOG_UIC("Lower UIC interrupt\n");
+ qemu_irq_lower(uic->irqs[PPCUIC_OUTPUT_INT]);
+ }
+ /* Trigger critical interrupt if any is pending and update vector */
+ if (cr != 0x0000000) {
+ qemu_irq_raise(uic->irqs[PPCUIC_OUTPUT_CINT]);
+ if (uic->use_vectors) {
+ /* Compute critical IRQ vector */
+ if (uic->uicvcr & 1) {
+ start = 31;
+ end = 0;
+ inc = -1;
+ } else {
+ start = 0;
+ end = 31;
+ inc = 1;
+ }
+ uic->uicvr = uic->uicvcr & 0xFFFFFFFC;
+ for (i = start; i <= end; i += inc) {
+ if (cr & (1 << i)) {
+ uic->uicvr += (i - start) * 512 * inc;
+ break;
+ }
+ }
+ }
+ LOG_UIC("Raise UIC critical interrupt - "
+ "vector %08" PRIx32 "\n", uic->uicvr);
+ } else {
+ LOG_UIC("Lower UIC critical interrupt\n");
+ qemu_irq_lower(uic->irqs[PPCUIC_OUTPUT_CINT]);
+ uic->uicvr = 0x00000000;
+ }
+}
+
+static void ppcuic_set_irq (void *opaque, int irq_num, int level)
+{
+ ppcuic_t *uic;
+ uint32_t mask, sr;
+
+ uic = opaque;
+ mask = 1 << (31-irq_num);
+ LOG_UIC("%s: irq %d level %d uicsr %08" PRIx32
+ " mask %08" PRIx32 " => %08" PRIx32 " %08" PRIx32 "\n",
+ __func__, irq_num, level,
+ uic->uicsr, mask, uic->uicsr & mask, level << irq_num);
+ if (irq_num < 0 || irq_num > 31)
+ return;
+ sr = uic->uicsr;
+
+ /* Update status register */
+ if (uic->uictr & mask) {
+ /* Edge sensitive interrupt */
+ if (level == 1)
+ uic->uicsr |= mask;
+ } else {
+ /* Level sensitive interrupt */
+ if (level == 1) {
+ uic->uicsr |= mask;
+ uic->level |= mask;
+ } else {
+ uic->uicsr &= ~mask;
+ uic->level &= ~mask;
+ }
+ }
+ LOG_UIC("%s: irq %d level %d sr %" PRIx32 " => "
+ "%08" PRIx32 "\n", __func__, irq_num, level, uic->uicsr, sr);
+ if (sr != uic->uicsr)
+ ppcuic_trigger_irq(uic);
+}
+
+static uint32_t dcr_read_uic (void *opaque, int dcrn)
+{
+ ppcuic_t *uic;
+ uint32_t ret;
+
+ uic = opaque;
+ dcrn -= uic->dcr_base;
+ switch (dcrn) {
+ case DCR_UICSR:
+ case DCR_UICSRS:
+ ret = uic->uicsr;
+ break;
+ case DCR_UICER:
+ ret = uic->uicer;
+ break;
+ case DCR_UICCR:
+ ret = uic->uiccr;
+ break;
+ case DCR_UICPR:
+ ret = uic->uicpr;
+ break;
+ case DCR_UICTR:
+ ret = uic->uictr;
+ break;
+ case DCR_UICMSR:
+ ret = uic->uicsr & uic->uicer;
+ break;
+ case DCR_UICVR:
+ if (!uic->use_vectors)
+ goto no_read;
+ ret = uic->uicvr;
+ break;
+ case DCR_UICVCR:
+ if (!uic->use_vectors)
+ goto no_read;
+ ret = uic->uicvcr;
+ break;
+ default:
+ no_read:
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_uic (void *opaque, int dcrn, uint32_t val)
+{
+ ppcuic_t *uic;
+
+ uic = opaque;
+ dcrn -= uic->dcr_base;
+ LOG_UIC("%s: dcr %d val 0x%x\n", __func__, dcrn, val);
+ switch (dcrn) {
+ case DCR_UICSR:
+ uic->uicsr &= ~val;
+ uic->uicsr |= uic->level;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICSRS:
+ uic->uicsr |= val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICER:
+ uic->uicer = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICCR:
+ uic->uiccr = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICPR:
+ uic->uicpr = val;
+ break;
+ case DCR_UICTR:
+ uic->uictr = val;
+ ppcuic_trigger_irq(uic);
+ break;
+ case DCR_UICMSR:
+ break;
+ case DCR_UICVR:
+ break;
+ case DCR_UICVCR:
+ uic->uicvcr = val & 0xFFFFFFFD;
+ ppcuic_trigger_irq(uic);
+ break;
+ }
+}
+
+static void ppcuic_reset (void *opaque)
+{
+ ppcuic_t *uic;
+
+ uic = opaque;
+ uic->uiccr = 0x00000000;
+ uic->uicer = 0x00000000;
+ uic->uicpr = 0x00000000;
+ uic->uicsr = 0x00000000;
+ uic->uictr = 0x00000000;
+ if (uic->use_vectors) {
+ uic->uicvcr = 0x00000000;
+ uic->uicvr = 0x0000000;
+ }
+}
+
+qemu_irq *ppcuic_init (CPUPPCState *env, qemu_irq *irqs,
+ uint32_t dcr_base, int has_ssr, int has_vr)
+{
+ ppcuic_t *uic;
+ int i;
+
+ uic = g_malloc0(sizeof(ppcuic_t));
+ uic->dcr_base = dcr_base;
+ uic->irqs = irqs;
+ if (has_vr)
+ uic->use_vectors = 1;
+ for (i = 0; i < DCR_UICMAX; i++) {
+ ppc_dcr_register(env, dcr_base + i, uic,
+ &dcr_read_uic, &dcr_write_uic);
+ }
+ qemu_register_reset(ppcuic_reset, uic);
+
+ return qemu_allocate_irqs(&ppcuic_set_irq, uic, UIC_MAX_IRQ);
+}
+
+/*****************************************************************************/
+/* SDRAM controller */
+typedef struct ppc4xx_sdram_t ppc4xx_sdram_t;
+struct ppc4xx_sdram_t {
+ uint32_t addr;
+ int nbanks;
+ MemoryRegion containers[4]; /* used for clipping */
+ MemoryRegion *ram_memories;
+ hwaddr ram_bases[4];
+ hwaddr ram_sizes[4];
+ uint32_t besr0;
+ uint32_t besr1;
+ uint32_t bear;
+ uint32_t cfg;
+ uint32_t status;
+ uint32_t rtr;
+ uint32_t pmit;
+ uint32_t bcr[4];
+ uint32_t tr;
+ uint32_t ecccfg;
+ uint32_t eccesr;
+ qemu_irq irq;
+};
+
+enum {
+ SDRAM0_CFGADDR = 0x010,
+ SDRAM0_CFGDATA = 0x011,
+};
+
+/* XXX: TOFIX: some patches have made this code become inconsistent:
+ * there are type inconsistencies, mixing hwaddr, target_ulong
+ * and uint32_t
+ */
+static uint32_t sdram_bcr (hwaddr ram_base,
+ hwaddr ram_size)
+{
+ uint32_t bcr;
+
+ switch (ram_size) {
+ case (4 * 1024 * 1024):
+ bcr = 0x00000000;
+ break;
+ case (8 * 1024 * 1024):
+ bcr = 0x00020000;
+ break;
+ case (16 * 1024 * 1024):
+ bcr = 0x00040000;
+ break;
+ case (32 * 1024 * 1024):
+ bcr = 0x00060000;
+ break;
+ case (64 * 1024 * 1024):
+ bcr = 0x00080000;
+ break;
+ case (128 * 1024 * 1024):
+ bcr = 0x000A0000;
+ break;
+ case (256 * 1024 * 1024):
+ bcr = 0x000C0000;
+ break;
+ default:
+ printf("%s: invalid RAM size " TARGET_FMT_plx "\n", __func__,
+ ram_size);
+ return 0x00000000;
+ }
+ bcr |= ram_base & 0xFF800000;
+ bcr |= 1;
+
+ return bcr;
+}
+
+static inline hwaddr sdram_base(uint32_t bcr)
+{
+ return bcr & 0xFF800000;
+}
+
+static target_ulong sdram_size (uint32_t bcr)
+{
+ target_ulong size;
+ int sh;
+
+ sh = (bcr >> 17) & 0x7;
+ if (sh == 7)
+ size = -1;
+ else
+ size = (4 * 1024 * 1024) << sh;
+
+ return size;
+}
+
+static void sdram_set_bcr(ppc4xx_sdram_t *sdram,
+ uint32_t *bcrp, uint32_t bcr, int enabled)
+{
+ unsigned n = bcrp - sdram->bcr;
+
+ if (*bcrp & 0x00000001) {
+ /* Unmap RAM */
+#ifdef DEBUG_SDRAM
+ printf("%s: unmap RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(*bcrp), sdram_size(*bcrp));
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &sdram->containers[n]);
+ memory_region_del_subregion(&sdram->containers[n],
+ &sdram->ram_memories[n]);
+ memory_region_destroy(&sdram->containers[n]);
+ }
+ *bcrp = bcr & 0xFFDEE001;
+ if (enabled && (bcr & 0x00000001)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: Map RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(bcr), sdram_size(bcr));
+#endif
+ memory_region_init(&sdram->containers[n], "sdram-containers",
+ sdram_size(bcr));
+ memory_region_add_subregion(&sdram->containers[n], 0,
+ &sdram->ram_memories[n]);
+ memory_region_add_subregion(get_system_memory(),
+ sdram_base(bcr),
+ &sdram->containers[n]);
+ }
+}
+
+static void sdram_map_bcr (ppc4xx_sdram_t *sdram)
+{
+ int i;
+
+ for (i = 0; i < sdram->nbanks; i++) {
+ if (sdram->ram_sizes[i] != 0) {
+ sdram_set_bcr(sdram,
+ &sdram->bcr[i],
+ sdram_bcr(sdram->ram_bases[i], sdram->ram_sizes[i]),
+ 1);
+ } else {
+ sdram_set_bcr(sdram, &sdram->bcr[i], 0x00000000, 0);
+ }
+ }
+}
+
+static void sdram_unmap_bcr (ppc4xx_sdram_t *sdram)
+{
+ int i;
+
+ for (i = 0; i < sdram->nbanks; i++) {
+#ifdef DEBUG_SDRAM
+ printf("%s: Unmap RAM area " TARGET_FMT_plx " " TARGET_FMT_lx "\n",
+ __func__, sdram_base(sdram->bcr[i]), sdram_size(sdram->bcr[i]));
+#endif
+ memory_region_del_subregion(get_system_memory(),
+ &sdram->ram_memories[i]);
+ }
+}
+
+static uint32_t dcr_read_sdram (void *opaque, int dcrn)
+{
+ ppc4xx_sdram_t *sdram;
+ uint32_t ret;
+
+ sdram = opaque;
+ switch (dcrn) {
+ case SDRAM0_CFGADDR:
+ ret = sdram->addr;
+ break;
+ case SDRAM0_CFGDATA:
+ switch (sdram->addr) {
+ case 0x00: /* SDRAM_BESR0 */
+ ret = sdram->besr0;
+ break;
+ case 0x08: /* SDRAM_BESR1 */
+ ret = sdram->besr1;
+ break;
+ case 0x10: /* SDRAM_BEAR */
+ ret = sdram->bear;
+ break;
+ case 0x20: /* SDRAM_CFG */
+ ret = sdram->cfg;
+ break;
+ case 0x24: /* SDRAM_STATUS */
+ ret = sdram->status;
+ break;
+ case 0x30: /* SDRAM_RTR */
+ ret = sdram->rtr;
+ break;
+ case 0x34: /* SDRAM_PMIT */
+ ret = sdram->pmit;
+ break;
+ case 0x40: /* SDRAM_B0CR */
+ ret = sdram->bcr[0];
+ break;
+ case 0x44: /* SDRAM_B1CR */
+ ret = sdram->bcr[1];
+ break;
+ case 0x48: /* SDRAM_B2CR */
+ ret = sdram->bcr[2];
+ break;
+ case 0x4C: /* SDRAM_B3CR */
+ ret = sdram->bcr[3];
+ break;
+ case 0x80: /* SDRAM_TR */
+ ret = -1; /* ? */
+ break;
+ case 0x94: /* SDRAM_ECCCFG */
+ ret = sdram->ecccfg;
+ break;
+ case 0x98: /* SDRAM_ECCESR */
+ ret = sdram->eccesr;
+ break;
+ default: /* Error */
+ ret = -1;
+ break;
+ }
+ break;
+ default:
+ /* Avoid gcc warning */
+ ret = 0x00000000;
+ break;
+ }
+
+ return ret;
+}
+
+static void dcr_write_sdram (void *opaque, int dcrn, uint32_t val)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = opaque;
+ switch (dcrn) {
+ case SDRAM0_CFGADDR:
+ sdram->addr = val;
+ break;
+ case SDRAM0_CFGDATA:
+ switch (sdram->addr) {
+ case 0x00: /* SDRAM_BESR0 */
+ sdram->besr0 &= ~val;
+ break;
+ case 0x08: /* SDRAM_BESR1 */
+ sdram->besr1 &= ~val;
+ break;
+ case 0x10: /* SDRAM_BEAR */
+ sdram->bear = val;
+ break;
+ case 0x20: /* SDRAM_CFG */
+ val &= 0xFFE00000;
+ if (!(sdram->cfg & 0x80000000) && (val & 0x80000000)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: enable SDRAM controller\n", __func__);
+#endif
+ /* validate all RAM mappings */
+ sdram_map_bcr(sdram);
+ sdram->status &= ~0x80000000;
+ } else if ((sdram->cfg & 0x80000000) && !(val & 0x80000000)) {
+#ifdef DEBUG_SDRAM
+ printf("%s: disable SDRAM controller\n", __func__);
+#endif
+ /* invalidate all RAM mappings */
+ sdram_unmap_bcr(sdram);
+ sdram->status |= 0x80000000;
+ }
+ if (!(sdram->cfg & 0x40000000) && (val & 0x40000000))
+ sdram->status |= 0x40000000;
+ else if ((sdram->cfg & 0x40000000) && !(val & 0x40000000))
+ sdram->status &= ~0x40000000;
+ sdram->cfg = val;
+ break;
+ case 0x24: /* SDRAM_STATUS */
+ /* Read-only register */
+ break;
+ case 0x30: /* SDRAM_RTR */
+ sdram->rtr = val & 0x3FF80000;
+ break;
+ case 0x34: /* SDRAM_PMIT */
+ sdram->pmit = (val & 0xF8000000) | 0x07C00000;
+ break;
+ case 0x40: /* SDRAM_B0CR */
+ sdram_set_bcr(sdram, &sdram->bcr[0], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x44: /* SDRAM_B1CR */
+ sdram_set_bcr(sdram, &sdram->bcr[1], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x48: /* SDRAM_B2CR */
+ sdram_set_bcr(sdram, &sdram->bcr[2], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x4C: /* SDRAM_B3CR */
+ sdram_set_bcr(sdram, &sdram->bcr[3], val, sdram->cfg & 0x80000000);
+ break;
+ case 0x80: /* SDRAM_TR */
+ sdram->tr = val & 0x018FC01F;
+ break;
+ case 0x94: /* SDRAM_ECCCFG */
+ sdram->ecccfg = val & 0x00F00000;
+ break;
+ case 0x98: /* SDRAM_ECCESR */
+ val &= 0xFFF0F000;
+ if (sdram->eccesr == 0 && val != 0)
+ qemu_irq_raise(sdram->irq);
+ else if (sdram->eccesr != 0 && val == 0)
+ qemu_irq_lower(sdram->irq);
+ sdram->eccesr = val;
+ break;
+ default: /* Error */
+ break;
+ }
+ break;
+ }
+}
+
+static void sdram_reset (void *opaque)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = opaque;
+ sdram->addr = 0x00000000;
+ sdram->bear = 0x00000000;
+ sdram->besr0 = 0x00000000; /* No error */
+ sdram->besr1 = 0x00000000; /* No error */
+ sdram->cfg = 0x00000000;
+ sdram->ecccfg = 0x00000000; /* No ECC */
+ sdram->eccesr = 0x00000000; /* No error */
+ sdram->pmit = 0x07C00000;
+ sdram->rtr = 0x05F00000;
+ sdram->tr = 0x00854009;
+ /* We pre-initialize RAM banks */
+ sdram->status = 0x00000000;
+ sdram->cfg = 0x00800000;
+}
+
+void ppc4xx_sdram_init (CPUPPCState *env, qemu_irq irq, int nbanks,
+ MemoryRegion *ram_memories,
+ hwaddr *ram_bases,
+ hwaddr *ram_sizes,
+ int do_init)
+{
+ ppc4xx_sdram_t *sdram;
+
+ sdram = g_malloc0(sizeof(ppc4xx_sdram_t));
+ sdram->irq = irq;
+ sdram->nbanks = nbanks;
+ sdram->ram_memories = ram_memories;
+ memset(sdram->ram_bases, 0, 4 * sizeof(hwaddr));
+ memcpy(sdram->ram_bases, ram_bases,
+ nbanks * sizeof(hwaddr));
+ memset(sdram->ram_sizes, 0, 4 * sizeof(hwaddr));
+ memcpy(sdram->ram_sizes, ram_sizes,
+ nbanks * sizeof(hwaddr));
+ qemu_register_reset(&sdram_reset, sdram);
+ ppc_dcr_register(env, SDRAM0_CFGADDR,
+ sdram, &dcr_read_sdram, &dcr_write_sdram);
+ ppc_dcr_register(env, SDRAM0_CFGDATA,
+ sdram, &dcr_read_sdram, &dcr_write_sdram);
+ if (do_init)
+ sdram_map_bcr(sdram);
+}
+
+/* Fill in consecutive SDRAM banks with 'ram_size' bytes of memory.
+ *
+ * sdram_bank_sizes[] must be 0-terminated.
+ *
+ * The 4xx SDRAM controller supports a small number of banks, and each bank
+ * must be one of a small set of sizes. The number of banks and the supported
+ * sizes varies by SoC. */
+ram_addr_t ppc4xx_sdram_adjust(ram_addr_t ram_size, int nr_banks,
+ MemoryRegion ram_memories[],
+ hwaddr ram_bases[],
+ hwaddr ram_sizes[],
+ const unsigned int sdram_bank_sizes[])
+{
+ ram_addr_t size_left = ram_size;
+ ram_addr_t base = 0;
+ int i;
+ int j;
+
+ for (i = 0; i < nr_banks; i++) {
+ for (j = 0; sdram_bank_sizes[j] != 0; j++) {
+ unsigned int bank_size = sdram_bank_sizes[j];
+
+ if (bank_size <= size_left) {
+ char name[32];
+ snprintf(name, sizeof(name), "ppc4xx.sdram%d", i);
+ memory_region_init_ram(&ram_memories[i], name, bank_size);
+ vmstate_register_ram_global(&ram_memories[i]);
+ ram_bases[i] = base;
+ ram_sizes[i] = bank_size;
+ base += bank_size;
+ size_left -= bank_size;
+ break;
+ }
+ }
+
+ if (!size_left) {
+ /* No need to use the remaining banks. */
+ break;
+ }
+ }
+
+ ram_size -= size_left;
+ if (size_left)
+ printf("Truncating memory to %d MiB to fit SDRAM controller limits.\n",
+ (int)(ram_size >> 20));
+
+ return ram_size;
+}
diff --git a/hw/ppc/ppc_booke.c b/hw/ppc/ppc_booke.c
new file mode 100644
index 0000000..30375c0
--- /dev/null
+++ b/hw/ppc/ppc_booke.c
@@ -0,0 +1,273 @@
+/*
+ * QEMU PowerPC Booke hardware System Emulator
+ *
+ * Copyright (c) 2011 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 "hw/hw.h"
+#include "hw/ppc.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "hw/nvram.h"
+#include "qemu/log.h"
+#include "hw/loader.h"
+
+
+/* Timer Control Register */
+
+#define TCR_WP_SHIFT 30 /* Watchdog Timer Period */
+#define TCR_WP_MASK (0x3 << TCR_WP_SHIFT)
+#define TCR_WRC_SHIFT 28 /* Watchdog Timer Reset Control */
+#define TCR_WRC_MASK (0x3 << TCR_WRC_SHIFT)
+#define TCR_WIE (1 << 27) /* Watchdog Timer Interrupt Enable */
+#define TCR_DIE (1 << 26) /* Decrementer Interrupt Enable */
+#define TCR_FP_SHIFT 24 /* Fixed-Interval Timer Period */
+#define TCR_FP_MASK (0x3 << TCR_FP_SHIFT)
+#define TCR_FIE (1 << 23) /* Fixed-Interval Timer Interrupt Enable */
+#define TCR_ARE (1 << 22) /* Auto-Reload Enable */
+
+/* Timer Control Register (e500 specific fields) */
+
+#define TCR_E500_FPEXT_SHIFT 13 /* Fixed-Interval Timer Period Extension */
+#define TCR_E500_FPEXT_MASK (0xf << TCR_E500_FPEXT_SHIFT)
+#define TCR_E500_WPEXT_SHIFT 17 /* Watchdog Timer Period Extension */
+#define TCR_E500_WPEXT_MASK (0xf << TCR_E500_WPEXT_SHIFT)
+
+/* Timer Status Register */
+
+#define TSR_FIS (1 << 26) /* Fixed-Interval Timer Interrupt Status */
+#define TSR_DIS (1 << 27) /* Decrementer Interrupt Status */
+#define TSR_WRS_SHIFT 28 /* Watchdog Timer Reset Status */
+#define TSR_WRS_MASK (0x3 << TSR_WRS_SHIFT)
+#define TSR_WIS (1 << 30) /* Watchdog Timer Interrupt Status */
+#define TSR_ENW (1 << 31) /* Enable Next Watchdog Timer */
+
+typedef struct booke_timer_t booke_timer_t;
+struct booke_timer_t {
+
+ uint64_t fit_next;
+ struct QEMUTimer *fit_timer;
+
+ uint64_t wdt_next;
+ struct QEMUTimer *wdt_timer;
+
+ uint32_t flags;
+};
+
+static void booke_update_irq(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_DECR,
+ (env->spr[SPR_BOOKE_TSR] & TSR_DIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_DIE));
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_WDT,
+ (env->spr[SPR_BOOKE_TSR] & TSR_WIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_WIE));
+
+ ppc_set_irq(cpu, PPC_INTERRUPT_FIT,
+ (env->spr[SPR_BOOKE_TSR] & TSR_FIS
+ && env->spr[SPR_BOOKE_TCR] & TCR_FIE));
+}
+
+/* Return the location of the bit of time base at which the FIT will raise an
+ interrupt */
+static uint8_t booke_get_fit_target(CPUPPCState *env, ppc_tb_t *tb_env)
+{
+ uint8_t fp = (env->spr[SPR_BOOKE_TCR] & TCR_FP_MASK) >> TCR_FP_SHIFT;
+
+ if (tb_env->flags & PPC_TIMER_E500) {
+ /* e500 Fixed-interval timer period extension */
+ uint32_t fpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_FPEXT_MASK)
+ >> TCR_E500_FPEXT_SHIFT;
+ fp = 63 - (fp | fpext << 2);
+ } else {
+ fp = env->fit_period[fp];
+ }
+
+ return fp;
+}
+
+/* Return the location of the bit of time base at which the WDT will raise an
+ interrupt */
+static uint8_t booke_get_wdt_target(CPUPPCState *env, ppc_tb_t *tb_env)
+{
+ uint8_t wp = (env->spr[SPR_BOOKE_TCR] & TCR_WP_MASK) >> TCR_WP_SHIFT;
+
+ if (tb_env->flags & PPC_TIMER_E500) {
+ /* e500 Watchdog timer period extension */
+ uint32_t wpext = (env->spr[SPR_BOOKE_TCR] & TCR_E500_WPEXT_MASK)
+ >> TCR_E500_WPEXT_SHIFT;
+ wp = 63 - (wp | wpext << 2);
+ } else {
+ wp = env->wdt_period[wp];
+ }
+
+ return wp;
+}
+
+static void booke_update_fixed_timer(CPUPPCState *env,
+ uint8_t target_bit,
+ uint64_t *next,
+ struct QEMUTimer *timer)
+{
+ ppc_tb_t *tb_env = env->tb_env;
+ uint64_t lapse;
+ uint64_t tb;
+ uint64_t period = 1 << (target_bit + 1);
+ uint64_t now;
+
+ now = qemu_get_clock_ns(vm_clock);
+ tb = cpu_ppc_get_tb(tb_env, now, tb_env->tb_offset);
+
+ lapse = period - ((tb - (1 << target_bit)) & (period - 1));
+
+ *next = now + muldiv64(lapse, get_ticks_per_sec(), tb_env->tb_freq);
+
+ /* XXX: If expire time is now. We can't run the callback because we don't
+ * have access to it. So we just set the timer one nanosecond later.
+ */
+
+ if (*next == now) {
+ (*next)++;
+ }
+
+ qemu_mod_timer(timer, *next);
+}
+
+static void booke_decr_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ env->spr[SPR_BOOKE_TSR] |= TSR_DIS;
+ booke_update_irq(cpu);
+
+ if (env->spr[SPR_BOOKE_TCR] & TCR_ARE) {
+ /* Auto Reload */
+ cpu_ppc_store_decr(env, env->spr[SPR_BOOKE_DECAR]);
+ }
+}
+
+static void booke_fit_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+
+ tb_env = env->tb_env;
+ booke_timer = tb_env->opaque;
+ env->spr[SPR_BOOKE_TSR] |= TSR_FIS;
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_fit_target(env, tb_env),
+ &booke_timer->fit_next,
+ booke_timer->fit_timer);
+}
+
+static void booke_wdt_cb(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+
+ tb_env = env->tb_env;
+ booke_timer = tb_env->opaque;
+
+ /* TODO: There's lots of complicated stuff to do here */
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_wdt_target(env, tb_env),
+ &booke_timer->wdt_next,
+ booke_timer->wdt_timer);
+}
+
+void store_booke_tsr(CPUPPCState *env, target_ulong val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+
+ env->spr[SPR_BOOKE_TSR] &= ~val;
+ booke_update_irq(cpu);
+}
+
+void store_booke_tcr(CPUPPCState *env, target_ulong val)
+{
+ PowerPCCPU *cpu = ppc_env_get_cpu(env);
+ ppc_tb_t *tb_env = env->tb_env;
+ booke_timer_t *booke_timer = tb_env->opaque;
+
+ tb_env = env->tb_env;
+ env->spr[SPR_BOOKE_TCR] = val;
+
+ booke_update_irq(cpu);
+
+ booke_update_fixed_timer(env,
+ booke_get_fit_target(env, tb_env),
+ &booke_timer->fit_next,
+ booke_timer->fit_timer);
+
+ booke_update_fixed_timer(env,
+ booke_get_wdt_target(env, tb_env),
+ &booke_timer->wdt_next,
+ booke_timer->wdt_timer);
+
+}
+
+static void ppc_booke_timer_reset_handle(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ env->spr[SPR_BOOKE_TSR] = 0;
+ env->spr[SPR_BOOKE_TCR] = 0;
+
+ booke_update_irq(cpu);
+}
+
+void ppc_booke_timers_init(PowerPCCPU *cpu, uint32_t freq, uint32_t flags)
+{
+ ppc_tb_t *tb_env;
+ booke_timer_t *booke_timer;
+
+ tb_env = g_malloc0(sizeof(ppc_tb_t));
+ booke_timer = g_malloc0(sizeof(booke_timer_t));
+
+ cpu->env.tb_env = tb_env;
+ tb_env->flags = flags | PPC_TIMER_BOOKE | PPC_DECR_ZERO_TRIGGERED;
+
+ tb_env->tb_freq = freq;
+ tb_env->decr_freq = freq;
+ tb_env->opaque = booke_timer;
+ tb_env->decr_timer = qemu_new_timer_ns(vm_clock, &booke_decr_cb, cpu);
+
+ booke_timer->fit_timer =
+ qemu_new_timer_ns(vm_clock, &booke_fit_cb, cpu);
+ booke_timer->wdt_timer =
+ qemu_new_timer_ns(vm_clock, &booke_wdt_cb, cpu);
+
+ qemu_register_reset(ppc_booke_timer_reset_handle, cpu);
+}
diff --git a/hw/ppc/ppce500_spin.c b/hw/ppc/ppce500_spin.c
new file mode 100644
index 0000000..d904fbe
--- /dev/null
+++ b/hw/ppc/ppce500_spin.c
@@ -0,0 +1,222 @@
+/*
+ * QEMU PowerPC e500v2 ePAPR spinning code
+ *
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * This code is not really a device, but models an interface that usually
+ * firmware takes care of. It's used when QEMU plays the role of firmware.
+ *
+ * Specification:
+ *
+ * https://www.power.org/resources/downloads/Power_ePAPR_APPROVED_v1.1.pdf
+ *
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+
+#define MAX_CPUS 32
+
+typedef struct spin_info {
+ uint64_t addr;
+ uint64_t r3;
+ uint32_t resv;
+ uint32_t pir;
+ uint64_t reserved;
+} QEMU_PACKED SpinInfo;
+
+typedef struct spin_state {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ SpinInfo spin[MAX_CPUS];
+} SpinState;
+
+typedef struct spin_kick {
+ PowerPCCPU *cpu;
+ SpinInfo *spin;
+} SpinKick;
+
+static void spin_reset(void *opaque)
+{
+ SpinState *s = opaque;
+ int i;
+
+ for (i = 0; i < MAX_CPUS; i++) {
+ SpinInfo *info = &s->spin[i];
+
+ info->pir = i;
+ info->r3 = i;
+ info->addr = 1;
+ }
+}
+
+/* Create -kernel TLB entries for BookE, linearly spanning 256MB. */
+static inline hwaddr booke206_page_size_to_tlb(uint64_t size)
+{
+ return (ffs(size >> 10) - 1) >> 1;
+}
+
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa,
+ hwaddr len)
+{
+ ppcmas_tlb_t *tlb = booke206_get_tlbm(env, 1, 0, 1);
+ hwaddr size;
+
+ size = (booke206_page_size_to_tlb(len) << MAS1_TSIZE_SHIFT);
+ tlb->mas1 = MAS1_VALID | size;
+ tlb->mas2 = (va & TARGET_PAGE_MASK) | MAS2_M;
+ tlb->mas7_3 = pa & TARGET_PAGE_MASK;
+ tlb->mas7_3 |= MAS3_UR | MAS3_UW | MAS3_UX | MAS3_SR | MAS3_SW | MAS3_SX;
+ env->tlb_dirty = true;
+}
+
+static void spin_kick(void *data)
+{
+ SpinKick *kick = data;
+ CPUState *cpu = CPU(kick->cpu);
+ CPUPPCState *env = &kick->cpu->env;
+ SpinInfo *curspin = kick->spin;
+ hwaddr map_size = 64 * 1024 * 1024;
+ hwaddr map_start;
+
+ cpu_synchronize_state(env);
+ stl_p(&curspin->pir, env->spr[SPR_PIR]);
+ env->nip = ldq_p(&curspin->addr) & (map_size - 1);
+ env->gpr[3] = ldq_p(&curspin->r3);
+ env->gpr[4] = 0;
+ env->gpr[5] = 0;
+ env->gpr[6] = 0;
+ env->gpr[7] = map_size;
+ env->gpr[8] = 0;
+ env->gpr[9] = 0;
+
+ map_start = ldq_p(&curspin->addr) & ~(map_size - 1);
+ mmubooke_create_initial_mapping(env, 0, map_start, map_size);
+
+ env->halted = 0;
+ env->exception_index = -1;
+ cpu->stopped = false;
+ qemu_cpu_kick(cpu);
+}
+
+static void spin_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned len)
+{
+ SpinState *s = opaque;
+ int env_idx = addr / sizeof(SpinInfo);
+ CPUState *cpu;
+ SpinInfo *curspin = &s->spin[env_idx];
+ uint8_t *curspin_p = (uint8_t*)curspin;
+
+ cpu = qemu_get_cpu(env_idx);
+ if (cpu == NULL) {
+ /* Unknown CPU */
+ return;
+ }
+
+ if (cpu->cpu_index == 0) {
+ /* primary CPU doesn't spin */
+ return;
+ }
+
+ curspin_p = &curspin_p[addr % sizeof(SpinInfo)];
+ switch (len) {
+ case 1:
+ stb_p(curspin_p, value);
+ break;
+ case 2:
+ stw_p(curspin_p, value);
+ break;
+ case 4:
+ stl_p(curspin_p, value);
+ break;
+ }
+
+ if (!(ldq_p(&curspin->addr) & 1)) {
+ /* run CPU */
+ SpinKick kick = {
+ .cpu = POWERPC_CPU(cpu),
+ .spin = curspin,
+ };
+
+ run_on_cpu(cpu, spin_kick, &kick);
+ }
+}
+
+static uint64_t spin_read(void *opaque, hwaddr addr, unsigned len)
+{
+ SpinState *s = opaque;
+ uint8_t *spin_p = &((uint8_t*)s->spin)[addr];
+
+ switch (len) {
+ case 1:
+ return ldub_p(spin_p);
+ case 2:
+ return lduw_p(spin_p);
+ case 4:
+ return ldl_p(spin_p);
+ default:
+ hw_error("ppce500: unexpected %s with len = %u", __func__, len);
+ }
+}
+
+static const MemoryRegionOps spin_rw_ops = {
+ .read = spin_read,
+ .write = spin_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+};
+
+static int ppce500_spin_initfn(SysBusDevice *dev)
+{
+ SpinState *s;
+
+ s = FROM_SYSBUS(SpinState, SYS_BUS_DEVICE(dev));
+
+ memory_region_init_io(&s->iomem, &spin_rw_ops, s, "e500 spin pv device",
+ sizeof(SpinInfo) * MAX_CPUS);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ qemu_register_reset(spin_reset, s);
+
+ return 0;
+}
+
+static void ppce500_spin_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = ppce500_spin_initfn;
+}
+
+static const TypeInfo ppce500_spin_info = {
+ .name = "e500-spin",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SpinState),
+ .class_init = ppce500_spin_class_init,
+};
+
+static void ppce500_spin_register_types(void)
+{
+ type_register_static(&ppce500_spin_info);
+}
+
+type_init(ppce500_spin_register_types)
diff --git a/hw/ppc/spapr.c b/hw/ppc/spapr.c
new file mode 100644
index 0000000..2709c66
--- /dev/null
+++ b/hw/ppc/spapr.c
@@ -0,0 +1,963 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2010 David Gibson, IBM Corporation.
+ *
+ * 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 "sysemu/sysemu.h"
+#include "hw/hw.h"
+#include "elf.h"
+#include "net/net.h"
+#include "sysemu/blockdev.h"
+#include "sysemu/cpus.h"
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+
+#include "hw/boards.h"
+#include "hw/ppc.h"
+#include "hw/loader.h"
+
+#include "hw/spapr.h"
+#include "hw/spapr_vio.h"
+#include "hw/spapr_pci.h"
+#include "hw/xics.h"
+#include "hw/pci/msi.h"
+
+#include "sysemu/kvm.h"
+#include "kvm_ppc.h"
+#include "hw/pci/pci.h"
+
+#include "exec/address-spaces.h"
+#include "hw/usb.h"
+#include "qemu/config-file.h"
+
+#include <libfdt.h>
+
+/* SLOF memory layout:
+ *
+ * SLOF raw image loaded at 0, copies its romfs right below the flat
+ * device-tree, then position SLOF itself 31M below that
+ *
+ * So we set FW_OVERHEAD to 40MB which should account for all of that
+ * and more
+ *
+ * We load our kernel at 4M, leaving space for SLOF initial image
+ */
+#define FDT_MAX_SIZE 0x10000
+#define RTAS_MAX_SIZE 0x10000
+#define FW_MAX_SIZE 0x400000
+#define FW_FILE_NAME "slof.bin"
+#define FW_OVERHEAD 0x2800000
+#define KERNEL_LOAD_ADDR FW_MAX_SIZE
+
+#define MIN_RMA_SLOF 128UL
+
+#define TIMEBASE_FREQ 512000000ULL
+
+#define MAX_CPUS 256
+#define XICS_IRQS 1024
+
+#define PHANDLE_XICP 0x00001111
+
+#define HTAB_SIZE(spapr) (1ULL << ((spapr)->htab_shift))
+
+sPAPREnvironment *spapr;
+
+int spapr_allocate_irq(int hint, bool lsi)
+{
+ int irq;
+
+ if (hint) {
+ irq = hint;
+ /* FIXME: we should probably check for collisions somehow */
+ } else {
+ irq = spapr->next_irq++;
+ }
+
+ /* Configure irq type */
+ if (!xics_get_qirq(spapr->icp, irq)) {
+ return 0;
+ }
+
+ xics_set_irq_type(spapr->icp, irq, lsi);
+
+ return irq;
+}
+
+/* Allocate block of consequtive IRQs, returns a number of the first */
+int spapr_allocate_irq_block(int num, bool lsi)
+{
+ int first = -1;
+ int i;
+
+ for (i = 0; i < num; ++i) {
+ int irq;
+
+ irq = spapr_allocate_irq(0, lsi);
+ if (!irq) {
+ return -1;
+ }
+
+ if (0 == i) {
+ first = irq;
+ }
+
+ /* If the above doesn't create a consecutive block then that's
+ * an internal bug */
+ assert(irq == (first + i));
+ }
+
+ return first;
+}
+
+static int spapr_fixup_cpu_dt(void *fdt, sPAPREnvironment *spapr)
+{
+ int ret = 0, offset;
+ CPUPPCState *env;
+ CPUState *cpu;
+ char cpu_model[32];
+ int smt = kvmppc_smt_threads();
+ uint32_t pft_size_prop[] = {0, cpu_to_be32(spapr->htab_shift)};
+
+ assert(spapr->cpu_model);
+
+ for (env = first_cpu; env != NULL; env = env->next_cpu) {
+ cpu = CPU(ppc_env_get_cpu(env));
+ uint32_t associativity[] = {cpu_to_be32(0x5),
+ cpu_to_be32(0x0),
+ cpu_to_be32(0x0),
+ cpu_to_be32(0x0),
+ cpu_to_be32(cpu->numa_node),
+ cpu_to_be32(cpu->cpu_index)};
+
+ if ((cpu->cpu_index % smt) != 0) {
+ continue;
+ }
+
+ snprintf(cpu_model, 32, "/cpus/%s@%x", spapr->cpu_model,
+ cpu->cpu_index);
+
+ offset = fdt_path_offset(fdt, cpu_model);
+ if (offset < 0) {
+ return offset;
+ }
+
+ if (nb_numa_nodes > 1) {
+ ret = fdt_setprop(fdt, offset, "ibm,associativity", associativity,
+ sizeof(associativity));
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ ret = fdt_setprop(fdt, offset, "ibm,pft-size",
+ pft_size_prop, sizeof(pft_size_prop));
+ if (ret < 0) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+
+static size_t create_page_sizes_prop(CPUPPCState *env, uint32_t *prop,
+ size_t maxsize)
+{
+ size_t maxcells = maxsize / sizeof(uint32_t);
+ int i, j, count;
+ uint32_t *p = prop;
+
+ for (i = 0; i < PPC_PAGE_SIZES_MAX_SZ; i++) {
+ struct ppc_one_seg_page_size *sps = &env->sps.sps[i];
+
+ if (!sps->page_shift) {
+ break;
+ }
+ for (count = 0; count < PPC_PAGE_SIZES_MAX_SZ; count++) {
+ if (sps->enc[count].page_shift == 0) {
+ break;
+ }
+ }
+ if ((p - prop) >= (maxcells - 3 - count * 2)) {
+ break;
+ }
+ *(p++) = cpu_to_be32(sps->page_shift);
+ *(p++) = cpu_to_be32(sps->slb_enc);
+ *(p++) = cpu_to_be32(count);
+ for (j = 0; j < count; j++) {
+ *(p++) = cpu_to_be32(sps->enc[j].page_shift);
+ *(p++) = cpu_to_be32(sps->enc[j].pte_enc);
+ }
+ }
+
+ return (p - prop) * sizeof(uint32_t);
+}
+
+#define _FDT(exp) \
+ do { \
+ int ret = (exp); \
+ if (ret < 0) { \
+ fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
+ #exp, fdt_strerror(ret)); \
+ exit(1); \
+ } \
+ } while (0)
+
+
+static void *spapr_create_fdt_skel(const char *cpu_model,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ hwaddr kernel_size,
+ const char *boot_device,
+ const char *kernel_cmdline,
+ uint32_t epow_irq)
+{
+ void *fdt;
+ CPUPPCState *env;
+ uint32_t start_prop = cpu_to_be32(initrd_base);
+ uint32_t end_prop = cpu_to_be32(initrd_base + initrd_size);
+ char hypertas_prop[] = "hcall-pft\0hcall-term\0hcall-dabr\0hcall-interrupt"
+ "\0hcall-tce\0hcall-vio\0hcall-splpar\0hcall-bulk";
+ char qemu_hypertas_prop[] = "hcall-memop1";
+ uint32_t refpoints[] = {cpu_to_be32(0x4), cpu_to_be32(0x4)};
+ uint32_t interrupt_server_ranges_prop[] = {0, cpu_to_be32(smp_cpus)};
+ char *modelname;
+ int i, smt = kvmppc_smt_threads();
+ unsigned char vec5[] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x80};
+
+ fdt = g_malloc0(FDT_MAX_SIZE);
+ _FDT((fdt_create(fdt, FDT_MAX_SIZE)));
+
+ if (kernel_size) {
+ _FDT((fdt_add_reservemap_entry(fdt, KERNEL_LOAD_ADDR, kernel_size)));
+ }
+ if (initrd_size) {
+ _FDT((fdt_add_reservemap_entry(fdt, initrd_base, initrd_size)));
+ }
+ _FDT((fdt_finish_reservemap(fdt)));
+
+ /* Root node */
+ _FDT((fdt_begin_node(fdt, "")));
+ _FDT((fdt_property_string(fdt, "device_type", "chrp")));
+ _FDT((fdt_property_string(fdt, "model", "IBM pSeries (emulated by qemu)")));
+
+ _FDT((fdt_property_cell(fdt, "#address-cells", 0x2)));
+ _FDT((fdt_property_cell(fdt, "#size-cells", 0x2)));
+
+ /* /chosen */
+ _FDT((fdt_begin_node(fdt, "chosen")));
+
+ /* Set Form1_affinity */
+ _FDT((fdt_property(fdt, "ibm,architecture-vec-5", vec5, sizeof(vec5))));
+
+ _FDT((fdt_property_string(fdt, "bootargs", kernel_cmdline)));
+ _FDT((fdt_property(fdt, "linux,initrd-start",
+ &start_prop, sizeof(start_prop))));
+ _FDT((fdt_property(fdt, "linux,initrd-end",
+ &end_prop, sizeof(end_prop))));
+ if (kernel_size) {
+ uint64_t kprop[2] = { cpu_to_be64(KERNEL_LOAD_ADDR),
+ cpu_to_be64(kernel_size) };
+
+ _FDT((fdt_property(fdt, "qemu,boot-kernel", &kprop, sizeof(kprop))));
+ }
+ if (boot_device) {
+ _FDT((fdt_property_string(fdt, "qemu,boot-device", boot_device)));
+ }
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-width", graphic_width)));
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-height", graphic_height)));
+ _FDT((fdt_property_cell(fdt, "qemu,graphic-depth", graphic_depth)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* cpus */
+ _FDT((fdt_begin_node(fdt, "cpus")));
+
+ _FDT((fdt_property_cell(fdt, "#address-cells", 0x1)));
+ _FDT((fdt_property_cell(fdt, "#size-cells", 0x0)));
+
+ modelname = g_strdup(cpu_model);
+
+ for (i = 0; i < strlen(modelname); i++) {
+ modelname[i] = toupper(modelname[i]);
+ }
+
+ /* This is needed during FDT finalization */
+ spapr->cpu_model = g_strdup(modelname);
+
+ for (env = first_cpu; env != NULL; env = env->next_cpu) {
+ CPUState *cpu = CPU(ppc_env_get_cpu(env));
+ int index = cpu->cpu_index;
+ uint32_t servers_prop[smp_threads];
+ uint32_t gservers_prop[smp_threads * 2];
+ char *nodename;
+ uint32_t segs[] = {cpu_to_be32(28), cpu_to_be32(40),
+ 0xffffffff, 0xffffffff};
+ uint32_t tbfreq = kvm_enabled() ? kvmppc_get_tbfreq() : TIMEBASE_FREQ;
+ uint32_t cpufreq = kvm_enabled() ? kvmppc_get_clockfreq() : 1000000000;
+ uint32_t page_sizes_prop[64];
+ size_t page_sizes_prop_size;
+
+ if ((index % smt) != 0) {
+ continue;
+ }
+
+ nodename = g_strdup_printf("%s@%x", modelname, index);
+
+ _FDT((fdt_begin_node(fdt, nodename)));
+
+ g_free(nodename);
+
+ _FDT((fdt_property_cell(fdt, "reg", index)));
+ _FDT((fdt_property_string(fdt, "device_type", "cpu")));
+
+ _FDT((fdt_property_cell(fdt, "cpu-version", env->spr[SPR_PVR])));
+ _FDT((fdt_property_cell(fdt, "dcache-block-size",
+ env->dcache_line_size)));
+ _FDT((fdt_property_cell(fdt, "icache-block-size",
+ env->icache_line_size)));
+ _FDT((fdt_property_cell(fdt, "timebase-frequency", tbfreq)));
+ _FDT((fdt_property_cell(fdt, "clock-frequency", cpufreq)));
+ _FDT((fdt_property_cell(fdt, "ibm,slb-size", env->slb_nr)));
+ _FDT((fdt_property_string(fdt, "status", "okay")));
+ _FDT((fdt_property(fdt, "64-bit", NULL, 0)));
+
+ /* Build interrupt servers and gservers properties */
+ for (i = 0; i < smp_threads; i++) {
+ servers_prop[i] = cpu_to_be32(index + i);
+ /* Hack, direct the group queues back to cpu 0 */
+ gservers_prop[i*2] = cpu_to_be32(index + i);
+ gservers_prop[i*2 + 1] = 0;
+ }
+ _FDT((fdt_property(fdt, "ibm,ppc-interrupt-server#s",
+ servers_prop, sizeof(servers_prop))));
+ _FDT((fdt_property(fdt, "ibm,ppc-interrupt-gserver#s",
+ gservers_prop, sizeof(gservers_prop))));
+
+ if (env->mmu_model & POWERPC_MMU_1TSEG) {
+ _FDT((fdt_property(fdt, "ibm,processor-segment-sizes",
+ segs, sizeof(segs))));
+ }
+
+ /* Advertise VMX/VSX (vector extensions) if available
+ * 0 / no property == no vector extensions
+ * 1 == VMX / Altivec available
+ * 2 == VSX available */
+ if (env->insns_flags & PPC_ALTIVEC) {
+ uint32_t vmx = (env->insns_flags2 & PPC2_VSX) ? 2 : 1;
+
+ _FDT((fdt_property_cell(fdt, "ibm,vmx", vmx)));
+ }
+
+ /* Advertise DFP (Decimal Floating Point) if available
+ * 0 / no property == no DFP
+ * 1 == DFP available */
+ if (env->insns_flags2 & PPC2_DFP) {
+ _FDT((fdt_property_cell(fdt, "ibm,dfp", 1)));
+ }
+
+ page_sizes_prop_size = create_page_sizes_prop(env, page_sizes_prop,
+ sizeof(page_sizes_prop));
+ if (page_sizes_prop_size) {
+ _FDT((fdt_property(fdt, "ibm,segment-page-sizes",
+ page_sizes_prop, page_sizes_prop_size)));
+ }
+
+ _FDT((fdt_end_node(fdt)));
+ }
+
+ g_free(modelname);
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* RTAS */
+ _FDT((fdt_begin_node(fdt, "rtas")));
+
+ _FDT((fdt_property(fdt, "ibm,hypertas-functions", hypertas_prop,
+ sizeof(hypertas_prop))));
+ _FDT((fdt_property(fdt, "qemu,hypertas-functions", qemu_hypertas_prop,
+ sizeof(qemu_hypertas_prop))));
+
+ _FDT((fdt_property(fdt, "ibm,associativity-reference-points",
+ refpoints, sizeof(refpoints))));
+
+ _FDT((fdt_property_cell(fdt, "rtas-error-log-max", RTAS_ERROR_LOG_MAX)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* interrupt controller */
+ _FDT((fdt_begin_node(fdt, "interrupt-controller")));
+
+ _FDT((fdt_property_string(fdt, "device_type",
+ "PowerPC-External-Interrupt-Presentation")));
+ _FDT((fdt_property_string(fdt, "compatible", "IBM,ppc-xicp")));
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+ _FDT((fdt_property(fdt, "ibm,interrupt-server-ranges",
+ interrupt_server_ranges_prop,
+ sizeof(interrupt_server_ranges_prop))));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 2)));
+ _FDT((fdt_property_cell(fdt, "linux,phandle", PHANDLE_XICP)));
+ _FDT((fdt_property_cell(fdt, "phandle", PHANDLE_XICP)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* vdevice */
+ _FDT((fdt_begin_node(fdt, "vdevice")));
+
+ _FDT((fdt_property_string(fdt, "device_type", "vdevice")));
+ _FDT((fdt_property_string(fdt, "compatible", "IBM,vdevice")));
+ _FDT((fdt_property_cell(fdt, "#address-cells", 0x1)));
+ _FDT((fdt_property_cell(fdt, "#size-cells", 0x0)));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 0x2)));
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+
+ _FDT((fdt_end_node(fdt)));
+
+ /* event-sources */
+ spapr_events_fdt_skel(fdt, epow_irq);
+
+ _FDT((fdt_end_node(fdt))); /* close root node */
+ _FDT((fdt_finish(fdt)));
+
+ return fdt;
+}
+
+static int spapr_populate_memory(sPAPREnvironment *spapr, void *fdt)
+{
+ uint32_t associativity[] = {cpu_to_be32(0x4), cpu_to_be32(0x0),
+ cpu_to_be32(0x0), cpu_to_be32(0x0),
+ cpu_to_be32(0x0)};
+ char mem_name[32];
+ hwaddr node0_size, mem_start;
+ uint64_t mem_reg_property[2];
+ int i, off;
+
+ /* memory node(s) */
+ node0_size = (nb_numa_nodes > 1) ? node_mem[0] : ram_size;
+ if (spapr->rma_size > node0_size) {
+ spapr->rma_size = node0_size;
+ }
+
+ /* RMA */
+ mem_reg_property[0] = 0;
+ mem_reg_property[1] = cpu_to_be64(spapr->rma_size);
+ off = fdt_add_subnode(fdt, 0, "memory@0");
+ _FDT(off);
+ _FDT((fdt_setprop_string(fdt, off, "device_type", "memory")));
+ _FDT((fdt_setprop(fdt, off, "reg", mem_reg_property,
+ sizeof(mem_reg_property))));
+ _FDT((fdt_setprop(fdt, off, "ibm,associativity", associativity,
+ sizeof(associativity))));
+
+ /* RAM: Node 0 */
+ if (node0_size > spapr->rma_size) {
+ mem_reg_property[0] = cpu_to_be64(spapr->rma_size);
+ mem_reg_property[1] = cpu_to_be64(node0_size - spapr->rma_size);
+
+ sprintf(mem_name, "memory@" TARGET_FMT_lx, spapr->rma_size);
+ off = fdt_add_subnode(fdt, 0, mem_name);
+ _FDT(off);
+ _FDT((fdt_setprop_string(fdt, off, "device_type", "memory")));
+ _FDT((fdt_setprop(fdt, off, "reg", mem_reg_property,
+ sizeof(mem_reg_property))));
+ _FDT((fdt_setprop(fdt, off, "ibm,associativity", associativity,
+ sizeof(associativity))));
+ }
+
+ /* RAM: Node 1 and beyond */
+ mem_start = node0_size;
+ for (i = 1; i < nb_numa_nodes; i++) {
+ mem_reg_property[0] = cpu_to_be64(mem_start);
+ mem_reg_property[1] = cpu_to_be64(node_mem[i]);
+ associativity[3] = associativity[4] = cpu_to_be32(i);
+ sprintf(mem_name, "memory@" TARGET_FMT_lx, mem_start);
+ off = fdt_add_subnode(fdt, 0, mem_name);
+ _FDT(off);
+ _FDT((fdt_setprop_string(fdt, off, "device_type", "memory")));
+ _FDT((fdt_setprop(fdt, off, "reg", mem_reg_property,
+ sizeof(mem_reg_property))));
+ _FDT((fdt_setprop(fdt, off, "ibm,associativity", associativity,
+ sizeof(associativity))));
+ mem_start += node_mem[i];
+ }
+
+ return 0;
+}
+
+static void spapr_finalize_fdt(sPAPREnvironment *spapr,
+ hwaddr fdt_addr,
+ hwaddr rtas_addr,
+ hwaddr rtas_size)
+{
+ int ret;
+ void *fdt;
+ sPAPRPHBState *phb;
+
+ fdt = g_malloc(FDT_MAX_SIZE);
+
+ /* open out the base tree into a temp buffer for the final tweaks */
+ _FDT((fdt_open_into(spapr->fdt_skel, fdt, FDT_MAX_SIZE)));
+
+ ret = spapr_populate_memory(spapr, fdt);
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup memory nodes in fdt\n");
+ exit(1);
+ }
+
+ ret = spapr_populate_vdevice(spapr->vio_bus, fdt);
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup vio devices in fdt\n");
+ exit(1);
+ }
+
+ QLIST_FOREACH(phb, &spapr->phbs, list) {
+ ret = spapr_populate_pci_dt(phb, PHANDLE_XICP, fdt);
+ }
+
+ if (ret < 0) {
+ fprintf(stderr, "couldn't setup PCI devices in fdt\n");
+ exit(1);
+ }
+
+ /* RTAS */
+ ret = spapr_rtas_device_tree_setup(fdt, rtas_addr, rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't set up RTAS device tree properties\n");
+ }
+
+ /* Advertise NUMA via ibm,associativity */
+ ret = spapr_fixup_cpu_dt(fdt, spapr);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't finalize CPU device tree properties\n");
+ }
+
+ if (!spapr->has_graphics) {
+ spapr_populate_chosen_stdout(fdt, spapr->vio_bus);
+ }
+
+ _FDT((fdt_pack(fdt)));
+
+ if (fdt_totalsize(fdt) > FDT_MAX_SIZE) {
+ hw_error("FDT too big ! 0x%x bytes (max is 0x%x)\n",
+ fdt_totalsize(fdt), FDT_MAX_SIZE);
+ exit(1);
+ }
+
+ cpu_physical_memory_write(fdt_addr, fdt, fdt_totalsize(fdt));
+
+ g_free(fdt);
+}
+
+static uint64_t translate_kernel_address(void *opaque, uint64_t addr)
+{
+ return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR;
+}
+
+static void emulate_spapr_hypercall(PowerPCCPU *cpu)
+{
+ CPUPPCState *env = &cpu->env;
+
+ if (msr_pr) {
+ hcall_dprintf("Hypercall made with MSR[PR]=1\n");
+ env->gpr[3] = H_PRIVILEGE;
+ } else {
+ env->gpr[3] = spapr_hypercall(cpu, env->gpr[3], &env->gpr[4]);
+ }
+}
+
+static void spapr_reset_htab(sPAPREnvironment *spapr)
+{
+ long shift;
+
+ /* allocate hash page table. For now we always make this 16mb,
+ * later we should probably make it scale to the size of guest
+ * RAM */
+
+ shift = kvmppc_reset_htab(spapr->htab_shift);
+
+ if (shift > 0) {
+ /* Kernel handles htab, we don't need to allocate one */
+ spapr->htab_shift = shift;
+ } else {
+ if (!spapr->htab) {
+ /* Allocate an htab if we don't yet have one */
+ spapr->htab = qemu_memalign(HTAB_SIZE(spapr), HTAB_SIZE(spapr));
+ }
+
+ /* And clear it */
+ memset(spapr->htab, 0, HTAB_SIZE(spapr));
+ }
+
+ /* Update the RMA size if necessary */
+ if (spapr->vrma_adjust) {
+ spapr->rma_size = kvmppc_rma_size(ram_size, spapr->htab_shift);
+ }
+}
+
+static void ppc_spapr_reset(void)
+{
+ /* Reset the hash table & recalc the RMA */
+ spapr_reset_htab(spapr);
+
+ qemu_devices_reset();
+
+ /* Load the fdt */
+ spapr_finalize_fdt(spapr, spapr->fdt_addr, spapr->rtas_addr,
+ spapr->rtas_size);
+
+ /* Set up the entry state */
+ first_cpu->gpr[3] = spapr->fdt_addr;
+ first_cpu->gpr[5] = 0;
+ first_cpu->halted = 0;
+ first_cpu->nip = spapr->entry_point;
+
+}
+
+static void spapr_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+
+ cpu_reset(CPU(cpu));
+
+ /* All CPUs start halted. CPU0 is unhalted from the machine level
+ * reset code and the rest are explicitly started up by the guest
+ * using an RTAS call */
+ env->halted = 1;
+
+ env->spr[SPR_HIOR] = 0;
+
+ env->external_htab = spapr->htab;
+ env->htab_base = -1;
+ env->htab_mask = HTAB_SIZE(spapr) - 1;
+ env->spr[SPR_SDR1] = (unsigned long)spapr->htab |
+ (spapr->htab_shift - 18);
+}
+
+static void spapr_create_nvram(sPAPREnvironment *spapr)
+{
+ QemuOpts *machine_opts;
+ DeviceState *dev;
+
+ dev = qdev_create(&spapr->vio_bus->bus, "spapr-nvram");
+
+ machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0);
+ if (machine_opts) {
+ const char *drivename;
+
+ drivename = qemu_opt_get(machine_opts, "nvram");
+ if (drivename) {
+ BlockDriverState *bs;
+
+ bs = bdrv_find(drivename);
+ if (!bs) {
+ fprintf(stderr, "No such block device \"%s\" for nvram\n",
+ drivename);
+ exit(1);
+ }
+ qdev_prop_set_drive_nofail(dev, "drive", bs);
+ }
+ }
+
+ qdev_init_nofail(dev);
+
+ spapr->nvram = (struct sPAPRNVRAM *)dev;
+}
+
+/* Returns whether we want to use VGA or not */
+static int spapr_vga_init(PCIBus *pci_bus)
+{
+ switch (vga_interface_type) {
+ case VGA_NONE:
+ case VGA_STD:
+ return pci_vga_init(pci_bus) != NULL;
+ default:
+ fprintf(stderr, "This vga model is not supported,"
+ "currently it only supports -vga std\n");
+ exit(0);
+ break;
+ }
+}
+
+/* pSeries LPAR / sPAPR hardware init */
+static void ppc_spapr_init(QEMUMachineInitArgs *args)
+{
+ ram_addr_t ram_size = args->ram_size;
+ const char *cpu_model = args->cpu_model;
+ const char *kernel_filename = args->kernel_filename;
+ const char *kernel_cmdline = args->kernel_cmdline;
+ const char *initrd_filename = args->initrd_filename;
+ const char *boot_device = args->boot_device;
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ PCIHostState *phb;
+ int i;
+ MemoryRegion *sysmem = get_system_memory();
+ MemoryRegion *ram = g_new(MemoryRegion, 1);
+ hwaddr rma_alloc_size;
+ uint32_t initrd_base = 0;
+ long kernel_size = 0, initrd_size = 0;
+ long load_limit, rtas_limit, fw_size;
+ char *filename;
+
+ msi_supported = true;
+
+ spapr = g_malloc0(sizeof(*spapr));
+ QLIST_INIT(&spapr->phbs);
+
+ cpu_ppc_hypercall = emulate_spapr_hypercall;
+
+ /* Allocate RMA if necessary */
+ rma_alloc_size = kvmppc_alloc_rma("ppc_spapr.rma", sysmem);
+
+ if (rma_alloc_size == -1) {
+ hw_error("qemu: Unable to create RMA\n");
+ exit(1);
+ }
+
+ if (rma_alloc_size && (rma_alloc_size < ram_size)) {
+ spapr->rma_size = rma_alloc_size;
+ } else {
+ spapr->rma_size = ram_size;
+
+ /* With KVM, we don't actually know whether KVM supports an
+ * unbounded RMA (PR KVM) or is limited by the hash table size
+ * (HV KVM using VRMA), so we always assume the latter
+ *
+ * In that case, we also limit the initial allocations for RTAS
+ * etc... to 256M since we have no way to know what the VRMA size
+ * is going to be as it depends on the size of the hash table
+ * isn't determined yet.
+ */
+ if (kvm_enabled()) {
+ spapr->vrma_adjust = 1;
+ spapr->rma_size = MIN(spapr->rma_size, 0x10000000);
+ }
+ }
+
+ /* We place the device tree and RTAS just below either the top of the RMA,
+ * or just below 2GB, whichever is lowere, so that it can be
+ * processed with 32-bit real mode code if necessary */
+ rtas_limit = MIN(spapr->rma_size, 0x80000000);
+ spapr->rtas_addr = rtas_limit - RTAS_MAX_SIZE;
+ spapr->fdt_addr = spapr->rtas_addr - FDT_MAX_SIZE;
+ load_limit = spapr->fdt_addr - FW_OVERHEAD;
+
+ /* We aim for a hash table of size 1/128 the size of RAM. The
+ * normal rule of thumb is 1/64 the size of RAM, but that's much
+ * more than needed for the Linux guests we support. */
+ spapr->htab_shift = 18; /* Minimum architected size */
+ while (spapr->htab_shift <= 46) {
+ if ((1ULL << (spapr->htab_shift + 7)) >= ram_size) {
+ break;
+ }
+ spapr->htab_shift++;
+ }
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+ cpu_model = kvm_enabled() ? "host" : "POWER7";
+ }
+ for (i = 0; i < smp_cpus; i++) {
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to find PowerPC CPU definition\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ /* Set time-base frequency to 512 MHz */
+ cpu_ppc_tb_init(env, TIMEBASE_FREQ);
+
+ /* PAPR always has exception vectors in RAM not ROM */
+ env->hreset_excp_prefix = 0;
+
+ /* Tell KVM that we're in PAPR mode */
+ if (kvm_enabled()) {
+ kvmppc_set_papr(cpu);
+ }
+
+ qemu_register_reset(spapr_cpu_reset, cpu);
+ }
+
+ /* allocate RAM */
+ spapr->ram_limit = ram_size;
+ if (spapr->ram_limit > rma_alloc_size) {
+ ram_addr_t nonrma_base = rma_alloc_size;
+ ram_addr_t nonrma_size = spapr->ram_limit - rma_alloc_size;
+
+ memory_region_init_ram(ram, "ppc_spapr.ram", nonrma_size);
+ vmstate_register_ram_global(ram);
+ memory_region_add_subregion(sysmem, nonrma_base, ram);
+ }
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "spapr-rtas.bin");
+ spapr->rtas_size = load_image_targphys(filename, spapr->rtas_addr,
+ rtas_limit - spapr->rtas_addr);
+ if (spapr->rtas_size < 0) {
+ hw_error("qemu: could not load LPAR rtas '%s'\n", filename);
+ exit(1);
+ }
+ if (spapr->rtas_size > RTAS_MAX_SIZE) {
+ hw_error("RTAS too big ! 0x%lx bytes (max is 0x%x)\n",
+ spapr->rtas_size, RTAS_MAX_SIZE);
+ exit(1);
+ }
+ g_free(filename);
+
+
+ /* Set up Interrupt Controller */
+ spapr->icp = xics_system_init(XICS_IRQS);
+ spapr->next_irq = XICS_IRQ_BASE;
+
+ /* Set up EPOW events infrastructure */
+ spapr_events_init(spapr);
+
+ /* Set up IOMMU */
+ spapr_iommu_init();
+
+ /* Set up VIO bus */
+ spapr->vio_bus = spapr_vio_bus_init();
+
+ for (i = 0; i < MAX_SERIAL_PORTS; i++) {
+ if (serial_hds[i]) {
+ spapr_vty_create(spapr->vio_bus, serial_hds[i]);
+ }
+ }
+
+ /* We always have at least the nvram device on VIO */
+ spapr_create_nvram(spapr);
+
+ /* Set up PCI */
+ spapr_pci_rtas_init();
+
+ phb = spapr_create_phb(spapr, 0, "pci");
+
+ for (i = 0; i < nb_nics; i++) {
+ NICInfo *nd = &nd_table[i];
+
+ if (!nd->model) {
+ nd->model = g_strdup("ibmveth");
+ }
+
+ if (strcmp(nd->model, "ibmveth") == 0) {
+ spapr_vlan_create(spapr->vio_bus, nd);
+ } else {
+ pci_nic_init_nofail(&nd_table[i], nd->model, NULL);
+ }
+ }
+
+ for (i = 0; i <= drive_get_max_bus(IF_SCSI); i++) {
+ spapr_vscsi_create(spapr->vio_bus);
+ }
+
+ /* Graphics */
+ if (spapr_vga_init(phb->bus)) {
+ spapr->has_graphics = true;
+ }
+
+ if (usb_enabled(spapr->has_graphics)) {
+ pci_create_simple(phb->bus, -1, "pci-ohci");
+ if (spapr->has_graphics) {
+ usbdevice_create("keyboard");
+ usbdevice_create("mouse");
+ }
+ }
+
+ if (spapr->rma_size < (MIN_RMA_SLOF << 20)) {
+ fprintf(stderr, "qemu: pSeries SLOF firmware requires >= "
+ "%ldM guest RMA (Real Mode Area memory)\n", MIN_RMA_SLOF);
+ exit(1);
+ }
+
+ if (kernel_filename) {
+ uint64_t lowaddr = 0;
+
+ kernel_size = load_elf(kernel_filename, translate_kernel_address, NULL,
+ NULL, &lowaddr, NULL, 1, ELF_MACHINE, 0);
+ if (kernel_size < 0) {
+ kernel_size = load_image_targphys(kernel_filename,
+ KERNEL_LOAD_ADDR,
+ load_limit - KERNEL_LOAD_ADDR);
+ }
+ if (kernel_size < 0) {
+ fprintf(stderr, "qemu: could not load kernel '%s'\n",
+ kernel_filename);
+ exit(1);
+ }
+
+ /* load initrd */
+ if (initrd_filename) {
+ /* Try to locate the initrd in the gap between the kernel
+ * and the firmware. Add a bit of space just in case
+ */
+ initrd_base = (KERNEL_LOAD_ADDR + kernel_size + 0x1ffff) & ~0xffff;
+ initrd_size = load_image_targphys(initrd_filename, initrd_base,
+ load_limit - initrd_base);
+ if (initrd_size < 0) {
+ fprintf(stderr, "qemu: could not load initial ram disk '%s'\n",
+ initrd_filename);
+ exit(1);
+ }
+ } else {
+ initrd_base = 0;
+ initrd_size = 0;
+ }
+ }
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, FW_FILE_NAME);
+ fw_size = load_image_targphys(filename, 0, FW_MAX_SIZE);
+ if (fw_size < 0) {
+ hw_error("qemu: could not load LPAR rtas '%s'\n", filename);
+ exit(1);
+ }
+ g_free(filename);
+
+ spapr->entry_point = 0x100;
+
+ /* Prepare the device tree */
+ spapr->fdt_skel = spapr_create_fdt_skel(cpu_model,
+ initrd_base, initrd_size,
+ kernel_size,
+ boot_device, kernel_cmdline,
+ spapr->epow_irq);
+ assert(spapr->fdt_skel != NULL);
+}
+
+static QEMUMachine spapr_machine = {
+ .name = "pseries",
+ .desc = "pSeries Logical Partition (PAPR compliant)",
+ .init = ppc_spapr_init,
+ .reset = ppc_spapr_reset,
+ .block_default_type = IF_SCSI,
+ .max_cpus = MAX_CPUS,
+ .no_parallel = 1,
+ .boot_order = NULL,
+};
+
+static void spapr_machine_init(void)
+{
+ qemu_register_machine(&spapr_machine);
+}
+
+machine_init(spapr_machine_init);
diff --git a/hw/ppc/spapr_events.c b/hw/ppc/spapr_events.c
new file mode 100644
index 0000000..ce78f09
--- /dev/null
+++ b/hw/ppc/spapr_events.c
@@ -0,0 +1,321 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * RTAS events handling
+ *
+ * Copyright (c) 2012 David Gibson, IBM Corporation.
+ *
+ * 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 "cpu.h"
+#include "sysemu/sysemu.h"
+#include "char/char.h"
+#include "hw/qdev.h"
+#include "sysemu/device_tree.h"
+
+#include "hw/spapr.h"
+#include "hw/spapr_vio.h"
+
+#include <libfdt.h>
+
+struct rtas_error_log {
+ uint32_t summary;
+#define RTAS_LOG_VERSION_MASK 0xff000000
+#define RTAS_LOG_VERSION_6 0x06000000
+#define RTAS_LOG_SEVERITY_MASK 0x00e00000
+#define RTAS_LOG_SEVERITY_ALREADY_REPORTED 0x00c00000
+#define RTAS_LOG_SEVERITY_FATAL 0x00a00000
+#define RTAS_LOG_SEVERITY_ERROR 0x00800000
+#define RTAS_LOG_SEVERITY_ERROR_SYNC 0x00600000
+#define RTAS_LOG_SEVERITY_WARNING 0x00400000
+#define RTAS_LOG_SEVERITY_EVENT 0x00200000
+#define RTAS_LOG_SEVERITY_NO_ERROR 0x00000000
+#define RTAS_LOG_DISPOSITION_MASK 0x00180000
+#define RTAS_LOG_DISPOSITION_FULLY_RECOVERED 0x00000000
+#define RTAS_LOG_DISPOSITION_LIMITED_RECOVERY 0x00080000
+#define RTAS_LOG_DISPOSITION_NOT_RECOVERED 0x00100000
+#define RTAS_LOG_OPTIONAL_PART_PRESENT 0x00040000
+#define RTAS_LOG_INITIATOR_MASK 0x0000f000
+#define RTAS_LOG_INITIATOR_UNKNOWN 0x00000000
+#define RTAS_LOG_INITIATOR_CPU 0x00001000
+#define RTAS_LOG_INITIATOR_PCI 0x00002000
+#define RTAS_LOG_INITIATOR_MEMORY 0x00004000
+#define RTAS_LOG_INITIATOR_HOTPLUG 0x00006000
+#define RTAS_LOG_TARGET_MASK 0x00000f00
+#define RTAS_LOG_TARGET_UNKNOWN 0x00000000
+#define RTAS_LOG_TARGET_CPU 0x00000100
+#define RTAS_LOG_TARGET_PCI 0x00000200
+#define RTAS_LOG_TARGET_MEMORY 0x00000400
+#define RTAS_LOG_TARGET_HOTPLUG 0x00000600
+#define RTAS_LOG_TYPE_MASK 0x000000ff
+#define RTAS_LOG_TYPE_OTHER 0x00000000
+#define RTAS_LOG_TYPE_RETRY 0x00000001
+#define RTAS_LOG_TYPE_TCE_ERR 0x00000002
+#define RTAS_LOG_TYPE_INTERN_DEV_FAIL 0x00000003
+#define RTAS_LOG_TYPE_TIMEOUT 0x00000004
+#define RTAS_LOG_TYPE_DATA_PARITY 0x00000005
+#define RTAS_LOG_TYPE_ADDR_PARITY 0x00000006
+#define RTAS_LOG_TYPE_CACHE_PARITY 0x00000007
+#define RTAS_LOG_TYPE_ADDR_INVALID 0x00000008
+#define RTAS_LOG_TYPE_ECC_UNCORR 0x00000009
+#define RTAS_LOG_TYPE_ECC_CORR 0x0000000a
+#define RTAS_LOG_TYPE_EPOW 0x00000040
+ uint32_t extended_length;
+} QEMU_PACKED;
+
+struct rtas_event_log_v6 {
+ uint8_t b0;
+#define RTAS_LOG_V6_B0_VALID 0x80
+#define RTAS_LOG_V6_B0_UNRECOVERABLE_ERROR 0x40
+#define RTAS_LOG_V6_B0_RECOVERABLE_ERROR 0x20
+#define RTAS_LOG_V6_B0_DEGRADED_OPERATION 0x10
+#define RTAS_LOG_V6_B0_PREDICTIVE_ERROR 0x08
+#define RTAS_LOG_V6_B0_NEW_LOG 0x04
+#define RTAS_LOG_V6_B0_BIGENDIAN 0x02
+ uint8_t _resv1;
+ uint8_t b2;
+#define RTAS_LOG_V6_B2_POWERPC_FORMAT 0x80
+#define RTAS_LOG_V6_B2_LOG_FORMAT_MASK 0x0f
+#define RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT 0x0e
+ uint8_t _resv2[9];
+ uint32_t company;
+#define RTAS_LOG_V6_COMPANY_IBM 0x49424d00 /* IBM<null> */
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_section_header {
+ uint16_t section_id;
+ uint16_t section_length;
+ uint8_t section_version;
+ uint8_t section_subtype;
+ uint16_t creator_component_id;
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_maina {
+#define RTAS_LOG_V6_SECTION_ID_MAINA 0x5048 /* PH */
+ struct rtas_event_log_v6_section_header hdr;
+ uint32_t creation_date; /* BCD: YYYYMMDD */
+ uint32_t creation_time; /* BCD: HHMMSS00 */
+ uint8_t _platform1[8];
+ char creator_id;
+ uint8_t _resv1[2];
+ uint8_t section_count;
+ uint8_t _resv2[4];
+ uint8_t _platform2[8];
+ uint32_t plid;
+ uint8_t _platform3[4];
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_mainb {
+#define RTAS_LOG_V6_SECTION_ID_MAINB 0x5548 /* UH */
+ struct rtas_event_log_v6_section_header hdr;
+ uint8_t subsystem_id;
+ uint8_t _platform1;
+ uint8_t event_severity;
+ uint8_t event_subtype;
+ uint8_t _platform2[4];
+ uint8_t _resv1[2];
+ uint16_t action_flags;
+ uint8_t _resv2[4];
+} QEMU_PACKED;
+
+struct rtas_event_log_v6_epow {
+#define RTAS_LOG_V6_SECTION_ID_EPOW 0x4550 /* EP */
+ struct rtas_event_log_v6_section_header hdr;
+ uint8_t sensor_value;
+#define RTAS_LOG_V6_EPOW_ACTION_RESET 0
+#define RTAS_LOG_V6_EPOW_ACTION_WARN_COOLING 1
+#define RTAS_LOG_V6_EPOW_ACTION_WARN_POWER 2
+#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN 3
+#define RTAS_LOG_V6_EPOW_ACTION_SYSTEM_HALT 4
+#define RTAS_LOG_V6_EPOW_ACTION_MAIN_ENCLOSURE 5
+#define RTAS_LOG_V6_EPOW_ACTION_POWER_OFF 7
+ uint8_t event_modifier;
+#define RTAS_LOG_V6_EPOW_MODIFIER_NORMAL 1
+#define RTAS_LOG_V6_EPOW_MODIFIER_ON_UPS 2
+#define RTAS_LOG_V6_EPOW_MODIFIER_CRITICAL 3
+#define RTAS_LOG_V6_EPOW_MODIFIER_TEMPERATURE 4
+ uint8_t extended_modifier;
+#define RTAS_LOG_V6_EPOW_XMODIFIER_SYSTEM_WIDE 0
+#define RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC 1
+ uint8_t _resv;
+ uint64_t reason_code;
+} QEMU_PACKED;
+
+struct epow_log_full {
+ struct rtas_error_log hdr;
+ struct rtas_event_log_v6 v6hdr;
+ struct rtas_event_log_v6_maina maina;
+ struct rtas_event_log_v6_mainb mainb;
+ struct rtas_event_log_v6_epow epow;
+} QEMU_PACKED;
+
+#define EVENT_MASK_INTERNAL_ERRORS 0x80000000
+#define EVENT_MASK_EPOW 0x40000000
+#define EVENT_MASK_HOTPLUG 0x10000000
+#define EVENT_MASK_IO 0x08000000
+
+#define _FDT(exp) \
+ do { \
+ int ret = (exp); \
+ if (ret < 0) { \
+ fprintf(stderr, "qemu: error creating device tree: %s: %s\n", \
+ #exp, fdt_strerror(ret)); \
+ exit(1); \
+ } \
+ } while (0)
+
+void spapr_events_fdt_skel(void *fdt, uint32_t epow_irq)
+{
+ uint32_t epow_irq_ranges[] = {cpu_to_be32(epow_irq), cpu_to_be32(1)};
+ uint32_t epow_interrupts[] = {cpu_to_be32(epow_irq), 0};
+
+ _FDT((fdt_begin_node(fdt, "event-sources")));
+
+ _FDT((fdt_property(fdt, "interrupt-controller", NULL, 0)));
+ _FDT((fdt_property_cell(fdt, "#interrupt-cells", 2)));
+ _FDT((fdt_property(fdt, "interrupt-ranges",
+ epow_irq_ranges, sizeof(epow_irq_ranges))));
+
+ _FDT((fdt_begin_node(fdt, "epow-events")));
+ _FDT((fdt_property(fdt, "interrupts",
+ epow_interrupts, sizeof(epow_interrupts))));
+ _FDT((fdt_end_node(fdt)));
+
+ _FDT((fdt_end_node(fdt)));
+}
+
+static struct epow_log_full *pending_epow;
+static uint32_t next_plid;
+
+static void spapr_powerdown_req(Notifier *n, void *opaque)
+{
+ sPAPREnvironment *spapr = container_of(n, sPAPREnvironment, epow_notifier);
+ struct rtas_error_log *hdr;
+ struct rtas_event_log_v6 *v6hdr;
+ struct rtas_event_log_v6_maina *maina;
+ struct rtas_event_log_v6_mainb *mainb;
+ struct rtas_event_log_v6_epow *epow;
+ struct tm tm;
+ int year;
+
+ if (pending_epow) {
+ /* For now, we just throw away earlier events if two come
+ * along before any are consumed. This is sufficient for our
+ * powerdown messages, but we'll need more if we do more
+ * general error/event logging */
+ g_free(pending_epow);
+ }
+ pending_epow = g_malloc0(sizeof(*pending_epow));
+ hdr = &pending_epow->hdr;
+ v6hdr = &pending_epow->v6hdr;
+ maina = &pending_epow->maina;
+ mainb = &pending_epow->mainb;
+ epow = &pending_epow->epow;
+
+ hdr->summary = cpu_to_be32(RTAS_LOG_VERSION_6
+ | RTAS_LOG_SEVERITY_EVENT
+ | RTAS_LOG_DISPOSITION_NOT_RECOVERED
+ | RTAS_LOG_OPTIONAL_PART_PRESENT
+ | RTAS_LOG_TYPE_EPOW);
+ hdr->extended_length = cpu_to_be32(sizeof(*pending_epow)
+ - sizeof(pending_epow->hdr));
+
+ v6hdr->b0 = RTAS_LOG_V6_B0_VALID | RTAS_LOG_V6_B0_NEW_LOG
+ | RTAS_LOG_V6_B0_BIGENDIAN;
+ v6hdr->b2 = RTAS_LOG_V6_B2_POWERPC_FORMAT
+ | RTAS_LOG_V6_B2_LOG_FORMAT_PLATFORM_EVENT;
+ v6hdr->company = cpu_to_be32(RTAS_LOG_V6_COMPANY_IBM);
+
+ maina->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINA);
+ maina->hdr.section_length = cpu_to_be16(sizeof(*maina));
+ /* FIXME: section version, subtype and creator id? */
+ qemu_get_timedate(&tm, spapr->rtc_offset);
+ year = tm.tm_year + 1900;
+ maina->creation_date = cpu_to_be32((to_bcd(year / 100) << 24)
+ | (to_bcd(year % 100) << 16)
+ | (to_bcd(tm.tm_mon + 1) << 8)
+ | to_bcd(tm.tm_mday));
+ maina->creation_time = cpu_to_be32((to_bcd(tm.tm_hour) << 24)
+ | (to_bcd(tm.tm_min) << 16)
+ | (to_bcd(tm.tm_sec) << 8));
+ maina->creator_id = 'H'; /* Hypervisor */
+ maina->section_count = 3; /* Main-A, Main-B and EPOW */
+ maina->plid = next_plid++;
+
+ mainb->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_MAINB);
+ mainb->hdr.section_length = cpu_to_be16(sizeof(*mainb));
+ /* FIXME: section version, subtype and creator id? */
+ mainb->subsystem_id = 0xa0; /* External environment */
+ mainb->event_severity = 0x00; /* Informational / non-error */
+ mainb->event_subtype = 0xd0; /* Normal shutdown */
+
+ epow->hdr.section_id = cpu_to_be16(RTAS_LOG_V6_SECTION_ID_EPOW);
+ epow->hdr.section_length = cpu_to_be16(sizeof(*epow));
+ epow->hdr.section_version = 2; /* includes extended modifier */
+ /* FIXME: section subtype and creator id? */
+ epow->sensor_value = RTAS_LOG_V6_EPOW_ACTION_SYSTEM_SHUTDOWN;
+ epow->event_modifier = RTAS_LOG_V6_EPOW_MODIFIER_NORMAL;
+ epow->extended_modifier = RTAS_LOG_V6_EPOW_XMODIFIER_PARTITION_SPECIFIC;
+
+ qemu_irq_pulse(xics_get_qirq(spapr->icp, spapr->epow_irq));
+}
+
+static void check_exception(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint32_t mask, buf, len;
+ uint64_t xinfo;
+
+ if ((nargs < 6) || (nargs > 7) || nret != 1) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ xinfo = rtas_ld(args, 1);
+ mask = rtas_ld(args, 2);
+ buf = rtas_ld(args, 4);
+ len = rtas_ld(args, 5);
+ if (nargs == 7) {
+ xinfo |= (uint64_t)rtas_ld(args, 6) << 32;
+ }
+
+ if ((mask & EVENT_MASK_EPOW) && pending_epow) {
+ if (sizeof(*pending_epow) < len) {
+ len = sizeof(*pending_epow);
+ }
+
+ cpu_physical_memory_write(buf, pending_epow, len);
+ g_free(pending_epow);
+ pending_epow = NULL;
+ rtas_st(rets, 0, 0);
+ } else {
+ rtas_st(rets, 0, 1);
+ }
+}
+
+void spapr_events_init(sPAPREnvironment *spapr)
+{
+ spapr->epow_irq = spapr_allocate_msi(0);
+ spapr->epow_notifier.notify = spapr_powerdown_req;
+ qemu_register_powerdown_notifier(&spapr->epow_notifier);
+ spapr_rtas_register("check-exception", check_exception);
+}
diff --git a/hw/ppc/spapr_hcall.c b/hw/ppc/spapr_hcall.c
new file mode 100644
index 0000000..7b89594
--- /dev/null
+++ b/hw/ppc/spapr_hcall.c
@@ -0,0 +1,741 @@
+#include "sysemu/sysemu.h"
+#include "cpu.h"
+#include "sysemu/sysemu.h"
+#include "helper_regs.h"
+#include "hw/spapr.h"
+
+#define HPTES_PER_GROUP 8
+
+#define HPTE_V_SSIZE_SHIFT 62
+#define HPTE_V_AVPN_SHIFT 7
+#define HPTE_V_AVPN 0x3fffffffffffff80ULL
+#define HPTE_V_AVPN_VAL(x) (((x) & HPTE_V_AVPN) >> HPTE_V_AVPN_SHIFT)
+#define HPTE_V_COMPARE(x, y) (!(((x) ^ (y)) & 0xffffffffffffff80UL))
+#define HPTE_V_BOLTED 0x0000000000000010ULL
+#define HPTE_V_LOCK 0x0000000000000008ULL
+#define HPTE_V_LARGE 0x0000000000000004ULL
+#define HPTE_V_SECONDARY 0x0000000000000002ULL
+#define HPTE_V_VALID 0x0000000000000001ULL
+
+#define HPTE_R_PP0 0x8000000000000000ULL
+#define HPTE_R_TS 0x4000000000000000ULL
+#define HPTE_R_KEY_HI 0x3000000000000000ULL
+#define HPTE_R_RPN_SHIFT 12
+#define HPTE_R_RPN 0x3ffffffffffff000ULL
+#define HPTE_R_FLAGS 0x00000000000003ffULL
+#define HPTE_R_PP 0x0000000000000003ULL
+#define HPTE_R_N 0x0000000000000004ULL
+#define HPTE_R_G 0x0000000000000008ULL
+#define HPTE_R_M 0x0000000000000010ULL
+#define HPTE_R_I 0x0000000000000020ULL
+#define HPTE_R_W 0x0000000000000040ULL
+#define HPTE_R_WIMG 0x0000000000000078ULL
+#define HPTE_R_C 0x0000000000000080ULL
+#define HPTE_R_R 0x0000000000000100ULL
+#define HPTE_R_KEY_LO 0x0000000000000e00ULL
+
+#define HPTE_V_1TB_SEG 0x4000000000000000ULL
+#define HPTE_V_VRMA_MASK 0x4001ffffff000000ULL
+
+static target_ulong compute_tlbie_rb(target_ulong v, target_ulong r,
+ target_ulong pte_index)
+{
+ target_ulong rb, va_low;
+
+ rb = (v & ~0x7fULL) << 16; /* AVA field */
+ va_low = pte_index >> 3;
+ if (v & HPTE_V_SECONDARY) {
+ va_low = ~va_low;
+ }
+ /* xor vsid from AVA */
+ if (!(v & HPTE_V_1TB_SEG)) {
+ va_low ^= v >> 12;
+ } else {
+ va_low ^= v >> 24;
+ }
+ va_low &= 0x7ff;
+ if (v & HPTE_V_LARGE) {
+ rb |= 1; /* L field */
+#if 0 /* Disable that P7 specific bit for now */
+ if (r & 0xff000) {
+ /* non-16MB large page, must be 64k */
+ /* (masks depend on page size) */
+ rb |= 0x1000; /* page encoding in LP field */
+ rb |= (va_low & 0x7f) << 16; /* 7b of VA in AVA/LP field */
+ rb |= (va_low & 0xfe); /* AVAL field */
+ }
+#endif
+ } else {
+ /* 4kB page */
+ rb |= (va_low & 0x7ff) << 12; /* remaining 11b of AVA */
+ }
+ rb |= (v >> 54) & 0x300; /* B field */
+ return rb;
+}
+
+static target_ulong h_enter(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong pteh = args[2];
+ target_ulong ptel = args[3];
+ target_ulong page_shift = 12;
+ target_ulong raddr;
+ target_ulong i;
+ uint8_t *hpte;
+
+ /* only handle 4k and 16M pages for now */
+ if (pteh & HPTE_V_LARGE) {
+#if 0 /* We don't support 64k pages yet */
+ if ((ptel & 0xf000) == 0x1000) {
+ /* 64k page */
+ } else
+#endif
+ if ((ptel & 0xff000) == 0) {
+ /* 16M page */
+ page_shift = 24;
+ /* lowest AVA bit must be 0 for 16M pages */
+ if (pteh & 0x80) {
+ return H_PARAMETER;
+ }
+ } else {
+ return H_PARAMETER;
+ }
+ }
+
+ raddr = (ptel & HPTE_R_RPN) & ~((1ULL << page_shift) - 1);
+
+ if (raddr < spapr->ram_limit) {
+ /* Regular RAM - should have WIMG=0010 */
+ if ((ptel & HPTE_R_WIMG) != HPTE_R_M) {
+ return H_PARAMETER;
+ }
+ } else {
+ /* Looks like an IO address */
+ /* FIXME: What WIMG combinations could be sensible for IO?
+ * For now we allow WIMG=010x, but are there others? */
+ /* FIXME: Should we check against registered IO addresses? */
+ if ((ptel & (HPTE_R_W | HPTE_R_I | HPTE_R_M)) != HPTE_R_I) {
+ return H_PARAMETER;
+ }
+ }
+
+ pteh &= ~0x60ULL;
+
+ if ((pte_index * HASH_PTE_SIZE_64) & ~env->htab_mask) {
+ return H_PARAMETER;
+ }
+ if (likely((flags & H_EXACT) == 0)) {
+ pte_index &= ~7ULL;
+ hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64);
+ for (i = 0; ; ++i) {
+ if (i == 8) {
+ return H_PTEG_FULL;
+ }
+ if ((ldq_p(hpte) & HPTE_V_VALID) == 0) {
+ break;
+ }
+ hpte += HASH_PTE_SIZE_64;
+ }
+ } else {
+ i = 0;
+ hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64);
+ if (ldq_p(hpte) & HPTE_V_VALID) {
+ return H_PTEG_FULL;
+ }
+ }
+ stq_p(hpte + (HASH_PTE_SIZE_64/2), ptel);
+ /* eieio(); FIXME: need some sort of barrier for smp? */
+ stq_p(hpte, pteh);
+
+ args[0] = pte_index + i;
+ return H_SUCCESS;
+}
+
+enum {
+ REMOVE_SUCCESS = 0,
+ REMOVE_NOT_FOUND = 1,
+ REMOVE_PARM = 2,
+ REMOVE_HW = 3,
+};
+
+static target_ulong remove_hpte(CPUPPCState *env, target_ulong ptex,
+ target_ulong avpn,
+ target_ulong flags,
+ target_ulong *vp, target_ulong *rp)
+{
+ uint8_t *hpte;
+ target_ulong v, r, rb;
+
+ if ((ptex * HASH_PTE_SIZE_64) & ~env->htab_mask) {
+ return REMOVE_PARM;
+ }
+
+ hpte = env->external_htab + (ptex * HASH_PTE_SIZE_64);
+
+ v = ldq_p(hpte);
+ r = ldq_p(hpte + (HASH_PTE_SIZE_64/2));
+
+ if ((v & HPTE_V_VALID) == 0 ||
+ ((flags & H_AVPN) && (v & ~0x7fULL) != avpn) ||
+ ((flags & H_ANDCOND) && (v & avpn) != 0)) {
+ return REMOVE_NOT_FOUND;
+ }
+ *vp = v;
+ *rp = r;
+ stq_p(hpte, 0);
+ rb = compute_tlbie_rb(v, r, ptex);
+ ppc_tlb_invalidate_one(env, rb);
+ return REMOVE_SUCCESS;
+}
+
+static target_ulong h_remove(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong avpn = args[2];
+ int ret;
+
+ ret = remove_hpte(env, pte_index, avpn, flags,
+ &args[0], &args[1]);
+
+ switch (ret) {
+ case REMOVE_SUCCESS:
+ return H_SUCCESS;
+
+ case REMOVE_NOT_FOUND:
+ return H_NOT_FOUND;
+
+ case REMOVE_PARM:
+ return H_PARAMETER;
+
+ case REMOVE_HW:
+ return H_HARDWARE;
+ }
+
+ assert(0);
+}
+
+#define H_BULK_REMOVE_TYPE 0xc000000000000000ULL
+#define H_BULK_REMOVE_REQUEST 0x4000000000000000ULL
+#define H_BULK_REMOVE_RESPONSE 0x8000000000000000ULL
+#define H_BULK_REMOVE_END 0xc000000000000000ULL
+#define H_BULK_REMOVE_CODE 0x3000000000000000ULL
+#define H_BULK_REMOVE_SUCCESS 0x0000000000000000ULL
+#define H_BULK_REMOVE_NOT_FOUND 0x1000000000000000ULL
+#define H_BULK_REMOVE_PARM 0x2000000000000000ULL
+#define H_BULK_REMOVE_HW 0x3000000000000000ULL
+#define H_BULK_REMOVE_RC 0x0c00000000000000ULL
+#define H_BULK_REMOVE_FLAGS 0x0300000000000000ULL
+#define H_BULK_REMOVE_ABSOLUTE 0x0000000000000000ULL
+#define H_BULK_REMOVE_ANDCOND 0x0100000000000000ULL
+#define H_BULK_REMOVE_AVPN 0x0200000000000000ULL
+#define H_BULK_REMOVE_PTEX 0x00ffffffffffffffULL
+
+#define H_BULK_REMOVE_MAX_BATCH 4
+
+static target_ulong h_bulk_remove(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ int i;
+
+ for (i = 0; i < H_BULK_REMOVE_MAX_BATCH; i++) {
+ target_ulong *tsh = &args[i*2];
+ target_ulong tsl = args[i*2 + 1];
+ target_ulong v, r, ret;
+
+ if ((*tsh & H_BULK_REMOVE_TYPE) == H_BULK_REMOVE_END) {
+ break;
+ } else if ((*tsh & H_BULK_REMOVE_TYPE) != H_BULK_REMOVE_REQUEST) {
+ return H_PARAMETER;
+ }
+
+ *tsh &= H_BULK_REMOVE_PTEX | H_BULK_REMOVE_FLAGS;
+ *tsh |= H_BULK_REMOVE_RESPONSE;
+
+ if ((*tsh & H_BULK_REMOVE_ANDCOND) && (*tsh & H_BULK_REMOVE_AVPN)) {
+ *tsh |= H_BULK_REMOVE_PARM;
+ return H_PARAMETER;
+ }
+
+ ret = remove_hpte(env, *tsh & H_BULK_REMOVE_PTEX, tsl,
+ (*tsh & H_BULK_REMOVE_FLAGS) >> 26,
+ &v, &r);
+
+ *tsh |= ret << 60;
+
+ switch (ret) {
+ case REMOVE_SUCCESS:
+ *tsh |= (r & (HPTE_R_C | HPTE_R_R)) << 43;
+ break;
+
+ case REMOVE_PARM:
+ return H_PARAMETER;
+
+ case REMOVE_HW:
+ return H_HARDWARE;
+ }
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_protect(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ target_ulong flags = args[0];
+ target_ulong pte_index = args[1];
+ target_ulong avpn = args[2];
+ uint8_t *hpte;
+ target_ulong v, r, rb;
+
+ if ((pte_index * HASH_PTE_SIZE_64) & ~env->htab_mask) {
+ return H_PARAMETER;
+ }
+
+ hpte = env->external_htab + (pte_index * HASH_PTE_SIZE_64);
+
+ v = ldq_p(hpte);
+ r = ldq_p(hpte + (HASH_PTE_SIZE_64/2));
+
+ if ((v & HPTE_V_VALID) == 0 ||
+ ((flags & H_AVPN) && (v & ~0x7fULL) != avpn)) {
+ return H_NOT_FOUND;
+ }
+
+ r &= ~(HPTE_R_PP0 | HPTE_R_PP | HPTE_R_N |
+ HPTE_R_KEY_HI | HPTE_R_KEY_LO);
+ r |= (flags << 55) & HPTE_R_PP0;
+ r |= (flags << 48) & HPTE_R_KEY_HI;
+ r |= flags & (HPTE_R_PP | HPTE_R_N | HPTE_R_KEY_LO);
+ rb = compute_tlbie_rb(v, r, pte_index);
+ stq_p(hpte, v & ~HPTE_V_VALID);
+ ppc_tlb_invalidate_one(env, rb);
+ stq_p(hpte + (HASH_PTE_SIZE_64/2), r);
+ /* Don't need a memory barrier, due to qemu's global lock */
+ stq_p(hpte, v);
+ return H_SUCCESS;
+}
+
+static target_ulong h_set_dabr(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* FIXME: actually implement this */
+ return H_HARDWARE;
+}
+
+#define FLAGS_REGISTER_VPA 0x0000200000000000ULL
+#define FLAGS_REGISTER_DTL 0x0000400000000000ULL
+#define FLAGS_REGISTER_SLBSHADOW 0x0000600000000000ULL
+#define FLAGS_DEREGISTER_VPA 0x0000a00000000000ULL
+#define FLAGS_DEREGISTER_DTL 0x0000c00000000000ULL
+#define FLAGS_DEREGISTER_SLBSHADOW 0x0000e00000000000ULL
+
+#define VPA_MIN_SIZE 640
+#define VPA_SIZE_OFFSET 0x4
+#define VPA_SHARED_PROC_OFFSET 0x9
+#define VPA_SHARED_PROC_VAL 0x2
+
+static target_ulong register_vpa(CPUPPCState *env, target_ulong vpa)
+{
+ uint16_t size;
+ uint8_t tmp;
+
+ if (vpa == 0) {
+ hcall_dprintf("Can't cope with registering a VPA at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ if (vpa % env->dcache_line_size) {
+ return H_PARAMETER;
+ }
+ /* FIXME: bounds check the address */
+
+ size = lduw_be_phys(vpa + 0x4);
+
+ if (size < VPA_MIN_SIZE) {
+ return H_PARAMETER;
+ }
+
+ /* VPA is not allowed to cross a page boundary */
+ if ((vpa / 4096) != ((vpa + size - 1) / 4096)) {
+ return H_PARAMETER;
+ }
+
+ env->vpa_addr = vpa;
+
+ tmp = ldub_phys(env->vpa_addr + VPA_SHARED_PROC_OFFSET);
+ tmp |= VPA_SHARED_PROC_VAL;
+ stb_phys(env->vpa_addr + VPA_SHARED_PROC_OFFSET, tmp);
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_vpa(CPUPPCState *env, target_ulong vpa)
+{
+ if (env->slb_shadow_addr) {
+ return H_RESOURCE;
+ }
+
+ if (env->dtl_addr) {
+ return H_RESOURCE;
+ }
+
+ env->vpa_addr = 0;
+ return H_SUCCESS;
+}
+
+static target_ulong register_slb_shadow(CPUPPCState *env, target_ulong addr)
+{
+ uint32_t size;
+
+ if (addr == 0) {
+ hcall_dprintf("Can't cope with SLB shadow at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ size = ldl_be_phys(addr + 0x4);
+ if (size < 0x8) {
+ return H_PARAMETER;
+ }
+
+ if ((addr / 4096) != ((addr + size - 1) / 4096)) {
+ return H_PARAMETER;
+ }
+
+ if (!env->vpa_addr) {
+ return H_RESOURCE;
+ }
+
+ env->slb_shadow_addr = addr;
+ env->slb_shadow_size = size;
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_slb_shadow(CPUPPCState *env, target_ulong addr)
+{
+ env->slb_shadow_addr = 0;
+ env->slb_shadow_size = 0;
+ return H_SUCCESS;
+}
+
+static target_ulong register_dtl(CPUPPCState *env, target_ulong addr)
+{
+ uint32_t size;
+
+ if (addr == 0) {
+ hcall_dprintf("Can't cope with DTL at logical 0\n");
+ return H_HARDWARE;
+ }
+
+ size = ldl_be_phys(addr + 0x4);
+
+ if (size < 48) {
+ return H_PARAMETER;
+ }
+
+ if (!env->vpa_addr) {
+ return H_RESOURCE;
+ }
+
+ env->dtl_addr = addr;
+ env->dtl_size = size;
+
+ return H_SUCCESS;
+}
+
+static target_ulong deregister_dtl(CPUPPCState *env, target_ulong addr)
+{
+ env->dtl_addr = 0;
+ env->dtl_size = 0;
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_register_vpa(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong flags = args[0];
+ target_ulong procno = args[1];
+ target_ulong vpa = args[2];
+ target_ulong ret = H_PARAMETER;
+ CPUPPCState *tenv;
+ CPUState *tcpu;
+
+ tcpu = qemu_get_cpu(procno);
+ if (!tcpu) {
+ return H_PARAMETER;
+ }
+ tenv = tcpu->env_ptr;
+
+ switch (flags) {
+ case FLAGS_REGISTER_VPA:
+ ret = register_vpa(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_VPA:
+ ret = deregister_vpa(tenv, vpa);
+ break;
+
+ case FLAGS_REGISTER_SLBSHADOW:
+ ret = register_slb_shadow(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_SLBSHADOW:
+ ret = deregister_slb_shadow(tenv, vpa);
+ break;
+
+ case FLAGS_REGISTER_DTL:
+ ret = register_dtl(tenv, vpa);
+ break;
+
+ case FLAGS_DEREGISTER_DTL:
+ ret = deregister_dtl(tenv, vpa);
+ break;
+ }
+
+ return ret;
+}
+
+static target_ulong h_cede(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUPPCState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ env->msr |= (1ULL << MSR_EE);
+ hreg_compute_hflags(env);
+ if (!cpu_has_work(cs)) {
+ env->halted = 1;
+ env->exception_index = EXCP_HLT;
+ cs->exit_request = 1;
+ }
+ return H_SUCCESS;
+}
+
+static target_ulong h_rtas(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong rtas_r3 = args[0];
+ uint32_t token = ldl_be_phys(rtas_r3);
+ uint32_t nargs = ldl_be_phys(rtas_r3 + 4);
+ uint32_t nret = ldl_be_phys(rtas_r3 + 8);
+
+ return spapr_rtas_call(spapr, token, nargs, rtas_r3 + 12,
+ nret, rtas_r3 + 12 + 4*nargs);
+}
+
+static target_ulong h_logical_load(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong size = args[0];
+ target_ulong addr = args[1];
+
+ switch (size) {
+ case 1:
+ args[0] = ldub_phys(addr);
+ return H_SUCCESS;
+ case 2:
+ args[0] = lduw_phys(addr);
+ return H_SUCCESS;
+ case 4:
+ args[0] = ldl_phys(addr);
+ return H_SUCCESS;
+ case 8:
+ args[0] = ldq_phys(addr);
+ return H_SUCCESS;
+ }
+ return H_PARAMETER;
+}
+
+static target_ulong h_logical_store(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong size = args[0];
+ target_ulong addr = args[1];
+ target_ulong val = args[2];
+
+ switch (size) {
+ case 1:
+ stb_phys(addr, val);
+ return H_SUCCESS;
+ case 2:
+ stw_phys(addr, val);
+ return H_SUCCESS;
+ case 4:
+ stl_phys(addr, val);
+ return H_SUCCESS;
+ case 8:
+ stq_phys(addr, val);
+ return H_SUCCESS;
+ }
+ return H_PARAMETER;
+}
+
+static target_ulong h_logical_memop(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong dst = args[0]; /* Destination address */
+ target_ulong src = args[1]; /* Source address */
+ target_ulong esize = args[2]; /* Element size (0=1,1=2,2=4,3=8) */
+ target_ulong count = args[3]; /* Element count */
+ target_ulong op = args[4]; /* 0 = copy, 1 = invert */
+ uint64_t tmp;
+ unsigned int mask = (1 << esize) - 1;
+ int step = 1 << esize;
+
+ if (count > 0x80000000) {
+ return H_PARAMETER;
+ }
+
+ if ((dst & mask) || (src & mask) || (op > 1)) {
+ return H_PARAMETER;
+ }
+
+ if (dst >= src && dst < (src + (count << esize))) {
+ dst = dst + ((count - 1) << esize);
+ src = src + ((count - 1) << esize);
+ step = -step;
+ }
+
+ while (count--) {
+ switch (esize) {
+ case 0:
+ tmp = ldub_phys(src);
+ break;
+ case 1:
+ tmp = lduw_phys(src);
+ break;
+ case 2:
+ tmp = ldl_phys(src);
+ break;
+ case 3:
+ tmp = ldq_phys(src);
+ break;
+ default:
+ return H_PARAMETER;
+ }
+ if (op == 1) {
+ tmp = ~tmp;
+ }
+ switch (esize) {
+ case 0:
+ stb_phys(dst, tmp);
+ break;
+ case 1:
+ stw_phys(dst, tmp);
+ break;
+ case 2:
+ stl_phys(dst, tmp);
+ break;
+ case 3:
+ stq_phys(dst, tmp);
+ break;
+ }
+ dst = dst + step;
+ src = src + step;
+ }
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_logical_icbi(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* Nothing to do on emulation, KVM will trap this in the kernel */
+ return H_SUCCESS;
+}
+
+static target_ulong h_logical_dcbf(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ /* Nothing to do on emulation, KVM will trap this in the kernel */
+ return H_SUCCESS;
+}
+
+static spapr_hcall_fn papr_hypercall_table[(MAX_HCALL_OPCODE / 4) + 1];
+static spapr_hcall_fn kvmppc_hypercall_table[KVMPPC_HCALL_MAX - KVMPPC_HCALL_BASE + 1];
+
+void spapr_register_hypercall(target_ulong opcode, spapr_hcall_fn fn)
+{
+ spapr_hcall_fn *slot;
+
+ if (opcode <= MAX_HCALL_OPCODE) {
+ assert((opcode & 0x3) == 0);
+
+ slot = &papr_hypercall_table[opcode / 4];
+ } else {
+ assert((opcode >= KVMPPC_HCALL_BASE) && (opcode <= KVMPPC_HCALL_MAX));
+
+ slot = &kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE];
+ }
+
+ assert(!(*slot));
+ *slot = fn;
+}
+
+target_ulong spapr_hypercall(PowerPCCPU *cpu, target_ulong opcode,
+ target_ulong *args)
+{
+ if ((opcode <= MAX_HCALL_OPCODE)
+ && ((opcode & 0x3) == 0)) {
+ spapr_hcall_fn fn = papr_hypercall_table[opcode / 4];
+
+ if (fn) {
+ return fn(cpu, spapr, opcode, args);
+ }
+ } else if ((opcode >= KVMPPC_HCALL_BASE) &&
+ (opcode <= KVMPPC_HCALL_MAX)) {
+ spapr_hcall_fn fn = kvmppc_hypercall_table[opcode - KVMPPC_HCALL_BASE];
+
+ if (fn) {
+ return fn(cpu, spapr, opcode, args);
+ }
+ }
+
+ hcall_dprintf("Unimplemented hcall 0x" TARGET_FMT_lx "\n", opcode);
+ return H_FUNCTION;
+}
+
+static void hypercall_register_types(void)
+{
+ /* hcall-pft */
+ spapr_register_hypercall(H_ENTER, h_enter);
+ spapr_register_hypercall(H_REMOVE, h_remove);
+ spapr_register_hypercall(H_PROTECT, h_protect);
+
+ /* hcall-bulk */
+ spapr_register_hypercall(H_BULK_REMOVE, h_bulk_remove);
+
+ /* hcall-dabr */
+ spapr_register_hypercall(H_SET_DABR, h_set_dabr);
+
+ /* hcall-splpar */
+ spapr_register_hypercall(H_REGISTER_VPA, h_register_vpa);
+ spapr_register_hypercall(H_CEDE, h_cede);
+
+ /* "debugger" hcalls (also used by SLOF). Note: We do -not- differenciate
+ * here between the "CI" and the "CACHE" variants, they will use whatever
+ * mapping attributes qemu is using. When using KVM, the kernel will
+ * enforce the attributes more strongly
+ */
+ spapr_register_hypercall(H_LOGICAL_CI_LOAD, h_logical_load);
+ spapr_register_hypercall(H_LOGICAL_CI_STORE, h_logical_store);
+ spapr_register_hypercall(H_LOGICAL_CACHE_LOAD, h_logical_load);
+ spapr_register_hypercall(H_LOGICAL_CACHE_STORE, h_logical_store);
+ spapr_register_hypercall(H_LOGICAL_ICBI, h_logical_icbi);
+ spapr_register_hypercall(H_LOGICAL_DCBF, h_logical_dcbf);
+ spapr_register_hypercall(KVMPPC_H_LOGICAL_MEMOP, h_logical_memop);
+
+ /* qemu/KVM-PPC specific hcalls */
+ spapr_register_hypercall(KVMPPC_H_RTAS, h_rtas);
+}
+
+type_init(hypercall_register_types)
diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c
new file mode 100644
index 0000000..8d500bf
--- /dev/null
+++ b/hw/ppc/spapr_iommu.c
@@ -0,0 +1,293 @@
+/*
+ * QEMU sPAPR IOMMU (TCE) code
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "sysemu/kvm.h"
+#include "hw/qdev.h"
+#include "kvm_ppc.h"
+#include "sysemu/dma.h"
+#include "exec/address-spaces.h"
+
+#include "hw/spapr.h"
+
+#include <libfdt.h>
+
+/* #define DEBUG_TCE */
+
+enum sPAPRTCEAccess {
+ SPAPR_TCE_FAULT = 0,
+ SPAPR_TCE_RO = 1,
+ SPAPR_TCE_WO = 2,
+ SPAPR_TCE_RW = 3,
+};
+
+typedef struct sPAPRTCETable sPAPRTCETable;
+
+struct sPAPRTCETable {
+ DMAContext dma;
+ uint32_t liobn;
+ uint32_t window_size;
+ sPAPRTCE *table;
+ bool bypass;
+ int fd;
+ QLIST_ENTRY(sPAPRTCETable) list;
+};
+
+
+QLIST_HEAD(spapr_tce_tables, sPAPRTCETable) spapr_tce_tables;
+
+static sPAPRTCETable *spapr_tce_find_by_liobn(uint32_t liobn)
+{
+ sPAPRTCETable *tcet;
+
+ QLIST_FOREACH(tcet, &spapr_tce_tables, list) {
+ if (tcet->liobn == liobn) {
+ return tcet;
+ }
+ }
+
+ return NULL;
+}
+
+static int spapr_tce_translate(DMAContext *dma,
+ dma_addr_t addr,
+ hwaddr *paddr,
+ hwaddr *len,
+ DMADirection dir)
+{
+ sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
+ enum sPAPRTCEAccess access = (dir == DMA_DIRECTION_FROM_DEVICE)
+ ? SPAPR_TCE_WO : SPAPR_TCE_RO;
+ uint64_t tce;
+
+#ifdef DEBUG_TCE
+ fprintf(stderr, "spapr_tce_translate liobn=0x%" PRIx32 " addr=0x"
+ DMA_ADDR_FMT "\n", tcet->liobn, addr);
+#endif
+
+ if (tcet->bypass) {
+ *paddr = addr;
+ *len = (hwaddr)-1;
+ return 0;
+ }
+
+ /* Check if we are in bound */
+ if (addr >= tcet->window_size) {
+#ifdef DEBUG_TCE
+ fprintf(stderr, "spapr_tce_translate out of bounds\n");
+#endif
+ return -EFAULT;
+ }
+
+ tce = tcet->table[addr >> SPAPR_TCE_PAGE_SHIFT].tce;
+
+ /* Check TCE */
+ if (!(tce & access)) {
+ return -EPERM;
+ }
+
+ /* How much til end of page ? */
+ *len = ((~addr) & SPAPR_TCE_PAGE_MASK) + 1;
+
+ /* Translate */
+ *paddr = (tce & ~SPAPR_TCE_PAGE_MASK) |
+ (addr & SPAPR_TCE_PAGE_MASK);
+
+#ifdef DEBUG_TCE
+ fprintf(stderr, " -> *paddr=0x" TARGET_FMT_plx ", *len=0x"
+ TARGET_FMT_plx "\n", *paddr, *len);
+#endif
+
+ return 0;
+}
+
+DMAContext *spapr_tce_new_dma_context(uint32_t liobn, size_t window_size)
+{
+ sPAPRTCETable *tcet;
+
+ if (spapr_tce_find_by_liobn(liobn)) {
+ fprintf(stderr, "Attempted to create TCE table with duplicate"
+ " LIOBN 0x%x\n", liobn);
+ return NULL;
+ }
+
+ if (!window_size) {
+ return NULL;
+ }
+
+ tcet = g_malloc0(sizeof(*tcet));
+ dma_context_init(&tcet->dma, &address_space_memory, spapr_tce_translate, NULL, NULL);
+
+ tcet->liobn = liobn;
+ tcet->window_size = window_size;
+
+ if (kvm_enabled()) {
+ tcet->table = kvmppc_create_spapr_tce(liobn,
+ window_size,
+ &tcet->fd);
+ }
+
+ if (!tcet->table) {
+ size_t table_size = (window_size >> SPAPR_TCE_PAGE_SHIFT)
+ * sizeof(sPAPRTCE);
+ tcet->table = g_malloc0(table_size);
+ }
+
+#ifdef DEBUG_TCE
+ fprintf(stderr, "spapr_iommu: New TCE table, liobn=0x%x, context @ %p, "
+ "table @ %p, fd=%d\n", liobn, &tcet->dma, tcet->table, tcet->fd);
+#endif
+
+ QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
+
+ return &tcet->dma;
+}
+
+void spapr_tce_free(DMAContext *dma)
+{
+
+ if (dma) {
+ sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
+
+ QLIST_REMOVE(tcet, list);
+
+ if (!kvm_enabled() ||
+ (kvmppc_remove_spapr_tce(tcet->table, tcet->fd,
+ tcet->window_size) != 0)) {
+ g_free(tcet->table);
+ }
+
+ g_free(tcet);
+ }
+}
+
+void spapr_tce_set_bypass(DMAContext *dma, bool bypass)
+{
+ sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
+
+ tcet->bypass = bypass;
+}
+
+void spapr_tce_reset(DMAContext *dma)
+{
+ sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, dma);
+ size_t table_size = (tcet->window_size >> SPAPR_TCE_PAGE_SHIFT)
+ * sizeof(sPAPRTCE);
+
+ tcet->bypass = false;
+ memset(tcet->table, 0, table_size);
+}
+
+static target_ulong put_tce_emu(sPAPRTCETable *tcet, target_ulong ioba,
+ target_ulong tce)
+{
+ sPAPRTCE *tcep;
+
+ if (ioba >= tcet->window_size) {
+ hcall_dprintf("spapr_vio_put_tce on out-of-boards IOBA 0x"
+ TARGET_FMT_lx "\n", ioba);
+ return H_PARAMETER;
+ }
+
+ tcep = tcet->table + (ioba >> SPAPR_TCE_PAGE_SHIFT);
+ tcep->tce = tce;
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_put_tce(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong liobn = args[0];
+ target_ulong ioba = args[1];
+ target_ulong tce = args[2];
+ sPAPRTCETable *tcet = spapr_tce_find_by_liobn(liobn);
+
+ if (liobn & 0xFFFFFFFF00000000ULL) {
+ hcall_dprintf("spapr_vio_put_tce on out-of-boundsw LIOBN "
+ TARGET_FMT_lx "\n", liobn);
+ return H_PARAMETER;
+ }
+
+ ioba &= ~(SPAPR_TCE_PAGE_SIZE - 1);
+
+ if (tcet) {
+ return put_tce_emu(tcet, ioba, tce);
+ }
+#ifdef DEBUG_TCE
+ fprintf(stderr, "%s on liobn=" TARGET_FMT_lx /*%s*/
+ " ioba 0x" TARGET_FMT_lx " TCE 0x" TARGET_FMT_lx "\n",
+ __func__, liobn, /*dev->qdev.id, */ioba, tce);
+#endif
+
+ return H_PARAMETER;
+}
+
+void spapr_iommu_init(void)
+{
+ QLIST_INIT(&spapr_tce_tables);
+
+ /* hcall-tce */
+ spapr_register_hypercall(H_PUT_TCE, h_put_tce);
+}
+
+int spapr_dma_dt(void *fdt, int node_off, const char *propname,
+ uint32_t liobn, uint64_t window, uint32_t size)
+{
+ uint32_t dma_prop[5];
+ int ret;
+
+ dma_prop[0] = cpu_to_be32(liobn);
+ dma_prop[1] = cpu_to_be32(window >> 32);
+ dma_prop[2] = cpu_to_be32(window & 0xFFFFFFFF);
+ dma_prop[3] = 0; /* window size is 32 bits */
+ dma_prop[4] = cpu_to_be32(size);
+
+ ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-address-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "ibm,#dma-size-cells", 2);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = fdt_setprop(fdt, node_off, propname, dma_prop, sizeof(dma_prop));
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+int spapr_tcet_dma_dt(void *fdt, int node_off, const char *propname,
+ DMAContext *iommu)
+{
+ if (!iommu) {
+ return 0;
+ }
+
+ if (iommu->translate == spapr_tce_translate) {
+ sPAPRTCETable *tcet = DO_UPCAST(sPAPRTCETable, dma, iommu);
+ return spapr_dma_dt(fdt, node_off, propname,
+ tcet->liobn, 0, tcet->window_size);
+ }
+
+ return -1;
+}
diff --git a/hw/ppc/spapr_rtas.c b/hw/ppc/spapr_rtas.c
new file mode 100644
index 0000000..5ec787f
--- /dev/null
+++ b/hw/ppc/spapr_rtas.c
@@ -0,0 +1,334 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * Hypercall based emulated RTAS
+ *
+ * Copyright (c) 2010-2011 David Gibson, IBM Corporation.
+ *
+ * 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 "cpu.h"
+#include "sysemu/sysemu.h"
+#include "char/char.h"
+#include "hw/qdev.h"
+#include "sysemu/device_tree.h"
+
+#include "hw/spapr.h"
+#include "hw/spapr_vio.h"
+
+#include <libfdt.h>
+
+#define TOKEN_BASE 0x2000
+#define TOKEN_MAX 0x100
+
+static void rtas_display_character(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ uint8_t c = rtas_ld(args, 0);
+ VIOsPAPRDevice *sdev = vty_lookup(spapr, 0);
+
+ if (!sdev) {
+ rtas_st(rets, 0, -1);
+ } else {
+ vty_putchars(sdev, &c, sizeof(c));
+ rtas_st(rets, 0, 0);
+ }
+}
+
+static void rtas_get_time_of_day(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct tm tm;
+
+ if (nret != 8) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ qemu_get_timedate(&tm, spapr->rtc_offset);
+
+ rtas_st(rets, 0, 0); /* Success */
+ rtas_st(rets, 1, tm.tm_year + 1900);
+ rtas_st(rets, 2, tm.tm_mon + 1);
+ rtas_st(rets, 3, tm.tm_mday);
+ rtas_st(rets, 4, tm.tm_hour);
+ rtas_st(rets, 5, tm.tm_min);
+ rtas_st(rets, 6, tm.tm_sec);
+ rtas_st(rets, 7, 0); /* we don't do nanoseconds */
+}
+
+static void rtas_set_time_of_day(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct tm tm;
+
+ tm.tm_year = rtas_ld(args, 0) - 1900;
+ tm.tm_mon = rtas_ld(args, 1) - 1;
+ tm.tm_mday = rtas_ld(args, 2);
+ tm.tm_hour = rtas_ld(args, 3);
+ tm.tm_min = rtas_ld(args, 4);
+ tm.tm_sec = rtas_ld(args, 5);
+
+ /* Just generate a monitor event for the change */
+ rtc_change_mon_event(&tm);
+ spapr->rtc_offset = qemu_timedate_diff(&tm);
+
+ rtas_st(rets, 0, 0); /* Success */
+}
+
+static void rtas_power_off(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if (nargs != 2 || nret != 1) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+ qemu_system_shutdown_request();
+ rtas_st(rets, 0, 0);
+}
+
+static void rtas_system_reboot(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if (nargs != 0 || nret != 1) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+ qemu_system_reset_request();
+ rtas_st(rets, 0, 0);
+}
+
+static void rtas_query_cpu_stopped_state(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong id;
+ CPUPPCState *env;
+ CPUState *cpu;
+
+ if (nargs != 1 || nret != 2) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ id = rtas_ld(args, 0);
+ for (env = first_cpu; env; env = env->next_cpu) {
+ cpu = CPU(ppc_env_get_cpu(env));
+ if (cpu->cpu_index != id) {
+ continue;
+ }
+
+ if (env->halted) {
+ rtas_st(rets, 1, 0);
+ } else {
+ rtas_st(rets, 1, 2);
+ }
+
+ rtas_st(rets, 0, 0);
+ return;
+ }
+
+ /* Didn't find a matching cpu */
+ rtas_st(rets, 0, -3);
+}
+
+static void rtas_start_cpu(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs,
+ target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ target_ulong id, start, r3;
+ CPUState *cpu;
+ CPUPPCState *env;
+
+ if (nargs != 3 || nret != 1) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ id = rtas_ld(args, 0);
+ start = rtas_ld(args, 1);
+ r3 = rtas_ld(args, 2);
+
+ for (env = first_cpu; env; env = env->next_cpu) {
+ cpu = CPU(ppc_env_get_cpu(env));
+
+ if (cpu->cpu_index != id) {
+ continue;
+ }
+
+ if (!env->halted) {
+ rtas_st(rets, 0, -1);
+ return;
+ }
+
+ /* This will make sure qemu state is up to date with kvm, and
+ * mark it dirty so our changes get flushed back before the
+ * new cpu enters */
+ kvm_cpu_synchronize_state(env);
+
+ env->msr = (1ULL << MSR_SF) | (1ULL << MSR_ME);
+ env->nip = start;
+ env->gpr[3] = r3;
+ env->halted = 0;
+
+ qemu_cpu_kick(cpu);
+
+ rtas_st(rets, 0, 0);
+ return;
+ }
+
+ /* Didn't find a matching cpu */
+ rtas_st(rets, 0, -3);
+}
+
+static struct rtas_call {
+ const char *name;
+ spapr_rtas_fn fn;
+} rtas_table[TOKEN_MAX];
+
+struct rtas_call *rtas_next = rtas_table;
+
+target_ulong spapr_rtas_call(sPAPREnvironment *spapr,
+ uint32_t token, uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ if ((token >= TOKEN_BASE)
+ && ((token - TOKEN_BASE) < TOKEN_MAX)) {
+ struct rtas_call *call = rtas_table + (token - TOKEN_BASE);
+
+ if (call->fn) {
+ call->fn(spapr, token, nargs, args, nret, rets);
+ return H_SUCCESS;
+ }
+ }
+
+ /* HACK: Some Linux early debug code uses RTAS display-character,
+ * but assumes the token value is 0xa (which it is on some real
+ * machines) without looking it up in the device tree. This
+ * special case makes this work */
+ if (token == 0xa) {
+ rtas_display_character(spapr, 0xa, nargs, args, nret, rets);
+ return H_SUCCESS;
+ }
+
+ hcall_dprintf("Unknown RTAS token 0x%x\n", token);
+ rtas_st(rets, 0, -3);
+ return H_PARAMETER;
+}
+
+int spapr_rtas_register(const char *name, spapr_rtas_fn fn)
+{
+ int i;
+
+ for (i = 0; i < (rtas_next - rtas_table); i++) {
+ if (strcmp(name, rtas_table[i].name) == 0) {
+ fprintf(stderr, "RTAS call \"%s\" registered twice\n", name);
+ exit(1);
+ }
+ }
+
+ assert(rtas_next < (rtas_table + TOKEN_MAX));
+
+ rtas_next->name = name;
+ rtas_next->fn = fn;
+
+ return (rtas_next++ - rtas_table) + TOKEN_BASE;
+}
+
+int spapr_rtas_device_tree_setup(void *fdt, hwaddr rtas_addr,
+ hwaddr rtas_size)
+{
+ int ret;
+ int i;
+
+ ret = fdt_add_mem_rsv(fdt, rtas_addr, rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add RTAS reserve entry: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-base",
+ rtas_addr);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add linux,rtas-base property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_devtree_setprop_cell(fdt, "/rtas", "linux,rtas-entry",
+ rtas_addr);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add linux,rtas-entry property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ ret = qemu_devtree_setprop_cell(fdt, "/rtas", "rtas-size",
+ rtas_size);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add rtas-size property: %s\n",
+ fdt_strerror(ret));
+ return ret;
+ }
+
+ for (i = 0; i < TOKEN_MAX; i++) {
+ struct rtas_call *call = &rtas_table[i];
+
+ if (!call->name) {
+ continue;
+ }
+
+ ret = qemu_devtree_setprop_cell(fdt, "/rtas", call->name,
+ i + TOKEN_BASE);
+ if (ret < 0) {
+ fprintf(stderr, "Couldn't add rtas token for %s: %s\n",
+ call->name, fdt_strerror(ret));
+ return ret;
+ }
+
+ }
+ return 0;
+}
+
+static void core_rtas_register_types(void)
+{
+ spapr_rtas_register("display-character", rtas_display_character);
+ spapr_rtas_register("get-time-of-day", rtas_get_time_of_day);
+ spapr_rtas_register("set-time-of-day", rtas_set_time_of_day);
+ spapr_rtas_register("power-off", rtas_power_off);
+ spapr_rtas_register("system-reboot", rtas_system_reboot);
+ spapr_rtas_register("query-cpu-stopped-state",
+ rtas_query_cpu_stopped_state);
+ spapr_rtas_register("start-cpu", rtas_start_cpu);
+}
+
+type_init(core_rtas_register_types)
diff --git a/hw/ppc/spapr_vio.c b/hw/ppc/spapr_vio.c
new file mode 100644
index 0000000..6eb3ab5
--- /dev/null
+++ b/hw/ppc/spapr_vio.c
@@ -0,0 +1,649 @@
+/*
+ * QEMU sPAPR VIO code
+ *
+ * Copyright (c) 2010 David Gibson, IBM Corporation <dwg@au1.ibm.com>
+ * Based on the s390 virtio bus code:
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/hw.h"
+#include "sysemu/sysemu.h"
+#include "hw/boards.h"
+#include "monitor/monitor.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "hw/sysbus.h"
+#include "sysemu/kvm.h"
+#include "sysemu/device_tree.h"
+#include "kvm_ppc.h"
+
+#include "hw/spapr.h"
+#include "hw/spapr_vio.h"
+#include "hw/xics.h"
+
+#ifdef CONFIG_FDT
+#include <libfdt.h>
+#endif /* CONFIG_FDT */
+
+/* #define DEBUG_SPAPR */
+
+#ifdef DEBUG_SPAPR
+#define dprintf(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+ do { } while (0)
+#endif
+
+static Property spapr_vio_props[] = {
+ DEFINE_PROP_UINT32("irq", VIOsPAPRDevice, irq, 0), \
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const TypeInfo spapr_vio_bus_info = {
+ .name = TYPE_SPAPR_VIO_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(VIOsPAPRBus),
+};
+
+VIOsPAPRDevice *spapr_vio_find_by_reg(VIOsPAPRBus *bus, uint32_t reg)
+{
+ BusChild *kid;
+ VIOsPAPRDevice *dev = NULL;
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ dev = (VIOsPAPRDevice *)kid->child;
+ if (dev->reg == reg) {
+ return dev;
+ }
+ }
+
+ return NULL;
+}
+
+static char *vio_format_dev_name(VIOsPAPRDevice *dev)
+{
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ char *name;
+
+ /* Device tree style name device@reg */
+ name = g_strdup_printf("%s@%x", pc->dt_name, dev->reg);
+
+ return name;
+}
+
+#ifdef CONFIG_FDT
+static int vio_make_devnode(VIOsPAPRDevice *dev,
+ void *fdt)
+{
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ int vdevice_off, node_off, ret;
+ char *dt_name;
+
+ vdevice_off = fdt_path_offset(fdt, "/vdevice");
+ if (vdevice_off < 0) {
+ return vdevice_off;
+ }
+
+ dt_name = vio_format_dev_name(dev);
+ node_off = fdt_add_subnode(fdt, vdevice_off, dt_name);
+ g_free(dt_name);
+ if (node_off < 0) {
+ return node_off;
+ }
+
+ ret = fdt_setprop_cell(fdt, node_off, "reg", dev->reg);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (pc->dt_type) {
+ ret = fdt_setprop_string(fdt, node_off, "device_type",
+ pc->dt_type);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (pc->dt_compatible) {
+ ret = fdt_setprop_string(fdt, node_off, "compatible",
+ pc->dt_compatible);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ if (dev->irq) {
+ uint32_t ints_prop[] = {cpu_to_be32(dev->irq), 0};
+
+ ret = fdt_setprop(fdt, node_off, "interrupts", ints_prop,
+ sizeof(ints_prop));
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ ret = spapr_tcet_dma_dt(fdt, node_off, "ibm,my-dma-window", dev->dma);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (pc->devnode) {
+ ret = (pc->devnode)(dev, fdt, node_off);
+ if (ret < 0) {
+ return ret;
+ }
+ }
+
+ return node_off;
+}
+#endif /* CONFIG_FDT */
+
+/*
+ * CRQ handling
+ */
+static target_ulong h_reg_crq(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong queue_addr = args[1];
+ target_ulong queue_len = args[2];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ /* We can't grok a queue size bigger than 256M for now */
+ if (queue_len < 0x1000 || queue_len > 0x10000000) {
+ hcall_dprintf("Queue size too small or too big (0x" TARGET_FMT_lx
+ ")\n", queue_len);
+ return H_PARAMETER;
+ }
+
+ /* Check queue alignment */
+ if (queue_addr & 0xfff) {
+ hcall_dprintf("Queue not aligned (0x" TARGET_FMT_lx ")\n", queue_addr);
+ return H_PARAMETER;
+ }
+
+ /* Check if device supports CRQs */
+ if (!dev->crq.SendFunc) {
+ hcall_dprintf("Device does not support CRQ\n");
+ return H_NOT_FOUND;
+ }
+
+ /* Already a queue ? */
+ if (dev->crq.qsize) {
+ hcall_dprintf("CRQ already registered\n");
+ return H_RESOURCE;
+ }
+ dev->crq.qladdr = queue_addr;
+ dev->crq.qsize = queue_len;
+ dev->crq.qnext = 0;
+
+ dprintf("CRQ for dev 0x" TARGET_FMT_lx " registered at 0x"
+ TARGET_FMT_lx "/0x" TARGET_FMT_lx "\n",
+ reg, queue_addr, queue_len);
+ return H_SUCCESS;
+}
+
+static target_ulong free_crq(VIOsPAPRDevice *dev)
+{
+ dev->crq.qladdr = 0;
+ dev->crq.qsize = 0;
+ dev->crq.qnext = 0;
+
+ dprintf("CRQ for dev 0x%" PRIx32 " freed\n", dev->reg);
+
+ return H_SUCCESS;
+}
+
+static target_ulong h_free_crq(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ return free_crq(dev);
+}
+
+static target_ulong h_send_crq(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong msg_hi = args[1];
+ target_ulong msg_lo = args[2];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ uint64_t crq_mangle[2];
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+ crq_mangle[0] = cpu_to_be64(msg_hi);
+ crq_mangle[1] = cpu_to_be64(msg_lo);
+
+ if (dev->crq.SendFunc) {
+ return dev->crq.SendFunc(dev, (uint8_t *)crq_mangle);
+ }
+
+ return H_HARDWARE;
+}
+
+static target_ulong h_enable_crq(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong reg = args[0];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+
+ if (!dev) {
+ hcall_dprintf("Unit 0x" TARGET_FMT_lx " does not exist\n", reg);
+ return H_PARAMETER;
+ }
+
+ return 0;
+}
+
+/* Returns negative error, 0 success, or positive: queue full */
+int spapr_vio_send_crq(VIOsPAPRDevice *dev, uint8_t *crq)
+{
+ int rc;
+ uint8_t byte;
+
+ if (!dev->crq.qsize) {
+ fprintf(stderr, "spapr_vio_send_creq on uninitialized queue\n");
+ return -1;
+ }
+
+ /* Maybe do a fast path for KVM just writing to the pages */
+ rc = spapr_vio_dma_read(dev, dev->crq.qladdr + dev->crq.qnext, &byte, 1);
+ if (rc) {
+ return rc;
+ }
+ if (byte != 0) {
+ return 1;
+ }
+
+ rc = spapr_vio_dma_write(dev, dev->crq.qladdr + dev->crq.qnext + 8,
+ &crq[8], 8);
+ if (rc) {
+ return rc;
+ }
+
+ kvmppc_eieio();
+
+ rc = spapr_vio_dma_write(dev, dev->crq.qladdr + dev->crq.qnext, crq, 8);
+ if (rc) {
+ return rc;
+ }
+
+ dev->crq.qnext = (dev->crq.qnext + 16) % dev->crq.qsize;
+
+ if (dev->signal_state & 1) {
+ qemu_irq_pulse(spapr_vio_qirq(dev));
+ }
+
+ return 0;
+}
+
+/* "quiesce" handling */
+
+static void spapr_vio_quiesce_one(VIOsPAPRDevice *dev)
+{
+ if (dev->dma) {
+ spapr_tce_reset(dev->dma);
+ }
+ free_crq(dev);
+}
+
+static void rtas_set_tce_bypass(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ VIOsPAPRBus *bus = spapr->vio_bus;
+ VIOsPAPRDevice *dev;
+ uint32_t unit, enable;
+
+ if (nargs != 2) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+ unit = rtas_ld(args, 0);
+ enable = rtas_ld(args, 1);
+ dev = spapr_vio_find_by_reg(bus, unit);
+ if (!dev) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ if (!dev->dma) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ spapr_tce_set_bypass(dev->dma, !!enable);
+
+ rtas_st(rets, 0, 0);
+}
+
+static void rtas_quiesce(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ VIOsPAPRBus *bus = spapr->vio_bus;
+ BusChild *kid;
+ VIOsPAPRDevice *dev = NULL;
+
+ if (nargs != 0) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ dev = (VIOsPAPRDevice *)kid->child;
+ spapr_vio_quiesce_one(dev);
+ }
+
+ rtas_st(rets, 0, 0);
+}
+
+static VIOsPAPRDevice *reg_conflict(VIOsPAPRDevice *dev)
+{
+ VIOsPAPRBus *bus = DO_UPCAST(VIOsPAPRBus, bus, dev->qdev.parent_bus);
+ BusChild *kid;
+ VIOsPAPRDevice *other;
+
+ /*
+ * Check for a device other than the given one which is already
+ * using the requested address. We have to open code this because
+ * the given dev might already be in the list.
+ */
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ other = DO_UPCAST(VIOsPAPRDevice, qdev, kid->child);
+
+ if (other != dev && other->reg == dev->reg) {
+ return other;
+ }
+ }
+
+ return 0;
+}
+
+static void spapr_vio_busdev_reset(DeviceState *qdev)
+{
+ VIOsPAPRDevice *dev = DO_UPCAST(VIOsPAPRDevice, qdev, qdev);
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+
+ /* Shut down the request queue and TCEs if necessary */
+ spapr_vio_quiesce_one(dev);
+
+ dev->signal_state = 0;
+
+ if (pc->reset) {
+ pc->reset(dev);
+ }
+}
+
+static int spapr_vio_busdev_init(DeviceState *qdev)
+{
+ VIOsPAPRDevice *dev = (VIOsPAPRDevice *)qdev;
+ VIOsPAPRDeviceClass *pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+ char *id;
+
+ if (dev->reg != -1) {
+ /*
+ * Explicitly assigned address, just verify that no-one else
+ * is using it. other mechanism). We have to open code this
+ * rather than using spapr_vio_find_by_reg() because sdev
+ * itself is already in the list.
+ */
+ VIOsPAPRDevice *other = reg_conflict(dev);
+
+ if (other) {
+ fprintf(stderr, "vio: %s and %s devices conflict at address %#x\n",
+ object_get_typename(OBJECT(qdev)),
+ object_get_typename(OBJECT(&other->qdev)),
+ dev->reg);
+ return -1;
+ }
+ } else {
+ /* Need to assign an address */
+ VIOsPAPRBus *bus = DO_UPCAST(VIOsPAPRBus, bus, dev->qdev.parent_bus);
+
+ do {
+ dev->reg = bus->next_reg++;
+ } while (reg_conflict(dev));
+ }
+
+ /* Don't overwrite ids assigned on the command line */
+ if (!dev->qdev.id) {
+ id = vio_format_dev_name(dev);
+ dev->qdev.id = id;
+ }
+
+ dev->irq = spapr_allocate_msi(dev->irq);
+ if (!dev->irq) {
+ return -1;
+ }
+
+ if (pc->rtce_window_size) {
+ uint32_t liobn = SPAPR_VIO_BASE_LIOBN | dev->reg;
+ dev->dma = spapr_tce_new_dma_context(liobn, pc->rtce_window_size);
+ }
+
+ return pc->init(dev);
+}
+
+static target_ulong h_vio_signal(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode,
+ target_ulong *args)
+{
+ target_ulong reg = args[0];
+ target_ulong mode = args[1];
+ VIOsPAPRDevice *dev = spapr_vio_find_by_reg(spapr->vio_bus, reg);
+ VIOsPAPRDeviceClass *pc;
+
+ if (!dev) {
+ return H_PARAMETER;
+ }
+
+ pc = VIO_SPAPR_DEVICE_GET_CLASS(dev);
+
+ if (mode & ~pc->signal_mask) {
+ return H_PARAMETER;
+ }
+
+ dev->signal_state = mode;
+
+ return H_SUCCESS;
+}
+
+VIOsPAPRBus *spapr_vio_bus_init(void)
+{
+ VIOsPAPRBus *bus;
+ BusState *qbus;
+ DeviceState *dev;
+
+ /* Create bridge device */
+ dev = qdev_create(NULL, "spapr-vio-bridge");
+ qdev_init_nofail(dev);
+
+ /* Create bus on bridge device */
+
+ qbus = qbus_create(TYPE_SPAPR_VIO_BUS, dev, "spapr-vio");
+ bus = DO_UPCAST(VIOsPAPRBus, bus, qbus);
+ bus->next_reg = 0x71000000;
+
+ /* hcall-vio */
+ spapr_register_hypercall(H_VIO_SIGNAL, h_vio_signal);
+
+ /* hcall-crq */
+ spapr_register_hypercall(H_REG_CRQ, h_reg_crq);
+ spapr_register_hypercall(H_FREE_CRQ, h_free_crq);
+ spapr_register_hypercall(H_SEND_CRQ, h_send_crq);
+ spapr_register_hypercall(H_ENABLE_CRQ, h_enable_crq);
+
+ /* RTAS calls */
+ spapr_rtas_register("ibm,set-tce-bypass", rtas_set_tce_bypass);
+ spapr_rtas_register("quiesce", rtas_quiesce);
+
+ return bus;
+}
+
+/* Represents sPAPR hcall VIO devices */
+
+static int spapr_vio_bridge_init(SysBusDevice *dev)
+{
+ /* nothing */
+ return 0;
+}
+
+static void spapr_vio_bridge_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = spapr_vio_bridge_init;
+ dc->no_user = 1;
+}
+
+static const TypeInfo spapr_vio_bridge_info = {
+ .name = "spapr-vio-bridge",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SysBusDevice),
+ .class_init = spapr_vio_bridge_class_init,
+};
+
+static void vio_spapr_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = spapr_vio_busdev_init;
+ k->reset = spapr_vio_busdev_reset;
+ k->bus_type = TYPE_SPAPR_VIO_BUS;
+ k->props = spapr_vio_props;
+}
+
+static const TypeInfo spapr_vio_type_info = {
+ .name = TYPE_VIO_SPAPR_DEVICE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(VIOsPAPRDevice),
+ .abstract = true,
+ .class_size = sizeof(VIOsPAPRDeviceClass),
+ .class_init = vio_spapr_device_class_init,
+};
+
+static void spapr_vio_register_types(void)
+{
+ type_register_static(&spapr_vio_bus_info);
+ type_register_static(&spapr_vio_bridge_info);
+ type_register_static(&spapr_vio_type_info);
+}
+
+type_init(spapr_vio_register_types)
+
+#ifdef CONFIG_FDT
+static int compare_reg(const void *p1, const void *p2)
+{
+ VIOsPAPRDevice const *dev1, *dev2;
+
+ dev1 = (VIOsPAPRDevice *)*(DeviceState **)p1;
+ dev2 = (VIOsPAPRDevice *)*(DeviceState **)p2;
+
+ if (dev1->reg < dev2->reg) {
+ return -1;
+ }
+ if (dev1->reg == dev2->reg) {
+ return 0;
+ }
+
+ /* dev1->reg > dev2->reg */
+ return 1;
+}
+
+int spapr_populate_vdevice(VIOsPAPRBus *bus, void *fdt)
+{
+ DeviceState *qdev, **qdevs;
+ BusChild *kid;
+ int i, num, ret = 0;
+
+ /* Count qdevs on the bus list */
+ num = 0;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ num++;
+ }
+
+ /* Copy out into an array of pointers */
+ qdevs = g_malloc(sizeof(qdev) * num);
+ num = 0;
+ QTAILQ_FOREACH(kid, &bus->bus.children, sibling) {
+ qdevs[num++] = kid->child;
+ }
+
+ /* Sort the array */
+ qsort(qdevs, num, sizeof(qdev), compare_reg);
+
+ /* Hack alert. Give the devices to libfdt in reverse order, we happen
+ * to know that will mean they are in forward order in the tree. */
+ for (i = num - 1; i >= 0; i--) {
+ VIOsPAPRDevice *dev = (VIOsPAPRDevice *)(qdevs[i]);
+
+ ret = vio_make_devnode(dev, fdt);
+
+ if (ret < 0) {
+ goto out;
+ }
+ }
+
+ ret = 0;
+out:
+ free(qdevs);
+
+ return ret;
+}
+
+int spapr_populate_chosen_stdout(void *fdt, VIOsPAPRBus *bus)
+{
+ VIOsPAPRDevice *dev;
+ char *name, *path;
+ int ret, offset;
+
+ dev = spapr_vty_get_default(bus);
+ if (!dev)
+ return 0;
+
+ offset = fdt_path_offset(fdt, "/chosen");
+ if (offset < 0) {
+ return offset;
+ }
+
+ name = vio_format_dev_name(dev);
+ path = g_strdup_printf("/vdevice/%s", name);
+
+ ret = fdt_setprop_string(fdt, offset, "linux,stdout-path", path);
+
+ g_free(name);
+ g_free(path);
+
+ return ret;
+}
+#endif /* CONFIG_FDT */
diff --git a/hw/ppc/virtex_ml507.c b/hw/ppc/virtex_ml507.c
new file mode 100644
index 0000000..41eab16
--- /dev/null
+++ b/hw/ppc/virtex_ml507.c
@@ -0,0 +1,274 @@
+/*
+ * Model of Xilinx Virtex5 ML507 PPC-440 refdesign.
+ *
+ * Copyright (c) 2010 Edgar E. Iglesias.
+ *
+ * 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 "hw/sysbus.h"
+#include "hw/hw.h"
+#include "hw/serial.h"
+#include "hw/flash.h"
+#include "sysemu/sysemu.h"
+#include "hw/devices.h"
+#include "hw/boards.h"
+#include "sysemu/device_tree.h"
+#include "hw/loader.h"
+#include "elf.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+
+#include "hw/ppc.h"
+#include "hw/ppc4xx.h"
+#include "hw/ppc405.h"
+
+#include "sysemu/blockdev.h"
+#include "hw/xilinx.h"
+
+#define EPAPR_MAGIC (0x45504150)
+#define FLASH_SIZE (16 * 1024 * 1024)
+
+static struct boot_info
+{
+ uint32_t bootstrap_pc;
+ uint32_t cmdline;
+ uint32_t fdt;
+ uint32_t ima_size;
+ void *vfdt;
+} boot_info;
+
+/* Create reset TLB entries for BookE, spanning the 32bit addr space. */
+static void mmubooke_create_initial_mapping(CPUPPCState *env,
+ target_ulong va,
+ hwaddr pa)
+{
+ ppcemb_tlb_t *tlb = &env->tlb.tlbe[0];
+
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1 << 31; /* up to 0x80000000 */
+ tlb->EPN = va & TARGET_PAGE_MASK;
+ tlb->RPN = pa & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+
+ tlb = &env->tlb.tlbe[1];
+ tlb->attr = 0;
+ tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4);
+ tlb->size = 1 << 31; /* up to 0xffffffff */
+ tlb->EPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->RPN = 0x80000000 & TARGET_PAGE_MASK;
+ tlb->PID = 0;
+}
+
+static PowerPCCPU *ppc440_init_xilinx(ram_addr_t *ram_size,
+ int do_init,
+ const char *cpu_model,
+ uint32_t sysclk)
+{
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ qemu_irq *irqs;
+
+ cpu = cpu_ppc_init(cpu_model);
+ if (cpu == NULL) {
+ fprintf(stderr, "Unable to initialize CPU!\n");
+ exit(1);
+ }
+ env = &cpu->env;
+
+ ppc_booke_timers_init(cpu, sysclk, 0/* no flags */);
+
+ ppc_dcr_init(env, NULL, NULL);
+
+ /* interrupt controller */
+ irqs = g_malloc0(sizeof(qemu_irq) * PPCUIC_OUTPUT_NB);
+ irqs[PPCUIC_OUTPUT_INT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_INT];
+ irqs[PPCUIC_OUTPUT_CINT] = ((qemu_irq *)env->irq_inputs)[PPC40x_INPUT_CINT];
+ ppcuic_init(env, irqs, 0x0C0, 0, 1);
+ return cpu;
+}
+
+static void main_cpu_reset(void *opaque)
+{
+ PowerPCCPU *cpu = opaque;
+ CPUPPCState *env = &cpu->env;
+ struct boot_info *bi = env->load_info;
+
+ cpu_reset(CPU(cpu));
+ /* Linux Kernel Parameters (passing device tree):
+ * r3: pointer to the fdt
+ * r4: 0
+ * r5: 0
+ * r6: epapr magic
+ * r7: size of IMA in bytes
+ * r8: 0
+ * r9: 0
+ */
+ env->gpr[1] = (16<<20) - 8;
+ /* Provide a device-tree. */
+ env->gpr[3] = bi->fdt;
+ env->nip = bi->bootstrap_pc;
+
+ /* Create a mapping for the kernel. */
+ mmubooke_create_initial_mapping(env, 0, 0);
+ env->gpr[6] = tswap32(EPAPR_MAGIC);
+ env->gpr[7] = bi->ima_size;
+}
+
+#define BINARY_DEVICE_TREE_FILE "virtex-ml507.dtb"
+static int xilinx_load_device_tree(hwaddr addr,
+ uint32_t ramsize,
+ hwaddr initrd_base,
+ hwaddr initrd_size,
+ const char *kernel_cmdline)
+{
+ char *path;
+ int fdt_size;
+#ifdef CONFIG_FDT
+ void *fdt;
+ int r;
+
+ /* Try the local "ppc.dtb" override. */
+ fdt = load_device_tree("ppc.dtb", &fdt_size);
+ if (!fdt) {
+ path = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE);
+ if (path) {
+ fdt = load_device_tree(path, &fdt_size);
+ g_free(path);
+ }
+ if (!fdt) {
+ return 0;
+ }
+ }
+
+ r = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs", kernel_cmdline);
+ if (r < 0)
+ fprintf(stderr, "couldn't set /chosen/bootargs\n");
+ cpu_physical_memory_write (addr, (void *)fdt, fdt_size);
+#else
+ /* We lack libfdt so we cannot manipulate the fdt. Just pass on the blob
+ to the kernel. */
+ fdt_size = load_image_targphys("ppc.dtb", addr, 0x10000);
+ if (fdt_size < 0) {
+ path = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE);
+ if (path) {
+ fdt_size = load_image_targphys(path, addr, 0x10000);
+ g_free(path);
+ }
+ }
+
+ if (kernel_cmdline) {
+ fprintf(stderr,
+ "Warning: missing libfdt, cannot pass cmdline to kernel!\n");
+ }
+#endif
+ return fdt_size;
+}
+
+static void virtex_init(QEMUMachineInitArgs *args)
+{
+ ram_addr_t ram_size = args->ram_size;
+ const char *cpu_model = args->cpu_model;
+ const char *kernel_filename = args->kernel_filename;
+ const char *kernel_cmdline = args->kernel_cmdline;
+ MemoryRegion *address_space_mem = get_system_memory();
+ DeviceState *dev;
+ PowerPCCPU *cpu;
+ CPUPPCState *env;
+ hwaddr ram_base = 0;
+ DriveInfo *dinfo;
+ MemoryRegion *phys_ram = g_new(MemoryRegion, 1);
+ qemu_irq irq[32], *cpu_irq;
+ int kernel_size;
+ int i;
+
+ /* init CPUs */
+ if (cpu_model == NULL) {
+ cpu_model = "440-Xilinx";
+ }
+
+ cpu = ppc440_init_xilinx(&ram_size, 1, cpu_model, 400000000);
+ env = &cpu->env;
+ qemu_register_reset(main_cpu_reset, cpu);
+
+ memory_region_init_ram(phys_ram, "ram", ram_size);
+ vmstate_register_ram_global(phys_ram);
+ memory_region_add_subregion(address_space_mem, ram_base, phys_ram);
+
+ dinfo = drive_get(IF_PFLASH, 0, 0);
+ pflash_cfi01_register(0xfc000000, NULL, "virtex.flash", FLASH_SIZE,
+ dinfo ? dinfo->bdrv : NULL, (64 * 1024),
+ FLASH_SIZE >> 16,
+ 1, 0x89, 0x18, 0x0000, 0x0, 1);
+
+ cpu_irq = (qemu_irq *) &env->irq_inputs[PPC40x_INPUT_INT];
+ dev = xilinx_intc_create(0x81800000, cpu_irq[0], 0);
+ for (i = 0; i < 32; i++) {
+ irq[i] = qdev_get_gpio_in(dev, i);
+ }
+
+ serial_mm_init(address_space_mem, 0x83e01003ULL, 2, irq[9], 115200,
+ serial_hds[0], DEVICE_LITTLE_ENDIAN);
+
+ /* 2 timers at irq 2 @ 62 Mhz. */
+ xilinx_timer_create(0x83c00000, irq[3], 0, 62 * 1000000);
+
+ if (kernel_filename) {
+ uint64_t entry, low, high;
+ hwaddr boot_offset;
+
+ /* Boots a kernel elf binary. */
+ kernel_size = load_elf(kernel_filename, NULL, NULL,
+ &entry, &low, &high, 1, ELF_MACHINE, 0);
+ boot_info.bootstrap_pc = entry & 0x00ffffff;
+
+ if (kernel_size < 0) {
+ boot_offset = 0x1200000;
+ /* If we failed loading ELF's try a raw image. */
+ kernel_size = load_image_targphys(kernel_filename,
+ boot_offset,
+ ram_size);
+ boot_info.bootstrap_pc = boot_offset;
+ high = boot_info.bootstrap_pc + kernel_size + 8192;
+ }
+
+ boot_info.ima_size = kernel_size;
+
+ /* Provide a device-tree. */
+ boot_info.fdt = high + (8192 * 2);
+ boot_info.fdt &= ~8191;
+ xilinx_load_device_tree(boot_info.fdt, ram_size, 0, 0, kernel_cmdline);
+ }
+ env->load_info = &boot_info;
+}
+
+static QEMUMachine virtex_machine = {
+ .name = "virtex-ml507",
+ .desc = "Xilinx Virtex ML507 reference design",
+ .init = virtex_init,
+ DEFAULT_MACHINE_OPTIONS,
+};
+
+static void virtex_machine_init(void)
+{
+ qemu_register_machine(&virtex_machine);
+}
+
+machine_init(virtex_machine_init);
diff --git a/hw/ppc/xics.c b/hw/ppc/xics.c
new file mode 100644
index 0000000..c3ef12f
--- /dev/null
+++ b/hw/ppc/xics.c
@@ -0,0 +1,588 @@
+/*
+ * QEMU PowerPC pSeries Logical Partition (aka sPAPR) hardware System Emulator
+ *
+ * PAPR Virtualized Interrupt System, aka ICS/ICP aka xics
+ *
+ * Copyright (c) 2010,2011 David Gibson, IBM Corporation.
+ *
+ * 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 "hw/hw.h"
+#include "trace.h"
+#include "hw/spapr.h"
+#include "hw/xics.h"
+
+/*
+ * ICP: Presentation layer
+ */
+
+struct icp_server_state {
+ uint32_t xirr;
+ uint8_t pending_priority;
+ uint8_t mfrr;
+ qemu_irq output;
+};
+
+#define XISR_MASK 0x00ffffff
+#define CPPR_MASK 0xff000000
+
+#define XISR(ss) (((ss)->xirr) & XISR_MASK)
+#define CPPR(ss) (((ss)->xirr) >> 24)
+
+struct ics_state;
+
+struct icp_state {
+ long nr_servers;
+ struct icp_server_state *ss;
+ struct ics_state *ics;
+};
+
+static void ics_reject(struct ics_state *ics, int nr);
+static void ics_resend(struct ics_state *ics);
+static void ics_eoi(struct ics_state *ics, int nr);
+
+static void icp_check_ipi(struct icp_state *icp, int server)
+{
+ struct icp_server_state *ss = icp->ss + server;
+
+ if (XISR(ss) && (ss->pending_priority <= ss->mfrr)) {
+ return;
+ }
+
+ trace_xics_icp_check_ipi(server, ss->mfrr);
+
+ if (XISR(ss)) {
+ ics_reject(icp->ics, XISR(ss));
+ }
+
+ ss->xirr = (ss->xirr & ~XISR_MASK) | XICS_IPI;
+ ss->pending_priority = ss->mfrr;
+ qemu_irq_raise(ss->output);
+}
+
+static void icp_resend(struct icp_state *icp, int server)
+{
+ struct icp_server_state *ss = icp->ss + server;
+
+ if (ss->mfrr < CPPR(ss)) {
+ icp_check_ipi(icp, server);
+ }
+ ics_resend(icp->ics);
+}
+
+static void icp_set_cppr(struct icp_state *icp, int server, uint8_t cppr)
+{
+ struct icp_server_state *ss = icp->ss + server;
+ uint8_t old_cppr;
+ uint32_t old_xisr;
+
+ old_cppr = CPPR(ss);
+ ss->xirr = (ss->xirr & ~CPPR_MASK) | (cppr << 24);
+
+ if (cppr < old_cppr) {
+ if (XISR(ss) && (cppr <= ss->pending_priority)) {
+ old_xisr = XISR(ss);
+ ss->xirr &= ~XISR_MASK; /* Clear XISR */
+ qemu_irq_lower(ss->output);
+ ics_reject(icp->ics, old_xisr);
+ }
+ } else {
+ if (!XISR(ss)) {
+ icp_resend(icp, server);
+ }
+ }
+}
+
+static void icp_set_mfrr(struct icp_state *icp, int server, uint8_t mfrr)
+{
+ struct icp_server_state *ss = icp->ss + server;
+
+ ss->mfrr = mfrr;
+ if (mfrr < CPPR(ss)) {
+ icp_check_ipi(icp, server);
+ }
+}
+
+static uint32_t icp_accept(struct icp_server_state *ss)
+{
+ uint32_t xirr = ss->xirr;
+
+ qemu_irq_lower(ss->output);
+ ss->xirr = ss->pending_priority << 24;
+
+ trace_xics_icp_accept(xirr, ss->xirr);
+
+ return xirr;
+}
+
+static void icp_eoi(struct icp_state *icp, int server, uint32_t xirr)
+{
+ struct icp_server_state *ss = icp->ss + server;
+
+ /* Send EOI -> ICS */
+ ss->xirr = (ss->xirr & ~CPPR_MASK) | (xirr & CPPR_MASK);
+ trace_xics_icp_eoi(server, xirr, ss->xirr);
+ ics_eoi(icp->ics, xirr & XISR_MASK);
+ if (!XISR(ss)) {
+ icp_resend(icp, server);
+ }
+}
+
+static void icp_irq(struct icp_state *icp, int server, int nr, uint8_t priority)
+{
+ struct icp_server_state *ss = icp->ss + server;
+
+ trace_xics_icp_irq(server, nr, priority);
+
+ if ((priority >= CPPR(ss))
+ || (XISR(ss) && (ss->pending_priority <= priority))) {
+ ics_reject(icp->ics, nr);
+ } else {
+ if (XISR(ss)) {
+ ics_reject(icp->ics, XISR(ss));
+ }
+ ss->xirr = (ss->xirr & ~XISR_MASK) | (nr & XISR_MASK);
+ ss->pending_priority = priority;
+ trace_xics_icp_raise(ss->xirr, ss->pending_priority);
+ qemu_irq_raise(ss->output);
+ }
+}
+
+/*
+ * ICS: Source layer
+ */
+
+struct ics_irq_state {
+ int server;
+ uint8_t priority;
+ uint8_t saved_priority;
+#define XICS_STATUS_ASSERTED 0x1
+#define XICS_STATUS_SENT 0x2
+#define XICS_STATUS_REJECTED 0x4
+#define XICS_STATUS_MASKED_PENDING 0x8
+ uint8_t status;
+};
+
+struct ics_state {
+ int nr_irqs;
+ int offset;
+ qemu_irq *qirqs;
+ bool *islsi;
+ struct ics_irq_state *irqs;
+ struct icp_state *icp;
+};
+
+static int ics_valid_irq(struct ics_state *ics, uint32_t nr)
+{
+ return (nr >= ics->offset)
+ && (nr < (ics->offset + ics->nr_irqs));
+}
+
+static void resend_msi(struct ics_state *ics, int srcno)
+{
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ /* FIXME: filter by server#? */
+ if (irq->status & XICS_STATUS_REJECTED) {
+ irq->status &= ~XICS_STATUS_REJECTED;
+ if (irq->priority != 0xff) {
+ icp_irq(ics->icp, irq->server, srcno + ics->offset,
+ irq->priority);
+ }
+ }
+}
+
+static void resend_lsi(struct ics_state *ics, int srcno)
+{
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ if ((irq->priority != 0xff)
+ && (irq->status & XICS_STATUS_ASSERTED)
+ && !(irq->status & XICS_STATUS_SENT)) {
+ irq->status |= XICS_STATUS_SENT;
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+ }
+}
+
+static void set_irq_msi(struct ics_state *ics, int srcno, int val)
+{
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ trace_xics_set_irq_msi(srcno, srcno + ics->offset);
+
+ if (val) {
+ if (irq->priority == 0xff) {
+ irq->status |= XICS_STATUS_MASKED_PENDING;
+ trace_xics_masked_pending();
+ } else {
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+ }
+ }
+}
+
+static void set_irq_lsi(struct ics_state *ics, int srcno, int val)
+{
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ trace_xics_set_irq_lsi(srcno, srcno + ics->offset);
+ if (val) {
+ irq->status |= XICS_STATUS_ASSERTED;
+ } else {
+ irq->status &= ~XICS_STATUS_ASSERTED;
+ }
+ resend_lsi(ics, srcno);
+}
+
+static void ics_set_irq(void *opaque, int srcno, int val)
+{
+ struct ics_state *ics = (struct ics_state *)opaque;
+
+ if (ics->islsi[srcno]) {
+ set_irq_lsi(ics, srcno, val);
+ } else {
+ set_irq_msi(ics, srcno, val);
+ }
+}
+
+static void write_xive_msi(struct ics_state *ics, int srcno)
+{
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ if (!(irq->status & XICS_STATUS_MASKED_PENDING)
+ || (irq->priority == 0xff)) {
+ return;
+ }
+
+ irq->status &= ~XICS_STATUS_MASKED_PENDING;
+ icp_irq(ics->icp, irq->server, srcno + ics->offset, irq->priority);
+}
+
+static void write_xive_lsi(struct ics_state *ics, int srcno)
+{
+ resend_lsi(ics, srcno);
+}
+
+static void ics_write_xive(struct ics_state *ics, int nr, int server,
+ uint8_t priority, uint8_t saved_priority)
+{
+ int srcno = nr - ics->offset;
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ irq->server = server;
+ irq->priority = priority;
+ irq->saved_priority = saved_priority;
+
+ trace_xics_ics_write_xive(nr, srcno, server, priority);
+
+ if (ics->islsi[srcno]) {
+ write_xive_lsi(ics, srcno);
+ } else {
+ write_xive_msi(ics, srcno);
+ }
+}
+
+static void ics_reject(struct ics_state *ics, int nr)
+{
+ struct ics_irq_state *irq = ics->irqs + nr - ics->offset;
+
+ trace_xics_ics_reject(nr, nr - ics->offset);
+ irq->status |= XICS_STATUS_REJECTED; /* Irrelevant but harmless for LSI */
+ irq->status &= ~XICS_STATUS_SENT; /* Irrelevant but harmless for MSI */
+}
+
+static void ics_resend(struct ics_state *ics)
+{
+ int i;
+
+ for (i = 0; i < ics->nr_irqs; i++) {
+ /* FIXME: filter by server#? */
+ if (ics->islsi[i]) {
+ resend_lsi(ics, i);
+ } else {
+ resend_msi(ics, i);
+ }
+ }
+}
+
+static void ics_eoi(struct ics_state *ics, int nr)
+{
+ int srcno = nr - ics->offset;
+ struct ics_irq_state *irq = ics->irqs + srcno;
+
+ trace_xics_ics_eoi(nr);
+
+ if (ics->islsi[srcno]) {
+ irq->status &= ~XICS_STATUS_SENT;
+ }
+}
+
+/*
+ * Exported functions
+ */
+
+qemu_irq xics_get_qirq(struct icp_state *icp, int irq)
+{
+ if (!ics_valid_irq(icp->ics, irq)) {
+ return NULL;
+ }
+
+ return icp->ics->qirqs[irq - icp->ics->offset];
+}
+
+void xics_set_irq_type(struct icp_state *icp, int irq, bool lsi)
+{
+ assert(ics_valid_irq(icp->ics, irq));
+
+ icp->ics->islsi[irq - icp->ics->offset] = lsi;
+}
+
+static target_ulong h_cppr(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ target_ulong cppr = args[0];
+
+ icp_set_cppr(spapr->icp, cs->cpu_index, cppr);
+ return H_SUCCESS;
+}
+
+static target_ulong h_ipi(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ target_ulong server = args[0];
+ target_ulong mfrr = args[1];
+
+ if (server >= spapr->icp->nr_servers) {
+ return H_PARAMETER;
+ }
+
+ icp_set_mfrr(spapr->icp, server, mfrr);
+ return H_SUCCESS;
+}
+
+static target_ulong h_xirr(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ uint32_t xirr = icp_accept(spapr->icp->ss + cs->cpu_index);
+
+ args[0] = xirr;
+ return H_SUCCESS;
+}
+
+static target_ulong h_eoi(PowerPCCPU *cpu, sPAPREnvironment *spapr,
+ target_ulong opcode, target_ulong *args)
+{
+ CPUState *cs = CPU(cpu);
+ target_ulong xirr = args[0];
+
+ icp_eoi(spapr->icp, cs->cpu_index, xirr);
+ return H_SUCCESS;
+}
+
+static void rtas_set_xive(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct ics_state *ics = spapr->icp->ics;
+ uint32_t nr, server, priority;
+
+ if ((nargs != 3) || (nret != 1)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+ server = rtas_ld(args, 1);
+ priority = rtas_ld(args, 2);
+
+ if (!ics_valid_irq(ics, nr) || (server >= ics->icp->nr_servers)
+ || (priority > 0xff)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ ics_write_xive(ics, nr, server, priority, priority);
+
+ rtas_st(rets, 0, 0); /* Success */
+}
+
+static void rtas_get_xive(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct ics_state *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 3)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ rtas_st(rets, 0, 0); /* Success */
+ rtas_st(rets, 1, ics->irqs[nr - ics->offset].server);
+ rtas_st(rets, 2, ics->irqs[nr - ics->offset].priority);
+}
+
+static void rtas_int_off(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct ics_state *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 1)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ ics_write_xive(ics, nr, ics->irqs[nr - ics->offset].server, 0xff,
+ ics->irqs[nr - ics->offset].priority);
+
+ rtas_st(rets, 0, 0); /* Success */
+}
+
+static void rtas_int_on(sPAPREnvironment *spapr, uint32_t token,
+ uint32_t nargs, target_ulong args,
+ uint32_t nret, target_ulong rets)
+{
+ struct ics_state *ics = spapr->icp->ics;
+ uint32_t nr;
+
+ if ((nargs != 1) || (nret != 1)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ nr = rtas_ld(args, 0);
+
+ if (!ics_valid_irq(ics, nr)) {
+ rtas_st(rets, 0, -3);
+ return;
+ }
+
+ ics_write_xive(ics, nr, ics->irqs[nr - ics->offset].server,
+ ics->irqs[nr - ics->offset].saved_priority,
+ ics->irqs[nr - ics->offset].saved_priority);
+
+ rtas_st(rets, 0, 0); /* Success */
+}
+
+static void xics_reset(void *opaque)
+{
+ struct icp_state *icp = (struct icp_state *)opaque;
+ struct ics_state *ics = icp->ics;
+ int i;
+
+ for (i = 0; i < icp->nr_servers; i++) {
+ icp->ss[i].xirr = 0;
+ icp->ss[i].pending_priority = 0xff;
+ icp->ss[i].mfrr = 0xff;
+ /* Make all outputs are deasserted */
+ qemu_set_irq(icp->ss[i].output, 0);
+ }
+
+ memset(ics->irqs, 0, sizeof(struct ics_irq_state) * ics->nr_irqs);
+ for (i = 0; i < ics->nr_irqs; i++) {
+ ics->irqs[i].priority = 0xff;
+ ics->irqs[i].saved_priority = 0xff;
+ }
+}
+
+struct icp_state *xics_system_init(int nr_irqs)
+{
+ CPUPPCState *env;
+ CPUState *cpu;
+ int max_server_num;
+ struct icp_state *icp;
+ struct ics_state *ics;
+
+ max_server_num = -1;
+ for (env = first_cpu; env != NULL; env = env->next_cpu) {
+ cpu = CPU(ppc_env_get_cpu(env));
+ if (cpu->cpu_index > max_server_num) {
+ max_server_num = cpu->cpu_index;
+ }
+ }
+
+ icp = g_malloc0(sizeof(*icp));
+ icp->nr_servers = max_server_num + 1;
+ icp->ss = g_malloc0(icp->nr_servers*sizeof(struct icp_server_state));
+
+ for (env = first_cpu; env != NULL; env = env->next_cpu) {
+ cpu = CPU(ppc_env_get_cpu(env));
+ struct icp_server_state *ss = &icp->ss[cpu->cpu_index];
+
+ switch (PPC_INPUT(env)) {
+ case PPC_FLAGS_INPUT_POWER7:
+ ss->output = env->irq_inputs[POWER7_INPUT_INT];
+ break;
+
+ case PPC_FLAGS_INPUT_970:
+ ss->output = env->irq_inputs[PPC970_INPUT_INT];
+ break;
+
+ default:
+ hw_error("XICS interrupt model does not support this CPU bus "
+ "model\n");
+ exit(1);
+ }
+ }
+
+ ics = g_malloc0(sizeof(*ics));
+ ics->nr_irqs = nr_irqs;
+ ics->offset = XICS_IRQ_BASE;
+ ics->irqs = g_malloc0(nr_irqs * sizeof(struct ics_irq_state));
+ ics->islsi = g_malloc0(nr_irqs * sizeof(bool));
+
+ icp->ics = ics;
+ ics->icp = icp;
+
+ ics->qirqs = qemu_allocate_irqs(ics_set_irq, ics, nr_irqs);
+
+ spapr_register_hypercall(H_CPPR, h_cppr);
+ spapr_register_hypercall(H_IPI, h_ipi);
+ spapr_register_hypercall(H_XIRR, h_xirr);
+ spapr_register_hypercall(H_EOI, h_eoi);
+
+ spapr_rtas_register("ibm,set-xive", rtas_set_xive);
+ spapr_rtas_register("ibm,get-xive", rtas_get_xive);
+ spapr_rtas_register("ibm,int-off", rtas_int_off);
+ spapr_rtas_register("ibm,int-on", rtas_int_on);
+
+ qemu_register_reset(xics_reset, icp);
+
+ return icp;
+}