summaryrefslogtreecommitdiff
path: root/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c
diff options
context:
space:
mode:
Diffstat (limited to 'FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c')
-rw-r--r--FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c b/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c
new file mode 100644
index 000000000..ab78cdeba
--- /dev/null
+++ b/FreeRTOS/Demo/RISC-V_RV32_SiFive_HiFive1_FreedomStudio/freedom-metal/src/pmp.c
@@ -0,0 +1,580 @@
+/* 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) (*((size_t *) &(_config)))
+#define INT_TO_CONFIG(_int) (*((struct metal_pmp_config *) &(_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 current hart */
+static int _pmp_regions() {
+ struct metal_cpu *current_cpu = metal_cpu_get(metal_cpu_get_current_hartid());
+
+ return __metal_driver_cpu_num_pmp_regions(current_cpu);
+}
+
+
+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;
+
+ 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);
+
+ 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;
+}
+