1 /* Copyright 2018 SiFive, Inc */
2 /* SPDX-License-Identifier: Apache-2.0 */
4 #include <metal/machine.h>
8 #define CONFIG_TO_INT(_config) (*((char *) &(_config)))
9 #define INT_TO_CONFIG(_int) (*((struct metal_pmp_config *)(char *) &(_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 given hart */
67 int metal_pmp_num_regions(int hartid)
69 struct metal_cpu *cpu = metal_cpu_get(hartid);
71 return __metal_driver_cpu_num_pmp_regions(cpu);
74 /* Get the number of pmp regions for the current hart */
75 static unsigned int _pmp_regions() {
76 return metal_pmp_num_regions(metal_cpu_get_current_hartid());
79 void metal_pmp_init(struct metal_pmp *pmp) {
84 struct metal_pmp_config init_config = {
85 .L = METAL_PMP_UNLOCKED,
92 for(unsigned int i = 0; i < _pmp_regions(); i++) {
93 metal_pmp_set_region(pmp, i, init_config, 0);
96 /* Detect the region granularity by writing all 1s to pmpaddr0 while
98 if(metal_pmp_set_address(pmp, 0, -1) != 0) {
99 /* Failed to detect granularity */
103 /* Calculate the granularity based on the value that pmpaddr0 takes on */
104 pmp->_granularity[metal_cpu_get_current_hartid()] = _get_detected_granularity(metal_pmp_get_address(pmp, 0));
107 metal_pmp_set_address(pmp, 0, 0);
110 int metal_pmp_set_region(struct metal_pmp *pmp,
112 struct metal_pmp_config config,
115 struct metal_pmp_config old_config;
122 /* Device handle cannot be NULL */
126 if(region > _pmp_regions()) {
127 /* Region outside of supported range */
131 if(config.A == METAL_PMP_NA4 && pmp->_granularity[metal_cpu_get_current_hartid()] > 4) {
132 /* The requested granularity is too small */
136 if(config.A == METAL_PMP_NAPOT &&
137 pmp->_granularity[metal_cpu_get_current_hartid()] > _get_pmpaddr_granularity(address))
139 /* The requested granularity is too small */
143 rc = metal_pmp_get_region(pmp, region, &old_config, &old_address);
145 /* Error reading region */
149 if(old_config.L == METAL_PMP_LOCKED) {
150 /* Cannot modify locked region */
154 /* Update the address first, because if the region is being locked we won't
155 * be able to modify it after we set the config */
156 if(old_address != address) {
159 __asm__("csrw pmpaddr0, %[addr]"
160 :: [addr] "r" (address) :);
163 __asm__("csrw pmpaddr1, %[addr]"
164 :: [addr] "r" (address) :);
167 __asm__("csrw pmpaddr2, %[addr]"
168 :: [addr] "r" (address) :);
171 __asm__("csrw pmpaddr3, %[addr]"
172 :: [addr] "r" (address) :);
175 __asm__("csrw pmpaddr4, %[addr]"
176 :: [addr] "r" (address) :);
179 __asm__("csrw pmpaddr5, %[addr]"
180 :: [addr] "r" (address) :);
183 __asm__("csrw pmpaddr6, %[addr]"
184 :: [addr] "r" (address) :);
187 __asm__("csrw pmpaddr7, %[addr]"
188 :: [addr] "r" (address) :);
191 __asm__("csrw pmpaddr8, %[addr]"
192 :: [addr] "r" (address) :);
195 __asm__("csrw pmpaddr9, %[addr]"
196 :: [addr] "r" (address) :);
199 __asm__("csrw pmpaddr10, %[addr]"
200 :: [addr] "r" (address) :);
203 __asm__("csrw pmpaddr11, %[addr]"
204 :: [addr] "r" (address) :);
207 __asm__("csrw pmpaddr12, %[addr]"
208 :: [addr] "r" (address) :);
211 __asm__("csrw pmpaddr13, %[addr]"
212 :: [addr] "r" (address) :);
215 __asm__("csrw pmpaddr14, %[addr]"
216 :: [addr] "r" (address) :);
219 __asm__("csrw pmpaddr15, %[addr]"
220 :: [addr] "r" (address) :);
226 if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
227 /* Mask to clear old pmpcfg */
228 cfgmask = (0xFF << (8 * (region % 4)) );
229 pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 4)) );
233 __asm__("csrc pmpcfg0, %[mask]"
234 :: [mask] "r" (cfgmask) :);
236 __asm__("csrs pmpcfg0, %[cfg]"
237 :: [cfg] "r" (pmpcfg) :);
240 __asm__("csrc pmpcfg1, %[mask]"
241 :: [mask] "r" (cfgmask) :);
243 __asm__("csrs pmpcfg1, %[cfg]"
244 :: [cfg] "r" (pmpcfg) :);
247 __asm__("csrc pmpcfg2, %[mask]"
248 :: [mask] "r" (cfgmask) :);
250 __asm__("csrs pmpcfg2, %[cfg]"
251 :: [cfg] "r" (pmpcfg) :);
254 __asm__("csrc pmpcfg3, %[mask]"
255 :: [mask] "r" (cfgmask) :);
257 __asm__("csrs pmpcfg3, %[cfg]"
258 :: [cfg] "r" (pmpcfg) :);
262 #elif __riscv_xlen==64
263 if(CONFIG_TO_INT(old_config) != CONFIG_TO_INT(config)) {
264 /* Mask to clear old pmpcfg */
265 cfgmask = (0xFF << (8 * (region % 8)) );
266 pmpcfg = (CONFIG_TO_INT(config) << (8 * (region % 8)) );
270 __asm__("csrc pmpcfg0, %[mask]"
271 :: [mask] "r" (cfgmask) :);
273 __asm__("csrs pmpcfg0, %[cfg]"
274 :: [cfg] "r" (pmpcfg) :);
277 __asm__("csrc pmpcfg2, %[mask]"
278 :: [mask] "r" (cfgmask) :);
280 __asm__("csrs pmpcfg2, %[cfg]"
281 :: [cfg] "r" (pmpcfg) :);
286 #error XLEN is not set to supported value for PMP driver
292 int metal_pmp_get_region(struct metal_pmp *pmp,
294 struct metal_pmp_config *config,
298 char *pmpcfg_convert = (char *)&pmpcfg;
300 if(!pmp || !config || !address) {
301 /* NULL pointers are invalid arguments */
305 if(region > _pmp_regions()) {
306 /* Region outside of supported range */
313 __asm__("csrr %[cfg], pmpcfg0"
314 : [cfg] "=r" (pmpcfg) ::);
317 __asm__("csrr %[cfg], pmpcfg1"
318 : [cfg] "=r" (pmpcfg) ::);
321 __asm__("csrr %[cfg], pmpcfg2"
322 : [cfg] "=r" (pmpcfg) ::);
325 __asm__("csrr %[cfg], pmpcfg3"
326 : [cfg] "=r" (pmpcfg) ::);
330 pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 4)) ) );
332 #elif __riscv_xlen==64
335 __asm__("csrr %[cfg], pmpcfg0"
336 : [cfg] "=r" (pmpcfg) ::);
339 __asm__("csrr %[cfg], pmpcfg2"
340 : [cfg] "=r" (pmpcfg) ::);
344 pmpcfg = (0xFF & (pmpcfg >> (8 * (region % 8)) ) );
347 #error XLEN is not set to supported value for PMP driver
350 *config = INT_TO_CONFIG(*pmpcfg_convert);
354 __asm__("csrr %[addr], pmpaddr0"
355 : [addr] "=r" (*address) ::);
358 __asm__("csrr %[addr], pmpaddr1"
359 : [addr] "=r" (*address) ::);
362 __asm__("csrr %[addr], pmpaddr2"
363 : [addr] "=r" (*address) ::);
366 __asm__("csrr %[addr], pmpaddr3"
367 : [addr] "=r" (*address) ::);
370 __asm__("csrr %[addr], pmpaddr4"
371 : [addr] "=r" (*address) ::);
374 __asm__("csrr %[addr], pmpaddr5"
375 : [addr] "=r" (*address) ::);
378 __asm__("csrr %[addr], pmpaddr6"
379 : [addr] "=r" (*address) ::);
382 __asm__("csrr %[addr], pmpaddr7"
383 : [addr] "=r" (*address) ::);
386 __asm__("csrr %[addr], pmpaddr8"
387 : [addr] "=r" (*address) ::);
390 __asm__("csrr %[addr], pmpaddr9"
391 : [addr] "=r" (*address) ::);
394 __asm__("csrr %[addr], pmpaddr10"
395 : [addr] "=r" (*address) ::);
398 __asm__("csrr %[addr], pmpaddr11"
399 : [addr] "=r" (*address) ::);
402 __asm__("csrr %[addr], pmpaddr12"
403 : [addr] "=r" (*address) ::);
406 __asm__("csrr %[addr], pmpaddr13"
407 : [addr] "=r" (*address) ::);
410 __asm__("csrr %[addr], pmpaddr14"
411 : [addr] "=r" (*address) ::);
414 __asm__("csrr %[addr], pmpaddr15"
415 : [addr] "=r" (*address) ::);
422 int metal_pmp_lock(struct metal_pmp *pmp, unsigned int region)
424 struct metal_pmp_config config;
428 rc = metal_pmp_get_region(pmp, region, &config, &address);
433 if(config.L == METAL_PMP_LOCKED) {
437 config.L = METAL_PMP_LOCKED;
439 rc = metal_pmp_set_region(pmp, region, config, address);
445 int metal_pmp_set_address(struct metal_pmp *pmp, unsigned int region, size_t address)
447 struct metal_pmp_config config;
451 rc = metal_pmp_get_region(pmp, region, &config, &old_address);
456 rc = metal_pmp_set_region(pmp, region, config, address);
461 size_t metal_pmp_get_address(struct metal_pmp *pmp, unsigned int region)
463 struct metal_pmp_config config;
466 metal_pmp_get_region(pmp, region, &config, &address);
472 int metal_pmp_set_address_mode(struct metal_pmp *pmp, unsigned int region, enum metal_pmp_address_mode mode)
474 struct metal_pmp_config config;
478 rc = metal_pmp_get_region(pmp, region, &config, &address);
485 rc = metal_pmp_set_region(pmp, region, config, address);
490 enum metal_pmp_address_mode metal_pmp_get_address_mode(struct metal_pmp *pmp, unsigned int region)
492 struct metal_pmp_config config;
495 metal_pmp_get_region(pmp, region, &config, &address);
501 int metal_pmp_set_executable(struct metal_pmp *pmp, unsigned int region, int X)
503 struct metal_pmp_config config;
507 rc = metal_pmp_get_region(pmp, region, &config, &address);
514 rc = metal_pmp_set_region(pmp, region, config, address);
519 int metal_pmp_get_executable(struct metal_pmp *pmp, unsigned int region)
521 struct metal_pmp_config config;
524 metal_pmp_get_region(pmp, region, &config, &address);
530 int metal_pmp_set_writeable(struct metal_pmp *pmp, unsigned int region, int W)
532 struct metal_pmp_config config;
536 rc = metal_pmp_get_region(pmp, region, &config, &address);
543 rc = metal_pmp_set_region(pmp, region, config, address);
548 int metal_pmp_get_writeable(struct metal_pmp *pmp, unsigned int region)
550 struct metal_pmp_config config;
553 metal_pmp_get_region(pmp, region, &config, &address);
559 int metal_pmp_set_readable(struct metal_pmp *pmp, unsigned int region, int R)
561 struct metal_pmp_config config;
565 rc = metal_pmp_get_region(pmp, region, &config, &address);
572 rc = metal_pmp_set_region(pmp, region, config, address);
577 int metal_pmp_get_readable(struct metal_pmp *pmp, unsigned int region)
579 struct metal_pmp_config config;
582 metal_pmp_get_region(pmp, region, &config, &address);