You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARM Cortex-M4启用MPU后切换至非特权模式触发硬故障求助

Cortex-M4 MPU启用后切换非特权模式触发硬故障(MUNSTKERR+IACCVIOL)

问题描述

我在ARM Cortex-M4设备上配置MPU,此前设备未启用MPU,全程运行在特权模式。启用MPU后切换至非特权模式时,触发硬故障,CFSR寄存器中MUNSTKERR和IACCVIOL位被置位,故障地址指向RAM中的大型静态uint8数组(0x20001900)。

测试验证

  • 特权模式下单独启用MPU,工作正常
  • 未启用MPU时,特权/非特权模式切换正常
  • 先切非特权模式再通过SVC启用MPU,仍触发故障,仅IACCVIOL标志消失

相关代码

MPU初始化函数

#define MPU_RBAR_ADDR_Pos                   5U                                            /*!< MPU RBAR: ADDR Position */
#define MPU_RBAR_ADDR_Msk                  (0x7FFFFFFUL << MPU_RBAR_ADDR_Pos)             /*!< MPU RBAR: ADDR Mask */
#define MPU_RBAR_VALID_Pos                  4U                                            /*!< MPU RBAR: VALID Position */
#define MPU_RBAR_VALID_Msk                 (1UL << MPU_RBAR_VALID_Pos)                    /*!< MPU RBAR: VALID Mask */
#define MPU_RBAR_REGION_Pos                 0U                                            /*!< MPU RBAR: REGION Position */
#define MPU_RBAR_REGION_Msk                (0xFUL /*<< MPU_RBAR_REGION_Pos*/)             /*!< MPU RBAR: REGION Mask */

#define MPU_AP_NOACCESS     0x0
#define MPU_AP_PRIVONLY     0x1
#define MPU_AP_USERREADONLY 0x2
#define MPU_AP_FULLACCESS   0x3
#define MPU_AP_READONLY     0x6
#define MPU_RASR_AP_NOACCESS     (MPU_AP_NOACCESS      << MPU_RASR_AP_Pos)              
#define MPU_RASR_AP_PRIVONLY     (MPU_AP_PRIVONLY      << MPU_RASR_AP_Pos)
#define MPU_RASR_AP_USERREADONLY (MPU_AP_USERREADONLY  << MPU_RASR_AP_Pos)
#define MPU_RASR_AP_FULLACCESS   (MPU_AP_FULLACCESS    << MPU_RASR_AP_Pos)
#define MPU_RASR_AP_READONLY     (MPU_AP_READONLY      << MPU_RASR_AP_Pos)

#define MPU_RASR_XN_Pos                    28U                                            /*!< MPU RASR: ATTRS.XN Position */
#define MPU_RASR_XN_Msk                    (1UL << MPU_RASR_XN_Pos)                       /*!< MPU RASR: ATTRS.XN Mask */
#define MPU_RASR_AP_Pos                    24U                                            /*!< MPU RASR: ATTRS.AP Position */
#define MPU_RASR_AP_Msk                    (0x7UL << MPU_RASR_AP_Pos)                     /*!< MPU RASR: ATTRS.AP Mask */
#define MPU_RASR_TEX_Pos                   19U                                            /*!< MPU RASR: ATTRS.TEX Position */
#define MPU_RASR_TEX_Msk                   (0x7UL << MPU_RASR_TEX_Pos)                    /*!< MPU RASR: ATTRS.TEX Mask */
#define MPU_RASR_S_Pos                     18U                                            /*!< MPU RASR: ATTRS.S Position */
#define MPU_RASR_S_Msk                     (1UL << MPU_RASR_S_Pos)                        /*!< MPU RASR: ATTRS.S Mask */
#define MPU_RASR_C_Pos                     17U                                            /*!< MPU RASR: ATTRS.C Position */
#define MPU_RASR_C_Msk                     (1UL << MPU_RASR_C_Pos)                        /*!< MPU RASR: ATTRS.C Mask */
#define MPU_RASR_B_Pos                     16U                                            /*!< MPU RASR: ATTRS.B Position */
#define MPU_RASR_B_Msk                     (1UL << MPU_RASR_B_Pos)                        /*!< MPU RASR: ATTRS.B Mask */
#define MPU_RASR_SRD_Pos                    8U                                            /*!< MPU RASR: Sub-Region Disable Position */
#define MPU_RASR_SRD_Msk                   (0xFFUL << MPU_RASR_SRD_Pos)                   /*!< MPU RASR: Sub-Region Disable Mask */

#define MPU_SIZE_64BYTES  0x05
#define MPU_RASR_SIZE_64BYTES  (MPU_SIZE_64BYTES  << MPU_RASR_SIZE_Pos)

#define MPU_RASR_ENABLE_TRUE  0x1
#define MPU_RASR_ENABLE_FALSE 0x0

#define MPU_CTRL_PRIVDEFENA_Pos             2U                                            /*!< MPU CTRL: PRIVDEFENA Position */
#define MPU_CTRL_PRIVDEFENA_Msk            (1UL << MPU_CTRL_PRIVDEFENA_Pos)               /*!< MPU CTRL: PRIVDEFENA Mask */
#define MPU_CTRL_ENABLE_Pos                 0U                                            /*!< MPU CTRL: ENABLE Position */
#define MPU_CTRL_ENABLE_Msk                (1UL /*<< MPU_CTRL_ENABLE_Pos*/)               /*!< MPU CTRL: ENABLE Mask */

void MPU_Registers_Init(uint32_t addr)
{
    MPU_Type *MpuReg = MPU;
    uint32_t base_addr = addr;     
    uint32_t region_number = 0x0;               // from 0~7 8 region available
   
    MPU->RBAR |= base_addr & MPU_RBAR_ADDR_Msk;
    MPU->RBAR |= ( 0x0u << MPU_RBAR_VALID_Pos) & MPU_RBAR_VALID_Msk;
    MPU->RBAR |= ( region_number << MPU_RBAR_REGION_Pos) & MPU_RBAR_REGION_Msk;

    uint32_t xn = 0x1u;                         // instruction fetches disabled  
    
    uint32_t tex = 0x000u;                      
    uint32_t c = 0x1u;                                                          
    uint32_t b = 0x0u;                          // no write-back
    uint32_t s = 0x1u;                          // shareable
    
    uint32_t srd = 0xFF;                        // disable Subregion
    
    MPU->RASR |= (xn << MPU_RASR_XN_Pos) & MPU_RASR_XN_Msk;
    
    MPU->RASR |= MPU_RASR_AP_USERREADONLY;              //p:RW up:RO    
    
    MPU->RASR |= (tex << MPU_RASR_TEX_Pos) & MPU_RASR_TEX_Msk;
    MPU->RASR |= (s << MPU_RASR_S_Pos) & MPU_RASR_S_Msk;
    MPU->RASR |= (c << MPU_RASR_C_Pos) & MPU_RASR_C_Msk;
    MPU->RASR |= (b << MPU_RASR_B_Pos) & MPU_RASR_B_Msk;
    MPU->RASR |= (srd << MPU_RASR_SRD_Pos) & MPU_RASR_SRD_Msk;
    
    MPU->RASR |= MPU_RASR_SIZE_64BYTES;
    MPU->RASR |= MPU_RASR_ENABLE_TRUE;
    
    MPU->CTRL |=  (0x01u << MPU_CTRL_PRIVDEFENA_Pos) & MPU_CTRL_PRIVDEFENA_Msk; 
    MPU->CTRL |=  (0x01u << MPU_CTRL_ENABLE_Pos) & MPU_CTRL_ENABLE_Msk;
}

非特权模式切换代码

uint32_t RegControl = __get_CONTROL();              
__set_CONTROL( RegControl | CONTROL_nPRIV_Msk );

故障原因与修复方案

核心原因

  1. MUNSTKERR:切换非特权模式后,CPU使用的栈指针(默认MSP)所在内存区域未配置非特权访问权限,导致栈操作被MPU拦截。
  2. IACCVIOL:代码执行区域未配置非特权模式下的指令获取权限,或MPU配置误将代码区域标记为XN(禁止指令获取)。
  3. 原有MPU配置无效:仅配置了一个64字节区域,但设置SRD=0xFF(禁用所有子区域),等于该区域未启用;且开启PRIVDEFENA仅对特权模式生效,非特权模式无默认访问权限。

修复步骤

1. 配置栈区域的MPU权限

确保非特权模式能访问栈内存:

// 配置栈区域示例(假设栈起始地址0x20000000,大小1KB)
void MPU_Config_Stack(void)
{
    // 先关闭MPU避免配置过程触发异常
    MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

    // 配置RBAR:区域1,栈基地址对齐到32字节边界
    MPU->RBAR = (0x20000000 & MPU_RBAR_ADDR_Msk) | MPU_RBAR_VALID_Msk | 1;
    // 配置RASR:全权限访问,1KB大小,启用区域
    MPU->RASR = MPU_RASR_AP_FULLACCESS | 
                (9 << MPU_RASR_SIZE_Pos) | // 2^(9+1)=1024字节=1KB
                MPU_RASR_ENABLE_TRUE;

    // 重新启用MPU
    MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

2. 配置代码区域的MPU权限

确保非特权模式能执行Flash中的代码:

// 配置Flash代码区域示例(起始地址0x08000000,大小64KB)
void MPU_Config_Code(void)
{
    MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

    MPU->RBAR = (0x08000000 & MPU_RBAR_ADDR_Msk) | MPU_RASR_VALID_Msk | 2;
    MPU->RASR = (0 << MPU_RASR_XN_Pos) | // 允许指令获取
                MPU_RASR_AP_FULLACCESS |
                (15 << MPU_RASR_SIZE_Pos) | // 2^(15+1)=65536字节=64KB
                MPU_RASR_ENABLE_TRUE;

    MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

3. 修复原有MPU初始化函数

将寄存器赋值方式从|=改为直接赋值,避免残留旧值;同时启用子区域:

void MPU_Registers_Init(uint32_t addr)
{
    MPU_Type *MpuReg = MPU;
    uint32_t base_addr = addr;     
    uint32_t region_number = 0x0;

    // 先关闭MPU
    MPU->CTRL &= ~MPU_CTRL_ENABLE_Msk;

    // 直接赋值RBAR,避免旧值干扰
    MPU->RBAR = (base_addr & MPU_RBAR_ADDR_Msk) | 
                (0x0u << MPU_RBAR_VALID_Pos) | 
                (region_number << MPU_RBAR_REGION_Pos);

    // 构建RASR完整值
    uint32_t rasr_val = 0;
    rasr_val |= (0x1u << MPU_RASR_XN_Pos);                         // 禁止指令获取(适合数据区域)
    rasr_val |= MPU_RASR_AP_USERREADONLY;                          // 特权RW,非特权RO
    rasr_val |= (0x0 << MPU_RASR_TEX_Pos);
    rasr_val |= (0x1u << MPU_RASR_S_Pos);
    rasr_val |= (0x1u << MPU_RASR_C_Pos);
    rasr_val |= (0x0u << MPU_RASR_B_Pos);
    rasr_val |= (0x0 << MPU_RASR_SRD_Pos); // 启用所有子区域
    rasr_val |= MPU_RASR_SIZE_64BYTES;
    rasr_val |= MPU_RASR_ENABLE_TRUE;

    MPU->RASR = rasr_val;

    // 开启特权默认区域并启用MPU
    MPU->CTRL |= MPU_CTRL_PRIVDEFENA_Msk | MPU_CTRL_ENABLE_Msk;
}

4. 规范非特权模式切换流程

切换前确保栈指针配置正确,添加指令同步屏障:

// 示例:使用PSP作为非特权模式栈指针
__set_PSP((uint32_t)&_estack); // _estack为链接脚本中定义的栈顶符号
uint32_t RegControl = __get_CONTROL();
RegControl |= CONTROL_SPSEL_Msk; // 选择PSP作为当前栈指针
RegControl |= CONTROL_nPRIV_Msk;
__set_CONTROL(RegControl);
__ISB(); // 指令同步屏障,确保控制寄存器修改生效

关键注意事项

  • 非特权模式下,所有需要访问的内存区域(栈、代码、数据、外设)必须显式配置MPU权限,PRIVDEFENA仅对特权模式生效。
  • 配置MPU时建议先关闭MPU,完成配置后再开启,避免配置过程中触发异常。
  • SRD位是8位掩码,每一位对应一个子区域,0xFF表示全部禁用,需根据需求设置(0x00启用所有子区域)。

内容的提问来源于stack exchange,提问作者Xiao Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 03:22:04