Skip to content

Commit 00006f0

Browse files
committed
tests: riscv: Implement unit tests for PMP memattr configuration and 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]>
1 parent 60adb49 commit 00006f0

File tree

7 files changed

+194
-7
lines changed

7 files changed

+194
-7
lines changed

arch/riscv/core/pmp.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,9 @@ LOG_MODULE_REGISTER(mpu);
6868
* @param start Pointer to where the calculated start address should be stored.
6969
* @param end Pointer to where the calculated end address should be stored.
7070
*/
71-
static void pmp_decode_region(uint8_t cfg_byte,
72-
unsigned long *pmp_addr,
73-
unsigned int index,
74-
unsigned long *start,
75-
unsigned long *end)
71+
IF_DISABLED(CONFIG_ZTEST, (static))
72+
void pmp_decode_region(uint8_t cfg_byte, unsigned long *pmp_addr, unsigned int index,
73+
unsigned long *start, unsigned long *end)
7674
{
7775
unsigned long tmp;
7876
unsigned long pmp_addr_val = pmp_addr[index];
@@ -139,7 +137,8 @@ static void print_pmp_entries(unsigned int pmp_start, unsigned int pmp_end,
139137
* @param pmp_cfg Pointer to the array where the CSR contents will be stored.
140138
* @param pmp_cfg_size The size of the pmp_cfg array, measured in unsigned long entries.
141139
*/
142-
static inline void z_riscv_pmp_read_config(unsigned long *pmp_cfg, size_t pmp_cfg_size)
140+
IF_DISABLED(CONFIG_ZTEST, (static inline))
141+
void z_riscv_pmp_read_config(unsigned long *pmp_cfg, size_t pmp_cfg_size)
143142
{
144143
__ASSERT(pmp_cfg_size == (size_t)(CONFIG_PMP_SLOTS / PMPCFG_STRIDE),
145144
"Invalid PMP config array size");
@@ -205,7 +204,8 @@ static inline void z_riscv_pmp_write_config(unsigned long *pmp_cfg, size_t pmp_c
205204
* @param pmp_addr Pointer to the array where the CSR contents will be stored.
206205
* @param pmp_addr_size The size of the pmp_addr array, measured in unsigned long entries.
207206
*/
208-
static inline void z_riscv_pmp_read_addr(unsigned long *pmp_addr, size_t pmp_addr_size)
207+
IF_DISABLED(CONFIG_ZTEST, (static inline))
208+
void z_riscv_pmp_read_addr(unsigned long *pmp_addr, size_t pmp_addr_size)
209209
{
210210
__ASSERT(pmp_addr_size == (size_t)(CONFIG_PMP_SLOTS), "PMP address array size mismatch");
211211

arch/riscv/include/pmp.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,11 @@ void z_riscv_pmp_usermode_init(struct k_thread *thread);
2424
void z_riscv_pmp_usermode_prepare(struct k_thread *thread);
2525
void z_riscv_pmp_usermode_enable(struct k_thread *thread);
2626

27+
#ifdef CONFIG_ZTEST
28+
void z_riscv_pmp_read_config(unsigned long *pmp_cfg, size_t pmp_cfg_size);
29+
void z_riscv_pmp_read_addr(unsigned long *pmp_addr, size_t pmp_addr_size);
30+
void pmp_decode_region(uint8_t cfg_byte, unsigned long *pmp_addr, unsigned int index,
31+
unsigned long *start, unsigned long *end);
32+
#endif /* CONFIG_ZTEST */
33+
2734
#endif /* PMP_H_ */
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(riscv_pmp)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})
9+
10+
target_include_directories(app PRIVATE
11+
${ZEPHYR_BASE}/kernel/include
12+
${ZEPHYR_BASE}/arch/${ARCH}/include
13+
)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2025 Google LLC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/dt-bindings/memory-attr/memory-attr-riscv.h>
8+
9+
/ {
10+
memattr_region1: memattr_region1@80000000 {
11+
compatible = "zephyr,memory-region";
12+
reg = <0x80000000 0x20000>;
13+
zephyr,memory-region = "MEMATTR_REGION1";
14+
zephyr,memory-attr = <(DT_MEM_RISCV_TYPE_IO_R |
15+
DT_MEM_RISCV_TYPE_IO_W |
16+
DT_MEM_RISCV_TYPE_IO_X)>;
17+
};
18+
19+
memattr_region2: memattr_region2@80000000 {
20+
compatible = "zephyr,memory-region";
21+
reg = <0x80020000 0x30000>;
22+
zephyr,memory-region = "MEMATTR_REGION2";
23+
zephyr,memory-attr = <(DT_MEM_RISCV_TYPE_IO_R |
24+
DT_MEM_RISCV_TYPE_IO_W |
25+
DT_MEM_RISCV_TYPE_IO_X)>;
26+
};
27+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_ZTEST=y
2+
CONFIG_MEM_ATTR=y
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (c) 2025 Google LLC
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <kernel_internal.h>
8+
#include <zephyr/mem_mgmt/mem_attr.h>
9+
#include <zephyr/tc_util.h>
10+
#include <zephyr/ztest.h>
11+
12+
/* Checks if the Machine Privilege Register Virtualization (MPRV) bit in mstatus is 1 (enabled). */
13+
static bool riscv_mprv_is_enabled(void)
14+
{
15+
return csr_read(mstatus) & MSTATUS_MPRV;
16+
}
17+
18+
/* Checks if the Machine Previous Privilege (MPP) field in mstatus is set to M-Mode (0b11). */
19+
static bool riscv_mpp_is_m_mode(void)
20+
{
21+
return (csr_read(mstatus) & MSTATUS_MPP) == MSTATUS_MPP;
22+
}
23+
24+
/* Helper structure to define the expected PMP regions derived from the Device Tree. */
25+
struct expected_region {
26+
uintptr_t base;
27+
size_t size;
28+
uint8_t perm;
29+
bool found;
30+
};
31+
32+
/*
33+
* Extract base address, size, and permission for the memory regions
34+
* defined in the Device Tree under the 'memattr' nodes.
35+
*/
36+
static struct expected_region dt_regions[] = {
37+
{.base = DT_REG_ADDR(DT_NODELABEL(memattr_region1)),
38+
.size = DT_REG_SIZE(DT_NODELABEL(memattr_region1)),
39+
.perm = DT_MEM_RISCV_TO_PMP_PERM(
40+
DT_PROP(DT_NODELABEL(memattr_region1), zephyr_memory_attr)),
41+
.found = false},
42+
{.base = DT_REG_ADDR(DT_NODELABEL(memattr_region2)),
43+
.size = DT_REG_SIZE(DT_NODELABEL(memattr_region2)),
44+
.perm = DT_MEM_RISCV_TO_PMP_PERM(
45+
DT_PROP(DT_NODELABEL(memattr_region2), zephyr_memory_attr)),
46+
.found = false}};
47+
48+
ZTEST(riscv_pmp_memattr_entries, test_pmp_devicetree_memattr_config)
49+
{
50+
const size_t num_pmpcfg_regs = CONFIG_PMP_SLOTS / sizeof(unsigned long);
51+
const size_t num_pmpaddr_regs = CONFIG_PMP_SLOTS;
52+
53+
unsigned long current_pmpcfg_regs[num_pmpcfg_regs];
54+
unsigned long current_pmpaddr_regs[num_pmpaddr_regs];
55+
56+
/* Read the current PMP configuration from the control registers */
57+
z_riscv_pmp_read_config(current_pmpcfg_regs, num_pmpcfg_regs);
58+
z_riscv_pmp_read_addr(current_pmpaddr_regs, num_pmpaddr_regs);
59+
60+
const uint8_t *const current_pmp_cfg_entries = (const uint8_t *)current_pmpcfg_regs;
61+
62+
for (unsigned int index = 0; index < CONFIG_PMP_SLOTS; ++index) {
63+
unsigned long start, end;
64+
uint8_t cfg_byte = current_pmp_cfg_entries[index];
65+
66+
/* Decode the configured PMP region (start and end addresses) */
67+
pmp_decode_region(cfg_byte, current_pmpaddr_regs, index, &start, &end);
68+
69+
/* Compare the decoded region against the list of expected DT regions */
70+
for (size_t i = 0; i < ARRAY_SIZE(dt_regions); ++i) {
71+
if ((start == dt_regions[i].base) &&
72+
(end == dt_regions[i].base + dt_regions[i].size - 1) &&
73+
((cfg_byte & 0x07) == dt_regions[i].perm)) {
74+
75+
dt_regions[i].found = true;
76+
break;
77+
}
78+
}
79+
}
80+
81+
for (size_t i = 0; i < ARRAY_SIZE(dt_regions); i++) {
82+
zassert_true(dt_regions[i].found,
83+
"PMP entry for DT region %zu (base 0x%lx, size 0x%zx, perm 0x%x) not "
84+
"found.",
85+
i + 1, dt_regions[i].base, dt_regions[i].size, dt_regions[i].perm);
86+
}
87+
}
88+
89+
ZTEST(riscv_pmp_memattr_entries, test_riscv_mprv_mpp_config)
90+
{
91+
zassert_true(riscv_mprv_is_enabled(),
92+
"MPRV should be enabled (1) to use the privilege specified by the MPP field.");
93+
94+
zassert_false(riscv_mpp_is_m_mode(),
95+
"MPP should be set to 0x00 (U-Mode) before execution.");
96+
}
97+
98+
ZTEST(riscv_pmp_memattr_entries, test_dt_pmp_perm_conversion)
99+
{
100+
uint8_t result;
101+
102+
result = DT_MEM_RISCV_TO_PMP_PERM(0);
103+
zassert_equal(result, 0, "Expected 0, got 0x%x", result);
104+
105+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_EMPTY);
106+
zassert_equal(result, 0, "Expected 0, got 0x%x", result);
107+
108+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R);
109+
zassert_equal(result, PMP_R, "Expected PMP_R (0x%x), got 0x%x", PMP_R, result);
110+
111+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_W);
112+
zassert_equal(result, PMP_W, "Expected PMP_W (0x%x), got 0x%x", PMP_W, result);
113+
114+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_X);
115+
zassert_equal(result, PMP_X, "Expected PMP_X (0x%x), got 0x%x", PMP_X, result);
116+
117+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R | DT_MEM_RISCV_TYPE_IO_W);
118+
zassert_equal(result, PMP_R | PMP_W, "Expected R|W (0x%x), got 0x%x", PMP_R | PMP_W,
119+
result);
120+
121+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R | DT_MEM_RISCV_TYPE_IO_W |
122+
DT_MEM_RISCV_TYPE_IO_X);
123+
zassert_equal(result, PMP_R | PMP_W | PMP_X, "Expected R|W|X (0x%x), got 0x%x",
124+
PMP_R | PMP_W | PMP_X, result);
125+
}
126+
127+
ZTEST_SUITE(riscv_pmp_memattr_entries, NULL, NULL, NULL, NULL, NULL);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
common:
2+
platform_allow:
3+
- qemu_riscv32
4+
- qemu_riscv32e
5+
- qemu_riscv64
6+
filter: CONFIG_RISCV_PMP
7+
8+
tests:
9+
arch.riscv.pmp.memattr.entries:
10+
extra_args:
11+
DTC_OVERLAY_FILE="memattr_mapping.overlay"

0 commit comments

Comments
 (0)