Extend the RISC-V PMP functionality

- Add test for mode support, which is architecture dependent
- Add tests for address alignment and region size
- Add option to query for access rights
 - The function goes through every PMP entry and tests if an address
   range from [base, base+size] has been configured for desired
   access rights.
 - If several PMP entries match the range and access rights, the
   information is combined
 - End result is either no access, a partial match was found, or a full
   match was found. Details about the partial match are not provided.

The intent for testing access rights and not just blindly applying them
is a case where they are already set in e.g. a bootloader. In this
case, nothing should be done, unless the configuration does not match,
in which case the software must not continue further.
This commit is contained in:
Ville Juven 2022-01-24 10:17:08 +02:00 committed by Xiang Xiao
parent 8a4881c4e5
commit 81188d9c94
6 changed files with 666 additions and 29 deletions

View File

@ -101,6 +101,7 @@ config ARCH_CHIP_MPFS
select ARCH_HAVE_RESET
select ARCH_HAVE_SPI_CS_CONTROL
select ARCH_HAVE_PWM_MULTICHAN
select PMP_HAS_LIMITED_FEATURES
---help---
MicroChip Polarfire processor (RISC-V 64bit core with GCVX extensions).
@ -188,6 +189,29 @@ config ARCH_MMU_TYPE_SV39
bool
default n
# MPU has certain architecture dependent configurations, which are presented
# here. Default is that the full RISC-V PMP specification is supported.
config PMP_HAS_LIMITED_FEATURES
bool
default n
config ARCH_MPU_MIN_BLOCK_SIZE
int "Minimum MPU (PMP) block size"
default 4 if !PMP_HAS_LIMITED_FEATURES
config ARCH_MPU_HAS_TOR
bool "PMP supports TOR"
default y if !PMP_HAS_LIMITED_FEATURES
config ARCH_MPU_HAS_NO4
bool "PMP supports NO4"
default y if !PMP_HAS_LIMITED_FEATURES
config ARCH_MPU_HAS_NAPOT
bool "PMP supports NAPOT"
default y if !PMP_HAS_LIMITED_FEATURES
source "arch/risc-v/src/opensbi/Kconfig"
source "arch/risc-v/src/common/Kconfig"

View File

@ -346,6 +346,7 @@
#define PMPCFG_R (1 << 0) /* readable ? */
#define PMPCFG_W (1 << 1) /* writeable ? */
#define PMPCFG_X (1 << 2) /* excutable ? */
#define PMPCFG_RWX_MASK (7 << 0) /* access rights mask */
#define PMPCFG_A_OFF (0 << 3) /* null region (disabled) */
#define PMPCFG_A_TOR (1 << 3) /* top of range */
#define PMPCFG_A_NA4 (2 << 3) /* naturally aligned four-byte region */

View File

@ -91,6 +91,12 @@
# endif
#endif
/* Return values from riscv_check_pmp_access */
#define PMP_ACCESS_OFF (0) /* Access for area not set */
#define PMP_ACCESS_DENIED (-1) /* Access set and denied */
#define PMP_ACCESS_FULL (1) /* Access set and allowed */
/****************************************************************************
* Public Types
****************************************************************************/
@ -177,8 +183,12 @@ void riscv_restorefpu(const uintptr_t *regs);
/* RISC-V PMP Config ********************************************************/
void riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
uintptr_t base, uintptr_t size);
int riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
uintptr_t base, uintptr_t size);
int riscv_check_pmp_access(uintptr_t attr, uintptr_t base, uintptr_t size);
int riscv_configured_pmp_regions(void);
int riscv_next_free_pmp_region(void);
/* Power management *********************************************************/

View File

@ -22,6 +22,9 @@
* Included Files
****************************************************************************/
#include <stdbool.h>
#include <nuttx/compiler.h>
#include <nuttx/config.h>
#include <nuttx/arch.h>
#include <arch/csr.h>
@ -32,6 +35,18 @@
* Pre-processor Definitions
****************************************************************************/
/* Minimum supported block size */
#if !defined CONFIG_ARCH_MPU_MIN_BLOCK_SIZE
#define MIN_BLOCK_SIZE (__riscv_xlen / 8)
#else
#define MIN_BLOCK_SIZE CONFIG_ARCH_MPU_MIN_BLOCK_SIZE
#endif
/* Address and block size alignment mask */
#define BLOCK_ALIGN_MASK (MIN_BLOCK_SIZE - 1)
#define PMP_CFG_BITS_CNT (8)
#define PMP_CFG_FLAG_MASK (0xFF)
@ -44,6 +59,361 @@
reg |= attr << (offset * PMP_CFG_BITS_CNT); \
} while(0);
#define PMP_READ_REGION_FROM_REG(region, reg) \
({ \
uintptr_t tmp = READ_CSR(reg); \
tmp >>= ((region % PMP_CFG_CNT_IN_REG) * PMP_CFG_BITS_CNT); \
tmp &= PMP_CFG_FLAG_MASK; \
tmp; \
})
#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a,b) ((a) > (b) ? (a) : (b))
#endif
/****************************************************************************
* Private Types
****************************************************************************/
/* Helper structure for handling a PMP entry */
struct pmp_entry_s
{
uintptr_t base; /* Base address of region */
uintptr_t end; /* End address of region */
uintptr_t size; /* Region size */
uint8_t mode; /* Address matching mode */
uint8_t rwx; /* Access rights */
};
typedef struct pmp_entry_s pmp_entry_t;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pmp_check_addrmatch_type
*
* Description:
* Test if an address matching type is supported by the architecture.
*
* Input Parameters:
* type - The type to test.
*
* Returned Value:
* true if it is, false otherwise.
*
****************************************************************************/
static bool pmp_check_addrmatch_type(uintptr_t type)
{
/* Parameter is potentially unused */
UNUSED(type);
#ifdef CONFIG_ARCH_MPU_HAS_TOR
if (type == PMPCFG_A_TOR)
{
return true;
}
#endif
#ifdef CONFIG_ARCH_MPU_HAS_NO4
if (type == PMPCFG_A_NA4)
{
return true;
}
#endif
#ifdef CONFIG_ARCH_MPU_HAS_NAPOT
if (type == PMPCFG_A_NAPOT)
{
return true;
}
#endif
/* None of the supported types match */
return false;
}
/****************************************************************************
* Name: pmp_check_region_attrs
*
* Description:
* Test if the base address and size of region meet alignment requirements.
*
* Input Parameters:
* base - The base address of the region.
* size - The memory length of the region.
*
* Returned Value:
* true if it is, false otherwise.
*
****************************************************************************/
static bool pmp_check_region_attrs(uintptr_t base, uintptr_t size)
{
/* Check that the size is not too small */
if (size < MIN_BLOCK_SIZE)
{
return false;
}
/* Check that the base address is aligned properly */
if ((base & BLOCK_ALIGN_MASK) != 0)
{
return false;
}
/* Check that the size is aligned properly */
if ((size & BLOCK_ALIGN_MASK) != 0)
{
return false;
}
return OK;
}
/****************************************************************************
* Name: pmp_read_region_cfg
*
* Description:
* Read PMP configuration for region
*
* Input Parameters:
* region - Region number.
*
* Returned Value:
* Configuration value from pmpcfg+region
*
****************************************************************************/
static uintptr_t pmp_read_region_cfg(uintptr_t region)
{
# if (__riscv_xlen == 32)
switch (region)
{
case 0 ... 3:
return PMP_READ_REGION_FROM_REG(region, pmpcfg0);
case 4 ... 7:
return PMP_READ_REGION_FROM_REG(region, pmpcfg1);
case 8 ... 11:
return PMP_READ_REGION_FROM_REG(region, pmpcfg2);
case 12 ... 15:
return PMP_READ_REGION_FROM_REG(region, pmpcfg3);
default:
break;
}
# elif (__riscv_xlen == 64)
switch (region)
{
case 0 ... 7:
return PMP_READ_REGION_FROM_REG(region, pmpcfg0);
case 8 ... 15:
return PMP_READ_REGION_FROM_REG(region, pmpcfg2);
default:
break;
}
#endif
/* Never executed */
return 0;
}
/****************************************************************************
* Name: pmp_read_addr
*
* Description:
* Read address for region
*
* Input Parameters:
* region - Region number.
*
* Returned Value:
* Address value from pmpcfg+region
*
****************************************************************************/
static uintptr_t pmp_read_addr(uintptr_t region)
{
switch (region)
{
case 0:
return READ_CSR(pmpaddr0);
case 1:
return READ_CSR(pmpaddr1);
case 2:
return READ_CSR(pmpaddr2);
case 3:
return READ_CSR(pmpaddr3);
case 4:
return READ_CSR(pmpaddr4);
case 5:
return READ_CSR(pmpaddr5);
case 6:
return READ_CSR(pmpaddr6);
case 7:
return READ_CSR(pmpaddr7);
case 8:
return READ_CSR(pmpaddr8);
case 9:
return READ_CSR(pmpaddr9);
case 10:
return READ_CSR(pmpaddr10);
case 11:
return READ_CSR(pmpaddr11);
case 12:
return READ_CSR(pmpaddr12);
case 13:
return READ_CSR(pmpaddr13);
case 14:
return READ_CSR(pmpaddr14);
case 15:
return READ_CSR(pmpaddr15);
default:
break;
}
/* Never executed */
return 0;
}
/****************************************************************************
* Name: pmp_napot_decode
*
* Description:
* Decode base and size from NAPOT value
*
* Input Parameters:
* val - Value to decode.
* base - Base out.
* size - Size out.
*
* Returned Value:
* None
*
****************************************************************************/
static void pmp_napot_decode(uintptr_t val, uintptr_t * base,
uintptr_t * size)
{
uint64_t mask = (uint64_t)(-1) >> 1;
uint64_t pot = __riscv_xlen + 2;
while (mask)
{
if ((val & mask) == mask)
{
break;
}
pot--;
mask >>= 1;
}
val &= ~mask;
*base = (val << 2);
*size = (1 << pot);
}
/****************************************************************************
* Name: pmp_read
*
* Description:
* Read PMP region into PMP entry
*
* Input Parameters:
* region - Region number.
* entry - Entry out
*
* Returned Value:
* None
*
****************************************************************************/
static void pmp_read(uintptr_t region, pmp_entry_t * entry)
{
uintptr_t addr = 0;
uintptr_t size = 0;
uintptr_t mode = 0;
uintptr_t rwx = 0;
uintptr_t cfg = 0;
addr = pmp_read_addr(region);
cfg = pmp_read_region_cfg(region);
mode = cfg & PMPCFG_A_MASK;
rwx = cfg & PMPCFG_RWX_MASK;
switch (mode)
{
case PMPCFG_A_TOR:
addr <<= 2;
/* TOR region, must peek into prior region for size */
if (region == 0)
{
size = addr;
}
else
{
size = addr - pmp_read_addr(region - 1);
}
break;
case PMPCFG_A_NA4:
addr <<= 2;
size = 4;
break;
case PMPCFG_A_NAPOT:
pmp_napot_decode(addr, &addr, &size);
break;
default:
break;
}
entry->base = addr;
entry->end = addr + size;
entry->size = size;
entry->rwx = rwx;
entry->mode = mode;
}
/****************************************************************************
* Public Functions
****************************************************************************/
@ -63,17 +433,32 @@
* and the size must be power-of-two according to the the PMP spec.
*
* Returned Value:
* None.
* 0 on succeess; negated error on failure
*
****************************************************************************/
void riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
uintptr_t base, uintptr_t size)
int riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
uintptr_t base, uintptr_t size)
{
uintptr_t addr = 0;
uintptr_t cfg = 0;
uintptr_t addr = 0;
uintptr_t cfg = 0;
uintptr_t type = (attr & PMPCFG_A_MASK);
/* TODO: check the base address alignment and size */
/* Check that the architecture supports address matching type */
if (pmp_check_addrmatch_type(type) == false)
{
return -EINVAL;
}
/* Check the region attributes */
if (pmp_check_region_attrs(base, size))
{
return -EINVAL;
}
/* Calculate new base address from type */
addr = base >> 2;
if (PMPCFG_A_NAPOT == (attr & PMPCFG_A_MASK))
@ -81,6 +466,8 @@ void riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
addr |= (size - 1) >> 3;
}
/* Set the address value */
switch (region)
{
case 0:
@ -151,6 +538,8 @@ void riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
break;
}
/* Set the configuration register value */
# if (__riscv_xlen == 32)
switch (region)
{
@ -206,4 +595,165 @@ void riscv_config_pmp_region(uintptr_t region, uintptr_t attr,
/* fence is needed when page-based virtual memory is implemented */
__asm volatile("sfence.vma x0, x0" : : : "memory");
return OK;
}
/****************************************************************************
* Name: riscv_check_pmp_access
*
* Description:
* This function will set the specific PMP region with the desired cfg.
*
* Input Parameters:
* attr - The region configurations.
* base - The base address of the region.
* size - The memory length of the region.
* For the NAPOT mode, the base address must aligned to the size boundary,
* and the size must be power-of-two according to the the PMP spec.
*
* Returned Value:
* 0 if access rights are not set at all
* < 0 if access rights are set and match match partially
* > 0 if access rights are set and match fully
*
****************************************************************************/
int riscv_check_pmp_access(uintptr_t attr, uintptr_t base, uintptr_t size)
{
pmp_entry_t entry;
uintptr_t end;
uintptr_t orgsize;
unsigned int region;
/* Go through every single configured region and test the attributes */
attr = (attr & PMPCFG_RWX_MASK);
end = base + size;
orgsize = size;
for (region = 0; region < 16 && size > 0; region++)
{
/* Find matching configuration first */
pmp_read(region, &entry);
/* Check if any configuration at all */
if (entry.mode == PMPCFG_A_OFF)
{
continue;
}
/* Does this address range match ? Take partial matches into account.
*
* There are four possibilities:
* 1: Full match; region inside mapped area
* 2: Partial match; mapped area inside region
* 3: Partial match; base inside mapped region, end outside
* 4: Partial match; base outside mapped region, end inside
*/
if ((base >= entry.base && end <= entry.end) ||
(base <= entry.base && end >= entry.end) ||
(base >= entry.base && base <= entry.end) ||
(end >= entry.base && end <= entry.end))
{
/* Found a matching splice, check rights */
if ((entry.rwx & attr) == attr)
{
/* Found matching region that allows access */
size -= min(end, entry.end) - max(base, entry.base);
}
else
{
/* Found matching region that does not allow access */
return PMP_ACCESS_DENIED;
}
}
}
/* Check if nothing configured at all ? */
if (size == orgsize)
{
return PMP_ACCESS_OFF;
}
/* If size is non-positive, the requested range is accessible */
if (size <= 0)
{
return PMP_ACCESS_FULL;
}
/* The requested range is either fully or partially inaccessible */
return PMP_ACCESS_DENIED;
}
/****************************************************************************
* Name: riscv_configured_pmp_regions
*
* Description:
* Count amount of configured PMP regions, note: is not atomic
*
* Input Parameters:
*
* Returned Value:
* Amount of configured PMP regions
*
****************************************************************************/
int riscv_configured_pmp_regions(void)
{
pmp_entry_t entry;
unsigned int region;
int ret = 0;
for (region = 0; region < 16; region++)
{
pmp_read(region, &entry);
if (entry.mode != PMPCFG_A_OFF)
{
ret++;
}
}
return ret;
}
/****************************************************************************
* Name: riscv_next_free_pmp_region
*
* Description:
* Returns next free PMP region, note: is not atomic
*
* Input Parameters:
*
* Returned Value:
* Next free PMP region, or -1 if none found
*
****************************************************************************/
int riscv_next_free_pmp_region(void)
{
pmp_entry_t entry;
unsigned int region;
for (region = 0; region < 16; region++)
{
pmp_read(region, &entry);
if (entry.mode == PMPCFG_A_OFF)
{
return region;
}
}
return -1;
}

View File

@ -312,3 +312,17 @@ config MPFS_DMA
menu "MPFS Others"
endmenu
# Override the default values for MPU / PMP parameters here
config ARCH_MPU_MIN_BLOCK_SIZE
default 4096
config ARCH_MPU_HAS_TOR
default n
config ARCH_MPU_HAS_NO4
default n
config ARCH_MPU_HAS_NAPOT
default y

View File

@ -39,11 +39,20 @@
* Pre-processor Definitions
****************************************************************************/
#define PMP_UFLASH_FLAGS (PMPCFG_A_NAPOT | PMPCFG_X | PMPCFG_R)
#define PMP_USRAM_FLAGS (PMPCFG_A_NAPOT | PMPCFG_W | PMPCFG_R)
#define UFLASH_START (uintptr_t)&__uflash_start
#define UFLASH_SIZE (uintptr_t)&__uflash_size
#define USRAM_START (uintptr_t)&__usram_start
#define USRAM_SIZE (uintptr_t)&__usram_size
/* Physical and virtual addresses to page tables (vaddr = paddr mapping) */
#define PGT_BASE_PADDR (uint64_t)&m_l1_pgtable
#define PGT_L1_PBASE (uint64_t)&m_l1_pgtable
#define PGT_L2_PBASE (uint64_t)&m_l2_pgtable
#define PGT_L3_PBASE (uint64_t)&m_l3_pgtable
#define PGT_L1_VBASE PGT_L1_PBASE
#define PGT_L2_VBASE PGT_L2_PBASE
#define PGT_L3_VBASE PGT_L3_PBASE
@ -52,6 +61,11 @@
#define MMU_UFLASH_FLAGS (PTE_R | PTE_X | PTE_U | PTE_G)
#define MMU_USRAM_FLAGS (PTE_R | PTE_W | PTE_U | PTE_G)
/* Kernel RAM needs to be opened (the page tables) */
#define KSRAM_START (uintptr_t)&__ksram_start
#define KSRAM_SIZE (uintptr_t)&__ksram_size
/****************************************************************************
* Private Functions
****************************************************************************/
@ -178,9 +192,8 @@ static void configure_mpu(void)
/* Configure the PMP to permit user-space access to its ROM and RAM.
*
* Note: PMP by default revokes access, thus if different privilege modes
* are in use (mstatus.mprv is set), the the user space _must_ be granted
* access here, otherwise an exception will fire when the user space task
* is started.
* are in use, the user space _must_ be granted access here, otherwise
* an exception will fire when the user space task is started.
*
* Note: according to the Polarfire reference manual, address bits [1:0]
* are not considered (due to 4 octet alignment), so strictly they don't
@ -198,19 +211,46 @@ static void configure_mpu(void)
*
*/
riscv_config_pmp_region(0, PMPCFG_A_NAPOT | PMPCFG_X | PMPCFG_R,
(uintptr_t)&__uflash_start,
(uintptr_t)&__uflash_size);
int ret;
int idx;
riscv_config_pmp_region(1, PMPCFG_A_NAPOT | PMPCFG_W | PMPCFG_R,
(uintptr_t)&__usram_start,
(uintptr_t)&__usram_size);
/* First, test access to user flash */
ret = riscv_check_pmp_access(PMP_UFLASH_FLAGS, UFLASH_START, UFLASH_SIZE);
/* No access or partial access means we must crash */
DEBUGASSERT(ret != PMP_ACCESS_DENIED);
if (ret == PMP_ACCESS_OFF)
{
idx = riscv_next_free_pmp_region();
DEBUGASSERT(idx >= 0);
riscv_config_pmp_region(idx, PMP_UFLASH_FLAGS, UFLASH_START,
UFLASH_SIZE);
}
/* Then, test access to user RAM */
ret = riscv_check_pmp_access(PMP_USRAM_FLAGS, USRAM_START, USRAM_SIZE);
DEBUGASSERT(ret != PMP_ACCESS_DENIED);
if (ret == PMP_ACCESS_OFF)
{
idx = riscv_next_free_pmp_region();
DEBUGASSERT(idx >= 0);
riscv_config_pmp_region(idx, PMP_USRAM_FLAGS, USRAM_START, USRAM_SIZE);
}
/* The supervisor must have access to the page tables */
riscv_config_pmp_region(2, PMPCFG_A_NAPOT | PMPCFG_W | PMPCFG_R,
(uintptr_t)&__ksram_start,
(uintptr_t)&__ksram_size);
ret = riscv_check_pmp_access(PMP_USRAM_FLAGS, KSRAM_START, KSRAM_SIZE);
DEBUGASSERT(ret != PMP_ACCESS_DENIED);
if (ret == PMP_ACCESS_OFF)
{
idx = riscv_next_free_pmp_region();
DEBUGASSERT(idx >= 0);
riscv_config_pmp_region(idx, PMP_USRAM_FLAGS, KSRAM_START, KSRAM_SIZE);
}
}
/****************************************************************************
@ -228,24 +268,22 @@ static void configure_mmu(void)
/* Setup the L3 references for executable memory */
mmu_ln_map_region(3, PGT_L3_VBASE, (uintptr_t)&__uflash_start,
(uintptr_t)&__uflash_start, (uintptr_t)&__uflash_size,
MMU_UFLASH_FLAGS);
mmu_ln_map_region(3, PGT_L3_VBASE, UFLASH_START, UFLASH_START,
UFLASH_SIZE, MMU_UFLASH_FLAGS);
/* Setup the L3 references for data memory */
mmu_ln_map_region(3, PGT_L3_VBASE, (uintptr_t)&__usram_start,
(uintptr_t)&__usram_start, (uintptr_t)&__usram_size,
MMU_USRAM_FLAGS);
mmu_ln_map_region(3, PGT_L3_VBASE, USRAM_START, USRAM_START,
USRAM_SIZE, MMU_USRAM_FLAGS);
/* Setup the L2 and L1 references */
mmu_ln_setentry(2, PGT_L2_VBASE, PGT_L3_PBASE, PGT_L3_VBASE, PTE_G);
mmu_ln_setentry(1, PGT_BASE_PADDR, PGT_L2_PBASE, PGT_L2_VBASE, PTE_G);
mmu_ln_setentry(1, PGT_L1_VBASE, PGT_L2_PBASE, PGT_L2_VBASE, PTE_G);
/* Enable MMU */
mmu_enable(PGT_BASE_PADDR, 0);
mmu_enable(PGT_L1_PBASE, 0);
}
#endif /* CONFIG_BUILD_PROTECTED */