| /* Copyright 2018 SiFive, Inc */ |
| /* SPDX-License-Identifier: Apache-2.0 */ |
| |
| #include <metal/machine.h> |
| #include <metal/pmp.h> |
| #include <metal/cpu.h> |
| |
| #define CONFIG_TO_INT(_config) (*((char *) &(_config))) |
| #define INT_TO_CONFIG(_int) (*((struct metal_pmp_config *)(char *) &(_int))) |
| |
| struct metal_pmp *metal_pmp_get_device(void) |
| { |
| #ifdef __METAL_DT_PMP_HANDLE |
| return __METAL_DT_PMP_HANDLE; |
| #else |
| return NULL; |
| #endif |
| } |
| |
| /* This function calculates the minimum granularity from the address |
| * that pmpaddr takes on after writing all ones to pmpaddr when pmpcfg = 0. |
| * |
| * Detect the address granularity based on the position of the |
| * least-significant 1 set in the address. |
| * |
| * For example, if the value read from pmpaddr is 0x3ffffc00, the |
| * least-significant set bit is in bit 10 (counting from 0), resulting |
| * in a detected granularity of 2^(10 + 2) = 4096. |
| */ |
| static uintptr_t _get_detected_granularity(uintptr_t address) { |
| if(address == 0) { |
| return (uintptr_t) -1; |
| } |
| |
| /* Get the index of the least significant set bit */ |
| int index = 0; |
| while(((address >> index) & 0x1) == 0) { |
| index += 1; |
| } |
| |
| /* The granularity is equal to 2^(index + 2) bytes */ |
| return (1 << (index + 2)); |
| } |
| |
| /* This function calculates the granularity requested by the user's provided |
| * value for pmpaddr. |
| * |
| * Calculate the requested granularity based on the position of the |
| * least-significant unset bit. |
| * |
| * For example, if the requested address is 0x20009ff, the least-significant |
| * unset bit is at index 9 (counting from 0), resulting in a requested |
| * granularity of 2^(9 + 3) = 4096. |
| */ |
| static uintptr_t _get_pmpaddr_granularity(uintptr_t address) { |
| /* Get the index of the least significant unset bit */ |
| int index = 0; |
| while(((address >> index) & 0x1) == 1) { |
| index += 1; |
| } |
| |
| /* The granularity is equal to 2^(index + 3) bytes */ |
| return (1 << (index + 3)); |
| } |
| |
| /* Get the number of pmp regions for the given hart */ |
| int metal_pmp_num_regions(int hartid) |
| { |
| struct metal_cpu *cpu = metal_cpu_get(hartid); |
| |
| return __metal_driver_cpu_num_pmp_regions(cpu); |
| } |
| |
| /* Get the number of pmp regions for the current hart */ |
| static unsigned int _pmp_regions() { |
| return metal_pmp_num_regions(metal_cpu_get_current_hartid()); |
| } |
| |
| void metal_pmp_init(struct metal_pmp *pmp) { |
| if(!pmp) { |
| return; |
| } |
| |
| struct metal_pmp_config init_config = { |
| .L = METAL_PMP_UNLOCKED, |
| .A = METAL_PMP_OFF, |
| .X = 0, |
| .W = 0, |
| .R = 0, |
| }; |
| |
| for(unsigned int i = 0; i < _pmp_regions(); i++) { |
| metal_pmp_set_region(pmp, i, init_config, 0); |
| } |
| |
| /* Detect the region granularity by writing all 1s to pmpaddr0 while |
| * pmpcfg0 = 0. */ |
| if(metal_pmp_set_address(pmp, 0, -1) != 0) { |
| /* Failed to detect granularity */ |
| return; |
| } |
| |
| /* Calculate the granularity based on the value that pmpaddr0 takes on */ |
| pmp->_granularity[metal_cpu_get_current_hartid()] = _get_detected_granularity(metal_pmp_get_address(pmp, 0)); |
| |
| /* Clear pmpaddr0 */ |
| metal_pmp_set_address(pmp, 0, 0); |
| } |
| |
| int metal_pmp_set_region(struct metal_pmp *pmp, |
| unsigned int region, |
| struct metal_pmp_config config, |
| size_t address) |
| { |
| struct metal_pmp_config old_config; |
| size_t old_address; |
| size_t cfgmask; |
| size_t pmpcfg; |
| int rc = 0; |
| |
| if(!pmp) { |
| /* Device handle cannot be NULL */ |
| return 1; |
| } |
| |
| if(region > _pmp_regions()) { |
| /* Region outside of supported range */ |
| return 2; |
| } |
| |
| if(config.A == METAL_PMP_NA4 && pmp->_granularity[metal_cpu_get_current_hartid()] > 4) { |
| /* The requested granularity is too small */ |
| return 3; |
| } |
| |
| if(config.A == METAL_PMP_NAPOT && |
| pmp->_granularity[metal_cpu_get_current_hartid()] > _get_pmpaddr_granularity(address)) |
| { |
| /* The requested granularity is too small */ |
| return 3; |
| } |
| |
| rc = metal_pmp_get_region(pmp, region, &old_config, &old_address); |
| if(rc) { |
| /* Error reading region */ |
| return rc; |
| } |
| |
| if(old_config.L == METAL_PMP_LOCKED) { |
| /* Cannot modify locked region */ |
| return 4; |
| } |
| |
| /* Update the address first, because if the region is being locked we won't |
| * be able to modify it after we set the config */ |
| if(old_address != address) { |
| switch(region) { |
| case 0: |
| __asm__("csrw pmpaddr0, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 1: |
| __asm__("csrw pmpaddr1, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 2: |
| __asm__("csrw pmpaddr2, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 3: |
| __asm__("csrw pmpaddr3, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 4: |
| __asm__("csrw pmpaddr4, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 5: |
| __asm__("csrw pmpaddr5, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 6: |
| __asm__("csrw pmpaddr6, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 7: |
| __asm__("csrw pmpaddr7, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 8: |
| __asm__("csrw pmpaddr8, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 9: |
| __asm__("csrw pmpaddr9, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 10: |
| __asm__("csrw pmpaddr10, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 11: |
| __asm__("csrw pmpaddr11, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 12: |
| __asm__("csrw pmpaddr12, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 13: |
| __asm__("csrw pmpaddr13, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 14: |
| __asm__("csrw pmpaddr14, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| case 15: |
| __asm__("csrw pmpaddr15, %[addr]" |
| :: [addr] "r" (address) :); |
| break; |
| } |
| } |
| |
| #if __riscv_xlen==32 |
| if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) { |
| /* Mask to clear old pmpcfg */ |
| cfgmask = (0xFF << (8 * (region % 4)) ); |
| pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 4)) ); |
| |
| switch(region / 4) { |
| case 0: |
| __asm__("csrc pmpcfg0, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg0, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| case 1: |
| __asm__("csrc pmpcfg1, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg1, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| case 2: |
| __asm__("csrc pmpcfg2, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg2, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| case 3: |
| __asm__("csrc pmpcfg3, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg3, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| } |
| } |
| #elif __riscv_xlen==64 |
| if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) { |
| /* Mask to clear old pmpcfg */ |
| cfgmask = (0xFF << (8 * (region % 8)) ); |
| pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 8)) ); |
| |
| switch(region / 8) { |
| case 0: |
| __asm__("csrc pmpcfg0, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg0, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| case 1: |
| __asm__("csrc pmpcfg2, %[mask]" |
| :: [mask] "r" (cfgmask) :); |
| |
| __asm__("csrs pmpcfg2, %[cfg]" |
| :: [cfg] "r" (pmpcfg) :); |
| break; |
| } |
| } |
| #else |
| #error XLEN is not set to supported value for PMP driver |
| #endif |
| |
| return 0; |
| } |
| |
| int metal_pmp_get_region(struct metal_pmp *pmp, |
| unsigned int region, |
| struct metal_pmp_config *config, |
| size_t *address) |
| { |
| size_t pmpcfg = 0; |
| char *pmpcfg_convert = (char *)&pmpcfg; |
| |
| if(!pmp || !config || !address) { |
| /* NULL pointers are invalid arguments */ |
| return 1; |
| } |
| |
| if(region > _pmp_regions()) { |
| /* Region outside of supported range */ |
| return 2; |
| } |
| |
| #if __riscv_xlen==32 |
| switch(region / 4) { |
| case 0: |
| __asm__("csrr %[cfg], pmpcfg0" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| case 1: |
| __asm__("csrr %[cfg], pmpcfg1" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| case 2: |
| __asm__("csrr %[cfg], pmpcfg2" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| case 3: |
| __asm__("csrr %[cfg], pmpcfg3" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| } |
| |
| pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 4)) ) ); |
| |
| #elif __riscv_xlen==64 |
| switch(region / 8) { |
| case 0: |
| __asm__("csrr %[cfg], pmpcfg0" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| case 1: |
| __asm__("csrr %[cfg], pmpcfg2" |
| : [cfg] "=r" (pmpcfg) ::); |
| break; |
| } |
| |
| pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 8)) ) ); |
| |
| #else |
| #error XLEN is not set to supported value for PMP driver |
| #endif |
| |
| *config = INT_TO_CONFIG(*pmpcfg_convert); |
| |
| switch(region) { |
| case 0: |
| __asm__("csrr %[addr], pmpaddr0" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 1: |
| __asm__("csrr %[addr], pmpaddr1" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 2: |
| __asm__("csrr %[addr], pmpaddr2" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 3: |
| __asm__("csrr %[addr], pmpaddr3" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 4: |
| __asm__("csrr %[addr], pmpaddr4" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 5: |
| __asm__("csrr %[addr], pmpaddr5" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 6: |
| __asm__("csrr %[addr], pmpaddr6" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 7: |
| __asm__("csrr %[addr], pmpaddr7" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 8: |
| __asm__("csrr %[addr], pmpaddr8" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 9: |
| __asm__("csrr %[addr], pmpaddr9" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 10: |
| __asm__("csrr %[addr], pmpaddr10" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 11: |
| __asm__("csrr %[addr], pmpaddr11" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 12: |
| __asm__("csrr %[addr], pmpaddr12" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 13: |
| __asm__("csrr %[addr], pmpaddr13" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 14: |
| __asm__("csrr %[addr], pmpaddr14" |
| : [addr] "=r" (*address) ::); |
| break; |
| case 15: |
| __asm__("csrr %[addr], pmpaddr15" |
| : [addr] "=r" (*address) ::); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| int metal_pmp_lock(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &address); |
| if(rc) { |
| return rc; |
| } |
| |
| if(config.L == METAL_PMP_LOCKED) { |
| return 0; |
| } |
| |
| config.L = METAL_PMP_LOCKED; |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| |
| int metal_pmp_set_address(struct metal_pmp *pmp, unsigned int region, size_t address) |
| { |
| struct metal_pmp_config config; |
| size_t old_address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &old_address); |
| if(rc) { |
| return rc; |
| } |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| size_t metal_pmp_get_address(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address = 0; |
| |
| metal_pmp_get_region(pmp, region, &config, &address); |
| |
| return address; |
| } |
| |
| |
| int metal_pmp_set_address_mode(struct metal_pmp *pmp, unsigned int region, enum metal_pmp_address_mode mode) |
| { |
| struct metal_pmp_config config; |
| size_t address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &address); |
| if(rc) { |
| return rc; |
| } |
| |
| config.A = mode; |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| enum metal_pmp_address_mode metal_pmp_get_address_mode(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address = 0; |
| |
| metal_pmp_get_region(pmp, region, &config, &address); |
| |
| return config.A; |
| } |
| |
| |
| int metal_pmp_set_executable(struct metal_pmp *pmp, unsigned int region, int X) |
| { |
| struct metal_pmp_config config; |
| size_t address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &address); |
| if(rc) { |
| return rc; |
| } |
| |
| config.X = X; |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| int metal_pmp_get_executable(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address = 0; |
| |
| metal_pmp_get_region(pmp, region, &config, &address); |
| |
| return config.X; |
| } |
| |
| |
| int metal_pmp_set_writeable(struct metal_pmp *pmp, unsigned int region, int W) |
| { |
| struct metal_pmp_config config; |
| size_t address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &address); |
| if(rc) { |
| return rc; |
| } |
| |
| config.W = W; |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| int metal_pmp_get_writeable(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address = 0; |
| |
| metal_pmp_get_region(pmp, region, &config, &address); |
| |
| return config.W; |
| } |
| |
| |
| int metal_pmp_set_readable(struct metal_pmp *pmp, unsigned int region, int R) |
| { |
| struct metal_pmp_config config; |
| size_t address; |
| int rc = 0; |
| |
| rc = metal_pmp_get_region(pmp, region, &config, &address); |
| if(rc) { |
| return rc; |
| } |
| |
| config.R = R; |
| |
| rc = metal_pmp_set_region(pmp, region, config, address); |
| |
| return rc; |
| } |
| |
| int metal_pmp_get_readable(struct metal_pmp *pmp, unsigned int region) |
| { |
| struct metal_pmp_config config; |
| size_t address = 0; |
| |
| metal_pmp_get_region(pmp, region, &config, &address); |
| |
| return config.R; |
| } |
| |