Initialize and set up of GPIO pins

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
  }
}