summaryrefslogtreecommitdiffstats
path: root/bsps/powerpc/ss555/start/irq.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/powerpc/ss555/start/irq.c')
-rw-r--r--bsps/powerpc/ss555/start/irq.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/bsps/powerpc/ss555/start/irq.c b/bsps/powerpc/ss555/start/irq.c
new file mode 100644
index 0000000000..8bb3cd240e
--- /dev/null
+++ b/bsps/powerpc/ss555/start/irq.c
@@ -0,0 +1,463 @@
+/*
+ * This file contains the implementation of the function described in irq.h
+ */
+
+/*
+ * MPC5xx port sponsored by Defence Research and Development Canada - Suffield
+ * Copyright (C) 2004, Real-Time Systems Inc. (querbach@realtime.bc.ca)
+ *
+ * Derived from libbsp/powerpc/mbx8xx/irq/irq.c:
+ *
+ * Copyright (C) 1998, 1999 valette@crf.canon.fr
+ *
+ * The license and distribution terms for this file may be
+ * found in the file LICENSE in this distribution or at
+ * http://www.rtems.org/license/LICENSE.
+ */
+
+#include <rtems.h>
+#include <mpc5xx.h>
+#include <libcpu/vectors.h>
+#include <libcpu/raw_exception.h>
+#include <libcpu/irq.h>
+#include <bsp/irq.h>
+
+/*
+ * Convert an rtems_irq_number constant to an interrupt level
+ * suitable for programming into an I/O device's interrupt level field.
+ */
+int CPU_irq_level_from_symbolic_name(const rtems_irq_number name)
+{
+ if (CPU_USIU_EXT_IRQ_0 <= name && name <= CPU_USIU_INT_IRQ_7)
+ return (name - CPU_USIU_EXT_IRQ_0) / 2;
+
+ if (CPU_UIMB_IRQ_8 <= name && name <= CPU_UIMB_IRQ_31)
+ return 8 + (name - CPU_UIMB_IRQ_8);
+
+ return 31; /* reasonable default */
+}
+
+/*
+ * default handler connected on each irq after bsp initialization
+ */
+static rtems_irq_connect_data default_rtems_entry;
+
+/*
+ * location used to store initial tables used for interrupt
+ * management.
+ */
+static rtems_irq_global_settings* internal_config;
+static rtems_irq_connect_data* rtems_hdl_tbl;
+
+/*
+ * Check if symbolic IRQ name is an USIU IRQ
+ */
+static inline int is_usiu_irq(const rtems_irq_number irqLine)
+{
+ return (((int) irqLine <= CPU_USIU_IRQ_MAX_OFFSET) &&
+ ((int) irqLine >= CPU_USIU_IRQ_MIN_OFFSET)
+ );
+}
+
+/*
+ * Check if symbolic IRQ name is an UIMB IRQ
+ */
+static inline int is_uimb_irq(const rtems_irq_number irqLine)
+{
+ return (((int) irqLine <= CPU_UIMB_IRQ_MAX_OFFSET) &&
+ ((int) irqLine >= CPU_UIMB_IRQ_MIN_OFFSET)
+ );
+}
+
+/*
+ * Check if symbolic IRQ name is a Processor IRQ
+ */
+static inline int is_proc_irq(const rtems_irq_number irqLine)
+{
+ return (((int) irqLine <= CPU_PROC_IRQ_MAX_OFFSET) &&
+ ((int) irqLine >= CPU_PROC_IRQ_MIN_OFFSET)
+ );
+}
+
+
+/*
+ * Masks used to mask off the interrupts. For exmaple, for ILVL2, the
+ * mask is used to mask off interrupts ILVL2, IRQ3, ILVL3, ... IRQ7
+ * and ILVL7.
+ *
+ */
+const static unsigned int USIU_IvectMask[CPU_USIU_IRQ_COUNT] =
+{
+ 0, /* external IRQ 0 */
+ 0xFFFFFFFF << 31, /* internal level 0 */
+ 0xFFFFFFFF << 30, /* external IRQ 1 */
+ 0xFFFFFFFF << 29, /* internal level 1 */
+ 0xFFFFFFFF << 28, /* external IRQ 2 */
+ 0xFFFFFFFF << 27, /* internal level 2 */
+ 0xFFFFFFFF << 26, /* external IRQ 3 */
+ 0xFFFFFFFF << 25, /* internal level 3 */
+ 0xFFFFFFFF << 24, /* external IRQ 4 */
+ 0xFFFFFFFF << 23, /* internal level 4 */
+ 0xFFFFFFFF << 22, /* external IRQ 5 */
+ 0xFFFFFFFF << 21, /* internal level 5 */
+ 0xFFFFFFFF << 20, /* external IRQ 6 */
+ 0xFFFFFFFF << 19, /* internal level 6 */
+ 0xFFFFFFFF << 18, /* external IRQ 7 */
+ 0xFFFFFFFF << 17 /* internal level 7 */
+};
+
+
+/*
+ * ------------------------ RTEMS Irq helper functions ----------------
+ */
+
+/*
+ * Caution : this function assumes the variable "internal_config"
+ * is already set and that the tables it contains are still valid
+ * and accessible.
+ */
+static void compute_USIU_IvectMask_from_prio (void)
+{
+ /*
+ * In theory this is feasible. No time to code it yet. See i386/shared/irq.c
+ * for an example based on 8259 controller mask. The actual masks defined
+ * correspond to the priorities defined for the USIU in irq_init.c.
+ */
+}
+
+/*
+ * This function check that the value given for the irq line
+ * is valid.
+ */
+static int isValidInterrupt(int irq)
+{
+ if ( (irq < CPU_MIN_OFFSET) || (irq > CPU_MAX_OFFSET)
+ || (irq == CPU_UIMB_INTERRUPT) )
+ return 0;
+ return 1;
+}
+
+static int CPU_irq_enable_at_uimb(const rtems_irq_number irqLine)
+{
+ if (!is_uimb_irq(irqLine))
+ return 1;
+ return 0;
+}
+
+static int CPU_irq_disable_at_uimb(const rtems_irq_number irqLine)
+{
+ if (!is_uimb_irq(irqLine))
+ return 1;
+ return 0;
+}
+
+static int CPU_irq_enable_at_usiu(const rtems_irq_number irqLine)
+{
+ int usiu_irq_index;
+
+ if (!is_usiu_irq(irqLine))
+ return 1;
+
+ usiu_irq_index = ((int) (irqLine) - CPU_USIU_IRQ_MIN_OFFSET);
+ ppc_cached_irq_mask |= (1 << (31-usiu_irq_index));
+ usiu.simask = ppc_cached_irq_mask;
+
+ return 0;
+}
+
+static int CPU_irq_disable_at_usiu(const rtems_irq_number irqLine)
+{
+ int usiu_irq_index;
+
+ if (!is_usiu_irq(irqLine))
+ return 1;
+
+ usiu_irq_index = ((int) (irqLine) - CPU_USIU_IRQ_MIN_OFFSET);
+ ppc_cached_irq_mask &= ~(1 << (31-usiu_irq_index));
+ usiu.simask = ppc_cached_irq_mask;
+
+ return 0;
+}
+
+/*
+ * --------------- RTEMS Single Irq Handler Mngt Routines ----------------
+ */
+
+int CPU_install_rtems_irq_handler (const rtems_irq_connect_data* irq)
+{
+ rtems_interrupt_level level;
+
+ if (!isValidInterrupt(irq->name)) {
+ return 0;
+ }
+ /*
+ * Check if default handler is actually connected. If not issue an error.
+ * You must first get the current handler via CPU_get_current_idt_entry
+ * and then disconnect it using CPU_delete_idt_entry.
+ * RATIONALE : to always have the same transition by forcing the user
+ * to get the previous handler before accepting to disconnect.
+ */
+ if (rtems_hdl_tbl[irq->name].hdl != default_rtems_entry.hdl) {
+ return 0;
+ }
+
+ rtems_interrupt_disable(level);
+
+ /*
+ * store the data provided by user
+ */
+ rtems_hdl_tbl[irq->name] = *irq;
+
+ if (is_uimb_irq(irq->name)) {
+ /*
+ * Enable interrupt at UIMB level
+ */
+ CPU_irq_enable_at_uimb (irq->name);
+ }
+
+ if (is_usiu_irq(irq->name)) {
+ /*
+ * Enable interrupt at USIU level
+ */
+ CPU_irq_enable_at_usiu (irq->name);
+ }
+
+ if (is_proc_irq(irq->name)) {
+ /*
+ * Should Enable exception at processor level but not needed. Will restore
+ * EE flags at the end of the routine anyway.
+ */
+ }
+ /*
+ * Enable interrupt on device
+ */
+ if (irq->on)
+ irq->on(irq);
+
+ rtems_interrupt_enable(level);
+
+ return 1;
+}
+
+
+int CPU_get_current_rtems_irq_handler (rtems_irq_connect_data* irq)
+{
+ if (!isValidInterrupt(irq->name)) {
+ return 0;
+ }
+ *irq = rtems_hdl_tbl[irq->name];
+ return 1;
+}
+
+int CPU_remove_rtems_irq_handler (const rtems_irq_connect_data* irq)
+{
+ rtems_interrupt_level level;
+
+ if (!isValidInterrupt(irq->name)) {
+ return 0;
+ }
+ /*
+ * Check if default handler is actually connected. If not issue an error.
+ * You must first get the current handler via CPU_get_current_idt_entry
+ * and then disconnect it using CPU_delete_idt_entry.
+ * RATIONALE : to always have the same transition by forcing the user
+ * to get the previous handler before accepting to disconnect.
+ */
+ if (rtems_hdl_tbl[irq->name].hdl != irq->hdl) {
+ return 0;
+ }
+ rtems_interrupt_disable(level);
+
+ /*
+ * Disable interrupt on device
+ */
+ if (irq->off)
+ irq->off(irq);
+
+ if (is_uimb_irq(irq->name)) {
+ /*
+ * disable interrupt at UIMB level
+ */
+ CPU_irq_disable_at_uimb (irq->name);
+ }
+ if (is_usiu_irq(irq->name)) {
+ /*
+ * disable interrupt at USIU level
+ */
+ CPU_irq_disable_at_usiu (irq->name);
+ }
+ if (is_proc_irq(irq->name)) {
+ /*
+ * disable exception at processor level
+ */
+ }
+
+ /*
+ * restore the default irq value
+ */
+ rtems_hdl_tbl[irq->name] = default_rtems_entry;
+
+ rtems_interrupt_enable(level);
+
+ return 1;
+}
+
+/*
+ * ---------------- RTEMS Global Irq Handler Mngt Routines ----------------
+ */
+
+int CPU_rtems_irq_mngt_set (rtems_irq_global_settings* config)
+{
+ int i;
+ rtems_interrupt_level level;
+
+ /*
+ * Store various code accelerators
+ */
+ internal_config = config;
+ default_rtems_entry = config->defaultEntry;
+ rtems_hdl_tbl = config->irqHdlTbl;
+
+ rtems_interrupt_disable(level);
+
+ /*
+ * Start with UIMB IRQ
+ */
+ for (i = CPU_UIMB_IRQ_MIN_OFFSET; i <= CPU_UIMB_IRQ_MAX_OFFSET ; i++) {
+ if (rtems_hdl_tbl[i].hdl != default_rtems_entry.hdl) {
+ CPU_irq_enable_at_uimb (i);
+ if (rtems_hdl_tbl[i].on)
+ rtems_hdl_tbl[i].on(&rtems_hdl_tbl[i]);
+ }
+ else {
+ if (rtems_hdl_tbl[i].off)
+ rtems_hdl_tbl[i].off(&rtems_hdl_tbl[i]);
+ CPU_irq_disable_at_uimb (i);
+ }
+ }
+
+ /*
+ * Continue with USIU IRQ
+ * Set up internal tables used by rtems interrupt prologue
+ */
+ compute_USIU_IvectMask_from_prio ();
+
+ for (i = CPU_USIU_IRQ_MIN_OFFSET; i <= CPU_USIU_IRQ_MAX_OFFSET ; i++) {
+ if (rtems_hdl_tbl[i].hdl != default_rtems_entry.hdl) {
+ CPU_irq_enable_at_usiu (i);
+ if (rtems_hdl_tbl[i].on)
+ rtems_hdl_tbl[i].on(&rtems_hdl_tbl[i]);
+ }
+ else {
+ if (rtems_hdl_tbl[i].off)
+ rtems_hdl_tbl[i].off(&rtems_hdl_tbl[i]);
+ CPU_irq_disable_at_usiu (i);
+ }
+ }
+
+ /*
+ * Enable all UIMB interrupt lines, then enable at USIU.
+ */
+ imb.uimb.umcr |= UIMB_UMCR_IRQMUX(3);
+ CPU_irq_enable_at_usiu (CPU_UIMB_INTERRUPT);
+
+ /*
+ * finish with Processor exceptions handled like IRQ
+ */
+ for (i = CPU_PROC_IRQ_MIN_OFFSET; i <= CPU_PROC_IRQ_MAX_OFFSET; i++) {
+ if (rtems_hdl_tbl[i].hdl != default_rtems_entry.hdl) {
+ if (rtems_hdl_tbl[i].on)
+ rtems_hdl_tbl[i].on(&rtems_hdl_tbl[i]);
+ }
+ else {
+ if (rtems_hdl_tbl[i].off)
+ rtems_hdl_tbl[i].off(&rtems_hdl_tbl[i]);
+ }
+ }
+ rtems_interrupt_enable(level);
+ return 1;
+}
+
+int CPU_rtems_irq_mngt_get(rtems_irq_global_settings** config)
+{
+ *config = internal_config;
+ return 0;
+}
+
+
+/*
+ * High level IRQ handler called from shared_raw_irq_code_entry
+ */
+void C_dispatch_irq_handler (MPC5XX_Interrupt_frame *frame, unsigned int excNum)
+{
+ register unsigned int irq;
+ register unsigned uimbIntr; /* boolean */
+ register unsigned oldMask; /* old siu pic masks */
+ register unsigned msr;
+ register unsigned new_msr;
+
+ /*
+ * Handle decrementer interrupt
+ */
+ if (excNum == ASM_DEC_VECTOR) {
+ _CPU_MSR_GET(msr);
+ new_msr = msr | MSR_EE;
+ _CPU_MSR_SET(new_msr);
+
+ rtems_hdl_tbl[CPU_DECREMENTER].hdl(rtems_hdl_tbl[CPU_DECREMENTER].handle);
+
+ _CPU_MSR_SET(msr);
+ return;
+ }
+
+ /*
+ * Handle external interrupt generated by USIU on PPC core
+ */
+ while ((ppc_cached_irq_mask & usiu.sipend) != 0) {
+ irq = (usiu.sivec >> 26);
+ uimbIntr = (irq == CPU_UIMB_INTERRUPT);
+ /*
+ * Disable the interrupt of the same and lower priority.
+ */
+ oldMask = ppc_cached_irq_mask;
+ ppc_cached_irq_mask = oldMask & USIU_IvectMask[irq];
+ usiu.simask = ppc_cached_irq_mask;
+ /*
+ * Acknowledge current interrupt. This has no effect on internal level
+ * interrupts.
+ */
+ usiu.sipend = (1 << (31 - irq));
+
+ if (uimbIntr) {
+ /*
+ * Look at the bits set in the UIMB interrupt-pending register. The
+ * highest-order set bit indicates the handler we will run.
+ *
+ * Unfortunately, we can't easily mask individual UIMB interrupts
+ * unless they use USIU levels 0 to 6, so we must mask all low-level
+ * (level > 7) UIMB interrupts while we service any interrupt.
+ */
+ int uipend = imb.uimb.uipend << 8;
+
+ if (uipend == 0) { /* spurious interrupt? use last vector */
+ irq = CPU_UIMB_IRQ_MAX_OFFSET;
+ }
+ else {
+ irq = CPU_UIMB_IRQ_MIN_OFFSET;
+ for ( ; (uipend & 0x8000000) == 0; uipend <<= 1) {
+ irq++;
+ }
+ }
+ }
+ _CPU_MSR_GET(msr);
+ new_msr = msr | MSR_EE;
+ _CPU_MSR_SET(new_msr);
+
+ rtems_hdl_tbl[irq].hdl(rtems_hdl_tbl[irq].handle);
+
+ _CPU_MSR_SET(msr);
+
+ ppc_cached_irq_mask = oldMask;
+ usiu.simask = ppc_cached_irq_mask;
+ }
+}