398 lines
16 KiB
C
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
|