| /* |
| * Copyright (c) 2020 BayLibre, SAS |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <kernel.h> |
| #include <kernel_internal.h> |
| #include <device.h> |
| #include <init.h> |
| #include <sys/__assert.h> |
| #include <sys/check.h> |
| #include <sys/math_extras.h> |
| #include <linker/linker-defs.h> |
| #include <core_pmp.h> |
| #include <arch/riscv/csr.h> |
| |
| #define LOG_LEVEL CONFIG_MPU_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(mpu); |
| |
| #ifdef CONFIG_64BIT |
| # define PR_ADDR "0x%016lx" |
| #else |
| # define PR_ADDR "0x%08lx" |
| #endif |
| |
| #ifdef CONFIG_64BIT |
| # define PMPCFG_NUM(index) (((index) / 8) * 2) |
| # define PMPCFG_SHIFT(index) (((index) % 8) * 8) |
| #else |
| # define PMPCFG_NUM(index) ((index) / 4) |
| # define PMPCFG_SHIFT(index) (((index) % 4) * 8) |
| #endif |
| |
| #if defined(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT) |
| # define PMP_MODE_DEFAULT PMP_MODE_NAPOT |
| # define PMP_USED_ENTRY_DEFAULT 1 /* NAPOT region use 1 PMP entry */ |
| #else |
| # define PMP_MODE_DEFAULT PMP_MODE_TOR |
| # define PMP_USED_ENTRY_DEFAULT 2 /* TOR region use 2 PMP entry */ |
| #endif |
| |
| #ifdef CONFIG_USERSPACE |
| /* |
| * Define used PMP regions before memory domain/partition. |
| * |
| * Already used PMP regions: |
| * 1. 1 entry for MCU state: R |
| * 2. 1/2 entry for user thread stack: RW |
| */ |
| #define PMP_REGION_NUM_FOR_U_THREAD (1 + (1 * PMP_USED_ENTRY_DEFAULT)) |
| #endif /* CONFIG_USERSPACE */ |
| |
| enum pmp_region_mode { |
| PMP_MODE_NA4, |
| /* If NAPOT mode region size is 4, apply NA4 region to PMP CSR. */ |
| PMP_MODE_NAPOT, |
| PMP_MODE_TOR, |
| }; |
| |
| /* Region definition data structure */ |
| struct riscv_pmp_region { |
| ulong_t start; |
| ulong_t size; |
| uint8_t perm; |
| enum pmp_region_mode mode; |
| }; |
| |
| #if defined(CONFIG_USERSPACE) && !defined(CONFIG_SMP) |
| extern uint32_t is_user_mode; |
| #endif |
| |
| /* |
| * RISC-V PMP regions usage (from low number to high): |
| * dynamic region -> static region -> MPRV region |
| * |
| * Lower number PMP region has higher priority. For overlapping PMP |
| * regions, static regions should have lower priority than dynamic |
| * regions. MPRV region should have the lowest priority. |
| */ |
| static uint8_t static_regions_num; |
| static uint8_t mprv_regions_num; |
| |
| /* Get the number of PMP regions */ |
| static inline uint8_t get_num_regions(void) |
| { |
| return CONFIG_PMP_SLOT; |
| } |
| |
| /* Get the maximum PMP region index for dynamic PMP region */ |
| static inline uint8_t max_dynamic_region(void) |
| { |
| return get_num_regions() - static_regions_num - |
| mprv_regions_num - 1; |
| } |
| |
| static const struct riscv_pmp_region static_regions[] = { |
| { |
| /* |
| * Program and RO data: RX permission for both |
| * user/supervisor mode. |
| */ |
| .start = (ulong_t) __rom_region_start, |
| .size = (ulong_t) __rom_region_size, |
| .perm = PMP_R | PMP_X | PMP_L, |
| .mode = PMP_MODE_DEFAULT, |
| }, |
| #ifdef CONFIG_PMP_STACK_GUARD |
| { |
| /* IRQ stack guard */ |
| .start = (ulong_t) z_interrupt_stacks[0], |
| .size = PMP_GUARD_ALIGN_AND_SIZE, |
| .perm = 0, |
| .mode = PMP_MODE_NAPOT, |
| }, |
| #endif |
| }; |
| |
| #ifdef CONFIG_PMP_STACK_GUARD |
| /* |
| * Basically, RISC-V PMP regions affecting M-mode permission should be |
| * locked and can't be a dynamic PMP region. RISC-V PMP stack guard rely |
| * on consistently using mstatus.MPRV bit in M-mode which can emulate |
| * U-mode memory protection policy in M-mode when MPRV bit is enabled. |
| * |
| * For consistent MPRV in M-mode, all memory accesses are denied by |
| * default (like U-mode default). We need to use a lowest priority PMP |
| * region (MPRV region) to permit memory access of whole memory. |
| * |
| * p.s. Zephyr User/Supervisor mode maps to RISC-V U-mode/M-mode. |
| * p.s. MPRV region only used in CONFIG_PMP_STACK_GUARD. |
| */ |
| static const struct riscv_pmp_region mprv_region = { |
| /* |
| * Enable all other memory access with lowest priority. |
| * |
| * Special case: start = size = 0 means whole memory. |
| */ |
| .start = 0, |
| .size = 0, |
| .perm = PMP_R | PMP_W | PMP_X, |
| .mode = PMP_MODE_NAPOT, |
| }; |
| #endif |
| |
| enum { |
| CSR_PMPCFG0, |
| CSR_PMPCFG1, |
| CSR_PMPCFG2, |
| CSR_PMPCFG3, |
| CSR_PMPADDR0, |
| CSR_PMPADDR1, |
| CSR_PMPADDR2, |
| CSR_PMPADDR3, |
| CSR_PMPADDR4, |
| CSR_PMPADDR5, |
| CSR_PMPADDR6, |
| CSR_PMPADDR7, |
| CSR_PMPADDR8, |
| CSR_PMPADDR9, |
| CSR_PMPADDR10, |
| CSR_PMPADDR11, |
| CSR_PMPADDR12, |
| CSR_PMPADDR13, |
| CSR_PMPADDR14, |
| CSR_PMPADDR15 |
| }; |
| |
| static ulong_t csr_read_enum(int pmp_csr_enum) |
| { |
| ulong_t res = -1; |
| |
| switch (pmp_csr_enum) { |
| case CSR_PMPCFG0: |
| res = csr_read(0x3A0); break; |
| case CSR_PMPCFG1: |
| res = csr_read(0x3A1); break; |
| case CSR_PMPCFG2: |
| res = csr_read(0x3A2); break; |
| case CSR_PMPCFG3: |
| res = csr_read(0x3A3); break; |
| case CSR_PMPADDR0: |
| res = csr_read(0x3B0); break; |
| case CSR_PMPADDR1: |
| res = csr_read(0x3B1); break; |
| case CSR_PMPADDR2: |
| res = csr_read(0x3B2); break; |
| case CSR_PMPADDR3: |
| res = csr_read(0x3B3); break; |
| case CSR_PMPADDR4: |
| res = csr_read(0x3B4); break; |
| case CSR_PMPADDR5: |
| res = csr_read(0x3B5); break; |
| case CSR_PMPADDR6: |
| res = csr_read(0x3B6); break; |
| case CSR_PMPADDR7: |
| res = csr_read(0x3B7); break; |
| case CSR_PMPADDR8: |
| res = csr_read(0x3B8); break; |
| case CSR_PMPADDR9: |
| res = csr_read(0x3B9); break; |
| case CSR_PMPADDR10: |
| res = csr_read(0x3BA); break; |
| case CSR_PMPADDR11: |
| res = csr_read(0x3BB); break; |
| case CSR_PMPADDR12: |
| res = csr_read(0x3BC); break; |
| case CSR_PMPADDR13: |
| res = csr_read(0x3BD); break; |
| case CSR_PMPADDR14: |
| res = csr_read(0x3BE); break; |
| case CSR_PMPADDR15: |
| res = csr_read(0x3BF); break; |
| default: |
| break; |
| } |
| return res; |
| } |
| |
| static void csr_write_enum(int pmp_csr_enum, ulong_t value) |
| { |
| switch (pmp_csr_enum) { |
| case CSR_PMPCFG0: |
| csr_write(0x3A0, value); break; |
| case CSR_PMPCFG1: |
| csr_write(0x3A1, value); break; |
| case CSR_PMPCFG2: |
| csr_write(0x3A2, value); break; |
| case CSR_PMPCFG3: |
| csr_write(0x3A3, value); break; |
| case CSR_PMPADDR0: |
| csr_write(0x3B0, value); break; |
| case CSR_PMPADDR1: |
| csr_write(0x3B1, value); break; |
| case CSR_PMPADDR2: |
| csr_write(0x3B2, value); break; |
| case CSR_PMPADDR3: |
| csr_write(0x3B3, value); break; |
| case CSR_PMPADDR4: |
| csr_write(0x3B4, value); break; |
| case CSR_PMPADDR5: |
| csr_write(0x3B5, value); break; |
| case CSR_PMPADDR6: |
| csr_write(0x3B6, value); break; |
| case CSR_PMPADDR7: |
| csr_write(0x3B7, value); break; |
| case CSR_PMPADDR8: |
| csr_write(0x3B8, value); break; |
| case CSR_PMPADDR9: |
| csr_write(0x3B9, value); break; |
| case CSR_PMPADDR10: |
| csr_write(0x3BA, value); break; |
| case CSR_PMPADDR11: |
| csr_write(0x3BB, value); break; |
| case CSR_PMPADDR12: |
| csr_write(0x3BC, value); break; |
| case CSR_PMPADDR13: |
| csr_write(0x3BD, value); break; |
| case CSR_PMPADDR14: |
| csr_write(0x3BE, value); break; |
| case CSR_PMPADDR15: |
| csr_write(0x3BF, value); break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * @brief Set a Physical Memory Protection slot |
| * |
| * Configure a memory region to be secured by one of the 16 PMP entries. |
| * |
| * @param index Number of the targeted PMP entry (0 to 15 only). |
| * @param cfg_val Configuration value (cf datasheet or defined flags) |
| * @param addr_val Address register value |
| * |
| * This function shall only be called from Secure state. |
| * |
| * @return -1 if bad argument, 0 otherwise. |
| */ |
| static int riscv_pmp_set(unsigned int index, uint8_t cfg_val, ulong_t addr_val) |
| { |
| ulong_t reg_val; |
| ulong_t shift, mask; |
| int pmpcfg_csr; |
| int pmpaddr_csr; |
| |
| if (index >= get_num_regions()) { |
| return -1; |
| } |
| |
| /* Calculate PMP config/addr register, shift and mask */ |
| pmpcfg_csr = CSR_PMPCFG0 + PMPCFG_NUM(index); |
| pmpaddr_csr = CSR_PMPADDR0 + index; |
| shift = PMPCFG_SHIFT(index); |
| mask = 0xFFUL << shift; |
| |
| reg_val = csr_read_enum(pmpcfg_csr); |
| reg_val = reg_val & ~mask; |
| reg_val = reg_val | ((ulong_t)cfg_val << shift); |
| |
| csr_write_enum(pmpaddr_csr, addr_val); |
| csr_write_enum(pmpcfg_csr, reg_val); |
| return 0; |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| static int riscv_pmp_get(unsigned int index, uint8_t *cfg_val, ulong_t *addr_val) |
| { |
| ulong_t reg_val; |
| ulong_t shift, mask; |
| int pmpcfg_csr; |
| int pmpaddr_csr; |
| |
| if (index >= get_num_regions()) { |
| return -1; |
| } |
| |
| /* Calculate PMP config/addr register, shift */ |
| pmpcfg_csr = CSR_PMPCFG0 + PMPCFG_NUM(index); |
| pmpaddr_csr = CSR_PMPADDR0 + index; |
| shift = PMPCFG_SHIFT(index); |
| mask = 0xFFUL << shift; |
| |
| reg_val = csr_read_enum(pmpcfg_csr); |
| *cfg_val = (reg_val & mask) >> shift; |
| |
| if (addr_val) { |
| pmpaddr_csr = CSR_PMPADDR0 + index; |
| *addr_val = csr_read_enum(pmpaddr_csr); |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_USERSPACE */ |
| |
| /* |
| * @brief Configure the range of pmpcfg CSRs. |
| * |
| * Configure pmpcfg array to range of pmpcfg CSRs from min_index to max_index. |
| * If u8_pmpcfg is NULL, this function will clear range of $pmpcfg CSRs to 0. |
| * |
| * @param min_index First number of the targeted PMP entry. |
| * @param max_index Last number of the targeted PMP entry. |
| * @param u8_pmpcfg Array of PMP configuration value. NULL means clearing PMPCFG. |
| */ |
| void riscv_pmpcfg_set_range(uint8_t min_index, uint8_t max_index, uint8_t *u8_pmpcfg) |
| { |
| ulong_t cfg_mask = 0; |
| ulong_t new_pmpcfg = 0; |
| ulong_t shift; |
| |
| for (int index = min_index; index <= max_index; index++) { |
| shift = PMPCFG_SHIFT(index); |
| cfg_mask |= (0xFFUL << shift); |
| |
| /* If u8_pmpcfg is NULL, new_pmpcfg is always 0 to clear pmpcfg CSR. */ |
| if (u8_pmpcfg != NULL) { |
| new_pmpcfg |= ((ulong_t) u8_pmpcfg[index]) << shift; |
| } |
| |
| /* If index+1 is new CSR or it's last index, apply new_pmpcfg value to the CSR. */ |
| if ((PMPCFG_SHIFT(index+1) == 0) || (index == max_index)) { |
| int pmpcfg_csr = CSR_PMPCFG0 + PMPCFG_NUM(index); |
| |
| /* Update pmpcfg CSR */ |
| if (cfg_mask == -1UL) { |
| /* Update whole CSR */ |
| csr_write_enum(pmpcfg_csr, new_pmpcfg); |
| } else { |
| /* Only update cfg_mask bits of CSR */ |
| ulong_t pmpcfg = csr_read_enum(pmpcfg_csr); |
| |
| pmpcfg &= ~cfg_mask; |
| pmpcfg |= (cfg_mask & new_pmpcfg); |
| csr_write_enum(pmpcfg_csr, pmpcfg); |
| } |
| |
| /* Clear variables for next pmpcfg */ |
| cfg_mask = 0; |
| new_pmpcfg = 0; |
| } |
| } |
| } |
| |
| #ifdef CONFIG_USERSPACE |
| /* |
| * @brief Get the expected PMP region value of current thread in user mode. |
| * |
| * Because PMP stack guard support will set different dynamic PMP regions for |
| * thread in user and supervisor mode, checking user thread permission couldn't |
| * directly use PMP CSR values in the HW, but also use expected PMP region |
| * of current thread in user mode. |
| */ |
| static int riscv_pmp_get_user_thread(unsigned int index, uint8_t *cfg_val, ulong_t *addr_val) |
| { |
| uint8_t last_static_index = max_dynamic_region() + 1; |
| |
| if (index >= get_num_regions()) { |
| return -1; |
| } |
| |
| if (index >= last_static_index) { |
| /* This PMP index is static PMP region, check PMP CSRs. */ |
| riscv_pmp_get(index, cfg_val, addr_val); |
| } else { |
| /* |
| * This PMP index is dynamic PMP region, check u_pmpcfg of |
| * current thread. |
| */ |
| uint8_t *u8_pmpcfg = (uint8_t *) _current->arch.u_pmpcfg; |
| ulong_t *pmpaddr = _current->arch.u_pmpaddr; |
| |
| *cfg_val = u8_pmpcfg[index]; |
| |
| if (addr_val) { |
| *addr_val = pmpaddr[index]; |
| } |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_USERSPACE */ |
| |
| static int riscv_pmp_region_translate(int index, |
| const struct riscv_pmp_region *region, bool to_csr, |
| ulong_t pmpcfg[], ulong_t pmpaddr[]) |
| { |
| int result, pmp_mode; |
| |
| if ((region->start == 0) && (region->size == 0)) { |
| /* special case: set whole memory as single PMP region. |
| * RV32: 0 ~ (2**32 - 1) |
| * RV64: 0 ~ (2**64 - 1) |
| */ |
| if (index >= CONFIG_PMP_SLOT) { |
| return -ENOSPC; |
| } |
| |
| pmp_mode = PMP_NAPOT; |
| |
| uint8_t cfg_val = PMP_NAPOT | region->perm; |
| #ifdef CONFIG_64BIT |
| ulong_t addr_val = 0x1FFFFFFFFFFFFFFF; |
| #else |
| ulong_t addr_val = 0x1FFFFFFF; |
| #endif |
| |
| if (to_csr) { |
| riscv_pmp_set(index, cfg_val, addr_val); |
| } else { |
| uint8_t *u8_pmpcfg = (uint8_t *)pmpcfg; |
| |
| u8_pmpcfg[index] = cfg_val; |
| pmpaddr[index] = addr_val; |
| } |
| |
| result = index+1; |
| } else if (region->mode == PMP_MODE_TOR) { |
| if ((index+1) >= CONFIG_PMP_SLOT) { |
| return -ENOSPC; |
| } |
| |
| pmp_mode = PMP_TOR; |
| |
| uint8_t cfg_val1 = PMP_NA4 | region->perm; |
| ulong_t addr_val1 = TO_PMP_ADDR(region->start); |
| uint8_t cfg_val2 = PMP_TOR | region->perm; |
| ulong_t addr_val2 = TO_PMP_ADDR(region->start + region->size); |
| |
| if (to_csr) { |
| riscv_pmp_set(index, cfg_val1, addr_val1); |
| riscv_pmp_set(index+1, cfg_val2, addr_val2); |
| } else { |
| uint8_t *u8_pmpcfg = (uint8_t *)pmpcfg; |
| |
| u8_pmpcfg[index] = cfg_val1; |
| pmpaddr[index] = addr_val1; |
| u8_pmpcfg[index+1] = cfg_val2; |
| pmpaddr[index+1] = addr_val2; |
| } |
| |
| result = index+2; |
| } else { |
| if (index >= CONFIG_PMP_SLOT) { |
| return -ENOSPC; |
| } |
| |
| if ((region->mode == PMP_MODE_NA4) || (region->size == 4)) { |
| pmp_mode = PMP_NA4; |
| } else { |
| pmp_mode = PMP_NAPOT; |
| } |
| |
| uint8_t cfg_val = pmp_mode | region->perm; |
| ulong_t addr_val = TO_PMP_NAPOT(region->start, region->size); |
| |
| if (to_csr) { |
| riscv_pmp_set(index, cfg_val, addr_val); |
| } else { |
| uint8_t *u8_pmpcfg = (uint8_t *)pmpcfg; |
| |
| u8_pmpcfg[index] = cfg_val; |
| pmpaddr[index] = addr_val; |
| } |
| |
| result = index+1; |
| } |
| |
| if (to_csr) { |
| LOG_DBG("Set PMP region %d: (" PR_ADDR ", " PR_ADDR |
| ", %s%s%s, %s)", index, region->start, region->size, |
| ((region->perm & PMP_R) ? "R" : " "), |
| ((region->perm & PMP_W) ? "W" : " "), |
| ((region->perm & PMP_X) ? "X" : " "), |
| ((pmp_mode == PMP_TOR) ? "TOR" : |
| (pmp_mode == PMP_NAPOT) ? "NAPOT" : "NA4")); |
| } else { |
| LOG_DBG("PMP context " PR_ADDR " add region %d: (" PR_ADDR |
| ", " PR_ADDR ", %s%s%s, %s)", (ulong_t) pmpcfg, index, |
| region->start, region->size, |
| ((region->perm & PMP_R) ? "R" : " "), |
| ((region->perm & PMP_W) ? "W" : " "), |
| ((region->perm & PMP_X) ? "X" : " "), |
| ((pmp_mode == PMP_TOR) ? "TOR" : |
| (pmp_mode == PMP_NAPOT) ? "NAPOT" : "NA4")); |
| } |
| |
| if (pmp_mode == PMP_TOR) { |
| LOG_DBG("TOR mode region also use entry %d", index+1); |
| } |
| |
| return result; |
| } |
| |
| #if defined(CONFIG_PMP_STACK_GUARD) || defined(CONFIG_USERSPACE) |
| static int riscv_pmp_regions_translate(int start_reg_index, |
| const struct riscv_pmp_region regions[], uint8_t regions_num, |
| ulong_t pmpcfg[], ulong_t pmpaddr[]) |
| { |
| int reg_index = start_reg_index; |
| |
| for (int i = 0; i < regions_num; i++) { |
| /* |
| * Empty region. |
| * |
| * Note: start = size = 0 is valid region (special case). |
| */ |
| if ((regions[i].size == 0U) && (regions[i].start != 0)) { |
| continue; |
| } |
| |
| /* Non-empty region. */ |
| reg_index = riscv_pmp_region_translate(reg_index, ®ions[i], |
| false, pmpcfg, pmpaddr); |
| |
| if (reg_index == -ENOSPC) { |
| LOG_ERR("no free PMP entry"); |
| return -ENOSPC; |
| } |
| } |
| |
| return reg_index; |
| } |
| #endif /* defined(CONFIG_PMP_STACK_GUARD) || defined(CONFIG_USERSPACE) */ |
| |
| static int riscv_pmp_region_set(int index, const struct riscv_pmp_region *region) |
| { |
| /* Don't check special case: start = size = 0 */ |
| if (!((region->start == 0) && (region->size == 0))) { |
| /* Check 4 bytes alignment */ |
| CHECKIF(!(((region->start & 0x3) == 0) && |
| ((region->size & 0x3) == 0) && |
| (region->size))) { |
| LOG_ERR("PMP address/size are not 4 bytes aligned"); |
| return -EINVAL; |
| } |
| } |
| |
| return riscv_pmp_region_translate(index, region, true, NULL, NULL); |
| } |
| |
| static int riscv_pmp_regions_set_from_last(int last_reg_index, |
| const struct riscv_pmp_region regions[], uint8_t regions_num) |
| { |
| int reg_index = last_reg_index; |
| |
| for (int i = 0; i < regions_num; i++) { |
| /* |
| * Empty region. |
| * |
| * Note: start = size = 0 is valid region (special case). |
| */ |
| if ((regions[i].size == 0U) && (regions[i].start != 0)) { |
| continue; |
| } |
| |
| /* Non-empty region. */ |
| int used_entry = 1; |
| |
| if (regions[i].mode == PMP_MODE_TOR) { |
| used_entry = 2; |
| } |
| |
| /* Update reg_index to next free entry (from last). */ |
| reg_index -= used_entry; |
| |
| /* Use reg_index+1 PMP entry. */ |
| riscv_pmp_region_set(reg_index+1, ®ions[i]); |
| } |
| |
| return reg_index; |
| } |
| |
| void z_riscv_pmp_clear_config(void) |
| { |
| uint8_t min_index = 0; |
| uint8_t max_index = max_dynamic_region(); |
| uint8_t mprv_index = get_num_regions() - mprv_regions_num; |
| |
| LOG_DBG("Clear all dynamic PMP regions: (%d, %d) index", |
| min_index, max_index); |
| |
| /* Clear all dynamic PMP regions (from min_index to max_index). */ |
| riscv_pmpcfg_set_range(min_index, max_index, NULL); |
| |
| /* Clear MPRV region which is also a dynamic region. |
| * It would be reconfigured when configuring M-mode dynamic region. |
| */ |
| riscv_pmpcfg_set_range(mprv_index, get_num_regions() - 1, NULL); |
| } |
| |
| #if defined(CONFIG_USERSPACE) |
| void z_riscv_init_user_accesses(struct k_thread *thread) |
| { |
| struct riscv_pmp_region dynamic_regions[] = { |
| #if !defined(CONFIG_SMP) |
| { |
| /* MCU state */ |
| .start = (ulong_t) &is_user_mode, |
| .size = 4, |
| .perm = PMP_R, |
| .mode = PMP_MODE_NA4, |
| }, |
| #endif |
| { |
| /* User-mode thread stack */ |
| .start = thread->stack_info.start, |
| .size = thread->stack_info.size, |
| .perm = PMP_R | PMP_W, |
| .mode = PMP_MODE_DEFAULT, |
| }, |
| }; |
| |
| uint8_t index = 0; |
| |
| riscv_pmp_regions_translate(index, dynamic_regions, |
| ARRAY_SIZE(dynamic_regions), thread->arch.u_pmpcfg, |
| thread->arch.u_pmpaddr); |
| } |
| |
| void z_riscv_configure_user_allowed_stack(struct k_thread *thread) |
| { |
| uint8_t min_index = 0; |
| uint8_t max_index = max_dynamic_region(); |
| |
| z_riscv_pmp_clear_config(); |
| |
| /* Set all dynamic PMP regions (from min_index to max_index). */ |
| for (uint8_t i = min_index; i <= max_index; i++) |
| csr_write_enum(CSR_PMPADDR0 + i, thread->arch.u_pmpaddr[i]); |
| |
| riscv_pmpcfg_set_range(min_index, max_index, (uint8_t *)thread->arch.u_pmpcfg); |
| |
| LOG_DBG("Apply user PMP context " PR_ADDR " to dynamic PMP regions: " |
| "(%d, %d) index", (ulong_t) thread->arch.u_pmpcfg, |
| min_index, max_index); |
| } |
| |
| int z_riscv_pmp_add_dynamic(struct k_thread *thread, |
| ulong_t addr, |
| ulong_t size, |
| unsigned char flags) |
| { |
| unsigned char index = 0U; |
| unsigned char *uchar_pmpcfg; |
| unsigned char max_index = max_dynamic_region(); |
| int ret = 0; |
| |
| /* Check 4 bytes alignment */ |
| CHECKIF(!(((addr & 0x3) == 0) && ((size & 0x3) == 0) && size)) { |
| LOG_ERR("address/size are not 4 bytes aligned\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| struct riscv_pmp_region pmp_region = { |
| .start = addr, |
| .size = size, |
| .perm = flags, |
| }; |
| |
| /* Get next free entry */ |
| uchar_pmpcfg = (unsigned char *) thread->arch.u_pmpcfg; |
| |
| index = PMP_REGION_NUM_FOR_U_THREAD; |
| |
| while ((index <= max_index) && uchar_pmpcfg[index]) { |
| index++; |
| } |
| |
| /* Select the best mode */ |
| if (size == 4) { |
| pmp_region.mode = PMP_MODE_NA4; |
| } else { |
| pmp_region.mode = PMP_MODE_DEFAULT; |
| } |
| |
| index = riscv_pmp_region_translate(index, &pmp_region, false, |
| thread->arch.u_pmpcfg, thread->arch.u_pmpaddr); |
| |
| if (index == -ENOSPC) { |
| ret = -ENOSPC; |
| } |
| |
| out: |
| return ret; |
| } |
| |
| /* |
| * Check the 1st bit zero of value from the least significant bit to most one. |
| */ |
| static ALWAYS_INLINE ulong_t count_trailing_one(ulong_t value) |
| { |
| #ifdef HAS_BUILTIN___builtin_ctzl |
| ulong_t revert_value = ~value; |
| |
| return __builtin_ctzl(revert_value); |
| #else |
| int shift = 0; |
| |
| while ((value >> shift) & 0x1) { |
| shift++; |
| } |
| |
| return shift; |
| #endif |
| } |
| |
| /** |
| * This internal function checks if the given buffer is in the region. |
| * |
| * Note: |
| * The caller must provide a valid region number. |
| */ |
| static inline int is_in_region(uint32_t index, uint32_t start, uint32_t size) |
| { |
| ulong_t addr_start; |
| ulong_t addr_end; |
| ulong_t end; |
| uint8_t pmpcfg = 0; |
| ulong_t pmpaddr = 0; |
| |
| riscv_pmp_get_user_thread(index, &pmpcfg, &pmpaddr); |
| |
| if ((pmpcfg & PMP_TYPE_MASK) == PMP_NA4) { |
| addr_start = FROM_PMP_ADDR(pmpaddr); |
| addr_end = addr_start + 4UL - 1UL; |
| } else if ((pmpcfg & PMP_TYPE_MASK) == PMP_NAPOT) { |
| ulong_t shift = count_trailing_one(pmpaddr); |
| ulong_t bitmask = (1UL << (shift+1)) - 1UL; |
| ulong_t size = FROM_PMP_ADDR(bitmask + 1UL); |
| |
| addr_start = FROM_PMP_ADDR(pmpaddr & ~bitmask); |
| addr_end = addr_start - 1UL + size; |
| } else if ((pmpcfg & PMP_TYPE_MASK) == PMP_TOR) { |
| if (index == 0) { |
| addr_start = 0; |
| addr_end = FROM_PMP_ADDR(pmpaddr) - 1UL; |
| } else { |
| ulong_t pmpaddr_prev = csr_read_enum(CSR_PMPADDR0 + index - 1); |
| |
| addr_start = FROM_PMP_ADDR(pmpaddr_prev); |
| addr_end = FROM_PMP_ADDR(pmpaddr) - 1UL; |
| } |
| } else { |
| /* PMP_OFF: PMP region isn't enabled. */ |
| return 0; |
| } |
| |
| size = size == 0U ? 0U : size - 1U; |
| #ifdef CONFIG_64BIT |
| if (u64_add_overflow(start, size, (uint64_t *)&end)) { |
| return 0; |
| } |
| #else |
| if (u32_add_overflow(start, size, (uint32_t *)&end)) { |
| return 0; |
| } |
| #endif |
| |
| if ((start >= addr_start) && (end <= addr_end)) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * This internal function checks if the region is user accessible or not. |
| * |
| * Note: |
| * The caller must provide a valid region number. |
| */ |
| static inline int is_user_accessible_region(uint32_t index, int write) |
| { |
| uint8_t pmpcfg = 0; |
| |
| riscv_pmp_get_user_thread(index, &pmpcfg, NULL); |
| |
| if (write != 0) { |
| return (pmpcfg & PMP_W) == PMP_W; |
| } |
| return (pmpcfg & PMP_R) == PMP_R; |
| } |
| |
| /** |
| * This function validates whether a given memory buffer |
| * is user accessible or not. |
| */ |
| int arch_buffer_validate(void *addr, size_t size, int write) |
| { |
| uint32_t index; |
| |
| /* Iterate all PMP regions */ |
| for (index = 0; index < get_num_regions(); index++) { |
| if (!is_in_region(index, (ulong_t)addr, size)) { |
| continue; |
| } |
| |
| /* |
| * For RISC-V PMP, low region number takes priority. |
| * Since we iterate all PMP regions, we can stop the iteration |
| * immediately once we find the matched region that |
| * grants permission or denies access. |
| */ |
| if (is_user_accessible_region(index, write)) { |
| return 0; |
| } else { |
| return -EPERM; |
| } |
| } |
| |
| return -EPERM; |
| } |
| |
| int arch_mem_domain_max_partitions_get(void) |
| { |
| /* |
| * Note: All static region numbers should be computed before PRE_KERNEL_1 |
| * stage, because kernel will check max_partitions at that time. |
| */ |
| |
| int available_regions = get_num_regions() - PMP_REGION_NUM_FOR_U_THREAD - |
| static_regions_num - mprv_regions_num; |
| |
| return available_regions / PMP_USED_ENTRY_DEFAULT; |
| } |
| |
| int arch_mem_domain_partition_add(struct k_mem_domain *domain, |
| uint32_t partition_id) |
| { |
| sys_dnode_t *node, *next_node; |
| struct k_thread *thread; |
| struct k_mem_partition *partition; |
| int ret = 0, ret2; |
| |
| partition = &domain->partitions[partition_id]; |
| |
| SYS_DLIST_FOR_EACH_NODE_SAFE(&domain->mem_domain_q, node, next_node) { |
| thread = CONTAINER_OF(node, struct k_thread, mem_domain_info); |
| |
| ret2 = z_riscv_pmp_add_dynamic(thread, |
| (ulong_t) partition->start, |
| (ulong_t) partition->size, partition->attr.pmp_attr); |
| ARG_UNUSED(ret2); |
| CHECKIF(ret2 != 0) { |
| ret = ret2; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int arch_mem_domain_partition_remove(struct k_mem_domain *domain, |
| uint32_t partition_id) |
| { |
| sys_dnode_t *node, *next_node; |
| uint32_t index, i, num; |
| uint32_t max_index = max_dynamic_region(); |
| ulong_t pmp_mode, pmp_addr; |
| unsigned char *uchar_pmpcfg; |
| struct k_thread *thread; |
| ulong_t size = (ulong_t) domain->partitions[partition_id].size; |
| ulong_t start = (ulong_t) domain->partitions[partition_id].start; |
| int ret = 0; |
| |
| node = sys_dlist_peek_head(&domain->mem_domain_q); |
| if (!node) { |
| /* |
| * No thread use this memory domain currently, so there isn't |
| * any user PMP region translated from this memory partition. |
| * |
| * We could do nothing and just successfully return. |
| */ |
| |
| return 0; |
| } |
| |
| if (size == 4) { |
| pmp_mode = PMP_NA4; |
| pmp_addr = TO_PMP_ADDR(start); |
| num = 1U; |
| } |
| #if !defined(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT) || defined(CONFIG_PMP_STACK_GUARD) |
| else if ((start & (size - 1)) || (size & (size - 1))) { |
| pmp_mode = PMP_TOR; |
| pmp_addr = TO_PMP_ADDR(start + size); |
| num = 2U; |
| } |
| #endif /* CONFIG_PMP_POWER_OF_TWO_ALIGNMENT || CONFIG_PMP_STACK_GUARD */ |
| else { |
| pmp_mode = PMP_NAPOT; |
| pmp_addr = TO_PMP_NAPOT(start, size); |
| num = 1U; |
| } |
| |
| /* |
| * Find the user PMP region translated from removed |
| * memory partition. |
| */ |
| thread = CONTAINER_OF(node, struct k_thread, mem_domain_info); |
| uchar_pmpcfg = (unsigned char *) thread->arch.u_pmpcfg; |
| for (index = PMP_REGION_NUM_FOR_U_THREAD; |
| index <= max_index; |
| index++) { |
| if (((uchar_pmpcfg[index] & PMP_TYPE_MASK) == pmp_mode) && |
| (pmp_addr == thread->arch.u_pmpaddr[index])) { |
| break; |
| } |
| } |
| |
| CHECKIF(!(index <= max_index)) { |
| LOG_DBG("%s: partition not found\n", __func__); |
| ret = -ENOENT; |
| goto out; |
| } |
| |
| /* |
| * Remove the user PMP region translated from removed |
| * memory partition. The removal affects all threads |
| * using this memory domain. |
| */ |
| #if !defined(CONFIG_PMP_POWER_OF_TWO_ALIGNMENT) || defined(CONFIG_PMP_STACK_GUARD) |
| if (pmp_mode == PMP_TOR) { |
| index--; |
| } |
| #endif /* CONFIG_PMP_POWER_OF_TWO_ALIGNMENT || CONFIG_PMP_STACK_GUARD */ |
| |
| SYS_DLIST_FOR_EACH_NODE_SAFE(&domain->mem_domain_q, node, next_node) { |
| thread = CONTAINER_OF(node, struct k_thread, mem_domain_info); |
| |
| uchar_pmpcfg = (unsigned char *) thread->arch.u_pmpcfg; |
| |
| for (i = index + num; i <= max_index; i++) { |
| uchar_pmpcfg[i - num] = uchar_pmpcfg[i]; |
| thread->arch.u_pmpaddr[i - num] = |
| thread->arch.u_pmpaddr[i]; |
| } |
| |
| uchar_pmpcfg[max_index] = 0U; |
| if (num == 2U) { |
| uchar_pmpcfg[max_index - 1] = 0U; |
| } |
| } |
| |
| out: |
| return ret; |
| } |
| |
| int arch_mem_domain_thread_add(struct k_thread *thread) |
| { |
| struct k_mem_partition *partition; |
| int ret = 0, ret2; |
| |
| for (int i = 0, pcount = 0; |
| pcount < thread->mem_domain_info.mem_domain->num_partitions; |
| i++) { |
| partition = &thread->mem_domain_info.mem_domain->partitions[i]; |
| if (partition->size == 0) { |
| continue; |
| } |
| pcount++; |
| |
| ret2 = z_riscv_pmp_add_dynamic(thread, |
| (ulong_t) partition->start, |
| (ulong_t) partition->size, partition->attr.pmp_attr); |
| ARG_UNUSED(ret2); |
| CHECKIF(ret2 != 0) { |
| ret = ret2; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int arch_mem_domain_thread_remove(struct k_thread *thread) |
| { |
| uint32_t i; |
| unsigned char *uchar_pmpcfg; |
| |
| uchar_pmpcfg = (unsigned char *) thread->arch.u_pmpcfg; |
| |
| for (i = PMP_REGION_NUM_FOR_U_THREAD; i < get_num_regions(); i++) { |
| uchar_pmpcfg[i] = 0U; |
| } |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_USERSPACE */ |
| |
| #ifdef CONFIG_PMP_STACK_GUARD |
| void z_riscv_init_stack_guard(struct k_thread *thread) |
| { |
| struct riscv_pmp_region dynamic_regions[3]; /* Maximum region_num is 3 */ |
| uint8_t region_num = 0U; |
| |
| /* stack guard: None */ |
| dynamic_regions[region_num].start = thread->stack_info.start; |
| dynamic_regions[region_num].size = PMP_GUARD_ALIGN_AND_SIZE; |
| dynamic_regions[region_num].perm = 0; |
| dynamic_regions[region_num].mode = PMP_MODE_TOR; |
| region_num++; |
| |
| #ifdef CONFIG_USERSPACE |
| if (thread->arch.priv_stack_start) { |
| ulong_t stack_guard_addr; |
| |
| #ifdef CONFIG_PMP_POWER_OF_TWO_ALIGNMENT |
| stack_guard_addr = thread->arch.priv_stack_start; |
| #else |
| stack_guard_addr = (ulong_t) thread->stack_obj; |
| #endif /* CONFIG_PMP_POWER_OF_TWO_ALIGNMENT */ |
| dynamic_regions[region_num].start = stack_guard_addr; |
| dynamic_regions[region_num].size = PMP_GUARD_ALIGN_AND_SIZE; |
| dynamic_regions[region_num].perm = 0; |
| dynamic_regions[region_num].mode = PMP_MODE_TOR; |
| region_num++; |
| } |
| #endif /* CONFIG_USERSPACE */ |
| |
| /* RAM: RW */ |
| dynamic_regions[region_num].start = CONFIG_SRAM_BASE_ADDRESS; |
| dynamic_regions[region_num].size = KB(CONFIG_SRAM_SIZE); |
| dynamic_regions[region_num].perm = PMP_R | PMP_W; |
| dynamic_regions[region_num].mode = PMP_MODE_NAPOT; |
| region_num++; |
| |
| uint8_t index = 0; |
| |
| riscv_pmp_regions_translate(index, dynamic_regions, region_num, |
| thread->arch.s_pmpcfg, thread->arch.s_pmpaddr); |
| } |
| |
| void z_riscv_configure_stack_guard(struct k_thread *thread) |
| { |
| uint8_t min_index = 0; |
| uint8_t max_index = min_index + PMP_REGION_NUM_FOR_STACK_GUARD - 1; |
| uint8_t mprv_index = get_num_regions() - 1; |
| |
| /* Disable PMP for machine mode */ |
| csr_clear(mstatus, MSTATUS_MPRV); |
| |
| z_riscv_pmp_clear_config(); |
| |
| /* Set all dynamic PMP regions (from min_index to max_index). */ |
| for (uint8_t i = min_index; i <= max_index; i++) |
| csr_write_enum(CSR_PMPADDR0 + i, thread->arch.s_pmpaddr[i]); |
| |
| riscv_pmpcfg_set_range(min_index, max_index, (uint8_t *)thread->arch.s_pmpcfg); |
| |
| /* Set MPRV region for consistently using mstatus.MPRV in M-mode */ |
| riscv_pmp_region_set(mprv_index, &mprv_region); |
| |
| /* Enable PMP for machine mode */ |
| csr_set(mstatus, MSTATUS_MPRV); |
| } |
| |
| #endif /* CONFIG_PMP_STACK_GUARD */ |
| |
| #if defined(CONFIG_PMP_STACK_GUARD) || defined(CONFIG_USERSPACE) |
| |
| void z_riscv_pmp_init_thread(struct k_thread *thread) |
| { |
| /* Clear [u|s]_pmp[cfg|addr] field of k_thread */ |
| unsigned char i; |
| ulong_t *pmpcfg; |
| |
| #if defined(CONFIG_PMP_STACK_GUARD) |
| pmpcfg = thread->arch.s_pmpcfg; |
| for (i = 0U; i < PMP_CFG_CSR_NUM_FOR_STACK_GUARD; i++) |
| pmpcfg[i] = 0; |
| #endif /* CONFIG_PMP_STACK_GUARD */ |
| |
| #if defined(CONFIG_USERSPACE) |
| pmpcfg = thread->arch.u_pmpcfg; |
| for (i = 0U; i < RISCV_PMP_CFG_NUM; i++) |
| pmpcfg[i] = 0; |
| #endif /* CONFIG_USERSPACE */ |
| } |
| #endif /* CONFIG_PMP_STACK_GUARD || CONFIG_USERSPACE */ |
| |
| void z_riscv_configure_static_pmp_regions(void) |
| { |
| #ifdef CONFIG_PMP_STACK_GUARD |
| mprv_regions_num = 1; |
| #endif |
| |
| /* Max dynamic PMP entry is also next free static PMP entry (from last). */ |
| int index = max_dynamic_region(); |
| int new_index = riscv_pmp_regions_set_from_last(index, |
| static_regions, ARRAY_SIZE(static_regions)); |
| |
| static_regions_num += index - new_index; |
| } |