| // 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 "sw/device/lib/base/bitfield.h" | |
| #include "sw/device/lib/base/mmio.h" | |
| #include "rv_plic_regs.h" // Generated. | |
| // If either of these static assertions fail, then the assumptions in this DIF | |
| // implementation should be revisited. In particular, `kPlicTargets` | |
| // may need updating, | |
| _Static_assert(RV_PLIC_PARAM_NUMSRC == 99, | |
| "PLIC instantiation parameters have changed."); | |
| _Static_assert(RV_PLIC_PARAM_NUMTARGET == 1, | |
| "PLIC instantiation parameters have changed."); | |
| const uint32_t kDifPlicMinPriority = 0; | |
| const uint32_t kDifPlicMaxPriority = 0x3u; | |
| /** | |
| * 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; | |
| bitfield_bit32_index_t bit_index; | |
| } 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 { | |
| /** | |
| * IRQ enable register offset for this target. | |
| */ | |
| ptrdiff_t irq_enable; | |
| /** | |
| * Claim/complete register offset for this target. | |
| */ | |
| ptrdiff_t claim_complete; | |
| /** | |
| * Threshold register offset for this target. | |
| */ | |
| ptrdiff_t threshold; | |
| /** | |
| * Software interrupt register offset. | |
| */ | |
| ptrdiff_t msip; | |
| } plic_target_reg_offset_t; | |
| // This array gives a way of getting the target-specific register offsets from | |
| // a `dif_plic_target_t`. | |
| // | |
| // There should be an instance of `plic_target_reg_offset_t` for each possible | |
| // target, so there should be `RV_PLIC_PARAM_NUMTARGET` entries in this array. | |
| // The `i`th entry should contain the offsets of the `i`th target specific | |
| // registers: | |
| // - `RV_PLIC_IE<i>_0_REG_OFFSET` (the first IE reg for target `i`). | |
| // - `RV_PLIC_CC<i>_REG_OFFSET` | |
| // - `RV_PLIC_THRESHOLD<i>_REG_OFFSET` | |
| // - `RV_PLIC_MSIP<i>_REG_OFFSET` | |
| static const plic_target_reg_offset_t kPlicTargets[] = { | |
| [0] = | |
| { | |
| .irq_enable = RV_PLIC_IE0_0_REG_OFFSET, | |
| .claim_complete = RV_PLIC_CC0_REG_OFFSET, | |
| .threshold = RV_PLIC_THRESHOLD0_REG_OFFSET, | |
| .msip = RV_PLIC_MSIP0_REG_OFFSET, | |
| }, | |
| }; | |
| _Static_assert(sizeof(kPlicTargets) / sizeof(*kPlicTargets) == | |
| RV_PLIC_PARAM_NUMTARGET, | |
| "There should be an entry in kPlicTargets for every target"); | |
| /** | |
| * Get an IE, IP or LE register offset (IE0_0, 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 32 would be IE01, ...). | |
| */ | |
| static ptrdiff_t plic_offset_from_reg0(dif_plic_irq_id_t irq) { | |
| uint8_t register_index = irq / RV_PLIC_PARAM_REG_WIDTH; | |
| 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 specific IRQ source ID (ID 32 would | |
| * be bit 0). | |
| */ | |
| static uint8_t plic_irq_bit_index(dif_plic_irq_id_t irq) { | |
| return irq % RV_PLIC_PARAM_REG_WIDTH; | |
| } | |
| /** | |
| * Get a target and an IRQ source specific Interrupt Enable register info. | |
| */ | |
| static plic_reg_info_t plic_irq_enable_reg_info(dif_plic_irq_id_t irq, | |
| dif_plic_target_t target) { | |
| ptrdiff_t offset = plic_offset_from_reg0(irq); | |
| return (plic_reg_info_t){ | |
| .offset = kPlicTargets[target].irq_enable + offset, | |
| .bit_index = plic_irq_bit_index(irq), | |
| }; | |
| } | |
| /** | |
| * Get an IRQ source specific Level/Edge register info. | |
| */ | |
| static plic_reg_info_t plic_irq_trigger_type_reg_info(dif_plic_irq_id_t irq) { | |
| ptrdiff_t offset = plic_offset_from_reg0(irq); | |
| return (plic_reg_info_t){ | |
| .offset = RV_PLIC_LE_0_REG_OFFSET + offset, | |
| .bit_index = plic_irq_bit_index(irq), | |
| }; | |
| } | |
| /** | |
| * Get an IRQ source specific Interrupt Pending register info. | |
| */ | |
| static plic_reg_info_t plic_irq_pending_reg_info(dif_plic_irq_id_t irq) { | |
| ptrdiff_t offset = plic_offset_from_reg0(irq); | |
| return (plic_reg_info_t){ | |
| .offset = RV_PLIC_IP_0_REG_OFFSET + offset, | |
| .bit_index = plic_irq_bit_index(irq), | |
| }; | |
| } | |
| /** | |
| * 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 = 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 the Interrupt Enable registers. | |
| for (int i = 0; i < RV_PLIC_IE0_MULTIREG_COUNT; ++i) { | |
| ptrdiff_t offset = RV_PLIC_IE0_0_REG_OFFSET + (i * sizeof(uint32_t)); | |
| mmio_region_write32(plic->params.base_addr, offset, 0); | |
| } | |
| // Clear all of the Level/Edge registers. | |
| for (int i = 0; i < RV_PLIC_LE_MULTIREG_COUNT; ++i) { | |
| ptrdiff_t offset = RV_PLIC_LE_0_REG_OFFSET + (i * sizeof(uint32_t)); | |
| mmio_region_write32(plic->params.base_addr, offset, 0); | |
| } | |
| // Clear all of the priority registers. | |
| for (int i = 0; i < RV_PLIC_PARAM_NUMSRC; ++i) { | |
| ptrdiff_t offset = plic_priority_reg_offset(i); | |
| mmio_region_write32(plic->params.base_addr, offset, 0); | |
| } | |
| // Clear all of the target threshold registers. | |
| for (dif_plic_target_t target = 0; target < RV_PLIC_PARAM_NUMTARGET; | |
| ++target) { | |
| ptrdiff_t offset = kPlicTargets[target].threshold; | |
| mmio_region_write32(plic->params.base_addr, offset, 0); | |
| } | |
| // Clear software interrupt pending register. | |
| mmio_region_write32(plic->params.base_addr, RV_PLIC_MSIP0_REG_OFFSET, 0); | |
| } | |
| dif_plic_result_t dif_plic_init(dif_plic_params_t params, dif_plic_t *plic) { | |
| if (plic == NULL) { | |
| return kDifPlicBadArg; | |
| } | |
| plic->params = params; | |
| // TODO: Move this out into its own function. | |
| plic_reset(plic); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_get_enabled(const dif_plic_t *plic, | |
| dif_plic_irq_id_t irq, | |
| dif_plic_target_t target, | |
| dif_plic_toggle_t *state) { | |
| if (plic == NULL || irq >= RV_PLIC_PARAM_NUMSRC || | |
| target >= RV_PLIC_PARAM_NUMTARGET) { | |
| return kDifPlicBadArg; | |
| } | |
| plic_reg_info_t reg_info = plic_irq_enable_reg_info(irq, target); | |
| uint32_t reg = mmio_region_read32(plic->params.base_addr, reg_info.offset); | |
| bool is_enabled = bitfield_bit32_read(reg, reg_info.bit_index); | |
| *state = is_enabled ? kDifPlicToggleEnabled : kDifPlicToggleDisabled; | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_set_enabled(const dif_plic_t *plic, | |
| dif_plic_irq_id_t irq, | |
| dif_plic_target_t target, | |
| dif_plic_toggle_t state) { | |
| if (plic == NULL || irq >= RV_PLIC_PARAM_NUMSRC || | |
| target >= RV_PLIC_PARAM_NUMTARGET) { | |
| return kDifPlicBadArg; | |
| } | |
| bool flag; | |
| switch (state) { | |
| case kDifPlicToggleEnabled: | |
| flag = true; | |
| break; | |
| case kDifPlicToggleDisabled: | |
| flag = false; | |
| break; | |
| default: | |
| return kDifPlicBadArg; | |
| } | |
| plic_reg_info_t reg_info = plic_irq_enable_reg_info(irq, target); | |
| uint32_t reg = mmio_region_read32(plic->params.base_addr, reg_info.offset); | |
| reg = bitfield_bit32_write(reg, reg_info.bit_index, flag); | |
| mmio_region_write32(plic->params.base_addr, reg_info.offset, reg); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_set_trigger(const dif_plic_t *plic, | |
| dif_plic_irq_id_t irq, | |
| dif_plic_irq_trigger_t trigger) { | |
| if (plic == NULL || irq >= RV_PLIC_PARAM_NUMSRC) { | |
| return kDifPlicBadArg; | |
| } | |
| bool flag; | |
| switch (trigger) { | |
| case kDifPlicIrqTriggerEdge: | |
| flag = true; | |
| break; | |
| case kDifPlicIrqTriggerLevel: | |
| flag = false; | |
| break; | |
| default: | |
| return kDifPlicBadArg; | |
| } | |
| plic_reg_info_t reg_info = plic_irq_trigger_type_reg_info(irq); | |
| uint32_t reg = mmio_region_read32(plic->params.base_addr, reg_info.offset); | |
| reg = bitfield_bit32_write(reg, reg_info.bit_index, flag); | |
| mmio_region_write32(plic->params.base_addr, reg_info.offset, reg); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_set_priority(const dif_plic_t *plic, | |
| dif_plic_irq_id_t irq, | |
| uint32_t priority) { | |
| if (plic == NULL || irq >= RV_PLIC_PARAM_NUMSRC || | |
| priority > kDifPlicMaxPriority) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t offset = plic_priority_reg_offset(irq); | |
| mmio_region_write32(plic->params.base_addr, offset, priority); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_target_set_threshold(const dif_plic_t *plic, | |
| dif_plic_target_t target, | |
| uint32_t threshold) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET || | |
| threshold > kDifPlicMaxPriority) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t threshold_offset = kPlicTargets[target].threshold; | |
| mmio_region_write32(plic->params.base_addr, threshold_offset, threshold); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_is_pending(const dif_plic_t *plic, | |
| dif_plic_irq_id_t irq, | |
| bool *is_pending) { | |
| if (plic == NULL || irq >= RV_PLIC_PARAM_NUMSRC || is_pending == NULL) { | |
| return kDifPlicBadArg; | |
| } | |
| plic_reg_info_t reg_info = plic_irq_pending_reg_info(irq); | |
| uint32_t reg = mmio_region_read32(plic->params.base_addr, reg_info.offset); | |
| *is_pending = bitfield_bit32_read(reg, reg_info.bit_index); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_claim(const dif_plic_t *plic, | |
| dif_plic_target_t target, | |
| dif_plic_irq_id_t *claim_data) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET || claim_data == NULL) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t claim_complete_reg = kPlicTargets[target].claim_complete; | |
| *claim_data = mmio_region_read32(plic->params.base_addr, claim_complete_reg); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_irq_complete( | |
| const dif_plic_t *plic, dif_plic_target_t target, | |
| const dif_plic_irq_id_t *complete_data) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET || | |
| complete_data == NULL) { | |
| return kDifPlicBadArg; | |
| } | |
| // Write back the claimed IRQ ID to the target specific CC register, | |
| // to notify the PLIC of the IRQ completion. | |
| ptrdiff_t claim_complete_reg = kPlicTargets[target].claim_complete; | |
| mmio_region_write32(plic->params.base_addr, claim_complete_reg, | |
| *complete_data); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_software_irq_force(const dif_plic_t *plic, | |
| dif_plic_target_t target) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t msip_offset = kPlicTargets[target].msip; | |
| mmio_region_write32(plic->params.base_addr, msip_offset, 1); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_software_irq_acknowledge(const dif_plic_t *plic, | |
| dif_plic_target_t target) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t msip_offset = kPlicTargets[target].msip; | |
| mmio_region_write32(plic->params.base_addr, msip_offset, 0); | |
| return kDifPlicOk; | |
| } | |
| dif_plic_result_t dif_plic_software_irq_is_pending(const dif_plic_t *plic, | |
| dif_plic_target_t target, | |
| bool *is_pending) { | |
| if (plic == NULL || target >= RV_PLIC_PARAM_NUMTARGET || is_pending == NULL) { | |
| return kDifPlicBadArg; | |
| } | |
| ptrdiff_t msip_offset = kPlicTargets[target].msip; | |
| uint32_t register_value = | |
| mmio_region_read32(plic->params.base_addr, msip_offset); | |
| *is_pending = (register_value == 1) ? true : false; | |
| return kDifPlicOk; | |
| } |