From 6daf3eabc5e3596857fb789b3b82c7c8d663b241 Mon Sep 17 00:00:00 2001 From: Sergiusz Bazanski Date: Mon, 22 Jan 2018 18:31:18 +0000 Subject: [PATCH] Implement IRQ software support for RISC-V. 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 | 53 +++-- litex/soc/software/libbase/crt0-riscv32.S | 238 ++++++++++++++++++++++ 2 files changed, 276 insertions(+), 15 deletions(-) diff --git a/litex/soc/software/include/base/irq.h b/litex/soc/software/include/base/irq.h index 331d6e6f2..a042384c2 100644 --- a/litex/soc/software/include/base/irq.h +++ b/litex/soc/software/include/base/irq.h @@ -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 #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 diff --git a/litex/soc/software/libbase/crt0-riscv32.S b/litex/soc/software/libbase/crt0-riscv32.S index 4b7a39222..1d6faa851 100644 --- a/litex/soc/software/libbase/crt0-riscv32.S +++ b/litex/soc/software/libbase/crt0-riscv32.S @@ -1,5 +1,145 @@ +/* + * Copyright 2018, Serge Bazanski + * + * 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 +