본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 8(Uart Interrupt)

이제 실제 gdb를 활용하여 앞서 설명한대로 동작하는지 점검해보자.

 

앞서 설명했을 때 IRQ_Interrupt가 걸리면 ARM에서 PC는 강제적으로 0x18로 jump를 하게 된다고 했다.

vector_start:
	LDR PC, reset_handler_addr       //0x00 Reset
	LDR PC, undef_handler_addr       //0x04 Undefined Instruction
	LDR PC, svc_handler_addr         //0x08 SVC exception
	LDR PC, pftch_abt_handler_addr   //0x0c Prefetch Abort
	LDR PC, data_abt_handler_addr    //0x10 Data Abort
	B .                              //0x14 Not used
	LDR PC, irq_handler_addr         //0x18 IRQ Interrupt
	LDR PC, fiq_handler_addr         //0x1C FIQ Interrupt

따라서 Debugger에 breakpoint를 pc = 0x18에 걸고 걸리는지 확인한다.

 

(gdb) c
Continuing.

Breakpoint 7, vector_start () at boot/Entry.S:17
17 LDR PC, irq_handler_addr         //0x18 IRQ Interrupt
(gdb) p $pc
$18 = (void (*)()) 0x18 <vector_start+24>

Interrupt가 걸렸을 때 PC가 강제적으로 0x18로 변경되었다.

irq_handler_addr: .word Irq_Handler 이므로

next를 하면 자동으로 Irq_Handler로 jump를 하게된다.

__attribute__ ((interrupt ("IRQ"))) void Irq_Handler(void)
{
    Hal_interrupt_run_handler();
}


void Hal_interrupt_run_handler(void)
{
    uint32_t interrupt_num = GicCpu->interruptack.bits.InterruptID;

    if (sHandlers[interrupt_num] != NULL)
    {
        sHandlers[interrupt_num]();
    }

    GicCpu->endofinterrupt.bits.InterruptID = interrupt_num;
}

IRQ_Handler는 다시 Hal_interrupt_run_handler를 호출하게 되고

여기서 Hal_interrupt_run_handler는 무슨 interrupt가 걸렸는지(InterruptID) 가지고 온다.

 

GicCpu 메모리를 확인해보자.

(gdb) p *GicCpu
$21 = {cpucontrol = {all = 1, bits = {Enable = 1, reserved = 0}}, prioritymask = {all = 240, 
    bits = {Reserved = 0, Prioritymask = 15, reserved = 0}}, binarypoint = {all = 0, bits = {
      Binarypoint = 0, reserved = 0}}, interruptack = {all = 44, bits = {InterruptID = 44
      CPUsourceID = 0, reserved = 0}}, endofinterrupt = {all = 0, bits = {InterruptID = 0, 
      CPUsourceID = 0, reserved = 0}}, runninginterrupt = {all = 0, bits = {Reserved = 0, 
      Priority = 0, reserved = 0}}, highestpendinter = {all = 44, bits = {InterruptID = 44, 
      CPUsourceID = 0, reserved = 0}}}
(gdb) 

 

 

InterruptID가 44인데 44는 UART_INTERRUPT0 = 44 를 의미한다(Hal_uart_init 함수 내부)

 

따라서 함수의 interrupt_num = 44 가되고

 

(gdb) p sHandlers[44]
$30 = (InterHdlr_fptr) 0x680 <interrupt_handler>

이므로 interrupt_handler를 호출하여 uart data를 terminal에 쓰게 된다.

 

함수 실행이 끝나면 LR -4로 return 하면서 모드도 IRQ Mode에서 다시 SYS Mode로 복귀한다.

 

Irq_Handler () at boot/Handler.c:8
8 }
5: /x $cpsr = 0x600001d2   //함수 끝나기전 IRQ 모드

 

 

main () at boot/Main.c:24
24    while(true);
5: /x $cpsr = 0x6000015f  // Main 함수

 

정리하면 

1.  interrupt(IRQ)가 발생하면 강제로 PC = (void (*))0x18로 변경한다.

2.  pc = *0x18에는 Irq_Handler 함수 포인터 값이 있으므로 Irq_Handler로 호출한다.

3.  Irq_Handler에서 Hal_interrupt_run_handler 호출한다.

4.  Hal_interrupt_run_handler 에서 Interrupt ID를 가지고 어떤 함수를 실행 할 건지 판단한다.

5.  sHandlers[44] 의 값은 interrupt_handler 함수 포인터값이 등록되어 있으므로 inetrrupt_handler 함수를 실행한다.

6.  Interrupt_handler를 통해 Uart 수신값을 송신한다.

7.  0x00000200 <+24>: ldm sp!, {r0, r1, r2, r3, r4, r11, r12, pc}^  CPSR, SP, PC 값을 전부 복구한다. (SYS모드)

8.  interrupt 전 실행 코드 계속 실행한다.