polling과 interrupt는 동작하는 방식이 다르다.
polling은 어떤 event가 발생하진 않았는지 CPU가 주기적으로 확인을 한다.
하지만 interrupt는 외부에서 어떤 event가 발생하면 외부에서 CPU로 신호를 직접 보내준다.
하나의 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의 첫번재 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로도 코딩할 수 있다. 어셈블리로 굳이 코딩 안해도 됨.
exception number가 priority를 의미하는 것은 아니다.
또한 priority 숫자가 작을수록 우선순위가 높다.
그림만 잘 읽어봐도 이해가 된다..
stacking과 unstacking은 이전 포스트에서 다뤘던 subroutine을 실행하기 전에 레지스터의 값을 save하는 것과 return될 때 restore하는 것을 의미한다.
현재 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한다.
interrupt handler exit를 감지해야 unstacking을 할 수 있는데 interrupt handler exit을 어떻게 감지하는 걸까?
exception handler 실행 마지막에 EXC_RETURN
이 PC
에 load되면 processor는 exception return sequence를 수행한다.
exception이 발생했을 때 EXC_RETURN
은 자동으로 generate되어 LR
에 set된다.
아래의 세 방법으로 interrupt return sequence를 trigger한다.
EXC_RETURN
이 interrupt에서 exit되었을 때 activate되어야 할 processor mode와 stack type을 나타내준다.
handler mode에서는 항상 MSP
(main stack pointer)를 사용하고 thread mode에서는 MSP
와 PSP
(process stack pointer)를 모두 사용한다 (참고).
thread mode일 때 Control[1]
이 0이면 MSP
이고, 1이면 PSP
이다 (참고).
interrupt service routine은 항상 handler mode다.
프로그램을 실행하다가 0x08000044
의 MOV
문에서 SysTick interrupt가 발생하였다.그러면 위에서 봤던 8개의 register를 stacking을 통해 save한다.
save하는 register 중에는 LR
도 포함된다.
그리고 LR
을 저장했으니 LR
의 값을 EXC_RETURN
으로 지정해준다.
user program이 thread mode에서 실행되고 있었고 MSP
를 사용중이었다는 것을 나타내기 위해 LR
을 0xFFFFFFF9
로 set하였다.
두 ADD
를 실행하고 나면 r3
과 r4
의 값에 각각 1 씩 더해진다.
그리고 BX lr
을 통해 return하면 unstacking이 진행된다.
unstacking이 진행되면 기존 레지스터 값들이 restore되면서 r3
의 값이 lost되었다.
이후 main program의 실행을 재개한다.
위와 동일한 상황인데 0x08000020
에서 BL sine
이 된 것만 다르다.
이 프로그램을 실행하다보면 BL sine
에서 LR
의 값을 바꾸게 된다!
만약 sine
이 0x08000024
에 위치한다고 생각해본다면 LR
이 이 값을 갖게 된다.
이렇게 되면 이후 BX lr
을 실행해도 unstacking이 되지 않는다.
LR
이 EXC_RETURN
이 아니기 때문이다..
이 문제를 해결하는 방법은 세 가지가 있다.
BL
문 전후로 lr
을 stack에 save했다가 BX lr
을 실행하기 전, 즉 interrupt handler에서 return하기 전에 이 값을 restore하기.
method 1과 비슷한데 BL
문 전에 lr
을 stack에 save했다가 이 값을 PC
에 restore하면 BX lr
과 같은 동작을 할 수 있다.
lr
을 stack에 push하지 않고 BL
문으로 바뀌었던 lr
의 값을 직접 0xFFFFFFF9
로 바꾸는 방법도 있다.
하지만 이 방법은 추천하지 않는다. processor mode와 stack type을 정확히 알아야만 값을 지정해 사용할 수 있기 때문이다.
PSR
interrupt number은 program status register(PSR
)의 8:0
에 저장된다.
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 number가 작을 수록 높은 우선순위를 가진다.
A
의 priority value가 5이고, B
의 priority value가 2라면 B
가 A
보다 우선순위가 높다.
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 줄어듦).
0b10100010
과 0b10001010
의 LSB를 조정해보자.
0b10100010
에서 SPN
을 구성하고 있던 부분이 10001
에서 100
이 되면서 17에서 4가 되었다.
0b10001010
에서는 SPN
을 구성하고 있던 부분이 00101
에서 001
이 되면서 5에서 1이 되었다.
기존의 우선순위를 살펴보면, 17 > 5 였고, 4 > 1 이므로 SPN
필드가 줄어들어도 우선순위가 유지되었다.
하지만 MSB를 조정하는 경우에는 다르다.
0b01000110
과 0b00010110
의 MSB를 조정해보자.
0b01000110
에서 SPN
을 구성하고 있던 부분이 10001
에서 001
이 되면서 17에서 1이 되었다.
0b00010110
에서는 SPN
을 구성하고 있던 부분이 00101
에서 101
이 되면서 5에서 5로 유지되었다.
기존의 우선순위를 살펴보면, 17 > 5 였지만 1 < 5 이므로 SPN
필드가 줄어들면서 우선순위가 바뀌었다 (priority reversal).
이러한 문제가 생기기 때문에 priority byte의 field를 조절할 때는 반드시 LSB를 조정한다.
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
FAULTMASK
는 PRIMASK
와 비슷하지만 현재 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