Initialize and set up of GPIO pins

Hey, I am quite new to using the HiFive1 rev b. I am using RISC-V MultiZone and want to implement a small window sensor software in one of the zones. Since I can’t figure out how to use the metal Freedom library with MultiZone I tried to set up the GPIO pin and the interrupt function with the given example. But I can’t seem to make it work. Can anybody help me and give me advice for the steps needed for the GPIO pin and interrupt to work?

Thanks in advance
Jonny

1 Like

Hi @Jonny040 I am not familiar with MultiZone but maybe these few notes can shed some light on your question.

The interrupt handling procedural flow is a detailed discussion and good diagram here in the forums.

A very lean yet complete working example is Demonstrating MTVEC on my github site.

In general there’s only a few simple steps:
Assuming you’ve set up mtvec properly, enabled the CSR’s mstatus.MIE bit, and enabled the appropriate bit of GPIO_IE,

a) Read PLIC_CLAIM to get the source id of the interrupt request – the 32 GPIO are id’s 8-39
b) Write PLIC_CLAIM to complete the request of that source
c) Jump to desired code
d) Write ‘1’ to appropriate bit of GPIO_IP to clear the pending interrupt
e) Exit with the mret instruction

You don’t need to clear the mstatus.MIP bit, because it is cleared automatically when mret happens.

The specific method of clearing external interrupts varies depending on the nature of the external peripheral whose interrupt you enable; step (d) above is shown in case of GPIO. See the procedural flow discussion above for clearing, for example, the UART, the SPI, the I2C, or the PWM periperals.

1 Like

Hey, thanks for the great help. I think I got a little closer to the answer. Since your example is written in assembly code its quite hard for me to read or replicate though. Do you know of any examples in C?

1 Like

Surely @Jonny040 glad it helps. Here are a few sources for examples in C and also some general descriptions.

The gpio-testbench.c is a small and self-contained example, with minimal dependencies. Do, however, keep the assembly code mentioned earlier in mind, to keep each bit of each register in mind and stay close to the true simplicity and beauty of the RISC-V ISA.

Explained in simple and concrete terms is the concept of the PLIC interrupt source IDs, which are otherwise vague, abstract, and highly obscure.

Some great and well-written general information is in the SiFive Interrupt Cookbook which you might find helpful. Note, however, that specific examples seem to work around the device tree .dts configuration file structures for which this overall genrality, to me, seems to distract from a fundamental basic understanding of how CLIC, CLINT, and PLIC work in practice.

There is always the SiFive Freedom Studio; with a simple guide for helping to install it. Make sure to look at all the github repo’s of the sifive repository.

Thanks, I really appreciate the help. Do you now of any examples which is not using the Freedom Metal library? I could not make it work with MultiZone due to different toolchains…

1 Like

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.

This is about the easiest way to configure a GPIO pin, in C
#define gpio_base 0x10012000
#define gpio_input_val  (*(volatile uint32_t *) (gpio_base + 0x00))
#define gpio_input_en   (*(volatile uint32_t *) (gpio_base + 0x04))
#define gpio_output_en  (*(volatile uint32_t *) (gpio_base + 0x08))
#define gpio_output_val (*(volatile uint32_t *) (gpio_base + 0x0C))
#define gpio_pue        (*(volatile uint32_t *) (gpio_base + 0x10))
#define gpio_ds         (*(volatile uint32_t *) (gpio_base + 0x14))
#define gpio_rise_ie    (*(volatile uint32_t *) (gpio_base + 0x18))
#define gpio_rise_ip    (*(volatile uint32_t *) (gpio_base + 0x1C))
#define gpio_fall_ie    (*(volatile uint32_t *) (gpio_base + 0x20))
#define gpio_fall_ip    (*(volatile uint32_t *) (gpio_base + 0x24))
#define gpio_high_ie    (*(volatile uint32_t *) (gpio_base + 0x28))
#define gpio_high_ip    (*(volatile uint32_t *) (gpio_base + 0x2C))
#define gpio_low_ie     (*(volatile uint32_t *) (gpio_base + 0x30))
#define gpio_low_ip     (*(volatile uint32_t *) (gpio_base + 0x34))
#define gpio_iof_en     (*(volatile uint32_t *) (gpio_base + 0x38))
#define gpio_iof_sel    (*(volatile uint32_t *) (gpio_base + 0x3C))
#define gpio_passthru_high_ie (*(volatile uint32_t *) (gpio_base + 0x44))
#define gpio_passthru_low_ie  (*(volatile uint32_t *) (gpio_base + 0x48))
Input and output pins for the 48-QFN package of FE310, in C
//
// ASIC PACKAGE pins
//   consult SoC datasheet for this information
//
#define FE310_48QFN_UNKN                      (0)          # non-existent pin
#define FE310_48QFN25_GPIO0_PWM0_0            (1UL <<  0)
#define FE310_48QFN26_GPIO1_PWM0_1            (1UL <<  1)
#define FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0   (1UL <<  2)
#define FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI  (1UL <<  3)
#define FE310_48QFN29_GPIO4_SPI1_MISO         (1UL <<  4)
#define FE310_48QFN31_GPIO5_SPI1_SCK          (1UL <<  5)
#define FE310_48QFN33_GPIO9_SPI1_SS2          (1UL <<  9)
#define FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3  (1UL << 10)
#define FE310_48QFN35_GPIO11_PWM2_1           (1UL << 11)
#define FE310_48QFN36_GPIO12_PWM2_2           (1UL << 12)
#define FE310_48QFN37_GPIO13_PWM2_3           (1UL << 13)
#define FE310_48QFN38_GPIO16_UART0_RX         (1UL << 16)
#define FE310_48QFN39_GPIO17_UART0_TX         (1UL << 17)
#define FE310_48QFN40_GPIO18_UART1_TX         (1UL << 18)
#define FE310_48QFN42_GPIO20_PWM1_0           (1UL << 20)
#define FE310_48QFN41_GPIO19_PWM1_1           (1UL << 19)
#define FE310_48QFN43_GPIO21_PWM1_2           (1UL << 21)
#define FE310_48QFN44_GPIO22_PWM1_3           (1UL << 22)
#define FE310_48QFN45_GPIO23_UART1_RX         (1UL << 23)

//
// GPIO pins
//   IOF_EN=0
//
#define GPIO0     FE310_48QFN25_GPIO0_PWM0_0
#define GPIO1     FE310_48QFN26_GPIO1_PWM0_1
#define GPIO2     FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0
#define GPIO3     FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI
#define GPIO4     FE310_48QFN29_GPIO4_SPI1_MISO
#define GPIO5     FE310_48QFN31_GPIO5_SPI1_SCK
#define GPIO8     FE310_48QFN32_UNKN
#define GPIO9     FE310_48QFN33_GPIO9_SPI1_SS2
#define GPIO10    FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3
#define GPIO11    FE310_48QFN35_GPIO11_PWM2_1
#define GPIO12    FE310_48QFN36_GPIO12_PWM2_2
#define GPIO13    FE310_48QFN37_GPIO13_PWM2_3
#define GPIO16    FE310_48QFN38_GPIO16_UART0_RX
#define GPIO17    FE310_48QFN39_GPIO17_UART0_TX
#define GPIO18    FE310_48QFN40_GPIO18_UART1_TX
#define GPIO20    FE310_48QFN42_GPIO20_PWM1_0
#define GPIO19    FE310_48QFN41_GPIO19_PWM1_1
#define GPIO21    FE310_48QFN43_GPIO21_PWM1_2
#define GPIO22    FE310_48QFN44_GPIO22_PWM1_3
#define GPIO23    FE310_48QFN45_GPIO23_UART1_RX

//
// UART pins
//   IOF_SEL=0, IOF_EN=1
//
#define UART0_RX  FE310_48QFN38_GPIO16_UART0_RX  # input to SoC
#define UART0_TX  FE310_48QFN39_GPIO17_UART0_TX  # output from SoC
//
#define UART1_RX  FE310_48QFN45_GPIO23_UART1_RX  # input to SoC
#define UART1_TX  FE310_48QFN40_GPIO18_UART1_TX  # output from SoC

//
// SPI pins
//   IOF_SEL=0, IOF_EN=1
//
#define SPI0_SCK  FE310_48QFN31_GPIO5_SPI1_SCK
#define SPI0_MOSI FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI
#define SPI0_MISO FE310_48QFN29_GPIO4_SPI1_MISO
#define SPI0_SS0  FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0
#define SPI0_SS1  FE310_48QFN_UNKN
#define SPI0_SS2  FE310_48QFN33_GPIO9_SPI1_SS2
#define SPI0_SS3  FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3
//
#define SPI1_SCK  FE310_48QFN31_GPIO5_SPI1_SCK
#define SPI1_MOSI FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI
#define SPI1_MISO FE310_48QFN29_GPIO4_SPI1_MISO
#define SPI1_SS0  FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0
#define SPI1_SS1  FE310_48QFN_UNKN
#define SPI1_SS2  FE310_48QFN33_GPIO9_SPI1_SS2
#define SPI1_SS3  FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3
//
#define SPI2_SCK  FE310_48QFN31_GPIO5_SPI1_SCK
#define SPI2_MOSI FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI
#define SPI2_MISO FE310_48QFN29_GPIO4_SPI1_MISO
#define SPI2_SS0  FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0
#define SPI2_SS1  FE310_48QFN_UNKN
#define SPI2_SS2  FE310_48QFN33_GPIO9_SPI1_SS2
#define SPI2_SS3  FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3

//
// PWM pins
//   IOF_SEL=1, IOF_EN=1
//
#define PWM0_0    FE310_48QFN25_GPIO0_PWM0_0
#define PWM0_1    FE310_48QFN26_GPIO1_PWM0_1
#define PWM0_2    FE310_48QFN27_GPIO2_PWM0_2_SPI1_SS0
#define PWM0_3    FE310_48QFN28_GPIO3_PWM0_3_SPI1_MOSI
//
#define PWM1_0    FE310_48QFN42_GPIO20_PWM1_0
#define PWM1_1    FE310_48QFN41_GPIO19_PWM1_1
#define PWM1_2    FE310_48QFN43_GPIO21_PWM1_2
#define PWM1_3    FE310_48QFN44_GPIO22_PWM1_3
//
#define PWM2_0    FE310_48QFN34_GPIO10_PWM2_0_SPI1_SS3
#define PWM2_1    FE310_48QFN35_GPIO11_PWM2_1
#define PWM2_2    FE310_48QFN36_GPIO12_PWM2_2
#define PWM2_3    FE310_48QFN37_GPIO13_PWM2_3

//
// I2C pins
//   IOF_SEL=0, IOF_EN=1
//
#define I2C0_SCK  FE310_48QFN_UNKN
#define I2C0_SDA  FE310_48QFN_UNKN
//
#define I2C1_SCK  FE310_48QFN_UNKN
#define I2C1_SDA  FE310_48QFN_UNKN

//
// PCBA LAYOUT pins - alternate (physical) representation
//   consult schematic for this information
//
// _A side of PCBA is on the left (with button S1 at the bottom)
#define LOFIVE_R1_PIN9_A  FE310_48QFN_GPIO0_PWM0_0
#define LOFIVE_R1_PIN10_A FE310_48QFN_GPIO1_PWM0_1
#define LOFIVE_R1_PIN11_A FE310_48QFN_GPIO2_PWM0_2_SPI1_SS0
#define LOFIVE_R1_PIN12_A FE310_48QFN_GPIO3_PWM0_3_SPI1_MOSI
#define LOFIVE_R1_PIN13_A FE310_48QFN_GPIO4_SPI1_MISO
#define LOFIVE_R1_PIN14_A FE310_48QFN_GPIO5_SPI1_SCK
//
// _B side of PCBA is on the right (with button S1 at the bottom)
#define LOFIVE_R1_PIN2_B  FE310_48QFN_GPIO23_UART1_RX         // LoFive pin 27
#define LOFIVE_R1_PIN3_B  FE310_48QFN_GPIO22_PWM1_3           // LoFive pin 26
#define LOFIVE_R1_PIN4_B  FE310_48QFN_GPIO21_PWM1_2           // LoFive pin 25
#define LOFIVE_R1_PIN5_B  FE310_48QFN_GPIO20_PWM1_0           // LoFive pin 24
#define LOFIVE_R1_PIN6_B  FE310_48QFN_GPIO19_PWM1_1           // LoFive pin 23
#define LOFIVE_R1_PIN7_B  FE310_48QFN_GPIO18_UART1_TX         // LoFive pin 22
#define LOFIVE_R1_PIN8_B  FE310_48QFN_GPIO17_UART0_TX         // LoFive pin 21
#define LOFIVE_R1_PIN9_B  FE310_48QFN_GPIO16_UART0_RX         // LoFive pin 20
#define LOFIVE_R1_PIN10_B FE310_48QFN_GPIO13_PWM2_3           // LoFive pin 19
#define LOFIVE_R1_PIN11_B FE310_48QFN_GPIO12_PWM2_2           // LoFive pin 18
#define LOFIVE_R1_PIN12_B FE310_48QFN_GPIO11_PWM2_1           // LoFive pin 17
#define LOFIVE_R1_PIN13_B, FE310_48QFN_GPIO10_PWM2_0_SPI1_SS3  // LoFive pin 16
#define LOFIVE_R1_PIN14_B FE310_48QFN_GPIO9_SPI1_SS2          // LoFive pin 15
Interrupt source ID's, in C
// interrupt enable and pending source IDs

// There is no PLIC interrupt source ‘0’, and the least significant (right-most) bit position #0 similarly reflects this.

// Lower register word

// AON : plic source id start=1 end=2

#define PLIC_AON_WDT  1  // (register bit position 1)
#define PLIC_AON_RTC  2

// UART : plic source id start=3 end=4

#define PLIC_UART0    3
#define PLIC_UART1    4

// SPI : plic source id start=5 end=7

#define PLIC_QSPI0    5
#define PLIC_SPI1     6
#define PLIC_SPI2     7

// GPIO : plic source id start=8 end=39

#define PLIC_GPIO0    8
#define PLIC_GPIO1    9
#define PLIC_GPIO2    10
#define PLIC_GPIO3    11
#define PLIC_GPIO4    12
#define PLIC_GPIO5    13
#define PLIC_GPIO6    14
#define PLIC_GPIO7    15
#define PLIC_GPIO8    16
#define PLIC_GPIO9    17
#define PLIC_GPIO10   18
#define PLIC_GPIO11   19
#define PLIC_GPIO12   20
#define PLIC_GPIO13   21
#define PLIC_GPIO14   22
#define PLIC_GPIO15   23
#define PLIC_GPIO16   24
#define PLIC_GPIO17   25
#define PLIC_GPIO18   26
#define PLIC_GPIO19   27
#define PLIC_GPIO20   28
#define PLIC_GPIO21   29
#define PLIC_GPIO22   30
#define PLIC_GPIO23   31  // (register bit position 31)

// Upper register word

#define PLIC_GPIO24   32  // (register bit position 0)
#define PLIC_GPIO25   33
#define PLIC_GPIO26   34
#define PLIC_GPIO27   35
#define PLIC_GPIO28   36
#define PLIC_GPIO29   37
#define PLIC_GPIO30   38
#define PLIC_GPIO31   39

// PWM : plic source id start=40 end=51

#define PLIC_PWM0_0   40
#define PLIC_PWM0_1   41
#define PLIC_PWM0_2   42
#define PLIC_PWM0_3   43
#define PLIC_PWM1_0   44
#define PLIC_PWM1_1   45
#define PLIC_PWM1_2   46
#define PLIC_PWM1_3   47
#define PLIC_PWM2_0   48
#define PLIC_PWM2_1   49
#define PLIC_pwm2_2   50
#define PLIC_pwm2_3   51

// I2C : plic source id start=52 end=52

#define PLIC_I2C      52  // (register bit position 20)
Put (or revise) these two lines in your start.s file
  # set trap handler
  li t0, clint_trap_handler
  csrrw zero, mtvec, t0

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.

// function prototypes and forward declarations
void disable_interrupts (void);  // lives in, e.g., start.s
void disable_external_interrupts (void);  // lives in, e.g., start.s
void disable_external_interrupt (uint8_t plic_source_id);  // lives in, e.g., start.s
void enable_external_interrupt (uint8_t plic_source_id, uint8_t plic_source_prio);  // lives in, e.g., start.s
void prio_external_interrupt (uint8_t plic_source_id, uint8_t plic_source_prio);  // lives in, e.g., start.s
void wait_for_interrupt (void);  // lives in, e.g., start.s
// where
//   plic_source_id: 1=aon_wdt, 2=aon_rtc, 3-4=uart, 5-7=spi, 8-39=gpio, 40-51=pwm, ..., 52=i2c
//   plic_source_prio: 0=off, 1=lowest, ..., 7=highest

void main()
{
  disable_interrupts();
  disable_external_interrupts();

  // route the path - these functions take input and output PIN numbers

  gpio_iof_en    &= ~(1UL << GPIO21);  // general (direct) function i/o mode
//gpio_iof_sel   &= ~(1UL << GPIO21);  // special function i/o #0
//gpio_iof_sel   |=  (1UL << GPIO21);  // special function i/o #1
//gpio_iof_en    |=  (1UL << GPIO21);  // special function i/o modes
  gpio_output_en |=  (1UL << GPIO21);  // optional internal control, when no external driving source connected
  gpio_input_en  |=  (1UL << GPIO21);  // req'd for sensing interrupt
  gpio_pue       &= ~(1UL << GPIO21);  // input pull-up (0=dis, 1=ena)

  // pick one, or more, sensing mode(s) as desired:
  gpio_rise_ie  = (1UL << GPIO21);
  gpio_fall_ie &= ~(1UL << GPIO21);
  gpio_high_ie &= ~(1UL << GPIO21);
  gpio_low_ie  &= ~(1UL << GPIO21);

  // enable the interrupt - these functions all take interrupt source ID numbers

  enable_external_interrupt( PLIC_GPIO21, 5 );  //gpio21=29, prio #5

  // trigger the event

  while(1)
  {
    gpio_output_val |= (1UL << GPIO21_PWM2_2);   // high
    gpio_output_val &= ~(1UL << GPIO21_PWM2_2);  // low
  }
}