soc: riscv: andes_v5: introduce Andes V5 PMA to support nocache memory

Andes V5 PMA can let SW programmably configure memory attribute of
physical memory region. This commit enable CONFIG_NOCACHE_MEMORY of
Andes V5 CPUs based on the PMA. Use PMA region 0 to set whole nocache
section as uncached memory.

Signed-off-by: Jim Shu <cwshu@andestech.com>
This commit is contained in:
Jim Shu 2021-07-29 12:23:19 +08:00 committed by Anas Nashif
commit 3b7221363a
4 changed files with 289 additions and 0 deletions

View file

@ -7,6 +7,8 @@ zephyr_sources(
soc_irq.S
)
zephyr_sources_ifdef(CONFIG_SOC_ANDES_V5_PMA pma.c)
# Note: AndeStar V5 DSP needs custom Andes V5 toolchain
if(CONFIG_SOC_ANDES_V5_HWDSP)
zephyr_cc_option(-mext-dsp)

View file

@ -63,4 +63,21 @@ config SOC_ANDES_V5_PFT
The PowerBrake extension throttles performance by reducing instruction
executing rate.
config SOC_ANDES_V5_PMA
bool "Enable Andes V5 Physical Memory Attribute (PMA)"
select ARCH_HAS_NOCACHE_MEMORY_SUPPORT
help
This option enables the Andes V5 PMA, in order to support SW to
configure physical memory attribute by PMA CSRs. The address
matching of Andes V5 PMA is like RISC-V PMP NAPOT mode
(power-of-two alignment).
config SOC_ANDES_V5_PMA_REGION_MIN_ALIGN_AND_SIZE
int
depends on SOC_ANDES_V5_PMA
default 4096
help
Minimum size (and alignment) of an PMA region. Use this symbol
to guarantee minimum size and alignment of PMA regions.
endif # SOC_SERIES_RISCV_ANDES_V5

View file

@ -59,6 +59,27 @@
#define RAM_BASE CONFIG_SRAM_BASE_ADDRESS
#define RAM_SIZE KB(CONFIG_SRAM_SIZE)
/* Make linker section alignment comply with PMA granularity. */
#if defined(CONFIG_SOC_ANDES_V5_PMA_REGION_MIN_ALIGN_AND_SIZE)
_region_min_align = CONFIG_SOC_ANDES_V5_PMA_REGION_MIN_ALIGN_AND_SIZE;
#else
_region_min_align = 4;
#endif
#if defined(CONFIG_SOC_ANDES_V5_PMA)
/*
* Andes-V5 PMA needs power-of-2 alignment.
*/
#define MPU_MIN_SIZE_ALIGN . = ALIGN(_region_min_align);
#define MPU_ALIGN(region_size) \
. = ALIGN(_region_min_align); \
. = ALIGN( 1 << LOG2CEIL(region_size))
#else
#define MPU_MIN_SIZE_ALIGN
#define MPU_ALIGN(region_size) \
. = ALIGN(_region_min_align)
#endif
MEMORY
{
#ifdef CONFIG_XIP

View file

@ -0,0 +1,249 @@
/*
* Copyright (c) 2021 Andes Technology Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <init.h>
#include <kernel.h>
#include <arch/cpu.h>
#include <linker/linker-defs.h>
/* Programmable PMA mechanism is supported */
#define MMSC_CFG_PPMA (1 << 30)
/*
* PMA Configuration (PMACFG) bitfields
*/
/* ETYPE: Entry address matching mode */
#define PMACFG_ETYPE_MASK 3
#define PMACFG_ETYPE_OFF 0
#define PMACFG_ETYPE_TOR 1
#define PMACFG_ETYPE_NA4 2
#define PMACFG_ETYPE_NAPOT 3
/* MTYPE: Memory type attribute */
#define PMACFG_MTYPE_MASK (0xF << 2)
/* non-cacheable attributes (bufferable or not) */
#define PMACFG_MTYPE_MEMORY_NOCACHE_BUFFERABLE (3 << 2)
/* cacheable attributes (write-through/back, no/read/write/RW-allocate) */
#define PMACFG_MTYPE_MEMORY_WBCACHE_RWALLOC (11 << 2)
/* pmaaddr is 4-byte granularity in each mode */
#define TO_PMA_ADDR(addr) ((addr) >> 2)
/* The base address is aligned to size */
#define NAPOT_BASE(start, size) TO_PMA_ADDR((start) & ~((size) - 1))
/* The encoding of size is 0b01...1
* (change the leading bit of bitmask to 0)
*/
#define NAPOT_SIZE(size) TO_PMA_ADDR(((size) - 1) >> 1)
#define NA4_ENCODING(start) TO_PMA_ADDR(start)
#define NAPOT_ENCODING(start, size) (NAPOT_BASE(start, size) \
| NAPOT_SIZE(size))
#ifdef CONFIG_64BIT
/* In riscv64, CSR pmacfg number are even number (0, 2, ...) */
# define PMACFG_NUM(index) ((index / RV_REGSIZE) * 2)
#else
# define PMACFG_NUM(index) (index / RV_REGSIZE)
#endif
#define PMACFG_SHIFT(index) ((index % RV_REGSIZE) * 8)
/* Wrappers of inline assembly */
#define read_csr(var, csr) \
__asm__ volatile ("csrr %0, %1" : "=r" (var) : "i" (csr))
#define write_csr(csr, val) \
__asm__ volatile ("csrw %0, %1" :: "i" (csr), "r" (val))
struct pma_region_attr {
/* Attributes belonging to pmacfg{i} */
uint8_t pmacfg;
};
struct pma_region {
ulong_t start;
ulong_t size;
struct pma_region_attr attr;
};
/*
* Write value to CSRs pmaaddr{i}
*/
static void write_pmaaddr_csr(const uint32_t index, ulong_t value)
{
switch (index) {
case 0:
write_csr(NDS_PMAADDR0, value); break;
case 1:
write_csr(NDS_PMAADDR1, value); break;
case 2:
write_csr(NDS_PMAADDR2, value); break;
case 3:
write_csr(NDS_PMAADDR3, value); break;
case 4:
write_csr(NDS_PMAADDR4, value); break;
case 5:
write_csr(NDS_PMAADDR5, value); break;
case 6:
write_csr(NDS_PMAADDR6, value); break;
case 7:
write_csr(NDS_PMAADDR7, value); break;
case 8:
write_csr(NDS_PMAADDR8, value); break;
case 9:
write_csr(NDS_PMAADDR9, value); break;
case 10:
write_csr(NDS_PMAADDR10, value); break;
case 11:
write_csr(NDS_PMAADDR11, value); break;
case 12:
write_csr(NDS_PMAADDR12, value); break;
case 13:
write_csr(NDS_PMAADDR13, value); break;
case 14:
write_csr(NDS_PMAADDR14, value); break;
case 15:
write_csr(NDS_PMAADDR15, value); break;
}
}
/*
* Write value to pma{i}cfg entry which are packed into CSRs pmacfg{j}
*/
static void write_pmacfg_entry(const uint32_t entry_index,
uint8_t entry_value)
{
/* 1-byte pma{i}cfg entries are packed into XLEN-byte CSRs pmacfg{j} */
uint32_t index = PMACFG_NUM(entry_index);
uint8_t shift = PMACFG_SHIFT(entry_index);
ulong_t pmacfg = 0;
switch (index) {
case 0:
read_csr(pmacfg, NDS_PMACFG0); break;
case 1:
read_csr(pmacfg, NDS_PMACFG1); break;
case 2:
read_csr(pmacfg, NDS_PMACFG2); break;
case 3:
read_csr(pmacfg, NDS_PMACFG3); break;
}
/* clear old value in pmacfg entry */
pmacfg &= ~(0xFF << shift);
/* set new value to pmacfg entry value */
pmacfg |= entry_value << shift;
switch (index) {
case 0:
write_csr(NDS_PMACFG0, pmacfg); break;
case 1:
write_csr(NDS_PMACFG1, pmacfg); break;
case 2:
write_csr(NDS_PMACFG2, pmacfg); break;
case 3:
write_csr(NDS_PMACFG3, pmacfg); break;
}
}
/*
* This internal function performs PMA region initialization.
*
* Note:
* The caller must provide a valid region index.
*/
static void region_init(const uint32_t index,
const struct pma_region *region_conf)
{
ulong_t pmaaddr;
uint8_t pmacfg;
if (region_conf->size == 4) {
pmaaddr = NA4_ENCODING(region_conf->start);
pmacfg = region_conf->attr.pmacfg | PMACFG_ETYPE_NA4;
} else {
pmaaddr = NAPOT_ENCODING(region_conf->start, region_conf->size);
pmacfg = region_conf->attr.pmacfg | PMACFG_ETYPE_NAPOT;
}
write_pmaaddr_csr(index, pmaaddr);
write_pmacfg_entry(index, pmacfg);
}
/*
* This internal function performs run-time sanity check for
* PMA region start address and size.
*/
static int pma_region_is_valid(const struct pma_region *region)
{
/* Region size must be power-of-two,
* and greater or equal to the minimum
* PMA region size. Start address of the
* region must align with size.
*/
int region_is_valid =
((region->size & (region->size - 1)) == 0U)
&&
(region->size >= CONFIG_SOC_ANDES_V5_PMA_REGION_MIN_ALIGN_AND_SIZE)
&&
((region->start & (region->size - 1)) == 0U);
return region_is_valid;
}
#ifdef CONFIG_NOCACHE_MEMORY
static void configure_nocache_region(void)
{
const struct pma_region nocache_region = {
.start = (ulong_t)&_nocache_ram_start,
.size = (ulong_t)&_nocache_ram_size,
.attr = {PMACFG_MTYPE_MEMORY_NOCACHE_BUFFERABLE},
};
if (pma_region_is_valid(&nocache_region) == -EINVAL) {
__ASSERT(0, "Configuring PMA region of nocache region failed\n");
}
/* Initialize nocache region at PMA region 0 */
region_init(0, &nocache_region);
}
#endif /* CONFIG_NOCACHE_MEMORY */
/*
* @brief Init PMA CSRs of each CPU core
*
* In SMP, each CPU has it's own PMA CSR and PMA CSR only affect one CPU.
* We should configure CSRs of all CPUs to make memory attribute
* (e.g. uncacheable) affects all CPUs.
*/
void pma_init_per_core(void)
{
#ifdef CONFIG_NOCACHE_MEMORY
configure_nocache_region();
#endif /* CONFIG_NOCACHE_MEMORY */
}
static int pma_init(const struct device *arg)
{
ulong_t mmsc_cfg;
__asm__ volatile ("csrr %0, %1" : "=r" (mmsc_cfg) : "i" (NDS_MMSC_CFG));
if (!(mmsc_cfg & MMSC_CFG_PPMA)) {
/* This CPU doesn't support PMA */
__ASSERT(0, "CPU doesn't support PMA. "
"Please disable CONFIG_SOC_ANDES_V5_PMA\n");
return -1;
}
pma_init_per_core();
return 0;
}
SYS_INIT(pma_init, PRE_KERNEL_2,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);