Skip to content

Conversation

@fsammoura1980
Copy link
Contributor

@fsammoura1980 fsammoura1980 commented Sep 18, 2025

This series significantly upgrades the RISC-V Physical Memory Protection (PMP) implementation by introducing dynamic configuration and Device Tree integration.

Key changes:

  • DT Integration: PMP entries can now be configured early and flexibly using the zephyr,memattr Device Tree property. The PMP domain resync logic is updated to respect these DT-defined regions.
  • Dynamic Mode: Introduce CONFIG_PMP_KERNEL_MODE_DYNAMIC to allow dynamic configuration and activation of Machine mode PMP entries for better context switching support.
  • Refactoring: Rename internal PMP functions (e.g., stackguard to kernelmode) to reflect their general kernel configuration role.
  • Modularity: Extract PMP region address decoding logic into a new helper function, improving code readability and enabling direct unit testing of address calculation modes (TOR, NA4, NAPOT).
  • Testing: Add a new unit test suite to validate the DT memory attribute configuration and PMP state.

@fkokosinski
Copy link
Member

Hi, thanks for the PR!

I see that we're limiting the user to just one custom protected memory region. Is this due to limitations imposed by Kconfig? Could we use devicetree for defining those regions instead? That would allow the user to specify more than one protected memory region.

@fsammoura1980
Copy link
Contributor Author

Hi, thanks for the PR!

I see that we're limiting the user to just one custom protected memory region. Is this due to limitations imposed by Kconfig? Could we use devicetree for defining those regions instead? That would allow the user to specify more than one protected memory region.

I am just defining only a new one because this is something that our application requires. users of the entry can get the config values from the device tree. Do you have any specific suggestion of how to generalize extracting things from a device tree early in the abstraction phase?

@fkokosinski
Copy link
Member

Do you have any specific suggestion of how to generalize extracting things from a device tree early in the abstraction phase?

We could start using zephyr,memory-attr for declaring PMP regions. IIRC the Zephyr ARM port uses them for MPU regions.

@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 2 times, most recently from 61fc494 to 7219bdd Compare October 6, 2025 17:28
@fsammoura1980
Copy link
Contributor Author

Do you have any specific suggestion of how to generalize extracting things from a device tree early in the abstraction phase?

We could start using zephyr,memory-attr for declaring PMP regions. IIRC the Zephyr ARM port uses them for MPU regions.

ok, I implemented it using zephyr,memattr approach.

@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 2 times, most recently from 4e71970 to 7bf83a8 Compare October 7, 2025 16:43
@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 4 times, most recently from 7567ba0 to 278fa15 Compare October 22, 2025 20:25
Comment on lines +30 to +58
/* Read the current PMP configuration from the control registers */
z_riscv_pmp_read_config(current_pmpcfg_regs, num_pmpcfg_regs);
z_riscv_pmp_read_addr(current_pmpaddr_regs, num_pmpaddr_regs);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IF_DISABLED(CONFIG_ZTEST, (static inline)) void z_riscv_pmp_read_config in pmp.c is odd to me.

I think we can read PMP CSR directly in test case.

#define PMPADDR_READ(x) current_pmpaddr_regs[x] = csr_read(pmpaddr##x)
	FOR_EACH(PMPADDR_READ, (;), 0, 1, 2, 3, 4, 5, 6, 7);

#if CONFIG_PMP_SLOTS > 8
	FOR_EACH(PMPADDR_READ, (;), 8, 9, 10, 11, 12, 13, 14, 15);
#endif
#undef PMPADDR_READ

#ifdef CONFIG_64BIT
	/* RV64: pmpcfg0 holds entries 0-7; pmpcfg2 holds entries 8-15. */
	current_pmpcfg_regs[0] = csr_read(pmpcfg0);
#if CONFIG_PMP_SLOTS > 8
	current_pmpcfg_regs[1] = csr_read(pmpcfg2);
#endif
#else
	/* RV32: Each pmpcfg register holds 4 entries. */
	current_pmpcfg_regs[0] = csr_read(pmpcfg0);
	current_pmpcfg_regs[1] = csr_read(pmpcfg1);
#if CONFIG_PMP_SLOTS > 8
	current_pmpcfg_regs[2] = csr_read(pmpcfg2);
	current_pmpcfg_regs[3] = csr_read(pmpcfg3);
#endif
#endif

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we usually remove the static for testing purposes in or codebase. I am open to suggestions. It is better than duplicating the code.

Copy link
Contributor

@jimmyzhe jimmyzhe Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, although this is my first time to see this usage, I am ok with this.

Could we move the declaration to arch/riscv/include/pmp.h and guard it with CONFIG_ZTEST? So that test cases can simply #include <kernel_internal.h> without declaring these again. This would be useful for other test cases that may need these PMP debug API in the future.

arch/riscv/include/pmp.h:

#ifdef CONFIG_ZTEST
void z_riscv_pmp_read_config(...);
void z_riscv_pmp_read_addr(...);
void pmp_decode_region(...);
#endif /* CONFIG_ZTEST */

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 2 times, most recently from 007c437 to 63bc418 Compare October 23, 2025 21:24
Copy link

@npitre npitre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added functionality is sound but the way this PR is organized needs
improvements.

You have only 3 commits but you are making at least 4 independent changes
deserving 5 independent commits, the second commit carries changes that
belong to the first one, stack guard for the init code without
CONFIG_MEM_ATTR is now broken, code is added to resync_pmp_domain()
without explanation, etc.

Suggestion:

Commit 1: Perform the function rename, and only the rename, but all the
rename, all comments included.

Commit 2: Introduce a CONFIG_RISCV_PMP_KERNEL_MODE_DYNAMIC symbol (or any
better name). Have it be selected by the stack guard config option. Then
propagate it in the code. The custom PMP functionality could select it too
later which would be cleaner than adding ... defined(CONFIG_MEM_ATTR)
everywhere.

Commit 3: Introduce set_pmp_mem_attr() functionality. Document all tricky
parts.

Commit 4: Add the test. It would be a good idea for the test not to presume
any specific PMP slot encoding as those may vary. In other words, we want to
make sure the test regions from DT do match the PMP after those PMP entries
are decoded. There is code in print_pmp_entries() to decode PMP ranges and
permissions, so commit #4 could factor that out and commit #5 could be the
actual test.

@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 6 times, most recently from f59b598 to b629bd6 Compare October 24, 2025 19:44
@fsammoura1980
Copy link
Contributor Author

The added functionality is sound but the way this PR is organized needs improvements.

You have only 3 commits but you are making at least 4 independent changes deserving 5 independent commits, the second commit carries changes that belong to the first one, stack guard for the init code without CONFIG_MEM_ATTR is now broken, code is added to resync_pmp_domain() without explanation, etc.

Suggestion:

Commit 1: Perform the function rename, and only the rename, but all the rename, all comments included.

Commit 2: Introduce a CONFIG_RISCV_PMP_KERNEL_MODE_DYNAMIC symbol (or any better name). Have it be selected by the stack guard config option. Then propagate it in the code. The custom PMP functionality could select it too later which would be cleaner than adding ... defined(CONFIG_MEM_ATTR) everywhere.

Commit 3: Introduce set_pmp_mem_attr() functionality. Document all tricky parts.

Commit 4: Add the test. It would be a good idea for the test not to presume any specific PMP slot encoding as those may vary. In other words, we want to make sure the test regions from DT do match the PMP after those PMP entries are decoded. There is code in print_pmp_entries() to decode PMP ranges and permissions, so commit #4 could factor that out and commit #5 could be the actual test.

I believe I responded to all of your comments

@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 4 times, most recently from 715ce91 to 31b6b88 Compare October 25, 2025 03:26
Rename the `z_riscv_pmp_stackguard_*` functions to
`z_riscv_pmp_kernelmode_*`. This change better reflects that
these functions are used for general kernel mode PMP configuration,
not strictly limited to stack guard purposes.

Call sites in fatal.c, isr.S, and switch.S have been updated accordingly.

Signed-off-by: Firas Sammoura <[email protected]>
Introduce `CONFIG_PMP_KERNEL_MODE_DYNAMIC` to enable dynamic
configuration and activation of Machine mode PMP entries. This allows
PMP settings to be managed efficiently during transitions between
kernel and thread contexts.

Signed-off-by: Firas Sammoura <[email protected]>
…utes

The Physical Memory Protection (PMP) initialization is updated to support
custom entries defined in the Device Tree (DT) using the `zephyr,memattr`
property, contingent on `CONFIG_MEM_ATTR` being enabled. A new function,
`set_pmp_mem_attr()`, iterates over DT-defined regions and programs PMP
entries in `z_riscv_pmp_init()`, allowing for early, flexible, and
hardware-specific R/W/X protection for critical memory areas. DT-based
entries are also installed in `z_riscv_pmp_kernelmode_prepare()` for
thread-specific configuration. The logic for the temporary PMP "catch-all"
entry is adjusted to account for new DT entries. Furthermore, the PMP
domain resync logic now masks user partition permissions against DT-defined
region permissions, preventing privilege escalation. `CONFIG_RISCV_PMP` is
updated to select `PMP_KERNEL_MODE_DYNAMIC` if `MEM_ATTR`. Finally, the
`pmp_cfg` array in `z_riscv_pmp_init()` is initialized to zero to prevent
writing uninitialized stack data to unused PMP entries.

Signed-off-by: Firas Sammoura <[email protected]>
The logic to decode PMP addressing modes (**TOR**, **NA4**, **NAPOT**) into
physical start and end addresses was previously embedded in
`print_pmp_entries()`.

Extract this calculation into a new static helper function,
`pmp_decode_region()`, to significantly improve the readability and
modularity of the PMP debug printing code.

The new helper function is fully self-contained and exposes a defined API
for the PMP address decoding logic. This enables **direct reuse** in
**unit tests** (e.g., using **Ztest**) to verify the core address
calculation accuracy for all PMP modes and boundary conditions, independent
of the main PMP initialization or logging path.

Signed-off-by: Firas Sammoura <[email protected]>
@fsammoura1980 fsammoura1980 force-pushed the custom_pmp branch 3 times, most recently from 8aa24d3 to 945ee0b Compare October 25, 2025 05:06
…state

This commit implements a new unit test suite to validate the
integration of Device Tree memory attributes (`zephyr,memory-attr`)
with the RISC-V Physical Memory Protection (PMP) hardware.

The test suite includes:
1. **`test_pmp_devicetree_memattr_config`**: Verifies that the PMP
   Control and Status Registers (CSRs) are programmed correctly based
   on the memory regions defined with `zephyr,memory-attr` in the
   Device Tree. It iterates through the active PMP entries and
   asserts a match against the expected DT-defined regions.
2. **`test_riscv_mprv_mpp_config`**: Checks the initial state of the
   Machine Privilege Register Virtualization (MPRV) bit and Machine
   Previous Privilege (MPP) field in the `mstatus` CSR to ensure PMP
   is configured for correct privilege level switching during boot.
3. **`test_dt_pmp_perm_conversion`**: Validates the
   `DT_MEM_RISCV_TO_PMP_PERM` macro to ensure the conversion from
   Device Tree memory attribute flags to RISC-V PMP permission bits
   (R/W/X) is correct.

Signed-off-by: Firas Sammoura <[email protected]>
@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Architectures area: RISCV RISCV Architecture (32-bit & 64-bit) area: Tests Issues related to a particular existing or missing test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants