blob: f0f3813fda2f1d1766e6e90803f5db7793088419 [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/runtime/pmp.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/csr.h"
#include "sw/device/lib/base/macros.h"
// "Volume II: RISC-V Privileged Architectures V20190608-Priv-MSU-Ratified",
// "3.6.1 Physical Memory Protection CSRs",
// "Figure 3.28: PMP configuration register format".
#define PMP_CFG_CSR_R 0
#define PMP_CFG_CSR_W 1
#define PMP_CFG_CSR_X 2
#define PMP_CFG_CSR_A 3
#define PMP_CFG_CSR_L 7
#define PMP_CFG_FIELDS_PER_REG 4
#define PMP_CFG_FIELD_WIDTH 8
#define PMP_CFG_FIELD_MASK 0xff
// "Volume II: RISC-V Privileged Architectures V20190608-Priv-MSU-Ratified",
// "3.6.1 Physical Memory Protection CSRs", "Address Matching".
#define PMP_CFG_CSR_MODE_OFF 0
#define PMP_CFG_CSR_MODE_TOR 1
#define PMP_CFG_CSR_MODE_NA4 2
#define PMP_CFG_CSR_MODE_NAPOT 3
#define PMP_CFG_CSR_MODE_MASK 0x3
typedef enum pmp_csr_access_type {
kPmpCsrAccessTypeRead = 0,
kPmpCsrAccessTypeWrite,
} pmp_csr_access_type_t;
static const bitfield_field32_t kPmpCfgModeField = {
.mask = PMP_CFG_CSR_MODE_MASK,
.index = PMP_CFG_CSR_A,
};
/**
* This is an X-Macro used for automatically deriving switch statements which
* link PMP region identifiers to associated information including their
* configuration register identifier.
*
* This macro should be invoked with a macro argument with the following
* signature:
*
* @param region_id PMP Region Identifier.
* @param config_reg_id Configuration Register ID for a given PMP Region (for
* Ibex).
*/
#define PMP_REGIONS(X) \
X(0, 0) \
X(1, 0) \
X(2, 0) \
X(3, 0) \
\
X(4, 1) \
X(5, 1) \
X(6, 1) \
X(7, 1) \
\
X(8, 2) \
X(9, 2) \
X(10, 2) \
X(11, 2) \
\
X(12, 3) \
X(13, 3) \
X(14, 3) \
X(15, 3)
/**
* Reads the pmpcfg for a given region.
*
* This reads the entire `pmpcfgN` value, not just the word associated with the
* current region.
*
* @param region PMP Region ID to read.
* @param[out] value Where to put the result of the read.
* @return `true` if `pmp_region_index_t` is valid and value was read, `false`
* otherwise.
*/
OT_WARN_UNUSED_RESULT
static bool pmp_cfg_csr_read(pmp_region_index_t region, uint32_t *value) {
#define PMP_READ_CONFIG_(region_id, config_reg_id) \
case region_id: { \
CSR_READ(CSR_REG_PMPCFG##config_reg_id, value); \
return true; \
}
switch (region) {
PMP_REGIONS(PMP_READ_CONFIG_)
default:
return false;
}
}
/**
* Writes the pmpcfg for a given region.
*
* This writes the entire `pmpcfgN` value, not just the word associated with the
* current region.
*
* @param region PMP Region ID to write.
* @param value the value to write.
* @return `true` if `pmp_region_index_t` is valid and value was written,
* `false` otherwise.
*/
OT_WARN_UNUSED_RESULT
static bool pmp_cfg_csr_write(pmp_region_index_t region, uint32_t value) {
#define PMP_WRITE_CONFIG_(region_id, config_reg_id) \
case region_id: { \
CSR_WRITE(CSR_REG_PMPCFG##config_reg_id, value); \
return true; \
}
switch (region) {
PMP_REGIONS(PMP_WRITE_CONFIG_)
default:
return false;
}
}
/**
* Reads the pmpaddr for a given region.
*
* @param region PMP Region ID to read.
* @param[out] value Where to put the result of the read.
* @return `true` if `pmp_region_index_t` is valid and value was read, `false`
* otherwise.
*/
OT_WARN_UNUSED_RESULT
static bool pmp_addr_csr_read(pmp_region_index_t region, uint32_t *value) {
#define PMP_READ_ADDR_(region_id, _) \
case region_id: { \
CSR_READ(CSR_REG_PMPADDR##region_id, value); \
return true; \
}
switch (region) {
PMP_REGIONS(PMP_READ_ADDR_)
default:
return false;
}
}
/**
* Writes the pmpcfg for a given region.
*
* @param region PMP region ID to get/set.
* @param value Value to write into a CSR.
* @return `pmp_region_configure_result_t`.
*/
OT_WARN_UNUSED_RESULT
static bool pmp_addr_csr_write(pmp_region_index_t region, uint32_t value) {
#define PMP_WRITE_ADDR_(region_id, _) \
case region_id: { \
CSR_WRITE(CSR_REG_PMPADDR##region_id, value); \
return true; \
}
switch (region) {
PMP_REGIONS(PMP_WRITE_ADDR_)
default:
return false;
}
}
/**
* Retrievs configuration information for the requested `region`.
*
* A single `pmpcfg` CSR packs configuration information for `N` regions.
*
* @param region PMP region ID.
* @param field_value Configuration information for the `region`.
* @return `pmp_region_configure_result_t`.
*/
OT_WARN_UNUSED_RESULT
static pmp_region_configure_result_t pmp_csr_cfg_field_read(
pmp_region_index_t region, uint32_t *field_value) {
uint32_t cfg_csr_original;
if (!pmp_cfg_csr_read(region, &cfg_csr_original)) {
return kPmpRegionConfigureError;
}
size_t field_index = (region % PMP_CFG_FIELDS_PER_REG) * PMP_CFG_FIELD_WIDTH;
bitfield_field32_t pmp_csr_cfg_field = {
.mask = PMP_CFG_FIELD_MASK,
.index = field_index,
};
*field_value = bitfield_field32_read(cfg_csr_original, pmp_csr_cfg_field);
return kPmpRegionConfigureOk;
}
/**
* Writes configuration information for the requested `region`.
*
* A single `pmpcfg` CSR packs configuration information for `N` regions.
*
* @param region PMP region ID.
* @param field_value Configuration information for the `region`.
* @return `pmp_region_configure_result_t`.
*/
OT_WARN_UNUSED_RESULT
static pmp_region_configure_result_t pmp_csr_cfg_field_write(
pmp_region_index_t region, uint32_t field_value) {
uint32_t cfg_csr_current;
if (!pmp_cfg_csr_read(region, &cfg_csr_current)) {
return kPmpRegionConfigureError;
}
// Determine the pmpcfg field index based on the `region`.
size_t field_index = (region % PMP_CFG_FIELDS_PER_REG) * PMP_CFG_FIELD_WIDTH;
bitfield_field32_t pmp_csr_cfg_field = {
.mask = PMP_CFG_FIELD_MASK,
.index = field_index,
};
uint32_t cfg_csr_new =
bitfield_field32_write(cfg_csr_current, pmp_csr_cfg_field, field_value);
if (!pmp_cfg_csr_write(region, cfg_csr_new)) {
return kPmpRegionConfigureError;
}
if (!pmp_cfg_csr_read(region, &cfg_csr_current)) {
return kPmpRegionConfigureError;
}
if (cfg_csr_current != cfg_csr_new) {
return kPmpRegionConfigureWarlError;
}
return kPmpRegionConfigureOk;
}
/**
* Writes `address` to a pmpaddr CSRs.
*
* The corresponding pmpaddrN index N is determined by `region`.
*
* PMP address must be at least 4bytes aligned, and pmpaddr holds only bits
* 33:2. This means that before writing an address to a pmpaddr CSR, it must be
* shifted 2 bits to the right.
*
* Please see:
* "Volume II: RISC-V Privileged Architectures V20190608-Priv-MSU-Ratified",
* "3.6.1 Physical Memory Protection CSRs",
* "Figure 3.26: PMP address register format, RV32".
*
* @param region PMP region to configure and set address for.
* @param address Address to be set.
* @return `pmp_region_configure_result_t`.
*/
pmp_region_configure_result_t pmp_csr_address_write(pmp_region_index_t region,
uintptr_t address) {
uint32_t address_shifted = address >> PMP_ADDRESS_SHIFT;
if (!pmp_addr_csr_write(region, address_shifted)) {
return kPmpRegionConfigureError;
}
uint32_t addr_csr_after_write;
if (!pmp_addr_csr_read(region, &addr_csr_after_write)) {
return kPmpRegionConfigureError;
}
if (address_shifted != addr_csr_after_write) {
return kPmpRegionConfigureWarlError;
}
return kPmpRegionConfigureOk;
}
/**
* Set PMP region permissions.
*
* @param perm Memory access permissions.
* @param bitfield Bitfield to set.
* @return `true` on success, `false` on failure.
*/
OT_WARN_UNUSED_RESULT
static bool pmp_cfg_permissions_set(pmp_region_permissions_t perm,
uint32_t *bitfield) {
switch (perm) {
case kPmpRegionPermissionsNone:
// No access is allowed.
break;
case kPmpRegionPermissionsReadOnly:
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_R, true);
break;
case kPmpRegionPermissionsExecuteOnly:
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_X, true);
break;
case kPmpRegionPermissionsReadExecute:
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_R, true);
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_X, true);
break;
case kPmpRegionPermissionsReadWrite:
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_R, true);
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_W, true);
break;
case kPmpRegionPermissionsReadWriteExecute:
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_R, true);
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_W, true);
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_X, true);
break;
default:
return false;
}
return true;
}
/**
* Set PMP region lock.
*
* @param lock Lock to indicate whether the region must be locked.
* @param bitfield Bitfield to set.
*/
static void pmp_cfg_mode_lock_set(pmp_region_lock_t lock, uint32_t *bitfield) {
bool flag = (lock == kPmpRegionLockLocked) ? true : false;
*bitfield = bitfield_bit32_write(*bitfield, PMP_CFG_CSR_L, flag);
}
/**
* Check whether `address` is correctly aligned.
*
* The alignment depend on the granularity, which is implementation specific,
* and for Ibex is `PMP_GRANULARITY_IBEX`. Default granularity "G" is 0, which
* means a minimal alignment of 4bytes. Please see:
* "Volume II: RISC-V Privileged Architectures V20190608-Priv-MSU-Ratified",
* "3.6 Physical Memory Protection", "Figure 3.26" and section
* "Address Matching".
*
* @param address System address.
* @return `true` on success, `false` on failure.
*/
static bool pmp_address_aligned(uintptr_t address) {
return address == (address & PMP_ADDRESS_ALIGNMENT_INVERTED_MASK);
}
/**
* Constructs a NAPOT address from the requested system address and size.
*
* This function makes sure that the `address` and `size` are valid, and then
* constructs a corresponding NAPOT address. Please see:
* "Volume II: RISC-V Privileged Architectures V20190608-Priv-MSU-Ratified",
* "3.6 Physical Memory Protection", "Figure 3.26" and "Table 3.10".
*
* @param address Conventional system address.
* @param size The size of a range to protect.
* @param pmp_address_napot Constructed NAPOT address.
* @return `pmp_region_configure_napot_result_t`.
*/
OT_WARN_UNUSED_RESULT
static pmp_region_configure_napot_result_t pmp_napot_address_construct(
uintptr_t address, uint32_t size, uintptr_t *pmp_address_napot) {
// Must be at least the size of the minimal alignment adjusted for
// granularity, and the minimal allowed size for the NAPOT mode.
if (size < PMP_ADDRESS_ALIGNMENT || size < PMP_ADDRESS_MIN_ALIGNMENT_NAPOT) {
return kPmpRegionConfigureNapotBadAddress;
}
// Check if the `size` is a Power Of Two.
uint32_t size_mask = size - 1;
if ((size & size_mask) != 0) {
return kPmpRegionConfigureNapotBadSize;
}
// Check if the address is aligned to the `size`.
if (address != (address & (~size_mask))) {
return kPmpRegionConfigureNapotBadAddress;
}
// `size_mask` must be right shifted, as the minimal legal size in NAPOT
// mode is 8 bytes.
*pmp_address_napot = address | (size_mask >> 1);
return kPmpRegionConfigureNapotOk;
}
pmp_region_configure_result_t pmp_region_configure_off(
pmp_region_index_t region, uintptr_t address) {
if (region >= PMP_REGIONS_NUM) {
return kPmpRegionConfigureBadRegion;
}
if (!pmp_address_aligned(address)) {
return kPmpRegionConfigureBadAddress;
}
// Address registers must be written prior to the configuration registers to
// ensure that they are not locked.
pmp_region_configure_result_t result = pmp_csr_address_write(region, address);
if (result != kPmpRegionConfigureOk) {
return result;
}
// Clear the appropriate region field of the pmpcfg CSR.
result = pmp_csr_cfg_field_write(region, 0);
if (result != kPmpRegionConfigureOk) {
return result;
}
return kPmpRegionConfigureOk;
}
pmp_region_configure_na4_result_t pmp_region_configure_na4(
pmp_region_index_t region, pmp_region_config_t config, uintptr_t address) {
if (PMP_GRANULARITY_IBEX > 0) {
return kPmpRegionConfigureNa4Unavailable;
}
if (region >= PMP_REGIONS_NUM) {
return kPmpRegionConfigureNa4BadRegion;
}
if (!pmp_address_aligned(address)) {
return kPmpRegionConfigureNa4BadAddress;
}
uint32_t field_value = 0;
if (!pmp_cfg_permissions_set(config.permissions, &field_value)) {
return kPmpRegionConfigureNa4Error;
}
pmp_cfg_mode_lock_set(config.lock, &field_value);
field_value = bitfield_field32_write(field_value, kPmpCfgModeField,
PMP_CFG_CSR_MODE_NA4);
// Address registers must be written prior to the configuration registers to
// ensure that they are not locked.
pmp_region_configure_result_t result = pmp_csr_address_write(region, address);
if (result != kPmpRegionConfigureOk) {
return (pmp_region_configure_na4_result_t)result;
}
result = pmp_csr_cfg_field_write(region, field_value);
if (result != kPmpRegionConfigureOk) {
return (pmp_region_configure_na4_result_t)result;
}
return kPmpRegionConfigureNa4Ok;
}
pmp_region_configure_napot_result_t pmp_region_configure_napot(
pmp_region_index_t region, pmp_region_config_t config, uintptr_t address,
uint32_t size) {
if (region >= PMP_REGIONS_NUM) {
return kPmpRegionConfigureNapotBadRegion;
}
uintptr_t napot_address;
pmp_region_configure_napot_result_t napot_result =
pmp_napot_address_construct(address, size, &napot_address);
if (napot_result != kPmpRegionConfigureNapotOk) {
return napot_result;
}
uint32_t field_value = 0;
if (!pmp_cfg_permissions_set(config.permissions, &field_value)) {
return kPmpRegionConfigureNapotError;
}
pmp_cfg_mode_lock_set(config.lock, &field_value);
field_value = bitfield_field32_write(field_value, kPmpCfgModeField,
PMP_CFG_CSR_MODE_NAPOT);
// Address registers must be written prior to the configuration registers to
// ensure that they are not locked.
pmp_region_configure_result_t result =
pmp_csr_address_write(region, napot_address);
if (result != kPmpRegionConfigureOk) {
return (pmp_region_configure_napot_result_t)result;
}
result = pmp_csr_cfg_field_write(region, field_value);
if (result != kPmpRegionConfigureOk) {
return (pmp_region_configure_napot_result_t)result;
}
return kPmpRegionConfigureNapotOk;
}
pmp_region_configure_result_t pmp_region_configure_tor(
pmp_region_index_t region_end, pmp_region_config_t config,
uintptr_t address_start, uintptr_t address_end) {
if (region_end >= PMP_REGIONS_NUM) {
return kPmpRegionConfigureBadRegion;
}
if (region_end == 0 && address_start > 0) {
return kPmpRegionConfigureBadAddress;
}
if (region_end > 0 && !pmp_address_aligned(address_start)) {
return kPmpRegionConfigureBadAddress;
}
if (!pmp_address_aligned(address_end)) {
return kPmpRegionConfigureBadAddress;
}
uint32_t field_value = 0;
if (!pmp_cfg_permissions_set(config.permissions, &field_value)) {
return kPmpRegionConfigureError;
}
pmp_cfg_mode_lock_set(config.lock, &field_value);
field_value = bitfield_field32_write(field_value, kPmpCfgModeField,
PMP_CFG_CSR_MODE_TOR);
// Address registers must be written prior to the configuration registers to
// ensure that they are not locked.
if (region_end != 0) {
pmp_region_configure_result_t result =
pmp_csr_address_write(region_end - 1, address_start);
if (result != kPmpRegionConfigureOk) {
return result;
}
}
pmp_region_configure_result_t result =
pmp_csr_address_write(region_end, address_end);
if (result != kPmpRegionConfigureOk) {
return result;
}
result = pmp_csr_cfg_field_write(region_end, field_value);
if (result != kPmpRegionConfigureOk) {
return result;
}
return kPmpRegionConfigureOk;
}
pmp_region_configure_result_t pmp_cfg_mode_lock_status_get(
pmp_region_index_t region, pmp_region_lock_t *lock) {
if (region >= PMP_REGIONS_NUM) {
return kPmpRegionConfigureBadRegion;
}
if (lock == NULL) {
return kPmpRegionConfigureBadArg;
}
uint32_t field_value;
pmp_region_configure_result_t result =
pmp_csr_cfg_field_read(region, &field_value);
if (result != kPmpRegionConfigureOk) {
return result;
}
bool flag = bitfield_bit32_read(field_value, PMP_CFG_CSR_L);
*lock = flag ? kPmpRegionLockLocked : kPmpRegionLockUnlocked;
return kPmpRegionConfigureOk;
}