Skip to content

Commit 61fc494

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 e0f1bcb commit 61fc494

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

arch/riscv/core/pmp.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
#include <zephyr/arch/riscv/csr.h>
3434
#include <zephyr/mem_mgmt/mem_attr.h>
3535

36+
#include <errno.h>
37+
#include <stdint.h>
38+
3639
#define LOG_LEVEL CONFIG_MPU_LOG_LEVEL
3740
#include <zephyr/logging/log.h>
3841
LOG_MODULE_REGISTER(mpu);
@@ -57,6 +60,8 @@ LOG_MODULE_REGISTER(mpu);
5760

5861
#define PMP_NONE 0
5962

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

488586
/**

arch/riscv/include/pmp.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,30 @@ 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+
* @param region_idx The index of the configured memory attribute region.
35+
*
36+
* @return 0 on success.
37+
* -ENOENT if the PMP entry for CUSTOM_PMP_ENTRY_START is not found.
38+
* -ENOTSUP if CONFIG_PMP_SLOTS > 8.
39+
* -EINVAL for unexpected internal errors.
40+
*/
41+
int riscv_pmp_set_write_permission(bool write_enable, size_t region_idx);
42+
43+
#endif
44+
1945
#endif /* PMP_H_ */

0 commit comments

Comments
 (0)