본문 바로가기

무언가 만들기 위한 지식/ARM Processor

SWI_Exception_Implement(System Call-3)

이번에는 작성했던 코드들을 직접 확인해보자.
필자가 모든 관련 파일들을 작성한 것은 아니고, 기존 crt0.s에 swi부분을 추가하여 구현한 것이고,
swi.s 에서는 어셈블리어로 함수를 만들어서 C에서 사용가능하도록 EXPORT하였다. (내부적으로 swi num; 사용)
SWI_Table.c 에서는 함수 포인터를 이용하여 배열을 만들어서 참조테이블을 활용하였다.
이는 crt0.s 상의 swi 구현부분에서 참조테이블을 로드하여 해당 함수를 불러오는 역할을 하게 된다.
devicedriver *.c 파일은 기존 제공된 보드에 이미 구현되어 있는 해당 기능을 하는 함수들이다.
(System Call은 이를 이어주는 역할을 한다.)
코드는 보드에서 제공되는 코드는 공개하지 않고(해당기업 소스니까...!), 필자가 참여한 코드만 공개한다.

순서대로 보도록 하자.

[swi.s]
   AREA Apcs, CODE, READONLY

      EXPORT FAT_Get_File_List

FAT_Get_File_List

      swi             0

      mov     pc,lr

      EXPORT FAT_Get_BR_Info

FAT_Get_BR_Info

      swi             1

      mov     pc,lr

               .

               .

               .

      EXPORT Chk_DATend

Chk_DATend

      swi             114

      mov pc,lr

      EXPORT Lcd_Frame_Buffer_Copy_SIZE

Lcd_Frame_Buffer_Copy_SIZE

      swi             115

      mov pc,lr

      END

 
어셈블러로 작성된 함수는 export로 외부 C 코드 상에서 사용이 가능하도록 한다.
코드 내부로 들어오면 이미 APCS에 의하여 인자들이 전달된 상태이며, 그 상태로 swi를 호출하여 Mode변경이 발생한다. swi 다음에 있는 번호는 SWI_Table상에 존재하는 함수포인터의 INDEX 번호이다. SWI_Table.c에 배열 및 함수명이 정돈되어 있다.


[SWI_Table.c]
   #include "2440addr.h"

#include "device_driver.h"

#include "macro.h"

extern int _FAT_Get_BR_Info(FAT16_BPB* BPB, VolStruct* pVol);

.

.

extern int _Chk_CMDend(int cmd, int be_resp);

extern int _Chk_DATend(void);

void * SWI_Table[] = {

      (void*)_FAT_Get_File_List,                                    //0

      (void*)_FAT_Get_BR_Info,                                    //1

      (void*)_FAT_File_Copy_To_RAM,                               //2

      (void*)_FAT_Read_File,                                      //3

               .

               .

               .

      (void*)_Chk_SD_OCR,

      (void*)_Chk_CMDend,

      (void*)_Chk_DATend,

      (void*)_Lcd_Frame_Buffer_Copy_SIZE                                   //115

};


SWI_Table 내부에는 궁극적으로 함수포인터들의 집합인 배열이 존재한다. 이들의 순서는 swi.s의 순서와 일치하도록 INDEX순으로 정렬되어 있다. crt0.s에서 이 Table을 참조하여 함수를 호출하게 된다.


[Device Driver에 해당하는 *.c File]

   Example : ( in Uart.c)

void _Uart_Init(int baud){

      // PORT GPIO initial

      Macro_Write_Block(rGPHCON, 0xf, 0xa, 4);

      rUFCON0 = 0x0;

      rUMCON0 = 0x0;

      rULCON0 = 0x3;

      rUCON0  = 0x245; 

      rUBRDIV0= ((unsigned int)(PCLK/16./baud+0.5)-1 );

}

void _Uart_Send_Byte(char data){

      while(Macro_Check_Bit_Clear(rUTRSTAT0,1));

      WrUTXH0(data);

}              

.

.

.

void _Uart_Printf(char *fmt,...){

    va_list ap;

    char string[256];

    va_start(ap,fmt);

    vsprintf(string,fmt,ap);

    Uart_Send_String(string);

    va_end(ap);

}


각 *.c 내부적으로 함수명앞에 _(UnderBar)를 붙이고, 기존의 이름은 swi.s에 사용하였다. 이는 DeviceDriver를 그대로 사용하던 팀원들에게 편리하게 배포하기 위해서 같은 이름을 사용하여도 이전 소스를 수정할 필요 없도록 하기 위해 위 방식을 사용하였다.

※ 필자팀은 Simple OS기능중 SystemCall 구현이었다. 다른 팀에서는 DeviceDriver의 함수를 그대로 사용하기 때문에 이렇게 바꿔주어 배포하기로 하였다.
(원래 함수명은 껍데기 처럼 사용하고, 진짜 실행코드에는 언더바를 붙여주었다. 배포시 모든팀의 소스가 무난하게 사용되게 하도록)   

[crt0.s]
   1     EXPORT HandlerSWI

2     IMPORT SWI_Table

3 HandlerSWI

4     stmfd    sp!,{r4-r7,lr}

5     mrs     r5, spsr          

6     stmfd   sp!, {r5}                 

7     sub     r4, lr, #4                 

8     ldr      r4, [r4]                          

9     ldr      r5, =0xffffff                       

10   and     r4, r4, r5                 

11   mrs     r5, SPSR

12   mrs     r6, CPSR

13   msr     CPSR_c, r5               

14   mov     r7, lr

15   ldr      r5, =SWI_Table                                                    

16   mov     lr, pc                           

17   ldr      pc, [r5, r4, lsl #2] 

18   mov     lr,r7

19   msr     CPSR_c, r6       

20   ldmfd    sp!, {r4}

21   msr     spsr_cf, r4                       

22   ldmfd   sp!,{r4-r7,PC}^

 

crt0.s의 이 HandlerSWI 부분이 코딩의 핵심부분이다.

모든 알고리즘, 인자전달 및 모드 변경처리방식이 위 HandlerSWI 내부에서 발생한다. 위 HandlerSWI Label은 swi가 호출되면 반드시 지나가게 되는 부분이다. 따라서 모든 상황에 대해 처리를 할 수 있어야 한다.

이 부분에 도착했다는 의미는 외부에서 swi가 호출되어 Vector Table을 거쳐왔다는 의미이다.

각 Line의 의미는 다음과 같다.


1~3 Line : HandlerSWI Label을 정의

4 Line :  r4,r5,r6,r7 및 lr Register들을 Stack에 저장하고 SP값을 저장한 크기만큼 증가시킨다. 엄밀히 말하면 stmfdmultiple Decrement before store이다. 즉 저장하기 전에 미리 감소한다. 주소값은 Stack이 쌓이는 방향으로 감소하기 때문에 감소한다는 표현이 더 명확하다. r4~r7을 미리 저장하는 이유는 현재 코드에서 사용되는 Register이기 때문에 보존/복구 책임이 있기 때문에 보존을 한다. 후에 22 Line에서 이를 복구한다.

5~6 Line : MRS 명령을 이용하여 spsr의 값을 r5로 이동한 후 6 Line에서 Stack에 저장한다. 이는 이전 모드가 저장된 SPSR이 같은 모드 호출이나, 중첩으로 호출되었을 때, 값이 손실되는 것을 방지하기 위함이다.

7~10 Line : crt0.s로 넘어오기전 swi.s에서 swi가 호출되었을때의 SoftWare Interrupt 번호를 알아오기 위한 코드이다. 7 Line에서 lr에서 4를 빼서 swi가 수행된 위치를 알아오고 8 Line을 통해서 그 주소값을 통해 해당 32bit 값을 읽어 r4에 저장한다. 9~10 Line에서는 SWI 번호를 알아내기 위해서 마스킹 기법으로 “and"연산을 취한 것이다. 이후 r4에는 swi 옆에 들어온 SWI 번호가 r4에 저장된다.

11~12 Line : 현재 Mode와 이전 Mode를 r5, r6에 각각 저장한다. 이는 모드별로 원활한 이동을 위해서이다.

13 Line :  SPSR의 값을 CPSR에 집어 넣음으로서 강제로 모드를 변경한다. 이로인해 이전 모드로 강제로 이동하게 된다. 이동하는 이유는 5개 이상의 인자를 받을 경우 통제로 이전 모드의 Stack을 사용하기 위해서이다.

14 Line :  강제로 Mode변경 후 그냥 사용하면, 이후 함수가 호출되는 경우에 LR값이 파손된다. 그렇기 때문에 반드시 LR을 보존해야하는데 만약 Stack에 저장한다면, 인자가 4개 이하일 경우에는 상관없지만 5개 이상일 경우 잘못된 접근으로 원하지 않는 값을 인자로 받게 된다.
그렇기 때문에 Register에 저장해 둔다. Register에 저장해도 상관 없는 이유는 이후 함수가 호출되도, 만약 그 함수내에서 해당 Register를 사용한다면 그 복구에 대한 책임은 호출된 함수에게 있기 때문이다. 함수에서 복귀하면 해당 Register에는 저장해두었던 lr값이 복구되어 있다.

15~17 Line : IMPORT된 SWI_Table의 주소를 r5에 가져오고 위에서 계산된 인덱스 번호 r4에 4를 곱하여 해당 함수로 바로 이동한다. 16 Line에서 mov lr, pc를 하는 이유는 Pipe Line에 의해서 복귀하는 위치를 18 Line으로 잡아주기 위함이다. 결국 호출된 함수내에서 mov pc, lr을 수행하게 되면 18 Line으로 복귀하게 된다. 17 Line에서 lsl #2는 곱하기 4의 의미인데, 이 이유는 포인터가 4byte이기 때문이다.

18~19 Line : 해당 함수에서 복귀하면 18 Line으로 위치하게 된다. 하지만 아직은 이전 Stack, 이전 Mode에서 계속 수행중이다. 이후 18 Line에서 저장해 두었던 lr값을 복구해주고 강제로 다시 모드변경을 한다. 그럼 결국 swi 이후 진입하였던 SVR모드로 돌아오게 된다.

20~22 Line : 보존해 두었던 값들을 차례대로 복구한다. 22 Line의 경우 복구하면서 바로 swi문 바로 아래쪽으로 이동하게 된다. ^ 표시는 마지막에 상태레지스토 복구를 의미하며 이는 다시 모드변경을 의미한다.



이상 구현된 것으로 소스를 OS코드에 삽입하고, Device Driver Library를 넘겨주면, 각 Application들이 잘 돌아간다.

ARM Assembler도 사용법이 좀 다양한 편이다. Stack 전용 명령어도 존재하고 각 명령어에 옵션 및 플래그가 존재한다.
처음에는 Assembler를 통한 명령어 Test 및 사용법을 포스팅 해보려고 했으나, 전체적인 프로젝트 진행과정을 하는쪽으로 바꾸었다. 필자도 처음해보는 ARM Assembler라서 책을 뒤져가면서 필요한 기능만 찾아서 사용하였다.
그러니, 혹시 ARM Assembler에 대한 설명을 얻고 싶다면 다른 사이트를 검색해보거나

ARM으로 배우는 임베디드 시스템
카테고리 컴퓨터/IT
지은이 안효복 (한빛미디어, 2006년)
상세보기

위 책을 참고한다면 큰 도움이 될 것이다. (전체적인 개념이나 필수적인 어셈블러에 대해 잘 설명되어 있다.)



ARM Processor의 포스팅 마지막으로 필자가 SystemCall 구현을 마치고 만들었던 간단한 Application을 올려보려고 한다.