rt/arch/arm/include/rt/arch/mpu.h

398 lines
16 KiB
C

#pragma once
#include <rt/mpu.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef RT_MPU_NUM_REGIONS
#define RT_MPU_NUM_REGIONS 8
#endif
#ifndef RT_MPU_NUM_TASK_REGIONS
#define RT_MPU_NUM_TASK_REGIONS 4
#endif
#if RT_MPU_NUM_TASK_REGIONS < 1
#error "At least one per-task region is required for the stack."
#endif
// Per-task regions have higher region IDs so they can override static regions.
#define RT_MPU_TASK_REGION_START_ID \
(RT_MPU_NUM_REGIONS - RT_MPU_NUM_TASK_REGIONS)
#if !defined(__ASSEMBLER__)
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define RT_MPU_CTRL_ENABLE (UINT32_C(1) << 0)
#define RT_MPU_CTRL_HFNMI_ENABLE (UINT32_C(1) << 1)
#define RT_MPU_CTRL_PRIVDEF_ENABLE (UINT32_C(1) << 2)
#if __ARM_ARCH_PROFILE == 'R'
#include <../r/coprocessor.h>
#elif __ARM_ARCH_PROFILE == 'M'
#define RT_MPU_REGS ((volatile struct rt_mpu *)0xE000ED90UL)
#endif
#if __ARM_ARCH == 6 || __ARM_ARCH == 7
/*
* In armv{6,7}, MPU regions have power-of-2 sizes with 8 subregions of equal
* size. armv6 supports regions with size 256 or greater, and armv7 supports
* regions with size 32 or greater, but only supports subregions for regions
* with size 256 or greater. An MPU region of size 32 can be achieved with a
* 256-byte region and one active subregion, so just use >= 256 bytes always.
* Let SIZE_MAX represent the maximum-size region (4GB), even though it is
* off-by-one.
*/
#define RT_MPU_SIZEBITS(n) \
(((n) <= 256) ? UINT32_C(7) \
: ((n) == SIZE_MAX) ? UINT32_C(31) \
: (UINT32_C(30) - (uint32_t)__builtin_clzl(n)))
#define RT_MPU_REGION_SIZE(n) (UINT32_C(1) << (RT_MPU_SIZEBITS(n) + 1))
#define RT_MPU_SUBREGION_SIZE(n) (RT_MPU_REGION_SIZE(n) / 8)
/*
* Only use subregions when the size is known at compile-time, to avoid
* run-time division during configurations. Static task stacks should have this
* property. Also require n > 0 because the calculations don't work for n == 0.
*/
#define RT_MPU_USE_SUBREGIONS(n) \
(__builtin_constant_p(n) && ((n) > 0) && ((n) != SIZE_MAX))
#define RT_MPU_SUBREGIONS(n) \
(RT_MPU_USE_SUBREGIONS(n) ? ((((n)-1) / RT_MPU_SUBREGION_SIZE(n)) + 1) : 8)
/* Calculate in which subregion an address resides, given the region size. */
#define RT_MPU_SUBREGION_OFFSET(a, n) \
((((a) & ~(RT_MPU_SUBREGION_SIZE(n) - 1)) - \
((a) & ~(RT_MPU_REGION_SIZE(n) - 1))) / \
RT_MPU_SUBREGION_SIZE(n))
#define RT_MPU_SRD_PREFIX(o) ((UINT32_C(1) << (o)) - 1)
#define RT_MPU_SRD_SUFFIX(o) \
((~((UINT32_C(1) << ((o) + 1)) - 1)) & UINT32_C(0xFF))
#define RT_MPU_SRD(a, n) \
(RT_MPU_USE_SUBREGIONS(n) \
? (RT_MPU_SRD_PREFIX(RT_MPU_SUBREGION_OFFSET((a), (n))) | \
RT_MPU_SRD_SUFFIX(RT_MPU_SUBREGION_OFFSET((a) + (n)-UINT32_C(1), \
(n)))) \
: UINT32_C(0))
#define RT_MPU_ALIGN(n) \
(((n) == SIZE_MAX) ? UINT64_C(0x100000000) \
: RT_MPU_SUBREGIONS(n) > 4 ? RT_MPU_REGION_SIZE(n) \
: RT_MPU_SUBREGIONS(n) > 2 \
? 4 * RT_MPU_SUBREGION_SIZE(n) \
: RT_MPU_SUBREGIONS(n) * RT_MPU_SUBREGION_SIZE(n))
#define RT_MPU_SIZE(n) \
(((n) == SIZE_MAX) ? UINT64_C(0x100000000) \
: RT_MPU_SUBREGION_SIZE(n) * RT_MPU_SUBREGIONS(n))
struct rt_mpu_region
{
uint32_t base_addr;
uint32_t attr_size;
};
struct rt_mpu_config
{
struct rt_mpu_region regions[RT_MPU_NUM_TASK_REGIONS];
};
#if __ARM_ARCH == 7
// v7-m implements alias registers, but v6-m does not.
#define RT_MPU_NUM_REGION_REGS 4
#else
#define RT_MPU_NUM_REGION_REGS 1
#endif
struct rt_mpu
{
uint32_t type;
uint32_t ctrl;
uint32_t number;
struct rt_mpu_region regions[RT_MPU_NUM_REGION_REGS];
};
#define RT_MPU_ATTR_XN (UINT32_C(1) << 28)
#define RT_MPU_ATTR_AP(p) ((uint32_t)(p) << 24)
#define RT_MPU_ATTR_NO_ACCESS RT_MPU_ATTR_AP(0)
#define RT_MPU_ATTR_RW_PRIV RT_MPU_ATTR_AP(1)
#define RT_MPU_ATTR_RW_PRIV_RO RT_MPU_ATTR_AP(2)
#define RT_MPU_ATTR_RW RT_MPU_ATTR_AP(3)
#define RT_MPU_ATTR_RO_PRIV RT_MPU_ATTR_AP(5)
#define RT_MPU_ATTR_RO RT_MPU_ATTR_AP(6)
#define RT_MPU_ATTR(tex_cb) ((uint32_t)(tex_cb) << 16)
#define RT_MPU_ATTR_STRONGLY_ORDERED RT_MPU_ATTR(0)
#define RT_MPU_ATTR_SHARED_DEVICE RT_MPU_ATTR(1)
#define RT_MPU_ATTR_CACHED_WT RT_MPU_ATTR(2)
#define RT_MPU_ATTR_CACHED_WB RT_MPU_ATTR(3)
#define RT_MPU_ATTR_NONCACHED RT_MPU_ATTR(8)
#define RT_MPU_ATTR_CACHED_WB_RWALLOC RT_MPU_ATTR(11)
#define RT_MPU_ATTR_DEVICE RT_MPU_ATTR(16)
#define RT_MPU_ATTR_CACHED(outer, inner) \
RT_MPU_ATTR((UINT32_C(1) << 5) | ((outer) << 3) | (inner))
#define RT_MPU_ATTR_DOMAIN_CACHED_NONE 0
#define RT_MPU_ATTR_DOMAIN_CACHED_WBWA 1
#define RT_MPU_ATTR_DOMAIN_CACHED_WT 2
#define RT_MPU_ATTR_DOMAIN_CACHED_WBNWA 3
// The shared attribute only has an effect for normal memory.
#define RT_MPU_ATTR_SHARED (UINT32_C(1) << 18)
#define RT_MPU_ATTR_ENABLE (UINT32_C(1) << 0)
#define RT_MPU_STACK_ATTR \
(RT_MPU_ATTR_RW | RT_MPU_ATTR_XN | RT_MPU_ATTR_CACHED_WB_RWALLOC | \
RT_MPU_ATTR_ENABLE)
#define RT_MPU_PERIPHERAL_ATTR \
(RT_MPU_ATTR_XN | RT_MPU_ATTR_RW | RT_MPU_ATTR_DEVICE | RT_MPU_ATTR_ENABLE)
#define RT_MPU_ATTR_SIZE(addr, size, attr) \
(RT_MPU_SIZEBITS(size) << 1 | RT_MPU_SRD((addr), (size)) << 8 | \
((attr) & ~(((size) == 0) ? RT_MPU_ATTR_ENABLE : UINT32_C(0))))
#define RT_MPU_REGION(addr, size, attr) \
{ \
.base_addr = (uintptr_t)(addr), \
.attr_size = RT_MPU_ATTR_SIZE((uintptr_t)(addr), size, attr), \
}
static inline void rt_mpu_region_set(uint32_t id, uintptr_t addr, size_t size,
uint32_t attr)
{
#if __ARM_ARCH_PROFILE == 'R'
rgnr_set(id);
drbar_set(addr);
const uint32_t attr_size = RT_MPU_ATTR_SIZE(addr, size, attr);
// The size, subregion disable, and enable bit are in drsr.
drsr_set(attr_size & UINT32_C(0xFFFF));
dracr_set(attr_size >> 16);
#elif __ARM_ARCH_PROFILE == 'M'
#define RT_MPU_REGION_ID_VALID (UINT32_C(1) << 4)
#define RT_MPU_REGION_ID_MASK (UINT32_C(0xF))
RT_MPU_REGS->regions[0].base_addr =
addr | (RT_MPU_REGION_ID_MASK & id) | RT_MPU_REGION_ID_VALID;
RT_MPU_REGS->regions[0].attr_size = RT_MPU_ATTR_SIZE(addr, size, attr);
#endif
}
#elif __ARM_ARCH == 8
/*
* In armv8, MPU regions are defined by start and end addresses that are
* multiples of 32 bytes.
*/
#define RT_MPU_ALIGN(n) 32UL
struct rt_mpu_region
{
uint32_t base_addr;
uint32_t limit_addr;
};
struct rt_mpu_config
{
struct rt_mpu_region regions[RT_MPU_NUM_TASK_REGIONS];
};
#ifdef __ARM_ARCH_8M_BASE__
#define RT_MPU_NUM_REGION_REGS 1
#define RT_MPU_NUM_RESERVED 4
#else
#define RT_MPU_NUM_REGION_REGS 4
#define RT_MPU_NUM_RESERVED 1
#endif
struct rt_mpu
{
uint32_t type;
uint32_t ctrl;
uint32_t number;
struct rt_mpu_region regions[RT_MPU_NUM_REGION_REGS];
uint32_t reserved[RT_MPU_NUM_RESERVED];
uint32_t attr_indirect[2];
};
#define RT_MPU_ATTR_XN (UINT32_C(1) << 0)
#define RT_MPU_ATTR_AP(p) ((uint32_t)(p) << 1)
#define RT_MPU_ATTR_RW_PRIV RT_MPU_ATTR_AP(0)
#define RT_MPU_ATTR_RW RT_MPU_ATTR_AP(1)
#define RT_MPU_ATTR_RO_PRIV RT_MPU_ATTR_AP(2)
#define RT_MPU_ATTR_RO RT_MPU_ATTR_AP(3)
#define RT_MPU_ATTR_SH(sh) ((uint32_t)(sh) << 3)
#define RT_MPU_ATTR_OUTER_SHAREABLE RT_MPU_ATTR_SH(2)
#define RT_MPU_ATTR_SHARED RT_MPU_ATTR_OUTER_SHAREABLE
#define RT_MPU_ATTR_INNER_SHAREABLE RT_MPU_ATTR_SH(3)
/* These attribute fields are in the limit_addr register, but to allow them to
* be passed as part of a single attribute argument, shift them up by 5 bits. */
#define RT_MPU_ATTR_RLAR_SHIFT 5
#define RT_MPU_ATTR_MASK (UINT32_C(0x1F))
#define RT_MPU_ADDR_MASK (~RT_MPU_ATTR_MASK)
#define RT_MPU_ATTR_ENABLE (UINT32_C(1) << (0 + RT_MPU_ATTR_RLAR_SHIFT))
#define RT_MPU_ATTR_INDEX(index) \
((uint32_t)(index) << (1 + RT_MPU_ATTR_RLAR_SHIFT))
#define RT_MPU_ATTR_PXN (UINT32_C(1) << (4 + RT_MPU_ATTR_RLAR_SHIFT))
#define RT_MPU_ATTR_INDIRECT(outer, inner) \
((uint32_t)(outer) << 4 | (uint32_t)(inner))
#define RT_MPU_ATTR_DEVICE(gre) RT_MPU_ATTR_INDIRECT(0, (gre) << 2)
#define RT_MPU_ATTR_DEVICE_NGNRNE RT_MPU_ATTR_DEVICE(0)
#define RT_MPU_ATTR_DEVICE_NGNRE RT_MPU_ATTR_DEVICE(1)
#define RT_MPU_ATTR_DEVICE_NGRE RT_MPU_ATTR_DEVICE(2)
#define RT_MPU_ATTR_DEVICE_GRE RT_MPU_ATTR_DEVICE(3)
#define RT_MPU_ATTR_WT_TRANSIENT(r, w) ((uint32_t)(r) << 1 | (uint32_t)(w))
#define RT_MPU_ATTR_NONCACHEABLE RT_MPU_ATTR_INDIRECT(0x4, 0x4)
#define RT_MPU_ATTR_WB_TRANSIENT(r, w) \
(UINT32_C(0x4) | (uint32_t)(r) << 1 | (uint32_t)(w))
#define RT_MPU_ATTR_WB_RALLOC \
RT_MPU_ATTR_INDIRECT(RT_MPU_ATTR_WB_TRANSIENT(1, 0), \
RT_MPU_ATTR_WB_TRANSIENT(1, 0))
#define RT_MPU_ATTR_WB_RWALLOC \
RT_MPU_ATTR_INDIRECT(RT_MPU_ATTR_WB_TRANSIENT(1, 1), \
RT_MPU_ATTR_WB_TRANSIENT(1, 1))
#define RT_MPU_ATTR_WT(r, w) \
(UINT32_C(0xC) | (uint32_t)(r) << 1 | (uint32_t)(w))
#define RT_MPU_ATTR_WT_RALLOC \
RT_MPU_ATTR_INDIRECT(RT_MPU_ATTR_WT(1, 0), RT_MPU_ATTR_WT(1, 0))
#define RT_MPU_ATTR_WT_RWALLOC \
RT_MPU_ATTR_INDIRECT(RT_MPU_ATTR_WT(1, 1), RT_MPU_ATTR_WT(1, 1))
/* The preset MPU attributes for stack and peripherals will use indirect
* attributes 0 and 1 respectively. */
#define RT_MPU_STACK_ATTR \
(RT_MPU_ATTR_INDEX(0) | RT_MPU_ATTR_RW | RT_MPU_ATTR_XN | \
RT_MPU_ATTR_PXN | RT_MPU_ATTR_ENABLE)
#define RT_MPU_PERIPHERAL_ATTR \
(RT_MPU_ATTR_INDEX(1) | RT_MPU_ATTR_XN | RT_MPU_ATTR_RW | \
RT_MPU_ATTR_PXN | RT_MPU_ATTR_ENABLE)
#define RT_MPU_BASE_ADDR(addr, attr) \
(((addr) & RT_MPU_ADDR_MASK) | ((attr) & RT_MPU_ATTR_MASK))
#define RT_MPU_LIMIT_ADDR(addr, size, attr) \
((((addr) + (size)-1) & RT_MPU_ADDR_MASK) | \
((((attr) & ~(((size) == 0) ? RT_MPU_ATTR_ENABLE : UINT32_C(0))) >> \
RT_MPU_ATTR_RLAR_SHIFT) & \
RT_MPU_ATTR_MASK))
#define RT_MPU_REGION(addr, size, attr) \
{ \
.base_addr = RT_MPU_BASE_ADDR((uintptr_t)(addr), attr), \
.limit_addr = RT_MPU_LIMIT_ADDR((uintptr_t)(addr), size, attr), \
}
static inline void rt_mpu_region_set(uint32_t id, uintptr_t addr, size_t size,
uint32_t attr)
{
RT_MPU_REGS->number = id;
RT_MPU_REGS->regions[0].base_addr = RT_MPU_BASE_ADDR(addr, attr);
RT_MPU_REGS->regions[0].limit_addr = RT_MPU_LIMIT_ADDR(addr, size, attr);
}
static inline void rt_mpu_attr_init(void)
{
RT_MPU_REGS->attr_indirect[0] = 0;
RT_MPU_REGS->attr_indirect[1] = 0;
}
static inline void rt_mpu_attr_set(uint32_t index, uint32_t attr)
{
volatile uint32_t *const mair = &RT_MPU_REGS->attr_indirect[index / 4];
const int shift = (index % 4) * 8;
const uint32_t mask = UINT32_C(0xFF) << shift;
*mair = (*mair & ~mask) | (attr << shift);
}
#else /* __ARM_ARCH */
#error "Unsupported __ARM_ARCH for MPU configuration."
#endif
static inline void rt_mpu_enable(void)
{
#if __ARM_ARCH_PROFILE == 'R'
sctlr_oreq(SCTLR_M | SCTLR_BR);
#elif __ARM_ARCH_PROFILE == 'M'
RT_MPU_REGS->ctrl = RT_MPU_CTRL_ENABLE | RT_MPU_CTRL_HFNMI_ENABLE |
RT_MPU_CTRL_PRIVDEF_ENABLE;
#if __ARM_ARCH == 8 && !defined(__ARM_ARCH_8M_BASE__) && \
(RT_MPU_NUM_TASK_REGIONS <= RT_MPU_NUM_REGION_REGS)
/* Once the MPU is enabled, set the region number to the offset that will
* be used for context switches because the region number is not part of
* the region config on v8-m like it is in v7-m. On v8-m.base, or if there
* are more task regions than region registers, we need to set the region
* number while reconfiguring anyway, so don't bother setting an offset up
* front. Mask off the lower two bits so that the non-alias configuration
* registers always refer to the region ID that is a multiple of 4. */
RT_MPU_REGS->number = RT_MPU_TASK_REGION_START_ID & ~UINT32_C(0x3);
#endif // v8-m && !v8-m.base && task regions <= region regs
#endif // __ARM_ARCH_PROFILE
__asm__("dsb; isb" ::: "memory");
}
#define RT_MPU_PRIV_BSS(name) __attribute__((section(".priv_bss." #name)))
#define RT_MPU_PRIV_DATA(name) __attribute__((section(".priv_data." #name)))
#if __ARM_ARCH_PROFILE == 'M' && __ARM_ARCH <= 7
/* In armv6-m and armv7-m, the MPU region configurations must also contain
* the region ID and an ID valid bit, otherwise the region number register
* that was last set will be used when modifying the region. */
#define RT_MPU_CONFIG_POSTINIT(config) \
do \
{ \
for (size_t i = 0; i < RT_MPU_NUM_TASK_REGIONS; ++i) \
{ \
const uint32_t id = RT_MPU_TASK_REGION_START_ID + i; \
(config)->regions[i].base_addr |= \
(RT_MPU_REGION_ID_MASK & id) | RT_MPU_REGION_ID_VALID; \
} \
} while (0)
#else // __ARM_ARCH_PROFILE != 'M' || __ARM_ARCH >= 8
#define RT_MPU_CONFIG_POSTINIT(config) \
do \
{ \
} while (0)
#endif
#define RT_MPU_CONFIG_INIT(config, ...) \
do \
{ \
const struct rt_mpu_region regions[] = {__VA_ARGS__}; \
for (size_t i = 0; i < sizeof regions / sizeof regions[0]; ++i) \
{ \
(config)->regions[i] = regions[i]; \
} \
RT_MPU_CONFIG_POSTINIT(config); \
} while (0)
#endif // !defined(__ASSEMBLER__)
#ifdef __cplusplus
}
#endif