diff mbox series

[v4,29/33] target/nios2: Implement EIC interrupt processing

Message ID 20220308072005.307955-30-richard.henderson@linaro.org
State New
Headers show
Series target/nios2: Shadow register set, EIC and VIC | expand

Commit Message

Richard Henderson March 8, 2022, 7:20 a.m. UTC
This is the cpu side of the operation.  Register one irq line,
called EIC.  Split out the rather different processing to a
separate function.

Delay initialization of gpio irqs until realize.  We need to
provide a window after init in which the board can set eic_present.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
---
 target/nios2/cpu.h    |  8 +++++
 target/nios2/cpu.c    | 75 +++++++++++++++++++++++++++++++++----------
 target/nios2/helper.c | 37 +++++++++++++++++++++
 3 files changed, 103 insertions(+), 17 deletions(-)
diff mbox series

Patch

diff --git a/target/nios2/cpu.h b/target/nios2/cpu.h
index 13e1d49f38..89c575c26d 100644
--- a/target/nios2/cpu.h
+++ b/target/nios2/cpu.h
@@ -115,6 +115,7 @@  FIELD(CR_STATUS, CRS, 10, 6)
 FIELD(CR_STATUS, PRS, 16, 6)
 FIELD(CR_STATUS, NMI, 22, 1)
 FIELD(CR_STATUS, RSIE, 23, 1)
+FIELD(CR_STATUS, SRS, 31, 1)
 
 #define CR_STATUS_PIE  (1u << R_CR_STATUS_PIE_SHIFT)
 #define CR_STATUS_U    (1u << R_CR_STATUS_U_SHIFT)
@@ -122,6 +123,7 @@  FIELD(CR_STATUS, RSIE, 23, 1)
 #define CR_STATUS_IH   (1u << R_CR_STATUS_IH_SHIFT)
 #define CR_STATUS_NMI  (1u << R_CR_STATUS_NMI_SHIFT)
 #define CR_STATUS_RSIE (1u << R_CR_STATUS_RSIE_SHIFT)
+#define CR_STATUS_SRS  (1u << R_CR_STATUS_SRS_SHIFT)
 
 FIELD(CR_EXCEPTION, CAUSE, 2, 5)
 FIELD(CR_EXCEPTION, ECCFTL, 31, 1)
@@ -252,6 +254,12 @@  struct Nios2CPU {
 
     /* Bits within each control register which are reserved or readonly. */
     ControlRegState cr_state[NUM_CR_REGS];
+
+    /* External Interrupt Controller Interface */
+    uint32_t rha; /* Requested handler address */
+    uint32_t ril; /* Requested interrupt level */
+    uint32_t rrs; /* Requested register set */
+    bool rnmi;    /* Requested nonmaskable interrupt */
 };
 
 
diff --git a/target/nios2/cpu.c b/target/nios2/cpu.c
index 6ece92a2b8..65a900a7fb 100644
--- a/target/nios2/cpu.c
+++ b/target/nios2/cpu.c
@@ -67,7 +67,19 @@  static void nios2_cpu_reset(DeviceState *dev)
 }
 
 #ifndef CONFIG_USER_ONLY
-static void nios2_cpu_set_irq(void *opaque, int irq, int level)
+static void eic_set_irq(void *opaque, int irq, int level)
+{
+    Nios2CPU *cpu = opaque;
+    CPUState *cs = CPU(cpu);
+
+    if (level) {
+        cpu_interrupt(cs, CPU_INTERRUPT_HARD);
+    } else {
+        cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
+    }
+}
+
+static void iic_set_irq(void *opaque, int irq, int level)
 {
     Nios2CPU *cpu = opaque;
     CPUNios2State *env = &cpu->env;
@@ -149,15 +161,6 @@  static void nios2_cpu_initfn(Object *obj)
 
 #if !defined(CONFIG_USER_ONLY)
     mmu_init(&cpu->env);
-
-    /*
-     * These interrupt lines model the IIC (internal interrupt
-     * controller). QEMU does not currently support the EIC
-     * (external interrupt controller) -- if we did it would be
-     * a separate device in hw/intc with a custom interface to
-     * the CPU, and boards using it would not wire up these IRQ lines.
-     */
-    qdev_init_gpio_in_named(DEVICE(cpu), nios2_cpu_set_irq, "IRQ", 32);
 #endif
 }
 
@@ -173,6 +176,14 @@  static void nios2_cpu_realizefn(DeviceState *dev, Error **errp)
     Nios2CPUClass *ncc = NIOS2_CPU_GET_CLASS(dev);
     Error *local_err = NULL;
 
+#ifndef CONFIG_USER_ONLY
+    if (cpu->eic_present) {
+        qdev_init_gpio_in_named(DEVICE(cpu), eic_set_irq, "EIC", 1);
+    } else {
+        qdev_init_gpio_in_named(DEVICE(cpu), iic_set_irq, "IRQ", 32);
+    }
+#endif
+
     cpu_exec_realizefn(cs, &local_err);
     if (local_err != NULL) {
         error_propagate(errp, local_err);
@@ -189,17 +200,47 @@  static void nios2_cpu_realizefn(DeviceState *dev, Error **errp)
 }
 
 #ifndef CONFIG_USER_ONLY
+static bool eic_take_interrupt(Nios2CPU *cpu)
+{
+    CPUNios2State *env = &cpu->env;
+
+    if (cpu->rnmi) {
+        return !(env->status & CR_STATUS_NMI);
+    }
+    if (!(env->status & CR_STATUS_PIE)) {
+        return false;
+    }
+    if (cpu->ril <= FIELD_EX32(env->status, CR_STATUS, IL)) {
+        return false;
+    }
+    if (cpu->rrs != FIELD_EX32(env->status, CR_STATUS, CRS)) {
+        return true;
+    }
+    return env->status & CR_STATUS_RSIE;
+}
+
+static bool iic_take_interrupt(Nios2CPU *cpu)
+{
+    CPUNios2State *env = &cpu->env;
+
+    if (!(env->status & CR_STATUS_PIE)) {
+        return false;
+    }
+    return env->ipending & env->ienable;
+}
+
 static bool nios2_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
 {
     Nios2CPU *cpu = NIOS2_CPU(cs);
-    CPUNios2State *env = &cpu->env;
 
-    if ((interrupt_request & CPU_INTERRUPT_HARD) &&
-        (env->status & CR_STATUS_PIE) &&
-        (env->ipending & env->ienable)) {
-        cs->exception_index = EXCP_IRQ;
-        nios2_cpu_do_interrupt(cs);
-        return true;
+    if (interrupt_request & CPU_INTERRUPT_HARD) {
+        if (cpu->eic_present
+            ? eic_take_interrupt(cpu)
+            : iic_take_interrupt(cpu)) {
+            cs->exception_index = EXCP_IRQ;
+            nios2_cpu_do_interrupt(cs);
+            return true;
+        }
     }
     return false;
 }
diff --git a/target/nios2/helper.c b/target/nios2/helper.c
index ccf2634c9b..11840496f7 100644
--- a/target/nios2/helper.c
+++ b/target/nios2/helper.c
@@ -49,6 +49,36 @@  void nios2_cpu_record_sigsegv(CPUState *cs, vaddr addr,
 
 #else /* !CONFIG_USER_ONLY */
 
+static void eic_do_interrupt(Nios2CPU *cpu)
+{
+    CPUNios2State *env = &cpu->env;
+    uint32_t old_status = env->status;
+    uint32_t old_rs = FIELD_EX32(old_status, CR_STATUS, CRS);
+    uint32_t new_rs = cpu->rrs;
+
+    env->status = FIELD_DP32(env->status, CR_STATUS, CRS, new_rs);
+    env->status = FIELD_DP32(env->status, CR_STATUS, IL, cpu->ril);
+    env->status = FIELD_DP32(env->status, CR_STATUS, NMI, cpu->rnmi);
+    env->status &= ~(CR_STATUS_RSIE | CR_STATUS_U);
+    env->status |= CR_STATUS_IH;
+    nios2_update_crs(env);
+
+    if (!(env->status & CR_STATUS_EH)) {
+        env->status = FIELD_DP32(env->status, CR_STATUS, PRS, old_rs);
+        if (new_rs == 0) {
+            env->estatus = old_status;
+        } else {
+            if (new_rs != old_rs) {
+                old_status |= CR_STATUS_SRS;
+            }
+            env->crs[R_SSTATUS] = old_status;
+        }
+        env->crs[R_EA] = env->pc + 4;
+    }
+
+    env->pc = cpu->rha;
+}
+
 void nios2_cpu_do_interrupt(CPUState *cs)
 {
     Nios2CPU *cpu = NIOS2_CPU(cs);
@@ -60,6 +90,10 @@  void nios2_cpu_do_interrupt(CPUState *cs)
     switch (cs->exception_index) {
     case EXCP_IRQ:
         qemu_log_mask(CPU_LOG_INT, "interrupt at pc=%x\n", env->pc);
+        if (cpu->eic_present) {
+            eic_do_interrupt(cpu);
+            return;
+        }
         break;
 
     case EXCP_TLBD:
@@ -113,6 +147,9 @@  void nios2_cpu_do_interrupt(CPUState *cs)
      * Finish Internal Interrupt or Noninterrupt Exception.
      */
 
+    env->status &= ~R_CR_STATUS_CRS_MASK;
+    nios2_update_crs(env);
+
     if (!(env->status & CR_STATUS_EH)) {
         env->ctrl[cr_estatus] = env->status;
         env->crs[r_ea] = env->pc + 4;