Use of external interrupt in vectored mode

Hello
I am using HiFive Rev-B board. I am trying to fire UART0 Rx interrupt using vectored mode. I have changed the mtvec LSB to 0x1 and written the base address of the interrupt vector table as well. Now, whenever I try to send a character from the serial terminal, I do receive the interrupt but PC always points to the base address of vector table and not the corresponding interrupt vector entry (in this case it should have been mtvec.BASE + 0x2C). I have already enabled the external interrupt in MIE and enabled the MIE in mstatus register. I have also enabled the interrupt in PLIC and changed the priority as needed.

Note: This works completely fine if I am using Direct mode instead of vectored mode for interrupt.

Can anyone please help to identify what am I missing here?

Hi Sjain,

I believe you’ve done everything correctly. As you said, the external interrupt should be getting serviced from BASE+0x2C as the UART0 interrupt is is coming from the PLIC.

The one item to check is that your table is 64-byte aligned, as this is a requirement for vectored interrupt mode.

Regards,
Ralph

Hi Ralph
Thank you for the response. I am using the below trap vector which is one of the examples assembly files I found in SDK. But, it is still not helping.

.section .vector_trap_entry
#if __riscv_xlen == 32
.balign 64,0
#else
.balign 128,0
#endif
.option norvc
.global __metal_vector_table
__metal_vector_table:
IRQ_0:
call metal_interrupt_vector_handler
IRQ_1:
call metal_interrupt_vector_handler
IRQ_2:
call metal_interrupt_vector_handler
IRQ_3:
call metal_software_interrupt_vector_handler
IRQ_4:
call metal_interrupt_vector_handler
IRQ_5:
call metal_interrupt_vector_handler
IRQ_6:
call metal_interrupt_vector_handler
IRQ_7:
call metal_timer_interrupt_vector_handler
IRQ_8:
call metal_interrupt_vector_handler
IRQ_9:
call metal_interrupt_vector_handler
IRQ_10:
call metal_interrupt_vector_handler
IRQ_11:
call riscv_interrupt_handle_machine_ext
IRQ_12:
call metal_interrupt_vector_handler
IRQ_13:
call metal_interrupt_vector_handler
IRQ_14:
call metal_interrupt_vector_handler
IRQ_15:
call metal_interrupt_vector_handler
IRQ_LC0:
call metal_lc0_interrupt_vector_handler
IRQ_LC1:
call metal_lc1_interrupt_vector_handler
IRQ_LC2:
call metal_lc2_interrupt_vector_handler
IRQ_LC3:
call metal_lc3_interrupt_vector_handler
IRQ_LC4:
call metal_lc4_interrupt_vector_handler
IRQ_LC5:
call metal_lc5_interrupt_vector_handler
IRQ_LC6:
call metal_lc6_interrupt_vector_handler
IRQ_LC7:
call metal_lc7_interrupt_vector_handler
IRQ_LC8:
call metal_lc8_interrupt_vector_handler
IRQ_LC9:
call metal_lc9_interrupt_vector_handler
IRQ_LC10:
call metal_lc10_interrupt_vector_handler
IRQ_LC11:
call metal_lc11_interrupt_vector_handler
IRQ_LC12:
call metal_lc12_interrupt_vector_handler
IRQ_LC13:
call metal_lc13_interrupt_vector_handler
IRQ_LC14:
call metal_lc14_interrupt_vector_handler
IRQ_LC15:
call metal_lc15_interrupt_vector_handler

Looks good @SJain you might like the interrupt procedural flow illustration and somewhat related discussion.

It’s not clear to me how synchronous interrupts, those with a positive mcause value such as the exceptions, are handled in Vectored mode. Could it be that, in Vectored mode, you always see BASE+0 because you’re getting an Instruction address misaligned exception – which happens to be mcause=0, without the msb?

You can see a fairly complete MTVEC Demonstration in the start.s file.

As @RalphF mentions, you want to make sure that your vectored handler (the trap_handler in line 77 of the Demonstration) is on an 4-byte boundary for Direct operation, and 64-byte boundary for Vectored operation. See Assembler Directives for description of the GNU Assembler.

Vectored mode lets you bypass the testing and arithmetic of mcause, but in the case of an external interrupt you still need to do the testing and arithmetic of the PLIC Claim procedure (i.e., int_external_m_mode in the Demonstration).

@pds Thank you for the response. Sorry, I’m fairly new to RISCV, this may be a dumb question but is it necessary to use CLIC/CLINT for vectored mode? My understanding was in vectored mode all exception goes to MTVEC.val + 0 and all other asynchronous interrupts go to MTVEC.val + interrupt_id4. Therefore, all external interrupts should cause a jump to MTVEC.val + 0xB4. From there, in the handler, we use PLIC_CLAIM to identify what caused the interrupt and then handle it accordingly. I was in the impression that there is no need of using CLINT and also for HiFive Rev-B, I think CLINT only supports software and timer interrupt.

I checked, and there is no Instruction address misaligned exception in my code. It always jumps to trap vector when I send a character over UART0, not sure what am I missing here.

Thanks for the clarification @SJain makes sense about exceptions in Vectored mode. In your trap vector, can you check what is the mcause value whenever you get in there, from characters sent over your UART0? Might give a clue …

A couple thoughts come to mind:

  1. Are you properly setting all 32 bits of the csr? That is, both the long immediate 20-bit upper part, and the short immediate 12-bit lower part?
lui t0, %hi(mtvec_vec_tab) 
addi t0, t0, %lo(mtvec_vec_tab)
andi t0, t0, 0xFFFFFFFC  # mtvec.BASE [1:0] 0=direct, 1=vector
ori t0, t0, 0x00000001
csrrw x0, mtvec, t0
  1. Did you fully implement all sixteen entries of the vector table? (example shown below shortened for brevity and compactness)
.balign 64  ;# 16 words of 4 bytes (32-bit) each
mtvec_vec_tab:
  j mtvec_int_sw_u / ..._s / ..._h / ..._m
  j mtvec_int_timer_u / ..._s / ..._h / ..._m
  j mtvec_int_ext_u / ..._s / ..._h / ..._m
  j mtvec_int_resvd_u / ..._s / ..._h / ..._m
  1. Did you check your assembled .lst 'ing output of your vector table to make sure no (two byte, 16-bit) compressed instruction forms are being used (perhaps automatically, by the assembler) in the vector table entry jump instructions? All sixteen entries should all be four bytes long. You can do this by specifying .balign 4 with each end every one of the vector table targets, for example in case of external m-mode (see also int_external_m_mode at line 304 of Demonstration):
.balign 4  ;# IMPORTANT: prevent compressed instruction forms
mtvec_int_ext_m:
  t0 = CLAIM
  push ra
  push t0
  calculate plic vec tab index
  jalr ra, 0(t0)
  pop t0
  pop ra
  CLAIM = t0  ;# complete
  mret

@SJain UART0 is used by OCD. Try with UART1.

@pds Thank you so much for your response. It worked for me, there were a couple of issues:

  1. My vector table was getting overwritten by another source code.
  2. You highlighted the issue was spot-on. When I looked into .lst file, entries in my vector table were 8-byte spread instead of 4-byte which I then fixed and now I am correctly jumping on the external interrupt handler.

However, when I am returning from my UART0 ISR, I get a hard fault. When I looked into the registers, I found that the fp/s0 is getting corrupted. I am not sure what may be the reason. I am using the “interrupt” attribute for external_interrutp_handler where PLIC identifies the cause of the interrupt and then jumps the control to UART0 handler. I thought the “interrupt” attribute takes care of saving and restoring the values of registers.

Thanks @SJain !

The call’s in your __metal_vector_table look strange to me, shouldn’t they be simple j(ump) instructions, so each vector table entry doesn’t destroy the return address x1/ra?

The riscv_interrupt_handle_machine_ext (referenced by your IRQ_11 table entry) should be declared with the “interrupt” attribute. Look again carefully at the .lst output of this handler. When the desired PLIC vector table entry is invoked, is it done with a jalr which uses x1 (ra)? If so, your specific UART code should end with only a ret, not an mret, because the mret is at the end of the riscv_interrupt_handle_machine_ext handler.

I prefer writing interrupts and handlers in assembly, not C, where you can see everything that is going on and how it is being done.

One more thought, at the very top level of all your program, are you initializing the stack pointer at top of available ram (or elsewhere with enough space)?

.globl _start
_start:
  lui a1, 0x80004
  addi sp, a1, -4  # initialize stack pointer
  ... 

Hi @pds
Thank you for the prompt response. Everything is working now. Yes, I have replaced call in my vector table with j. However, the problem was I was using the “interrupt” attribute at wrong place.

Thank you for being patient and responding to all my queries. :slight_smile:

1 Like