| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| #ifndef OPENTITAN_SW_DEVICE_LIB_RUNTIME_EPMP_H_ | |
| #define OPENTITAN_SW_DEVICE_LIB_RUNTIME_EPMP_H_ | |
| #include <stdint.h> | |
| /** | |
| * Enhanced Physical Memory Protection (EPMP). | |
| * | |
| * This library is intended for PMP entry configuration management in Machine | |
| * mode (M-mode). | |
| * | |
| * Assumptions (should be initialized in assembly but can be verified using | |
| * `epmp_check`): | |
| * - Machine Mode Whitelist Policy is enabled (mseccfg.MMWP = 1) | |
| * - Machine Mode Lockdown is not set (mseccfg.MML = 0) | |
| * | |
| * Typically this library will be used with Rule Locking Bypass | |
| * (mseccfg.RLB = 1) enabled however this is not a hard requirement and RLB | |
| * can be disabled using this library. | |
| * | |
| * Ibex PMP Documentation: | |
| * https://ibex-core.readthedocs.io/en/latest/03_reference/pmp.html | |
| */ | |
| /** | |
| * EPMP entry permissions. | |
| * | |
| * Entries configured with locked permissions may only be modified | |
| * if Rule Locking Bypass is set (mseccfg.RLB = 1). | |
| * | |
| * When Machine Mode Lockdown is disabled (mseccfg.MML = 0) the | |
| * combination R=0 W=1 is reserved. This is the assumed state and so | |
| * it is not possible to set these values. The combination R=1 W=1 | |
| * X=1 has also been reserved by this library and may not be used. | |
| * | |
| * An entry may only be configured with unlocked permissions if the | |
| * entry is also configured as OFF. | |
| * | |
| * Note: permissions may have different meanings when Machine Mode | |
| * Lockdown (mseccfg.MML) is set. | |
| */ | |
| typedef enum epmp_perm { | |
| /** | |
| * Unlocked with R=0, W=0 and X=0. | |
| * | |
| * Note: full access (R=1, W=1 and X=1) in Machine Mode. Only use | |
| * in disabled (OFF) entries. | |
| */ | |
| kEpmpPermUnlocked, | |
| /** | |
| * Locked with R=0, W=0 and X=0. | |
| */ | |
| kEpmpPermLockedNoAccess, | |
| /** | |
| * Locked with R=0, W=0 and X=1. | |
| */ | |
| kEpmpPermLockedExecuteOnly, | |
| /** | |
| * Locked with R=1, W=0 and X=0. | |
| */ | |
| kEpmpPermLockedReadOnly, | |
| /** | |
| * Locked with R=1, W=0 and X=1. | |
| */ | |
| kEpmpPermLockedReadExecute, | |
| /** | |
| * Locked with R=1, W=1 and X=0. | |
| */ | |
| kEpmpPermLockedReadWrite, | |
| } epmp_perm_t; | |
| /** | |
| * EPMP constants. | |
| */ | |
| enum { | |
| kEpmpNumRegions = 16, | |
| }; | |
| /** | |
| * EPMP region specification. | |
| * | |
| * Provides the start and end addresses of a particular region. These addresses | |
| * are byte-aligned (i.e. they are like regular pointers rather than encoded | |
| * addresses). | |
| * | |
| * The `start` address is inclusive and the `end` address is exclusive. | |
| */ | |
| typedef struct epmp_region { | |
| uintptr_t start; | |
| uintptr_t end; | |
| } epmp_region_t; | |
| /** | |
| * EPMP entry index. | |
| */ | |
| typedef enum epmp_entry { | |
| kEpmpEntry0, | |
| kEpmpEntry1, | |
| kEpmpEntry2, | |
| kEpmpEntry3, | |
| kEpmpEntry4, | |
| kEpmpEntry5, | |
| kEpmpEntry6, | |
| kEpmpEntry7, | |
| kEpmpEntry8, | |
| kEpmpEntry9, | |
| kEpmpEntry10, | |
| kEpmpEntry11, | |
| kEpmpEntry12, | |
| kEpmpEntry13, | |
| kEpmpEntry14, | |
| kEpmpEntry15, | |
| } epmp_entry_t; | |
| /** | |
| * EPMP entry count. | |
| */ | |
| typedef enum epmp_entry_count { | |
| kEpmpEntryCount0, | |
| kEpmpEntryCount1, | |
| kEpmpEntryCount2, | |
| kEpmpEntryCount3, | |
| kEpmpEntryCount4, | |
| kEpmpEntryCount5, | |
| kEpmpEntryCount6, | |
| kEpmpEntryCount7, | |
| kEpmpEntryCount8, | |
| kEpmpEntryCount9, | |
| kEpmpEntryCount10, | |
| kEpmpEntryCount11, | |
| kEpmpEntryCount12, | |
| kEpmpEntryCount13, | |
| kEpmpEntryCount14, | |
| kEpmpEntryCount15, | |
| kEpmpEntryCount16, | |
| } epmp_entry_count_t; | |
| /** | |
| * EPMP generic status codes. | |
| * | |
| * These error codes can be used by any function. However if a function | |
| * requires additional status codes, it must define a set of status codes to | |
| * be used exclusively by such function. | |
| */ | |
| typedef enum epmp_result { | |
| kEpmpOk = 0, | |
| kEpmpError, | |
| kEpmpBadArg, | |
| } epmp_result_t; | |
| /** | |
| * Initialize the EPMP library. | |
| * | |
| * The current state of the EPMP CSRs will be read and stored inside the | |
| * library. This function must be called before using the library. The | |
| * library can only be initialized once. | |
| * | |
| * Note: it may be desirable to check the state is as expected. This | |
| * can be done using `epmp_get_state`. | |
| */ | |
| epmp_result_t epmp_init(void); | |
| /** | |
| * Check that the actual EPMP state matches what the library expects | |
| * it to be. | |
| */ | |
| epmp_result_t epmp_check(void); | |
| /** | |
| * Start an EPMP transaction. | |
| * | |
| * A new transaction will be initialized to expect the given number of | |
| * entries to be reconfigured. Only one transaction may be in progress | |
| * at a time. Each entry may only be reconfigured once per transaction. | |
| * Call `epmp_transaction_end` to finalize the transaction and update | |
| * the EPMP control registers. | |
| * | |
| * @param count Number of entries to be configured. | |
| * @returns `epmp_result_t` | |
| */ | |
| epmp_result_t epmp_transaction_start(epmp_entry_count_t count); | |
| /** | |
| * Finish an EPMP transaction and update EPMP control registers. | |
| * | |
| * Check that the expected number of regions have been reconfigured and | |
| * then update the EPMP control registers. Once this call returns the | |
| * transaction will be complete and a new transaction should be | |
| * started if further updates are required. | |
| * | |
| * This call will also perform checks equivalent to an `epmp_check` | |
| * call before modifying any registers. | |
| * | |
| * Updates will occur in the following sequence: | |
| * | |
| * 1. pmpaddr0-pmpaddr15 | |
| * 2. pmpcfg0-pmpcfg3 | |
| * | |
| * @param count Number of entries that should have been configured. | |
| * @returns `epmp_result_t` | |
| */ | |
| epmp_result_t epmp_transaction_end(epmp_entry_count_t count); | |
| /** | |
| * EPMP configuration status codes. | |
| */ | |
| typedef enum epmp_entry_configure_result { | |
| kEpmpEntryConfigureOk = kEpmpOk, | |
| kEpmpEntryConfigureError = kEpmpError, | |
| kEpmpEntryConfigureBadArg = kEpmpBadArg, | |
| /** | |
| * Invalid addresses provided for the selected address mode. | |
| */ | |
| kEpmpEntryConfigureBadRegion, | |
| /** | |
| * The transaction is in a bad state. This can be caused by: | |
| * - Missing call the `epmp_transaction_start`. | |
| * - An attempt to configure more entries than was initially specified. | |
| * - A prior configuration attempt encountered an error. | |
| */ | |
| kEpmpEntryConfigureBadTransaction, | |
| /** | |
| * The requested entry is out of range or has previously been configured | |
| * in this transaction. Each entry may only be configured once per | |
| * transaction. | |
| */ | |
| kEpmpEntryConfigureBadEntry, | |
| /** | |
| * Encoding the entry would interfere with a different pre-existing entry. | |
| * | |
| * New entries will be rejected if they: | |
| * - Modify the start or end address of an adjacent TOR entry. | |
| * - Would result in an address being used in both a NAPOT/NA4 entry and a | |
| * TOR entry. | |
| */ | |
| kEpmpEntryConfigureConflict, | |
| } epmp_entry_configure_result_t; | |
| /** | |
| * Disable address matching for a PMP entry. | |
| * | |
| * The `region` start address will be encoded and configured as the address | |
| * register for `entry`. The length of `region` must be 0 (i.e. the `region` end | |
| * address must match the start address). If the following entry is configured | |
| * using the TOR address mode then the `region` start address must match the | |
| * pre-existing address. | |
| * | |
| * Note: addresses are encoded by dividing them by four. This matches the | |
| * address encoding used by the TOR address mode. | |
| * | |
| * IMPORTANT: the `pmpaddr` and `pmpcfg` control registers will not be | |
| * updated until `epmp_transaction_end` is called. | |
| * | |
| * Example: | |
| * | |
| * ... | |
| * res0 = epmp_transaction_start(kEpmpEntryCount2); | |
| * res1 = epmp_entry_configure_off(kEpmpEntry0, | |
| * (epmp_region_t){0}, | |
| * kEpmpPermUnlocked); | |
| * res2 = epmp_entry_configure_off(kEpmpEntry1, | |
| * (epmp_region_t){ .start = 0x10, .end = 0x10 }, | |
| * kEpmpPermLockedNoAccess); | |
| * res3 = epmp_transaction_end(kEpmpEntryCount2); | |
| * ... | |
| * | |
| * Result: | |
| * | |
| * Entry | Value of `pmpaddr` | Value of `pmpcfg` | | |
| * ======+====================+===================+ | |
| * 0 | 0x00 (0x00 >> 2) | 0b0000000 | | |
| * 1 | 0x04 (0x10 >> 2) | 0b1000000 | | |
| * | |
| * @param entry Entry index to update (0 <= `entry` < `kEpmpNumRegions`) | |
| * @param region Region to encode into `pmpaddr`. | |
| * @param permissions Updated permissions to write to pmpcfg for `entry`. | |
| * @return `epmp_entry_configure_result_t`. | |
| */ | |
| epmp_entry_configure_result_t epmp_entry_configure_off(epmp_entry_t entry, | |
| epmp_region_t region, | |
| epmp_perm_t permissions); | |
| /** | |
| * Configures a PMP entry using the Top Of Range (TOR) address mode. | |
| * | |
| * The `region` end address will be encoded and configured as the address | |
| * register associated with `entry`. | |
| * | |
| * The `region` start address will be encoded and configured as the address | |
| * register associated with the preceding entry if that entry is disabled (i.e. | |
| * configured as OFF). If the preceding entry is configured using TOR mode then | |
| * its pre-configured end address must match the `region` start address. This | |
| * behavior allows adjacent regions configured using TOR to share an address, | |
| * removing the need for a disabled entry between them. If configuring entry 0 | |
| * then the `region` start address must be 0. All other configurations will be | |
| * rejected. | |
| * | |
| * IMPORTANT: the `pmpaddr` and `pmpcfg` control registers will not be | |
| * updated until `epmp_transaction_end` is called. | |
| * | |
| * Example (two adjacent TOR regions + one standalone TOR region): | |
| * | |
| * ... | |
| * res0 = epmp_transaction_start(kEpmpEntryCount4); | |
| * res1 = epmp_entry_configure_tor(kEpmpEntry0, | |
| * (epmp_region_t){ .start = 0x00, .end = 0x10 }, | |
| * kEpmpPermLockedReadOnly); | |
| * res2 = epmp_entry_configure_tor(kEpmpEntry1, | |
| * (epmp_region_t){ .start = 0x10, .end = 0x20 }, | |
| * kEpmpPermLockedReadOnly); | |
| * res3 = epmp_entry_configure_off(kEpmpEntry2, | |
| * (epmp_region_t){ .start = 0x00, .end = 0x00 }, | |
| * kEpmpPermLockedNoAccess); | |
| * res4 = epmp_entry_configure_tor(kEpmpEntry3, | |
| * (epmp_region_t){ .start = 0x30, .end = 0x40 }, | |
| * kEpmpPermLockedReadOnly); | |
| * res5 = epmp_transaction_end(kEpmpEntryCount4); | |
| * ... | |
| * | |
| * Result: | |
| * | |
| * Entry | Value of `pmpaddr` | Value of `pmpcfg` | | |
| * ======+====================+===================+ | |
| * 0 | 0x04 (0x10 >> 2) | 0b1001001 | | |
| * 1 | 0x08 (0x20 >> 2) | 0b1001001 | | |
| * 2 | 0x0c (0x30 >> 2) | 0b1000000 | | |
| * 3 | 0x10 (0x40 >> 2) | 0b1001001 | | |
| * | |
| * @param entry Entry index to update (0 <= `entry` < `kEpmpNumRegions`) | |
| * @param region Region start and end addresses. Start address must be 0 | |
| * for `entry` 0. | |
| * @param permissions Updated permissions to write to pmpcfg for `entry`. | |
| * @return `epmp_entry_configure_result_t`. | |
| */ | |
| epmp_entry_configure_result_t epmp_entry_configure_tor(epmp_entry_t entry, | |
| epmp_region_t region, | |
| epmp_perm_t permissions); | |
| /** | |
| * Configures a PMP entry using the Naturally Aligned 4-byte (NA4) address mode. | |
| * | |
| * The `region` start address will be encoded and configured as the address in | |
| * `entry`. The length of `region` must be exactly four bytes. | |
| * | |
| * This function will return `kEpmpEntryConfigureBadRegion` if | |
| * the PMP granularity is greater than 0. | |
| * | |
| * IMPORTANT: the `pmpaddr` and `pmpcfg` control registers will not be | |
| * updated until `epmp_transaction_end` is called. | |
| * | |
| * Example: | |
| * | |
| * ... | |
| * res0 = epmp_transaction_start(kEpmpEntryCount1); | |
| * res1 = epmp_entry_configure_na4(kEpmpEntry0, | |
| * (epmp_region_t){ .start = 0x10, .end = 0x14 }, | |
| * kEpmpPermLockedReadOnly); | |
| * res2 = epmp_transaction_end(kEpmpEntryCount1); | |
| * ... | |
| * | |
| * Result: | |
| * | |
| * Entry | Value of `pmpaddr` | Value of `pmpcfg` | | |
| * ======+====================+===================+ | |
| * 0 | 0x04 (0x10 >> 2) | 0b1010001 | | |
| * | |
| * @param entry Entry index to update (0 <= `entry` < `kEpmpNumRegions`) | |
| * @param region Region start and end addresses. Must be 4 byte aligned. | |
| * @param permissions Updated permissions to write to pmpcfg for `entry`. | |
| * @return `epmp_entry_configure_result_t`. | |
| */ | |
| epmp_entry_configure_result_t epmp_entry_configure_na4(epmp_entry_t entry, | |
| epmp_region_t region, | |
| epmp_perm_t permissions); | |
| /** | |
| * Configures a PMP entry using the Naturally Aligned Power-Of-Two (NAPOT) | |
| * address mode. | |
| * | |
| * The `region` will be encoded and configured as the address for `entry`. | |
| * The length of `region` must be a power of two greater than four and the | |
| * `region` (both start and end addresses) must also be aligned to the same | |
| * power of two. | |
| * | |
| * If the PMP granularity (G) is greater than zero then the entire `region` | |
| * must also be aligned to `2 ** (2 + G)`. | |
| * | |
| * IMPORTANT: the `pmpaddr` and `pmpcfg` control registers will not be | |
| * updated until `epmp_transaction_end` is called. | |
| * | |
| * Example: | |
| * | |
| * ... | |
| * res0 = epmp_transaction_start(kEpmpEntryCount2); | |
| * res1 = epmp_entry_configure_napot(kEpmpEntry0, | |
| * (epmp_region_t){ .start = 0x10, .end = 0x20 }, | |
| * kEpmpPermLockedReadOnly); | |
| * res2 = epmp_entry_configure_napot(kEpmpEntry1, | |
| * (epmp_region_t){ .start = 0x50, .end = 0x58 }, | |
| * kEpmpPermLockedReadWrite); | |
| * res3 = epmp_transaction_end(kEpmpEntryCount2); | |
| * ... | |
| * | |
| * Result: | |
| * | |
| * Entry | Value of `pmpaddr` | Value of `pmpcfg` | | |
| * ======+===========================+===================+ | |
| * 0 | 0x41 ((0x10 >> 2) | 0b01) | 0b1011001 | | |
| * 1 | 0x14 ((0x50 >> 2) | 0b00) | 0b1011011 | | |
| * | |
| * @param entry Entry index to update (0 <= `entry` < `kEpmpNumRegions`) | |
| * @param region Region start and end addresses. | |
| * @param permissions Updated permissions to write to pmpcfg for `entry`. | |
| * @return `epmp_entry_configure_result_t`. | |
| */ | |
| epmp_entry_configure_result_t epmp_entry_configure_napot( | |
| epmp_entry_t entry, epmp_region_t region, epmp_perm_t permissions); | |
| /** | |
| * Disable the Rule Locking Bypass (RLB) feature. | |
| * | |
| * When enabled (mseccfg.RLB = 1) the Rule Locking Bypass features allows | |
| * locked entries to be modified. If any PMP entries are locked and RLB | |
| * is disabled (mseccfg.RLB = 0) then it is no longer possible to enable | |
| * RLB. RLB will disabled or an error will be returned. | |
| * | |
| * @return `epmp_result_t` | |
| */ | |
| epmp_result_t epmp_disable_rule_locking_bypass(void); | |
| /** | |
| * A copy of EPMP control register state for debugging purposes. | |
| */ | |
| typedef struct epmp_debug_state { | |
| /** | |
| * PMP configuration values (pmp0cfg - pmp15cfg). | |
| * | |
| * These configuration values are stored in registers pmpcfg0 - pmpcfg3. | |
| * | |
| * Each 8-bit configuration value is encoded as follows: | |
| * | |
| * Layout: | |
| * | |
| * +---+-------+-------+---+---+---+ | |
| * | L | 0 | A | X | W | R | | |
| * +---+-------+-------+---+---+---+ | |
| * 8 7 6 5 4 3 2 1 0 | |
| * | |
| * Key: | |
| * | |
| * L = Locked | |
| * A = Address-matching Mode (OFF=0, TOR=1, NA4=2, NAPOT=3) | |
| * X = Executable | |
| * W = Writeable | |
| * R = Readable | |
| * | |
| * Note: the interpretation of these configuration bits depends on | |
| * whether Machine Mode Lockdown (mseccfg.MML) is enabled or not. | |
| * See the PMP Enhancements specification for more details. | |
| */ | |
| uint8_t pmpcfg[kEpmpNumRegions]; | |
| /** | |
| * PMP address registers (pmpaddr0 - pmpaddr15). | |
| * | |
| * The way that address register values are interpreted differs | |
| * depending on the address-matching mode (A) in the relevant pmpcfg | |
| * register(s). | |
| */ | |
| uintptr_t pmpaddr[kEpmpNumRegions]; | |
| /** | |
| * Machine Security Configuration register (mseccfg). | |
| * | |
| * +---...---+------+------+------+ | |
| * | 0 | RLB | MMWP | MML | | |
| * +---...---+------+------+------+ | |
| * 63 3 2 1 0 | |
| * | |
| * Key: | |
| * | |
| * RLB = Rule Locking Bypass | |
| * MMWP = Machine Mode Whitelist Policy | |
| * MML = Machine Mode Lockdown | |
| * | |
| * See the PMP Enhancements specification for more details. | |
| */ | |
| uint64_t mseccfg; | |
| } epmp_debug_state_t; | |
| /** | |
| * Get the current PMP configuration. | |
| * | |
| * Read all the PMP CSRs and return their values. | |
| * | |
| * @param[out] state Destination to write register values to. | |
| * @return `epmp_result_t` | |
| */ | |
| epmp_result_t epmp_debug_get_state(epmp_debug_state_t *state); | |
| /** | |
| * Read the current staged register state. | |
| * | |
| * The staged state consists of values that have been configured as | |
| * part of an ongoing transaction. It therefore only makes sense to call | |
| * this function after `epmp_transaction_start` and before | |
| * `epmp_transaction_end`. | |
| * | |
| * @param[out] state Destination to write register values to. | |
| * @return `epmp_result_t` | |
| */ | |
| epmp_result_t epmp_debug_get_transaction_state(epmp_debug_state_t *state); | |
| #endif // OPENTITAN_SW_DEVICE_LIB_RUNTIME_EPMP_H_ |