diff mbox series

[08/11,v2] target/riscv: Update interrupt handling in CLIC mode

Message ID 20240819160742.27586-12-Ian.Brockbank@cirrus.com (mailing list archive)
State New, archived
Headers show
Series RISC-V: support CLIC v0.9 specification | expand

Commit Message

Ian Brockbank Aug. 19, 2024, 4:02 p.m. UTC
From: Ian Brockbank <ian.brockbank@cirrus.com>

Decode CLIC interrupt information from exccode, includes interrupt
privilege mode, interrupt level, and irq number.

Then update CSRs xcause, xstatus, xepc, xintstatus and jump to
correct PC according to the CLIC specification.

Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
Signed-off-by: Ian Brockbank <ian.brockbank@cirrus.com>
---
 target/riscv/cpu_helper.c | 129 +++++++++++++++++++++++++++++++++++---
 1 file changed, 119 insertions(+), 10 deletions(-)

--
2.46.0.windows.1
This message and any attachments may contain privileged and confidential information that is intended solely for the person(s) to whom it is addressed. If you are not an intended recipient you must not: read; copy; distribute; discuss; take any action in or make any reliance upon the contents of this message; nor open or read any attachment. If you have received this message in error, please notify us as soon as possible on the following telephone number and destroy this message including any attachments. Thank you. Cirrus Logic International (UK) Ltd and Cirrus Logic International Semiconductor Ltd are companies registered in Scotland, with registered numbers SC089839 and SC495735 respectively. Our registered office is at 7B Nightingale Way, Quartermile, Edinburgh, EH3 9EG, UK. Tel: +44 (0)131 272 7000. www.cirrus.com

Comments

Alistair Francis Sept. 6, 2024, 3:49 a.m. UTC | #1
On Tue, Aug 20, 2024 at 2:14 AM Ian Brockbank <Ian.Brockbank@cirrus.com> wrote:
>
> From: Ian Brockbank <ian.brockbank@cirrus.com>
>
> Decode CLIC interrupt information from exccode, includes interrupt
> privilege mode, interrupt level, and irq number.
>
> Then update CSRs xcause, xstatus, xepc, xintstatus and jump to
> correct PC according to the CLIC specification.
>
> Signed-off-by: LIU Zhiwei <zhiwei_liu@c-sky.com>
> Signed-off-by: Ian Brockbank <ian.brockbank@cirrus.com>
> ---
>  target/riscv/cpu_helper.c | 129 +++++++++++++++++++++++++++++++++++---
>  1 file changed, 119 insertions(+), 10 deletions(-)
>
> diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
> index 395a1d9140..944afb68d2 100644
> --- a/target/riscv/cpu_helper.c
> +++ b/target/riscv/cpu_helper.c
> @@ -24,6 +24,7 @@
>  #include "internals.h"
>  #include "pmu.h"
>  #include "exec/exec-all.h"
> +#include "exec/cpu_ldst.h"
>  #include "exec/page-protection.h"
>  #include "instmap.h"
>  #include "tcg/tcg-op.h"
> @@ -33,6 +34,7 @@
>  #include "cpu_bits.h"
>  #include "debug.h"
>  #include "tcg/oversized-guest.h"
> +#include "hw/intc/riscv_clic.h"
>
>  int riscv_env_mmu_index(CPURISCVState *env, bool ifetch)
>  {
> @@ -428,6 +430,20 @@ int riscv_cpu_vsirq_pending(CPURISCVState *env)
>                                      (irqs | irqs_f_vs), env->hviprio);
>  }
>
> +static int riscv_cpu_local_irq_mode_enabled(CPURISCVState *env, int mode)
> +{
> +    switch (mode) {
> +    case PRV_M:
> +        return env->priv < PRV_M ||
> +            (env->priv == PRV_M && get_field(env->mstatus, MSTATUS_MIE));
> +    case PRV_S:
> +        return env->priv < PRV_S ||
> +            (env->priv == PRV_S && get_field(env->mstatus, MSTATUS_SIE));
> +    default:
> +        return false;
> +    }
> +}
> +
>  static int riscv_cpu_local_irq_pending(CPURISCVState *env)
>  {
>      uint64_t irqs, pending, mie, hsie, vsie, irqs_f, irqs_f_vs;
> @@ -506,6 +522,18 @@ bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
>              return true;
>          }
>      }
> +    if (interrupt_request & CPU_INTERRUPT_CLIC) {
> +        RISCVCPU *cpu = RISCV_CPU(cs);
> +        CPURISCVState *env = &cpu->env;
> +        int mode = get_field(env->exccode, RISCV_EXCP_CLIC_MODE);
> +        int enabled = riscv_cpu_local_irq_mode_enabled(env, mode);
> +        if (enabled) {
> +            cs->exception_index = RISCV_EXCP_CLIC | env->exccode;
> +            cs->interrupt_request = cs->interrupt_request & ~CPU_INTERRUPT_CLIC;
> +            riscv_cpu_do_interrupt(cs);
> +            return true;
> +        }
> +    }
>      return false;
>  }
>
> @@ -1641,6 +1669,60 @@ static target_ulong riscv_transformed_insn(CPURISCVState *env,
>      return xinsn;
>  }
>
> +static target_ulong riscv_intr_pc(CPURISCVState *env, target_ulong tvec,
> +                                  target_ulong tvt, bool async,
> +                                  int cause, int mode)
> +{
> +    int mode1 = tvec & XTVEC_MODE;
> +    int mode2 = tvec & XTVEC_FULL_MODE;
> +

This is going to need extension checks

> +    if (!async) {
> +        return tvec & XTVEC_OBASE;
> +    }
> +    /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */
> +    switch (mode1) {
> +    case XTVEC_CLINT_DIRECT:
> +        return tvec & XTVEC_OBASE;
> +    case XTVEC_CLINT_VECTORED:
> +        return (tvec & XTVEC_OBASE) + cause * 4;
> +    default:
> +        if (env->clic && (mode2 == XTVEC_CLIC)) {
> +            /* Non-vectored, clicintattr[i].shv = 0 || cliccfg.nvbits = 0 */
> +            if (!riscv_clic_shv_interrupt(env->clic, cause)) {
> +                /* NBASE = mtvec[XLEN-1:6]<<6 */
> +                return tvec & XTVEC_NBASE;
> +            } else {
> +                /*
> +                 * pc := M[TBASE + XLEN/8 * exccode)] & ~1,
> +                 * TBASE = mtvt[XLEN-1:6]<<6
> +                 */
> +                int size = TARGET_LONG_BITS / 8;
> +                target_ulong tbase = (tvt & XTVEC_NBASE) + size * cause;
> +                void *host = tlb_vaddr_to_host(env, tbase, MMU_DATA_LOAD, mode);

This doesn't look right.

I think you want cpu_ l*_mmuidx_ra(). That will raise an exception on
an access failure

> +                if (host != NULL) {
> +                    target_ulong new_pc = tbase;
> +                    if (!riscv_clic_use_jump_table(env->clic)) {
> +                        /*
> +                         * Standard CLIC: the vector entry is a function pointer
> +                         * so look up the destination.
> +                         */
> +                        new_pc = ldn_p(host, size);
> +                        host = tlb_vaddr_to_host(env, new_pc,
> +                                                 MMU_INST_FETCH, mode);

At xtvt is the base address of a table of pointers, you also then want
to call cpu_ l*_mmuidx_ra() a second time and that value should be
returned as the next PC

> +                    }
> +                    if (host) {
> +                        return new_pc;
> +                    }
> +                }
> +                qemu_log_mask(LOG_GUEST_ERROR,
> +                              "CLIC: load trap handler error!\n");
> +                exit(1);

Don't allow the guest to exit. Print a guest error and keep going

Alistair
diff mbox series

Patch

diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 395a1d9140..944afb68d2 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -24,6 +24,7 @@ 
 #include "internals.h"
 #include "pmu.h"
 #include "exec/exec-all.h"
+#include "exec/cpu_ldst.h"
 #include "exec/page-protection.h"
 #include "instmap.h"
 #include "tcg/tcg-op.h"
@@ -33,6 +34,7 @@ 
 #include "cpu_bits.h"
 #include "debug.h"
 #include "tcg/oversized-guest.h"
+#include "hw/intc/riscv_clic.h"

 int riscv_env_mmu_index(CPURISCVState *env, bool ifetch)
 {
@@ -428,6 +430,20 @@  int riscv_cpu_vsirq_pending(CPURISCVState *env)
                                     (irqs | irqs_f_vs), env->hviprio);
 }

+static int riscv_cpu_local_irq_mode_enabled(CPURISCVState *env, int mode)
+{
+    switch (mode) {
+    case PRV_M:
+        return env->priv < PRV_M ||
+            (env->priv == PRV_M && get_field(env->mstatus, MSTATUS_MIE));
+    case PRV_S:
+        return env->priv < PRV_S ||
+            (env->priv == PRV_S && get_field(env->mstatus, MSTATUS_SIE));
+    default:
+        return false;
+    }
+}
+
 static int riscv_cpu_local_irq_pending(CPURISCVState *env)
 {
     uint64_t irqs, pending, mie, hsie, vsie, irqs_f, irqs_f_vs;
@@ -506,6 +522,18 @@  bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
             return true;
         }
     }
+    if (interrupt_request & CPU_INTERRUPT_CLIC) {
+        RISCVCPU *cpu = RISCV_CPU(cs);
+        CPURISCVState *env = &cpu->env;
+        int mode = get_field(env->exccode, RISCV_EXCP_CLIC_MODE);
+        int enabled = riscv_cpu_local_irq_mode_enabled(env, mode);
+        if (enabled) {
+            cs->exception_index = RISCV_EXCP_CLIC | env->exccode;
+            cs->interrupt_request = cs->interrupt_request & ~CPU_INTERRUPT_CLIC;
+            riscv_cpu_do_interrupt(cs);
+            return true;
+        }
+    }
     return false;
 }

@@ -1641,6 +1669,60 @@  static target_ulong riscv_transformed_insn(CPURISCVState *env,
     return xinsn;
 }

+static target_ulong riscv_intr_pc(CPURISCVState *env, target_ulong tvec,
+                                  target_ulong tvt, bool async,
+                                  int cause, int mode)
+{
+    int mode1 = tvec & XTVEC_MODE;
+    int mode2 = tvec & XTVEC_FULL_MODE;
+
+    if (!async) {
+        return tvec & XTVEC_OBASE;
+    }
+    /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */
+    switch (mode1) {
+    case XTVEC_CLINT_DIRECT:
+        return tvec & XTVEC_OBASE;
+    case XTVEC_CLINT_VECTORED:
+        return (tvec & XTVEC_OBASE) + cause * 4;
+    default:
+        if (env->clic && (mode2 == XTVEC_CLIC)) {
+            /* Non-vectored, clicintattr[i].shv = 0 || cliccfg.nvbits = 0 */
+            if (!riscv_clic_shv_interrupt(env->clic, cause)) {
+                /* NBASE = mtvec[XLEN-1:6]<<6 */
+                return tvec & XTVEC_NBASE;
+            } else {
+                /*
+                 * pc := M[TBASE + XLEN/8 * exccode)] & ~1,
+                 * TBASE = mtvt[XLEN-1:6]<<6
+                 */
+                int size = TARGET_LONG_BITS / 8;
+                target_ulong tbase = (tvt & XTVEC_NBASE) + size * cause;
+                void *host = tlb_vaddr_to_host(env, tbase, MMU_DATA_LOAD, mode);
+                if (host != NULL) {
+                    target_ulong new_pc = tbase;
+                    if (!riscv_clic_use_jump_table(env->clic)) {
+                        /*
+                         * Standard CLIC: the vector entry is a function pointer
+                         * so look up the destination.
+                         */
+                        new_pc = ldn_p(host, size);
+                        host = tlb_vaddr_to_host(env, new_pc,
+                                                 MMU_INST_FETCH, mode);
+                    }
+                    if (host) {
+                        return new_pc;
+                    }
+                }
+                qemu_log_mask(LOG_GUEST_ERROR,
+                              "CLIC: load trap handler error!\n");
+                exit(1);
+            }
+        }
+        g_assert_not_reached();
+    }
+}
+
 /*
  * Handle Traps
  *
@@ -1654,12 +1736,14 @@  void riscv_cpu_do_interrupt(CPUState *cs)
     bool virt = env->virt_enabled;
     bool write_gva = false;
     uint64_t s;
+    int mode, level, irq;

     /*
      * cs->exception is 32-bits wide unlike mcause which is XLEN-bits wide
      * so we mask off the MSB and separate into trap type and cause.
      */
-    bool async = !!(cs->exception_index & RISCV_EXCP_INT_FLAG);
+    bool clic = !!(cs->exception_index & RISCV_EXCP_CLIC);
+    bool async = !!(cs->exception_index & RISCV_EXCP_INT_FLAG) || clic;
     target_ulong cause = cs->exception_index & RISCV_EXCP_INT_MASK;
     uint64_t deleg = async ? env->mideleg : env->medeleg;
     bool s_injected = env->mvip & (1 << cause) & env->mvien &&
@@ -1746,6 +1830,28 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         }
     }

+    if (clic) {
+        riscv_clic_decode_exccode(cause, &mode, &level, &irq);
+        cause = irq | get_field(env->mstatus, MSTATUS_MPP) << XCAUSE_XPP_SHIFT;
+        switch (mode) {
+        case PRV_M:
+            cause |= get_field(env->mintstatus, MINTSTATUS_MIL)
+                     << XCAUSE_XPIL_SHIFT;
+            cause |= get_field(env->mstatus, MSTATUS_MIE) << XCAUSE_XPIE_SHIFT;
+            env->mintstatus = set_field(env->mintstatus, MINTSTATUS_MIL, level);
+            break;
+        case PRV_S:
+            cause |= get_field(env->mintstatus, MINTSTATUS_SIL)
+                     << XCAUSE_XPIL_SHIFT;
+            cause |= get_field(env->mstatus, MSTATUS_SPIE) << XCAUSE_XPIE_SHIFT;
+            env->mintstatus = set_field(env->mintstatus, MINTSTATUS_SIL, level);
+            break;
+        }
+    } else {
+        mode = env->priv <= PRV_S && cause < 64 &&
+            ((deleg >> cause) & 1 || s_injected || vs_injected) ? PRV_S : PRV_M;
+    }
+
     trace_riscv_trap(env->mhartid, async, cause, env->pc, tval,
                      riscv_cpu_get_trap_name(cause, async));

@@ -1755,8 +1861,7 @@  void riscv_cpu_do_interrupt(CPUState *cs)
                   __func__, env->mhartid, async, cause, env->pc, tval,
                   riscv_cpu_get_trap_name(cause, async));

-    if (env->priv <= PRV_S && cause < 64 &&
-        (((deleg >> cause) & 1) || s_injected || vs_injected)) {
+    if (PRV_S == mode) {
         /* handle the trap in S-mode */
         if (riscv_has_ext(env, RVH)) {
             uint64_t hdeleg = async ? env->hideleg : env->hedeleg;
@@ -1765,7 +1870,7 @@  void riscv_cpu_do_interrupt(CPUState *cs)
                 (((hdeleg >> cause) & 1) || vs_injected)) {
                 /* Trap to VS mode */
                 /*
-                 * See if we need to adjust cause. Yes if its VS mode interrupt
+                 * See if we need to adjust cause. Yes if it's VS mode interrupt
                  * no if hypervisor has delegated one of hs mode's interrupt
                  */
                 if (async && (cause == IRQ_VS_TIMER || cause == IRQ_VS_SOFT ||
@@ -1796,13 +1901,15 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         s = set_field(s, MSTATUS_SPP, env->priv);
         s = set_field(s, MSTATUS_SIE, 0);
         env->mstatus = s;
-        env->scause = cause | ((target_ulong)async << (TARGET_LONG_BITS - 1));
+        if (async) {
+            env->scause = cause | SCAUSE_INT;
+        }
         env->sepc = env->pc;
         env->stval = tval;
         env->htval = htval;
         env->htinst = tinst;
-        env->pc = (env->stvec >> 2 << 2) +
-                  ((async && (env->stvec & 3) == 1) ? cause * 4 : 0);
+        env->pc = riscv_intr_pc(env, env->stvec, env->stvt, async,
+                                cause & SCAUSE_EXCCODE, PRV_S);
         riscv_cpu_set_mode(env, PRV_S, virt);
     } else {
         /* handle the trap in M-mode */
@@ -1827,13 +1934,15 @@  void riscv_cpu_do_interrupt(CPUState *cs)
         s = set_field(s, MSTATUS_MPP, env->priv);
         s = set_field(s, MSTATUS_MIE, 0);
         env->mstatus = s;
-        env->mcause = cause | ~(((target_ulong)-1) >> async);
+        if (async) {
+            env->mcause = cause | MCAUSE_INT;
+        }
         env->mepc = env->pc;
         env->mtval = tval;
         env->mtval2 = mtval2;
         env->mtinst = tinst;
-        env->pc = (env->mtvec >> 2 << 2) +
-                  ((async && (env->mtvec & 3) == 1) ? cause * 4 : 0);
+        env->pc = riscv_intr_pc(env, env->mtvec, env->mtvt, async,
+                                cause & MCAUSE_EXCCODE, PRV_M);
         riscv_cpu_set_mode(env, PRV_M, virt);
     }