임베디드 레시피

Egloos | Log-in





SWI 의 진실

Exception Handler를 다루다 보니, 궁금한 게 생겼어요. Hardware없이 interrupt를 거는 방법이 있을까요? SoftWare Interrupt라는 거 앞에서부터 언급되었었는데, Interrupt는 Asynchronous하게 암때나 걸리는 게 Interrupt일진데, Software로 어떻게 암때나 interrupt를 건다는 얘길까요. 실은 Software Interrupt라는 건 Interrupt가 아니에요. 실은 Software적으로 Exception을 걸 수 있는 걸 말하는 거죠. Software적으로 Exception을 걸게 되면 보통 User mode에 있던 System이 Supervisor mode로 전환이 되고요, Software Interrupt를 거는 순간부터는 Privileged Mode로 전환되는 거니까, System을 마음대로 주무를 수 있는 권한이 생기는 거죠.
 
왜 이런 SWI를 굳이 사용하는 걸까요? compatibility때문에 사용하게 되는 경우가 많습니다. 보통은 kernel service를 이용할 때 많이 사용되는데요, 이렇게 하면 좋은 점이 말이죠, 일단은 Privileged mode로 들어갈 수 있게 되고, Kernel service로 들어서는 입구가 하나로 한정이 되므로 Kernel 입장에서는 SWI 부분만 수정을 잘하면 되는 장점이 있지요. 이런걸 유식한 말로 compatibility라고 합니다. 냐하하.
 
SWI가 이용되는 대표적인 예는 아래의 두 가지에요.
1. System Call (Kernel)
2. Semi hosting
 
System Call이 보통 Kernel등에서 많이 사용되는 개념인데, Kernel은 SVC mode에서 동작하고, 일반 application은 User Mode에서 동작하기 때문에, User Mode의 일반 application이 Kernel에게 service를 요청할 때 사용하지요. SWI를 걸어서 Kernel Mode로 System mode를 바꾸는 거죠 뭐.
 
그리고, Semi hosting이라는 건 Target에서 I/O에 관련된 것들을 경로를 바꾸어 Target에서 실행되어야 하는 I/O를 Debugger를 실행하고 있는 Host system에서 대신 수행하게 하는 것인데, 예를 들면 UART로 Message를 뿌리던 target의 routine을 Semi hosting routine으로 갈아치우면, UART가 아닌 Debugger로 target의 Reporting을 직접 받을 수 있는 장점이 있어요. 이런 Semi hosting에 관련된 자세한 내용은 How to Debug section에서 다시 자세히 기술할 예정이오니, 여기서는 concept만 잘 가져가시도록 하시죠.
 
그러면, SWI는 어떻게 구현되고, 어떻게 Handler를 구현하게 되는 걸까 하는 문제에 focusing을 해보는 게 어떨까 생각 중입니다.
 
Software Interrupt를 걸려면 어떻게 해야 할까요? 간단하죠.
SWI {condition}
이렇게 assembly를 삽입해 주면 SWI 명령어를 만나는 순간 Exception은 발생하고, SWI Exception Vector (0x0008)번지로 branch하게 됩니다. 이렇게 SWI 명령어 자체로 Exception을 걸어 User mode에서 강제로 SVC mode로 갈 수 있다니 이 얼마나 시적인 일입니꺄.
 
SWI {condition} 이라고 했는데, 그러면 SWI에 대해서 여러 가지 종류의 SWI를 걸 수 있다는 말인데요, 즉 슨 SWI 뒤에 숫자를 적어 넣고, 막상 Handler에서 뒤에 parameter로 준 condition을 switch 걸어서 여러 가지 case를 처리할 수 있다는 말이기도 하지요.
 
그런데, ARM에서는 Exception이 발생하고 나면, Exception Vector로 jump해서 곧바로 Handler로 branch한다고 보았었는데, 어떻게 parameter를 준다는 말입니까. 이거야 말로 귀신이 곡할 노릇노릇 노른자이네요. 이것이야 말로 System Software를 하는 사람들의 실력이 빛을 발하는 순간이에요.
 
모든 것은 원하는 것이 있으면 통하는 법이죠. 그것의 비밀은 SWI 명령자체가 비밀입니다. SWI 명령어의 32bit binary 형태를 한번 볼까요?
 
 
 
32bit 명령어 중 MSB 4bit는 조건을 나타내는 명령어, 즉 EQ, NEQ 등의 뭐 그거 있었잖아요. condition flag를 보고서 어떻게 할거냐는 조건 명령 그게 4bit 들어가고요. 바로 다음에 따라오는 4bit에 연달아 1111이 있으며 SWI 명령입니다. 그러면, 뒤에 24bit가 남게 되죠. 이 24bit에 해당하는 곳에 parameter가 들어갈 수 있게 되는 거죠. 실은 버려지게 되는 내용이지만, 재활용 수준에서 이걸 가져다가 여러 가지 종류의 SWI를 거는 것처럼 만들 수 있어요. 순수하게 System Software Engineer가 구현해야 하는 몫이에요. 뒤에 붙어 있는 LSB 24bit와는 아무 상관없이 일단 1111 (SWI)가 있으면 Processor는 Exception을 발생시킵니다. 그러면, 뒤에 붙어 있던 LSB 24 bit를 어떻게 parameter로 재활용할 수 있을 까요. 자자, 생각해 봅시다. 힌트는 Software Interrupt가 걸리게 되면, R14_SVC에 돌아갈 주소가 저장된다는 사실이에요.
 
으흐흐, 그렇죠. 돌아갈 주소를 안다는 것은 돌아갈 주소에서 한 칸만 이전을 가져올 수만 있다면 SWI가 걸린 위치를 가져올 수 있다는 말이지요. 그러면 SWI가 걸린 위치를 가져와서 LSB 24bit만 Masking해내면 요리 끝이에요. 뭐 다 이런 식인 거에요.
 
LDR r0, [r14, #-4]
BIC r0, r0, #0xFF000000
 
요렇게 하면 r14가 가리키는 곳에서 4를 뺀 곳의 값 자체를 읽어와서 하위 24 bit를 r0에 긁어 올 수 있겠어요. 짜란! 자자, 그러면 실제로 어떻게 Software를 짜는지 알아야 하지 않겠어요?
 
두 가지 예를 들어볼 게요.하나는 위에서 본 것과 같은 SWI 호출과 Handler의 구현입니다. 나머지 한가지는 C Level의 SWI 의 직접 호출 구현이지요. 궁금 안 하실지도 모르겠으나, 조금 살펴 보져 머.
 
첫 번째, 일반적인 SWI 호출하는 방법을 한번 해보시지요.
 일단 c file에서 SWI를 호출해야겠죠. SWI_OOOPS()라는 함수에서 SWI 호출해 보겠습니다.
 
 
SWI_OOOPS()
{
    .............
    SWI_Exception();
    .............
}
 
그러면, SWI Assembly 명령어를 사용하기 위하여, Assembly file 하나에서 SWI_Exception 함수를 구현해 볼까요. 아래처럼 직접 SWI에 parameter 0x121212를 줘봅시다.
 
SWI_Exception
  SWI  0x121212
이렇게 하면   SWI  0x121212 를 만나는 순간 곧바로 Software Interrupt가 걸리게 되고 Exception Vector로 branch하게 됩니다.
 
0x8번지에 이렇게 씌여져 있겠죠.
b SWI_Handler
 
그러면, 이제 간단하게 C Level의 SWI Handler를 부르기 위한 Handler를 만들어 주면 됩니다. 물론 여기에서 LSB 24 bit도 건져 내야죠.
 
 
SWI_Handler
   STMFD sp!, {lr}                  ; 돌아갈 주소 저장
   LDR  r0,  [lr, #-4]                  ; SWI 주소의 값을 가져옴.
   BIC  r0,  r0,  #0xFF000000      ; LSB 24bit 건져냄.
   BL  SWI_C_Handler                 ; r0에 넣었으니까, AAPCS에 의하여 첫번째 argument로
   LDMFD sp!, {lr}                     ; 다 처리하고 왔으면 돌아가야지~

 

뭐 이런 Story죠.

그럼 이때! SWI_C_Handler에 잘 도달 할 수 있도록 수로를 잘 터왔으니까, SWI_C_Handler만 잘 구현하면 됩니다. 잘 보세요.

 

 

void SWI_C_Handler (int OPTion)
{
 
 switch(option)
 {
  case 0x123432: 
           UART_MESSAGE ("SWI 0x123432");
      break ;
  case 0x121212 :
           LCD_MESSAGE ("SWI 0x121212");
      break ;
  case 0x654321:
           USB_MESSAGE ("SWI 0x654321");
      break ;
 }
 
}
 
r0에 LSB 24 bit를 넣었으니까, 그 값은 int option으로 넘어오게 되고, 그 option에 대해서 switch를 걸었으니까 결국 LCD_MESSAGE 를 찍겠네요. 음.. 어렵지 않군요.
 
자, 여기서 그럼 다른 종류의 LSB 24bit를 걸기 위해서 SWI_Exception() 함수를 Assembly로 따로따로 구현해야 하느냐! 조금 더 편한 방법이 있어요.
 
함수를 SWI와 번호로 선언할 수 있어요.

 
 __swi(0x123432) void UART_OUT (void)라고 선언하고서,
 
SWI_OOOPS()
{
    .............
    SWI_Exception();
    UART_OUT();
    .............
}
 
이렇게 부르면 차례로 LCD MESSAGE와 UART MESSAGE가 실행되게 됩니다. 어떻게 해서 이런 일이 가능한 것이냐 하면, UART_OUT() 함수 자체가 SWI 0x123432으로 compile 된답니다.
결국 SWI_OOOPS()함수는
 
 
SWI_OOOPS
   ........
   SWI 0x121212
   SWI 0x123432
   .........
 
이런 식으로 컴파일 되겠죠 뭐.

by 히언 | 2009/07/09 22:55 | ARM미장센 | 트랙백 | 핑백(1)

Linked at 임베디드 시스템 개발자 되기 .. at 2009/07/09 22:58

... 그리고 ^ 접미사 ⓖ Exception Vector Table (EVT)과 각 Handler의 구현 ⓗ SWI의 진실 ⓘ Coprocessor Assembly ⓙ Bootloader와 Memory Budget ( ... more

◀ 이전 페이지          다음 페이지 ▶