123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586 |
- /* 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;
- }
|