cortex-m: mpu: Support unaligned regions and protect code RAM

Support protection of regions that aren't aligned to a power of 2 by
using two MPU entries, and taking advantage of the sub-region feature.
Also protect code RAM from being overwritten, on parts that use external
storage.

BUG=chromium:782244
BRANCH=None
TEST=On kevin, call:
mpu_protect_data_ram();
mpu_protect_code_ram();
mpu_enable();
Verify that first call results in the following update_region params:
addr: 0x200c2000 size: 0xc01d
Decoded: Protect 24K region
Verify that second call results in the following params:
addr: 0x100a8000 size: 0xc021
Decoded: Protect 96K region
addr: 0x100c0000 size: 0xf01b
Decoded: Protect remaining 8K region
Also verify that writes to beginning and end of code ram region trigger
data access violation after enabling protection.
Also verify that sysjump fails.

Change-Id: Ieb7a4ec3a089e8a2d29f231e1e3acf2e78e560a1
Signed-off-by: Shawn Nematbakhsh <shawnn@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/757721
Commit-Ready: Shawn N <shawnn@chromium.org>
Tested-by: Shawn N <shawnn@chromium.org>
Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
This commit is contained in:
Shawn Nematbakhsh
2017-11-07 16:11:03 -08:00
committed by chrome-bot
parent 2a62a3dfca
commit b6991dd96d
4 changed files with 150 additions and 61 deletions

View File

@@ -41,9 +41,9 @@
#undef CONFIG_RAM_BASE
#define CONFIG_RAM_BASE (0x200C0000 + RAM_SHIFT_SIZE)
#undef CONFIG_RAM_SIZE
#define CONFIG_RAM_SIZE (0x00008000 - 0x800 - RAM_SHIFT_SIZE)
/* Region sizes are no longer a power of 2 so we can't enable MPU */
#undef CONFIG_MPU
#undef CONFIG_DATA_RAM_SIZE
#define CONFIG_DATA_RAM_SIZE (0x00008000 - RAM_SHIFT_SIZE)
#define CONFIG_RAM_SIZE (CONFIG_DATA_RAM_SIZE - 0x800)
/* Optional features */
#define CONFIG_BOARD_VERSION

View File

@@ -325,22 +325,33 @@ void system_disable_jump(void)
#ifdef CONFIG_MPU
if (system_is_locked()) {
int ret;
int enable_mpu = 0;
enum system_image_copy_t copy;
enum system_image_copy_t __attribute__((unused)) copy;
CPRINTS("MPU type: %08x", mpu_get_type());
/*
* Protect RAM from code execution
* Protect data RAM from code execution
*/
ret = mpu_protect_ram();
ret = mpu_protect_data_ram();
if (ret == EC_SUCCESS) {
enable_mpu = 1;
CPRINTS("RAM locked. Exclusion %08x-%08x",
CPRINTS("data RAM locked. Exclusion %08x-%08x",
&__iram_text_start, &__iram_text_end);
} else {
CPRINTS("Failed to lock RAM (%d)", ret);
CPRINTS("Failed to lock data RAM (%d)", ret);
return;
}
#ifdef CONFIG_EXTERNAL_STORAGE
/*
* Protect code RAM from being overwritten
*/
ret = mpu_protect_code_ram();
if (ret == EC_SUCCESS) {
CPRINTS("code RAM locked.");
} else {
CPRINTS("Failed to lock code RAM (%d)", ret);
return;
}
#else
/*
* Protect inactive image (ie. RO if running RW, vice versa)
* from code execution.
@@ -359,20 +370,21 @@ void system_disable_jump(void)
ret = !EC_SUCCESS;
}
if (ret == EC_SUCCESS) {
enable_mpu = 1;
CPRINTS("%s image locked",
system_image_copy_t_to_string(copy));
} else {
CPRINTS("Failed to lock %s image (%d)",
system_image_copy_t_to_string(copy), ret);
return;
}
#endif /* !CONFIG_EXTERNAL_STORAGE */
if (enable_mpu)
mpu_enable();
/* All regions were configured successfully, enable MPU */
mpu_enable();
} else {
CPRINTS("System is unlocked. Skip MPU configuration");
}
#endif
#endif /* CONFIG_MPU */
}
test_mockable enum system_image_copy_t system_get_image_copy(void)

View File

@@ -10,6 +10,26 @@
#include "common.h"
/*
* Region assignment. 7 as the highest, a higher index has a higher priority.
* For example, using 7 for .iram.text allows us to mark entire RAM XN except
* .iram.text, which is used for hibernation.
* Region assignment is currently wasteful and can be changed if more
* regions are needed in the future. For example, a second region may not
* be necessary for all types, and REGION_CODE_RAM / REGION_STORAGE can be
* made mutually exclusive.
*/
enum mpu_region {
REGION_DATA_RAM = 0, /* For internal data RAM */
REGION_DATA_RAM2 = 1, /* Second region for unaligned size */
REGION_CODE_RAM = 2, /* For internal code RAM */
REGION_CODE_RAM2 = 3, /* Second region for unaligned size */
REGION_STORAGE = 4, /* For mapped internal storage */
REGION_STORAGE2 = 5, /* Second region for unaligned size */
REGION_DATA_RAM_TEXT = 6, /* Exempt region of data RAM */
REGION_CHIP_RESERVED = 7, /* Reserved for use in chip/ */
};
#define MPU_TYPE REG32(0xe000ed90)
#define MPU_CTRL REG32(0xe000ed94)
#define MPU_NUMBER REG32(0xe000ed98)
@@ -60,10 +80,15 @@ extern char __iram_text_end;
/**
* Protect RAM from code execution
*/
int mpu_protect_ram(void);
int mpu_protect_data_ram(void);
/**
* Protect flash memory from code execution
* Protect code RAM from being overwritten
*/
int mpu_protect_code_ram(void);
/**
* Protect internal mapped flash memory from code execution
*/
int mpu_lock_ro_flash(void);
int mpu_lock_rw_flash(void);

View File

@@ -11,15 +11,6 @@
#include "task.h"
#include "util.h"
/* Region assignment. 7 as the highest, a higher index has a higher priority.
* For example, using 7 for .iram.text allows us to mark entire RAM XN except
* .iram.text, which is used for hibernation. */
enum mpu_region {
REGION_IRAM = 0, /* For internal RAM */
REGION_FLASH_MEMORY = 1, /* For flash memory */
REGION_IRAM_TEXT = 7 /* For *.(iram.text) */
};
/**
* Update a memory region.
*
@@ -28,11 +19,12 @@ enum mpu_region {
* size_bit: size of the region in power of two.
* attr: attribute bits. Current value will be overwritten if enable is true.
* enable: enables the region if non zero. Otherwise, disables the region.
* srd: subregion mask to partition region into 1/8ths, 0 = subregion enabled.
*
* Based on 3.1.4.1 'Updating an MPU Region' of Stellaris LM4F232H5QC Datasheet
*/
static void mpu_update_region(uint8_t region, uint32_t addr, uint8_t size_bit,
uint16_t attr, uint8_t enable)
uint16_t attr, uint8_t enable, uint8_t srd)
{
asm volatile("isb; dsb;");
@@ -41,7 +33,7 @@ static void mpu_update_region(uint8_t region, uint32_t addr, uint8_t size_bit,
if (enable) {
MPU_BASE = addr;
MPU_ATTR = attr;
MPU_SIZE = (size_bit - 1) << 1 | 1; /* Enable */
MPU_SIZE = (srd << 8) | ((size_bit - 1) << 1) | 1; /* Enable */
}
asm volatile("isb; dsb;");
@@ -56,44 +48,76 @@ static void mpu_update_region(uint8_t region, uint32_t addr, uint8_t size_bit,
* attr: Attribute bits. Current value will be overwritten if enable is set.
* enable: Enables the region if non zero. Otherwise, disables the region.
*
* Returns EC_SUCCESS on success or EC_ERROR_INVAL if a parameter is invalid.
* Returns EC_SUCCESS on success or -EC_ERROR_INVAL if a parameter is invalid.
*/
static int mpu_config_region(uint8_t region, uint32_t addr, uint32_t size,
uint16_t attr, uint8_t enable)
{
int size_bit = 0;
uint8_t blocks, srd1, srd2;
if (!size)
return EC_SUCCESS;
while (!(size & 1)) {
size_bit++;
size >>= 1;
}
/* Region size must be a power of 2 (size == 0) and equal or larger than
* 32 (size_bit >= 5) */
if (size > 1 || size_bit < 5)
/* Bit position of first '1' in size */
size_bit = 31 - __builtin_clz(size);
/* Min. region size is 32 bytes */
if (size_bit < 5)
return -EC_ERROR_INVAL;
mpu_update_region(region, addr, size_bit, attr, enable);
/* If size is a power of 2 then represent it with a single MPU region */
if (POWER_OF_TWO(size)) {
mpu_update_region(region, addr, size_bit, attr, enable, 0);
return EC_SUCCESS;
}
/* Sub-regions are not supported for region <= 128 bytes */
if (size_bit < 7)
return -EC_ERROR_INVAL;
/* Verify we can represent range with <= 2 regions */
if (size & ~(0x3f << (size_bit - 5)))
return -EC_ERROR_INVAL;
/*
* Round up size of first region to power of 2.
* Calculate the number of fully occupied blocks (block size =
* region size / 8) in the first region.
*/
blocks = size >> (size_bit - 2);
/* Represent occupied blocks of two regions with srd mask. */
srd1 = (1 << blocks) - 1;
srd2 = (1 << ((size >> (size_bit - 5)) & 0x7)) - 1;
/*
* Second region not supported for DATA_RAM_TEXT, also verify size of
* second region is sufficient to support sub-regions.
*/
if (srd2 && (region == REGION_DATA_RAM_TEXT || size_bit < 10))
return -EC_ERROR_INVAL;
/* Write first region. */
mpu_update_region(region, addr, size_bit + 1, attr, enable, ~srd1);
/*
* Second protection region (if necessary) begins at the first block
* we marked unoccupied in the first region.
* Size of the second region is the block size of first region.
*/
addr += (1 << (size_bit - 2)) * blocks;
/*
* Now represent occupied blocks in the second region. It's possible
* that the first region completely represented the occupied area, if
* so then no second protection region is required.
*/
if (srd2)
mpu_update_region(region + 1, addr, size_bit - 2, attr, enable,
~srd2);
return EC_SUCCESS;
}
/**
* Set a region non-executable and read-write.
*
* region: index of the region
* addr: base address of the region
* size: size of the region in bytes
* texscb: TEX and SCB bit field
*/
static int mpu_lock_region(uint8_t region, uint32_t addr, uint32_t size,
uint8_t texscb)
{
return mpu_config_region(region, addr, size,
MPU_ATTR_XN | MPU_ATTR_RW_RW | texscb, 1);
}
/**
* Set a region executable and read-write.
*
@@ -124,31 +148,59 @@ uint32_t mpu_get_type(void)
return MPU_TYPE;
}
int mpu_protect_ram(void)
int mpu_protect_data_ram(void)
{
int ret;
ret = mpu_lock_region(REGION_IRAM, CONFIG_RAM_BASE,
CONFIG_DATA_RAM_SIZE, MPU_ATTR_INTERNAL_SRAM);
/* Prevent code execution from data RAM */
ret = mpu_config_region(REGION_DATA_RAM,
CONFIG_RAM_BASE,
CONFIG_DATA_RAM_SIZE,
MPU_ATTR_XN |
MPU_ATTR_RW_RW |
MPU_ATTR_INTERNAL_SRAM,
1);
if (ret != EC_SUCCESS)
return ret;
ret = mpu_unlock_region(
REGION_IRAM_TEXT, (uint32_t)&__iram_text_start,
/* Exempt the __iram_text section */
return mpu_unlock_region(
REGION_DATA_RAM_TEXT, (uint32_t)&__iram_text_start,
(uint32_t)(&__iram_text_end - &__iram_text_start),
MPU_ATTR_INTERNAL_SRAM);
return ret;
}
#ifdef CONFIG_EXTERNAL_STORAGE
int mpu_protect_code_ram(void)
{
/* Prevent write access to code RAM */
return mpu_config_region(REGION_STORAGE,
CONFIG_PROGRAM_MEMORY_BASE + CONFIG_RO_MEM_OFF,
CONFIG_RO_SIZE,
MPU_ATTR_RO_NO | MPU_ATTR_INTERNAL_SRAM,
1);
}
#else
int mpu_lock_ro_flash(void)
{
return mpu_lock_region(REGION_FLASH_MEMORY, CONFIG_RO_MEM_OFF,
CONFIG_RO_SIZE, MPU_ATTR_FLASH_MEMORY);
/* Prevent execution from internal mapped RO flash */
return mpu_config_region(REGION_STORAGE,
CONFIG_MAPPED_STORAGE_BASE + CONFIG_RO_MEM_OFF,
CONFIG_RO_SIZE,
MPU_ATTR_XN | MPU_ATTR_RW_RW |
MPU_ATTR_FLASH_MEMORY, 1);
}
int mpu_lock_rw_flash(void)
{
return mpu_lock_region(REGION_FLASH_MEMORY, CONFIG_RW_MEM_OFF,
CONFIG_RW_SIZE, MPU_ATTR_FLASH_MEMORY);
/* Prevent execution from internal mapped RW flash */
return mpu_config_region(REGION_STORAGE,
CONFIG_MAPPED_STORAGE_BASE + CONFIG_RW_MEM_OFF,
CONFIG_RW_SIZE,
MPU_ATTR_XN | MPU_ATTR_RW_RW |
MPU_ATTR_FLASH_MEMORY, 1);
}
#endif /* !CONFIG_EXTERNAL_STORAGE */
int mpu_pre_init(void)
{