| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| #include "sw/device/lib/dif/dif_plic.h" |
| |
| #include <stdbool.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "rv_plic_regs.h" // Generated. |
| #include "sw/device/lib/base/mmio.h" |
| |
| // The highest interrupt priority. |
| #define RV_PLIC_MAX_PRIORITY 0x3u |
| |
| // These defines are used to calculate the IRQ index in IP, LE, IE registers. |
| // These registers are 32bit wide, and in order to accommodate all the IRQs, |
| // multiple of the same type registers are defined (IE00, IE01, ...). For |
| // example, IRQ ID 33 corresponds to bit 0 in registers IP1, LE1, IE01. |
| #define PLIC_ID_TO_INDEX_REG_SIZE 32u |
| #define PLIC_ID_TO_INDEX(id) ((uint32_t)id - 1) |
| |
| /** |
| * PLIC register info. |
| * |
| * This data type is used to store IRQ source bit offset within a register, |
| * and the offset of this register inside the PLIC. |
| */ |
| typedef struct plic_reg_info { |
| ptrdiff_t offset; /*<< Register offset. */ |
| uint8_t bit_index; /*<< Bit index within the register. */ |
| } plic_reg_info_t; |
| |
| /** |
| * PLIC target specific register offsets. |
| * |
| * PLIC is designed to support multiple targets, and every target has a set |
| * of its own registers. This data type is used to store PLIC target specific |
| * register offsets. |
| */ |
| typedef struct plic_target_reg_offset { |
| ptrdiff_t ie; /*<< Interrupt Enable register offset. */ |
| ptrdiff_t cc; /*<< Claim/complete register offset. */ |
| ptrdiff_t threshold; /*<< Threshold register offset. */ |
| } plic_target_reg_offset_t; |
| |
| /** |
| * PLIC peripheral IRQ range. |
| * |
| * PLIC IRQ source IDs are grouped together by a peripheral they belong to. |
| * Meaning that all IRQ IDs of the same peripheral are guaranteed to be |
| * consecutive. This data type is used to store IRQ ID ranges of a peripheral. |
| */ |
| typedef struct plic_peripheral_range { |
| dif_plic_irq_id_t first_irq_id; /*<< The first IRQ ID of a peripheral. */ |
| dif_plic_irq_id_t last_irq_id; /*<< The last IRQ ID of a peripheral. */ |
| } plic_peripheral_range_t; |
| |
| // An array of target specific set of register offsets. Every supported PLIC |
| // target must have an entry in this array. |
| static const plic_target_reg_offset_t plic_target_reg_offsets[] = { |
| [kDifPlicTargetIbex0] = |
| { |
| .ie = RV_PLIC_IE00_REG_OFFSET, |
| .cc = RV_PLIC_CC0_REG_OFFSET, |
| .threshold = RV_PLIC_THRESHOLD0_REG_OFFSET, |
| }, |
| }; |
| |
| // An array of IRQ source ID ranges per peripheral. Every peripheral supported |
| // by the PLIC, must have an entry in this array. |
| static const plic_peripheral_range_t plic_peripheral_ranges[] = { |
| [kDifPlicPeripheralGpio] = |
| { |
| .first_irq_id = kDifPlicIrqIdGpio0, |
| .last_irq_id = kDifPlicIrqIdGpio31, |
| }, |
| [kDifPlicPeripheralUart] = |
| { |
| .first_irq_id = kDifPlicIrqIdUartTxWatermark, |
| .last_irq_id = kDifPlicIrqIdUartRxParityErr, |
| }, |
| [kDifPlicPeripheralSpiDevice] = |
| { |
| .first_irq_id = kDifPlicIrqIdSpiDeviceRxF, |
| .last_irq_id = kDifPlicIrqIdSpiDeviceTxUnderflow, |
| }, |
| [kDifPlicPeripheralFlashCtrl] = |
| { |
| .first_irq_id = kDifPlicIrqIdFlashCtrlProgEmpty, |
| .last_irq_id = kDifPlicIrqIdFlashCtrlOpError, |
| }, |
| [kDifPlicPeripheralHmac] = |
| { |
| .first_irq_id = kDifPlicIrqIdHmacDone, |
| .last_irq_id = kDifPlicIrqIdHmacErr, |
| }, |
| [kDifPlicPeripheralAlertHandler] = |
| { |
| .first_irq_id = kDifPlicIrqIdAlertHandlerClassA, |
| .last_irq_id = kDifPlicIrqIdAlertHandlerClassD, |
| }, |
| [kDifPlicPeripheralNmiGen] = |
| { |
| .first_irq_id = kDifPlicIrqIdNmiGenEsc0, |
| .last_irq_id = kDifPlicIrqIdNmiGenEsc3, |
| }, |
| [kDifPlicPeripheralUsbDev] = |
| { |
| .first_irq_id = kDifPlicIrqIdUsbDevPktReceived, |
| .last_irq_id = kDifPlicIrqIdUsbDevConnected, |
| }, |
| }; |
| |
| /** |
| * Get a number of IE, IP or LE registers (IE00, IE01, ...). |
| * |
| * With more than 32 IRQ sources, there is a multiple of these registers to |
| * accommodate all the bits (1 bit per IRQ source). |
| */ |
| static size_t plic_num_irq_reg(void) { |
| size_t register_num = kDifPlicIrqIdLast / PLIC_ID_TO_INDEX_REG_SIZE; |
| |
| // Determine whether there are remaining IRQ sources that have a register. |
| if ((kDifPlicIrqIdLast % PLIC_ID_TO_INDEX_REG_SIZE) != 0) { |
| ++register_num; |
| } |
| |
| return register_num; |
| } |
| |
| /** |
| * Get an IE, IP or LE register offset (IE00, IE01, ...) from an IRQ source ID. |
| * |
| * With more than 32 IRQ sources, there is a multiple of these registers to |
| * accommodate all the bits (1 bit per IRQ source). This function calculates |
| * the offset for a specific IRQ source ID (ID 33 would be IE01, ...). |
| */ |
| static ptrdiff_t plic_offset_from_reg0(dif_plic_irq_id_t irq) { |
| uint8_t register_index = PLIC_ID_TO_INDEX(irq) / PLIC_ID_TO_INDEX_REG_SIZE; |
| return register_index * sizeof(uint32_t); |
| } |
| |
| /** |
| * Get an IE, IP, LE register bit index from an IRQ source ID. |
| * |
| * With more than 32 IRQ sources, there is a multiple of these registers to |
| * accommodate all the bits (1 bit per IRQ source). This function calculates |
| * the bit position within a register for a specifci IRQ source ID (ID 33 would |
| * be bit 0). |
| */ |
| static uint8_t plic_reg_bit_index_from_irq_id(dif_plic_irq_id_t irq) { |
| return PLIC_ID_TO_INDEX(irq) % PLIC_ID_TO_INDEX_REG_SIZE; |
| } |
| |
| /** |
| * Get a target and an IRQ source specific Interrupt Enable register info. |
| */ |
| static void plic_irq_enable_reg_info(dif_plic_irq_id_t irq, |
| dif_plic_target_t target, |
| plic_reg_info_t *reg_info) { |
| ptrdiff_t offset = plic_offset_from_reg0(irq); |
| reg_info->offset = plic_target_reg_offsets[target].ie + offset; |
| reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq); |
| } |
| |
| /** |
| * Get an IRQ source specific Level/Edge register info. |
| */ |
| static void plic_irq_trigger_type_reg_info(dif_plic_irq_id_t irq, |
| plic_reg_info_t *reg_info) { |
| ptrdiff_t offset = plic_offset_from_reg0(irq); |
| reg_info->offset = RV_PLIC_LE0_REG_OFFSET + offset; |
| reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq); |
| } |
| |
| /** |
| * Get an IRQ source specific Interrupt Pending register info. |
| */ |
| static void plic_irq_pending_reg_info(dif_plic_irq_id_t irq, |
| plic_reg_info_t *reg_info) { |
| ptrdiff_t offset = plic_offset_from_reg0(irq); |
| reg_info->offset = RV_PLIC_IP0_REG_OFFSET + offset; |
| reg_info->bit_index = plic_reg_bit_index_from_irq_id(irq); |
| } |
| |
| /** |
| * Get a total number of priority registers (one for every IRQ source). |
| * |
| * The IRQ source IDs start from 1, so the last IRQ ID variant is also |
| * the number of priority registers (one per IRQ source). |
| */ |
| static size_t plic_num_priority_reg(void) { return kDifPlicIrqIdLast; } |
| |
| /** |
| * Get a PRIO register offset (PRIO0, PRIO1, ...) from an IRQ source ID. |
| * |
| * There is one PRIO register per IRQ source, this function calculates the IRQ |
| * source specific PRIO register offset. |
| */ |
| static ptrdiff_t plic_priority_reg_offset(dif_plic_irq_id_t irq) { |
| ptrdiff_t offset = PLIC_ID_TO_INDEX(irq) * sizeof(uint32_t); |
| return RV_PLIC_PRIO0_REG_OFFSET + offset; |
| } |
| |
| /** |
| * Reset the requested PLIC peripheral. |
| * |
| * This function resets all the relevant PLIC registers, apart from the CC |
| * register. There is no reliable way of knowing the ID of an IRQ that has |
| * claimed the CC register, so we assume that the previous "owner" of the |
| * resource has cleared/completed the CC access. |
| */ |
| static void plic_reset(const dif_plic_t *plic) { |
| // Clear all of Interrupt Enable and Level/Edge registers. |
| for (int i = 0; i < plic_num_irq_reg(); ++i) { |
| ptrdiff_t offset = RV_PLIC_IE00_REG_OFFSET + (i * sizeof(uint32_t)); |
| mmio_region_write32(plic->base_addr, offset, 0); |
| |
| offset = RV_PLIC_LE0_REG_OFFSET + (i * sizeof(uint32_t)); |
| mmio_region_write32(plic->base_addr, offset, 0); |
| } |
| |
| // Clear all of the priority registers. |
| for (int i = 0; i < plic_num_priority_reg(); ++i) { |
| ptrdiff_t offset = RV_PLIC_PRIO0_REG_OFFSET + (i * sizeof(uint32_t)); |
| mmio_region_write32(plic->base_addr, offset, 0); |
| } |
| |
| // Clear all of the target threshold registers. |
| for (dif_plic_target_t target = kDifPlicTargetIbex0; |
| target <= kDifPlicTargetLast; ++target) { |
| ptrdiff_t offset = plic_target_reg_offsets[target].threshold; |
| mmio_region_write32(plic->base_addr, offset, 0); |
| } |
| |
| // Clear software interrupt pending register. |
| mmio_region_write32(plic->base_addr, RV_PLIC_MSIP0_REG_OFFSET, 0); |
| } |
| |
| bool dif_plic_init(mmio_region_t base_addr, dif_plic_t *plic) { |
| if (plic == NULL) { |
| return false; |
| } |
| |
| plic->base_addr = base_addr; |
| |
| plic_reset(plic); |
| |
| return true; |
| } |
| |
| bool dif_plic_irq_enable_set(const dif_plic_t *plic, dif_plic_irq_id_t irq, |
| dif_plic_target_t target, |
| dif_plic_enable_t enable) { |
| if (plic == NULL) { |
| return false; |
| } |
| |
| // Get a target and an IRQ source specific Interrupt Enable register info. |
| plic_reg_info_t reg_info; |
| plic_irq_enable_reg_info(irq, target, ®_info); |
| |
| if (enable == kDifPlicEnable) { |
| mmio_region_nonatomic_set_bit32(plic->base_addr, reg_info.offset, |
| reg_info.bit_index); |
| } else { |
| mmio_region_nonatomic_clear_bit32(plic->base_addr, reg_info.offset, |
| reg_info.bit_index); |
| } |
| |
| return true; |
| } |
| |
| bool dif_plic_irq_trigger_type_set(const dif_plic_t *plic, |
| dif_plic_irq_id_t irq, |
| dif_plic_enable_t enable) { |
| if (plic == NULL) { |
| return false; |
| } |
| |
| // Get an IRQ source specific Level/Edge register info. |
| plic_reg_info_t reg_info; |
| plic_irq_trigger_type_reg_info(irq, ®_info); |
| |
| if (enable == kDifPlicEnable) { |
| mmio_region_nonatomic_set_bit32(plic->base_addr, reg_info.offset, |
| reg_info.bit_index); |
| } else { |
| mmio_region_nonatomic_clear_bit32(plic->base_addr, reg_info.offset, |
| reg_info.bit_index); |
| } |
| |
| return true; |
| } |
| |
| bool dif_plic_irq_priority_set(const dif_plic_t *plic, dif_plic_irq_id_t irq, |
| uint32_t priority) { |
| if (plic == NULL || priority > RV_PLIC_MAX_PRIORITY) { |
| return false; |
| } |
| |
| ptrdiff_t offset = plic_priority_reg_offset(irq); |
| mmio_region_write32(plic->base_addr, offset, priority); |
| |
| return true; |
| } |
| |
| bool dif_plic_target_threshold_set(const dif_plic_t *plic, |
| dif_plic_target_t target, |
| uint32_t threshold) { |
| if (plic == NULL || threshold > RV_PLIC_MAX_PRIORITY) { |
| return false; |
| } |
| |
| ptrdiff_t threshold_offset = plic_target_reg_offsets[target].threshold; |
| mmio_region_write32(plic->base_addr, threshold_offset, threshold); |
| |
| return true; |
| } |
| |
| bool dif_plic_irq_pending_status_get(const dif_plic_t *plic, |
| dif_plic_irq_id_t irq, |
| dif_plic_flag_t *status) { |
| if (plic == NULL || status == NULL) { |
| return false; |
| } |
| |
| plic_reg_info_t reg_info; |
| plic_irq_pending_reg_info(irq, ®_info); |
| |
| if (mmio_region_get_bit32(plic->base_addr, reg_info.offset, |
| reg_info.bit_index)) { |
| *status = kDifPlicSet; |
| } else { |
| *status = kDifPlicUnset; |
| } |
| |
| return true; |
| } |
| |
| bool dif_plic_irq_claim(const dif_plic_t *plic, dif_plic_target_t target, |
| dif_irq_claim_data_t *claim_data) { |
| if (plic == NULL || claim_data == NULL) { |
| return false; |
| } |
| |
| // Get an IRQ ID from the target specific CC register. |
| ptrdiff_t cc_offset = plic_target_reg_offsets[target].cc; |
| uint32_t irq_id = mmio_region_read32(plic->base_addr, cc_offset); |
| |
| // Validate an IRQ source, and determine which peripheral it belongs to. |
| dif_plic_irq_id_t irq_src = (dif_plic_irq_id_t)irq_id; |
| for (dif_plic_peripheral_t peripheral = kDifPlicPeripheralGpio; |
| peripheral <= kDifPlicPeripheralLast; ++peripheral) { |
| if (irq_src < plic_peripheral_ranges[peripheral].first_irq_id || |
| irq_src > plic_peripheral_ranges[peripheral].last_irq_id) { |
| continue; |
| } |
| |
| claim_data->peripheral = peripheral; |
| claim_data->source = irq_src; |
| claim_data->cc_offset = cc_offset; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool dif_plic_irq_complete(const dif_plic_t *plic, |
| const dif_irq_claim_data_t *complete_data) { |
| if (plic == NULL || complete_data == NULL) { |
| return false; |
| } |
| |
| // Write back the claimed IRQ ID to the target specific CC register, |
| // to notify the PLIC of the IRQ completion. |
| mmio_region_write32(plic->base_addr, complete_data->cc_offset, |
| (uint32_t)complete_data->source); |
| |
| return true; |
| } |