1 /* Copyright 2018 SiFive, Inc */
2 /* SPDX-License-Identifier: Apache-2.0 */
4 #include <metal/machine.h>
8 #define CONFIG_TO_INT(_config) (*((size_t *) &(_config)))
9 #define INT_TO_CONFIG(_int) (*((struct metal_pmp_config *) &(_int)))
11 struct metal_pmp *metal_pmp_get_device(void)
13 #ifdef __METAL_DT_PMP_HANDLE
14 return __METAL_DT_PMP_HANDLE;
20 /* This function calculates the minimum granularity from the address
21 * that pmpaddr takes on after writing all ones to pmpaddr when pmpcfg = 0.
23 * Detect the address granularity based on the position of the
24 * least-significant 1 set in the address.
26 * For example, if the value read from pmpaddr is 0x3ffffc00, the
27 * least-significant set bit is in bit 10 (counting from 0), resulting
28 * in a detected granularity of 2^(10 + 2) = 4096.
30 static uintptr_t _get_detected_granularity(uintptr_t address) {
32 return (uintptr_t) -1;
35 /* Get the index of the least significant set bit */
37 while(((address >> index) & 0x1) == 0) {
41 /* The granularity is equal to 2^(index + 2) bytes */
42 return (1 << (index + 2));
45 /* This function calculates the granularity requested by the user's provided
48 * Calculate the requested granularity based on the position of the
49 * least-significant unset bit.
51 * For example, if the requested address is 0x20009ff, the least-significant
52 * unset bit is at index 9 (counting from 0), resulting in a requested
53 * granularity of 2^(9 + 3) = 4096.
55 static uintptr_t _get_pmpaddr_granularity(uintptr_t address) {
56 /* Get the index of the least significant unset bit */
58 while(((address >> index) & 0x1) == 1) {
62 /* The granularity is equal to 2^(index + 3) bytes */
63 return (1 << (index + 3));
66 /* Get the number of pmp regions for the current hart */
67 static int _pmp_regions() {
68 struct metal_cpu *current_cpu = metal_cpu_get(metal_cpu_get_current_hartid());
70 return __metal_driver_cpu_num_pmp_regions(current_cpu);
74 void metal_pmp_init(struct metal_pmp *pmp) {
79 struct metal_pmp_config init_config = {
80 .L = METAL_PMP_UNLOCKED,
87 for(unsigned int i = 0; i < _pmp_regions(); i++) {
88 metal_pmp_set_region(pmp, i, init_config, 0);
91 /* Detect the region granularity by writing all 1s to pmpaddr0 while
93 if(metal_pmp_set_address(pmp, 0, -1) != 0) {
94 /* Failed to detect granularity */
98 /* Calculate the granularity based on the value that pmpaddr0 takes on */
99 pmp->_granularity[metal_cpu_get_current_hartid()] = _get_detected_granularity(metal_pmp_get_address(pmp, 0));
102 metal_pmp_set_address(pmp, 0, 0);
105 int metal_pmp_set_region(struct metal_pmp *pmp,
107 struct metal_pmp_config config,
110 struct metal_pmp_config old_config;
117 /* Device handle cannot be NULL */
121 if(region > _pmp_regions()) {
122 /* Region outside of supported range */
126 if(config.A == METAL_PMP_NA4 && pmp->_granularity[metal_cpu_get_current_hartid()] > 4) {
127 /* The requested granularity is too small */
131 if(config.A == METAL_PMP_NAPOT &&
132 pmp->_granularity[metal_cpu_get_current_hartid()] > _get_pmpaddr_granularity(address))
134 /* The requested granularity is too small */
138 rc = metal_pmp_get_region(pmp, region, &old_config, &old_address);
140 /* Error reading region */
144 if(old_config.L == METAL_PMP_LOCKED) {
145 /* Cannot modify locked region */
149 /* Update the address first, because if the region is being locked we won't
150 * be able to modify it after we set the config */
151 if(old_address != address) {
154 asm("csrw pmpaddr0, %[addr]"
155 :: [addr] "r" (address) :);
158 asm("csrw pmpaddr1, %[addr]"
159 :: [addr] "r" (address) :);
162 asm("csrw pmpaddr2, %[addr]"
163 :: [addr] "r" (address) :);
166 asm("csrw pmpaddr3, %[addr]"
167 :: [addr] "r" (address) :);
170 asm("csrw pmpaddr4, %[addr]"
171 :: [addr] "r" (address) :);
174 asm("csrw pmpaddr5, %[addr]"
175 :: [addr] "r" (address) :);
178 asm("csrw pmpaddr6, %[addr]"
179 :: [addr] "r" (address) :);
182 asm("csrw pmpaddr7, %[addr]"
183 :: [addr] "r" (address) :);
186 asm("csrw pmpaddr8, %[addr]"
187 :: [addr] "r" (address) :);
190 asm("csrw pmpaddr9, %[addr]"
191 :: [addr] "r" (address) :);
194 asm("csrw pmpaddr10, %[addr]"
195 :: [addr] "r" (address) :);
198 asm("csrw pmpaddr11, %[addr]"
199 :: [addr] "r" (address) :);
202 asm("csrw pmpaddr12, %[addr]"
203 :: [addr] "r" (address) :);
206 asm("csrw pmpaddr13, %[addr]"
207 :: [addr] "r" (address) :);
210 asm("csrw pmpaddr14, %[addr]"
211 :: [addr] "r" (address) :);
214 asm("csrw pmpaddr15, %[addr]"
215 :: [addr] "r" (address) :);
221 if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
222 /* Mask to clear old pmpcfg */
223 cfgmask = (0xFF << (8 * (region % 4)) );
224 pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 4)) );
228 asm("csrc pmpcfg0, %[mask]"
229 :: [mask] "r" (cfgmask) :);
231 asm("csrs pmpcfg0, %[cfg]"
232 :: [cfg] "r" (pmpcfg) :);
235 asm("csrc pmpcfg1, %[mask]"
236 :: [mask] "r" (cfgmask) :);
238 asm("csrs pmpcfg1, %[cfg]"
239 :: [cfg] "r" (pmpcfg) :);
242 asm("csrc pmpcfg2, %[mask]"
243 :: [mask] "r" (cfgmask) :);
245 asm("csrs pmpcfg2, %[cfg]"
246 :: [cfg] "r" (pmpcfg) :);
249 asm("csrc pmpcfg3, %[mask]"
250 :: [mask] "r" (cfgmask) :);
252 asm("csrs pmpcfg3, %[cfg]"
253 :: [cfg] "r" (pmpcfg) :);
257 #elif __riscv_xlen==64
258 if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
259 /* Mask to clear old pmpcfg */
260 cfgmask = (0xFF << (8 * (region % 8)) );
261 pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 8)) );
265 asm("csrc pmpcfg0, %[mask]"
266 :: [mask] "r" (cfgmask) :);
268 asm("csrs pmpcfg0, %[cfg]"
269 :: [cfg] "r" (pmpcfg) :);
272 asm("csrc pmpcfg2, %[mask]"
273 :: [mask] "r" (cfgmask) :);
275 asm("csrs pmpcfg2, %[cfg]"
276 :: [cfg] "r" (pmpcfg) :);
281 #error XLEN is not set to supported value for PMP driver
287 int metal_pmp_get_region(struct metal_pmp *pmp,
289 struct metal_pmp_config *config,
294 if(!pmp || !config || !address) {
295 /* NULL pointers are invalid arguments */
299 if(region > _pmp_regions()) {
300 /* Region outside of supported range */
307 asm("csrr %[cfg], pmpcfg0"
308 : [cfg] "=r" (pmpcfg) ::);
311 asm("csrr %[cfg], pmpcfg1"
312 : [cfg] "=r" (pmpcfg) ::);
315 asm("csrr %[cfg], pmpcfg2"
316 : [cfg] "=r" (pmpcfg) ::);
319 asm("csrr %[cfg], pmpcfg3"
320 : [cfg] "=r" (pmpcfg) ::);
324 pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 4)) ) );
326 #elif __riscv_xlen==64
329 asm("csrr %[cfg], pmpcfg0"
330 : [cfg] "=r" (pmpcfg) ::);
333 asm("csrr %[cfg], pmpcfg2"
334 : [cfg] "=r" (pmpcfg) ::);
338 pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 8)) ) );
341 #error XLEN is not set to supported value for PMP driver
344 *config = INT_TO_CONFIG(pmpcfg);
348 asm("csrr %[addr], pmpaddr0"
349 : [addr] "=r" (*address) ::);
352 asm("csrr %[addr], pmpaddr1"
353 : [addr] "=r" (*address) ::);
356 asm("csrr %[addr], pmpaddr2"
357 : [addr] "=r" (*address) ::);
360 asm("csrr %[addr], pmpaddr3"
361 : [addr] "=r" (*address) ::);
364 asm("csrr %[addr], pmpaddr4"
365 : [addr] "=r" (*address) ::);
368 asm("csrr %[addr], pmpaddr5"
369 : [addr] "=r" (*address) ::);
372 asm("csrr %[addr], pmpaddr6"
373 : [addr] "=r" (*address) ::);
376 asm("csrr %[addr], pmpaddr7"
377 : [addr] "=r" (*address) ::);
380 asm("csrr %[addr], pmpaddr8"
381 : [addr] "=r" (*address) ::);
384 asm("csrr %[addr], pmpaddr9"
385 : [addr] "=r" (*address) ::);
388 asm("csrr %[addr], pmpaddr10"
389 : [addr] "=r" (*address) ::);
392 asm("csrr %[addr], pmpaddr11"
393 : [addr] "=r" (*address) ::);
396 asm("csrr %[addr], pmpaddr12"
397 : [addr] "=r" (*address) ::);
400 asm("csrr %[addr], pmpaddr13"
401 : [addr] "=r" (*address) ::);
404 asm("csrr %[addr], pmpaddr14"
405 : [addr] "=r" (*address) ::);
408 asm("csrr %[addr], pmpaddr15"
409 : [addr] "=r" (*address) ::);
416 int metal_pmp_lock(struct metal_pmp *pmp, unsigned int region)
418 struct metal_pmp_config config;
422 rc = metal_pmp_get_region(pmp, region, &config, &address);
427 if(config.L == METAL_PMP_LOCKED) {
431 config.L = METAL_PMP_LOCKED;
433 rc = metal_pmp_set_region(pmp, region, config, address);
439 int metal_pmp_set_address(struct metal_pmp *pmp, unsigned int region, size_t address)
441 struct metal_pmp_config config;
445 rc = metal_pmp_get_region(pmp, region, &config, &old_address);
450 rc = metal_pmp_set_region(pmp, region, config, address);
455 size_t metal_pmp_get_address(struct metal_pmp *pmp, unsigned int region)
457 struct metal_pmp_config config;
460 metal_pmp_get_region(pmp, region, &config, &address);
466 int metal_pmp_set_address_mode(struct metal_pmp *pmp, unsigned int region, enum metal_pmp_address_mode mode)
468 struct metal_pmp_config config;
472 rc = metal_pmp_get_region(pmp, region, &config, &address);
479 rc = metal_pmp_set_region(pmp, region, config, address);
484 enum metal_pmp_address_mode metal_pmp_get_address_mode(struct metal_pmp *pmp, unsigned int region)
486 struct metal_pmp_config config;
489 metal_pmp_get_region(pmp, region, &config, &address);
495 int metal_pmp_set_executable(struct metal_pmp *pmp, unsigned int region, int X)
497 struct metal_pmp_config config;
501 rc = metal_pmp_get_region(pmp, region, &config, &address);
508 rc = metal_pmp_set_region(pmp, region, config, address);
513 int metal_pmp_get_executable(struct metal_pmp *pmp, unsigned int region)
515 struct metal_pmp_config config;
518 metal_pmp_get_region(pmp, region, &config, &address);
524 int metal_pmp_set_writeable(struct metal_pmp *pmp, unsigned int region, int W)
526 struct metal_pmp_config config;
530 rc = metal_pmp_get_region(pmp, region, &config, &address);
537 rc = metal_pmp_set_region(pmp, region, config, address);
542 int metal_pmp_get_writeable(struct metal_pmp *pmp, unsigned int region)
544 struct metal_pmp_config config;
547 metal_pmp_get_region(pmp, region, &config, &address);
553 int metal_pmp_set_readable(struct metal_pmp *pmp, unsigned int region, int R)
555 struct metal_pmp_config config;
559 rc = metal_pmp_get_region(pmp, region, &config, &address);
566 rc = metal_pmp_set_region(pmp, region, config, address);
571 int metal_pmp_get_readable(struct metal_pmp *pmp, unsigned int region)
573 struct metal_pmp_config config;
576 metal_pmp_get_region(pmp, region, &config, &address);