Implement IRQ software support for RISC-V.
authorSergiusz Bazanski <q3k@q3k.org>
Mon, 22 Jan 2018 18:31:18 +0000 (18:31 +0000)
committerSergiusz Bazanski <q3k@q3k.org>
Mon, 22 Jan 2018 18:50:26 +0000 (18:50 +0000)
Well, at least PicoRV32-specific. Turns out there is no RISC-V
specification for simple microcontroller-like interrupts, so PicoRV32
implements its' own based on custom opcodes.

It's somewhat esoteric, and for example doesn't offer a global interrupt
enable/disable. For this we implement a thin wrapper in assembly and
then expose it via a few helpers in irq.h.

litex/soc/software/include/base/irq.h
litex/soc/software/libbase/crt0-riscv32.S

index 331d6e6f2c89c2247c0814c846aebb8c216d5357..a042384c28bcea4004dab6c35c84f8ec51a69797 100644 (file)
@@ -5,6 +5,27 @@
 extern "C" {
 #endif
 
+#ifdef __riscv
+// PicoRV32 has a very limited interrupt support, implemented via custom
+// instructions. It also doesn't have a global interrupt enable/disable, so
+// we have to emulate it via saving and restoring a mask and using 0/~1 as a
+// hardware mask.
+// Due to all this somewhat low-level mess, all of the glue is implementein
+// the RiscV crt0, and this header is kept as a thin wrapper. Since interrupts
+// managed by this layer, do not call interrupt instructions directly, as the
+// state will go out of sync with the hardware.
+
+// Read only.
+extern unsigned int _irq_pending;
+// Read only.
+extern unsigned int _irq_mask;
+// Read only.
+extern unsigned int _irq_enabled;
+extern void _irq_enable(void);
+extern void _irq_disable(void);
+extern void _irq_setmask(unsigned int);
+#endif
+
 #ifdef __or1k__
 #include <system.h>
 #endif
@@ -17,9 +38,8 @@ static inline unsigned int irq_getie(void)
        return ie;
 #elif defined (__or1k__)
        return !!(mfspr(SPR_SR) & SPR_SR_IEE);
-#elif defined (__riscv__)
-       /* FIXME */
-       return 0;
+#elif defined (__riscv)
+       return _irq_enabled != 0;
 #else
 #error Unsupported architecture
 #endif
@@ -34,9 +54,11 @@ static inline void irq_setie(unsigned int ie)
                mtspr(SPR_SR, mfspr(SPR_SR) | SPR_SR_IEE);
        else
                mtspr(SPR_SR, mfspr(SPR_SR) & ~SPR_SR_IEE);
-#elif defined (__riscv__)
-       /* FIXME */
-       return 0;
+#elif defined (__riscv)
+    if (ie & 0x1)
+        _irq_enable();
+    else
+        _irq_disable();
 #else
 #error Unsupported architecture
 #endif
@@ -50,9 +72,10 @@ static inline unsigned int irq_getmask(void)
        return mask;
 #elif defined (__or1k__)
        return mfspr(SPR_PICMR);
-#elif defined (__riscv__)
-       /* FIXME */
-       return 0;
+#elif defined (__riscv)
+    // PicoRV32 interrupt mask bits are high-disabled. This is the inverse of how
+    // LiteX sees things.
+    return ~_irq_mask;
 #else
 #error Unsupported architecture
 #endif
@@ -64,9 +87,10 @@ static inline void irq_setmask(unsigned int mask)
        __asm__ __volatile__("wcsr IM, %0" : : "r" (mask));
 #elif defined (__or1k__)
        mtspr(SPR_PICMR, mask);
-#elif defined (__riscv__)
-       /* FIXME */
-       return 0;
+#elif defined (__riscv)
+    // PicoRV32 interrupt mask bits are high-disabled. This is the inverse of how
+    // LiteX sees things.
+    _irq_setmask(~mask);
 #else
 #error Unsupported architecture
 #endif
@@ -80,9 +104,8 @@ static inline unsigned int irq_pending(void)
        return pending;
 #elif defined (__or1k__)
        return mfspr(SPR_PICSR);
-#elif defined (__riscv__)
-       /* FIXME */
-       return 0;
+#elif defined (__riscv)
+       return _irq_pending;
 #else
 #error Unsupported architecture
 #endif
index 4b7a392221dee17931ad1179d26905d455b892e9..1d6faa851f0ef5b4725b85ef6ba853121bc01568 100644 (file)
@@ -1,5 +1,145 @@
+/*
+ * Copyright 2018, Serge Bazanski <serge@bazanski.pl>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ */
+
+#include "picorv32-extraops.S"
+
+/*
+ * Interrupt vector.
+ */
 .global _start
 _start:
+
+.org 0x00000000 # Reset
+  j _crt0
+
+.org 0x00000010 # IRQ
+_irq_vector:
+  j _irq
+
+
+/*
+ * IRQ handler, branched to from the vector.
+ */
+_irq:
+  /* save x1/x2 to q1/q2 */
+  picorv32_setq_insn(q2, x1)
+  picorv32_setq_insn(q3, x2)
+
+  /* use x1 to index into irq_regs */
+  lui x1, %hi(irq_regs)
+  addi x1, x1, %lo(irq_regs)
+
+  /* use x2 as scratch space for saving registers */
+
+  /* q0 (== x1), q2(== x2), q3 */
+  picorv32_getq_insn(x2, q0)
+  sw x2,   0*4(x1)
+  picorv32_getq_insn(x2, q2)
+  sw x2,   1*4(x1)
+  picorv32_getq_insn(x2, q3)
+  sw x2,   2*4(x1)
+
+  /* save x3 - x31 */
+  sw x3,   3*4(x1)
+  sw x4,   4*4(x1)
+  sw x5,   5*4(x1)
+  sw x6,   6*4(x1)
+  sw x7,   7*4(x1)
+  sw x8,   8*4(x1)
+  sw x9,   9*4(x1)
+  sw x10, 10*4(x1)
+  sw x11, 11*4(x1)
+  sw x12, 12*4(x1)
+  sw x13, 13*4(x1)
+  sw x14, 14*4(x1)
+  sw x15, 15*4(x1)
+  sw x16, 16*4(x1)
+  sw x17, 17*4(x1)
+  sw x18, 18*4(x1)
+  sw x19, 19*4(x1)
+  sw x20, 20*4(x1)
+  sw x21, 21*4(x1)
+  sw x22, 22*4(x1)
+  sw x23, 23*4(x1)
+  sw x24, 24*4(x1)
+  sw x25, 25*4(x1)
+  sw x26, 26*4(x1)
+  sw x27, 27*4(x1)
+  sw x28, 28*4(x1)
+  sw x29, 29*4(x1)
+  sw x30, 30*4(x1)
+  sw x31, 31*4(x1)
+
+  /* update _irq_pending to the currently pending interrupts */
+  picorv32_getq_insn(t0, q1)
+  la t1, (_irq_pending)
+  sw t0, 0(t1)
+
+  /* prepare C handler stack */
+  lui sp, %hi(_irq_stack)
+  addi sp, sp, %lo(_irq_stack)
+
+  /* call C handler */
+  jal ra, isr
+
+  /* use x1 to index into irq_regs */
+  lui x1, %hi(irq_regs)
+  addi x1, x1, %lo(irq_regs)
+
+  /* restore q0 - q2 */
+  lw x2,   0*4(x1)
+  picorv32_setq_insn(q0, x2)
+  lw x2,   1*4(x1)
+  picorv32_setq_insn(q1, x2)
+  lw x2,   2*4(x1)
+  picorv32_setq_insn(q2, x2)
+
+  /* restore x3 - x31 */
+  lw x3,   3*4(x1)
+  lw x4,   4*4(x1)
+  lw x5,   5*4(x1)
+  lw x6,   6*4(x1)
+  lw x7,   7*4(x1)
+  lw x8,   8*4(x1)
+  lw x9,   9*4(x1)
+  lw x10, 10*4(x1)
+  lw x11, 11*4(x1)
+  lw x12, 12*4(x1)
+  lw x13, 13*4(x1)
+  lw x14, 14*4(x1)
+  lw x15, 15*4(x1)
+  lw x16, 16*4(x1)
+  lw x17, 17*4(x1)
+  lw x18, 18*4(x1)
+  lw x19, 19*4(x1)
+  lw x20, 20*4(x1)
+  lw x21, 21*4(x1)
+  lw x22, 22*4(x1)
+  lw x23, 23*4(x1)
+  lw x24, 24*4(x1)
+  lw x25, 25*4(x1)
+  lw x26, 26*4(x1)
+  lw x27, 27*4(x1)
+  lw x28, 28*4(x1)
+  lw x29, 29*4(x1)
+  lw x30, 30*4(x1)
+  lw x31, 31*4(x1)
+
+  /* restore x1 - x2 from q registers */
+  picorv32_getq_insn(x1, q1)
+  picorv32_getq_insn(x2, q2)
+
+  /* return from interrupt */
+  picorv32_retirq_insn()
+
+/*
+ * Reset handler, branched to from the vector.
+ */
+_crt0:
   /* zero-initialize all registers */
   addi x1, zero, 0
   addi x2, zero, 0
@@ -33,5 +173,103 @@ _start:
   addi x30, zero, 0
   addi x31, zero, 0
 
+  /* mask all interrupts */
+  li t0, 0xffffffff
+  picorv32_maskirq_insn(zero, t0)
+  /* reflect that in _irq_mask */
+  la t1, _irq_mask
+  sw t0, 0(t1)
+
+  /* set main stack */
+  la sp, _fstack
+
   /* jump to main */
   jal ra, main
+
+1:
+  /* loop forever */
+  j 1b
+
+
+/*
+ * Enable interrupts by copying the software mask to the hardware mask
+ */
+.global _irq_enable
+_irq_enable:
+  /* Set _irq_enabled to true */
+  la t0, _irq_enabled
+  addi t1, zero, 1
+  sw t1, 0(t0)
+  /* Set the HW IRQ mask to _irq_mask */
+  la t0, _irq_mask
+  lw t0, 0(t0)
+  picorv32_maskirq_insn(zero, t0)
+  ret
+
+/* 
+ * Disable interrupts by masking all interrupts (the mask should already be
+ * up to date)
+ */
+.global _irq_disable
+_irq_disable:
+  /* Mask all IRQs */
+  li t0, 0xffffffff
+  picorv32_maskirq_insn(zero, t0)
+  /* Set _irq_enabled to false */
+  la t0, _irq_enabled
+  sw zero, (t0)
+  ret
+
+/*
+ * Set interrrupt mask.
+ * This updates the software mask (for readback and interrupt inable/disable)
+ * and the hardware mask.
+ * 1 means interrupt is masked (disabled).
+ */
+.global _irq_setmask
+_irq_setmask:
+  /* Update _irq_mask */
+  la t0, _irq_mask
+  sw a0, (t0)
+  /* Are interrupts enabled? */
+  la t0, _irq_enabled
+  lw t0, 0(t0)
+  beq t0, zero, 1f
+  /* If so, update the HW IRQ mask */
+  picorv32_maskirq_insn(zero, a0)
+1:
+  ret
+  
+
+.section .bss
+irq_regs:
+  /* saved interrupt registers, x0 - x31 */
+  .fill 32,4
+
+  /* interrupt stack */
+  .fill 256,4
+_irq_stack:
+
+/*
+ * Bitfield of pending interrupts, updated on ISR entry.
+ */
+.global _irq_pending
+_irq_pending:
+  .word 0
+
+/*
+ * Software copy of enabled interrupts. Do not write directly, use
+ * _irq_set_mask instead.
+ */
+.global _irq_mask
+_irq_mask:
+  .word 0
+
+/*
+ * Software state of global interrupts being enabled or disabled. Do not write
+ * directly, use _irq_disable / _irq_enable instead.
+ */
+.global _irq_enabled
+_irq_enabled:
+  .word 0
+