Interrupt Handling Basics

Polling / Interrupt

polling과 interrupt는 동작하는 방식이 다르다.

polling은 어떤 event가 발생하진 않았는지 CPU가 주기적으로 확인을 한다.
하지만 interrupt는 외부에서 어떤 event가 발생하면 외부에서 CPU로 신호를 직접 보내준다.

ARMv7-M Interrupt Handling

하나의 non-maskable interrupt(NMI)를 지원한다.

우선순위를 지정할 수 있는 512개의 interrupt/exception(496개의 interrupt, 16개의 exception)을 지원한다.
interrupt는 mask될 수 있고 implementation option이 지원되는 interrupt의 수를 선택한다.

nested vectored interrupt controller(NVIC)는 processor core의 바로 옆에 붙어있는 형태이다.
NVIC에 exception이나 interrupt signal이 들어가면 그걸 core에게 전달한다.
그럼 core가 전달받은 interrupt나 exception을 handle할 수 있다.

Interrupt Service Routine Vector Table

interrupt service routine vector table의 첫번재 entry는 initial main sp를 담고 있다 (이전에 잠깐 언급된 적이 있다).

다른 모든 entry들은 exception/interrupt handler의 address를 담고 있다.
handler address의 LSB는 반드시 1 이어야 한다. Thumb mode이기 때문이다 (실제로는 짝수에 align되어있지만 주소는 +1해서 LSB를 1로 만들어 나타냄).

이 table은 relocate될 수 있다 (반드시 0x00에 위치하는 것이 아니다).
vector table offset register를 이용하면 된다.
하지만 core를 booting하기 위해 최소한의 table은 0x00에 위치해야한다.

또한 이 table은 C로도 코딩할 수 있다. 어셈블리로 굳이 코딩 안해도 됨.

Interrupt Service Routine Vector Table of Cortex-M Processors

exception number가 priority를 의미하는 것은 아니다.
또한 priority 숫자가 작을수록 우선순위가 높다.

Interrupt Handling Process

그림만 잘 읽어봐도 이해가 된다..

stacking과 unstacking은 이전 포스트에서 다뤘던 subroutine을 실행하기 전에 레지스터의 값을 save하는 것과 return될 때 restore하는 것을 의미한다.

Stacking / Unstacking

현재 mode (Thread mode 혹은 Handler mode)의 stack이 stacking/unstacking을 위해 사용된다.
thread mode와 handler mode 모두 interrupt가 발생할 수 있기 때문이다.
processor은 interrupt handler가 실행되기 전에 자동으로 이 8개의 register를 push하고(stacking), interrupt handler에서 exit할 때 8개의 register를 pop한다(unstacking).
위의 그림을 보자.
user program은 thread mode에서 실행된다.

interrupt signal이 발생했을 때, handler mode의 interrupt handler를 실행하기 전에 stacking을 통해 register의 값을 push하였다.
이후 interrupt handler를 실행한 다음 exit하고 나서 stacking 해두었던 register 값들을 restore하는 unstacking을 하였다.
위의 그림을 보자.
handler mode에서 handler program이 실행되고 있었다.

interrupt signal이 발생하면, interrupt handler가 실행되기 전에 register의 값들을 자신의 stack에 stacking한다.
stacking이 끝나면 interrupt handler를 실행하고, exit하고 나면 다시 stacking했던 값들을 unstacking한다.

Exception Exits

interrupt handler exit를 감지해야 unstacking을 할 수 있는데 interrupt handler exit을 어떻게 감지하는 걸까?

exception handler 실행 마지막에 EXC_RETURNPC에 load되면 processor는 exception return sequence를 수행한다.

exception이 발생했을 때 EXC_RETURN은 자동으로 generate되어 LR에 set된다.

아래의 세 방법으로 interrupt return sequence를 trigger한다.
EXC_RETURN이 interrupt에서 exit되었을 때 activate되어야 할 processor mode와 stack type을 나타내준다.

Registers

handler mode에서는 항상 MSP(main stack pointer)를 사용하고 thread mode에서는 MSPPSP(process stack pointer)를 모두 사용한다 (참고).

thread mode일 때 Control[1]이 0이면 MSP이고, 1이면 PSP이다 (참고).

interrupt service routine은 항상 handler mode다.

Example 1 (Stacking & Unstacking)

프로그램을 실행하다가 0x08000044MOV문에서 SysTick interrupt가 발생하였다.그러면 위에서 봤던 8개의 register를 stacking을 통해 save한다.
save하는 register 중에는 LR도 포함된다.

그리고 LR을 저장했으니 LR의 값을 EXC_RETURN으로 지정해준다.
user program이 thread mode에서 실행되고 있었고 MSP를 사용중이었다는 것을 나타내기 위해 LR0xFFFFFFF9로 set하였다.
ADD를 실행하고 나면 r3r4의 값에 각각 1 씩 더해진다.
그리고 BX lr을 통해 return하면 unstacking이 진행된다.
unstacking이 진행되면 기존 레지스터 값들이 restore되면서 r3의 값이 lost되었다.
이후 main program의 실행을 재개한다.

Example 2 (Stacking & Unstacking)

위와 동일한 상황인데 0x08000020에서 BL sine이 된 것만 다르다.
이 프로그램을 실행하다보면 BL sine에서 LR의 값을 바꾸게 된다!
만약 sine0x08000024에 위치한다고 생각해본다면 LR이 이 값을 갖게 된다.
이렇게 되면 이후 BX lr을 실행해도 unstacking이 되지 않는다.
LREXC_RETURN이 아니기 때문이다..

이 문제를 해결하는 방법은 세 가지가 있다.

Method 1

BL문 전후로 lr을 stack에 save했다가 BX lr을 실행하기 전, 즉 interrupt handler에서 return하기 전에 이 값을 restore하기.

Method 2

method 1과 비슷한데 BL문 전에 lr을 stack에 save했다가 이 값을 PC에 restore하면 BX lr과 같은 동작을 할 수 있다.

Method 3

lr을 stack에 push하지 않고 BL문으로 바뀌었던 lr의 값을 직접 0xFFFFFFF9로 바꾸는 방법도 있다.

하지만 이 방법은 추천하지 않는다. processor mode와 stack type을 정확히 알아야만 값을 지정해 사용할 수 있기 때문이다.

Interrupt Number in PSR

interrupt number은 program status register(PSR)의 8:0에 저장된다.

Enable an Interrupt / Exception

  • Enable a system exception
    몇 개의 exception은 항상 enable되어있다 (disable할 수 없음).
    exception enable/disable에 centralize된 register는 없다.
    각 exception은 그에 대응하는 component에 의해 control된다 (SysTick module같은 것들).

  • Enable a peripheral interrupt
    interrupt enable/disable을 위한 centralized register array가 존재한다.
    NVIC의 interrupt set enable register(ISER) 0~15번 register를 enabling할 때 사용한다.
    NVIC의 interrupt clear enable register(ICER) 0~15번 register를 disabling할 때 사용한다.

Priority Management

Interrupt Priority

priority number가 작을 수록 높은 우선순위를 가진다.

A의 priority value가 5이고, B의 priority value가 2라면 BA보다 우선순위가 높다.

Reset, NMI, Hard Fault exception의 우선순위는 고정되어있다.
나머지 다른 exception/interrupt의 priority는 조정할 수 있다.

interrupt 우선순위는 NVIC안에 있는 interrupt priority register(IPR) 0~123에 지정된다.

(124개의 IPR 레지스터) * (IPR당 4개의 priority configuration) = 496으로, ARMv7-M에서 지원하는 전체 interrupt의 개수를 모두 커버할 수 있다.

각 우선순위는 preempt priority number와 sub-priority number의 두 field로 이루어져 있다.
preempt priority number은 preemption을 위한 priority를 정의한다.
sub-priority number은 여러 개의 interrupt가 같은 preempt priority number를 가지고 pending되었을 때 우선순위를 결정해준다.

preemption priority field와 sub-priority number field의 길이는 LSB의 위치를 조정해서 바꿀 수 있다.

📌 LSB를 조정하는 이유는 무엇일까?
2/5 bits priority field를 2/3 bits priority field로 바꿨다고 가정하자 (sub-priority number field가 2 bit 줄어듦).
0b101000100b10001010의 LSB를 조정해보자.
0b10100010에서 SPN을 구성하고 있던 부분이 10001에서 100이 되면서 17에서 4가 되었다.
0b10001010에서는 SPN을 구성하고 있던 부분이 00101에서 001이 되면서 5에서 1이 되었다.

기존의 우선순위를 살펴보면, 17 > 5 였고, 4 > 1 이므로 SPN 필드가 줄어들어도 우선순위가 유지되었다.

하지만 MSB를 조정하는 경우에는 다르다.
0b010001100b00010110의 MSB를 조정해보자.

0b01000110에서 SPN을 구성하고 있던 부분이 10001에서 001이 되면서 17에서 1이 되었다.
0b00010110에서는 SPN을 구성하고 있던 부분이 00101에서 101이 되면서 5에서 5로 유지되었다.

기존의 우선순위를 살펴보면, 17 > 5 였지만 1 < 5 이므로 SPN 필드가 줄어들면서 우선순위가 바뀌었다 (priority reversal).

이러한 문제가 생기기 때문에 priority byte의 field를 조절할 때는 반드시 LSB를 조정한다.

Exception-masking Registers

PRIMASK, FAULTMASK, BASEPRI는 exception을 masking할 때 사용하는 레지스터이다 (참고).

PRIMASK

PRIMASK는 non-maskable interrupt(NMI)와 hard fault exception을 제외한 모든 exception을 disable하는 데에 사용된다.
NMI와 hard fault exception을 제외한 모든 interrupt를 disable하고 싶으면 PRIMASK에 1을 쓴다.

MOV		R0, #1
MSR		PRIMASK, R0

만약 모든 interrupt를 enable하고 싶다면 PRIMASK에 0을 쓴다.

MOV		R0, #0
MSR		PRIMASK, R0

FAULTMASK

FAULTMASKPRIMASK와 비슷하지만 현재 priority number를 -1로 바꾸기 때문에 hard fault handler도 block된다.

hard fault exception의 priority value가 -1이기 때문에 block되는 것이다.

BASEPRI

현재 level보다 우선순위가 낮은 interrupt만 disable한다.

만약 0x60보다 priority가 낮은(priority value가 큰) exception을 disable하고 싶다면 다음과 같은 코드를 사용하면 된다.

MOV		R0, #0x60
MSR		BASEPRI, R0

0개의 댓글