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