소프트 인터럽트 핸들러
software interrupt
SVCall을 사용해서 구현
#0. 구현 부터 시작
- a. linker script 확인
section 정의에 interrupt vector 가 잘 정의 되어있는지 체크
/* Sections */
SECTIONS
{
/* The startup code into ROM memory */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
} >ROM
...
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} >ROM
- b. interrupt vector 잘 선언되어 있는지 확인
Drivers/CMSIS/ST/STM32xxxx/Source/Templates/gcc/startup_stm32xxxxxx.s
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word NMI_Handler
... 생략 ...
.word 0
.word SVC_Handler
Vector Table :D022708, 39p 표 참조
@@
offset이 역순으로 나와있는 것에 주의
SVC_Handler가 정확한 위치에 잘 선언되어 있다
- c. SVC_Handler 구현
stm32f4xx_it.h 에 void SVC_Handler(void); 선언
stm32f4xx_it.c 에 void SVC_Handler(void) 정의
이 파일을 보면 각종 interrupt/Exception handler 들이 정의되어 있다
인제 SVC 명령이 호출되면 이 핸들러가 실행이 된다~!!
궁금하면 핸들러에 led 점멸이나 uart 메세지 보내는 것을 사용해서 시험이 가능하다
Exception 발생하면 core 하드웨어가 자동으로 stack에 일부 register들을 백업한다
그 후 exception handler가 실행된다
그와 동시에 EXEC_RETURN 값이 LR에 기록된다
EXEC_RETURN는 Exception이전에 어떤 mode 상태에서 돌고 있다가 handler로 들어왔는지 알 수 있는데
적당히 모드 보고 MSP를 참조하든 PSP를 참조하든 알아서 해라라는 소리
이하의 내용은 'd. svc 호출' 항목을 참조한 뒤 보면 좋다
stack에는 r0-r3, r12, LR, PC, xPSR 이 들어가 있다
r0에는 우리가 필요한 void* data 가 들어가 있다
svc와 함께 전달되는 imm 8bit 숫자는 r1에서 읽어도 되고 다른 방법으로 추출 해도 된다
일단 필요한게 r0 인자값이랑 SVC와 같이 넘어온 imm값
이 두가지를 알기 위해 r0가 잠들어 있는 stack pointer가 필요하다
그런데 sp를 알기 위해선 arm core가 thread mode에서 넘어왔는지 process mode에서 넘어왔는지 구분을 해야 한다
(간단하게 설명하면 os 유/무 mode 구분)
위에서 이걸 EXEC_RETURN으로 알 수 있다는 것을 안다
참고 : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203ik/BABFGEFG.html
참고 : ITE 구문 http://trace32.com/wiki/index.php/If-Then
아래의 코드를 보면 이해가 될꺼다
msp - 0xFFFF FFF9, 9(1001)
psp - 0xFFFF FFFD, D(1101)
그래서 TST로 LR을 4(0100) 와 비교한다
void SVC_Handler(void)
{
__asm volatile(
"TST lr, #4 \n" //if(LR & 0x4) { Z=1 } else { Z=0 } update C, N flag
"ITE EQ \n" //if then else(ITE) if(Z==1 /*EQ*/) { MRSEQ } else { MRSNE }
"MRSEQ r0, MSP \n" //conditional execution (Z==1) - for thread mode
"MRSNE r0, PSP \n" //conditional execution (Z==0) - for process mode
"B SVC_Handler_main"
);
}
void SVC_Handler(void)
{
__asm volatile(
"TST lr, #4 \n"
"ITE EQ \n"
"MRSEQ r0, MSP \n"
"MRSNE r0, PSP \n"
"B SVC_Handler_main"
);
}
process mode 자체를 안 쓴다면 아예 그냥 MRS r0, MSP랑 B svc_main_handler 구문만 남겨도 ok
SVC_Handler_main 구현은 뒷쪽에 이어서
- d. SVC 호출 : DDI0403EB, 455p
SVC<c><q> #<imm8>
cq는 일단 생략
imm8 - immediate 8bit 즉 8bit 숫자
gcc inline assembly 기준
__asm volatile (
"SVC #2"
);
이런식으로 호출 가능, handler 쪽에서는 숫자2를 같이 건네 받음
인자로 imm 값을 건네고 싶을 때 다음과 같이 구현가능
__attribute__((always_inline)) static inline void invoke_exception(uint8_t value)
{
__asm volatile (
"SVC %0"
:
: "i" (value) //imm
);
}
만약 인자 1개를 추가로 같이 보내고 싶다고 가정하면 형태가 많이 변경된다
exception이 발생하면 하드웨어적으로 아래의 register들을 stack에 백업한다
@@ D022708, 42p
그리고 함수를 호출하면 인자 4개 까지는 r0 - r3 레지스터에 들어간다
예를 들면 void test(int a, unsigned char b, char* c, void* d)
r0 = a, r1 = b, r2 = c, r3 = d
이렇게 들어간채로 함수가 불리는데 이게 바로 calling convention
Parameter Passing, AAPCS(Arm Architecture Procedure Call Standard) 18p
그래서 SVC 호출과 함께 맘대로 인자를 보내고 싶다면 함수내에서 SVC 호출을 하면 된다
뭔소린고 하니
#define svc(code) asm volatile ("svc %[immediate]"::[immediate] "i" (code))
#define SVC_TYPE1 0x01
#define SVC_TYPE2 0x02
__attribute__ ((noinline)) int invoke_exception(void *data, uint8_t immnum)
{
switch(immnum)
{
case SVC_TYPE1:
svc(SVC_TYPE1);
break;
case SVC_TYPE2:
svc(SVC_TYPE2);
break;
default:
return -1;
}
return 0;
}
아름다운 구현 방법이 있다면 좋겠는데 이 이상의 방법을 찾지 못하겠다
참고
https://falstaff.agner.ch/2013/02/18/cortex-m3-supervisor-call-svc-using-gcc/
같은 고민을 하는 분들
http://stackoverflow.com/questions/11377453/using-gcc-inline-assembly-with-instructions-that-take-immediate-values
inline mode는 함수를 풀어버리기 때문에 함수를 통해 인자를 받아야 하는 우리는 __attribute__((noinline))을 꼭 선언해줘야 한다
invoke_exception 함수를 호출하면 r0에 void* data 가 들어가게 되고
이 후 svc 호출로 r0는 stack에 들어가게 된다
svc와 함께 전달되는 imm 8bit 숫자는 r1에서 읽어도 되고 다른 방법으로 추출 해도 된다
호출은 일단 했으니 이 후의 내용은 '이전 c. SVC_handler' 항목을 참조
- e. SVC_Handler_main 상세 구현
SVC_Handler를 통해 r0에 MSP(stack pointer)를 얻어왔다
그리고 B SVC_Handler_main을 통해 다시 함수로 이동해 왔다
void SVC_Handler_main(void *svc_args)
{
unsigned int svc_number;
if(svc_args == NULL)
return ;
svc_number = ((char *) ((unsigned int*)svc_args)[6])[-2]; //imm
void* arg = (void*) ((unsigned int*)svc_args)[0];
}
현재 상태
r0 == void *svc_args == MSP
우리가 필요한 건 MSP에 저장되어 있는 r0(void *data)
그러므로
void *arg = (void *) ((unsigned int*) svc_args)[0];
unsigned int*로 type casting 하는 이유는 register 크기가 32bit 니까
만약에 r1을 얻고 싶다면 ((unsigned int*) svc_args)[1]; 하면 된다
svc_number 즉 svc 랑 같이 보낸 imm 추출은 좀 더 많이 복잡하다
svc 명령 중 하위 8bit가 imm 이다
일단 svc 명령이 어떻게 내려졌는지 그걸 가져오고 그 다음 하위 8bit 를 구해서 imm을 획득하자
svc 명령이 불려지면 핸들러가 실행되기 전에 레지스터들이 백업 되는데 PC값에는 return address
다시 말해 svc 명령 다음 실행 위치가 들어가 있다
그럼 PC - 2 하면 svc 명령 위치가 나오겠네?
PC = ((unsigned int *) svc_args)[6]
SVC 명령 위치 = ((unsigned int*) svc_args)[6] - 2
SVC 명령 = *(uint16_t *) (((unsigned int*) svc_args)[6] - 2)
svc 명령이 16bit 니까 (uint16_t *)로 type casting
이 값을 16진수로 뽑으면 0xDF## 이 된다
imm = SVC 명령 & 0xff
실제 연산하기 위해서는 16bit 포인터로 변환
unsigned int* pc_address = ((unsigned int*) svc_args)[6];
uint16_t* svc_address = pc_address - 2;
uint16_t svc = *(uint16_t *) svc_address;
uint8_t svc_number = svc & 0xff;
이렇게 해도 되는데 이걸 짧게 줄이면
svc_number = ((char *) ((unsigned int*) svc_args)[6])[-2];
svc_number = ((char*) pc_address)[-2]
대충 메모리 구조 보면
[ ... pc ]
[ svc \ imm ]
\ \ \--> svc 시작 주소값 == pc 시작 주소값 - sizeof(uint8_t)x2 == &((char*) pc_address)[-2]
\ \--> svc 시작 주소값 + sizeof(uint8_t) == svc 주소값 + 1
\--> pc 주소값 == svc 끝 주소값 == svc 시작 주소값 + sizeof(uint8_t)x2
가능하면 쉽게 쓰려고 노력했는데 가독성이 영 꽝인거 같다
기타 관련 개념들
# ARMv7에는 A시리즈도 있고 R 시리즈도 있고 M 시리즈도 있는데 이놈들이 거의 같긴 한데 다른 부분들이 있다
그래서 예제같은거를 대충 가져와서 쓴다고해서 돌아가는게 아니라 정확하게 맞는 시리즈에 적용해야 된다
좋은 예제 리스트
linux kernel source의
arch/arm/include/asm/io.h 등 기타 여러가지
arch/x86/include/asm/io.h
ARMv7-M Architecture Reference Manual - dmIO403E_B, 569p
Supervisor call(SVCall)
An exception caused explicitly by the SVC instruction. Application software uses the SVC instruction
to make a call to an underlying operating system. This is called a Supervisor call. The SVC instruction
enables the application to issue a Supervisor call that requires privileged access to the system and
executes in program order with respect to the application. ARMv7-M also supports an
interrupt-driven Supervisor-calling mechanism
579p
Interrupt Control and State Register - ICSR, 655p
# The Vector Table - 581p
The vector table contains the initialization value for the stack pointer, and the entry point addresses of each
exception handler
word offset in table - Exception using that Exception Number
그래서 linker descriptor 파일 보면
section에 isr_vector 정의했고 startup_stm32f479xx.s 파일 보면
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
.word BusFault_Handler
... 생략
.word SVC_Handler
.word DebugMon_Handler
.word 0
.word PendSV_Handler
.word SysTick_Handler
할당은 이렇게 해놓았고
stm32f4xx_it.c 여기에 void BusFault_Handler(void); 함수 정의
stm32f4xx_it.h 여기에도 선언 추가
http://egloos.zum.com/recipes/v/5037342
http://www.thewireframecommunity.com/writing-a-basic-multitasking-os-for-arm-cortex-m3-processor
http://stackoverflow.com/questions/24162109/arm-assembly-code-and-svc-numbering
https://falstaff.agner.ch/2013/02/18/cortex-m3-supervisor-call-svc-using-gcc/
ARMv7m pdf, 455p SVC 상세 이용법
svc<c><q> #<imm>
<c><q> -
imm - immediate constant
#Exception entry behavior 587p
이해하기 앞서 선행 정보 간단 요약
엄청 기니까 대충 넘겼다가 나중에 봐도 ok
Processor Mode : D022708 16p
thread mode
기본 사용 모드
handler mode
exception 처리할 때 사용된다. 처리 끝나면 다시 thread mode로 복귀
STACK
full descending stack
아래 방향으로 증가한다고 생각하면 편할듯
새 아이템을 스택에 넣으면 sp는 되려 감소한다
그리고 새 메모리 위치에 아이템을 기록한다
stack에는 2가지 종류가 있는데 main stack, process stack이 있다
thread mode 에서는 core가 어떤 stack을 쓸지 CONTROL register에 의해 결정할 수 있다
handler mode 에서는 항상 main stack을 사용한다(msp)
SP(R13) - Stack Pointer : D022708 18p
MSP(main stack pointer)
PSP(process stack pointer) - os 있을 때 context switching 용도로 사용
위의 내용들 참고
LR(R14) - Link Register
subroutine이나 함수 호출, exception 에서 돌아갈 정보를 저장한다
PC(R15) - Program Counter
현재 프로그램 주소를 담고 있음
APSR - Application Program Status Register : 20p
명령어 실행하는 동안 발생한 상태 정보에 대해 나와있음. 표 참조
@@
Exception Priority : 37p, DDI0403EB 584p
SVC의 우선 순위는 SHPRx(System Handler Priority Register) 에서 설정 가능
exception 순위가 낮으면 높은 handler에게 preemption 선점 당할 수 있음
그 와중에 낮은 순위의 exception이 들어오면 pending 된 상태로 대기
Exception 종류 및 IRQ와의 관계 - D022708 36-39p
Vector Table
SHPRx - System Handler Priority Register : D022708 323p
exception 들의 priority 를 설정 가능
아마도 작을 수록 높은 우선 순위를 갖는 것 같다
(명시 되어있는 것은 못 찾겠으나 IRQ의 경우도 그렇고 37p의 Table. 16을 보면 -3 reset이 highest priority다)
Exception Entry - Exception이 발동하면? : 41p
Stack Frame Layout : 42p
@@
Exception return - EXEC_RETURN : D022708, 43p
EXEC_RETURN은 exception 진입할 때 LR에 들어가는 값이다
@@