소프트 인터럽트 핸들러

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 */
	SECTIONS
	{
		/* The startup code into ROM memory */
		.isr_vector :
		{
		. = ALIGN(4);
		KEEP(*(.isr_vector)) /* Startup code */
		. = 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"			//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"
		);
	}

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에 들어가는 값이다

@@



'devel > man & example' 카테고리의 다른 글

gcc __attribute__ section 지정으로 직접 변수 기록될 영역 고정하기  (0) 2016.08.11
gcc inline assembly  (0) 2016.08.11
arm assembly byte swap 예제 분석  (0) 2016.04.13
dev file  (0) 2015.08.04
ioctl  (0) 2012.07.26
Posted by 쵸코케키

블로그 이미지
chocokeki
쵸코케키

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함