Exception Handling in Cortex-M 2

Nitroblue 1·2025년 9월 21일

Special Rules about 'privileged level'

  1. If unprivileged code attempts to read any stack pointer, the priority masks, or the IPSR, the read returns zero.

  2. The processor ignores writes from unprivileged Thread mode to any stack pointer, the EPSR, the IPSR, the masks, or CONTROL. If privileged Thread mode software writes a 1 to the CONTROL.nPRIV bit, the processor switches to unprivileged Thread mode execution, and ignores any further writes to special-purpose registers. After any Thread mode transition from privileged to unprivileged execution, software must issue an ISB instruction to ensure instruction fetch correctness.


CONTROL register

  • One of the most important special registers for context switching.
  • SPSEL [1]th bit : controls what stack pointer is in use!
  • nPriv [0]th bit : controls whether or not thread mode is operating as 'privileged' or 'unprivileged'. When set to 1, thread mode operates as unprivileged otherwise it operates as privileged.

Stack Pointers

  • The Cortex-M Architecture는 기본적으로 2개의 스택을 구현한다.

1. Main Stack (tracked in the msp reg)

  • 주로 Handler Mode(커널/예외 처리)에서 사용.
  • 리셋 후 부팅할 때 항상 활성화됨.
  • 초기값은 벡터 테이블의 첫 번째 워드에서 로드됨. (링커 스크립트의 _stack_start같은 것)
    What's Vector Table?
    • Cortex-M 계열 MCU는 리셋되거나 예외(interrupt)가 발생할 때, '어디로 실행을 점프해야 하는 지' 알아야 한다.
    • 이 정보를 모아둔 테이블이 바로 '벡터 테이블'이다.
    • 보통 플래시(0x0800_0000) 맨 앞 쪽에 위치한다.
    • VT Structure (각 엔트리는 32bit = 4Byte = Word)
      0x00 : 초기 MSP 값 (Main Stack Pointer 초기화 값)
      0x04 : Reset Handler 주소 (리셋 시 실행할 함수)
      0x08 : NMI Handler 주소
      0x0C : HardFault Handler 주소
      0x10~ : 그 외 예외/인터럽트 핸들러 주소들

      따라서, 초기값은 벡터 테이블의 첫 번째 워드에서 로드된다의 의미는 벡터 테이블의 첫 4바이트 = 초기 MSP 값이다. 즉, MCU가 전원을 켜면

      • MSP <- [0x0000_0000] : 벡터 테이블 첫 번째 값을 읽어서 MSP에 넣음
      • PC <- [0x0000_0004] : 벡터 테이블 두 번째 값을 읽어서 PC에 넣음 -> Reset_Handler로 점프.
벡터 테이블 (Flash 시작 주소)
주소      값 (Word)                의미
0x0800_0000  0x2002_0000   → 초기 MSP (스택 시작 주소, 보통 SRAM 끝)
0x0800_0004  0x0800_0101   → Reset_Handler (첫 실행 함수 주소)
0x0800_0008  ...           → NMI_Handler
0x0800_000C  ...           → HardFault_Handler
...

2. Process Stack (tracked in the psp reg)

  • 주로 Thread Mode(User task)에서 사용
  • OS가 process마다 PSP를 분리해주면, task별 독립적인 stack을 가질 수 있다!

SP(Active Stack Pointer)의 개념

  • CPU는 항상 '어느 스택을 active로 쓸지'를 관리한다.
  • sp를 읽거나 쓸 때, 사실은 msp 또는 psp중 현재 활성화된 것이 반환된다!

Handler Mode vs Thread Mode

  1. Handler Mode : 예외 처리 중, 즉 ISR 실행 중
  • 무조건 MSP 사용. psp는 쓰이지 않는다.
  1. Thread Mode : 일반 코드 실행 중, task 실행 중
  • 어느 stack을 쓸지 제어 가능. 2가지 방식으로 컨트롤 가능.
    1) CONTROL reg의 SPSEL bit
    - CONTROL.SPSEL = 0 -> MSP 사용
    - CONTROL.SPSEL = 1 -> PSP 사용
    2) exception return시 EXC_RETURN value (Role of EXC_RETURN에서 다룸)

    즉, OS가 PSP를 활성해줘야만 User Task가 자기 Stack 위에서 돌아간다.


Role of EXC_RETURN

  • 예외(Interrupt/PendSV 등)에서 복귀할 때, LR(링크 레지스터)에 특수한 값 EXC_RETURN이 들어간다.
  • 이 값이 CPU에게 "예외 복귀 후 어떤 모드와 어떤 스택을 쓸 지" 알려준다.
    0xFFFFFFF1 → Handler Mode | MSP
    0xFFFFFFF9 → Thread Mode | MSP
    0xFFFFFFFD → Thread Mode | PSP
    0xFFFFFFE1 → Handler Mode (FPU Extended Frame) | MSP
    0xFFFFFFE9 → Thread Mode (FPU Extended Frame) | MSP
    0xFFFFFFED → Thread Mode (FPU Extended Frame) | PSP

즉, OS는 Task Switching할 때 EXC_RETURN을 맞춰서 PSP 기반 Thread Mode로 내려줘야 한다.


최종 정리

  • Kernel은 MSP를 쓰고, Application은 PSP를 쓰도록 서로 분리해줘야 하며, 그렇지 않으면 모든 Taks가 커널 스택을 공유하게 되어 HardFault가 발생한다.

  • 따라서 PendSV Assembly Routine 안에서

  1. PSP에 현재 Task의 SP 저장
  2. PSP를 다음 Task의 SP로 교체
  3. CONTROL.SPSEL = 1, EXC_RETURN = 0xFFFFFFFD or 0xFFFFFFED로 복귀
    이 과정을 통해 커널(MSP) <-> User Task(PSP) 전환이 일어난다.

0개의 댓글