Skip to content

Commit f59b598

Browse files
committed
tests: riscv: Add uint test for PMP memattr configuration
Introduce a new test suite to validate the RISC-V Physical Memory Protection (PMP) settings configured via the Zephyr Device Tree 'zephyr,memory-attr' property. The test case `test_pmp_devicetree_memattr_config` reads the actual PMP configuration (pmpcfg) and address (pmpaddr) Control and Status Registers (CSRs) using the helper functions `z_riscv_pmp_read_config` and `z_riscv_pmp_read_addr`. It then compares the first three PMP entries against a set of hardcoded expected values. These expected values are derived from the memory regions defined with `zephyr,memory-attr` in the Device Tree for the specific board or test setup. This ensures that the PMP hardware is programmed correctly based on the Device Tree definitions. Signed-off-by: Firas Sammoura <[email protected]>
1 parent fb80e9f commit f59b598

File tree

6 files changed

+185
-3
lines changed

6 files changed

+185
-3
lines changed

arch/riscv/core/pmp.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ LOG_MODULE_REGISTER(mpu);
6464
* @param start Pointer to where the calculated start address should be stored.
6565
* @param end Pointer to where the calculated end address should be stored.
6666
*/
67-
static void pmp_decode_region(uint8_t cfg_byte,
67+
IF_DISABLED(CONFIG_ZTEST, (static)) void pmp_decode_region(uint8_t cfg_byte,
6868
unsigned long *pmp_addr,
6969
unsigned int index,
7070
unsigned long *start,
@@ -135,7 +135,8 @@ static void print_pmp_entries(unsigned int pmp_start, unsigned int pmp_end,
135135
* @param pmp_cfg Pointer to the array where the CSR contents will be stored.
136136
* @param pmp_cfg_size The size of the pmp_cfg array, measured in unsigned long entries.
137137
*/
138-
static inline void z_riscv_pmp_read_config(unsigned long *pmp_cfg, size_t pmp_cfg_size)
138+
IF_DISABLED(CONFIG_ZTEST, (static inline)) void z_riscv_pmp_read_config(unsigned long *pmp_cfg,
139+
size_t pmp_cfg_size)
139140
{
140141
__ASSERT(pmp_cfg_size == (size_t)(CONFIG_PMP_SLOTS / PMPCFG_STRIDE),
141142
"Invalid PMP config array size");
@@ -201,7 +202,8 @@ static inline void z_riscv_pmp_write_config(unsigned long *pmp_cfg, size_t pmp_c
201202
* @param pmp_addr Pointer to the array where the CSR contents will be stored.
202203
* @param pmp_addr_size The size of the pmp_addr array, measured in unsigned long entries.
203204
*/
204-
static inline void z_riscv_pmp_read_addr(unsigned long *pmp_addr, size_t pmp_addr_size)
205+
IF_DISABLED(CONFIG_ZTEST, (static inline)) void z_riscv_pmp_read_addr(unsigned long *pmp_addr,
206+
size_t pmp_addr_size)
205207
{
206208
__ASSERT(pmp_addr_size == (size_t)(CONFIG_PMP_SLOTS), "PMP address array size mismatch");
207209

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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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 | DT_MEM_RISCV_TYPE_IO_X)>;
16+
};
17+
18+
memattr_region2: memattr_region2@80000000 {
19+
compatible = "zephyr,memory-region";
20+
reg = <0x80020000 0x30000>;
21+
zephyr,memory-region = "MEMATTR_REGION2";
22+
zephyr,memory-attr = <(DT_MEM_RISCV_TYPE_IO_R | \
23+
DT_MEM_RISCV_TYPE_IO_W | DT_MEM_RISCV_TYPE_IO_X)>;
24+
};
25+
};
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: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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+
void z_riscv_pmp_read_config(unsigned long *pmp_cfg, size_t pmp_cfg_size);
13+
void z_riscv_pmp_read_addr(unsigned long *pmp_addr, size_t pmp_addr_size);
14+
void pmp_decode_region(uint8_t cfg_byte, unsigned long *pmp_addr, unsigned int index,
15+
unsigned long *start, unsigned long *end);
16+
17+
/* Checks if the Machine Privilege Register Virtualization (MPRV) bit in mstatus is 1 (enabled). */
18+
static bool riscv_mprv_is_enabled(void)
19+
{
20+
return csr_read(mstatus) & MSTATUS_MPRV;
21+
}
22+
23+
/* Checks if the Machine Previous Privilege (MPP) field in mstatus is set to M-Mode (0b11). */
24+
static bool riscv_mpp_is_m_mode(void)
25+
{
26+
return (csr_read(mstatus) & MSTATUS_MPP) == MSTATUS_MPP;
27+
}
28+
29+
/* Helper structure to define the expected PMP regions derived from the Device Tree. */
30+
struct expected_region {
31+
uintptr_t base;
32+
size_t size;
33+
uint8_t perm;
34+
bool found;
35+
};
36+
37+
/*
38+
* Extract base address, size, and permission for the memory regions
39+
* defined in the Device Tree under the 'memattr' nodes.
40+
*/
41+
static struct expected_region dt_regions[] = {
42+
{.base = DT_REG_ADDR(DT_NODELABEL(memattr_region1)),
43+
.size = DT_REG_SIZE(DT_NODELABEL(memattr_region1)),
44+
.perm = DT_MEM_RISCV_TO_PMP_PERM(
45+
DT_PROP(DT_NODELABEL(memattr_region1), zephyr_memory_attr)),
46+
.found = false},
47+
{.base = DT_REG_ADDR(DT_NODELABEL(memattr_region2)),
48+
.size = DT_REG_SIZE(DT_NODELABEL(memattr_region2)),
49+
.perm = DT_MEM_RISCV_TO_PMP_PERM(
50+
DT_PROP(DT_NODELABEL(memattr_region2), zephyr_memory_attr)),
51+
.found = false}};
52+
53+
ZTEST(riscv_pmp_memattr_entries, test_pmp_devicetree_memattr_config)
54+
{
55+
const size_t num_pmpcfg_regs = CONFIG_PMP_SLOTS / sizeof(unsigned long);
56+
const size_t num_pmpaddr_regs = CONFIG_PMP_SLOTS;
57+
58+
unsigned long current_pmpcfg_regs[num_pmpcfg_regs];
59+
unsigned long current_pmpaddr_regs[num_pmpaddr_regs];
60+
61+
/* Read the current PMP configuration from the control registers */
62+
z_riscv_pmp_read_config(current_pmpcfg_regs, num_pmpcfg_regs);
63+
z_riscv_pmp_read_addr(current_pmpaddr_regs, num_pmpaddr_regs);
64+
65+
const uint8_t *const current_pmp_cfg_entries = (const uint8_t *)current_pmpcfg_regs;
66+
67+
for (unsigned int index = 0; index < CONFIG_PMP_SLOTS; ++index) {
68+
unsigned long start, end;
69+
uint8_t cfg_byte = current_pmp_cfg_entries[index];
70+
71+
/* Decode the configured PMP region (start and end addresses) */
72+
pmp_decode_region(cfg_byte, current_pmpaddr_regs, index, &start, &end);
73+
74+
/* Compare the decoded region against the list of expected DT regions */
75+
for (size_t i = 0; i < ARRAY_SIZE(dt_regions); ++i) {
76+
if ((start == dt_regions[i].base) &&
77+
(end == dt_regions[i].base + dt_regions[i].size - 1) &&
78+
((cfg_byte & 0x07) == dt_regions[i].perm)) {
79+
80+
dt_regions[i].found = true;
81+
break;
82+
}
83+
}
84+
}
85+
86+
for (size_t i = 0; i < ARRAY_SIZE(dt_regions); i++) {
87+
zassert_true(dt_regions[i].found,
88+
"PMP entry for DT region %zu (base 0x%lx, size 0x%zx, perm 0x%x) not "
89+
"found.",
90+
i + 1, dt_regions[i].base, dt_regions[i].size, dt_regions[i].perm);
91+
}
92+
}
93+
94+
ZTEST(riscv_pmp_memattr_entries, test_riscv_mprv_mpp_config)
95+
{
96+
zassert_true(riscv_mprv_is_enabled(),
97+
"MPRV should be enabled (1) to use the privilege specified by the MPP field.");
98+
99+
zassert_false(riscv_mpp_is_m_mode(),
100+
"MPP should be set to 0x00 (U-Mode) before execution.");
101+
}
102+
103+
ZTEST(riscv_pmp_memattr_entries, test_dt_pmp_perm_conversion)
104+
{
105+
uint8_t result;
106+
107+
result = DT_MEM_RISCV_TO_PMP_PERM(0);
108+
zassert_equal(result, 0, "Expected 0, got 0x%x", result);
109+
110+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R);
111+
zassert_equal(result, PMP_R, "Expected PMP_R (0x%x), got 0x%x", PMP_R, result);
112+
113+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_W);
114+
zassert_equal(result, PMP_W, "Expected PMP_W (0x%x), got 0x%x", PMP_W, result);
115+
116+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_X);
117+
zassert_equal(result, PMP_X, "Expected PMP_X (0x%x), got 0x%x", PMP_X, result);
118+
119+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R | DT_MEM_RISCV_TYPE_IO_W);
120+
zassert_equal(result, PMP_R | PMP_W, "Expected R|W (0x%x), got 0x%x", PMP_R | PMP_W,
121+
result);
122+
123+
result = DT_MEM_RISCV_TO_PMP_PERM(DT_MEM_RISCV_TYPE_IO_R | DT_MEM_RISCV_TYPE_IO_W |
124+
DT_MEM_RISCV_TYPE_IO_X);
125+
zassert_equal(result, PMP_R | PMP_W | PMP_X, "Expected R|W|X (0x%x), got 0x%x",
126+
PMP_R | PMP_W | PMP_X, result);
127+
}
128+
129+
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)