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/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/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.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/hal.h b/arch/riscv/hal.h index 8354264..3b10c4f 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). @@ -27,6 +28,25 @@ extern uint32_t _end; /* End of kernel image */ */ #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 @@ -110,3 +130,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.c b/arch/riscv/pmp.c new file mode 100644 index 0000000..fba4525 --- /dev/null +++ b/arch/riscv/pmp.c @@ -0,0 +1,427 @@ +/* RISC-V Physical Memory Protection (PMP) Implementation + * + * Provides hardware-enforced memory isolation using PMP in TOR mode. + */ + +#include +#include +#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) + * + * 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); + 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 + * + * 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])) + +/* 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; +} + +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) +{ + 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); +} + +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, 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); + 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; +} + +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; +} + +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; +} + +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; +} + +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 new file mode 100644 index 0000000..2117f06 --- /dev/null +++ b/arch/riscv/pmp.h @@ -0,0 +1,121 @@ +/* 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 +#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 */ + +/* 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. + */ +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); + +/* 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); + +/* 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/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 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 */ diff --git a/include/sys/memprot.h b/include/sys/memprot.h new file mode 100644 index 0000000..69b5bd5 --- /dev/null +++ b/include/sys/memprot.h @@ -0,0 +1,130 @@ +/* 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_)) + +/* 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); + +/* 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); + +/* 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/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) */ diff --git a/kernel/memprot.c b/kernel/memprot.c new file mode 100644 index 0000000..5298600 --- /dev/null +++ b/kernel/memprot.c @@ -0,0 +1,147 @@ +/* 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 +#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); +} + +/* Selects victim flexpage for eviction using priority-based algorithm */ +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; +} + +/* Loads a flexpage into a PMP hardware region */ +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 */ +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; +} + +/* 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)); + 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 */ +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); +}