blob: b05dbeb6f2dac2840c3d3db0e32a441b3654197b [file] [log] [blame]
/*
* Copyright 2023 Google LLC
* Copyright lowRISC contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sw/device/lib/dif/dif_rv_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_smc_regs.h" // Generated.
const uint32_t kDifRvPlicMinPriority = 0;
const uint32_t kDifRvPlicMaxPriority = RV_PLIC_SMC_PRIO0_PRIO0_MASK;
/**
* 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;
/**
* 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_rv_plic_irq_id_t irq) {
uint8_t register_index = irq / RV_PLIC_SMC_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_rv_plic_irq_id_t irq) {
return irq % RV_PLIC_SMC_PARAM_REG_WIDTH;
}
/**
* Get the address of the first target N interrupt enable register (IEN0).
*/
static ptrdiff_t plic_irq_enable_base_for_target(dif_rv_plic_target_t target) {
ptrdiff_t range = RV_PLIC_SMC_IE0_MULTIREG_COUNT * sizeof(uint32_t);
return RV_PLIC_SMC_IE0_0_REG_OFFSET + (range * target);
}
/**
* Get the address of the first target N software interrupt register (MSIPN).
*/
static ptrdiff_t plic_software_irq_base_for_target(
dif_rv_plic_target_t target) {
return RV_PLIC_SMC_MSIP0_REG_OFFSET + (target * sizeof(uint32_t));
}
/**
* Get the address of the first target N threshold register (THRESHOLDN).
*/
static ptrdiff_t plic_threshold_base_for_target(dif_rv_plic_target_t target) {
return RV_PLIC_SMC_THRESHOLD0_REG_OFFSET + (target * sizeof(uint32_t));
}
/**
* Get the address of the first target N claim complete register (CCN).
*/
static ptrdiff_t plic_claim_complete_base_for_target(
dif_rv_plic_target_t target) {
return RV_PLIC_SMC_CC0_REG_OFFSET + (target * sizeof(uint32_t));
}
/**
* Get a target and an IRQ source specific Interrupt Enable register info.
*/
static plic_reg_info_t plic_irq_enable_reg_info(dif_rv_plic_irq_id_t irq,
dif_rv_plic_target_t target) {
ptrdiff_t offset = plic_offset_from_reg0(irq);
return (plic_reg_info_t){
.offset = plic_irq_enable_base_for_target(target) + 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_rv_plic_irq_id_t irq) {
ptrdiff_t offset = plic_offset_from_reg0(irq);
return (plic_reg_info_t){
.offset = RV_PLIC_SMC_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_rv_plic_irq_id_t irq) {
ptrdiff_t offset = irq * sizeof(uint32_t);
return RV_PLIC_SMC_PRIO0_REG_OFFSET + offset;
}
dif_result_t dif_rv_plic_reset(const dif_rv_plic_t *plic) {
if (plic == NULL) {
return kDifBadArg;
}
// Clear all of the priority registers.
for (int i = 0; i < RV_PLIC_SMC_PARAM_NUM_SRC; ++i) {
ptrdiff_t offset = plic_priority_reg_offset(i);
mmio_region_write32(plic->base_addr, offset, 0);
}
// Clear all of the target related registers.
for (dif_rv_plic_target_t target = 0; target < RV_PLIC_SMC_PARAM_NUM_TARGET;
++target) {
// Clear interrupt enable registers.
ptrdiff_t offset = plic_irq_enable_base_for_target(target);
for (int i = 0; i < RV_PLIC_SMC_IE0_MULTIREG_COUNT; ++i) {
ptrdiff_t multireg_offset = offset + (i * sizeof(uint32_t));
mmio_region_write32(plic->base_addr, multireg_offset, 0);
}
// Clear threshold registers.
offset = plic_threshold_base_for_target(target);
mmio_region_write32(plic->base_addr, offset, 0);
// Clear software interrupt registers.
offset = plic_software_irq_base_for_target(target);
mmio_region_write32(plic->base_addr, offset, 0);
}
return kDifOk;
}
dif_result_t dif_rv_plic_irq_get_enabled(const dif_rv_plic_t *plic,
dif_rv_plic_irq_id_t irq,
dif_rv_plic_target_t target,
dif_toggle_t *state) {
if (plic == NULL || irq >= RV_PLIC_SMC_PARAM_NUM_SRC ||
target >= RV_PLIC_SMC_PARAM_NUM_TARGET) {
return kDifBadArg;
}
plic_reg_info_t reg_info = plic_irq_enable_reg_info(irq, target);
uint32_t reg = mmio_region_read32(plic->base_addr, reg_info.offset);
bool is_enabled = bitfield_bit32_read(reg, reg_info.bit_index);
*state = is_enabled ? kDifToggleEnabled : kDifToggleDisabled;
return kDifOk;
}
dif_result_t dif_rv_plic_irq_set_enabled(const dif_rv_plic_t *plic,
dif_rv_plic_irq_id_t irq,
dif_rv_plic_target_t target,
dif_toggle_t state) {
if (plic == NULL || irq >= RV_PLIC_SMC_PARAM_NUM_SRC ||
target >= RV_PLIC_SMC_PARAM_NUM_TARGET) {
return kDifBadArg;
}
bool flag;
switch (state) {
case kDifToggleEnabled:
flag = true;
break;
case kDifToggleDisabled:
flag = false;
break;
default:
return kDifBadArg;
}
plic_reg_info_t reg_info = plic_irq_enable_reg_info(irq, target);
uint32_t reg = mmio_region_read32(plic->base_addr, reg_info.offset);
reg = bitfield_bit32_write(reg, reg_info.bit_index, flag);
mmio_region_write32(plic->base_addr, reg_info.offset, reg);
return kDifOk;
}
dif_result_t dif_rv_plic_irq_set_priority(const dif_rv_plic_t *plic,
dif_rv_plic_irq_id_t irq,
uint32_t priority) {
if (plic == NULL || irq >= RV_PLIC_SMC_PARAM_NUM_SRC ||
priority > kDifRvPlicMaxPriority) {
return kDifBadArg;
}
ptrdiff_t offset = plic_priority_reg_offset(irq);
mmio_region_write32(plic->base_addr, offset, priority);
return kDifOk;
}
dif_result_t dif_rv_plic_target_set_threshold(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target,
uint32_t threshold) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET ||
threshold > kDifRvPlicMaxPriority) {
return kDifBadArg;
}
ptrdiff_t threshold_offset = plic_threshold_base_for_target(target);
mmio_region_write32(plic->base_addr, threshold_offset, threshold);
return kDifOk;
}
dif_result_t dif_rv_plic_irq_is_pending(const dif_rv_plic_t *plic,
dif_rv_plic_irq_id_t irq,
bool *is_pending) {
if (plic == NULL || irq >= RV_PLIC_SMC_PARAM_NUM_SRC || is_pending == NULL) {
return kDifBadArg;
}
plic_reg_info_t reg_info = plic_irq_pending_reg_info(irq);
uint32_t reg = mmio_region_read32(plic->base_addr, reg_info.offset);
*is_pending = bitfield_bit32_read(reg, reg_info.bit_index);
return kDifOk;
}
dif_result_t dif_rv_plic_irq_claim(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target,
dif_rv_plic_irq_id_t *claim_data) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET ||
claim_data == NULL) {
return kDifBadArg;
}
ptrdiff_t claim_complete_reg = plic_claim_complete_base_for_target(target);
*claim_data = mmio_region_read32(plic->base_addr, claim_complete_reg);
return kDifOk;
}
dif_result_t dif_rv_plic_irq_complete(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target,
dif_rv_plic_irq_id_t complete_data) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET) {
return kDifBadArg;
}
// 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 = plic_claim_complete_base_for_target(target);
mmio_region_write32(plic->base_addr, claim_complete_reg, complete_data);
return kDifOk;
}
dif_result_t dif_rv_plic_software_irq_force(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET) {
return kDifBadArg;
}
ptrdiff_t msip_offset = plic_software_irq_base_for_target(target);
mmio_region_write32(plic->base_addr, msip_offset, 1);
return kDifOk;
}
dif_result_t dif_rv_plic_software_irq_acknowledge(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET) {
return kDifBadArg;
}
ptrdiff_t msip_offset = plic_software_irq_base_for_target(target);
mmio_region_write32(plic->base_addr, msip_offset, 0);
return kDifOk;
}
dif_result_t dif_rv_plic_software_irq_is_pending(const dif_rv_plic_t *plic,
dif_rv_plic_target_t target,
bool *is_pending) {
if (plic == NULL || target >= RV_PLIC_SMC_PARAM_NUM_TARGET ||
is_pending == NULL) {
return kDifBadArg;
}
ptrdiff_t msip_offset = plic_software_irq_base_for_target(target);
uint32_t register_value = mmio_region_read32(plic->base_addr, msip_offset);
*is_pending = (register_value == 1) ? true : false;
return kDifOk;
}