From c94324f6b7e20dd7c8230f59401dbdaa96c966d3 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Tue, 28 Oct 2025 10:18:52 +0800 Subject: [PATCH 01/18] Add PMP infrastructure and management structures Introduces RISC-V Physical Memory Protection (PMP) support for hardware-enforced memory isolation. TOR mode is adopted as the addressing scheme for its flexibility in supporting arbitrary address ranges without alignment requirements, simplifying region management for task stacks of varying sizes. Adds CSR definitions for PMP registers, permission encodings, and hardware constants. Provides structures for region configuration and state tracking, with priority-based management to handle the 16-region hardware limit. Includes error codes and functions for region configuration and access verification. --- arch/riscv/csr.h | 79 ++++++++++++++++++++++++++++++++++++ arch/riscv/hal.h | 5 +++ arch/riscv/pmp.h | 88 +++++++++++++++++++++++++++++++++++++++++ include/private/error.h | 8 ++++ 4 files changed, 180 insertions(+) create mode 100644 arch/riscv/pmp.h diff --git a/arch/riscv/csr.h b/arch/riscv/csr.h index 2f27ed8..e57869b 100644 --- a/arch/riscv/csr.h +++ b/arch/riscv/csr.h @@ -179,3 +179,82 @@ /* Machine Scratch Register - For temporary storage during traps */ #define CSR_MSCRATCH 0x340 + +/* PMP Address Registers (pmpaddr0-pmpaddr15) - 16 regions maximum + * In TOR (Top-of-Range) mode, these define the upper boundary of each region. + * The lower boundary is defined by the previous region's upper boundary. + */ +#define CSR_PMPADDR0 0x3b0 +#define CSR_PMPADDR1 0x3b1 +#define CSR_PMPADDR2 0x3b2 +#define CSR_PMPADDR3 0x3b3 +#define CSR_PMPADDR4 0x3b4 +#define CSR_PMPADDR5 0x3b5 +#define CSR_PMPADDR6 0x3b6 +#define CSR_PMPADDR7 0x3b7 +#define CSR_PMPADDR8 0x3b8 +#define CSR_PMPADDR9 0x3b9 +#define CSR_PMPADDR10 0x3ba +#define CSR_PMPADDR11 0x3bb +#define CSR_PMPADDR12 0x3bc +#define CSR_PMPADDR13 0x3bd +#define CSR_PMPADDR14 0x3be +#define CSR_PMPADDR15 0x3bf + +/* PMP Configuration Registers (pmpcfg0-pmpcfg3) + * Each configuration register controls 4 PMP regions (on RV32). + * pmpcfg0 controls pmpaddr0-3, pmpcfg1 controls pmpaddr4-7, etc. + */ +#define CSR_PMPCFG0 0x3a0 +#define CSR_PMPCFG1 0x3a1 +#define CSR_PMPCFG2 0x3a2 +#define CSR_PMPCFG3 0x3a3 + +/* PMP Configuration Field Bits (8 bits per region within pmpcfg) + * Layout in each byte of pmpcfg: + * Bit 7: L (Lock) - Locks this region until hardware reset + * Bits 6-5: Reserved + * Bits 4-3: A (Address Matching Mode) + * Bit 2: X (Execute permission) + * Bit 1: W (Write permission) + * Bit 0: R (Read permission) + */ + +/* Lock bit: Prevents further modification of this region */ +#define PMPCFG_L (1U << 7) + +/* Address Matching Mode (bits 3-4) + * Choose TOR mode for no alignment requirements on region sizes, and support + * for arbitrary address ranges. +*/ +#define PMPCFG_A_SHIFT 3 +#define PMPCFG_A_MASK (0x3U << PMPCFG_A_SHIFT) +#define PMPCFG_A_OFF (0x0U << PMPCFG_A_SHIFT) /* Null region (disabled) */ +#define PMPCFG_A_TOR (0x1U << PMPCFG_A_SHIFT) /* Top-of-Range mode */ + +/* Permission bits */ +#define PMPCFG_X (1U << 2) /* Execute permission */ +#define PMPCFG_W (1U << 1) /* Write permission */ +#define PMPCFG_R (1U << 0) /* Read permission */ + +/* Common permission combinations */ +#define PMPCFG_PERM_NONE (0x0U) /* No access */ +#define PMPCFG_PERM_R (PMPCFG_R) /* Read-only */ +#define PMPCFG_PERM_RW (PMPCFG_R | PMPCFG_W) /* Read-Write */ +#define PMPCFG_PERM_X (PMPCFG_X) /* Execute-only */ +#define PMPCFG_PERM_RX (PMPCFG_R | PMPCFG_X) /* Read-Execute */ +#define PMPCFG_PERM_RWX (PMPCFG_R | PMPCFG_W | PMPCFG_X) /* All access */ + +/* Utility macros for PMP configuration manipulation */ + +/* Extract PMP address matching mode */ +#define PMPCFG_GET_A(cfg) (((cfg) & PMPCFG_A_MASK) >> PMPCFG_A_SHIFT) + +/* Extract permission bits from configuration byte */ +#define PMPCFG_GET_PERM(cfg) ((cfg) & (PMPCFG_R | PMPCFG_W | PMPCFG_X)) + +/* Check if region is locked */ +#define PMPCFG_IS_LOCKED(cfg) (((cfg) & PMPCFG_L) != 0) + +/* Check if region is enabled (address mode is not OFF) */ +#define PMPCFG_IS_ENABLED(cfg) (PMPCFG_GET_A(cfg) != PMPCFG_A_OFF) diff --git a/arch/riscv/hal.h b/arch/riscv/hal.h index 8354264..d895347 100644 --- a/arch/riscv/hal.h +++ b/arch/riscv/hal.h @@ -110,3 +110,8 @@ void hal_cpu_idle(void); /* Default stack size for new tasks if not otherwise specified */ #define DEFAULT_STACK_SIZE 4096 + +/* Physical Memory Protection (PMP) region limit constants */ +#define PMP_MAX_REGIONS 16 /* RISC-V supports 16 PMP regions */ +#define PMP_TOR_PAIRS 8 /* In TOR mode, 16 regions = 8 pairs (uses 2 addrs each) */ +#define MIN_PMP_REGION_SIZE 4 /* Minimum addressable size in TOR mode (4 bytes) */ diff --git a/arch/riscv/pmp.h b/arch/riscv/pmp.h new file mode 100644 index 0000000..7373aff --- /dev/null +++ b/arch/riscv/pmp.h @@ -0,0 +1,88 @@ +/* RISC-V Physical Memory Protection (PMP) Hardware Layer + * + * Low-level interface to RISC-V PMP using TOR (Top-of-Range) mode for + * flexible region management without alignment constraints. + */ + +#pragma once + +#include + +/* PMP Region Priority Levels (lower value = higher priority) + * + * Used for eviction decisions when hardware PMP regions are exhausted. + */ +typedef enum { + PMP_PRIORITY_KERNEL = 0, + PMP_PRIORITY_STACK = 1, + PMP_PRIORITY_SHARED = 2, + PMP_PRIORITY_TEMPORARY = 3, + PMP_PRIORITY_COUNT = 4 +} pmp_priority_t; + +/* PMP Region Configuration */ +typedef struct { + uint32_t addr_start; /* Start address (inclusive) */ + uint32_t addr_end; /* End address (exclusive, written to pmpaddr) */ + uint8_t permissions; /* R/W/X bits (PMPCFG_R | PMPCFG_W | PMPCFG_X) */ + pmp_priority_t priority; /* Eviction priority */ + uint8_t region_id; /* Hardware region index (0-15) */ + uint8_t locked; /* Lock bit (cannot modify until reset) */ +} pmp_region_t; + +/* PMP Global State */ +typedef struct { + pmp_region_t regions[PMP_MAX_REGIONS]; /* Shadow of hardware config */ + uint8_t region_count; /* Active region count */ + uint8_t next_region_idx; /* Next free region index */ + uint32_t initialized; /* Initialization flag */ +} pmp_config_t; + +/* PMP Management Functions */ + +/* Initializes the PMP hardware and configuration state. + * @config : Pointer to pmp_config_t structure to be initialized. + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init(pmp_config_t *config); + +/* Configures a single PMP region in TOR mode. + * @config : Pointer to PMP configuration state + * @region : Pointer to pmp_region_t structure with desired configuration + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region); + +/* Reads the current configuration of a PMP region. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to read (0-15) + * @region : Pointer to pmp_region_t to store the result + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_get_region(const pmp_config_t *config, uint8_t region_idx, + pmp_region_t *region); + +/* Disables a PMP region. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to disable (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_disable_region(pmp_config_t *config, uint8_t region_idx); + +/* Locks a PMP region to prevent further modification. + * @config : Pointer to PMP configuration state + * @region_idx : Index of the region to lock (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx); + +/* Verifies that a memory access is allowed by the current PMP configuration. + * @config : Pointer to PMP configuration state + * @addr : Address to check + * @size : Size of the access in bytes + * @is_write : 1 for write access, 0 for read access + * @is_execute : 1 for execute access, 0 for data access + * Returns 1 if access is allowed, 0 if denied, or negative error code. + */ +int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, + uint32_t size, uint8_t is_write, uint8_t is_execute); diff --git a/include/private/error.h b/include/private/error.h index 27dcfb8..6172dd5 100644 --- a/include/private/error.h +++ b/include/private/error.h @@ -29,6 +29,14 @@ enum { ERR_STACK_CHECK, /* Stack overflow or corruption detected */ ERR_HEAP_CORRUPT, /* Heap corruption or invalid free detected */ + /* PMP Configuration Errors */ + ERR_PMP_INVALID_REGION, /* Invalid PMP region parameters */ + ERR_PMP_NO_REGIONS, /* No free PMP regions available */ + ERR_PMP_LOCKED, /* Region is locked by higher priority */ + ERR_PMP_SIZE_MISMATCH, /* Size doesn't meet alignment requirements */ + ERR_PMP_ADDR_RANGE, /* Address range is invalid */ + ERR_PMP_NOT_INIT, /* PMP not initialized */ + /* IPC and Synchronization Errors */ ERR_PIPE_ALLOC, /* Pipe allocation failed */ ERR_PIPE_DEALLOC, /* Pipe deallocation failed */ From 977625453c9c893101c233330dee14f8a42bfce4 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Tue, 28 Oct 2025 11:06:11 +0800 Subject: [PATCH 02/18] Add memory abstraction structures Introduces three abstractions that build upon the PMP infrastructure for managing memory protection at different granularities. Flexpages represent contiguous physical memory regions with protection attributes, providing arbitrary base addresses and sizes without alignment constraints. Memory spaces implement the address space concept but use distinct terminology to avoid confusion with virtual address spaces, as this structure represents a task's memory protection domain in a physical-address-only system. They organize flexpages into task memory views and support sharing across multiple tasks without requiring an MMU. Memory pools define static regions for boot-time initialization of kernel memory protection. Field naming retains 'as_' prefix (e.g., as_id, as_next) to reflect the underlying address space concept, while documentation uses "memory space" terminology for clarity in physical-memory-only contexts. Structures are used to enable runtime iteration, simplify debugging, and maintain consistency with other subsystems. Macro helpers reduce initialization boilerplate while maintaining type safety. --- include/sys/memprot.h | 76 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 include/sys/memprot.h diff --git a/include/sys/memprot.h b/include/sys/memprot.h new file mode 100644 index 0000000..1ef6f34 --- /dev/null +++ b/include/sys/memprot.h @@ -0,0 +1,76 @@ +/* Memory Protection Abstractions + * + * Software abstractions for managing memory protection at different + * granularities. These structures build upon hardware protection + * mechanisms (such as RISC-V PMP) to provide flexible, architecture- + * independent memory isolation. + */ + +#pragma once + +#include + +/* Forward declarations */ +struct fpage; +struct as; + +/* Flexpage + * + * Contiguous physical memory region with hardware-enforced protection. + * Supports arbitrary base addresses and sizes without alignment constraints. + */ +typedef struct fpage { + struct fpage *as_next; /* Next in address space list */ + struct fpage *map_next; /* Next in mapping chain */ + struct fpage *pmp_next; /* Next in PMP queue */ + uint32_t base; /* Physical base address */ + uint32_t size; /* Region size */ + uint32_t rwx; /* R/W/X permission bits */ + uint32_t pmp_id; /* PMP region index */ + uint32_t flags; /* Status flags */ + uint32_t priority; /* Eviction priority */ + int used; /* Usage counter */ +} fpage_t; + +/* Memory Space + * + * Collection of flexpages forming a task's memory view. Can be shared + * across multiple tasks. + */ +typedef struct memspace { + uint32_t as_id; /* Memory space identifier */ + struct fpage *first; /* Head of flexpage list */ + struct fpage *pmp_first; /* Head of PMP-loaded list */ + struct fpage *pmp_stack; /* Stack regions */ + uint32_t shared; /* Shared flag */ +} memspace_t; + +/* Memory Pool + * + * Static memory region descriptor for boot-time PMP initialization. + */ +typedef struct { + const char *name; /* Pool name */ + uintptr_t start; /* Start address */ + uintptr_t end; /* End address */ + uint32_t flags; /* Access permissions */ + uint32_t tag; /* Pool type/priority */ +} mempool_t; + +/* Memory Pool Declaration Helpers + * + * Simplifies memory pool initialization with designated initializers. + * DECLARE_MEMPOOL_FROM_SYMBOLS uses token concatenation to construct + * linker symbol names automatically. + */ +#define DECLARE_MEMPOOL(name_, start_, end_, flags_, tag_) \ + { \ + .name = (name_), \ + .start = (uintptr_t)(start_), \ + .end = (uintptr_t)(end_), \ + .flags = (flags_), \ + .tag = (tag_), \ + } + +#define DECLARE_MEMPOOL_FROM_SYMBOLS(name_, sym_base_, flags_, tag_) \ + DECLARE_MEMPOOL((name_), &(sym_base_##_start), &(sym_base_##_end), (flags_), (tag_)) From a7ec1423400c96ba66ab08431be4077a0200fc4b Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Tue, 28 Oct 2025 11:11:33 +0800 Subject: [PATCH 03/18] Declare memory pools from linker symbols Defines static memory pools for boot-time PMP initialization using linker symbols to identify kernel memory regions. Linker symbol declarations are updated to include text segment boundaries and match actual linker script definitions for stack regions. Five kernel memory pools protect text as read-execute, data and bss as read-write, heap and stack as read-write without execute to prevent code injection. Macro helpers reduce initialization boilerplate while maintaining debuggability through struct arrays. Priority-based management handles the 16-region hardware constraint. --- arch/riscv/hal.h | 7 +++-- arch/riscv/pmp.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++ arch/riscv/pmp.h | 20 +++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 arch/riscv/pmp.c diff --git a/arch/riscv/hal.h b/arch/riscv/hal.h index d895347..3c54cb1 100644 --- a/arch/riscv/hal.h +++ b/arch/riscv/hal.h @@ -3,13 +3,14 @@ #include /* Symbols from the linker script, defining memory boundaries */ -extern uint32_t _stack_start, _stack_end; /* Start/end of the STACK memory */ -extern uint32_t _heap_start, _heap_end; /* Start/end of the HEAP memory */ -extern uint32_t _heap_size; /* Size of HEAP memory */ +extern uint32_t _stext, _etext; /* Start/end of the .text section */ extern uint32_t _sidata; /* Start address for .data initialization */ extern uint32_t _sdata, _edata; /* Start/end address for .data section */ extern uint32_t _sbss, _ebss; /* Start/end address for .bss section */ extern uint32_t _end; /* End of kernel image */ +extern uint32_t _heap_start, _heap_end; /* Start/end of the HEAP memory */ +extern uint32_t _heap_size; /* Size of HEAP memory */ +extern uint32_t _stack_bottom, _stack_top; /* Bottom/top of the STACK memory */ /* Read a RISC-V Control and Status Register (CSR). * @reg : The symbolic name of the CSR (e.g., mstatus). diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c new file mode 100644 index 0000000..24ab8ec --- /dev/null +++ b/arch/riscv/pmp.c @@ -0,0 +1,73 @@ +/* RISC-V Physical Memory Protection (PMP) Implementation + * + * Provides hardware-enforced memory isolation using PMP in TOR mode. + */ + +#include +#include +#include "csr.h" +#include "pmp.h" + +/* Static Memory Pools for Boot-time PMP Initialization + * + * Defines kernel memory regions protected at boot. Each pool specifies + * a memory range and access permissions. + */ +static const mempool_t kernel_mempools[] = { + DECLARE_MEMPOOL("kernel_text", &_stext, &_etext, PMPCFG_PERM_RX, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_data", &_sdata, &_edata, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_bss", &_sbss, &_ebss, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_heap", &_heap_start, &_heap_end, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), + DECLARE_MEMPOOL("kernel_stack", &_stack_bottom, &_stack_top, PMPCFG_PERM_RW, + PMP_PRIORITY_KERNEL), +}; + +#define KERNEL_MEMPOOL_COUNT \ + (sizeof(kernel_mempools) / sizeof(kernel_mempools[0])) + +int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, + size_t count) +{ + if (!config || !pools || count == 0) + return ERR_PMP_INVALID_REGION; + + /* Initialize PMP hardware and state */ + int32_t ret = pmp_init(config); + if (ret < 0) + return ret; + + /* Configure each memory pool as a PMP region */ + for (size_t i = 0; i < count; i++) { + const mempool_t *pool = &pools[i]; + + /* Validate pool boundaries */ + if (pool->start >= pool->end) + return ERR_PMP_ADDR_RANGE; + + /* Prepare PMP region configuration */ + pmp_region_t region = { + .addr_start = pool->start, + .addr_end = pool->end, + .permissions = pool->flags & (PMPCFG_R | PMPCFG_W | PMPCFG_X), + .priority = pool->tag, + .region_id = i, + .locked = 0, + }; + + /* Configure the PMP region */ + ret = pmp_set_region(config, ®ion); + if (ret < 0) + return ret; + } + + return ERR_OK; +} + +int32_t pmp_init_kernel(pmp_config_t *config) +{ + return pmp_init_pools(config, kernel_mempools, KERNEL_MEMPOOL_COUNT); +} diff --git a/arch/riscv/pmp.h b/arch/riscv/pmp.h index 7373aff..0b45e6b 100644 --- a/arch/riscv/pmp.h +++ b/arch/riscv/pmp.h @@ -8,6 +8,9 @@ #include +/* Forward declaration */ +typedef struct mempool mempool_t; + /* PMP Region Priority Levels (lower value = higher priority) * * Used for eviction decisions when hardware PMP regions are exhausted. @@ -86,3 +89,20 @@ int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx); */ int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, uint32_t size, uint8_t is_write, uint8_t is_execute); + +/* Memory Pool Management Functions */ + +/* Initializes PMP regions from an array of memory pool descriptors. + * @config : Pointer to PMP configuration state + * @pools : Array of memory pool descriptors + * @count : Number of pools in the array + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, + size_t count); + +/* Initializes PMP with default kernel memory pools. + * @config : Pointer to PMP configuration state + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_init_kernel(pmp_config_t *config); From a5892f2f0ca21592193a52791da5e0472a1dedde Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Wed, 29 Oct 2025 14:31:41 +0800 Subject: [PATCH 04/18] Link tasks to memory spaces Extends TCB with a memory space pointer to enable per-task memory isolation. Each task can now reference its own memory protection domain through the flexpage mechanism. --- include/sys/task.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/sys/task.h b/include/sys/task.h index 33d0b60..99a8fa0 100644 --- a/include/sys/task.h +++ b/include/sys/task.h @@ -59,6 +59,9 @@ enum task_states { #define TASK_TIMESLICE_LOW 10 /* Low priority: longer slice */ #define TASK_TIMESLICE_IDLE 15 /* Idle tasks: longest slice */ +/* Forward declaration */ +struct memspace; + /* Task Control Block (TCB) * * Contains all essential information about a single task, including saved @@ -71,6 +74,8 @@ typedef struct tcb { size_t stack_sz; /* Total size of the stack in bytes */ void (*entry)(void); /* Task's entry point function */ + /* Memory Protection */ + struct memspace *mspace; /* Memory space for task isolation */ /* Scheduling Parameters */ uint16_t prio; /* Encoded priority (base and time slice counter) */ uint8_t prio_level; /* Priority level (0-7, 0 = highest) */ From b65fae4125a5acc9caeda7e4a98e0b87fa2ffe3b Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Fri, 31 Oct 2025 14:46:57 +0800 Subject: [PATCH 05/18] Implement flexpage lifecycle management Adds creation and destruction functions for flexpages, which are software abstractions representing contiguous physical memory regions with hardware-enforced protection attributes. These primitives will be used by higher-level memory space management to construct per-task memory views for PMP-based isolation. Function naming follows kernel conventions to reflect that these operations manage abstract memory protection objects rather than just memory allocation. --- Makefile | 2 +- include/sys/memprot.h | 17 +++++++++++++++++ kernel/memprot.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 kernel/memprot.c diff --git a/Makefile b/Makefile index 61576a1..86f1dcf 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ include arch/$(ARCH)/build.mk INC_DIRS += -I $(SRC_DIR)/include \ -I $(SRC_DIR)/include/lib -KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o error.o syscall.o task.o main.o +KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o error.o syscall.o task.o memprot.o main.o KERNEL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(KERNEL_OBJS)) deps += $(KERNEL_OBJS:%.o=%.o.d) diff --git a/include/sys/memprot.h b/include/sys/memprot.h index 1ef6f34..61c0b83 100644 --- a/include/sys/memprot.h +++ b/include/sys/memprot.h @@ -74,3 +74,20 @@ typedef struct { #define DECLARE_MEMPOOL_FROM_SYMBOLS(name_, sym_base_, flags_, tag_) \ DECLARE_MEMPOOL((name_), &(sym_base_##_start), &(sym_base_##_end), (flags_), (tag_)) + +/* Flexpage Management Functions */ + +/* Creates and initializes a new flexpage. + * @base : Physical base address + * @size : Size in bytes + * @rwx : Permission bits + * @priority : Eviction priority + * Returns pointer to created flexpage, or NULL on failure. + */ +fpage_t *mo_fpage_create(uint32_t base, uint32_t size, uint32_t rwx, + uint32_t priority); + +/* Destroys a flexpage. + * @fpage : Pointer to flexpage to destroy + */ +void mo_fpage_destroy(fpage_t *fpage); diff --git a/kernel/memprot.c b/kernel/memprot.c new file mode 100644 index 0000000..ff7e854 --- /dev/null +++ b/kernel/memprot.c @@ -0,0 +1,42 @@ +/* Memory Protection Management + * + * Provides allocation and management functions for flexpages, which are + * software abstractions representing contiguous physical memory regions with + * hardware-enforced protection attributes. + */ + +#include +#include +#include + +/* Creates and initializes a flexpage */ +fpage_t *mo_fpage_create(uint32_t base, uint32_t size, uint32_t rwx, + uint32_t priority) +{ + fpage_t *fpage = malloc(sizeof(fpage_t)); + if (!fpage) + return NULL; + + /* Initialize all fields */ + fpage->as_next = NULL; + fpage->map_next = NULL; + fpage->pmp_next = NULL; + fpage->base = base; + fpage->size = size; + fpage->rwx = rwx; + fpage->pmp_id = 0; /* Not loaded into PMP initially */ + fpage->flags = 0; /* No flags set initially */ + fpage->priority = priority; + fpage->used = 0; /* Not in use initially */ + + return fpage; +} + +/* Destroys a flexpage */ +void mo_fpage_destroy(fpage_t *fpage) +{ + if (!fpage) + return; + + free(fpage); +} From 025067afb93fee6ab67b19900e6846eefe6ca2a0 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Fri, 31 Oct 2025 14:48:43 +0800 Subject: [PATCH 06/18] Include memory protection in public API Makes flex page management functions available to user applications by adding the memory protection header to the public API collection. --- include/linmo.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/linmo.h b/include/linmo.h index 420e7d0..b67c589 100644 --- a/include/linmo.h +++ b/include/linmo.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include From d8dfb2d5af6c276bf13b6cdf90eba85e11895f25 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Fri, 31 Oct 2025 21:30:20 +0800 Subject: [PATCH 07/18] Initialize global PMP configuration state Adds centralized management of PMP hardware state through a global configuration instance. This enables the memory protection subsystem to track and coordinate PMP register usage across the kernel without requiring each component to maintain its own state. Provides controlled access to the configuration through a dedicated accessor function, maintaining encapsulation while supporting dynamic region allocation and eviction during task switching. --- arch/riscv/pmp.c | 8 ++++++++ arch/riscv/pmp.h | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 24ab8ec..9039e54 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -29,6 +29,14 @@ static const mempool_t kernel_mempools[] = { #define KERNEL_MEMPOOL_COUNT \ (sizeof(kernel_mempools) / sizeof(kernel_mempools[0])) +/* Global PMP configuration (shadow of hardware state) */ +static pmp_config_t pmp_global_config; + +pmp_config_t *pmp_get_config(void) +{ + return &pmp_global_config; +} + int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, size_t count) { diff --git a/arch/riscv/pmp.h b/arch/riscv/pmp.h index 0b45e6b..3e051f4 100644 --- a/arch/riscv/pmp.h +++ b/arch/riscv/pmp.h @@ -6,11 +6,9 @@ #pragma once +#include #include -/* Forward declaration */ -typedef struct mempool mempool_t; - /* PMP Region Priority Levels (lower value = higher priority) * * Used for eviction decisions when hardware PMP regions are exhausted. @@ -43,6 +41,9 @@ typedef struct { /* PMP Management Functions */ +/* Returns pointer to global PMP configuration */ +pmp_config_t *pmp_get_config(void); + /* Initializes the PMP hardware and configuration state. * @config : Pointer to pmp_config_t structure to be initialized. * Returns 0 on success, or negative error code on failure. From 6236b34ec5a6b9932c3d05dce7eb1ab72aa7bb4b Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Fri, 31 Oct 2025 21:55:47 +0800 Subject: [PATCH 08/18] Implement priority-based victim selection Add flexpage selection algorithm that identifies eviction candidates when hardware PMP regions are exhausted. The algorithm selects the flexpage with the highest priority value (lowest importance), while protecting kernel regions (priority 0) from eviction. --- kernel/memprot.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/kernel/memprot.c b/kernel/memprot.c index ff7e854..b214e14 100644 --- a/kernel/memprot.c +++ b/kernel/memprot.c @@ -7,6 +7,7 @@ #include #include +#include #include /* Creates and initializes a flexpage */ @@ -40,3 +41,28 @@ void mo_fpage_destroy(fpage_t *fpage) free(fpage); } + +/* Selects victim flexpage for eviction using priority-based algorithm. + * + * @mspace : Pointer to memory space + * Returns pointer to victim flexpage, or NULL if no evictable page found. + */ +fpage_t *select_victim_fpage(memspace_t *mspace) +{ + if (!mspace) + return NULL; + + fpage_t *victim = NULL; + uint32_t lowest_prio = 0; + + /* Select page with highest priority value (lowest priority). + * Kernel regions (priority 0) are never selected. */ + for (fpage_t *fp = mspace->pmp_first; fp; fp = fp->pmp_next) { + if (fp->priority > lowest_prio) { + victim = fp; + lowest_prio = fp->priority; + } + } + + return victim; +} From b88420b921bd284c0f4848716459e52c2b3ba82f Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Fri, 31 Oct 2025 22:36:47 +0800 Subject: [PATCH 09/18] Implement PMP region load and evict operations Add functions to dynamically load flexpages into hardware PMP regions and evict them when no longer needed. These operations bridge the software flexpage abstraction with hardware PMP configuration, enabling runtime memory protection management. --- kernel/memprot.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/kernel/memprot.c b/kernel/memprot.c index b214e14..8a810c4 100644 --- a/kernel/memprot.c +++ b/kernel/memprot.c @@ -66,3 +66,62 @@ fpage_t *select_victim_fpage(memspace_t *mspace) return victim; } + +/* Loads a flexpage into a PMP hardware region. + * + * @fpage : Pointer to flexpage to load + * @region_idx : Hardware PMP region index (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx) +{ + if (!fpage) + return -1; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + /* Configure PMP region from flexpage attributes */ + pmp_region_t region = { + .addr_start = fpage->base, + .addr_end = fpage->base + fpage->size, + .permissions = fpage->rwx, + .priority = fpage->priority, + .region_id = region_idx, + .locked = 0, + }; + + int32_t ret = pmp_set_region(config, ®ion); + if (ret == 0) { + fpage->pmp_id = region_idx; + } + + return ret; +} + +/* Evicts a flexpage from its PMP hardware region. + * + * @fpage : Pointer to flexpage to evict + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_evict_fpage(fpage_t *fpage) +{ + if (!fpage) + return -1; + + /* Only evict if actually loaded into PMP */ + if (fpage->pmp_id == 0) + return 0; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + int32_t ret = pmp_disable_region(config, fpage->pmp_id); + if (ret == 0) { + fpage->pmp_id = 0; + } + + return ret; +} From 8566636ea8a71452a436e2b2c442c45b9ab42de6 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 20:58:52 +0800 Subject: [PATCH 10/18] Implement memory space lifecycle management Add functions to create and destroy memory spaces, which serve as containers for flexpages. A memory space can be dedicated to a single task or shared across multiple tasks, supporting both isolated and shared memory models. --- include/sys/memprot.h | 14 ++++++++++++++ kernel/memprot.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/include/sys/memprot.h b/include/sys/memprot.h index 61c0b83..f09ea8b 100644 --- a/include/sys/memprot.h +++ b/include/sys/memprot.h @@ -91,3 +91,17 @@ fpage_t *mo_fpage_create(uint32_t base, uint32_t size, uint32_t rwx, * @fpage : Pointer to flexpage to destroy */ void mo_fpage_destroy(fpage_t *fpage); + +/* Memory Space Management Functions */ + +/* Creates and initializes a memory space. + * @as_id : Memory space identifier + * @shared : Whether this space can be shared across tasks + * Returns pointer to created memory space, or NULL on failure. + */ +memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared); + +/* Destroys a memory space and all its flexpages. + * @mspace : Pointer to memory space to destroy + */ +void mo_memspace_destroy(memspace_t *mspace); diff --git a/kernel/memprot.c b/kernel/memprot.c index 8a810c4..eab868d 100644 --- a/kernel/memprot.c +++ b/kernel/memprot.c @@ -125,3 +125,44 @@ int32_t pmp_evict_fpage(fpage_t *fpage) return ret; } + +/* Creates and initializes a memory space. + * + * @as_id : Memory space identifier + * @shared : Whether this space can be shared across tasks + * Returns pointer to created memory space, or NULL on failure. + */ +memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared) +{ + memspace_t *mspace = malloc(sizeof(memspace_t)); + if (!mspace) + return NULL; + + mspace->as_id = as_id; + mspace->first = NULL; + mspace->pmp_first = NULL; + mspace->pmp_stack = NULL; + mspace->shared = shared; + + return mspace; +} + +/* Destroys a memory space and all its flexpages. + * + * @mspace : Pointer to memory space to destroy + */ +void mo_memspace_destroy(memspace_t *mspace) +{ + if (!mspace) + return; + + /* Free all flexpages in the list */ + fpage_t *fp = mspace->first; + while (fp) { + fpage_t *next = fp->as_next; + mo_fpage_destroy(fp); + fp = next; + } + + free(mspace); +} From 7a992b6c4887179742d347b348dc5802194573cb Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 21:47:53 +0800 Subject: [PATCH 11/18] Add PMP CSR access infrastructure Provide helper functions for runtime-indexed access to PMP control and status registers alongside existing compile-time CSR macros. RISC-V CSR instructions encode register addresses as immediate values in the instruction itself, making dynamic selection impossible through simple arithmetic. These helpers use switch-case dispatch to map runtime indices to specific CSR instructions while preserving type safety. This enables PMP register management code to iterate over regions without knowing exact register numbers at compile-time, supporting features with multiple registers of the same type. PMP implementation is now included in the build system to make these helpers and future PMP functionality available at link time. --- arch/riscv/build.mk | 2 +- arch/riscv/hal.h | 19 +++++++++++ arch/riscv/pmp.c | 81 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/arch/riscv/build.mk b/arch/riscv/build.mk index 6efa81c..4e40f72 100644 --- a/arch/riscv/build.mk +++ b/arch/riscv/build.mk @@ -63,7 +63,7 @@ LDFLAGS += --gc-sections ARFLAGS = r LDSCRIPT = $(ARCH_DIR)/riscv32-qemu.ld -HAL_OBJS := boot.o hal.o muldiv.o +HAL_OBJS := boot.o hal.o muldiv.o pmp.o HAL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(HAL_OBJS)) deps += $(HAL_OBJS:%.o=%.o.d) diff --git a/arch/riscv/hal.h b/arch/riscv/hal.h index 3c54cb1..3b10c4f 100644 --- a/arch/riscv/hal.h +++ b/arch/riscv/hal.h @@ -28,6 +28,25 @@ extern uint32_t _stack_bottom, _stack_top; /* Bottom/top of the STACK memory */ */ #define write_csr(reg, val) ({ asm volatile("csrw " #reg ", %0" ::"rK"(val)); }) +/* Read CSR by numeric address (for dynamic register selection). + * Used when CSR number is not known at compile-time (e.g., PMP registers). + * @csr_num : CSR address as a compile-time constant. + */ +#define read_csr_num(csr_num) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrr %0, %1" : "=r"(__tmp) : "i"(csr_num)); \ + __tmp; \ + }) + +/* Write CSR by numeric address (for dynamic register selection). + * Used when CSR number is not known at compile-time (e.g., PMP registers). + * @csr_num : CSR address as a compile-time constant. + * @val : The 32-bit value to write. + */ +#define write_csr_num(csr_num, val) \ + ({ asm volatile("csrw %0, %1" :: "i"(csr_num), "rK"(val)); }) + /* Globally enable or disable machine-level interrupts by setting mstatus.MIE. * @enable : Non-zero to enable, zero to disable. * Returns the previous state of the interrupt enable bit (1 if enabled, 0 if diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 9039e54..90cffb0 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -7,6 +7,87 @@ #include #include "csr.h" #include "pmp.h" +#include "private/error.h" + +/* PMP CSR Access Helpers + * + * RISC-V CSR instructions require compile-time constant addresses encoded in + * the instruction itself. These helpers use switch-case dispatch to provide + * runtime indexed access to PMP configuration and address registers. + * + * - pmpcfg0-3: Four 32-bit configuration registers (16 regions, 8 bits each) + * - pmpaddr0-15: Sixteen address registers for TOR (Top-of-Range) mode + */ + +/* Read PMP configuration register by index (0-3) */ +static uint32_t read_pmpcfg(uint8_t idx) +{ + switch (idx) { + case 0: return read_csr_num(CSR_PMPCFG0); + case 1: return read_csr_num(CSR_PMPCFG1); + case 2: return read_csr_num(CSR_PMPCFG2); + case 3: return read_csr_num(CSR_PMPCFG3); + default: return 0; + } +} + +/* Write PMP configuration register by index (0-3) */ +static void write_pmpcfg(uint8_t idx, uint32_t val) +{ + switch (idx) { + case 0: write_csr_num(CSR_PMPCFG0, val); break; + case 1: write_csr_num(CSR_PMPCFG1, val); break; + case 2: write_csr_num(CSR_PMPCFG2, val); break; + case 3: write_csr_num(CSR_PMPCFG3, val); break; + } +} + +/* Read PMP address register by index (0-15) */ +static uint32_t read_pmpaddr(uint8_t idx) +{ + switch (idx) { + case 0: return read_csr_num(CSR_PMPADDR0); + case 1: return read_csr_num(CSR_PMPADDR1); + case 2: return read_csr_num(CSR_PMPADDR2); + case 3: return read_csr_num(CSR_PMPADDR3); + case 4: return read_csr_num(CSR_PMPADDR4); + case 5: return read_csr_num(CSR_PMPADDR5); + case 6: return read_csr_num(CSR_PMPADDR6); + case 7: return read_csr_num(CSR_PMPADDR7); + case 8: return read_csr_num(CSR_PMPADDR8); + case 9: return read_csr_num(CSR_PMPADDR9); + case 10: return read_csr_num(CSR_PMPADDR10); + case 11: return read_csr_num(CSR_PMPADDR11); + case 12: return read_csr_num(CSR_PMPADDR12); + case 13: return read_csr_num(CSR_PMPADDR13); + case 14: return read_csr_num(CSR_PMPADDR14); + case 15: return read_csr_num(CSR_PMPADDR15); + default: return 0; + } +} + +/* Write PMP address register by index (0-15) */ +static void write_pmpaddr(uint8_t idx, uint32_t val) +{ + switch (idx) { + case 0: write_csr_num(CSR_PMPADDR0, val); break; + case 1: write_csr_num(CSR_PMPADDR1, val); break; + case 2: write_csr_num(CSR_PMPADDR2, val); break; + case 3: write_csr_num(CSR_PMPADDR3, val); break; + case 4: write_csr_num(CSR_PMPADDR4, val); break; + case 5: write_csr_num(CSR_PMPADDR5, val); break; + case 6: write_csr_num(CSR_PMPADDR6, val); break; + case 7: write_csr_num(CSR_PMPADDR7, val); break; + case 8: write_csr_num(CSR_PMPADDR8, val); break; + case 9: write_csr_num(CSR_PMPADDR9, val); break; + case 10: write_csr_num(CSR_PMPADDR10, val); break; + case 11: write_csr_num(CSR_PMPADDR11, val); break; + case 12: write_csr_num(CSR_PMPADDR12, val); break; + case 13: write_csr_num(CSR_PMPADDR13, val); break; + case 14: write_csr_num(CSR_PMPADDR14, val); break; + case 15: write_csr_num(CSR_PMPADDR15, val); break; + } +} /* Static Memory Pools for Boot-time PMP Initialization * From 34010063be2508250ffe94b6337407119370f3fb Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 21:59:05 +0800 Subject: [PATCH 12/18] Implement PMP hardware initialization function Clear all PMP regions and initialize shadow configuration state. Sets up hardware and software state for subsequent region configuration. --- arch/riscv/pmp.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 90cffb0..30edcda 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -118,6 +118,32 @@ pmp_config_t *pmp_get_config(void) return &pmp_global_config; } +int32_t pmp_init(pmp_config_t *config) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Clear all PMP regions in hardware and shadow configuration */ + for (uint8_t i = 0; i < PMP_MAX_REGIONS; i++) { + write_pmpaddr(i, 0); + if (i % 4 == 0) + write_pmpcfg(i / 4, 0); + + config->regions[i].addr_start = 0; + config->regions[i].addr_end = 0; + config->regions[i].permissions = 0; + config->regions[i].priority = PMP_PRIORITY_TEMPORARY; + config->regions[i].region_id = i; + config->regions[i].locked = 0; + } + + config->region_count = 0; + config->next_region_idx = 0; + config->initialized = 1; + + return ERR_OK; +} + int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, size_t count) { From 06cc83b349c6f68876d6fc8291497a534d95ebe5 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 22:03:43 +0800 Subject: [PATCH 13/18] Configure individual PMP regions in TOR mode Implements region configuration that validates addresses, constructs configuration bytes with proper addressing mode and permission bits, and synchronizes both hardware CSRs and shadow state. Supports optional region locking to prevent further modification. --- arch/riscv/pmp.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 30edcda..245773d 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -186,3 +186,60 @@ int32_t pmp_init_kernel(pmp_config_t *config) { return pmp_init_pools(config, kernel_mempools, KERNEL_MEMPOOL_COUNT); } + +int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region) +{ + if (!config || !region) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region->region_id >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + /* Validate address range */ + if (region->addr_start >= region->addr_end) + return ERR_PMP_ADDR_RANGE; + + /* Check if region is already locked */ + if (config->regions[region->region_id].locked) + return ERR_PMP_LOCKED; + + uint8_t region_idx = region->region_id; + uint8_t pmpcfg_idx = region_idx / 4; + uint8_t pmpcfg_offset = (region_idx % 4) * 8; + + /* Build configuration byte with TOR mode and permissions */ + uint8_t pmpcfg_perm = region->permissions & (PMPCFG_R | PMPCFG_W | PMPCFG_X); + uint8_t pmpcfg_byte = PMPCFG_A_TOR | pmpcfg_perm; + if (region->locked) + pmpcfg_byte |= PMPCFG_L; + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Clear the configuration byte for this region */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write new configuration byte */ + pmpcfg_val |= (pmpcfg_byte << pmpcfg_offset); + + /* Write pmpaddr register with the upper boundary */ + write_pmpaddr(region_idx, region->addr_end); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].addr_start = region->addr_start; + config->regions[region_idx].addr_end = region->addr_end; + config->regions[region_idx].permissions = region->permissions; + config->regions[region_idx].priority = region->priority; + config->regions[region_idx].region_id = region_idx; + config->regions[region_idx].locked = region->locked; + + /* Update region count if this is a newly used region */ + if (region_idx >= config->region_count) + config->region_count = region_idx + 1; + + return ERR_OK; +} From b5d6577d1d5086ff4c3b4b2ab41291875ff24d5c Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 22:13:58 +0800 Subject: [PATCH 14/18] Extract helper for configuration index calculation Refactors the computation of configuration register index and bit offset into a reusable helper to reduce code duplication and improve maintainability. Updates existing code to use the new helper. --- arch/riscv/pmp.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 245773d..e4ee021 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -113,6 +113,14 @@ static const mempool_t kernel_mempools[] = { /* Global PMP configuration (shadow of hardware state) */ static pmp_config_t pmp_global_config; +/* Helper to compute pmpcfg register index and bit offset for a given region */ +static inline void pmp_get_cfg_indices(uint8_t region_idx, uint8_t *cfg_idx, + uint8_t *cfg_offset) +{ + *cfg_idx = region_idx / 4; + *cfg_offset = (region_idx % 4) * 8; +} + pmp_config_t *pmp_get_config(void) { return &pmp_global_config; @@ -205,8 +213,8 @@ int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region) return ERR_PMP_LOCKED; uint8_t region_idx = region->region_id; - uint8_t pmpcfg_idx = region_idx / 4; - uint8_t pmpcfg_offset = (region_idx % 4) * 8; + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); /* Build configuration byte with TOR mode and permissions */ uint8_t pmpcfg_perm = region->permissions & (PMPCFG_R | PMPCFG_W | PMPCFG_X); From 658dd30b0e55a49db390f56a4a191be56b98cd1f Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 22:14:30 +0800 Subject: [PATCH 15/18] Disable and lock regions for protection Implements region disabling which clears configuration to remove protection, and region locking which sets the lock bit to prevent further modification without hardware reset. Both operations preserve other regions in their respective configuration registers. --- arch/riscv/pmp.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index e4ee021..78f6282 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -251,3 +251,72 @@ int32_t pmp_set_region(pmp_config_t *config, const pmp_region_t *region) return ERR_OK; } + +int32_t pmp_disable_region(pmp_config_t *config, uint8_t region_idx) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + /* Check if region is already locked */ + if (config->regions[region_idx].locked) + return ERR_PMP_LOCKED; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Clear the configuration byte for this region (disables it) */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].addr_start = 0; + config->regions[region_idx].addr_end = 0; + config->regions[region_idx].permissions = 0; + + return ERR_OK; +} + +int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read current pmpcfg register to preserve other regions */ + uint32_t pmpcfg_val = read_pmpcfg(pmpcfg_idx); + + /* Get current configuration byte for this region */ + uint8_t pmpcfg_byte = (pmpcfg_val >> pmpcfg_offset) & 0xFFU; + + /* Set lock bit */ + pmpcfg_byte |= PMPCFG_L; + + /* Clear the configuration byte for this region */ + pmpcfg_val &= ~(0xFFU << pmpcfg_offset); + + /* Write new configuration byte with lock bit set */ + pmpcfg_val |= (pmpcfg_byte << pmpcfg_offset); + + /* Write pmpcfg register with updated configuration */ + write_pmpcfg(pmpcfg_idx, pmpcfg_val); + + /* Update shadow configuration */ + config->regions[region_idx].locked = 1; + + return ERR_OK; +} From 01ab4f48fae1c69a73ba749b94967debde69fb27 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 22:16:15 +0800 Subject: [PATCH 16/18] Read PMP region configuration from shadow state Retrieves the address range, permissions, priority, and lock status of a configured region from the shadow configuration state. --- arch/riscv/pmp.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 78f6282..f70433d 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -320,3 +320,27 @@ int32_t pmp_lock_region(pmp_config_t *config, uint8_t region_idx) return ERR_OK; } + +int32_t pmp_get_region(const pmp_config_t *config, uint8_t region_idx, + pmp_region_t *region) +{ + if (!config || !region) + return ERR_PMP_INVALID_REGION; + + /* Validate region index is within bounds */ + if (region_idx >= PMP_MAX_REGIONS) + return ERR_PMP_INVALID_REGION; + + uint8_t pmpcfg_idx, pmpcfg_offset; + pmp_get_cfg_indices(region_idx, &pmpcfg_idx, &pmpcfg_offset); + + /* Read the address and configuration from shadow configuration */ + region->addr_start = config->regions[region_idx].addr_start; + region->addr_end = config->regions[region_idx].addr_end; + region->permissions = config->regions[region_idx].permissions; + region->priority = config->regions[region_idx].priority; + region->region_id = region_idx; + region->locked = config->regions[region_idx].locked; + + return ERR_OK; +} From 749495957b933fda8538791d134e553c8fa44605 Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Sun, 2 Nov 2025 22:21:35 +0800 Subject: [PATCH 17/18] Verify memory access permissions in PMP regions Checks if a memory access falls within a configured region by comparing the requested address and size against the region boundaries. When a matching region is found, validates that the region's permissions match the requested operation type. Address register read helpers are marked unused as the current implementation maintains shadow state in memory rather than reading hardware registers. They remain available for potential future use cases requiring hardware state verification. --- arch/riscv/pmp.c | 47 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index f70433d..9606dc7 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -42,8 +42,13 @@ static void write_pmpcfg(uint8_t idx, uint32_t val) } } -/* Read PMP address register by index (0-15) */ -static uint32_t read_pmpaddr(uint8_t idx) +/* Read PMP address register by index (0-15) + * + * Currently unused as the implementation maintains shadow state in memory + * rather than reading hardware registers. Provided for API completeness + * and potential future use cases requiring hardware state verification. + */ +static uint32_t __attribute__((unused)) read_pmpaddr(uint8_t idx) { switch (idx) { case 0: return read_csr_num(CSR_PMPADDR0); @@ -344,3 +349,41 @@ int32_t pmp_get_region(const pmp_config_t *config, uint8_t region_idx, return ERR_OK; } + +int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, + uint32_t size, uint8_t is_write, uint8_t is_execute) +{ + if (!config) + return ERR_PMP_INVALID_REGION; + + uint32_t access_end = addr + size; + + /* In TOR mode, check all regions in priority order */ + for (uint8_t i = 0; i < config->region_count; i++) { + const pmp_region_t *region = &config->regions[i]; + + /* Skip disabled regions */ + if (region->addr_start == 0 && region->addr_end == 0) + continue; + + /* Check if access falls within this region */ + if (addr >= region->addr_start && access_end <= region->addr_end) { + /* Verify permissions match access type */ + uint8_t required_perm = 0; + if (is_write) + required_perm |= PMPCFG_W; + if (is_execute) + required_perm |= PMPCFG_X; + if (!is_write && !is_execute) + required_perm = PMPCFG_R; + + if ((region->permissions & required_perm) == required_perm) + return 1; /* Access allowed */ + else + return 0; /* Access denied */ + } + } + + /* Access not covered by any region */ + return 0; +} From f6c391293b22b25ee119f31fe52fb8ddd83bb68e Mon Sep 17 00:00:00 2001 From: HeatCrab Date: Mon, 3 Nov 2025 17:14:57 +0800 Subject: [PATCH 18/18] Handle PMP access faults with dynamic region loading When a task accesses memory that is not currently loaded in a PMP region, the hardware raises an access fault. Rather than immediately panicking, we now attempt to recover by dynamically loading the required region. This enables a task to access more memory than can fit simultaneously in the 16 available hardware regions. If all regions are in use, we select a victim region and evict it to make space. This requires exposing internal region management functions in the public header so the handler can invoke them. Simplify function documentation at implementation sites since detailed documentation now resides in headers. --- arch/riscv/hal.c | 7 +++++++ arch/riscv/pmp.c | 38 ++++++++++++++++++++++++++++++++++++++ arch/riscv/pmp.h | 12 ++++++++++++ include/sys/memprot.h | 23 +++++++++++++++++++++++ kernel/memprot.c | 31 +++++-------------------------- 5 files changed, 85 insertions(+), 26 deletions(-) diff --git a/arch/riscv/hal.c b/arch/riscv/hal.c index 35703a8..e69e504 100644 --- a/arch/riscv/hal.c +++ b/arch/riscv/hal.c @@ -3,6 +3,7 @@ #include #include "csr.h" +#include "pmp.h" #include "private/stdio.h" #include "private/utils.h" @@ -294,6 +295,12 @@ void do_trap(uint32_t cause, uint32_t epc) const char *reason = "Unknown exception"; if (code < ARRAY_SIZE(exc_msg) && exc_msg[code]) reason = exc_msg[code]; + + /* Attempt to recover PMP access faults */ + if ((code == 5 || code == 7) && pmp_handle_access_fault(epc, code == 7) == 0) + return; + + /* All other exceptions are fatal */ printf("[EXCEPTION] code=%u (%s), epc=%08x, cause=%08x\n", code, reason, epc, cause); hal_panic(); diff --git a/arch/riscv/pmp.c b/arch/riscv/pmp.c index 9606dc7..fba4525 100644 --- a/arch/riscv/pmp.c +++ b/arch/riscv/pmp.c @@ -5,6 +5,7 @@ #include #include +#include #include "csr.h" #include "pmp.h" #include "private/error.h" @@ -387,3 +388,40 @@ int32_t pmp_check_access(const pmp_config_t *config, uint32_t addr, /* Access not covered by any region */ return 0; } + +int32_t pmp_handle_access_fault(uint32_t epc, uint8_t is_write) +{ + if (!kcb || !kcb->task_current || !kcb->task_current->data) + return -1; + + memspace_t *mspace = ((tcb_t *)kcb->task_current->data)->mspace; + if (!mspace) + return -1; + + /* Find flexpage containing faulting address */ + fpage_t *target_fpage = NULL; + for (fpage_t *fp = mspace->first; fp; fp = fp->as_next) { + if (epc >= fp->base && epc < (fp->base + fp->size)) { + target_fpage = fp; + break; + } + } + + if (!target_fpage || target_fpage->pmp_id != 0) + return -1; + + pmp_config_t *config = pmp_get_config(); + if (!config) + return -1; + + /* Load into available region or evict victim */ + if (config->next_region_idx < PMP_MAX_REGIONS) + return pmp_load_fpage(target_fpage, config->next_region_idx); + + fpage_t *victim = select_victim_fpage(mspace); + if (!victim) + return -1; + + int32_t ret = pmp_evict_fpage(victim); + return (ret == 0) ? pmp_load_fpage(target_fpage, victim->pmp_id) : ret; +} diff --git a/arch/riscv/pmp.h b/arch/riscv/pmp.h index 3e051f4..2117f06 100644 --- a/arch/riscv/pmp.h +++ b/arch/riscv/pmp.h @@ -107,3 +107,15 @@ int32_t pmp_init_pools(pmp_config_t *config, const mempool_t *pools, * Returns 0 on success, or negative error code on failure. */ int32_t pmp_init_kernel(pmp_config_t *config); + +/* Handles PMP access violations (exception codes 5 and 7). + * + * Attempts to recover from PMP access faults by loading the required memory + * region into a hardware PMP region. If all 16 regions are in use, selects a + * victim for eviction and reuses its region. + * + * @epc : Program counter where violation occurred + * @is_write : 1 for store/AMO access (exception code 7), 0 for load (code 5) + * Returns 0 on successful recovery, negative error code on failure. + */ +int32_t pmp_handle_access_fault(uint32_t epc, uint8_t is_write); diff --git a/include/sys/memprot.h b/include/sys/memprot.h index f09ea8b..69b5bd5 100644 --- a/include/sys/memprot.h +++ b/include/sys/memprot.h @@ -105,3 +105,26 @@ memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared); * @mspace : Pointer to memory space to destroy */ void mo_memspace_destroy(memspace_t *mspace); + +/* PMP Hardware Loading Functions */ + +/* Loads a flexpage into a PMP hardware region. + * @fpage : Pointer to flexpage to load + * @region_idx : Hardware PMP region index (0-15) + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx); + +/* Evicts a flexpage from its PMP hardware region. + * @fpage : Pointer to flexpage to evict + * Returns 0 on success, or negative error code on failure. + */ +int32_t pmp_evict_fpage(fpage_t *fpage); + +/* Victim Selection for PMP Region Eviction + * + * Selects a flexpage for eviction using priority-based algorithm. + * @mspace : Pointer to memory space + * Returns pointer to victim flexpage, or NULL if no evictable page found. + */ +fpage_t *select_victim_fpage(memspace_t *mspace); diff --git a/kernel/memprot.c b/kernel/memprot.c index eab868d..5298600 100644 --- a/kernel/memprot.c +++ b/kernel/memprot.c @@ -42,11 +42,7 @@ void mo_fpage_destroy(fpage_t *fpage) free(fpage); } -/* Selects victim flexpage for eviction using priority-based algorithm. - * - * @mspace : Pointer to memory space - * Returns pointer to victim flexpage, or NULL if no evictable page found. - */ +/* Selects victim flexpage for eviction using priority-based algorithm */ fpage_t *select_victim_fpage(memspace_t *mspace) { if (!mspace) @@ -67,12 +63,7 @@ fpage_t *select_victim_fpage(memspace_t *mspace) return victim; } -/* Loads a flexpage into a PMP hardware region. - * - * @fpage : Pointer to flexpage to load - * @region_idx : Hardware PMP region index (0-15) - * Returns 0 on success, or negative error code on failure. - */ +/* Loads a flexpage into a PMP hardware region */ int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx) { if (!fpage) @@ -100,11 +91,7 @@ int32_t pmp_load_fpage(fpage_t *fpage, uint8_t region_idx) return ret; } -/* Evicts a flexpage from its PMP hardware region. - * - * @fpage : Pointer to flexpage to evict - * Returns 0 on success, or negative error code on failure. - */ +/* Evicts a flexpage from its PMP hardware region */ int32_t pmp_evict_fpage(fpage_t *fpage) { if (!fpage) @@ -126,12 +113,7 @@ int32_t pmp_evict_fpage(fpage_t *fpage) return ret; } -/* Creates and initializes a memory space. - * - * @as_id : Memory space identifier - * @shared : Whether this space can be shared across tasks - * Returns pointer to created memory space, or NULL on failure. - */ +/* Creates and initializes a memory space */ memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared) { memspace_t *mspace = malloc(sizeof(memspace_t)); @@ -147,10 +129,7 @@ memspace_t *mo_memspace_create(uint32_t as_id, uint32_t shared) return mspace; } -/* Destroys a memory space and all its flexpages. - * - * @mspace : Pointer to memory space to destroy - */ +/* Destroys a memory space and all its flexpages */ void mo_memspace_destroy(memspace_t *mspace) { if (!mspace)