Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
2085 lines (1722 sloc) 89.2 KB

SiFive Proposal for a RISC-V Core-Local Interrupt Controller (CLIC)

Table of Contents

1. Background and Motivation

The CLIC is designed to provide low-latency, vectored, pre-emptive interrupts for RISC-V systems. When activated the CLIC subsumes and replaces the existing RISC-V local interrupt scheme (CLINT). The CLIC design has a base design that requires minimal hardware, but supports additional extensions to provide hardware acceleration. The goal of the CLIC design is to provide support for a variety of software ABI and interrupt models, without complex hardware that can impact high-performance processor implementations.

Note
While the current CLIC design provides only hart-local interrupt control, future additions might also support directing interrupts to harts within a core, hence the name (also CLIC sounds better than HLIC or HIC).

1.1. Existing RISC-V Interrupts

The existing RISC-V interrupt system already supports interrupt preemption, but only based on privilege mode. At any point in time, a RISC-V hart is running with a current privilege mode. The global interrupt enable bits, MIE/SIE/UIE, held in the mstatus/sstatus/ustatus registers respectively, control whether interrupts can be taken for the current or higher privilege modes; interrupts are always disabled for lower-privileged modes. Any enabled interrupt from a higher-privilege mode will stop execution at the current privilege mode, and enter the handler at the higher privilege mode. Each privilege mode has its own interrupt state registers (mepc/mcause for M-mode, sepc/scause for S-mode, uepc/ucause for U-mode with N extension) to support preemption, or generically mepc for privilege mode m. Preemption by a higher-privilege-mode interrupt also pushes current privilege mode and interrupt enable status onto the mpp and mpie stacks in the mstatus register of the higher-privilege mode.

The mtvec register specifies both the interrupt mode and the base address of the interrupt vector table. The low bits of the WARL mtvec register indicate what interrupt model is supported. The original settings of mtvec mode (*00 and *01) indicate use of the CLINT model with either non-vectored or vectored transfer to a handler function, with the 4-byte (or greater) aligned table base address held in the upper bits of mtvec.

Note
WARL means "Write Any, Read Legal" indicating that any value can be attempted to be written but only supported values are retained.
Note
CLIC mode is enabled using previously reserved values (*10 or *11) in the low two bits of mtvec.

1.2. CLIC versus PLIC

The standard RISC-V platform-level interrupt controller (PLIC) provides centralized interrupt prioritization and routing for shared platform-level interrupts, and sends only a single external interrupt signal per privilege mode (meip/seip/ueip) to each hart.

The CLIC complements the PLIC. Smaller single-core systems might have only a CLIC, while multicore systems might have a CLIC per-core and a single shared PLIC. The PLIC meip signals are treated as hart-local interrupt sources by the CLIC at each core.

1.3. CLIC versus CLINT

The Core-Local Interrupt (CLINT) block was a small unit that provided local interrupts on earlier designs, and managed the software, timer, and external interrupt signals (msip/mtip/meip signals in the mip register). The CLINT also allowed additional custom fast interrupt signals to be added in bits 16 and up of the mip register.

New settings of mtvec mode as described below are used to enable CLIC modes instead of the original CLINT modes. Platform profiles may require either or both of the original CLINT and CLIC interrupt modes.

2. CLIC Design

This section describes the design of the Core-Local Interrupt Controller that receives interrupt signals and presents the next interrupt to be processed to the processor.

2.1. CLIC Interrupt levels

The CLIC extends interrupt preemption to support up to 256 interrupt levels for each privilege mode, where higher-numbered interrupt levels can preempt lower-numbered interrupt levels. Interrupt level 0 corresponds to regular execution outside of an interrupt handler. Levels 1—​255 correspond to interrupt handler levels. Platform profiles will dictate how many interrupt levels must be supported.

Incoming interrupts with a higher interrupt level can preempt an active interrupt handler running at a lower interrupt level in the same privilege mode, provided interrupts are globally enabled in this privilege mode.

Note
Existing RISC-V interrupt behavior is retained, where incoming interrupts for a higher privilege mode can preempt an active interrupt handler running in a lower privilege mode, regardless of global interrupt enable in lower privilege mode.

2.2. CLIC Interrupt Input Control (clicintctl)

The CLIC design supports up to 1,024 interrupt inputs per hart, with each interrupt input i having an 8-bit memory-mapped WARL control register, clicintctl[i]. The first 16 interrupt inputs are reserved for the CLINT interrupts present in the low 16 bits of the mip and mie registers, so up to 1,008 local external interrupts can be added.

A fixed parameter of the CLIC (CLICINTCTLBITS) is how many total bits are present in the clicintctl registers , with 2 <= CLICINTCTLBITS <= 8. The implemented bits are kept left-justified in the most-significant bits of each 8-bit clicintctl[i] register, with the lower unimplemented bits treated as hardwired to 1. These configuration bits are interpreted as mode, level, and priority depend on the setting of the cliccfg register as described below.

Each interrupt input also has an orthogonal interrupt-enable bit (clicintie[i]) as well as an interrupt-pending bit (clicintip[i]) in the memory map. For level-sensitive hardware interrupts, the interrupt-pending bits are read-only.

The CLIC circuitry treats the clicintctl values as unsigned integers, and takes a global maximum across all pending-and-enabled clicintctl values to select the interrupt to present to the core. The cliccfg setting then determines how to split the maximum clicintctl value into privilege mode (M/S/U), interrupt level (0—​255), and interrupt priority (0—​255).

Warning
Selecting an interrupt level of 0 for a high privilege mode disables the interrupt, but also masks any interrupt at a lower privilege mode since the higher-privilege mode causes the interrupt signal to appear more urgent than any lower-privilege mode interrupt.
Note
On a multithreaded core, the clicintctl[i] register might also contain a hart-id field to direct the interrupt to one hart on the core. This multithreaded option is not discussed further in this proposal.

2.2.1. Interrupt Input Cause Codes

The 1024 CLIC interrupt vectors are given unique mcause exccode values. The original CLINT interrupts retain their original cause values, while the new interrupts are numbered starting at 16.

Note
When upgrading an earlier CLINT-based system design that had local interrupts attached directly to bits 16 and above, these local interrupts can be now attached as CLIC inputs 16 and above to retain the same interrupt IDs.

2.3. CLIC configuration (cliccfg)

The CLIC has a single memory-mapped 6-bit global configuration register, cliccfg, that defines how the clicintctl[i] registers are subdivided into mode, level, and priority fields, which are held in descending order from the most-significant to the least-significant bits of each clicintctl register. The lowest variable bit in the clicintctl[i] field can also be used to control whether interrupt i uses hardware vectoring.

The cliccfg register has three WARL fields, a 2-bit nmbits field, a 4-bit nlbits field, and a 1-bit nvbits field, plus a reserved bit WARL-hardwired to zero in current spec.

  cliccfg register layout

  bits    field
  7       reserved (WARL 0)
  6:5     nmbits[1:0]
  4:1     nlbits[3:0]
    0     nvbits

The cliccfg register resets to 0 (i.e., all interrupts are M-mode at level 255).

2.3.1. Specifying interrupt privilege mode

Note
Figure out interaction with hypervisor mode.

The 2-bit cliccfg.nmbits WARL field encodes how many bits in a clicintctl[i] register are used to hold an input i's privilege mode.

M-mode-only systems do not support privilege-mode fields in the clicintctl registers (cliccfg.nmbits = 0).

M/U-mode systems with user-level interrupts support cliccfg.nmbits = 0 or 1. If cliccfg.nmbits = 0, then all interrupts are treated as M-mode interrupts. If the cliccfg.nmbits = 1, then a value of 1 in the MSB of an clicintctl[i] register indicates that interrupt intput is taken in M-mode, while a value of 0 indicates that interrupt is taken in U-mode.

M/S/U-mode systems support 0, 1, or 2 bits of privilege-mode field. cliccfg.nmbits = 0 indicates that all local interrupts are taken in M-mode. cliccfg.nmbits = 1 indicates that the MSB selects between M-mode (1) and S-mode (0). cliccfg.nmbits = 2 indicates that the two MSBs of each clicintctl[i] register encode the interrupt’s privilege mode using the same encoding as the mstatus.mpp field.

priv-modes nmbits clicintctl[i] Interpretation
       M      0     xxxxxxxx     M-mode interrupt with level+priority=xxxxxxxx

     M/U      0     xxxxxxxx     M-mode interrupt with level+priority=xxxxxxxx
     M/U      1     0xxxxxxx     U-mode interrupt with level+priority=xxxxxxx
     M/U      1     1xxxxxxx     M-mode interrupt with level+priority=xxxxxxx

   M/S/U      0     xxxxxxxx     M-mode interrupt with level+priority=xxxxxxxx
   M/S/U      1     0xxxxxxx     S-mode interrupt with level+priority=xxxxxxx
   M/S/U      1     1xxxxxxx     M-mode interrupt with level+priority=xxxxxxx
   M/S/U      2     00xxxxxx     U-mode interrupt with level+priority=xxxxxx
   M/S/U      2     01xxxxxx     S-mode interrupt with level+priority=xxxxxx
   M/S/U      2     10xxxxxx     Reserved (or extended S-mode)
   M/S/U      2     11xxxxxx     M-mode interrupt with level+priority=xxxxxx

   M/S/U      3     xxxxxxxx     Reserved

2.3.2. Specifying interrupt level

The 4-bit cliccfg.nlbits WARL field indicates how many bits immediately below the cliccfg.nmbits privilege-mode bits encode the level at which the interrupt is taken. Valid values are 0—​8.

If the nmbits + nlbits > CLICINTCTLBITS, then the lower bits of the 8-bit interrupt level are assumed to be all 1s. If nlbits < 8, then the lower bits of the 8-bit interrupt level are assumed to be all 1s. The following table shows how levels are encoded in either of these two cases.

 #bits   encoding          interrupt levels
     1    l.......                        127,                            255
     2    ll......           63,          127,            191,            255
     3    lll.....     31,   63,   95,    127,    159,    191,    223,    255
     4    llll....  15,31,47,63,79,95,111,127,143,159,175,191,207,223,239,255

 "l" bits are available variable bits in level specification
 "." bits are non-existent bits for level encoding, assumed to be 1

If nlbits = 0, then all interrupts are treated as level 255.

Examples of cliccfg settings:

CLICINTCTLBITS nmbits nlbits clicintctl[i] interrupt levels
      2         2       2      mm......     255
      3         2       2      mml.....     127,255
      4         2       2      mmll....     63,127,191,255
      5         2       3      mmlll...     31,63,95,127,159,191,223,255
      5         1       1      mlppp...     127,255

2.3.3. Specifying interrupt priority

The least-significant bits in clicintctl[i] that are not configured to be part of the mode or level are used to prioritize among interrupts pending-and-enabled at the same privilege mode and interrupt level. The highest-priority interrupt at a given privilege mode and interrupt level is taken first. In case there are multiple pending-and-enabled interrupts at the same highest priority, the highest-numbered interrupt is taken first.

Note
The highest numbered interrupt wins in a tie. This is the same as the original CLINT, but different than the PLIC.

Any implemented priority bits are treated as the most-significant bits of a 8-bit unsigned integer with lower unimplemented bits set to 1. For example, with one priority bit (p111_1111), interrupts can be set to have priorities 127 or 255, and with two priority bits (pp11_1111), interrupts can be set to have priorities 63, 127, 191, or 255.

2.3.4. Specifying selective interrupt hardware vectoring

The single-bit WARL nvbits field in cliccfg enables or disables selective interrupt hardware vectoring.

If nvbits = 0, then selective interrupt vectoring is turned off, and all interrupts are vectored according to the mode setting held in the low bits of mtvec. The mtvec register is used to specify the handler PC for non-vectored CLIC mode. A separate mtvt CSR holds the base address of a table of trap-handler function pointers used when mtvec is set to hardware-vectored CLIC mode. The same mtvt CSR provides the table base address for selective hardware vectoring and for software vectoring using the mnxti CSR.

If nvbits = 1, then selective interrupt vectoring is turned on. The least-significant implemented bit of clicintctl[i] (i.e., clicinfcfg[i][8-CLICINTCTLBITS]) controls the vectoring behavior of interrupt i. If the relevant bit of clicintctl[i] is 0, then behavior follows the default specified by mtvec. If the relevant bit of clicintctl[i] is 1, then the interrupt is hardware vectored independent of the settings of mtvec. This allows some interrupts to all jump to a common base address held in mtvec, while the others are vectored in hardware via a table pointed to by the additional mtvt CSR.

Note
Selective hardware vectoring is intended to be used with the non-vectored CLIC mode. Selective hardware vectoring has no effect if the base mode is hardware vectoring.
Note
We could alternatively have clicintctl[i][8-CLICINTCTLBITS] invert the vectoring setting for input i.
Note
The setting of nvbits does not alter the way in which the implemented clicintctl[i] bits are interpreted as mode, level, or priority. The encoding means that selectively hardware-vectored (SHV) interrupts are always handled before non-hardware-vectored interrupts with identical settings in the other upper bits of clicintctl[i]. The assumption is that this is usually the desired behavior. Additional cliccfg bits could specify different treatment of the hardware vectoring bits (e.g., ignoring the vectoring bit in priority calculation, or inverting the encoding to handle non-vectored before vectored).

Examples:

CLICINTCTLBITS nmbits nlbits nvbits clicintctl[i] Vectored?
      2         2       2      1    mm......      M/S-mode interrupts
      3         2       2      1    mml.....      Level 255
      4         2       2      1    mmll....      Levels 127, 255
      5         2       3      1    mmlll...      Levels 63,127,191,255
      5         1       1      1    mlppp...      Priorities 63,127,191,255

2.4. CLIC interaction with other local interrupts

The CLIC subsumes the functionality of the fast local interrupts previously provided in bits 16 and up of mip/mie, so these are no longer visible in mip/mie.

The existing timer (mtip/stip/utip), software (msip/ssip/usip), and external interrupt inputs (meip/seip/ueip) are treated as additional local interrupt sources, where the privilege mode, interrupt level, and priority can be altered using memory-mapped clicintctl[i] registers. For each of meip/mtip/msip/seip/stip/ssip/ueip/utip/usip, an 8-bit configuration register is provided, which follows the format of the above.

Note
In CLIC mode, interrupt delegation for these signals is achieved via changing the interrupt’s privilege mode in the CLIC interrupt input configuration, as with any other CLIC interrupt input.

3. CLIC CSRs

This section describes the CLIC-related hart-specific CSRs. When in CLINT mode, the behavior is intended to be software compatible with CLINT-only systems.

The interrupt-handling CSRs are listed below, with changes and additions for CLIC mode described in the following sections.

       Number  Name         Description
       0xm00   mstatus      Status register
       0xm02   medeleg      Exception delegation register
       0xm03   mideleg      Interrupt delegation register (INACTIVE IN CLIC MODE)
       0xm04   mie          Interrupt-enable register     (INACTIVE IN CLIC MODE)
       0xm05   mtvec        Trap-handler base address / interrupt mode
(NEW)  0xm07   mtvt         Trap-handler vector table base address
       0xm40   mscratch     Scratch register for trap handlers
       0xm41   mepc         Exception program counter
       0xm42   mcause       Cause of trap
       0xm43   mtval        Bad address or instruction
       0xm44   mip          Interrupt-pending register    (INACTIVE IN CLIC MODE)
 (NEW) 0xm45   mnxti        Interrupt handler address and enable modifier
 (NEW) 0xm46   mintstatus   Current interrupt levels
 (NEW) 0xm48   mscratchcsw  Conditional scratch swap on priv mode change
 (NEW) 0xm49   mscratchcswl Conditional scratch swap on level change

         m is the nibble encoding the privilege mode (M=0x3, S=0x1, U=0x0)

3.1. Changes to mstatus CSRs

When in CLINT mode, the mstatus register behavior is unchanged (i.e., backwards-compatible with CLINT mode). When in CLIC mode, the mpp and mpie in mstatus are now accessible via fields in the mcause register.

3.2. Changes to Delegation (medeleg/mideleg) CSRs

In CLIC mode, the CLIC input configuration clcintcfg[i] specifies the privilege mode in which each interrupt should be taken, so the mideleg CSR ceases to have effect in CLIC mode. The mideleg CSR is still accessible and state bits retain their values when switching between CLIC and CLINT modes.

Exception delegation specified by medeleg functions the same in CLIC mode as in CLINT mode.

3.3. Changes to mie/mip CSRs

The mie CSR appears hardwired to zero in CLIC mode, replaced by separate memory-mapped interrupt enables (clicintie[i]).

The mip CSR appears hardwired to zero in CLIC mode, replaced by separate memory-mapped interrupt pendings (clicintip[i]).

In systems that support both CLINT and CLIC modes, the state bits in mie and mip retain their value when switching between modes.

3.4. New mtvec CSR modes

The new interrupt-handling modes are encoded as new states in the existing mtvec WARL register, where the low two bits of mtvec are 10 or 11. In these modes, the trap vector base address held in mtvec is constrained to be aligned on a 64-byte or larger power-of-two boundary.

 mtvec   Action on Interrupt
 aaaa00  pc := OBASE                (original non-vectored CLINT mode)
 aaaa01  pc := OBASE + 4 * exccode      (original vectored CLINT mode)
 000010  pc := NBASE                          (non-vectored CLIC mode)
 000011  pc := M[TBASE + XLEN/8 * exccode)] & ~1  (vectored CLIC mode)
 xxxx1?  (xxxx!=0000)                            Reserved

 OBASE = mtvec[XLEN-1:2]<<2 # Original vector base was at least 4-byte aligned.
 NBASE = mtvec[XLEN-1:6]<<6 # New vector base is at least 64-byte aligned.
 TBASE = mtvt[XLEN-1:6]<<6  # Trap vector table base is at least 64-byte aligned.

Writing a value to mtvec with the low two bits 10 selects a non-vectored CLIC mode, where the processor jumps to the trap handler address held in the upper XLEN-6 bits of mtvec for all exceptions and interrupts in privilege mode m.

Writing a value to mtvec with the low two bits 11 selects vectored CLIC mode. In vectored CLIC mode, on an interrupt, the processor switches to the handler’s privilege mode and sets the hardware vectoring bit minhv in mcause, then fetches an XLEN-bit handler address from the in-memory table whose base address (TBASE) is in mtvt. The trap handler function address is fetched from TBASE+XLEN/8*exccode. If the fetch is successful, the processor clears the low bit of the handler address, sets the PC to this handler address, then clears the minhv bit in mcause. The overall effect is:

pc := M[TBASE + XLEN/8 * exccode] & ~1
           # Vector table layout for RV32 (4-byte function pointers)
  mtvt ->  0x800000 # Interrupt 0 handler function pointer
           0x800004 # Interrupt 1 handler function pointer
           0x800008 # Interrupt 2 handler function pointer
           0x80000c # Interrupt 3 handler function pointer

           # Vector table layout for RV64 (8-byte function pointers)
  mtvt ->  0x800000 # Interrupt 0 handler function pointer
           0x800008 # Interrupt 1 handler function pointer
           0x800010 # Interrupt 2 handler function pointer
           0x800018 # Interrupt 3 handler function pointer
Note
The original CLINT vectored mode simply jumped to an address in the trap vector table, while the new CLIC vectored mode reads a handler function address from the table, and jumps to it in hardware.
Note
This version of the proposal has dropped the previously proposed instruction encoding for the trap handler vector addresses, as it complicated static initialization in C. The entries in the table are simple XLEN-bit function pointers.
Note
The hardware vectoring bit minhv is provided to allow resumable traps on fetches to the trap vector table.

Implementations might support only one of CLINT or CLIC mode. If only CLINT mode is supported, writes to bit 1 are ignored and it’s always set to zero (current behavior). If only CLIC mode is supported, writes to bit 1 are also ignored and it’s always set to one. CLIC mode hardwires mtvec bits 2-5 to zero (assuming no further CLIC extensions are supported).

For permissions-checking purposes, the memory access to retrieve the function pointer for vectoring is treated as a load with the privilege mode of the interrupt handler. If there is an access exception on the table load, mepc holds the faulting address. If this was a page fault, the table load can be resumed by returning with mepc pointing to the table entry and the trap handler mode bit set.

Instruction fetch at the handler address might cause misaligned or access exceptions, which are reported with mepc containing the faulting instruction fetch address.

In both vectored and non-vectored CLIC modes, synchronous exception traps always jump to NBASE.

3.5. New mtvt CSR

The mtvt WARL XLEN-bit CSR holds the base address of the trap vector table, aligned on a 64-byte or greater power-of-two boundary. Values other than 0 in the low 6 bits of mtvt are reserved.

In systems that support both CLINT and CLIC modes, the mtvt CSR is still accessible in CLINT mode (but does not have any effect).

Note
A previous proposal used positive and negative offsets from mtvec to point at the shared trampoline code and the trap vector table respectively, but this imposed a hard constraint on memory layout, which would be problematic when the trampoline code would prefer to be in flash/ROM while the writable vector table should be in SRAM. The mtvt CSR allows the trampoline code and the trap vector table to be independently located in the system memory map.

3.6. Changes to mepc CSRs

The mepc CSRs behave the same in both modes, capturing the PC at which execution was interrupted.

3.7. Changes to mcause CSRs

In both CLINT and CLIC modes, the mcause CSR is written at the time an interrupt or synchronous trap is taken, recording the reason for the interrupt or trap. For CLIC mode, mcause is also extended to record more information about the interrupted context, which is used to reduce the overhead to save and restore that context for an mret instruction. CLIC mode mcause also adds state to record progress through the trap handling process.

mcause
Bits    Field      Description
XLEN-1 Interrupt    Interrupt=1, Exception=0
   30  minhv        Hardware vectoring in progress when set
29:28  mpp[1:0]     Previous privilege mode, same as mstatus.mpp
   27  mpie         Previous interrupt enable, same as mstatus.mpie
26:24  (reserved)
23:16  mpil[7:0]    Previous interrupt level
15:10  (reserved)
  9:0  Exccode[9:0] Exception/interrupt code

The mcause.mpp and mcause.mpie fields mirror the mstatus.mpp and mstatus.mpie fields, and are aliased into mcause to reduce context save/restore code.

If the hart is currently running at some privilege mode (pp) at some interrupt level (pil) and an enabled interrupt becomes pending at any interrupt level in a higher privilege mode or if an interrupt at a higher interrupt level in the current privilege mode becomes pending and interrupts are globally enabled in this privilege mode, then execution is immediately transferred to a handler running with the new interrupt’s privilege mode (m) and interrupt level (il).

The CSR mepc is set to the PC of the interrupted application code or preempted interrupt handler, while the mcause register now captures the previous privilege mode (pp), interrupt level (pil) and interrupt enable (pie), as well as the id of the interrupt in exccode.

In systems supporting both CLINT and CLIC modes, the new CLIC-specific fields (minhv, mpp, mpil, mpie) appear to be hardwired to zero in CLINT mode for backwards compatibilty. When CLINT mode is written to mtvec, the new mcause state fields (mhinv and mpil) are zeroed. The other new mcause fields, mpp and mpie, appear as zero in the mcause CSR but the corresponding state bits in the mstatus register are not cleared.

The supervisor scause register has only a single spp bit (to indicate user/supervisor) mirrored from sstatus.spp, while the user ucause register has no upp bit as interrupts can only have come from user mode.

 scause
 Bits    Field      Description
 XLEN-1 Interrupt    Interrupt=1, Exception=0
    30  sinhv        Hardware vectoring in progress when set
    29 (reserved)
    28  spp          Previous privilege mode, same as sstatus.spp
    27  spie         Previous interrupt enable, same as sstatus.spie
 26:24  (reserved)
 23:16  spil[7:0]    Previous interrupt level
 15:10  (reserved)
   9:0  exccode[9:0] Exception/interrupt code

 ucause
 Bits    Field      Description
 XLEN-1 Interrupt   Interrupt=1, Exception=0
    30  uinhv       Hardware vectoring in progress when set
 29:28 (reserved)
    27  upie        Previous interrupt enable, same as ustatus.upie
 26:24  (reserved)
 23:16  upil[7:0]    Previous interrupt level
 15:10  (reserved)
   9:0  exccode[9:0] Exception/interrupt code
Note
Not clear if user mode should ever see hardware vectoring in progress.

3.8. Next Interrupt Handler Address and Interrupt-Enable CSR (mnxti)

The mnxti CSR can be used by software to service the next horizontal interrupt for the same privilege mode when it has greater level than the saved interrupt context (held in mcause`.pil`), without incuring the full cost of an interrupt pipeline flush and context save/restore. The mnxti CSR is designed to be accessed using CSRRSI/CSRRCI instructions, where the value read is a pointer to an entry in the trap handler table and the write back updates the interrupt-enable status. In addition, accesses to the mnxti have side-effects that update the interrupt context state.

Note
This is different than a regular CSR instruction as the value returned is different from the value used in the read-modify-write operation.

A read of the mnxti CSR returns either zero, indicating there is no suitable interrupt to service or that the highest ranked interrupt is SHV or that the system is not in a CLIC mode, or returns a non-zero address of the entry in the trap handler table for software trap vectoring.

Note
The mtvt CSR could be set to memory addresses such that a table entry was at address zero, and this would be indistinguishable from the no-interrupt case.

If the CSR instruction that acccesses mnxti includes a write, the mstatus CSR is the one used for the read-modify-write portion of the operation, while the mcause register’s exccode field and the mintstatus register’s mil field can also be updated with the new interrupt id and level respectively.

Note
Following the usual convention for CSR instructions, if the CSR instruction does not include write side effects (e.g., csrr t0, mnxti), then no state update on any CSR occurs. This can be used to determine if an interrupt could be taken without actually updating mil and exccode.

The mnxti CSR is intended to be used inside an interrupt handler after an initial interrupt has been taken and mcause and mepc registers updated with the interrupted context and the id of the interrupt.

 // Pseudo-code for csrrsi rd, mnxti, uimm[4:0] in M mode.
 mstatus |= uimm[4:0]; // Performed regardless of interrupt readiness.
 if (clic.priv==M && clic.level > mcause.pil
     && (cliccfg.nvbits==0 || clicintctl[clic.id][8-CLICINTCTLBITS]==0) ) {
   // The CLIC interrupt should be serviced before returning to the saved context,
   // unless it's a selectively hardware vectored interupt.
   minstatus.mil = clic.level; // Update hart's interrupt level.
   mcause.exccode = clic.id;   // Update interrupt id.
   rd = TBASE + XLEN/8 * clic.id; // Return pointer to trap handler entry.
 } else {
   // No interrupt, or a selectively hardware vectored interrupt, or in non-CLIC mode.
   rd = 0;
 }
Note
Vertical interrupts to different privilege modes will be taken preemptively by the hardware, so mnxti effectively only ever handles the next interrupt in the same privilege mode.

In CLINT mode, reads of mnxti return 0, updates to mstatus proceed as in CLIC mode, but updates to mintstatus and mcause do not take effect.

3.9. New Interrupt status (mintstatus) CSR

A new M-mode CSR, mintstatus, holds the active interrupt level for each supported privilege mode. These fields are read-only. The primary reason to expose these fields is to support debug.

mintstatus fields
31:24 mil
23:16 (reserved) # To follow pattern of others.
15: 8 sil
 7: 0 uil

Corresponding supervisor mode, sintstatus, and user, uintstatus, provide restricted views of mintstatus.

sintstatus fields
31:16 (reserved)
15: 8 sil
 7: 0 uil
uintstatus fields
31: 8 (reserved)
 7: 0 uil

The mintstatus registers are accessible in CLINT mode for system that support both modes.

4. CLIC Implementation Parameters

Name           Value Range                     Description
CLICANDCLINT   0-1                             Implements CLINT mode also?
CLICPRIVMODES  1-3                             Number privilege modes: 1=M, 2=M/U, 3=M/S/U
CLICLEVELS     2-256                           Number of interrupt levels including 0
CLICINPUTS     4-1024                          Always has MSIP, MTIP, MEIP, CSIP
CLICMAXID      12-1023                         Largest interrupt ID
CLICINTCTLBITS 2-8                             Number of bits implemented in clicintctl[i]
CLICCFGMBITS   0-ceil(lg2(CLICPRIVMODES))      Number of bits implemented for cliccfg.nmbits
CLICCFGLBITS   0-ceil(lg2((lg2(CLICLEVELS))))  Number of bits implemented for cliccfg.nlbits
CLICSELHVEC    0-1                             Selective hardware vectoring supported?
CLICMTVECALIGN 6-13                            Number hardwired zero LSBs in mtvec address.
CLICMNXTI      0-1                             Has mnxti CSR implemented?
CLICMCSW       0-1                             Has mscratchcsw/mscratchcswl implemented?

5. CLIC Interrupt Operation

This section describes the operation of CLIC interrupts.

5.1. General interrupt overview

At any time, a hart is running in some privilege mode with some interrupt level. The hart’s privilege mode is held internally in the processor but is not visible to software running on a hart (to avoid virtualization holes), but the current interrupt level is made visible in the mintstatus register. Interrupt level 0 corresponds to regular execution outside of an interrupt handler.

Within a privilege mode m, if the associated global interrupt-enable mie is clear, then no interrupts will be taken in that privilege mode, but a pending-enabled interrupt in a higher privilege mode will preempt current execution. If mie is set, then pending-enabled interrupts at a higher interrupt level in the same privilege mode will preempt current execution and run the interrupt handler for the higher interrupt level.

As with the existing RISC-V mechanism, when an interrupt or synchronous exception is taken, the privilege mode and interrupt level are modified to reflect the new privilege mode and interrupt level. The global interrupt-enable bit of the handler’s privilege mode is cleared, to prevent preemption by higher-level interrupts in the same privilege mode.

The overall behavior is summarized in the following table: the Current p/ie/il fields represent the current privilege mode P (not software visible), interrupt enable in mstatus ie and interrupt level L in mintstatus; the CLIC priv,level, and id fields represent the highest-ranked interrupt currently present in the CLIC with nP representing the new privilege mode, nL representing the new interrupt level, and id representing the interrupt’s id; Current' shows the p/ie/il context in the handler’s privilege mode; pc represents the program counter with V representing the result of any hardware vectoring; cde represents the mcause exccode field; while the Previous pp/il/ie/epc columns represent previous context fields in mcause and mepc.

Current  |      CLIC          |->      Current'          Previous
p/ie/il  | priv level   id    |->    p/ie/il  pc  cde   pp/il/ie epc
P  ?  ?  | nP<P     ?      ?  |->    - -  -   -   -     -  -  -  -   # Interrupt ignored
P  0  ?  | nP=P     ?      ?  |->    - -  -   -   -     -  -  -  -   # Interrupts disabled
P  1  ?  | nP=P     0      ?  |->    - -  -   -   -     -  -  -  -   # No interrupt
P  1  L  | nP=P   0<nL<=L  ?  |->    - -  -   -   -     -  -  -  -   # Interrupt ignored
P  1  L  | nP=P   L<nL    id  |->    P 0  nL  V   id    P  L  1  pc  # Horizontal interrupt taken
P  ?  ?  | nP>P     0      ?  |->    - -  -   -   -     -  -  -  -   # No interrupt
P  e  L  | nP>P   0<nL    id  |->   nP 0  nL  V   id    P  L  e  pc  # Vertical interrupt taken

5.2. Critical Sections in Interrupt Handlers

To implement a critical section between interrupt handlers at different levels in the same privilege mode, an interrupt handler at any interrupt level can clear the mode’s global interrupt-enable bit, mie, to prevent any interrupts with the same privilege mode from being taken.

Note
Need to specify a way to set an interrupt-level threshold, to more selectively disable preemption.

5.3. Synchronous Exception Handling

Horizontal synchronous exception traps, which stay within a privilege mode, are serviced with the same interrupt level as the instruction that raised the exception.

Warning
The synchronous trap will overwrite the mepc and mcause values, so exceptions causing horizontal traps should generally be avoided in interrupt handlers.

Vertical synchronous exception traps, which are serviced at a higher privilege mode, are taken at interrupt level 0 in the higher privilege mode.

Warning
Vertical synchronous trap handlers should avoid causing horizontal traps, as these will overwrite mepc and mcause.

5.4. Returns from Handlers

The regular mret instructions are used to return from handlers in privilege mode m. Execution continues at the saved privilege mode mcause.mpp, at PC mepc, with interrupt level mcause.mpil, and with the global interrupt enable for the restored mode as mcause.mpie.

The mret instruction does not modify the mcause.mpil field in mcause. The mcause.mpp and mcause.mpie fields are modified following the behavior previously defined for mstatus.mpp and mstatus.mpie respectively.

6. Interrupt Handling Software

6.1. Interrupt Stack Software Conventions

The CLIC supports multiple nested interrupt handlers, and each handler requires some working registers. To make registers available, each handler typically saves and restores registers from the interrupted context on a memory-resident stack. In addition, the memory-resident stack is used to hold other interrupted context information, such as mepc and mcause, which are required by the mret instruction.

The standard RISC-V ABI convention is that stacks grow downwards, and that memory addresses below the current stack pointer can be dynamically altered by another agent, such as an interrupt handler.

When interrupts are taken horizontally within the same privilege mode, the interrupt handler may be able to use the same stack as the interrupted thread, by allocating a new stack frame below the current stack pointer.

When interrupts are taken vertically into a higher privilege mode, the stack pointer must be swapped to a stack within the higher privilege mode to avoid a security hole. The mscratch registers can be used to hold the stack pointer of a higher-privilege mode while lower-privilege code is executing, or mscratch can be used to point to more extensive thread-local context that might contain a stack pointer.

6.2. Inline interrupt handlers and "interrupt attribute" for C

Inline interrupt handlers are small leaf functions that handle simple interrupts. To provide easy C coding for inline interrupt handlers, while reducing register save/restore overhead, we use standard interrupt attributes, which have the following syntax:

  /* Small ISR to poke device to clear interrupt and increment in-memory counter. */
  void __attribute__ ((interrupt))
  foo (void)
  {
    extern volatile int INTERRUPT_FLAG;
    INTERRUPT_FLAG = 0;
    extern volatile int COUNTER;
  #ifdef __riscv_atomic
    __atomic_fetch_add (&COUNTER, 1, __ATOMIC_RELAXED);
  #else
    COUNTER++;
  #endif
  }

The attribute tells the C compiler to use callee-save for all registers, so the handler has to "pay as it goes" to use registers, and only save the full caller-save set if it makes a nested regular C call. The attribute also tells the C compiler to align the function entry point on an 8-byte boundary.

   .align 3
      # Inline non-preemptible interrupt handler.
      # Only safe for horizontal interrupts.
   foo:
      addi sp, sp, -FRAMESIZE      # Create a frame on stack.
      sw a0, OFFSET(sp)            # Save working register.
      sw x0, INTERRUPT_FLAG, a0    # Clear interrupt flag.
      sw a1, OFFSET(sp)            # Save working register.
      la a0, COUNTER               # Get counter address.
      li a1, 1
      amoadd.w x0, (a0), a1        # Increment counter in memory.
      lw a1, OFFSET(sp)            # Restore registers.
      lw a0, OFFSET(sp)
      addi sp, sp, FRAMESIZE       # Free stack frame.
      mret                         # Return from handler using saved mepc.

With hardware vectoring, inline interrupt handlers can provide very rapid response for small tasks.

Note
The above entire handler executes in 13 instructions. The INTERRUPT_FLAG store and the la require two instructions each to build up a global address. A simple pipeline would encounter two pipeline flushes (on entry and on exit), plus the cycles taken to fetch the hardware vector entry.

These inline handlers can be used with the original CLINT design as well as the new CLIC design.

To take advantage of hardware preemption in the new CLIC design, inline handlers must save and restore mepc and mcause before enabling interrupts:

   .align 3
      # Inline preemptible interuppt handler.
      # Only safe for horizontal interrupts.
   foo:
      #----- Interrupts disabled on entry ---#
      addi sp, sp, -FRAMESIZE      # Create a frame on stack.
      sw a0, OFFSET(sp)            # Save working register.
      csrr a0, mcause              # Read cause.
      sw a1, OFFSET(sp)            # Save working register.
      csrr a1, mepc                # Read epc.
      csrrsi x0, mstatus, MIE      # Enable interrupts.
      #----- Interrupts enabled ---------#
      sw a0, OFFSET(sp)            # Save cause on stack.
      sw x0, INTERRUPT_FLAG, a0    # Clear interrupt flag.
      sw a1, OFFSET(sp)            # Save epc on stack.
      la a0, COUNTER               # Get counter address.
      li a1, 1
      amoadd.w x0, (a0), a1        # Increment counter in memory.
      lw a1, OFFSET(sp)            # Restore epc
      lw a0, OFFSET(sp)            # Restore cause
      csrrci x0, mstatus, MIE      # Disable interrupts.
      #----- Interrupts disabled  ---------#
      csrw mepc, a1                # Put epc back.
      lw a1, OFFSET(sp)            # Restore a1.
      csrw mcause, a0              # Put cause back.
      lw s0, OFFSET(sp)            # Restore s0.
      addi sp, sp, FRAMESIZE       # Free stack frame.
      mret                         # Return from handler.
      #------------------------------------#
Note
This version requires 10 more instructions, but reduces the time a preempting interrupt has to wait from a 13-instruction window to a 6-instruction window (the instruction that disables interrupts can be preempted before committing).
Warning
This form cannot be used with the existing CLINT scheme, unless the original interrupt pending signal is cleared before re-enabling interrupts.

7. Calling C-ABI functions as Interrupt Handlers

An alternative model is where all interrupt handler routines use the standard C ABI. In this case, the CLIC would use no hardware vectoring for the C ABI handlers and instead use a common software trampoline, which uses the mnxti instruction to obtain the trap-handler address. The code sequence below is annotated with an explanation of its operation.

7.1. C-ABI Trampoline Code

  # Example Unix C ABI interrupt trampoline.
  # Only safe for horizontal interrupts.
  # FRAMESIZE should be defined appropriately to hold saved context with ABI-specified alignment.
  # OFFSET should be replaced with individual stack frame locations.
  # Register save/restore pseudo-code should be expanded to individual instructions.

  irq_enter:
  #----Interrupts disabled for 7 + SREGS instructions, where SREGS is number of registers saved. (1)
    addi sp, sp, -FRAMESIZE # Allocate space on stack. (2)
    sw a1, OFFSET(sp)       # Save a1.
    csrr a1, mcause         # Get mcause of interrupted context.
    sw a0, OFFSET(sp)       # Save a0.
    csrr a0, mepc           # Get mepc of interrupt context.
    bgez a1, handle_exc     # Handle synchronous exception. (3)
    sw a0, OFFSET(sp)       # Save mepc.
    sw a1, OFFSET(sp)       # Save mcause of interrupted context.
    sw a2-a7, OFFSET(sp)    # Save other argument registers.
    sw t0-t6, OFFSET(sp)    # Save temporaries.
    sw ra, OFFSET(sp)       # 1 return address (5)
    csrrsi a0, mnxti, MIE   # Get highest current interrupt and enable interrupts.
                            # Will return original interrupt if no others appear. (6)
  #----Interrupts enabled ---------------------(7)
    beqz a0, exit           # Check if original interrupt vanished. (8)

  service_loop:             # 5 instructions in pending-interrupt service loop.
    lw a1, (a0)             # Indirect into handler vector table for function pointer. (9)
    csrrsi x0, mstatus, MIE # Ensure interrupts enabled. (10)

    jalr a1                 # Call C ABI Routine, a0 has interrupt ID encoded. (11)
                            # Routine must clear down interrupt in CLIC.
    csrrsi a0, mnxti, MIE   # Claim any pending interrupt at level > mcause.pil (12)
    bnez a0, service_loop   # Loop to service any interrupt. (13)

  #--- Restore ABI registers with interrupts enabled -(14)
    lw ra, OFFSET(sp)       # Restore return address
    lw t0-t6, OFFSET(sp)    # Restore temporaries.
    lw a2-a7, OFFSET(sp)    # Restore other arguments.
    lw a1, OFFSET(sp)       # Get saved mcause,
  exit:                     # Fast exit point.
    lw a0, OFFSET(sp)       # Get saved mepc.

    csrrci x0, mstatus, MIE # Disable interrupts (15)
  #---- Critical section with interrupts disabled -----------------------
    csrw mcause, a1         # Restore previous context.

    lw a1, OFFSET(sp)       # Restore original a1 value.
    csrw mepc, a0           # Restore previous context.

    csrrci a0, mnxti, MIE   # Claim highest current interrupt. (16)
    bnez a0, service_loop   # Go around if new interrupt.

    lw a0, OFFSET(sp)       # Restore original a0 value.
    addi sp, sp, FRAMESIZE  # Reclaim stack space.
    mret                    # Return from interrupt.
  #-----------------------------------------------------------------------
  #-----------------------------------------------------------------------
   handle_exc:
    # ...
    # Perform exception processing with interrupts disabled (4)
    # ...
    addi sp, sp, FRAMESIZE   # Reclaim stack space.
    mret # Return from exception
  #----------------------------------------------------------------------
  1. An initial interrupt (II) causes entry to the handler with interrupts disabled, and mepc and mcause CSRs hold values representing the original interrupted context (OIC), including the PC in mepc, the privilege mode in mpp (visible in both mcause and mstatus), the interrupt level in {pil} (in mcause) and the interrupt enable state in mpie (visible in both mcause and mstatus). The mcause CSR and the mintstatus CSRs additionally hold information on the interrupt to be handled, including exccode in mcause and mil in mintstatus.

  2. The interrupt trampoline needs sufficient space to store the OIC’s caller-save registers as well as its epc and cause values, which are saved in a frame on the memory stack to support preemption. This routine is M-mode only so does not need to consider swapping stacks from other privilege modes. A simple constant bump of the stack pointer sp is sufficient to provide space to store the OIC.

  3. The trap handler could have been entered by a synchronous exception instead of an interrupt, which can be determined by examining the sign bit of the returned mcause value. If the trap was for an exception (sign bit zero), the code jumps to exception handler code while keeping interrupts disabled.

  4. The exception handler code is located here out of line to reduce performance impact on interrupts. The main body of the trampoline only handles interrupts.

  5. If this was an interrupt, the trampoline entry code continues to save all the caller-save registers to the stack. This is done with interrupts disabled, as even if an interrupt arrived with a higher interrupt level it would still require all registers to be saved.

  6. When mnxti is read here, the interrupt inputs to the CLIC might have changed from the time the handler was initially entered. The return value of mnxti, which holds a pointer to an entry in the trap vector table, is saved in register a0 so it can be passed as the first argument to the software-vectored interrupt handler, where it can be used to reconstruct the original interrupt id in the case where multiple vector entries use a common handler. There are multiple cases to consider, all of which are handled correctly by the definition of mnxti:

    • The II is still the ranking interrupt (no change). In this case, as the level of the II will still be higher than pil from the OIC, mil and exccode will be rewritten with the same value that they already had (effectively unchanged), and mnxti will return the table entry for the II.

    • The II has been superceded by a higher-level non-SHV interrupt. In this case, mil will be set to the new higher interrupt level, exccode will be updated to the new interrupt id, and mnxti will return the vector table entry for the new higher-level interrupt. The OIC is not disturbed, retaining the original epc and the original pil. This case reduces latency to service a more-important interrupt that arrives after the state-save sequence was begun for the less-important II. The II, if still pending-enabled, will be serviced sometime after the higher-level interrupt as described below.

    • The II has been superceded by a higher-priority non-SHV interrupt at the same level. This operates similarly to the previous case, with exccode updated to the new interrupt id. Because the lower-priority interrupt had not begun to run its service routine, this optimization preserves the property that interrupt handlers at the same interrupt level but different priorities execute atomically with respect to each other (i.e., they do not preempt each other).

    • The II has disappeared and a lower-ranked non-SHV interrupt, which has interrupt level greater than the OIC’s pil is present in CLIC. In this case, the mil of the handler will be reduced to the lower-ranked interrupt’s level, exccode will be updated with the new interrupt id, and mnxti will return a pointer to the appropriate handler in table. In this case, the new lower-ranked interrupt would still have caused the original context to have been interrupted to run the handler, and the disappearing II has simply caused the lower-ranked interrupt’s entry and state-save sequence to begin earlier.

    • The II has disappeared and either there is no current interrupt from the CLIC, or the current ranking interrupt is a non-SHV interrupt with level lower than {pil}. In this case, the mil and exccode are not updated, and 0 is returned by mnxti. The following trampoline code will then not fetch a vector from the table, and instead just restore the OIC context and mret back to it. This preserves the property that the OIC completes execution before servicing any new interrupt with a lower or equal interrupt level.

    • The II has been superceded by a higher-level SHV interrupt. In this case, the mil and exccode are not updated, and 0 is returned by mnxti. Once interrupts are reenabled for the following instruction, the processor will preempt the current handler and execute the vectored interrupt at a higher interrupt level using the function pointer stored in the vector table.

  7. Interrupts are now enabled. If a higher-level SHV interrupt had arrived while interrupts were disabled, then the current handler will be preempted and execution starts at the SHV handler address. If a non-vectored higher-level interrupt arrives now, it will also preempt the current handler and begin a nested state-save sequence at the handler entry point irq_enter.

  8. The branch checks if the II disappeared or if a higher priority SHV at the same level appeared, in which case the current handler returns to the OIC. As most registers have not been touched, the routine can skip past most of the register restore code. This preserves the property that interrupts (SHV or non-SHV) at the same level do not preempt each other.

  9. The value returned by mnxti is used to index the vector table and return the function pointer.

  10. This csrrsi instruction enables interrupts and is redundant when proceeding sequentially from the first mnxti read (6) or if looping back from the end of the service_loop (13). However, it is required on the backward path from (16) to re-enable interrupts to allow preemption. It is scheduled after the table lookup to use what will often be a load-use delay slot.

  11. The jalr instruction actually calls the C ABI function that implements the handler. Interrupts are enabled at this point, so the C function can be preempted at any time by an interrupt with a higher level than current mil.

  12. Once the handler returns, another read of mnxti checks if there are any more interrupts to service. Interrupts remain enabled. The csrrsi includes a redundant set of the mie interrupt enable to force the CSR instruction to update CSR state. Only non-SHV interrupts with a level greater than pil will be serviced in this loop. Note that mil can decrease from its current value on the mnxti read. mil should not increase in this code, as interrupts are enabled here and if a higher-level interrupt was ready, it should have preempted this instruction.

  13. If there was another appropriate interrupt to service, the code loops back to perform the next handler call. The service_loop only contains 5 instructions, allowing multiple back-back interrupts to be handled without saving and restoring contexts. On a simple pipeline with a one-cycle load-use penalty, single-cycle CSR access, and a one-cycle taken-branch penalty, the service loop can initiate a new interrupt service with only 7 clock cycles of overhead per handler call.

  14. This instruction sequence restores the OIC. Interrupts are still enabled, so preemption is allowed during this restore.

  15. Interrupts are disabled for the final steps of restoring the OIC, which requires loading mcause and mepc from the stacked values, and recovering the final register values from the OIC.

  16. A final read of mnxti is performed before returning, to reduce the maximum interrupt latency. If a suitable interrupt arrives, it can be serviced without saving context. The csrrci instruction includes a redundant clear of the interrupt enable bit to ensure the CSR state updates occur. Interrupts must stay disabled until after the following branch to maintain the critical section used to restore the OIC in the case that there is no interrupt to service.

The following table summarizes the machine state changes that occur at the first mnxti:

IC    at entry |->           |       at first nxti (6)
il     CLIC                  |    CLIC
    level id V |->  mil code | level id V    |-> mil code rd
p    e<=p  ? ? |->           |                               # Shouldn't happen
p    e>p   i 0 |->   e    i  |   f>p  j 0    |->  f    j   T # Same or superceded interrupt
p    e>p   i 0 |->   e    i  |   f>p  j 1    |->  e    i   0 # Ignore vectored interrupt
p    e>p   i 0 |->   e    i  |   f<=p j ?    |->  e    i   0 # Interrupt disappeared
p    e>p   i 1 |->   e    i  |                               # Won't be in trampoline

7.2. Revised C-ABI for Embedded RISC-V

The overhead to save and restore registers in the interrupt trampoline can be reduced with a new embedded ABI that reduces the number of caller-save registers. Work is underway to define such an ABI, but it is likely to require around 7 integer registers to be saved/restored instead of 16 in the standard Unix ABI.

This will result in 18 instructions executed in the trampoline code before arriving at the correct handler function, of which 9 are stores (saving 7 registers plus 2 words for mepc and mcause).

7.3. Analysis of worst-case interrupt latencies for C-ABI trampoline

The following analysis assumes a system with M-mode only and a new embedded ABI requiring 7 caller-save registers to be saved and restored. For cycle timings, we assume a simple 3-stage pipeline that has a one-cycle taken-branch or pipeline flush penalty, a one-cycle load-use delay, and single-cycle CSR access. This simple model ignores effects from contention in shared memory structures, or pipeline hazards from continuing long-latency operations in the interrupted code.

There are several cases to consider for the worst-case latency for a C-ABI higher-level interrupt handler that preempts lower-level code.

If an interrupt arrives while interrupts are enabled, either inside or outside of a current handler, the processor will jump directly to irq_enter at the new interrupt level. The system must flush the execution pipeline and then execute 18 instructions, the last of which is the jalr that calls the handler function. These 18 instructions execute in 20 cycles using the simple pipeline model.

When interrupts are disabled, the arriving preempting handler could be delayed. If the preempting interrupt arrives while interrupts are disabled during the initial entry sequence (1)--(6), there will be no additional delay as the first mnxti instruction (6) will cause the higher-level interrupt handler to be invoked, replacing the original interrupt cause.

If the preempting interrupt arrives after interrupts are disabled (15) but before mnxti is read (16), then the trampoline will observe the new interrupt during execution of the mnxti read (16), and take a short branch back to the service_loop, which is lower latency than the interrupt-disabled case.

If the preempting interrupt arrives after the read of mnxti commits (16), then the interrupt has to wait an additional 4 instructions until the mret reenables interrupts, at which point the interrupt will be taken and the handler entered at irq_enter. In the simple pipeline model, mret adds an additional pipeline flush cycle, so the preemption latency is 20+5 cycles, which represents the worst-case for a preempting C-ABI interrupt handler.

8. Interrupt-Driven C-ABI Model

For many embedded systems, after initialization, essentially all code is run in response to an interrupt, interrupt levels are used to prioritize execution of different tasks, and the processor should sleep inbetween interrupt events to save energy.

The following code can be used as the background code that runs at interrupt level 0 and which when there is no active work to do, puts the processor to sleep with no active context, waiting for an interrupt using the wfi instruction. The code is entered at the enter_loop location and never returns directly.

    # Source code for interrupt-driven model background code.
sleep:
    csrrci x0, mstatus, MIE # Disable interrupts.  (1)
    wfi                     # Processor waits for next interrupt event.
    csrrsi a0, mnxti, MIE   # Gather interrupt details, and enable interrupts. (2)
    beqz a0, sleep          # Go back to sleep if no interrupt (will be preempted if SHV). (3)

service_loop: (4)
    lw a1, (a0)             # Get handler address.
    csrrsi x0, mstatus, MIE # Enable interrupts
    jalr a1                 # Call C-ABI handler routine
    csrrsi a0, mnxti, MIE   # Claim any pending interrupt at level > 0
    bnez a0, service_loop   # Loop to service any interrupt.

    # This is also entry point to begin sleeping.
enter_sleep: (5)
    la a0, sleep
    csrci x0, mstatus, MIE  # Disable interrupts.
    #--- Interrupts disabled
    csrw mepc, a0           # Initialize mepc to point to sleep
    li a0, (MMODE)<<PP|(0)<<PIL|(1)<<PIE
    csrw mcause, a0         # Initialize mcause to have pp=M, pil=0, pie=1
    mret                    # Jump to sleep at level 0 with interrupts enabled.
    #--- Interrupts enabled
  1. The sleep loop is used to stall the processor while waiting for work and is always entered at interrupt level 0. Interrupts are disabled, then a wfi is executed. The wfi will stall the processor until some event occurs. When an event, including an interrupt occurs, the wfi retires. Because interrupts are disabled, the hart does not jump to an interrupt handler but instead executes the next instruction, avoiding context save/restore overhead.

  2. The read of mnxti will determine if any non-SHV interrupt is available, and if so return a pointer to the table entry. Interrupts are enabled by this instruction to allow SHV interrupts to be taken via preemption.

  3. The value in a0 checked by the branch can be zero for two reasons. Either there was no interrupt detected or an SHV interrupt was detected. If there was no interrupt, the branch loops back to put the hart to sleep. Interrupts are enabled, so any SHV interrupt (which all have higher interrupt level than the current interrupt level of 0) will preempt the branch’s execution and call the SHV handler. Once the SHV handler returns, the branch will resume and cause execution to return back to the sleep_loop.

  4. The service loop is identical to that in the C-ABI interrupt handler, except that the previous interrupt level is 0, so all pending interrupts will be serviced in the loop before the loop exits. Interrupts are enabled, so preemption is allowed for both C-ABI trampoline and SHV interrupts. When an SHV interrupt at the same or lower interrupt level is the next to be serviced, the mnxti instruction will return 0 causing execution to drop out of the loop. The following code will reinitialize the hart’s interrupt level to 0, and disable interrupts for one instruction, to ensure the SHV interrupt will be taken.

  5. This code initializes mepc and mcause then uses an mret to jump to the sleep loop while simultaneously reseting interrupt level to 0 and enabling interrupts. This is also the entry point to initiate interrupt-driven execution. Interrupts are enabled to allow SHV interrupts to preempt execution on the first instruction in sleep (which disables interrupts again).

This code does not increase worst-case interrupt latency over that of the C-ABI trampoline.

9. Alternate Interrupt Models for Software Vectoring

Platforms may only implement non-vectored CLIC mode (mtvec=?000010) without selective hardware vectoring (clic.nvbits=0), in which case, hardware vectoring can be emulated by a single software trampoline present at NBASE using the separate vector table address in mtvt. There are several different software approaches possible, depending on system requirements and constraints, as detailed in following subsections.

9.1. gp trampoline to inline interrupt handlers in single privilege mode

Where interrupts are known to be generated and handled in a single privilege mode (i.e., M-mode only systems, or U-mode interrupt handlers), a three-instruction sequence using the gp register to hold the handler address can be used to indirect to an inline interrupt handler of the type described in Inlines.

    # Software-vectored interrupt servicing.
    # Only safe for horizontal interrupts.
    # Must be placed three instructions back from gp.
irq_enter:
    csrrci gp, mnxti, MIE   # Overwrite gp, keep interrupts disabled.
    beqz gp, handle_exc     # Encountered exception.
    jalr gp, gp             # Recreate gp and jump to handler.
gp:                         # Must be right before system's gp location.
    # ... gp data section

    # Must be within range of beqz instruction.
handle_exc:
    # Has to recreate gp.

The three-instruction sequence relies on the jalr instruction recreating the value in the gp register, which is a known constant pointing into the middle of the global data area, by placing the jalr directly before the gp location in memory. The routine jumped to by the jalr does not return via a j ra but instead ends with an mret.

Note
This constraint on memory layout might not always be possible, particularly if the system does not allow placing executable memory right next to read-write memory, for example if the system does not allow a protection boundary to be placed at 'gp' and if executable code must not be writeable.

The code can be used with preemptible inline interrupt handlers.

9.2. Trampoline for preemptible inline handlers

This section describes a more general software-trampoline scheme for calling preemptible inline handlers, which factors out the mepc/mcause save code into the trampoline, and which uses a different interrupt handler calling convention.

The interrupt handlers for this scheme have a calling convention where there is one caller-save argument register a0 that passes in the handler address to distinguish different interrupt inputs, and one temporary register a1 that is also caller-save. These two registers had to be saved already by the trampoline. All other registers are callee-save, except for the return address ra. The handler normally returns with a regular j ra.

  # Example handler with new calling convention.
  # Only safe for horizontal interrupts.
  # Handlers have two temporary registers available, a0, a1.
handler_example:
  sw x0, INTERRUPT_FLAG, a0     # Clear interrupt flag.
  la a0, COUNTER                # Get counter address.
  li a1, 1                      # Increment value.
  amoadd.w x0, (a0), a1         # Bump counter.
  j ra

  # Interrupt trampoline code.
irq_enter:
  #----- Interrupts disabled on entry ---#
  addi sp, sp, -FRAMESIZE      # Create a frame on stack.
  sw a0, OFFSET(sp)            # Save working register.
  csrr a0, mcause              # Read cause.
  bgez a0, handle_exc          # Handler exception.
  sw a1, OFFSET(sp)            # Save working register.
  csrr a1, mepc                # Read epc.
  sw a0, OFFSET(sp)            # Save cause
  csrrsi a0, mnxti, MIE        # Get highest interrupt, enable interrupts.
  #----- Interrupts enabled ---------#
  beqz a0, exit
  sw a1, OFFSET(sp)            # Save epc.
  sw ra, OFFSET(sp)            # Save return address.

irq_loop:
  lw a1, (a0)                  # Get function pointer.
  jalr a1                      # Call handler code.
  csrrsi a0, mnxti, MIE        # Get any next interrupt.
  bnez a0, irq_loop            # Service interrupt if any.

  lw ra, OFFSET(sp)            # Restore ra.
  lw a1, OFFSET(sp)            # Get epc.
exit:
  lw a0, OFFSET(sp)            # Get cause.
  csrrci x0, mstatus, MIE      # Disable interrupts.
  #----- Interrupts disabled  ---------#
  csrw mepc, a1                # Put epc back.
  lw a1, OFFSET(sp)            # Restore a1.
  csrw mcause, a0              # Put cause back.
  lw a0, OFFSET(sp)            # Restore a0.
  addi sp, sp, FRAMESIZE       # Free stack frame.
  mret                         # Return from handler.
  #------------------------------------#

handle_exc:
  # ...
  # Handle exception with interrupts disabled.
  # ...
  addi sp, sp, FRAMESIZE  # Deallocate stack space
  mret                    # Return from handler.
  #------------------------------------#

This interrupt handler can be used together with the wfi sleep background routine shown above.

10. Managing Interrupt Stacks Across Privilege Modes

Interrupt handlers need to have a place to save the previous context’s state to provide working registers for the handler code. If a handler can be entered from a lower-privilege mode, a pointer to some safe memory for the context save must be swapped in at entry to the higher-privileged handler to avoid security holes. The RISC-V privileged architecture provides the mscratch register to hold this information for a higher-privilege mode while executing in a lower-privilege mode. For the following discussion and code examples, the assumption is that mscratch is used to hold the higher-privilege-mode stack pointer but other software conventions are possible (e.g., mscratch points to a thread context block).

Existing RISC-V ABIs allow addresses immediately below the stack pointer to be overwritten by interrupt service routines. The current stack pointer in sp (x2) should be swapped with mscratch whenever a handler is entered from a lower-privilege mode, but should not be swapped if entered from another handler in the same privilege mode, including when preempting an existing interrupt handler. At exit from a handler, the lower-privilege stack pointer should be swapped back in if transitioning back to the lower-privilege mode.

10.1. Software Privileged Stack Swap

In this convention, when code is running in a lower privilege mode, mscratch holds the stack pointer for the higher-privilege mode. When the higher-privilege mode is entered, mscratch is set to zero to signal to any preempting handlers that the stack pointer has already been swapped.

The old stack pointer is saved to new stack frame before new frame is created by bumping stack pointer, but this is done with interrupts disabled.

  # This code is out of line to reduce worst-case preemption latency.
enter_M:
  sw sp, OFFSET-FRAMESIZE(sp) # Save previous mscratch (M-mode sp)
  addi sp, sp, -FRAMESIZE   # Create a frame on stack.
  sw a0, OFFSET(sp)         # Save a register.
  csrrw a0, mscratch, 0     # Get previous sp, and zero mscratch.
  sw a0, OFFSET(sp)         # Save previous sp (U-mode sp)
  j  continue               # Jump back into handler

irq_enter:
  #----- Interrupts disabled on entry ---#
  csrrw sp, mscratch, sp       # Swap stack pointer and scratch.
  bnez mscratch, enter_M       # Check if entering M-mode
  csrrw sp, mscratch, sp       # Already in M-mode, so swap sp back.
  sw sp, OFFSET-FRAMESIZE(sp)  # Save previous sp to stack.
  addi sp, sp, -FRAMESIZE      # Create a frame on stack.
  sw x0, OFFSET(sp)            # Save previous mscratch to stack (was zero).
  sw a0, OFFSET(sp)            # Save a register.
continue:
  csrr a0, mcause              # Read cause.
  bgez a0, handle_exc          # Handle exception.
  sw a1, OFFSET(sp)            # Save working register.
  csrr a1, mepc                # Read epc.
  sw a0, OFFSET(sp)            # Save cause
  csrrsi a0, mnxti, MIE        # Get highest interrupt, enable interrupts.
  #----- Interrupts enabled ---------#
  beqz a0, exit
  ...

  #---- Critical section with interrupts disabled -----------------------
    ...

    lw a0, OFFSET(sp)       # Get previous mscratch.
    csrw mscratch, a0       # Put back in mscratch.
    lw a0, OFFSET(sp)       # Restore original a0 value.
    lw sp, OFFSET(sp)       # Restore previous sp
    mret                    # Return from interrupt.
  #-----------------------------------------------------------------------

This code can be used in a secure model where user-level code has one stack, and all interrupts and exceptions are handled on a second M-mode-only stack. In addition, background non-handler code in M-mode can either use the same M-mode stack as the interrupt handler, or a separate M-mode stack. The only difference is in the value held in mscratch while the M-mode background thread is running (either 0 to indicate use the existing stack pointer in sp or non-zero to indicate this stack pointer should be used in the handler.

10.2. Optional Scratch Swap CSR (mscratchcsw) for multiple privilege modes

The above software scheme adds 7 instructions to the interrupt code path when preempting the same privilege mode, and adds an additional 6 instructions (13 total including two taken branches) for interrupts from a lower-privilege mode into a higher-privileged mode.

To accelerate interrupt handling with multiple privilege modes, a new CSR mscratchcsw can be defined for all but the lowest privilege mode to support conditional swapping of the mscratch register when transitioning between privilege modes. The CSR instruction is used once at the entry to a handler routine and once at handler exit, so only adds two instructions to the interrupt code path. Though designed to be used with csrrw instructions, these CSRs can be accessed with any CSR instruction.

For all CSR instructions accessing mscratchcsw, the value written into rd is either mscratch if mpp is different than the current privilege mode, or rs1 if mpp is the same as the current privilege mode. The mscratch register is only written if there is a privilege mode difference, and if so, it is written obeying the usual CSR read-modify-write conventions (e.g., swap/set/clear bits) using the original mscratch value as one source operand and the other source operand specified as usual in the instruction.

Note
This is different than a regular CSR instruction as the value returned is different from the value used in the read-modify-write operation.
Note
The CSR instructions are defined to always copy a result (mscratch or rs1) to the rd destination to simplify implementations using register renaming, and in normal use the instructions set both rs1 = sp and rd = sp.
  csrrw rd, mscratchcsw, rs1

  // Pseudocode operation.
  if (mcause.mpp!=M-mode) then {
      t = rs1; rd = mscratch; mscratch = t;
  } else {
      rd = rs1; // mscratch unchanged.
  }

  // Usual use: csrrw sp, mscratchcsw, sp
Note
To avoid virtualization holes, software cannot directly read the hart’s current privilege mode. The swap instruction will trap if software tries to access a given mode’s mscratchcsw CSR from a lesser-privileged mode, so the new CSR does not open a virtualization hole.

10.2.1. Stack Swap Example Code

Interrupt handlers running in the lowest privilege mode do not need to swap stack pointers, as they will only be entered by a horizontal interrupt from the same privilege mode. In systems with multiple privilege modes, handlers running in higher privilege modes must account for vertical interrupts taken from a lower privilege mode (in which case the stack pointer must be swapped) as well as horizontal interrupts from the same privilege mode.

    # Example of inline interrupt with stack swapping.
   .align 3
   foo:
      csrrw sp, mscratchcsw, sp    # Conditionally swap in stack pointer.
      addi sp, sp, -FRAMESIZE      # Create a frame on stack.
      sw s0, OFFSET(sp)            # Save working register.
      sw x0, INTERRUPT_FLAG, s0    # Clear interrupt flag.
      sw s1, OFFSET(sp)            # Save working register.
      la s0, COUNTER               # Get counter address.
      li s1, 1
      amoadd.w x0, (s0), s1        # Increment counter in memory.
      lw s1, OFFSET(sp)            # Restore registers.
      lw s0, OFFSET(sp)
      addi sp, sp, FRAMESIZE       # Free stack frame.
      csrrw sp, mscratchcsw, sp    # Conditionally swap out stack pointer.
      mret                         # Return from handler using saved mepc.
    # Example of inline preemptible interrupt with stack swapping.
   .align 3
   foo:
      #----- Interrupts disabled on entry ---#
      csrrw sp, mscratchcsw, sp    # Conditionally swap in stack pointer.
      addi sp, sp, -FRAMESIZE      # Create a frame on stack.
      sw s0, OFFSET(sp)            # Save working register.
      sw s1, OFFSET(sp)            # Save working register.
      csrr s0, mcause              # Read cause.
      csrr s1, mepc                # Read epc.
      csrrsi x0, mstatus, MIE      # Enable interrupts.
      #----- Interrupts enabled ---------#
      sw s0, OFFSET(sp)            # Save cause on stack.
      sw x0, INTERRUPT_FLAG, s0    # Clear interrupt flag.
      sw s1, OFFSET(sp)            # Save epc on stack.
      la s0, COUNTER               # Get counter address.
      li s1, 1
      amoadd.w x0, (s0), s1        # Increment counter in memory.
      lw s1, OFFSET(sp)            # Restore epc
      lw s0, OFFSET(sp)            # Restore cause
      #----- Interrupts disabled  ---------#
      csrrci x0, mstatus, MIE      # Disable interrupts.
      csrw mepc, s1                # Put epc back.
      csrw mcause, s0              # Put cause back.
      lw s1, OFFSET(sp)            # Restore s1.
      lw s0, OFFSET(sp)            # Restore s0.
      addi sp, sp, FRAMESIZE       # Free stack frame.
      csrrw sp, mscratchcsw, sp    # Conditionally swap out stack pointer.
      mret                         # Return from handler.
      #------------------------------------#
  # Example C-ABI interrupt trampoline with stack swapping.

  irq_enter:
  #----
    csrrw sp, mscratchcsw, sp # Conditionally swap in stack pointer.
    addi sp, sp, -FRAMESIZE   # Allocate space on stack.
    # ...
    # Everything else same as above.
    # ...
    addi sp, sp, FRAMESIZE    # Reclaim stack space.
    csrrw sp, mscratchcsw, sp # Conditionally swap back stack pointer.
    mret                      # Return from interrupt.
  #-----------------------------------------------------------------------
  #-----------------------------------------------------------------------
   handle_exc:
    # ...
    # Perform exception processing with interrupts disabled
    # ...
    addi sp, sp, FRAMESIZE    # Reclaim stack space.
    csrrw sp, mscratchcsw, sp # Conditionally swap back stack pointer.
    mret                      # Return from exception
  #----------------------------------------------------------------------

In all cases, conditionally swapping the stack to account for potential privilege-mode changes adds two extra instructions to all interrupt handlers.

11. Separating stack per interrupt level

Within a single privilege mode, it can be useful to separate interrupt handler tasks from application tasks to increase robustness, reduce space usage, and aid in system debugging. Interrupt handler tasks have non-zero interrupt levels, while application tasks have an interrupt level of zero.

11.1. Optional Scratch Swap CSR (mscratchcswl) for interrupt levels

A new mscratchcswl CSR is added to support faster swapping of the stack pointer between interrupt and non-interrupt code running in the same privilege mode.

  csrrw rd, mscratchcswl, rs1

  // Pseudocode operation.
  if ( (mcause.pil==0) != (minstatus.mil==0) ) then {
      t = rs1; rd = mscratch; mscratch = t;
  } else {
      rd = rs1; // mscratch unchanged.
  }

  // Usual use: csrrw sp, mscratchcswl, sp

This new CSR operates similarly to mscratchcsw except that the swap condition is true when the interrupter and interruptee are not both application tasks or not both interrupt handlers.

12. Safe execution of urgent interrupts in lower-privileged modes

Note
This is a preliminary proposal.

In some case it is desirable to preempt a higher-privilege mode to execute time-critical interrupts in a lower-privilege mode. These cases are handled by registering an interrupt handler trampoline in the higher-privilege mode at the appropriate interrupt level which then sets up a call to the user-level interrupt code.

The higher-privilege trampoline code has to perform the following operations:

  • save entire higher-privilege context state

  • clear entire context state

  • ensure "trap on lower-privilege RET instruction" is set

  • set timeout timer

  • redirect to lower-privilege handler

  • lower-privilege handler runs, and either exits with a RET that causes a trap, or timeout timer interrupt

  • restore entire higher-privilege context state

The mstatus register already contains a TSR bit to trap on supervisor return. An additional TUR bit in mstatus would allowing trapping user-level URET instructions to support such interrupts.

A complication is that the user-level interrupt handler might be preempted by another user-level handler at a higher interrupt level, which will execute URET but for a different context than that intiated by the higher-privilege mode. This problem can be avoided by always running the urgent lower-privilege handlers at the highest interrupt level in the the lower-privilege mode.

13. CLIC Interrupt IDs

The original CLINT interrupts retain their interrupt ID in CLIC mode. The clicintctl settings are now used to delegate these interrupts as required.

An additional CLIC software interrupt bit (csip) is provided. This is generally available for software use, but is usually used for the local background interrupt thread.

CLIC interrupt inputs are allocated IDs beginning at interrupt ID 16. Any fast local interrupts that would have been connected at interrupt ID 16 and above should now be mapped into corresponding inputs of the CLIC.

ID  Interrupt   Note

 0  usip        User software Interrupt
 1  ssip        Supervisor software Interrupt
 2  reserved
 3  msip        Machine software interrupt

 4  utip        User timer interrupt
 5  stip        Supervisor timer interrupt
 6  reserved
 7  mtip        Machine timer interrupt

 8  ueip        User external (PLIC) interrupt
 9  seip        Supervisor external (PLIC) interrupt
10  reserved
11  meip        Machine external (PLIC) interrupt

12  csip        CLIC software interrupt
13  reserved
14  reserved
15  reserved

16+ inputs      CLIC external inputs

14. CLIC memory map

14.1. CLIC/CLINT shared memory map

The CLIC reuses the CLINT memory map for the msip, mtimecmp, and mtime memory-mapped registers. This simplifies providing both CLINT and CLIC mode in the same hardware. The CLIC/CLINT memory map is given relative to a local interrupt base address (in SiFive Freedom platforms, the local interrupt base address is 0x0200_0000).

 CLIC/CLINT shared memory map

 Offset  Extent  Permissions   Contents
 0x0000  4B      RW            hart0 msip
 0x0004  4B      RW            hart1 msip
 ..
 0x4000  4B      RW            hart0 mtimecmp lo
 0x4004  4B      RW            hart0 mtimecmp hi
 0x4008  4B      RW            hart1 mtimecmp lo
 0x400c  4B      RW            hart1 mtimecmp hi
 ..
 0xbff8  4B      RW            mtime lo
 0xbffc  4B      RW            mtime hi

14.2. M-Mode CLIC regions

Each hart has a separate CLIC accessed by a separate address region. The following is the memory map of the 4KiB memory region designed to support M-mode access. Where a system has PMP, this region must be made accessible to the M-mode software running on the hart.

 CLIC memory map
 M-mode regions
 Offset
0x000+i   1B/input    R or RW       clicintip[i]
0x400+i   1B/input    RW            clicintie[i]
0x800+i   1B/input    RW            clicintctl[i]
0xc00     1B          RW            cliccfg

clicintip[i] is read-only for level-based interrupts and read-write
for edge-triggered interrupts.
Note
The clicintip[i] region is placed first as this is the only region usually written in interrupt handlers to clear down edge-triggered interrupt signal bits. The CLIC base address, which also corresponds to the start of the clicintip array, can be loaded with a single lui instruction to reduce instruction count in handlers that have to calculate the appropriate clicintip[i] address.

If an input i is not present in the hardware, the corresponding clicintip[i], clicintie[i], clicintctl[i] memory locations appear hardwired to zero.

In systems that support additional privilege modes on a hart, an additional memory region is defined for each privilege mode that provides a restricted view of the CLIC’s state.

14.3. S-Mode CLIC regions for M/S/U harts

Supervisor-mode CLIC regions only expose interrupts that have been configured to be supervisor-accessible via the M-mode CLIC region. System software must configure virtual memory and PMP permissions to only allow access to this region from appropriate supervisor-mode code.

Layout of Supervisor-mode CLIC regions
0x000+i   1B/input    R or RW       clicintip[i]
0x400+i   1B/input    RW            clicintie[i]
0x800+i   1B/input    RW            clicintctl[i]

Any interrupt i that is not accessible to S-mode appears as hard-wired zeros in clicintip[i], clicintie[i], and clicintctl[i].

Where cliccfg.nmbits = 0, all interrupts are M-mode only, and all are inaccessible to S-mode.

Where cliccfg.nmbits = 1, if clicintctl[i] is set to S-mode (bit 7 is clear), interrupt i is visible in the S-mode region except that only the low 7 bits of clicintctl[i] can be written via the S-mode memory region.

Where cliccfg.nmbits = 2, if bit 7 of clicintctl[i] is clear (S-mode or U-mode), interrupt i is visible through the S-mode region except that only the low 7 bits of clicintctl[i] can be written via the S-mode memory region. This allows the supervisor region to be used to selectively configure the interrupt as S-mode or U-mode.

14.4. U-Mode CLIC regions in M/U harts or M/S/U harts

User-mode CLIC regions only expose interrupts that have been configured to be user-accessible via the M-mode CLIC region. System software must configure virtual memory and PMP permissions to only allow access to this region from appropriate user-mode code.

Layout of user-mode CLIC regions
0x000+i   1B/input    R or RW       clicintip[i]
0x400+i   1B/input    RW            clicintie[i]
0x800+i   1B/input    RW            clicintctl[i]

Any interrupt i that is not accessible to U-mode appears as hard-wired zeros in clicintip[i], clicintie[i], and clicintctl[i].

Where cliccfg.nmbits = 0, all interrupts are M-mode only, and all are inaccessible to U-mode.

In M/U-only harts, where cliccfg.nmbits = 1, if clicintctl[i] is set to U-mode (bit 7 is clear), then interrupt i is visible in the U-mode region except that only the low 7 bits of clicintctl[i] can be written via the U-mode memory region.

In M/S/U harts, if cliccfg.nmbits< 2 then all interrupts are either M-mode or S-mode, and all are inaccessible to U-mode.

In M/S/U harts, where cliccfg.nmbits = 2, if clicintctl[i] is set to U-mode (bits 6 and 7 are clear), then interrupt i is visible in the U-mode region except that only the low 6 bits of clicintctl[i] can be written via the U-mode memory region.

14.5. CLIC memory map for Multiple Harts

In a system with multiple harts, the M-mode CLIC regions for all the harts are placed contiguously in the memory space, followed by the S-mode CLIC regions for all harts.

In the SiFive Freedom platforms, the memory map is as follows, supporting up to 512 CLICs in a platform:

  0x0200_0000   CLINT

  0x0280_0000   M-mode aperture for CLIC 0
  0x0280_1000   M-mode aperture for CLIC 1
  ...
  0x02A0_0000   HS-mode aperture for CLIC 0
  0x02A0_1000   HS-mode aperture for CLIC 1
  ...
  0x02C0_0000   S-mode aperture for CLIC 0
  0x02C0_1000   S-mode aperture for CLIC 1
  ...
  0x02E0_0000   U-mode aperture for CLIC 0
  0x02E0_1000   U-mode aperture for CLIC 1

14.6. Expanded CLIC Memory Map Option

Note
This is an early proposal.

The expanded CLIC memory map provides a simpler interface to the individual fields in clicintctl and now supports up to 4,096 total interrupt inputs. A systems supporting the expanded CLIC memory map must also support the original compressed CLIC memory map. The two CLIC memory regions will control the same hardware.

   Expanded CLIC memory map
   M-mode regions
   Offset
  0x0000+i   1B/input    R or RW   clicintip[i]
  0x1000+i   1B/input    RW        clicintie[i]
  0x2000     1B          RW        cliccfg
  0x4000+i*4 1B/input    RW        clicintctl[i].mode byte
  0x4001+i*4 1B/input    RW        clicintctl[i].level byte
  0x4002+i*4 1B/input    RW        clicintctl[i].priority byte
  0x4003+i*4 1B/input    RW        clicintctl[i].shv byte
  ...
  0x7FFF     1B/input    RW        clicintctl[4095].shv

clicintip[i] is read-only for level-based interrupts and read-write
for edge-triggered interrupts.

The expanded memory map retains the same byte address for interrupt pending and interrupt enable, but expands the interrupt control register into four separate byte fields. Each of the mode, level, priority, and selective-hardware vectoring settings is allocated a separate byte address. The bits that are writeable/readable in that byte are those selected by cliccfg. A single 32-bit memory instruction can access all four bytes controlling a single interrupt’s settings.

Any writable mode bits are made accessible in the least-significant bits of the first byte, with the upper bits hardwired to zero. If there are no mode bits, then the whole byte is hardwired to return zero.

Any writable level bits are made accessible in the most-significant bits of the level byte, while the least-significant bits return 1 when read.

Any writable priority bits are made accessible in the most-significant bits of the priority byte, while the least-significant bits return 1 when read.

If the selective hardware vectoring bit is supported it appears in the LSB of the shv byte. The upper bits return 0 when read.

Writing to the SHV byte when selective hardware vectoring is enabled can change the mode, level, or priority bytes also depending on the value in cliccfg, which determines which of these fields overlaps with SHV.

Example setting of cliccfg and resulting memory map for CLICINTCTLBITS=4

  cliccfg.nmbits = 0, nlbits=2, nshv=1

  Byte Offset  Bits       Meaning
  0x4000      0000_0000   Hardwired to zero
  0x4001      ll11_1111   Two writable level bits plus 6 trailing hardwired 1s
  0x4002      pp11_1111   Two writable priority bits plus 6 trailing hardwired 1s
  0x4003      0000_000s   One writable SHV bit that overlaps bit 6 of previous byte (lowest p bit)

  cliccfg.nmbits = 1, nlbits=1, nshv=0

  Byte Offset  Bits       Meaning
  0x4000      0000_000m   Single writable bit (1=M, 0=U)
  0x4001      l111_1111   One writable level bit plus 7 trailing hardwired 1s
  0x4002      ppp1_1111   Three writable priority bits plus 5 trailing hardwired 1s
  0x4003      0000_0000   Hardwired to zero.

Other privilege modes have a similar memory layout in a different memory region to support multiple privilege modes. In each lower-privilege aperture, only those interrupt inputs that have been configured by machine mode will appear in the lower-privilege memory aperture.

15. CLIC and Message-Signalled Interrupts (MSI)

Note
This is an early proposal.

PCI MSIs contain an interrupt number in the data portion of a memory store. The CLIC will be extended to add another address to which MSIs can be directed. A MSI store to that address will set the clicintip[i] bit corresponding to the MSI data value. The CLIC MSI address will reside in the same 0xc00 region as the cliccfg field, and will also be made available in S-mode and U-mode regions to allow MSIs to be restricted to S-mode and U-mode interrupt inputs respectively.

The same mechanism can also be used by other software and non-PCI devices.