Skip to content

Commit edfc116

Browse files
committed
arch/riscv: Add API to control write permission for custom PMP entry
Implement the `riscv_pmp_set_write_permission()` API, which allows the 'W' bit in the PMP configuration register (pmpcfg) to be dynamically enabled or disabled for a memory region defined by CUSTOM_PMP_ENTRY_START and CUSTOM_PMP_ENTRY_SIZE. This functionality is crucial for security platforms to protect critical flash areas, such as rollback data, against accidental or malicious writes. Also adds the Kconfig symbol `PLATFORM_EC_ROLLBACK_PMP_PROTECT` which depends on `RISCV_PMP` and ensures `CUSTOM_PMP_ENTRY` is selected when rollback protection is desired. The current implementation is limited to PMP slots 0-7. Signed-off-by: Firas Sammoura <[email protected]>
1 parent 04419c9 commit edfc116

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

arch/riscv/core/pmp.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
#include <zephyr/arch/arch_interface.h>
3333
#include <zephyr/arch/riscv/csr.h>
3434

35+
#include <errno.h>
36+
#include <stdint.h>
37+
3538
#define LOG_LEVEL CONFIG_MPU_LOG_LEVEL
3639
#include <zephyr/logging/log.h>
3740
LOG_MODULE_REGISTER(mpu);
@@ -56,6 +59,8 @@ LOG_MODULE_REGISTER(mpu);
5659

5760
#define PMP_NONE 0
5861

62+
#define PMP_CFG_W_BIT 1 /* Write permission bit in the PMP config byte */
63+
5964
static void print_pmp_entries(unsigned int pmp_start, unsigned int pmp_end,
6065
unsigned long *pmp_addr, unsigned long *pmp_cfg,
6166
const char *banner)
@@ -476,6 +481,93 @@ void z_riscv_custom_pmp_entry_enable(void)
476481
csr_clear(mstatus, MSTATUS_MPRV | MSTATUS_MPP);
477482
csr_set(mstatus, MSTATUS_MPRV);
478483
}
484+
485+
int riscv_pmp_set_write_permission(bool write_enable)
486+
{
487+
if (CONFIG_PMP_SLOTS > 8) {
488+
LOG_ERR("This function only supports up to 8 PMP slots.");
489+
return -ENOTSUP;
490+
}
491+
492+
int entry_index = -1;
493+
uintptr_t target_pmpaddr_start = PMP_ADDR(CONFIG_CUSTOM_PMP_ENTRY_START);
494+
uintptr_t target_pmpaddr_end =
495+
PMP_ADDR(CONFIG_CUSTOM_PMP_ENTRY_START + CONFIG_CUSTOM_PMP_ENTRY_SIZE);
496+
uintptr_t target_pmpaddr_napot =
497+
PMP_ADDR_NAPOT(CONFIG_CUSTOM_PMP_ENTRY_START, CONFIG_CUSTOM_PMP_ENTRY_SIZE);
498+
499+
for (uint8_t i = 0; i < CONFIG_PMP_SLOTS; ++i) {
500+
if ((PMPADDR_READ(i) == target_pmpaddr_start) &&
501+
(PMPADDR_READ(i + 1) == target_pmpaddr_end)) {
502+
entry_index = i + 1;
503+
break;
504+
}
505+
if (PMPADDR_READ(i) == target_pmpaddr_napot) {
506+
entry_index = i;
507+
break;
508+
}
509+
}
510+
511+
if (entry_index == -1) {
512+
LOG_ERR("PMP entry for address 0x%x not found", CONFIG_CUSTOM_PMP_ENTRY_START);
513+
return -ENOENT;
514+
}
515+
516+
uint8_t cfg_reg_idx = entry_index / PMPCFG_STRIDE;
517+
uint8_t entry_in_reg = entry_index % PMPCFG_STRIDE;
518+
int bit_position = (entry_in_reg * 8) + PMP_CFG_W_BIT;
519+
unsigned long mask = 1UL << bit_position;
520+
521+
unsigned long pmpcfg_val;
522+
523+
#if defined(CONFIG_64BIT)
524+
/*
525+
* RV64: pmpcfg0 holds configurations for entries 0-7.
526+
* On RV64, the PMP configuration registers are even-numbered:
527+
* pmpcfg0, pmpcfg2, pmpcfg4, and so on.
528+
*/
529+
if (cfg_reg_idx == 0) { /* Entries 0-7 are in pmpcfg0 */
530+
pmpcfg_val = csr_read(pmpcfg0);
531+
if (write_enable) {
532+
pmpcfg_val |= mask;
533+
} else {
534+
pmpcfg_val &= ~mask;
535+
}
536+
csr_write(pmpcfg0, pmpcfg_val);
537+
} else {
538+
LOG_ERR("cfg_reg_idx %d unexpected for <= 8 slots on RV64", cfg_reg_idx);
539+
return -EINVAL;
540+
}
541+
#else
542+
/*
543+
* RV32: pmpcfg0 holds configurations for entries 0-3, pmpcfg1 holds entries 4-7.
544+
* On RV32, all pmpcfg registers are valid: pmpcfg0, pmpcfg1, pmpcfg2, and so on.
545+
*/
546+
if (cfg_reg_idx == 0) { /* Entries 0-3 */
547+
pmpcfg_val = csr_read(pmpcfg0);
548+
if (write_enable) {
549+
pmpcfg_val |= mask;
550+
} else {
551+
pmpcfg_val &= ~mask;
552+
}
553+
csr_write(pmpcfg0, pmpcfg_val);
554+
} else if (cfg_reg_idx == 1) { /* Entries 4-7 */
555+
pmpcfg_val = csr_read(pmpcfg1);
556+
if (write_enable) {
557+
pmpcfg_val |= mask;
558+
} else {
559+
pmpcfg_val &= ~mask;
560+
}
561+
csr_write(pmpcfg1, pmpcfg_val);
562+
} else {
563+
LOG_ERR("cfg_reg_idx %d unexpected for <= 8 slots on RV32", cfg_reg_idx);
564+
return -EINVAL;
565+
}
566+
#endif
567+
568+
return 0;
569+
}
570+
479571
#endif
480572

481573
/**

arch/riscv/include/pmp.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,29 @@ void z_riscv_pmp_usermode_prepare(struct k_thread *thread);
1616
void z_riscv_pmp_usermode_enable(struct k_thread *thread);
1717
void z_riscv_custom_pmp_entry_enable(void);
1818

19+
20+
#ifdef CONFIG_CUSTOM_PMP_ENTRY
21+
22+
/**
23+
* @brief Sets the write permission for a specific PMP entry.
24+
*
25+
* Searches for the PMP entry matching CUSTOM_PMP_ENTRY_START.
26+
* Modifies the Write (W) bit in this entry's PMP configuration.
27+
*
28+
* @note This function currently supports up to 8 PMP slots (CONFIG_PMP_SLOTS <=
29+
* 8). Extending beyond 8 slots in C requires more switch cases or an assembly
30+
* helper.
31+
*
32+
* @param write_enable If true, enables writes to the region (sets W bit).
33+
* If false, disables writes (clears W bit).
34+
*
35+
* @return 0 on success.
36+
* -ENOENT if the PMP entry for CUSTOM_PMP_ENTRY_START is not found.
37+
* -ENOTSUP if CONFIG_PMP_SLOTS > 8.
38+
* -EINVAL for unexpected internal errors.
39+
*/
40+
int riscv_pmp_set_write_permission(bool write_enable);
41+
42+
#endif
43+
1944
#endif /* PMP_H_ */

0 commit comments

Comments
 (0)