.. _interrupt: Interrupt Handling in Nuclei processor core =========================================== .. warning:: For latest version of Nuclei ISA Spec since 2022, please get it from AE of Nuclei System Technology. This online version is out of date. Interrupt Overview ------------------ Interrupt, that is, the core is suddenly interrupted by other requests during the execution of the current program, and the current program is stopped, and then the core turns to handle other requests. After handling other requests, the core goes back and continues to execute the previous program. The key points of interrupts are the followings: - The "other request" interrupts the processor core is called Interrupt Request. The source of this request is called the Interrupt Source. The interrupt source is usually comes from outside the core which is called the External Interrupt Source, but some of the interrupt sources are core-internal, which are called the Internal Interrupt Sources. - The program used to handle the "other request" is called the Interrupt Service Routine (ISR). - Interrupt mechanism is a normal mechanism, not an error situation. Once the core receives an interrupt request, it needs to save the context of the current execution status, which is referred as "context saving". After processing the request, the core needs to restore the previous status, referred to "context restoring", thereby continuing to execute the previously interrupted program. - There may be multiple interrupt sources that simultaneously initiate requests to the core, and an arbitration is needed to select one from these sources to determine which interrupt source is prioritized. This scenario is called "interrupt arbitration", and different interrupts can be assigned with different levels and priorities to facilitate the arbitration, so there is a concept of "interrupt level" and "interrupt priority". CLIC mode and CLINT mode ------------------------ .. _interrupt_set_clint_or_clic_mode: Setting CLINT or CLIC mode ~~~~~~~~~~~~~~~~~~~~~~~~~~ The Nuclei processor core supports the "CLNT interrupt mode (CLINT mode in short)" and "CLIC interrupt mode (CLIC mode in short)". Software can set the different mode by writing least significant bits of mtvec, please refer to |csr_mtvec| for more details. Please refer to following sections for the recommendations of when to set the CLIC mode or CLINT mode. CLINT mode ~~~~~~~~~~ CLINT mode is the default mode after reset, it is a simple interrupt handling scheme. The CLINT mode relying on the PLIC (Platform Level Interrupt Controller) in conjunction with the CSR register |csr_mie| and |csr_mip|, which is part of RISC-V standard privileged architecture specification. Please refer to RISC-V standard privileged architecture specification for more details. The CLINT mode is recommended to be used in Linux capable applications or symmetric multi-processor (SMP) applications, please refer to :ref:`plic` for more details. CLIC mode ~~~~~~~~~ CLIC mode is not the default mode after reset, hence need software to explicitly turn it on. CLIC mode is a relevantly complicate interrupt handling scheme. The CLIC mode relying on the ECLIC (Enhanced Core Local Interrupt Controller), but the CSR register mie and mip are functionally bypassed in this mode. The CLIC mode is recommended to be used in real-time or microcontroller applications, please refer to :ref:`eclic` for more details. Interrupt Type --------------- The types of interrupts supported by the Nuclei processor core are shown in `figure_interrupt_1`. .. _figure_interrupt_1: .. figure:: /asserts/media/image010.png :width: 80 % :align: center :alt: Interrupt Types Interrupt Types These will be detailed in the following sections. External Interrupt ~~~~~~~~~~~~~~~~~~ An external interrupt is an interrupt initiated from outside the core. External interrupts allow user to connect to an external interrupt source, such as an interrupt generated by an external device like UART, GPIO and so on. The Nuclei processor core supports multiple external interrupt sources. .. note:: - In CLINT mode, all of external interrupts are managed by the PLIC, as depicted in :ref:`eclic_plic_conn_single_core_plic`. - In CLIC mode, all of external interrupts are managed by the ECLIC, as depicted in :ref:`eclic_plic_conn_single_core_eclic`. Internal Interrupt ~~~~~~~~~~~~~~~~~~ The Nuclei processor core has several core-internal private interrupts as the followings: - Software Interrupt - The Nuclei processor core implements a TIMER unit, and an msip register is defined in the TIMER unit, through which software interrupts can be generated. Please see :ref:`timer_gen_swirq` for details. - Timer Interrupt - The Nuclei processor core implements a TIMER unit, and a counter is defined in the TIMER unit, through which time interrupts can be generated. Please see :ref:`timer_gen_timeirq` for details. .. note:: - In CLINT mode, the internal interrupts of the Nuclei processor core are managed by CSR register |csr_mie| and |csr_mip|. - In CLIC mode, the internal interrupts of the Nuclei processor core are also managed by the :ref:`eclic`. Interrupt Masking ----------------- Global Interrupt Masking ~~~~~~~~~~~~~~~~~~~~~~~~ Interrupts in machine mode can be masked globally by the control bit of CSR mstatus.MIE in Nuclei processor core. Please refer to RISC-V standard privileged architecture specification for more details. Individual Interrupt Masking ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It can also be masked individually for different interrupt sources: - In CLINT mode: - In machine mode, the CSR register mie.MSIE/MTIE can be used to disable software interrupt, timer interrupt individually respectively. The mie.MEIE can be used to disable all the external interrupts managed by PLIC. Please refer to RISC-V standard privileged architecture specification for more details. - And PLIC unit also have memory mapped registers to enable/disable each interrupt source managed by :ref:`plic`. - In CLIC mode, ECLIC have memory mapped register to enable/disable each interrupt source managed by ECLIC. Users can program the corresponding ECLIC register to manage some specified interrupt sources. Please see :ref:`eclic_registers` for details. .. _interrupt_level_prio_arbitration: Interrupt Levels, Priorities and Arbitration -------------------------------------------- When multiple interrupts are initiated at the same time, the arbitration is required: - In CLINT mode: - The PLIC manages all external interrupts. PLIC assigns its own interrupt priority registers to each external interrupt source. Users can program the PLIC registers to manage the priority of the specified interrupt sources. When multiple interrupts occur simultaneously, the PLIC will select the one that has the highest priority and sent interrupt request to the core (as meip). Please see :ref:`plic_registers` for details. - In CLIC mode: - The ECLIC manages all interrupts. ECLIC assigns its own interrupt level and priority registers to each interrupt source. Users can program the ECLIC registers to manage the level and priority of the specified interrupt sources. When multiple interrupts occur simultaneously, the ECLIC will select the one that has the highest level/priority to be taken. Please see :ref:`eclic_registers` for more details. .. _figure_interrupt_2: .. figure:: /asserts/media/image011.png :width: 80 % :align: center :alt: Arbitration among Multiple Interrupts Arbitration among Multiple Interrupts (CLIC mode) Entering Interrupt Handling Mode -------------------------------------------- If it is in CLINT mode, taking an interrupt, hardware behaviors of the Nuclei processor core are following RISC-V standard privileged architecture specification. This document will not repeat its content, please refer to RISC-V standard privileged architecture specification for more details. If it is in CLIC mode, taking an interrupt, hardware behaviors of the Nuclei processor core are described as below. Note that the following operations are done simultaneously in one cycle: - Stop the execution of the current program, and jump to another PC to execute. - Update the following CSR registers: |csr_mcause| - mepc - mstatus - |csr_mintstatus| - Update the Privilege Mode and Machine Sub-Mode of the core. - The overall process of interrupt is shown in :ref:`figure_interrupt_3`. These will be detailed in the following sections. .. _figure_interrupt_3: .. figure:: /asserts/media/image012.png :width: 80 % :align: center :alt: The Overall Process of Interrupt for CLIC mode The Overall Process of Interrupt for CLIC mode Execute from a new PC ~~~~~~~~~~~~~~~~~~~~~~ In CLIC mode, each interrupt source of the ECLIC can be set to vectored or non-vectored interrupt (via the shv filed of the register :ref:`eclic_reg_clicintattr`). The key points are as follows: - If the interrupt is configured as a vectored interrupt, then the core will jump to the corresponding target address of this interrupt in the Vector Table Entry when this interrupt is taken. For details about the Interrupt Vector Table, please refer to :ref:`interrupt_eclic_vector_table`. For details of the vectored processing mode, please refer to :ref:`interrupt_vectored_process_mode`. - If the interrupt is configured as a non-vectored interrupt, then the core will jump to a common base address shared by all interrupts. For details of the non-vectored processing mode, please refer to :ref:`interrupt_non-vectored_process_mode`. Update the Privilege Mode ~~~~~~~~~~~~~~~~~~~~~~~~~ The privilege mode will be switched to Machine Mode when the core takes an Interrupt. Update the Machine Sub-Mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Machine Sub-Mode of the Nuclei processor core is indicated in the msubm.TYP filed in real time. When the core takes an interrupt, the Machine Sub-Mode will be updated to interrupt handling mode, so: - The value of msubm.PTYP will be updated to the value of msub.TYP before taking the interrupt as shown in :ref:`figure_interrupt_4`. The value of msubm.PTYP will be used to restore the value of msubm.PTYP after exiting the interrupt handler. - The filed msubm.TYP is updated to interrupt handling mode, as described in :ref:`figure_interrupt_4`, to reflect the current Machine Sub-Mode is "interrupt handling mode". Update the CSR mepc ~~~~~~~~~~~~~~~~~~~ The return address when the Nuclei processor core exits the interrupt handler is stored in the CSR mepc. When the core takes an interrupt, the hardware will update the CSR mepc automatically, and the value in this CSR will be the return address when exit the interrupt handler. After handling the interrupt, the PC value is restored from this CSR mepc to return to the execution point that was previously stopped. .. note:: - When an interrupt is taken, the CSR mepc is updated to the PC of the instruction that encounters the interrupt. Then after exiting the interrupt, the program will continue to execute from the instruction that encounters the interrupt. - Although the CSR mepc can be updated automatically encountering an interrupt, it is a both readable and writeable register, so the software can modify it explicitly. Update the CSRs mcause and mstatus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Nuclei processor core will update the CSR |csr_mcause| by the hardware automatically, as described in :ref:`figure_interrupt_4`, explained as follows: - A mechanism is required to record the ID of the interrupt being taken. - When an interrupt is taken by the Nuclei processor core, the field mcause.EXCCODE is updated to the ID of the taken interrupt by the ECLIC, so the software can query the ID of this selected interrupt by reading this register. - When the current interrupt is taken, a mechanism is required to record the global interrupt enable bit and the Privilege Mode before taking the interrupt. - When the Nuclei processor core takes an interrupt, the filed mstatus.MPIE will be updated to the value of mstatus.MIE, and the filed mstatus.MIE will be set to 0, which means interrupts are globally masked, and all interrupts will not be taken. - When the Nuclei processor core takes an interrupt, the Privilege Mode of the core will be switched to Machine Mode, and the field mstatus.MPP will be set to the Privilege Mode before taking the interrupt. - When the current interrupt is taken, possibly it is preempting the interrupt who was previously being processed (whose interrupt level is relatively lower, so it can be preempted), and a mechanism is needed to record the interrupt level of the preempted interrupt. - When an interrupt is taken by the Nuclei processor core, the field mcause.MPIL is updated to the value of minstatus.MIL. The value of mcause.MPIL is used to restore the value of mcause.MIL after handling the interrupt. - If the taken interrupt is a vectored interrupt, the core will jump to the corresponding target address stored in the Vector Table Entry. For a detailed description of the vectored interrupt processing mode, please see :ref:`interrupt_vectored_process_mode`. In terms of the hardware implementation, the processing of an interrupt needs to be divided into two steps. The first step is to query the target address from the Vector Table, and then jump to the target address in the second step. Then, it is possible that a memory access occurs in the first step, querying the target address from the Vector Table, so a mechanism is required to record such a special memory access error. - When the Nuclei processor core takes an interrupt, if the interrupt is a vectored mode interrupt, the value of mcause.minhv will be updated to 1, and then cleared to 0 when the above "two-step" operation is completed. Assuming a memory access error occurs midway, it will raise an Instruction Access Fault exception, and the value of mcause.minhv will be 1 assuming this bit is not cleared. .. note:: the mcause.MPIE and mcause.MPP are mirrored with the field of mstatus.MPIE and mstatus.MPP. Which means normally the value of mstatus.MPIE is always the same as the value of mcause.MPIE and the value of mstatus.MPP is the same as the value of mcasue.MPP. .. _figure_interrupt_4: .. figure:: /asserts/media/image013.png :width: 60 % :align: center :alt: The CSR updating when enter/exit the Interrupt The CSR updating when enter/exit the Interrupt (CLIC mode) Exit the Interrupt Handling Mode -------------------------------------------- If it is in CLINT mode, after handling the interrupt, hardware behaviors of the Nuclei processor core are following RISC-V standard privileged architecture specification. This document will not repeat its content, please refer to RISC-V standard privileged architecture specification for more details. If it is in CLIC mode, after handling the interrupt, the core needs to exit from the interrupt handler eventually, and return to execute the main program. Since the interrupt is handling in Machine Mode, the software has to execute mret to exit the interrupt handler. The hardware behavior of the processor after executing mret instruction is as depicted in :ref:`figure_interrupt_5`. Note that the following hardware behaviors are done simultaneously in one cycle: - Stop the execution of the current program, and start from the PC address defined by the CSR mepc. - Update the following CSRs: - mstatus - |csr_mcause| - |csr_mintstatus| - Update the Privilege Mode and the Machine Sub-Mode. .. _figure_interrupt_5: .. figure:: /asserts/media/image014.png :width: 80 % :align: center :alt: The overall process of exiting an interrupt The overall process of exiting an interrupt These will be detailed in the following sections. Executing from the Address Defined by mepc ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When an interrupt is taking, the mepc is updated to the PC value of the instruction encountered the interrupt. Through this mechanism, executing the mret instruction, the core will return to the instruction encountered the interrupt, and continues to execute the program. Update the CSRs mcause and mstatus ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Nuclei processor core will update the CSR mcause when executes one mret instruction, explained as follows: - When an interrupt is taken, the value of mcause.MPIL will be updated to the value of mintstatus.MIL before taking the interrupt. The hardware will restore the value of minstatus.MIL using the value of mcause.MPIL when executes the mret instruction to exit the interrupt handler. Through this mechanism, the value of mintstatus.MIL is restored to the previous value before taking the interrupt. - When an interrupt is taken, the value of mcause.MPIE will be updated to the value of mintstatus.MIE before taking the interrupt. The hardware will restore the value of minstatus.MIE using the value of mcause.MPIE when executes the mret instruction to exit the interrupt handler. Through this mechanism, the value of mintstatus.MIE is restored to the previous value before taking the interrupt. - When an interrupt is taken, the value of mcause.MPP will be updated to the Privilege Mode before taking the interrupt. The hardware will restore the Privilege Mode using the value of mcause.MPP when executes the mret instruction to exit the interrupt handler. Through this mechanism, the Privilege Mode is restored to the previous value before taking the interrupt. .. note:: the mcause.MPIE and mcause.MPP are mirrored with the field of mstatus.MPIE and mstatus.MPP. Which means normally the value of mstatus.MPIE is always the same as the value of mcause.MPIE and the value of mstatus.MPP is the same as the value of mcasue.MPP. Update the Privilege Mode ~~~~~~~~~~~~~~~~~~~~~~~~~ The hardware will update the Privilege Mode using the value of mcause.MPP automatically after the execution of the mret instruction: - Taking an interrupt, the value of mstatus.MPP was updated to the Privilege Mode of the core before taking the interrupt, and after executing the mret instruction, the value of Privilege Mode is restored by the value of mstatus.MPP. Through this mechanism, the core is guaranteed to return to the Privilege Mode before taking the interrupt. Update the Machine Sub-Mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The value of msubm.TYP indicates the Machine Sub-Mode of the Nuclei processor core in real time. After executing the mret instruction, the hardware will automatically restore the core's Machine Sub-Mode by the value of msubm.PTYP: - Taking an interrupt, the value of msubm.PTYP is updated to the Machine Sub-Mode before taking the interrupt. After executing the mret instruction, the hardware will automatically restore the Machine Sub-Mode using the value of msubm.PTYP. Through this mechanism, the Machine Sub-Mode of the core is restored to the same mode before taking the interrupt. .. _interrupt_eclic_vector_table: (CLIC mode) Interrupt Vector Table ---------------------------------- If in CLINT mode, Nuclei processor core does not support the vector mode. Hence, there is no vector table relevant. Herein this section only introduces the CLIC mode interrupt vector table. If in CLIC mode, as shown in :ref:`figure_interrupt_6`, the interrupt vector table is a contiguous address space in the memory, and each word of this address space is used to store the address of the interrupt service routine corresponding to each interrupt source of the ECLIC. The base address of the interrupt vector table is defined by the CSR |csr_mtvt|. The role of the interrupt vector table is very important. When the core takes an interrupt, no matter a vectored or non-vectored interrupt, the hardware will eventually jump to the corresponding PC of the interrupt service routine by querying the interrupt vector table. Please see :ref:`interrupt_process_modes` for more details. .. _figure_interrupt_6: .. figure:: /asserts/media/image015.png :width: 80 % :align: center :alt: Interrupt Vector Table Interrupt Vector Table Context Saving and Restoring ---------------------------- Nuclei processor core based on the RISC-V architecture do not support the hardware automatic context saving and restoring when take or exit an interrupt. So the software is required to write the instructions (in assembly language) for context saving and restoring. For CLIC mode, depending on whether the interrupt is a vectored or non-vectored, the context requiring saving and restoring will vary. Please see :ref:`interrupt_process_modes` for more details. Interrupt Response Latency -------------------------- The concept of interrupt response latency usually refers to the cycle consumed from the time point "external interrupt source asserting" to the time point "the first instruction in the corresponding interrupt service routine of C function is executed". Therefore, the interrupt latency usually includes the following aspects of the cycle overhead: - The overhead of jumping to the target PC - The overhead of context saving - The overhead of jumping to the Interrupt Service Routine of C function For CLIC mode, interrupt response latency varies depending on whether the interrupt is a vectored or non-vectored. Please see :ref:`interrupt_process_modes` for more details. .. _interrupt_eclic_preemption: (CLIC mode) Interrupt Preemption -------------------------------- If in CLINT mode, Nuclei processor core does not support the interrupt preemption. Herein this section only introduces the CLIC mode interrupt preemption. If in CLIC mode, while the core is handling an interrupt, there may be another new interrupt request of a higher level, and then the core can stop the current interrupt service routine and start to taken the new one and execute its "Interrupt Service Routine". Hence, the interrupt preemption is formed (that is, the previous interrupt has not returned yet, and the new interrupt is taken), and there could be multi-level of nesting. Take the case in :ref:`figure_interrupt_7` as an example: - Assuming that the core is handling one timer interrupt and suddenly an interrupt is initiated by button 1 and this interrupt has a higher level than the timer interrupt. The core will stop processing the timer interrupt and start to handle the interrupt initiated by button 1. - Then another interrupt is initiated by button 2, which has a higher level than the interrupt initiated by button 1, so the core will stop processing the interrupt of button 1 and start to handle the interrupt of button 2. - After that no other higher-level interrupts arrive, the button 2 interrupt will not be preempted, and the core can successfully complete the interrupt service routine of the button 2 interrupt, and then return to process the button 1 interrupt. - Completing the interrupt service routine of button 1 interrupt, the core will return to execute the timer interrupt service routine to handle the timer interrupt. .. _figure_interrupt_7: .. figure:: /asserts/media/image016.png :width: 80 % :align: center :alt: Interrupt Preemption Interrupt Preemption In the Nuclei processor core, the supported methods for interrupt preemption depending on whether the interrupt is a vectored interrupt or a non-vectored interrupt. Please see :ref:`interrupt_process_modes` for more details. .. _interrupt_eclic_tail_chaining: (CLIC mode) Interrupt Tail-Chaining ----------------------------------- If in CLINT mode, Nuclei processor core does not support the interrupt tail-chaining. Herein this section only introduces the CLIC mode interrupt tail-chaining. If in CLIC mode, while the core is processing one interrupt, a new interrupt request is initiated, but the level of the new request is not higher than the handling one, so the new interrupt request cannot preempt the handling one. After handling the current interrupt, theoretically it is necessary to restore the context, then exit the interrupt service routine, return to the main program, and then take the new interrupt. To take the new interrupt, it is necessary to save the context again. Therefore, there is a back-to-back "context saving" and "context restoring". The "tail-chaining" can save the cost of this back-to-back "context saving" and "context-restoring", as shown in the :ref:`figure_interrupt_8`. .. _figure_interrupt_8: .. figure:: /asserts/media/image017.png :width: 80 % :align: center :alt: Interrupt tail-chaining Interrupt tail-chaining As for the Nuclei processor core, only non-vectored interrupts (CLIC mode) support the feature of tail-chaining. Please see :ref:`interrupt_non-vectored_interrupt_tail_chaining` for more details. .. _interrupt_process_modes: (CLIC mode) Vectored and Non-Vectored Processing Mode of Interrupts ------------------------------------------------------------------- In CLIC mode, each interrupt source can be configured to vectored or non-vectored processing mode (via the shv field of the ECLIC register clicinattr[i]). There is obvious difference between the vectored and non-vector processing mode, which are described in the following sections. .. _interrupt_non-vectored_process_mode: Non-Vectored Processing Mode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. _interrupt_non-vectored_feature_latency: Feature and Latency of Non-Vectored Processing Mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the interrupt is non-vectored, once it is taken, the core will jump to the common base entry shared by all non-vectored interrupts, and the address of this entry can be set by software: - If the least significant bit of the CSR |csr_mtvt2| is 0 (default value after reset), the common base address shared by all non-vectored interrupts is specified by the CSR mtvec (ignoring the value of the lowest 2 bits). Since the CSR mtvec also indicates the entry address of exceptions, which means exceptions and all non-vector interrupts share the entry address. - If the least significant bit of the CSR |csr_mtvt2| is 1, the common entry address of all non-vectored interrupts is defined by the CSR mtvt2 (ignoring the value of the lowest 2 bits). In order to handle the interrupt as fast as possible, it is recommended to set the least significant bit of the CSR mtvt2 to 1, which means the entry address for all non-vectored interrupts is separated from the entry of exceptions (exception entry is defined by the CSR mtvec). After entering the common base entry of non-vectored interrupts, the core will start to execute a common program, as the example shown in Feature 6‑9, the program is typically as follows: - Firstly, save the CSR mepc, |csr_mcause|, |csr_msubm| into the stack. These CSR registers are saved to ensure that subsequent preempted interruption can be handled correctly, because taken the new preempted interrupt will overwrite the values of mepc, mcause, msubm, so they need to be saved into the stack first. - Save several general-purpose registers (the execution context) into the stack. - Then execute a Nuclei self-defined instruction ``csrrw ra, CSR_JALMNXTI, ra``. If there is no pending interrupt, then this instruction will be regarded as a ``nop``. If there is a pending interrupt, the core will take the following operations: - Jump to the target address stored in Vector Table Entry and execute the corresponding Interrupt Service Routine. - The hardware will set the global interrupt enable bit mstatus.MIE while the core jumps to the interrupt service routine. Setting the mstatus.MIE bit, new interrupt will be taken and form an interrupt preemption. - In addition to jump to the Interrupt Service Routine, the instruction ``csrrw ra, CSR_JALMNXTI, ra`` also have the effect of a JAL (Jump and Link) instruction. The hardware will update the value of the link register to the PC of this instruction as the return address of the function. Therefore, returning from the interrupt handler, the core will return to the instruction ``csrrw ra, CSR_JALMNXTI, ra``, and re-judge whether there is still an interrupt pending to implement the operation of the tail-chaining. See more description of tail-chaining from :ref:`interrupt_non-vectored_interrupt_tail_chaining`. - At the end of the interrupt service routine, the software also needs to add the corresponding context restoring operation. Before restoring the CSR *mepc, mcause, msubm*, and the global interrupt enable bit mstatus.MIE needs to be cleared again to ensure the atomicity of the recovery operations of *mepc, mcause, and msubm*. .. _figure_interrupt_9: .. figure:: /asserts/media/image018.png :width: 80 % :align: center :alt: Example for non-vectored interrupt Example for non-vectored interrupt Since the core needs to execute a common handler before jump to the specified interrupt service routine of the corresponding non-vector interrupt. Therefore, the total cycle overhead from the interrupt initiation to the first instruction in the interrupt service routine (C function) is executed are as below: - The overhead caused by jumping to the interrupt handler which is about 4 cycles ideally. - The overhead caused by saving CSRs mepc, mcause, msubm into the stack is about 3 cycles ideally. - The overhead caused by saving the context. If the architecture is RV32E, then it only takes 8 cycles to save 8 general purpose registers; if it is RV32I architecture, then there are 16 general purpose registers required to be saved. - The overhead caused by jumping to the Interrupt Service Routine which is about 5 cycles ideally. Preemption of Non-Vectored Interrupt ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As mentioned above, non-vectored interrupt processing mode can always support interrupt preemption as the example shown in :ref:`figure_interrupt_10`: assuming that the three interrupts 30, 31, 32 come sequentially, and the level of interrupt 32 is greater than the level of interrupt 31 which is greater than the level of interrupt 30. Since then, the subsequent interrupts will preempt interrupts that were previously processed to form interrupt preemptions. .. _figure_interrupt_10: .. figure:: /asserts/media/image019.png :width: 80 % :align: center :alt: Interrupt preemptions caused by three sequential non-vectored interrupts Interrupt preemptions caused by three sequential non-vectored interrupts .. _interrupt_non-vectored_interrupt_tail_chaining: Non-Vectored Interrupt Tail-Chaining ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As mentioned in :ref:`interrupt_eclic_tail_chaining`, the tail-chaining can save cycles overhead significantly (reduced a back-to-back context saving and restoring). For non-vectored interrupts (CLIC mode), as mentioned in :ref:`interrupt_non-vectored_feature_latency`, the instruction ``csrrw ra, CSR_JALMNXTI, ra`` in the common base handler also achieves the effect of JAL (Jump and Link), which means the hardware will update the value of the Link register to the PC of this instruction as the return address. Therefore, the core will execute the instruction ``csrrw ra, CSR_JALMNXTI, ra`` again when it return from the interrupt service handler (C function) and re-execute ``csrrw ra, CSR_JALMNXTI, ra``, i.e., re-judge if there is a pending interrupt to perform the tail-chaining operation. As the example shown in :ref:`figure_interrupt_11`: assuming the interrupts 30, 29, 28 come successively, and *"the level of interrupt 30 " >= "the level of interrupt 29" >= "the level of interrupt 28"*, then the subsequent interrupt will not preempt the interrupt that was taken before, which means no preemption will happen, but all these subsequent interrupt will be marked as **pending**. When the interrupt 30 has been already handled, the core will handle the interrupt 29 directly without the intermediate "context restoring" and "context saving" procedures. .. _figure_interrupt_11: .. figure:: /asserts/media/image020.png :width: 80 % :align: center :alt: Interrupt tail-chaining Interrupt tail-chaining .. _interrupt_vectored_process_mode: Vectored Processing Mode ~~~~~~~~~~~~~~~~~~~~~~~~ Feature and Latency of Vectored Processing Mode ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If the interrupt is vectored, once it is taken, the core will jump to the target address saved in the Vector Table Entry directly, which is the corresponding interrupt service routine (C function) of the interrupt, as shown in :ref:`figure_interrupt_12`. .. _figure_interrupt_12: .. figure:: /asserts/media/image021.png :width: 80 % :align: center :alt: Example for vectored interrupt Example for vectored interrupt **Vectored Processing Mode** has the following features: - The core will jump directly to the interrupt service routine without context saving. Therefore, the latency of the vectored interrupt is very short. Ideally, it only takes 6 cycles from the interrupt initiation to the execution of the first instruction of the interrupt service routine (C function), because the hardware only need to perform one lookup and jump. - For an interrupt service routine of a vectored interrupt, the indication ``__attribute__ ((interrupt))`` is required to indicate compiler this C function is an interrupt service routine. Why this attribute is needed? Explained as below: - In the vector processing mode, since the core does not save the context before jumping to the interrupt service routine, theoretically the interrupt handler cannot call any sub-function which means the handler must be a leaf function. - If the interrupt service routine accidentally calls another sub-function, which means the routine is not a leaf function, it will cause a function error because of context corruption. - In order to avoid this accidental error, as long as the ``__attribute__ ((interrupt))`` is used to indicate this function is an interrupt handler, the compiler will automatically detect if this function calls any sub-function. If it calls any sub-function, the compiler will automatically insert a piece of code to save the context. .. note:: in this case, although the function correctness is guaranteed, the overhead caused by context saving will actually increase the latency of the response of the interrupt (equivalent to the non-vectored interrupt processing) and cause the expansion of the code size. Hence, in practice, it is not recommended to call other sub-functions in the interrupt service routine of a vectored interrupt. Preemption of Vectored Interrupt ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In vectored processing mode, the core does not perform any special operation before jumping to the interrupt service routine, and the value of mstatus.MIE is updated to 0 by the hardware, which means the interrupt is global disabled and no new interrupt will be taken once the core is handling the interrupt. Therefore, the vectored processing mode does not support interrupt preemption by default. In order to support vectored interrupt preemption, a special stack-push operation is necessary at the beginning of the interrupt service routine as shown in :ref:`figure_interrupt_13`: - First save the CSRs mepc, mcause, msubm to the stack. These CSRs are saved to ensure that subsequent interrupt preemption can perform correctly, because the new taken interrupt will overwrite the values of mepc, mcause, and msubm, so they need to be saved to the stack first. - Re-enable the global interrupt enable bit, that is, set the mstatus.MIE to 1. After the global interrupt enable bit is set, the new interrupt can be taken to allow the mechanism of interrupt preemption. - At the end of the interrupt service routine, it is necessary to add the operation of context restoring. And before CSRs mepc, mcause, and msubm are restored from the stack, the global interrupt enable bit must be set as 0 to guarantee the atomicity of the restoring peration of CSRs mepc, mcause, and msubm (not interrupted by the ew interrupt). .. _figure_interrupt_13: .. figure:: /asserts/media/image022.png :width: 80 % :align: center :alt: Example for vectored interrupt supported preemption Example for vectored interrupt supported preemption As described above, with the special processing, the vectored processing mode can support interrupt preemption, as shown in :ref:`figure_interrupt_14`: assuming that the three interrupts 30, 31, 32 come sequentially, and the level of interrupt 32 is greater than the level of interrupt 31 which is greater than the level of interrupt 30. Since then, the subsequent interrupts will preempt interrupts that were previously processed to form interrupt preemptions. .. _figure_interrupt_14: .. figure:: /asserts/media/image023.png :width: 80 % :align: center :alt: Interrupt preemptions caused by three sequential vectored interrupts Interrupt preemptions caused by three sequential vectored interrupts Vectored Interrupt Tail-Chaining ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For the vectored processing mode, the core does not save the context before jumping to the interrupt service routine, so the meaning of "interrupt tail-chaining" is not significant. Therefore, the vectored processing mode does not support the features of "interrupt tail-chaining".