blob: b6e6d53177e5fe6a7505835fc93be77f99290a68 [file] [log] [blame]
// 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;
}