pebble/src/fw/drivers/mpu.c
2025-01-27 11:38:16 -08:00

226 lines
7.6 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "mpu.h"
#include "mcu/cache.h"
#include "system/passert.h"
#include "util/size.h"
#include <inttypes.h>
#include "FreeRTOS.h"
#include "task.h"
#include "portmacro.h"
#define CMSIS_COMPATIBLE
#include <mcu.h>
extern const uint32_t __SRAM_size__[];
#if !defined(SRAM_BASE)
// On the STM32F2, SRAM_BASE is not defined, but is equal to SRAM1_BASE
#define SRAM_BASE SRAM1_BASE
#endif
#define SRAM_END (SRAM_BASE + (uint32_t)__SRAM_size__)
typedef struct PermissionMapping {
bool priv_read:1;
bool priv_write:1;
bool user_read:1;
bool user_write:1;
uint8_t value:3;
} PermissionMapping;
static const PermissionMapping s_permission_mappings[] = {
{ false, false, false, false, 0x0 },
{ true, true, false, false, 0x1 },
{ true, true, true, false, 0x2 },
{ true, true, true, true, 0x3 },
{ true, false, false, false, 0x5 },
{ true, false, true, false, 0x6 },
{ true, false, true, false, 0x7 } // Both 0x6 and 0x7 map to the same permissions.
};
static const uint32_t s_cache_settings[MpuCachePolicyNum] = {
[MpuCachePolicy_NotCacheable] = (0x1 << MPU_RASR_TEX_Pos) | (MPU_RASR_S_Msk),
[MpuCachePolicy_WriteThrough] = (MPU_RASR_S_Msk | MPU_RASR_C_Msk),
[MpuCachePolicy_WriteBackWriteAllocate] =
(0x1 << MPU_RASR_TEX_Pos) | (MPU_RASR_S_Msk | MPU_RASR_C_Msk | MPU_RASR_B_Msk),
[MpuCachePolicy_WriteBackNoWriteAllocate] =
(MPU_RASR_S_Msk | MPU_RASR_C_Msk | MPU_RASR_B_Msk),
};
static uint8_t get_permission_value(const MpuRegion* region) {
for (unsigned int i = 0; i < ARRAY_LENGTH(s_permission_mappings); ++i) {
if (s_permission_mappings[i].priv_read == region->priv_read &&
s_permission_mappings[i].priv_write == region->priv_write &&
s_permission_mappings[i].user_read == region->user_read &&
s_permission_mappings[i].user_write == region->user_write) {
return s_permission_mappings[i].value;
}
}
WTF;
return 0;
}
static uint8_t get_size_field(const MpuRegion* region) {
unsigned int size = 32;
int result = 4;
while (size != region->size) {
PBL_ASSERT(size < region->size || size == 0x400000, "Invalid region size: %"PRIu32,
region->size);
size *= 2;
++result;
}
return result;
}
void mpu_enable(void) {
MPU->CTRL |= (MPU_CTRL_ENABLE_Msk | MPU_CTRL_PRIVDEFENA_Msk);
}
void mpu_disable(void) {
MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;
}
// Get the required region base address and region attribute register settings for the given region.
// These are the values which should written to the RBAR and RASR registers to configure that
// region.
void mpu_get_register_settings(const MpuRegion* region, uint32_t *base_address_reg,
uint32_t *attributes_reg) {
PBL_ASSERTN(region);
PBL_ASSERTN((region->base_address & 0x1f) == 0);
PBL_ASSERTN((region->region_num & ~0xf) == 0);
PBL_ASSERTN((region->cache_policy != MpuCachePolicy_Invalid) &&
(region->cache_policy < MpuCachePolicyNum));
// MPU Region Base Address Register
// | Addr (27 bits) | Region Valid Bit | Region Num (4 bits) |
// The address is unshifted, we take the top bits of the address and assume everything below
// is zero, since the address must be power of 2 size aligned.
*base_address_reg = region->base_address |
0x1 << 4 |
region->region_num;
// MPU Region Attribute and Size Register
// A lot of stuff here! Split into bytes...
// | Reserved (3 bits) | XN Bit | Reserved Bit | Permission Field (3 bits) |
// | Reserved (2 bits) | TEX (3 bits) | S | C | B |
// | Subregion Disable Byte |
// | Reserved (2 bits) | Size Field (5 bits) | Enable Bit |
*attributes_reg = (get_permission_value(region) << 24) |
s_cache_settings[region->cache_policy] |
region->disabled_subregions << 8 | // Disabled subregions
(get_size_field(region) << 1) |
region->enabled; // Enabled
}
void mpu_set_region(const MpuRegion* region) {
uint32_t base_reg, attr_reg;
mpu_get_register_settings(region, &base_reg, &attr_reg);
MPU->RBAR = base_reg;
MPU->RASR = attr_reg;
}
MpuRegion mpu_get_region(int region_num) {
MpuRegion region = { .region_num = region_num };
MPU->RNR = region_num;
const uint32_t attributes = MPU->RASR;
region.enabled = attributes & 0x1;
if (region.enabled) {
const uint8_t size_field = (attributes >> 1) & 0x1f;
region.size = 32 << (size_field - 4);
region.disabled_subregions = (attributes & 0x0000ff00) >> 8;
const uint32_t raw_base_address = MPU->RBAR;
region.base_address = raw_base_address & ~(region.size - 1);
const uint8_t access_permissions = (attributes >> 24) & 0x7;
for (unsigned int i = 0; i < ARRAY_LENGTH(s_permission_mappings); ++i) {
if (s_permission_mappings[i].value == access_permissions) {
region.priv_read = s_permission_mappings[i].priv_read;
region.priv_write = s_permission_mappings[i].priv_write;
region.user_read = s_permission_mappings[i].user_read;
region.user_write = s_permission_mappings[i].user_write;
break;
}
}
}
return region;
}
// Fill in the task parameters for a new task with the configurable memory regions we want.
void mpu_set_task_configurable_regions(MemoryRegion_t *memory_regions,
const MpuRegion **region_ptrs) {
unsigned int region_num, region_idx;
uint32_t base_reg, attr_reg;
// Setup the configurable MPU regions
for (region_num=portFIRST_CONFIGURABLE_REGION, region_idx=0; region_num <= portLAST_CONFIGURABLE_REGION;
region_num++, region_idx++) {
const MpuRegion *mpu_region = region_ptrs[region_idx];
MpuRegion unused_region = {};
// If not region defined, use unused
if (mpu_region == NULL) {
mpu_region = &unused_region;
attr_reg = 0; // Has a 0 in the enable bit, so this region won't be enabled.
} else {
// Make sure that the region numbers passed in jive with the configurable region numbers.
PBL_ASSERTN(mpu_region->region_num == region_num);
// Our FreeRTOS port makes the assumption that the ulParameters field contains exactly what
// should be placed into the MPU_RASR register. It will figure out the MPU_RBAR from the
// pvBaseAddress field.
mpu_get_register_settings(mpu_region, &base_reg, &attr_reg);
}
memory_regions[region_idx] = (MemoryRegion_t) {
.pvBaseAddress = (void *)mpu_region->base_address,
.ulLengthInBytes = mpu_region->size,
.ulParameters = attr_reg,
};
}
}
bool mpu_memory_is_cachable(const void *addr) {
if (!dcache_is_enabled()) {
return false;
}
// TODO PBL-37601: We're assuming only SRAM is cachable for now for simplicity sake. We should
// account for MPU configuration and also the fact that memory-mapped QSPI access goes through the
// cache.
return ((uint32_t)addr >= SRAM_BASE) && ((uint32_t)addr < SRAM_END);
}
void mpu_init_region_from_region(MpuRegion *copy, const MpuRegion *from, bool allow_user_access) {
*copy = *from;
copy->user_read = allow_user_access;
copy->user_write = allow_user_access;
}