4.Demo2: MPU使用,Privileged与Unprivileged
G0系列主要是用来替代F0系列的产品, 所以从Cortex M0到Cortex M0+算是一个升级. Cortex M0与Cortex M0+的区别很细微, 很多不注意细节的工程师可能还说不出来两者到底有什么差别. 其实ARM公司当初推出Cortex M0的初衷就是替换8bit, 16bit的产品, 但是很多市场的变化就是嵌入式软件复杂度一直在增加. 所以Cortex M0被删除的一些功能在Cortex M0+上又补回来了. 当然其中很多Cortex M0+新增的功能都是Optional, 即可实现也可不实现. 对设计软件的工程师而言Cortex M0+相对于Cortex M0的改变有如下几点比较重要:
- 1. 流水线从三级变成了两级, 这是为功耗与中断响应速度而考虑的改进
- 2. VTOR可选, Cortex M0的中断向量是不能有偏移量的, 这点设计过Bootloader的程序员会感受很深.
- 3. MPU可选, 做过RTOS的任务间隔离的程序员会感受很深.
- 4. 两级CPU权限:privileged和unprivileged两种权限状态. Cortex M0也有这两种权限, 只是两种权限完全一样, 所以等于没有.
这里做个Demo展示一下MPU与两级内核状态的使用. 设计目的: 有一个数组, 在用户态仅仅允许读, 在内核态可读可写. 这种设计在Cortex M0上基本上不可能. 在Cortex M0+的芯片如STM32G0F70上, 利用MPU和CPU权限可以轻易做到.
首先声明数组,配置MPU, 注意用的是数组声明是绝对定位:
#define ARRAY_ADDRESS_START (0x20002000UL)
#define ARRAY_SIZE MPU_REGION_SIZE_256B
#define ARRAY_REGION_NUMBER MPU_REGION_NUMBER3
uint8_t PrivilegedReadOnlyArray[32] __attribute__((at(ARRAY_ADDRESS_START)));
void MPU_Config(void) {
MPU_Region_InitTypeDef MPU_InitStruct = {0};
HAL_MPU_Disable();
/* Configure RAM region as Region N°0, 256KB of size and R/W region */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = EXAMPLE_RAM_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_RAM_SIZE;
MPU_InitStruct.AccessPermission = portMPU_REGION_READ_WRITE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = EXAMPLE_RAM_REGION_NUMBER;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure FLASH region as REGION N°1, 1MB of size and R/W region */
MPU_InitStruct.BaseAddress = EXAMPLE_FLASH_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_FLASH_SIZE;
MPU_InitStruct.Number = EXAMPLE_FLASH_REGION_NUMBER;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Configure Peripheral region as REGION N°2, 512MB of size, R/W and Execute
Never region */
MPU_InitStruct.BaseAddress = EXAMPLE_PERIPH_ADDRESS_START;
MPU_InitStruct.Size = EXAMPLE_PERIPH_SIZE;
MPU_InitStruct.Number = EXAMPLE_PERIPH_REGION_NUMBER;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable MPU (any access not covered by any enabled region will cause a fault) */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
void MPU_AccessPermConfig(void) {
MPU_Region_InitTypeDef MPU_InitStruct = {0};
HAL_MPU_Disable();
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = ARRAY_ADDRESS_START;
MPU_InitStruct.Size = ARRAY_SIZE;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = ARRAY_REGION_NUMBER;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* Enable MPU (any access not covered by any enabled region will cause a fault) */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
主函数中调用上述函数以使能MPU, 并且切换至用户状态:
MPU_Config();
MPU_AccessPermConfig();
__set_CONTROL(THREAD_MODE_UNPRIVILEGED | SP_MAIN);
__ISB();
接下来实验读写权限:
printf("%s %u\n", __func__, __LINE__);
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
这句没毛病, 只是读.
这句就会引起HardFault, 因为在用户状态这个数组是不允许写的.
PrivilegedReadOnlyArray[0] = (uint8_t)HAL_GetTick();
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
那么要写怎么办?
答案就是使用SVC指令进入内核权限.
asm_svc_2(HAL_GetTick());
printf("%s %u %02X\n", __func__, __LINE__, PrivilegedReadOnlyArray[0]);
注意asm_svc_2这个是汇编语言写的函数, 要放在另外的.s文件中,详情直接看后文的代码下载连接.
;Supervisor Call 2
ALIGN
asm_svc_2 FUNCTION
EXPORT asm_svc_2
SVC #2
bx lr
ENDP
注意要将SVC_Handler的声明改成有参数输入, 生成的代码是没有参数输入的. 按照AAPS的调用规约, 这个函数可以有输入参数. 为了求简便, 没有区分SVC号, 实际工程中这样就比较浪费SVC号了.
void SVC_Handler(uint32_t input){
PrivilegedReadOnlyArray[0] = (uint8_t)input;
}
这样就可以达到设计目的了.
6.参考与下载
ST官方G0系列产品页面 https://www.st.com/en/microcontrollers-microprocessors/stm32g0-series.html
本文所有代码, 注意每个实验一个branch: https://github.com/zhanzr/stm32g0-demo.git
|