Am still looking for a simple standalone example of GPIO with the PLIC in C. Meanwhile @Jonny040 here are a few sketches that might help you.
Here is the interrupt handler. The high level Utility Functions are all callable from C. Simply copy/paste into the bottom of your start.s
file, above any .end
statement line, of course.
interrupt handler (put in start.s, near the end)
.equ CSR_MSTATUS_MPP, 0x00001800 # [12:11] machine prev priv mode
.equ CSR_MSTATUS_SPP, 0x00000100 # [8] supervisor prev prev mode
.equ CSR_MSTATUS_MPIE, 0x00000080 # [7] machine prev int enable
.equ CSR_MSTATUS_SPIE, 0x00000020 # [5] supervisor prev int enable
.equ CSR_MSTATUS_MIE, 0x00000008 # [3] machine int enable
.equ CSR_MSTATUS_SIE, 0x00000002 # [1] supervisor int enable
.equ CSR_MIE_MEIE, 0x00000800 # [11] machine external int enable
.equ CSR_MIE_SEIE, 0x00000200 # [9] supervisor external int enable
.equ CSR_MIE_MTIE, 0x00000080 # [7] machine timer int enable
.equ CSR_MIE_STIE, 0x00000020 # [5] supervisor int enable
.equ CSR_MIE_MSIE, 0x00000008 # [3] machine software int enable
.equ CSR_MIE_SSIE, 0x00000002 # [1] supervisor software int enable
.equ CSR_MIP_MEIP, 0x00000800 # [11] machine external int pending
.equ CSR_MIP_SEIP, 0x00000200 # [9] supervisor external int pending
.equ CSR_MIP_MTIP, 0x00000080 # [7] machine timer int pending
.equ CSR_MIP_STIP, 0x00000020 # [5] supervisor int pending
.equ CSR_MIP_MSIP, 0x00000008 # [3] machine software int pending
.equ CSR_MIP_SSIP, 0x00000002 # [1] supervisor software int pending
.equ CSR_MCAUSE_EC, 0x0000003F # 0x3ff ??? # [9:0] exception code
.equ CLINT_BASE, 0x02000000
.equ CLINT_MSIP_BASE, (CLINT_BASE + 0x0000)
.equ CLINT_MTIMECMP_BASE, (CLINT_BASE + 0x4000)
.equ CLINT_MTIME, (CLINT_BASE + 0xbff8)
.equ CLINT_MSIP_HART0, (CLINT_MSIP_BASE + 8 * 0)
.equ CLINT_MSIP_HART1, (CLINT_MSIP_BASE + 8 * 1)
.equ CLINT_MSIP_HART2, (CLINT_MSIP_BASE + 8 * 2)
.equ CLINT_MSIP_HART3, (CLINT_MSIP_BASE + 8 * 3)
.equ CLINT_MTIMECMP_HART0, (CLINT_MTIMECMP_BASE + 8 * 0)
.equ CLINT_MTIMECMP_HART1, (CLINT_MTIMECMP_BASE + 8 * 1)
.equ CLINT_MTIMECMP_HART2, (CLINT_MTIMECMP_BASE + 8 * 2)
.equ CLINT_MTIMECMP_HART3, (CLINT_MTIMECMP_BASE + 8 * 3)
.equ PLIC_BASE, 0x0C000000
.equ PLIC_PRIO, (PLIC_BASE + 0x000000) # 4 * CLAIM number
.equ PLIC_PEND, (PLIC_BASE + 0x001000) # 64-bit value
.equ PLIC_ENA, (PLIC_BASE + 0x002000) # 64-bit value
.equ PLIC_THRESHOLD, (PLIC_BASE + 0x200000)
.equ PLIC_CLAIM, (PLIC_BASE + 0x200004)
.equ PLIC_MAX_NUM_SOURCES, 52 # FE310-G002
###############################################
##
## clint trap handler
##
.balign 8 # required 64-bit alignment for mtvec
clint_trap_handler: .global clint_trap_handler
csrr t0, mcause # read trap cause
andi t1, t0, CSR_MCAUSE_EC # isolate exception code
#
addi t2, zero, 16 # CLINT_MAX_NUM_SOURCES
bge t1, t2, clint_trap_unkn
#
slli t1, t1, 2 # put on four byte (far jump) boundary
#
bgez t0, clint_trap_exception # branch if not an interrupt
#
clint_trap_interrupt:
la t0, clint_int_vec_tab # interrupt (asynchronous) source: cause < 0
add t0, t0, t1
jr t0
clint_trap_exception:
j .
clint_trap_unkn:
j . # unknown cause - loop forever, for now
#
# interrupt (asynchronous) sources
#
.option push
.option norvc
.balign 4
clint_int_vec_tab: # each must be four byte (far jump) elements, or else
j . # source id 0
j . # source id 1
j . # source id 2
j . # source id 3 (middle async prio)
j . # source id 4
j . # source id 5
j . # source id 6
j . # source id 7 (lowest async prio)
j int_external_m_mode # source id 8 (int_external_u_mode, really)
j int_external_m_mode # source id 9 (int_external_u_mode, really)
j int_external_m_mode # source id 10 (int_external_u_mode, really)
j int_external_m_mode # source id 11 (highest async prio)
j . # source id 12
j . # source id 13
j . # source id 14
j . # source id 15
.option pop
###############################################
##
## plic trap handler
##
##
## external interrupt (asynchronous) source
##
.balign 4 # required 64-bit alignment for mtvec
int_external_m_mode: .global 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
#
addi sp, sp, -16 # alloc stack frame
sw ra, 12(sp) # save return addr
sw t1, 8(sp) # save int source id
sw t0, 4(sp) # save claim/complete reg
#
addi t2, zero, PLIC_MAX_NUM_SOURCES
bge t1, t2, int_external_unkn
#
slli t1, t1, 2 # put on four byte (far jump) boundary
la t0, plic_vec_tab
add t0, t0, t1
jalr t0
#
lw t0, 4(sp) # restore claim/complete reg
lw t1, 8(sp) # restore int source id
lw ra, 12(sp) # restore return addr
addi sp, sp, 16 # dealloc stack frame
#
sw t1, 0(t0) # signal claim complete
#
mret
int_external_unkn:
j . # loop forever, for now
.option push
.option norvc
.balign 4
plic_vec_tab: # each must be four byte (far jump) elements, or else
j . # source id 0
j . # source id 1
j . # source id 2
j . # source id 3
j . # source id 4
j . # source id 5
j . # source id 6
j . # source id 7
j plic_gpio0 # source id 8
j plic_gpio1 # source id 9
j plic_gpio2 # source id 10
j plic_gpio3 # source id 11
j plic_gpio4 # source id 12
j plic_gpio5 # source id 13
j plic_gpio6 # source id 14
j plic_gpio7 # source id 15
j plic_gpio8 # source id 16
j plic_gpio9 # source id 17
j plic_gpio10 # source id 18
j plic_gpio11 # source id 19
j plic_gpio12 # source id 20
j plic_gpio13 # source id 21
j plic_gpio14 # source id 22
j plic_gpio15 # source id 23
j plic_gpio16 # source id 24
j plic_gpio17 # source id 25
j plic_gpio18 # source id 26
j plic_gpio19 # source id 27
j plic_gpio20 # source id 28
j plic_gpio21 # source id 29
j plic_gpio22 # source id 30
j plic_gpio23 # source id 31
j plic_gpio24 # source id 32
j plic_gpio25 # source id 33
j plic_gpio26 # source id 34
j plic_gpio27 # source id 35
j plic_gpio28 # source id 36
j plic_gpio29 # source id 37
j plic_gpio30 # source id 38
j plic_gpio31 # source id 39
j . # source id 40
j . # source id 41
j . # source id 42
j . # source id 43
j . # source id 44
j . # source id 45
j . # source id 46
j . # source id 47
j . # source id 48
j . # source id 49
j . # source id 50
j . # source id 51
j . # source id 52
.option pop
# Handle an interrupt
# In case of GPIO direct external mode (i.e., ‘GPIO’ mode, meaning non-IOF
# functions, when the IOF_EN bit for that pin is set to 0), you must manually
# clear the pending interrupt before the handler returns.
# Otherwise, you will see a never-ending stream of interrupts back to the
# same handler. For the GPIO block, clear the _IP pending bits by setting
# them, rather than clearing them.
# Some time during the handling process, either before or after your code,
# you need to CLAIM the interrupt, by reading the value from 0x0C200004,
# and writing it back to the same register. I read the claim value before my
# code, save it on the stack, and write it back after my code,
# as suggested in the RISC-V documentation.
.equ GPIO_BASE, 0x10012000
.equ GPIO_RISE_IP, 0x1C
.equ GPIO_FALL_IP, 0x24
.equ GPIO_HIGH_IP, 0x2C
.equ GPIO_LOW_IP, 0x34
plic_gpio21: .global plic_gpio21 # plic source id 29
# your stuff goes here ...
# set to '1' the corresponding interrupt pending bit
# to clear the trigger mode(s) you have enabled
lui t0, %hi(GPIO_BASE)
addi t2, t2, 1
slli t2, t2, 21 # 2 ^ 21
lw t1, GPIO_RISE_IP(t0)
or t1, t1, t2
sw t1, GPIO_RISE_IP(t0)
lw t1, GPIO_FALL_IP(t0)
or t1, t1, t2
sw t1, GPIO_FALL_IP(t0)
lw t1, GPIO_HIGH_IP(t0)
or t1, t1, t2
sw t1, GPIO_HIGH_IP(t0)
lw t1, GPIO_LOW_IP(t0)
or t1, t1, t2
sw t1, GPIO_LOW_IP(t0)
ret
# other pin(s) if you want to implement them as above
plic_gpio0: .weak plic_gpio0
plic_gpio1: .weak plic_gpio1
plic_gpio2: .weak plic_gpio2
plic_gpio3: .weak plic_gpio3
plic_gpio4: .weak plic_gpio4
plic_gpio5: .weak plic_gpio5
plic_gpio6: .weak plic_gpio6
plic_gpio7: .weak plic_gpio7
plic_gpio8: .weak plic_gpio8
plic_gpio9: .weak plic_gpio9
plic_gpio10: .weak plic_gpio10
plic_gpio11: .weak plic_gpio11
plic_gpio12: .weak plic_gpio12
plic_gpio13: .weak plic_gpio13
plic_gpio14: .weak plic_gpio14
plic_gpio15: .weak plic_gpio15
plic_gpio16: .weak plic_gpio16
plic_gpio17: .weak plic_gpio17
plic_gpio18: .weak plic_gpio18
plic_gpio19: .weak plic_gpio19
plic_gpio20: .weak plic_gpio20
plic_gpio21: .weak plic_gpio21
plic_gpio22: .weak plic_gpio22
plic_gpio23: .weak plic_gpio23
plic_gpio24: .weak plic_gpio24
plic_gpio25: .weak plic_gpio25
plic_gpio26: .weak plic_gpio26
plic_gpio27: .weak plic_gpio27
plic_gpio28: .weak plic_gpio28
plic_gpio29: .weak plic_gpio29
plic_gpio30: .weak plic_gpio30
plic_gpio31: .weak plic_gpio31
###############################################
##
## user mode utility functions - callable from C
##
#
# void disable_interrupts (void);
#
disable_interrupts: .globl disable_interrupts
csrrci zero, mstatus, (CSR_MSTATUS_MIE | CSR_MSTATUS_SIE) # disable interrupts
#
lui t0, %hi(CSR_MIE_MEIE | CSR_MIE_MTIE | CSR_MIE_MSIE) # disable interrupt sources
addi t0, t0, %lo(CSR_MIE_MEIE | CSR_MIE_MTIE | CSR_MIE_MSIE) # disable interrupt sources
csrrc zero, mie, t0
#
ret
#
# void disable_external_interrupts (void);
#
disable_external_interrupts: .global disable_external_interrupts
lui t0, %hi(PLIC_ENA)
addi t0, t0, %lo(PLIC_ENA)
sw zero, 0(t0) # 1 .. 31
sw zero, 4(t0) # 32 .. 52
lui t0, %hi(CSR_MIE_MEIE) # disable external interrupt source
addi t0, t0, %lo(CSR_MIE_MEIE)
csrrc zero, mie, t0
#
ret
# INPUT:
# a0 - plic_source_id
# OUTPUT:
# t0 - address of plic_ena register
# t2 - desired bit position
# USES:
# t1
plic_enable_bit:
lui t0, %hi(PLIC_ENA)
addi t0, t0, %lo(PLIC_ENA)
addi t1, zero, 32
#
divu t2, a0, t1 # t2 = a0 / 32
slli t2, t2, 2 # t2 = 4 * t2
add t0, t0, t2
#
remu t2, a0, t1 # t2 = a2 % 32
addi t1, zero, 1
sll t2, t1, t2 # t2 = 2 ^ t2
#
ret
#
# void disable_external_interrupt (uint8_t plic_source_id);
#
# a0 - plic_source_id: 1=aon_wdt, 2=aon_rtc, 3-4=uart, 5-7=spi, 8-39=gpio, 40-51=pwm, ..., 52=i2c
#
disable_external_interrupt: .global disable_external_interrupt
addi sp, sp, -16
sw ra, 12(sp)
#
jal plic_enable_bit # a0=id --> t0=addr(reg), t2=packed-bit
#
lw t1, 0(t0)
not t2, t2
and t1, t1, t2 # clear
sw t1, 0(t0)
#
lw ra, 12(sp)
addi sp, sp, 16
#
ret
#
# void enable_external_interrupt (uint8_t plic_source_id, uint8_t plic_source_prio);
#
# a0 - plic_source_id: 1=aon_wdt, 2=aon_rtc, 3-4=uart, 5-7=spi, 8-39=gpio, 40-51=pwm, ..., 52=i2c
# a1 - plic_source_prio: 0=off, 1=lowest, ..., 7=highest
#
enable_external_interrupt: .global enable_external_interrupt
addi sp, sp, -16
sw ra, 12(sp)
#
jal prio_external_interrupt # a0=id, a1=prio
jal plic_enable_bit # a0=id --> t0=addr(reg), t2=packed-bit
#
lw t1, 0(t0)
or t1, t1, t2 # set
sw t1, 0(t0)
lui t0, %hi(PLIC_THRESHOLD)
addi t0, t0, %lo(PLIC_THRESHOLD) # define external interrupt threshold level
addi t1, zero, 0 # 0=permit-all-nonzero-prio, ..., 7=mask-all-prio
sw t1, 0(t0)
#
lui t0, %hi(CSR_MIP_MEIP)
addi t0, t0, %lo(CSR_MIP_MEIP) # clear external interrupt pending bit
csrrc zero, mip, t0
#
lui t0, %hi(CSR_MIE_MEIE)
addi t0, t0, %lo(CSR_MIE_MEIE) # enable external interrupt sources
csrrs zero, mie, t0
#
csrrsi zero, mstatus, CSR_MSTATUS_MIE # enable interrupts
#
lw ra, 12(sp)
addi sp, sp, 16
#
ret
#
# void prio_external_interrupt (uint8_t plic_source_id, uint8_t plic_source_prio);
#
# a0 - plic_source_id: 1=aon_wdt, 2=aon_rtc, 3-4=uart, 5-7=spi, 8-39=gpio, 40-51=pwm, ..., 52=i2c
# a1 - plic_source_prio: 0=off, 1=lowest, ..., 7=highest
#
prio_external_interrupt: .global prio_external_interrupt
lui t0, %hi(PLIC_PRIO)
addi t0, t0, %lo(PLIC_PRIO)
slli t1, a0, 2 # 4 * plic_source_id
add t0, t0, t1
sw a1, 0(t0) # plic_source_prio
#
ret
#
# void wait_for_interrupt (void);
#
wait_for_interrupt: .globl wait_for_interrupt
wfi
#
ret
You can configure and generate an interrupt on GPIO like this, for example, with GPIO21 as an input and only rising edge interrupt. Notice carefully that gpio_XXX
functions take input and output pin numbers, while the XXX_external_interrupt
functions take interrupt source ID numbers.