Skip to content

Commit 7219bdd

Browse files
committed
arch/riscv: Add API to control write permission for custom PMP entry
Introduce the `riscv_pmp_set_write_permission()` API to dynamically control the write access to memory regions protected by the RISC-V Physical Memory Protection (PMP) unit. This function enables or disables the Write (W) bit in the PMP configuration register (pmpcfg) for a specific region. The region is identified by an index (`region_idx`) referencing the list of memory regions defined in the Device Tree with the "zephyr,memattr" property. This API is particularly useful for security-sensitive applications, such as protecting critical flash sections like firmware rollback data from accidental or unauthorized writes, while allowing writes only during specific, controlled operations. The current implementation supports systems with up to 8 PMP slots (CONFIG_PMP_SLOTS <= 8) due to the method used to access pmpcfg registers. Signed-off-by: Firas Sammoura <[email protected]>
1 parent c85bafb commit 7219bdd

File tree

2 files changed

+139
-0
lines changed

2 files changed

+139
-0
lines changed

arch/riscv/core/pmp.c

Lines changed: 113 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,114 @@ 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+
if (region_idx >= num_regions) {
505+
LOG_ERR("region_idx %zu is out of bounds (num_regions: %zu)", region_idx,
506+
num_regions);
507+
return -EINVAL;
508+
}
509+
510+
uintptr_t region_start_address = region[region_idx].dt_addr;
511+
size_t region_size = region[region_idx].dt_size;
512+
513+
int entry_index = -1;
514+
uintptr_t target_pmpaddr_start = PMP_ADDR(region_start_address);
515+
uintptr_t target_pmpaddr_end = PMP_ADDR(region_start_address + region_size);
516+
uintptr_t target_pmpaddr_napot = PMP_ADDR_NAPOT(region_start_address, region_size);
517+
518+
for (uint8_t i = 0; i < CONFIG_PMP_SLOTS; ++i) {
519+
uintptr_t current_pmpaddr = PMPADDR_READ(i);
520+
521+
/* Check for NAPOT match */
522+
if (current_pmpaddr == target_pmpaddr_napot) {
523+
entry_index = i;
524+
break;
525+
}
526+
527+
/* Check for TOR match start */
528+
if (current_pmpaddr == target_pmpaddr_start) {
529+
if (i < (CONFIG_PMP_SLOTS - 1)) {
530+
if (PMPADDR_READ(i + 1) == target_pmpaddr_end) {
531+
entry_index =
532+
i + 1;
533+
break;
534+
}
535+
}
536+
}
537+
}
538+
539+
if (entry_index == -1) {
540+
LOG_ERR("PMP entry for address 0x%x not found", region_start_address);
541+
return -ENOENT;
542+
}
543+
544+
uint8_t cfg_reg_idx = entry_index / PMPCFG_STRIDE;
545+
uint8_t entry_in_reg = entry_index % PMPCFG_STRIDE;
546+
int bit_position = (entry_in_reg * 8) + PMP_CFG_W_BIT;
547+
unsigned long mask = 1UL << bit_position;
548+
549+
unsigned long pmpcfg_val;
550+
551+
#if defined(CONFIG_64BIT)
552+
/*
553+
* RV64: pmpcfg0 holds configurations for entries 0-7.
554+
* On RV64, the PMP configuration registers are even-numbered:
555+
* pmpcfg0, pmpcfg2, pmpcfg4, and so on.
556+
*/
557+
if (cfg_reg_idx == 0) { /* Entries 0-7 are in pmpcfg0 */
558+
pmpcfg_val = csr_read(pmpcfg0);
559+
if (write_enable) {
560+
pmpcfg_val |= mask;
561+
} else {
562+
pmpcfg_val &= ~mask;
563+
}
564+
csr_write(pmpcfg0, pmpcfg_val);
565+
} else {
566+
LOG_ERR("cfg_reg_idx %d unexpected for <= 8 slots on RV64", cfg_reg_idx);
567+
return -EINVAL;
568+
}
569+
#else
570+
/*
571+
* RV32: pmpcfg0 holds configurations for entries 0-3, pmpcfg1 holds entries 4-7.
572+
* On RV32, all pmpcfg registers are valid: pmpcfg0, pmpcfg1, pmpcfg2, and so on.
573+
*/
574+
if (cfg_reg_idx == 0) { /* Entries 0-3 */
575+
pmpcfg_val = csr_read(pmpcfg0);
576+
if (write_enable) {
577+
pmpcfg_val |= mask;
578+
} else {
579+
pmpcfg_val &= ~mask;
580+
}
581+
csr_write(pmpcfg0, pmpcfg_val);
582+
} else if (cfg_reg_idx == 1) { /* Entries 4-7 */
583+
pmpcfg_val = csr_read(pmpcfg1);
584+
if (write_enable) {
585+
pmpcfg_val |= mask;
586+
} else {
587+
pmpcfg_val &= ~mask;
588+
}
589+
csr_write(pmpcfg1, pmpcfg_val);
590+
} else {
591+
LOG_ERR("cfg_reg_idx %d unexpected for <= 8 slots on RV32", cfg_reg_idx);
592+
return -EINVAL;
593+
}
594+
#endif
595+
596+
return 0;
597+
}
598+
486599
#endif
487600

488601
/**

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 corresponding to the Device Tree memory
26+
* region at the given index. Modifies the Write (W) bit in this
27+
* entry's PMP configuration.
28+
*
29+
* @note This function currently supports up to 8 PMP slots (CONFIG_PMP_SLOTS <= 8).
30+
*
31+
* @param write_enable If true, enables writes to the region (sets W bit).
32+
* If false, disables writes (clears W bit).
33+
* @param region_idx The index of the region in the array returned
34+
* by mem_attr_get_regions(), for which to modify PMP settings.
35+
*
36+
* @return 0 on success.
37+
* -EINVAL if region_idx is out of bounds.
38+
* -ENOENT if the matching PMP entry is not found.
39+
* -ENOTSUP if CONFIG_PMP_SLOTS > 8.
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)