aboutsummaryrefslogtreecommitdiff
path: root/sim/bfin/dv-bfin_cec.c
diff options
context:
space:
mode:
authorMike Frysinger <vapier@gentoo.org>2011-03-06 00:20:21 +0000
committerMike Frysinger <vapier@gentoo.org>2011-03-06 00:20:21 +0000
commitef016f835f292f01f065412fcfd84c50bfff1fea (patch)
treefe72facf7bcdc58af74f3a37d30f0f25d501f6a0 /sim/bfin/dv-bfin_cec.c
parent7dcf22fd41de725f3280c80a1274bcef309d6891 (diff)
downloadgdb-ef016f835f292f01f065412fcfd84c50bfff1fea.zip
gdb-ef016f835f292f01f065412fcfd84c50bfff1fea.tar.gz
gdb-ef016f835f292f01f065412fcfd84c50bfff1fea.tar.bz2
sim: bfin: new port
This can boot Das U-Boot and a Linux kernel. It also supports Linux userspace FLAT and FDPIC (dynamic and static) ELFs. Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'sim/bfin/dv-bfin_cec.c')
-rw-r--r--sim/bfin/dv-bfin_cec.c807
1 files changed, 807 insertions, 0 deletions
diff --git a/sim/bfin/dv-bfin_cec.c b/sim/bfin/dv-bfin_cec.c
new file mode 100644
index 0000000..88fe9dd
--- /dev/null
+++ b/sim/bfin/dv-bfin_cec.c
@@ -0,0 +1,807 @@
+/* Blackfin Core Event Controller (CEC) model.
+
+ Copyright (C) 2010-2011 Free Software Foundation, Inc.
+ Contributed by Analog Devices, Inc.
+
+ This file is part of simulators.
+
+ This program 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 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+
+#include "sim-main.h"
+#include "devices.h"
+#include "dv-bfin_cec.h"
+#include "dv-bfin_evt.h"
+#include "dv-bfin_mmu.h"
+
+struct bfin_cec
+{
+ bu32 base;
+ SIM_CPU *cpu;
+ struct hw *me;
+ struct hw_event *pending;
+
+ /* Order after here is important -- matches hardware MMR layout. */
+ bu32 evt_override, imask, ipend, ilat, iprio;
+};
+#define mmr_base() offsetof(struct bfin_cec, evt_override)
+#define mmr_offset(mmr) (offsetof(struct bfin_cec, mmr) - mmr_base())
+
+static const char * const mmr_names[] = {
+ "EVT_OVERRIDE", "IMASK", "IPEND", "ILAT", "IPRIO",
+};
+#define mmr_name(off) mmr_names[(off) / 4]
+
+static void _cec_raise (SIM_CPU *, struct bfin_cec *, int);
+
+static void
+bfin_cec_hw_event_callback (struct hw *me, void *data)
+{
+ struct bfin_cec *cec = data;
+ hw_event_queue_deschedule (me, cec->pending);
+ _cec_raise (cec->cpu, cec, -1);
+ cec->pending = NULL;
+}
+static void
+bfin_cec_check_pending (struct hw *me, struct bfin_cec *cec)
+{
+ if (cec->pending)
+ return;
+ cec->pending = hw_event_queue_schedule (me, 0, bfin_cec_hw_event_callback, cec);
+}
+static void
+_cec_check_pending (SIM_CPU *cpu, struct bfin_cec *cec)
+{
+ bfin_cec_check_pending (cec->me, cec);
+}
+
+static void
+_cec_imask_write (struct bfin_cec *cec, bu32 value)
+{
+ cec->imask = (value & IVG_MASKABLE_B) | (cec->imask & IVG_UNMASKABLE_B);
+}
+
+static unsigned
+bfin_cec_io_write_buffer (struct hw *me, const void *source,
+ int space, address_word addr, unsigned nr_bytes)
+{
+ struct bfin_cec *cec = hw_data (me);
+ bu32 mmr_off;
+ bu32 value;
+
+ value = dv_load_4 (source);
+ mmr_off = addr - cec->base;
+
+ HW_TRACE_WRITE ();
+
+ switch (mmr_off)
+ {
+ case mmr_offset(evt_override):
+ cec->evt_override = value;
+ break;
+ case mmr_offset(imask):
+ _cec_imask_write (cec, value);
+ bfin_cec_check_pending (me, cec);
+ break;
+ case mmr_offset(ipend):
+ /* Read-only register. */
+ break;
+ case mmr_offset(ilat):
+ dv_w1c_4 (&cec->ilat, value, 0);
+ break;
+ case mmr_offset(iprio):
+ cec->iprio = (value & IVG_UNMASKABLE_B);
+ break;
+ }
+
+ return nr_bytes;
+}
+
+static unsigned
+bfin_cec_io_read_buffer (struct hw *me, void *dest,
+ int space, address_word addr, unsigned nr_bytes)
+{
+ struct bfin_cec *cec = hw_data (me);
+ bu32 mmr_off;
+ bu32 *valuep;
+
+ mmr_off = addr - cec->base;
+ valuep = (void *)((unsigned long)cec + mmr_base() + mmr_off);
+
+ HW_TRACE_READ ();
+
+ dv_store_4 (dest, *valuep);
+
+ return nr_bytes;
+}
+
+static const struct hw_port_descriptor bfin_cec_ports[] = {
+ { "emu", IVG_EMU, 0, input_port, },
+ { "rst", IVG_RST, 0, input_port, },
+ { "nmi", IVG_NMI, 0, input_port, },
+ { "evx", IVG_EVX, 0, input_port, },
+ { "ivhw", IVG_IVHW, 0, input_port, },
+ { "ivtmr", IVG_IVTMR, 0, input_port, },
+ { "ivg7", IVG7, 0, input_port, },
+ { "ivg8", IVG8, 0, input_port, },
+ { "ivg9", IVG9, 0, input_port, },
+ { "ivg10", IVG10, 0, input_port, },
+ { "ivg11", IVG11, 0, input_port, },
+ { "ivg12", IVG12, 0, input_port, },
+ { "ivg13", IVG13, 0, input_port, },
+ { "ivg14", IVG14, 0, input_port, },
+ { "ivg15", IVG15, 0, input_port, },
+ { NULL, 0, 0, 0, },
+};
+
+static void
+bfin_cec_port_event (struct hw *me, int my_port, struct hw *source,
+ int source_port, int level)
+{
+ struct bfin_cec *cec = hw_data (me);
+ _cec_raise (cec->cpu, cec, my_port);
+}
+
+static void
+attach_bfin_cec_regs (struct hw *me, struct bfin_cec *cec)
+{
+ address_word attach_address;
+ int attach_space;
+ unsigned attach_size;
+ reg_property_spec reg;
+
+ if (hw_find_property (me, "reg") == NULL)
+ hw_abort (me, "Missing \"reg\" property");
+
+ if (!hw_find_reg_array_property (me, "reg", 0, &reg))
+ hw_abort (me, "\"reg\" property must contain three addr/size entries");
+
+ hw_unit_address_to_attach_address (hw_parent (me),
+ &reg.address,
+ &attach_space, &attach_address, me);
+ hw_unit_size_to_attach_size (hw_parent (me), &reg.size, &attach_size, me);
+
+ if (attach_size != BFIN_COREMMR_CEC_SIZE)
+ hw_abort (me, "\"reg\" size must be %#x", BFIN_COREMMR_CEC_SIZE);
+
+ hw_attach_address (hw_parent (me),
+ 0, attach_space, attach_address, attach_size, me);
+
+ cec->base = attach_address;
+ /* XXX: should take from the device tree. */
+ cec->cpu = STATE_CPU (hw_system (me), 0);
+ cec->me = me;
+}
+
+static void
+bfin_cec_finish (struct hw *me)
+{
+ struct bfin_cec *cec;
+
+ cec = HW_ZALLOC (me, struct bfin_cec);
+
+ set_hw_data (me, cec);
+ set_hw_io_read_buffer (me, bfin_cec_io_read_buffer);
+ set_hw_io_write_buffer (me, bfin_cec_io_write_buffer);
+ set_hw_ports (me, bfin_cec_ports);
+ set_hw_port_event (me, bfin_cec_port_event);
+
+ attach_bfin_cec_regs (me, cec);
+
+ /* Initialize the CEC. */
+ cec->imask = IVG_UNMASKABLE_B;
+ cec->ipend = IVG_RST_B | IVG_IRPTEN_B;
+}
+
+const struct hw_descriptor dv_bfin_cec_descriptor[] = {
+ {"bfin_cec", bfin_cec_finish,},
+ {NULL, NULL},
+};
+
+static const char * const excp_decoded[] = {
+ [VEC_SYS ] = "Custom exception 0 (system call)",
+ [VEC_EXCPT01 ] = "Custom exception 1 (software breakpoint)",
+ [VEC_EXCPT02 ] = "Custom exception 2 (KGDB hook)",
+ [VEC_EXCPT03 ] = "Custom exception 3 (userspace stack overflow)",
+ [VEC_EXCPT04 ] = "Custom exception 4 (dump trace buffer)",
+ [VEC_EXCPT05 ] = "Custom exception 5",
+ [VEC_EXCPT06 ] = "Custom exception 6",
+ [VEC_EXCPT07 ] = "Custom exception 7",
+ [VEC_EXCPT08 ] = "Custom exception 8",
+ [VEC_EXCPT09 ] = "Custom exception 9",
+ [VEC_EXCPT10 ] = "Custom exception 10",
+ [VEC_EXCPT11 ] = "Custom exception 11",
+ [VEC_EXCPT12 ] = "Custom exception 12",
+ [VEC_EXCPT13 ] = "Custom exception 13",
+ [VEC_EXCPT14 ] = "Custom exception 14",
+ [VEC_EXCPT15 ] = "Custom exception 15",
+ [VEC_STEP ] = "Hardware single step",
+ [VEC_OVFLOW ] = "Trace buffer overflow",
+ [VEC_UNDEF_I ] = "Undefined instruction",
+ [VEC_ILGAL_I ] = "Illegal instruction combo (multi-issue)",
+ [VEC_CPLB_VL ] = "DCPLB protection violation",
+ [VEC_MISALI_D ] = "Unaligned data access",
+ [VEC_UNCOV ] = "Unrecoverable event (double fault)",
+ [VEC_CPLB_M ] = "DCPLB miss",
+ [VEC_CPLB_MHIT ] = "Multiple DCPLB hit",
+ [VEC_WATCH ] = "Watchpoint match",
+ [VEC_ISTRU_VL ] = "ADSP-BF535 only",
+ [VEC_MISALI_I ] = "Unaligned instruction access",
+ [VEC_CPLB_I_VL ] = "ICPLB protection violation",
+ [VEC_CPLB_I_M ] = "ICPLB miss",
+ [VEC_CPLB_I_MHIT] = "Multiple ICPLB hit",
+ [VEC_ILL_RES ] = "Illegal supervisor resource",
+};
+
+#define CEC_STATE(cpu) DV_STATE_CACHED (cpu, cec)
+
+#define __cec_get_ivg(val) (ffs ((val) & ~IVG_IRPTEN_B) - 1)
+#define _cec_get_ivg(cec) __cec_get_ivg ((cec)->ipend & ~IVG_EMU_B)
+
+int
+cec_get_ivg (SIM_CPU *cpu)
+{
+ switch (STATE_ENVIRONMENT (CPU_STATE (cpu)))
+ {
+ case OPERATING_ENVIRONMENT:
+ return _cec_get_ivg (CEC_STATE (cpu));
+ default:
+ return IVG_USER;
+ }
+}
+
+static bool
+_cec_is_supervisor_mode (struct bfin_cec *cec)
+{
+ return (cec->ipend & ~(IVG_EMU_B | IVG_IRPTEN_B));
+}
+bool
+cec_is_supervisor_mode (SIM_CPU *cpu)
+{
+ switch (STATE_ENVIRONMENT (CPU_STATE (cpu)))
+ {
+ case OPERATING_ENVIRONMENT:
+ return _cec_is_supervisor_mode (CEC_STATE (cpu));
+ case USER_ENVIRONMENT:
+ return false;
+ default:
+ return true;
+ }
+}
+static bool
+_cec_is_user_mode (struct bfin_cec *cec)
+{
+ return !_cec_is_supervisor_mode (cec);
+}
+bool
+cec_is_user_mode (SIM_CPU *cpu)
+{
+ return !cec_is_supervisor_mode (cpu);
+}
+static void
+_cec_require_supervisor (SIM_CPU *cpu, struct bfin_cec *cec)
+{
+ if (_cec_is_user_mode (cec))
+ cec_exception (cpu, VEC_ILL_RES);
+}
+void
+cec_require_supervisor (SIM_CPU *cpu)
+{
+ /* Do not call _cec_require_supervisor() to avoid CEC_STATE()
+ as that macro requires OS operating mode. */
+ if (cec_is_user_mode (cpu))
+ cec_exception (cpu, VEC_ILL_RES);
+}
+
+#define excp_to_sim_halt(reason, sigrc) \
+ sim_engine_halt (CPU_STATE (cpu), cpu, NULL, PCREG, reason, sigrc)
+void
+cec_exception (SIM_CPU *cpu, int excp)
+{
+ SIM_DESC sd = CPU_STATE (cpu);
+ int sigrc = -1;
+
+ TRACE_EVENTS (cpu, "processing exception %#x in EVT%i", excp,
+ cec_get_ivg (cpu));
+
+ /* Ideally what would happen here for real hardware exceptions (not
+ fake sim ones) is that:
+ - For service exceptions (excp <= 0x11):
+ RETX is the _next_ PC which can be tricky with jumps/hardware loops/...
+ - For error exceptions (excp > 0x11):
+ RETX is the _current_ PC (i.e. the one causing the exception)
+ - PC is loaded with EVT3 MMR
+ - ILAT/IPEND in CEC is updated depending on current IVG level
+ - the fault address MMRs get updated with data/instruction info
+ - Execution continues on in the EVT3 handler */
+
+ /* Handle simulator exceptions first. */
+ switch (excp)
+ {
+ case VEC_SIM_HLT:
+ excp_to_sim_halt (sim_exited, 0);
+ return;
+ case VEC_SIM_ABORT:
+ excp_to_sim_halt (sim_exited, 1);
+ return;
+ case VEC_SIM_TRAP:
+ /* GDB expects us to step over EMUEXCPT. */
+ /* XXX: What about hwloops and EMUEXCPT at the end?
+ Pretty sure gdb doesn't handle this already... */
+ SET_PCREG (PCREG + 2);
+ /* Only trap when we are running in gdb. */
+ if (STATE_OPEN_KIND (sd) == SIM_OPEN_DEBUG)
+ excp_to_sim_halt (sim_stopped, SIM_SIGTRAP);
+ return;
+ case VEC_SIM_DBGA:
+ /* If running in gdb, simply trap. */
+ if (STATE_OPEN_KIND (sd) == SIM_OPEN_DEBUG)
+ excp_to_sim_halt (sim_stopped, SIM_SIGTRAP);
+ else
+ excp_to_sim_halt (sim_exited, 2);
+ }
+
+ if (excp <= 0x3f)
+ {
+ SET_EXCAUSE (excp);
+ if (STATE_ENVIRONMENT (sd) == OPERATING_ENVIRONMENT)
+ {
+ /* ICPLB regs always get updated. */
+ /* XXX: Should optimize this call path ... */
+ if (excp != VEC_MISALI_I && excp != VEC_MISALI_D
+ && excp != VEC_CPLB_I_M && excp != VEC_CPLB_M
+ && excp != VEC_CPLB_I_VL && excp != VEC_CPLB_VL
+ && excp != VEC_CPLB_I_MHIT && excp != VEC_CPLB_MHIT)
+ mmu_log_ifault (cpu);
+ _cec_raise (cpu, CEC_STATE (cpu), IVG_EVX);
+ /* We need to restart the engine so that we don't return
+ and continue processing this bad insn. */
+ if (EXCAUSE >= 0x20)
+ sim_engine_restart (sd, cpu, NULL, PCREG);
+ return;
+ }
+ }
+
+ TRACE_EVENTS (cpu, "running virtual exception handler");
+
+ switch (excp)
+ {
+ case VEC_SYS:
+ bfin_syscall (cpu);
+ break;
+
+ case VEC_EXCPT01: /* Userspace gdb breakpoint. */
+ sigrc = SIM_SIGTRAP;
+ break;
+
+ case VEC_UNDEF_I: /* Undefined instruction. */
+ sigrc = SIM_SIGILL;
+ break;
+
+ case VEC_ILL_RES: /* Illegal supervisor resource. */
+ case VEC_MISALI_I: /* Misaligned instruction. */
+ sigrc = SIM_SIGBUS;
+ break;
+
+ case VEC_CPLB_M:
+ case VEC_CPLB_I_M:
+ sigrc = SIM_SIGSEGV;
+ break;
+
+ default:
+ sim_io_eprintf (sd, "Unhandled exception %#x at 0x%08x (%s)\n",
+ excp, PCREG, excp_decoded[excp]);
+ sigrc = SIM_SIGILL;
+ break;
+ }
+
+ if (sigrc != -1)
+ excp_to_sim_halt (sim_stopped, sigrc);
+}
+
+bu32 cec_cli (SIM_CPU *cpu)
+{
+ struct bfin_cec *cec;
+ bu32 old_mask;
+
+ if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT)
+ return 0;
+
+ cec = CEC_STATE (cpu);
+ _cec_require_supervisor (cpu, cec);
+
+ /* XXX: what about IPEND[4] ? */
+ old_mask = cec->imask;
+ _cec_imask_write (cec, 0);
+
+ TRACE_EVENTS (cpu, "CLI changed IMASK from %#x to %#x", old_mask, cec->imask);
+
+ return old_mask;
+}
+
+void cec_sti (SIM_CPU *cpu, bu32 ints)
+{
+ struct bfin_cec *cec;
+ bu32 old_mask;
+
+ if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT)
+ return;
+
+ cec = CEC_STATE (cpu);
+ _cec_require_supervisor (cpu, cec);
+
+ /* XXX: what about IPEND[4] ? */
+ old_mask = cec->imask;
+ _cec_imask_write (cec, ints);
+
+ TRACE_EVENTS (cpu, "STI changed IMASK from %#x to %#x", old_mask, cec->imask);
+
+ /* Check for pending interrupts that are now enabled. */
+ _cec_check_pending (cpu, cec);
+}
+
+static void
+cec_irpten_enable (SIM_CPU *cpu, struct bfin_cec *cec)
+{
+ /* Globally mask interrupts. */
+ TRACE_EVENTS (cpu, "setting IPEND[4] to globally mask interrupts");
+ cec->ipend |= IVG_IRPTEN_B;
+}
+
+static void
+cec_irpten_disable (SIM_CPU *cpu, struct bfin_cec *cec)
+{
+ /* Clear global interrupt mask. */
+ TRACE_EVENTS (cpu, "clearing IPEND[4] to not globally mask interrupts");
+ cec->ipend &= ~IVG_IRPTEN_B;
+}
+
+static void
+_cec_raise (SIM_CPU *cpu, struct bfin_cec *cec, int ivg)
+{
+ SIM_DESC sd = CPU_STATE (cpu);
+ int curr_ivg = _cec_get_ivg (cec);
+ bool snen;
+ bool irpten;
+
+ TRACE_EVENTS (cpu, "processing request for EVT%i while at EVT%i",
+ ivg, curr_ivg);
+
+ irpten = (cec->ipend & IVG_IRPTEN_B);
+ snen = (SYSCFGREG & SYSCFG_SNEN);
+
+ if (curr_ivg == -1)
+ curr_ivg = IVG_USER;
+
+ /* Just check for higher latched interrupts. */
+ if (ivg == -1)
+ {
+ if (irpten)
+ goto done; /* All interrupts are masked anyways. */
+
+ ivg = __cec_get_ivg (cec->ilat & cec->imask);
+ if (ivg < 0)
+ goto done; /* Nothing latched. */
+
+ if (ivg > curr_ivg)
+ goto done; /* Nothing higher latched. */
+
+ if (!snen && ivg == curr_ivg)
+ goto done; /* Self nesting disabled. */
+
+ /* Still here, so fall through to raise to higher pending. */
+ }
+
+ cec->ilat |= (1 << ivg);
+
+ if (ivg <= IVG_EVX)
+ {
+ /* These two are always processed. */
+ if (ivg == IVG_EMU || ivg == IVG_RST)
+ goto process_int;
+
+ /* Anything lower might trigger a double fault. */
+ if (curr_ivg <= ivg)
+ {
+ /* Double fault ! :( */
+ SET_EXCAUSE (VEC_UNCOV);
+ /* XXX: SET_RETXREG (...); */
+ sim_io_error (sd, "%s: double fault at 0x%08x ! :(", __func__, PCREG);
+ excp_to_sim_halt (sim_stopped, SIM_SIGABRT);
+ }
+
+ /* No double fault -> always process. */
+ goto process_int;
+ }
+ else if (irpten && curr_ivg != IVG_USER)
+ {
+ /* Interrupts are globally masked. */
+ }
+ else if (!(cec->imask & (1 << ivg)))
+ {
+ /* This interrupt is masked. */
+ }
+ else if (ivg < curr_ivg || (snen && ivg == curr_ivg))
+ {
+ /* Do transition! */
+ bu32 oldpc;
+
+ process_int:
+ cec->ipend |= (1 << ivg);
+ cec->ilat &= ~(1 << ivg);
+
+ /* Interrupts are processed in between insns which means the return
+ point is the insn-to-be-executed (which is the current PC). But
+ exceptions are handled while executing an insn, so we may have to
+ advance the PC ourselves when setting RETX.
+ XXX: Advancing the PC should only be for "service" exceptions, and
+ handling them after executing the insn should be OK, which
+ means we might be able to use the event interface for it. */
+
+ oldpc = PCREG;
+ switch (ivg)
+ {
+ case IVG_EMU:
+ /* Signal the JTAG ICE. */
+ /* XXX: what happens with 'raise 0' ? */
+ SET_RETEREG (oldpc);
+ excp_to_sim_halt (sim_stopped, SIM_SIGTRAP);
+ /* XXX: Need an easy way for gdb to signal it isnt here. */
+ cec->ipend &= ~IVG_EMU_B;
+ break;
+ case IVG_RST:
+ /* Have the core reset simply exit (i.e. "shutdown"). */
+ excp_to_sim_halt (sim_exited, 0);
+ break;
+ case IVG_NMI:
+ /* XXX: Should check this. */
+ SET_RETNREG (oldpc);
+ break;
+ case IVG_EVX:
+ /* Non-service exceptions point to the excepting instruction. */
+ if (EXCAUSE >= 0x20)
+ SET_RETXREG (oldpc);
+ else
+ {
+ bu32 nextpc = hwloop_get_next_pc (cpu, oldpc, INSN_LEN);
+ SET_RETXREG (nextpc);
+ }
+
+ break;
+ case IVG_IRPTEN:
+ /* XXX: what happens with 'raise 4' ? */
+ sim_io_error (sd, "%s: what to do with 'raise 4' ?", __func__);
+ break;
+ default:
+ SET_RETIREG (oldpc | (ivg == curr_ivg ? 1 : 0));
+ break;
+ }
+
+ /* If EVT_OVERRIDE is in effect (IVG7+), use the reset address. */
+ if ((cec->evt_override & 0xff80) & (1 << ivg))
+ SET_PCREG (cec_get_reset_evt (cpu));
+ else
+ SET_PCREG (cec_get_evt (cpu, ivg));
+
+ TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC (to EVT%i):", ivg);
+ BFIN_CPU_STATE.did_jump = true;
+
+ /* Enable the global interrupt mask upon interrupt entry. */
+ if (ivg >= IVG_IVHW)
+ cec_irpten_enable (cpu, cec);
+ }
+
+ /* When moving between states, don't let internal states bleed through. */
+ DIS_ALGN_EXPT &= ~1;
+
+ /* When going from user to super, we set LSB in LB regs to avoid
+ misbehavior and/or malicious code.
+ Also need to load SP alias with KSP. */
+ if (curr_ivg == IVG_USER)
+ {
+ int i;
+ for (i = 0; i < 2; ++i)
+ if (!(LBREG (i) & 1))
+ SET_LBREG (i, LBREG (i) | 1);
+ SET_USPREG (SPREG);
+ SET_SPREG (KSPREG);
+ }
+
+ done:
+ TRACE_EVENTS (cpu, "now at EVT%i", _cec_get_ivg (cec));
+}
+
+static bu32
+cec_read_ret_reg (SIM_CPU *cpu, int ivg)
+{
+ switch (ivg)
+ {
+ case IVG_EMU: return RETEREG;
+ case IVG_NMI: return RETNREG;
+ case IVG_EVX: return RETXREG;
+ default: return RETIREG;
+ }
+}
+
+void
+cec_latch (SIM_CPU *cpu, int ivg)
+{
+ struct bfin_cec *cec;
+
+ if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT)
+ {
+ bu32 oldpc = PCREG;
+ SET_PCREG (cec_read_ret_reg (cpu, ivg));
+ TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC");
+ return;
+ }
+
+ cec = CEC_STATE (cpu);
+ cec->ilat |= (1 << ivg);
+ _cec_check_pending (cpu, cec);
+}
+
+void
+cec_hwerr (SIM_CPU *cpu, int hwerr)
+{
+ SET_HWERRCAUSE (hwerr);
+ cec_latch (cpu, IVG_IVHW);
+}
+
+void
+cec_return (SIM_CPU *cpu, int ivg)
+{
+ SIM_DESC sd = CPU_STATE (cpu);
+ struct bfin_cec *cec;
+ bool snen;
+ int curr_ivg;
+ bu32 oldpc, newpc;
+
+ oldpc = PCREG;
+
+ BFIN_CPU_STATE.did_jump = true;
+ if (STATE_ENVIRONMENT (sd) != OPERATING_ENVIRONMENT)
+ {
+ SET_PCREG (cec_read_ret_reg (cpu, ivg));
+ TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC");
+ return;
+ }
+
+ cec = CEC_STATE (cpu);
+
+ /* XXX: This isn't entirely correct ... */
+ cec->ipend &= ~IVG_EMU_B;
+
+ curr_ivg = _cec_get_ivg (cec);
+ if (curr_ivg == -1)
+ curr_ivg = IVG_USER;
+ if (ivg == -1)
+ ivg = curr_ivg;
+
+ TRACE_EVENTS (cpu, "returning from EVT%i (should be EVT%i)", curr_ivg, ivg);
+
+ /* Not allowed to return from usermode. */
+ if (curr_ivg == IVG_USER)
+ cec_exception (cpu, VEC_ILL_RES);
+
+ if (ivg > IVG15 || ivg < 0)
+ sim_io_error (sd, "%s: ivg %i out of range !", __func__, ivg);
+
+ _cec_require_supervisor (cpu, cec);
+
+ switch (ivg)
+ {
+ case IVG_EMU:
+ /* RTE -- only valid in emulation mode. */
+ /* XXX: What does the hardware do ? */
+ if (curr_ivg != IVG_EMU)
+ cec_exception (cpu, VEC_ILL_RES);
+ break;
+ case IVG_NMI:
+ /* RTN -- only valid in NMI. */
+ /* XXX: What does the hardware do ? */
+ if (curr_ivg != IVG_NMI)
+ cec_exception (cpu, VEC_ILL_RES);
+ break;
+ case IVG_EVX:
+ /* RTX -- only valid in exception. */
+ /* XXX: What does the hardware do ? */
+ if (curr_ivg != IVG_EVX)
+ cec_exception (cpu, VEC_ILL_RES);
+ break;
+ default:
+ /* RTI -- not valid in emulation, nmi, exception, or user. */
+ /* XXX: What does the hardware do ? */
+ if (curr_ivg == IVG_EMU || curr_ivg == IVG_NMI
+ || curr_ivg == IVG_EVX || curr_ivg == IVG_USER)
+ cec_exception (cpu, VEC_ILL_RES);
+ break;
+ case IVG_IRPTEN:
+ /* XXX: Is this even possible ? */
+ excp_to_sim_halt (sim_stopped, SIM_SIGABRT);
+ break;
+ }
+ newpc = cec_read_ret_reg (cpu, ivg);
+
+ /* XXX: Does this nested trick work on EMU/NMI/EVX ? */
+ snen = (newpc & 1);
+ /* XXX: Delayed clear shows bad PCREG register trace above ? */
+ SET_PCREG (newpc & ~1);
+
+ TRACE_BRANCH (cpu, oldpc, PCREG, -1, "CEC changed PC (from EVT%i)", ivg);
+
+ /* Update ipend after the TRACE_BRANCH so dv-bfin_trace
+ knows current CEC state wrt overflow. */
+ if (!snen)
+ cec->ipend &= ~(1 << ivg);
+
+ /* Disable global interrupt mask to let any interrupt take over, but
+ only when we were already in a RTI level. Only way we could have
+ raised at that point is if it was cleared in the first place. */
+ if (ivg >= IVG_IVHW || ivg == IVG_RST)
+ cec_irpten_disable (cpu, cec);
+
+ /* When going from super to user, we clear LSB in LB regs in case
+ it was set on the transition up.
+ Also need to load SP alias with USP. */
+ if (_cec_get_ivg (cec) == -1)
+ {
+ int i;
+ for (i = 0; i < 2; ++i)
+ if (LBREG (i) & 1)
+ SET_LBREG (i, LBREG (i) & ~1);
+ SET_KSPREG (SPREG);
+ SET_SPREG (USPREG);
+ }
+
+ /* Check for pending interrupts before we return to usermode. */
+ _cec_check_pending (cpu, cec);
+}
+
+void
+cec_push_reti (SIM_CPU *cpu)
+{
+ /* XXX: Need to check hardware with popped RETI value
+ and bit 1 is set (when handling nested interrupts).
+ Also need to check behavior wrt SNEN in SYSCFG. */
+ struct bfin_cec *cec;
+
+ if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT)
+ return;
+
+ TRACE_EVENTS (cpu, "pushing RETI");
+
+ cec = CEC_STATE (cpu);
+ cec_irpten_disable (cpu, cec);
+ /* Check for pending interrupts. */
+ _cec_check_pending (cpu, cec);
+}
+
+void
+cec_pop_reti (SIM_CPU *cpu)
+{
+ /* XXX: Need to check hardware with popped RETI value
+ and bit 1 is set (when handling nested interrupts).
+ Also need to check behavior wrt SNEN in SYSCFG. */
+ struct bfin_cec *cec;
+
+ if (STATE_ENVIRONMENT (CPU_STATE (cpu)) != OPERATING_ENVIRONMENT)
+ return;
+
+ TRACE_EVENTS (cpu, "popping RETI");
+
+ cec = CEC_STATE (cpu);
+ cec_irpten_enable (cpu, cec);
+}