arch: arm64: Armv8-R AArch64 MPU implementation

Armv8-R AArch64 MPU can support a maximum 16 memory regions, and the
actual region number can be retrieved from the system register(MPUIR)
during MPU initialization.
Current MPU driver only suppots EL1.

Signed-off-by: Haibo Xu <haibo.xu@arm.com>
Signed-off-by: Jaxson Han <jaxson.han@arm.com>
This commit is contained in:
Jaxson Han 2021-03-04 17:51:11 +08:00 committed by Anas Nashif
parent 475374ce2f
commit 30ed92c218
4 changed files with 255 additions and 0 deletions

View file

@ -23,6 +23,7 @@
/arch/arm/include/aarch32/cortex_m/cmse.h @ioannisg
/arch/arm/core/aarch32/cortex_a_r/ @MaureenHelm @galak @ioannisg @bbolen @stephanosio
/arch/arm64/ @carlocaione
/arch/arm64/core/cortex_r/ @povergoing
/arch/common/ @ioannisg @andyross
/soc/arc/snps_*/ @abrodkin @ruuddw @evgeniy-paltsev
/soc/nios2/ @nashif

View file

@ -0,0 +1,7 @@
# Copyright (c) 2021 Arm Limited (or its affiliates). All rights reserved.
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(arm_mpu.c)

View file

@ -0,0 +1,60 @@
# Memory Protection Unit (MPU) configuration options
#Copyright (c) 2017 Linaro Limited.
#Copyright (c) 2021 Arm Limited (or its affiliates). All rights reserved.
# SPDX-License-Identifier: Apache-2.0
if CPU_HAS_MPU
config ARM_MPU
bool "ARM MPU Support"
select THREAD_STACK_INFO
default y
help
MPU implements Memory Protection Unit.
Notes:
The ARMv8-R MPU architecture requires a power-of-two alignment
of MPU region base address and size(64 bytes aligned).
The ARMv8-R MPU requires the active MPU regions be non-overlapping.
As a result of this, the ARMv8-R MPU needs to fully partition the
memory map when programming dynamic memory regions (e.g. PRIV stack
guard, user thread stack, and application memory domains), if the
system requires PRIV access policy different from the access policy
of the ARMv8-R background memory map. The application developer may
enforce full PRIV (kernel) memory partition by enabling the
CONFIG_MPU_GAP_FILLING option.
By not enforcing full partition, MPU may leave part of kernel
SRAM area covered only by the default ARMv8-R memory map. This
is fine for User Mode, since the background ARM map does not
allow nPRIV access at all. However, since the background map
policy allows instruction fetches by privileged code, forcing
this Kconfig option off prevents the system from directly
triggering MemManage exceptions upon accidental attempts to
execute code from SRAM in XIP builds.
Since this does not compromise User Mode, we make the skipping
of full partitioning the default behavior for the ARMv8-R MPU
driver.
config ARM_MPU_REGION_MIN_ALIGN_AND_SIZE
int
default 64 if ARM_MPU
default 4
help
Minimum size (and alignment) of an ARM MPU region. Use this
symbol to guarantee minimum size and alignment of MPU regions.
A minimum 4-byte alignment is enforced in ARM builds without
support for Memory Protection.
if ARM_MPU
config MPU_ALLOW_FLASH_WRITE
bool "Add MPU access to write to flash"
help
Enable this to allow MPU RWX access to flash memory
endif # ARM_MPU
endif # CPU_HAS_MPU

View file

@ -0,0 +1,187 @@
/*
* Copyright (c) 2017 Linaro Limited.
* Copyright (c) 2021 Arm Limited (or its affiliates). All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <device.h>
#include <init.h>
#include <kernel.h>
#include <soc.h>
#include <arch/arm64/cortex_r/mpu/arm_mpu.h>
#include <linker/linker-defs.h>
#define LOG_LEVEL CONFIG_MPU_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_DECLARE(mpu);
/*
* AArch64 Memory Model Feature Register 0
* Provides information about the implemented memory model and memory
* management support in AArch64 state.
* See Arm Architecture Reference Manual Supplement
* Armv8, for Armv8-R AArch64 architecture profile, G1.3.7
*
* ID_AA64MMFR0_MSA_FRAC, bits[55:52]
* ID_AA64MMFR0_MSA, bits [51:48]
*/
#define ID_AA64MMFR0_MSA_msk (0xFFUL << 48U)
#define ID_AA64MMFR0_PMSA_EN (0x1FUL << 48U)
/*
* Global status variable holding the number of HW MPU region indices, which
* have been reserved by the MPU driver to program the static (fixed) memory
* regions.
*/
static uint8_t static_regions_num;
/* Get the number of supported MPU regions. */
static inline uint8_t get_num_regions(void)
{
uint64_t type;
type = read_mpuir_el1();
type = type & MPU_IR_REGION_Msk;
return (uint8_t)type;
}
/* ARM Core MPU Driver API Implementation for ARM MPU */
/**
* @brief enable the MPU
*/
void arm_core_mpu_enable(void)
{
uint64_t val;
val = read_sctlr_el1();
val |= SCTLR_M_BIT;
write_sctlr_el1(val);
dsb();
isb();
}
/**
* @brief disable the MPU
*/
void arm_core_mpu_disable(void)
{
uint64_t val;
/* Force any outstanding transfers to complete before disabling MPU */
dmb();
val = read_sctlr_el1();
val &= ~SCTLR_M_BIT;
write_sctlr_el1(val);
dsb();
isb();
}
/* ARM MPU Driver Initial Setup
*
* Configure the cache-ability attributes for all the
* different types of memory regions.
*/
static void mpu_init(void)
{
/* Device region(s): Attribute-0
* Flash region(s): Attribute-1
* SRAM region(s): Attribute-2
* SRAM no cache-able regions(s): Attribute-3
*/
uint64_t mair = MPU_MAIR_ATTRS;
write_mair_el1(mair);
dsb();
isb();
}
static inline void mpu_set_region(uint32_t rnr, uint64_t rbar,
uint64_t rlar)
{
write_prselr_el1(rnr);
dsb();
write_prbar_el1(rbar);
write_prlar_el1(rlar);
dsb();
isb();
}
/* This internal functions performs MPU region initialization. */
static void region_init(const uint32_t index,
const struct arm_mpu_region *region_conf)
{
uint64_t rbar = region_conf->base & MPU_RBAR_BASE_Msk;
uint64_t rlar = (region_conf->limit - 1) & MPU_RLAR_LIMIT_Msk;
rbar |= region_conf->attr.rbar &
(MPU_RBAR_XN_Msk | MPU_RBAR_AP_Msk | MPU_RBAR_SH_Msk);
rlar |= (region_conf->attr.mair_idx << MPU_RLAR_AttrIndx_Pos) &
MPU_RLAR_AttrIndx_Msk;
rlar |= MPU_RLAR_EN_Msk;
mpu_set_region(index, rbar, rlar);
}
/*
* @brief MPU default configuration
*
* This function provides the default configuration mechanism for the Memory
* Protection Unit (MPU).
*/
static int arm_mpu_init(const struct device *arg)
{
uint64_t val;
uint32_t r_index;
/* Current MPU code supports only EL1 */
val = read_currentel();
__ASSERT(GET_EL(val) == MODE_EL1,
"Exception level not EL1, MPU not enabled!\n");
/* Check whether the processor supports MPU */
val = read_id_aa64mmfr0_el1();
if ((val & ID_AA64MMFR0_MSA_msk) != ID_AA64MMFR0_PMSA_EN) {
__ASSERT(0, "MPU not supported!\n");
return -1;
}
if (mpu_config.num_regions > get_num_regions()) {
/* Attempt to configure more MPU regions than
* what is supported by hardware. As this operation
* is executed during system (pre-kernel) initialization,
* we want to ensure we can detect an attempt to
* perform invalid configuration.
*/
__ASSERT(0,
"Request to configure: %u regions (supported: %u)\n",
mpu_config.num_regions,
get_num_regions());
return -1;
}
LOG_DBG("total region count: %d", get_num_regions());
arm_core_mpu_disable();
/* Architecture-specific configuration */
mpu_init();
/* Program fixed regions configured at SOC definition. */
for (r_index = 0U; r_index < mpu_config.num_regions; r_index++) {
region_init(r_index, &mpu_config.mpu_regions[r_index]);
}
/* Update the number of programmed MPU regions. */
static_regions_num = mpu_config.num_regions;
arm_core_mpu_enable();
return 0;
}
SYS_INIT(arm_mpu_init, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);