diff --git a/include/plat/spike/plat/instance/hifive/hardware.h b/include/plat/spike/plat/instance/hifive/hardware.h index cb038b842..b469efea2 100644 --- a/include/plat/spike/plat/instance/hifive/hardware.h +++ b/include/plat/spike/plat/instance/hifive/hardware.h @@ -14,6 +14,7 @@ #define __PLAT_INSTANCE_HARDWARE_H #define PLIC_MAX_NUM_INT 53 +#define IRQ_CNODE_SLOT_BITS 6 #define PLIC_BASE 0x0C000000 #define PLIC_PPTR_BASE 0xFFFFFFFFCC000000 diff --git a/include/plat/spike/plat/instance/qemu/hardware.h b/include/plat/spike/plat/instance/qemu/hardware.h index 175f60756..496e54574 100644 --- a/include/plat/spike/plat/instance/qemu/hardware.h +++ b/include/plat/spike/plat/instance/qemu/hardware.h @@ -14,6 +14,7 @@ #define __PLAT_INSTANCE_HARDWARE_H #define PLIC_MAX_NUM_INT 0 +#define IRQ_CNODE_SLOT_BITS 1 static inline interrupt_t plic_get_claim(void) { diff --git a/include/plat/spike/plat/instance/rocket-chip/hardware.h b/include/plat/spike/plat/instance/rocket-chip/hardware.h index 97d0f8ef4..45cdf3649 100644 --- a/include/plat/spike/plat/instance/rocket-chip/hardware.h +++ b/include/plat/spike/plat/instance/rocket-chip/hardware.h @@ -14,6 +14,7 @@ #define __PLAT_INSTANCE_HARDWARE_H #define PLIC_MAX_NUM_INT 0 +#define IRQ_CNODE_SLOT_BITS 1 static inline interrupt_t plic_get_claim(void) { diff --git a/include/plat/spike/plat/machine.h b/include/plat/spike/plat/machine.h index 2ac2dc4ca..61eec3bbe 100644 --- a/include/plat/spike/plat/machine.h +++ b/include/plat/spike/plat/machine.h @@ -56,7 +56,7 @@ typedef uint32_t interrupt_t; typedef uint32_t irq_t; enum irqNumbers { - irqInvalid = 6 + irqInvalid = 0 }; #if defined(CONFIG_BUILD_SPIKE_QEMU) @@ -69,15 +69,28 @@ enum irqNumbers { #error "Unsupported spike platform chosen" #endif +/* + * seL4 assigns all IRQs global interrupt numbers that are used in interrupt + * invocations. On RISC-V we have 3 different types of interrupts: core timer, + * core software generated, and global external IRQs delivered through the PLIC. + * Only global external interrupts are available from user level and so it is + * nice to be able to match PLIC IRQ numbers to seL4 IRQ numbers. The PLIC uses + * IRQ 0 to refer to no IRQ pending and so we can also use 0 for irqInvalid in + * the global IRQ number space and not have any aliasing issues. We then place + * the kernel timer interrupts after the last PLIC interrupt and intend on + * placing software generated interrupts after this in the future. As the kernel + * timer and SGI interrupts are never seen outside of the kernel, it doesn't + * matter what number they get assigned to as we can refer to them by their enum + * field name. + */ enum IRQConstants { - INTERRUPT_SW = 0, - INTERRUPT_TIMER = 5, - /* TODO: Handle PLIC and add external IRQs upon needed */ - maxIRQ = 5 + PLIC_IRQ_OFFSET = 0, + PLIC_MAX_IRQ = PLIC_IRQ_OFFSET + PLIC_MAX_NUM_INT, + INTERRUPT_CORE_TIMER, + maxIRQ = INTERRUPT_CORE_TIMER, } platform_interrupt_t; -#define KERNEL_TIMER_IRQ INTERRUPT_TIMER -#define IRQ_CNODE_SLOT_BITS 3 +#define KERNEL_TIMER_IRQ INTERRUPT_CORE_TIMER #endif /* !__ASSEMBLER__ */ #endif diff --git a/include/plat/spike/plat/machine/hardware.h b/include/plat/spike/plat/machine/hardware.h index 952bdb9e9..ae8c13698 100644 --- a/include/plat/spike/plat/machine/hardware.h +++ b/include/plat/spike/plat/machine/hardware.h @@ -66,19 +66,14 @@ int get_num_dev_p_regs(void); p_region_t get_dev_p_reg(word_t i); void map_kernel_devices(void); -bool_t CONST isReservedIRQ(irq_t irq); void ackInterrupt(irq_t irq); bool_t isIRQPending(void); -/** MODIFIES: [*] */ void maskInterrupt(bool_t enable, irq_t irq); -/** MODIFIES: */ irq_t getActiveIRQ(void); -/** MODIFIES: [*] */ static inline void setInterruptMode(irq_t irq, bool_t levelTrigger, bool_t polarityLow) { } /** MODIFIES: [*] */ void initTimer(void); /* L2 cache control */ -/** MODIFIES: [*] */ void initL2Cache(void); void initIRQController(void); diff --git a/src/arch/riscv/kernel/boot.c b/src/arch/riscv/kernel/boot.c index f8ef18a36..71f49c345 100644 --- a/src/arch/riscv/kernel/boot.c +++ b/src/arch/riscv/kernel/boot.c @@ -108,7 +108,10 @@ BOOT_CODE static void init_irqs(cap_t root_cnode_cap) irq_t i; for (i = 0; i <= maxIRQ; i++) { - setIRQState(IRQInactive, i); + if (i != irqInvalid) { + /* IRQ 0 is irqInvalid */ + setIRQState(IRQInactive, i); + } } setIRQState(IRQTimer, KERNEL_TIMER_IRQ); diff --git a/src/plat/spike/machine/hardware.c b/src/plat/spike/machine/hardware.c index 99422051c..9debe2ca2 100644 --- a/src/plat/spike/machine/hardware.c +++ b/src/plat/spike/machine/hardware.c @@ -39,6 +39,7 @@ #define RESET_CYCLES ((CONFIG_SPIKE_CLOCK_FREQ / MS_IN_S) * CONFIG_TIMER_TICK_MS) +#define IS_IRQ_VALID(X) (((X)) <= maxIRQ && (X)!= irqInvalid) BOOT_CODE int get_num_avail_p_regs(void) { @@ -85,63 +86,156 @@ BOOT_CODE void map_kernel_devices(void) } } -/** - DONT_TRANSLATE +/* + * The following assumes familiarity with RISC-V interrupt delivery and the PLIC. + * See the RISC-V privileged specifivation v1.10 and the comment in + * include/plat/spike/plat/machine.h for more information. + * RISC-V IRQ handling on seL4 works as follows: + * + * On other architectures the kernel masks interrupts between delivering them to + * userlevel and receiving the acknowledgement invocation. This strategy doesn't + * work on RISC-V as an IRQ is implicitly masked when it is claimed, until the + * claim is acknowledged. If we mask and unmask the interrupt at the PLIC while + * a claim is in progress we sometimes experience IRQ sources not being masked + * and unmasked as expected. Because of this, we don't mask and unmask IRQs that + * are for user level, and also call plic_complete_claim for seL4_IRQHandler_Ack. */ -interrupt_t getActiveIRQ(void) + +/** + * Gets the new active irq from the PLIC or STIP. + * + * getNewActiveIRQ is only called by getActiveIRQ and checks for a pending IRQ. + * We read sip and if the SEIP bit is set we claim an + * IRQ from the PLIC. If STIP is set then it is a kernel timer interrupt. + * Otherwise we return IRQ invalid. It is possible to reveive irqInvalid from + * the PLIC if another HART context has claimed the IRQ before us. This function + * is not idempotent as plic_get_claim is called which accepts an IRQ message + * from the PLIC and will claim different IRQs if called subsequent times. + * + * @return The new active irq. + */ +static irq_t getNewActiveIRQ(void) { - uint64_t temp = 0; - asm volatile("csrr %0, scause":"=r"(temp)); + uint64_t sip = read_sip(); - if (!(temp & BIT(CONFIG_WORD_SIZE - 1))) { + if (sip & BIT(STIMER_IP)) { + // Supervisor timer interrupt + return INTERRUPT_CORE_TIMER; + } else if (BIT(SEXTERNAL_IP)) { + /* External IRQ */ + return plic_get_claim(); + } else { return irqInvalid; } - - return (temp & 0xf); } -/* Check for pending IRQ */ +static uint32_t active_irq = irqInvalid; + + +/** + * Gets the active irq. Returns the same irq if called again before ackInterrupt. + * + * getActiveIRQ is used to return a currently pending IRQ. This function can be + * called multiple times and needs to return the same IRQ until ackInterrupt is + * called. getActiveIRQ returns irqInvalid if no interrupt is pending. It is + * assumed that if isIRQPending is true, then getActiveIRQ will not return + * irqInvalid. getActiveIRQ will call getNewActiveIRQ and cache its result until + * ackInterrupt is called. + * + * @return The active irq. + */ +irq_t getActiveIRQ(void) +{ + + uint32_t irq; + if (!IS_IRQ_VALID(active_irq)) { + active_irq = getNewActiveIRQ(); + } + + if (IS_IRQ_VALID(active_irq)) { + irq = active_irq; + } else { + irq = irqInvalid; + } + + return irq; +} + +#ifdef HAVE_SET_TRIGGER +/** + * Sets the irq trigger. + * + * setIRQTrigger can change the trigger between edge and level at the PLIC for + * external interrupts. It is implementation specific as whether the PLIC has + * support for this operation. + * + * @param[in] irq The irq + * @param[in] edge_triggered edge triggered otherwise level triggered + */ +void setIRQTrigger(irq_t irq, bool_t edge_triggered) +{ + plic_irq_set_trigger(irq, edge_triggered); +} +#endif + +/* isIRQPending is used to determine whether to preempt long running + * operations at various preemption points throughout the kernel. If this + * returns true, it means that if the Kernel were to return to user mode, it + * would then immediately take an interrupt. We check the SIP register for if + * either a timer interrupt (STIP) or an external interrupt (SEIP) is pending. + * We don't check software generated interrupts. These are used to perform cross + * core signalling which isn't currently supported. + * TODO: Add SSIP check when SMP support is added. + */ bool_t isIRQPending(void) { word_t sip = read_sip(); return (sip & (BIT(STIMER_IP) | BIT(SEXTERNAL_IP))); } -/* Enable or disable irq according to the 'disable' flag. */ /** - DONT_TRANSLATE -*/ + * Disable or enable IRQs. + * + * maskInterrupt disables and enables IRQs. When an IRQ is disabled, it should + * not raise an interrupt on the Kernel's HART context. This either masks the + * core timer on the sie register or masks an external IRQ at the plic. + * + * @param[in] disable The disable + * @param[in] irq The irq + */ void maskInterrupt(bool_t disable, interrupt_t irq) { - if (disable) { - if (irq > 1) { - clear_sie_mask(BIT(irq)); + assert(IS_IRQ_VALID(irq)); + if (irq == INTERRUPT_CORE_TIMER) { + if (disable) { + clear_sie_mask(BIT(STIMER_IE)); + } else { + set_sie_mask(BIT(STIMER_IE)); } } else { - /* FIXME: currently only account for user/supervisor and timer interrupts */ - if (irq == 4 /* user timer */ || irq == 5 /* supervisor timer */) { - set_sie_mask(BIT(irq)); - } else { - /* TODO: account for external and PLIC interrupts */ - } + plic_mask_irq(disable, irq); } } -/* Determine if the given IRQ should be reserved by the kernel. */ -bool_t CONST isReservedIRQ(irq_t irq) -{ - printf("isReservedIRQ \n"); - return false; -} - +/** + * Kernel has dealt with the pending interrupt getActiveIRQ can return next IRQ. + * + * ackInterrupt is used by the kernel to indicate it has processed the interrupt + * delivery and getActiveIRQ is now able to return a different IRQ number. Note + * that this is called after a notification has been signalled to user level, + * but before user level has handled the cause. + * + * @param[in] irq The irq + */ void ackInterrupt(irq_t irq) { - // don't ack the kernel timer interrupt, see the comment in resetTimer - // to understand why + assert(IS_IRQ_VALID(irq)); + active_irq = irqInvalid; - if (irq == 1) { - sbi_clear_ipi(); + if (irq == INTERRUPT_CORE_TIMER) { + /* Reprogramming the timer has cleared the interrupt. */ + return; } } @@ -201,19 +295,16 @@ void plat_cleanInvalidateL2Range(paddr_t start, paddr_t end) { } -/** - DONT_TRANSLATE - */ BOOT_CODE void initL2Cache(void) { } -/** - DONT_TRANSLATE - */ BOOT_CODE void initIRQController(void) { - /* Do nothing */ + printf("Initialing PLIC...\n"); + + plic_init_controller(); + set_sie_mask(BIT(9)); } void handleSpuriousIRQ(void)