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