[PLIC] Interrupt does not occur when disabling in the ISR before complete

Hi,

Point 1 - The Interrupt is not working
Point 2 - The Interrupt is working

The SiFive PLIC is tested on the Microchip PolarFire SoC.

1). The Interrupt does not occur when disabling the interrupt before interrupt complete, Is this expected behaviour with the SiFive PLIC?
With below Pseudo code, Claim the interrupt number and a call to the handler, the interrupt is disabled in the handler and there is no impact on the Interrupt Complete register when writing with the interrupt number, so the next interrupt(same) doesn’t occur even though the interrupt is enabled.

PLIC External Interrupt ISR Pseudo Code**********
void handle_m_ext_interrupt(void)
{
int_num = PLIC_ClaimIRQ(); /* get interrupt number by reading interrupt claim register /
ext_irq_handler(int_num); /
service the interrupt handler /
PLIC_CompleteIRQ(int_num); /
Clear interrupt claim/complete register /
PLIC_EnableIRQ(int_num); /
enable interrupt here or later /
}
void ext_irq_handler(int_num)
{
PLIC_DisableIRQ(int_num); /
disable interrupt */
}


2). The Interrupt occurs when the interrupt complete is cleared before disable the interrupt and getting the next interrupt.

PLIC External Interrupt ISR Pseudo Code**********
void handle_m_ext_interrupt(void)
{
int_num = PLIC_ClaimIRQ(); /* get interrupt number by reading interrupt claim register /
ext_irq_handler(int_num); /
service the interrupt service handler /
PLIC_EnableIRQ(int_num); /
enable interrupt here or later /
}
void ext_irq_handler(int_num)
{
PLIC_CompleteIRQ(int_num); /
Clear interrupt claim/complete register /
PLIC_DisableIRQ(int_num); /
disable interrupt */
}


Regards
Padmarao

Good question @padmaraob though I’m not familiar with the PolarFire SoC two thoughts might help you:

First, it’s not really necessary to disable and re-enable the PLIC interrupt source, because when an interrupt occurs this happens automatically at the top-most level–the MIE bit in mstatus is automatically turned off.

Second, the PLIC interrupt pending bit (for the source you’ve claimed) is automatically cleared during the claim process–by reading the Claim register as you’ve done. However, depending on the nature of your particular interrupt source, it may be necessary to manually clear that peripheral’s interrupt pending bit.

A simple and complete working example of PLIC, as well as CLIC and CLINT, is in the start.s file of the Demonstrating-MTVEC repo. The int_external_m_mode function at line 304 shows how this happens:

int_external_m_mode:
  lui t0, %hi(PLIC_CLAIM)    # read external interrupt source
  addi t0, t0, %lo(PLIC_CLAIM)
  lw t1, 0(t0)               # act of reading clears pending bit
  sw t1, 0(t0)               # signal claim complete
  addi t2, zero, PLIC_MAX_NUM_SOURCES
  bge t1, t2, int_external_unkn
  slli t1, t1, 1           # put on two byte boundary
  la t0, plic_vec_tab
  add t0, t0, t1
  jr t0

You may also find helpful the SiFive Interrupt Cookbook, especially Section 3.3 which states

A successful claim will atomically clear the pending bit in the PLIC interrupt pending register, …

The PolarFire SoC is based on SiFive’s U54-MC family and includes SiFive PLIC.

The disable and re-enable the PLIC interrupts are required to implement a two step interrupt processing (interrupt context followed by task context).

1). As per start.s file. The int_external_m_mode function at line 307

  lw t1, 0(t0)               # act of reading clears pending bit
  sw t1, 0(t0)               # signal claim complete

Read the interrupt number from the interrupt claim register and immediately write it back to the interrupt complete register to clear and called the plic_vec_tab later.

Do we need to follow the same procedure? because in this scenario the interrupt disable and re-enable is working fine to get the next interrupt.
Refer interrupt-completion -The interrupt can be completed only when the interrupt is enabled but not when disabled.

or

2). In the SiFive Interrupt Cookbook , Section 3.3.2?
Claim the Interrupt, branch to the handler and complete the interrupt.

void machine_external_interrupt()
{
//get the highest priority pending PLIC interrupt
uint32_t int_num = plic.claim_comlete;
//branch to handler
plic_handler[int_num]();
//complete interrupt by writing interrupt number back to PLIC
plic.claim_complete = int_num;
}

Thanks @padmaraob the FU540 (E54 core, 4-hart) is rather similar to the FE310 (E31 core, 1-hart) which I use.

No, you don’t need to follow the same procedure exactly. You are free to Complete the interrupt process at any time, whether before invoking your handler, as in example (1) above, or upon return from your handler, as in (2). It doesn’t matter. Just make sure to Complete the interrupt before executing your mret (or, sret, as appropriate) instruction and, thus, returning to main code to process other tasks.

Section 8 of the RISC-V PLIC Specification as you point out details the internal operation of the interrupt completion process, which all happens automatically by the core whenever you write to the CLAIM/COMPLETE register. Nothing much is of concern here. The PLIC peripheral’s CLAIM/COMPLETE register is at offset 0x0020 n004, where n is the hart number 0, 1, 2, 3, etc. For example, Table 37 in the FU540 Manual shows this. Both SoC’s FU540 and FE310 have the same base address, which is 0x0C00 0000, for their PLIC peripherals.

The only really significant statement in Section 8 is

The PLIC does not check whether the completion ID is the same as the last claim ID for that target. If the completion ID does not match an interrupt source that is currently enabled for the target, the completion is silently ignored.

In other words, if you COMPLETE (i.e., write) a PLIC interrupt ID that never was CLAIMed (i.e., read), then nothing happens, and after returning from the interrupt (with m/s-ret instruction) you’ll immediately re-enter the interrupt context with a pending interrupt ID. Similarly, if you COMPLETE an interrupt ID that never occurred, then nothing happens.

The reason that I use a look-up table (plic_vec_tab) is to cover all the possible claim-ID possibilities and select the one(s) that I want. You can also use a simpler approach to compare-and-branch for a single specific ID as well. Note that to get the table index position the interrupt ID is multiplied by two using slli t1, t1, 1, which must conform to the assembler directive alignment .balign 2 and size of each table element’s instruction j plic_...; this is the case for the two-byte compressed form of each jump instruction. Of course, use slli t1,t1,2 and .balign 4 for full four-byte jump instructions if you make a table that way.

The individual claim-ID’s for all 53 interrupt sources in the FU540 are shown in Table 38 of Section 10.2 of the FU540 Manual.. Again, remember that some of these sources may need their interrupt pending bits to be manually cleared by the handler; see their individual descriptions as needed.

Thank You Paul Sherman for your support. :+1:

1 Like