본문 바로가기

Firmware/RTOS

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

.
├── boot
│   ├── Entry.S
│   ├── Handler.c
│   └── Main.c
├── build
│   ├── armcpu.o
│   ├── Entry.os
│   ├── Handler.o
│   ├── Interrupt.o
│   ├── Main.o
│   ├── navilos.axf
│   ├── navilos.bin
│   ├── navilos.map
│   ├── Regs.o
│   ├── stdio.o
│   └── Uart.o
├── hal
│   ├── HalInterrupt.h
│   ├── HalUart.h
│   └── rvpb
│       ├── Interrupt.c
│       ├── Interrupt.h
│       ├── Regs.c
│       ├── Uart.c
│       └── Uart.h
├── include
│   ├── ARMv7AR.h
│   ├── memio.h
│   ├── MemoryMap.h
│   ├── stdarg.h
│   └── stdint.h
├── lib
│   ├── armcpu.c
│   ├── armcpu.h
│   ├── stdio.c
│   └── stdio.h
├── Makefile
└── navilos.ld

 

tree 에 uart interrupt처리를 위한 소스가 추가 되었다.

 

먼저  main 문을 살펴보면 interrupt가 계속 걸릴 수 있도록 마지막에 while문을 추가해 주었다.

 

Main.c

int main(void)
{
    Hw_init();

    uint32_t length;
    putstr("Hello world!\n");

    printf_test();
    //length = getstr();


   while(true);
}

 

hal/HalInterrupt.h

#ifndef HAL_HALINTERRUPT_H_
#define HAL_HALINTERRUPT_H_

#define INTERRUPT_HANDLER_NUM   255

typedef void (*InterHdlr_fptr)(void);

void Hal_interrupt_init(void);
void Hal_interrupt_enable(uint32_t interrupt_num);
void Hal_interrupt_disable(uint32_t interrupt_num);
void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num);
void Hal_interrupt_run_handler(void);

#endif /* HAL_HALINTERRUPT_H_ */

 

hal/rvpb/Interrupt.c

void Hal_interrupt_init(void)
{
    GicCpu->cpucontrol.bits.Enable = 1;
    GicCpu->prioritymask.bits.Prioritymask = GIC_PRIORITY_MASK_NONE;
    GicDist->distributorctrl.bits.Enable = 1;

    for(uint32_t i = 0; i < INTERRUPT_HANDLER_NUM; i++)
    {
        sHandlers[i] = NULL;
    }

    enable_irq();
}

Hal_interrupt_init 함수에 GicCpu가 나오는데

 

먼저 GicCpu는 Generic Interrupt Controller 에 약자이며 cpu interface 를 나타낸다.

 

volatile GicCput_t* GicCpu = (GicCput_t*)GIC_CPU_BASE;

#define GIC_CPU_BASE 0x1E000000 //CPU interface

 

GicCpu의 구조체는 다음과 같다.

 

(gdb) p *GicCpu
$6 = {cpucontrol = {all = 0, bits = {Enable = 0, reserved = 0}}, prioritymask = {all = 0, bits = {
      Reserved = 0, Prioritymask = 0, reserved = 0}}, binarypoint = {all = 0, bits = {
      Binarypoint = 0, reserved = 0}}, interruptack = {all = 1023, bits = {InterruptID = 1023, 
      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 = 1023, bits = {InterruptID = 1023, 
      CPUsourceID = 0, reserved = 0}}}

 

GicCpu->cpucontrol.bits.Enable = 1; // Generic Interrupt Controller Enable

GicCpu->prioritymask.bits.Prioritymask = GIC_PRIORITY_MASK_NONE; // 우선 순위 설정

 

실행 후 GicCpu 를 보면

 

(gdb) p *GicCpu
$7 = {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 = 1023, bits = {InterruptID = 1023, 
      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 = 1023, bits = {InterruptID = 1023, 
      CPUsourceID = 0, reserved = 0}}}

 

Generic Interrupt가 활성화 되고 priority mask가 0xF로 설정 되어 있으므로  인터럽트 우선순위가  0x0 ~ 0xE까지인 인터럽트를 모두 허용한다.(책 105p)

 

sHandlers 변수는 인터럽트 핸들러를 저장하는 변수이다.

 

enable_irq는 CPSR 의 레지스터의 EAIF에서 I 레지스터를 0으로 clear 에서 인터럽트를 활성화 한다.

void enable_irq(void)
{
    __asm__ ("PUSH {R0,R1}");
    __asm__ ("MRS  R0, cpsr");
    __asm__ ("BIC  R1, R0, #0x80");
    __asm__ ("MSR  cpsr, R1");
    __asm__ ("POP  {R0, R1}");
}

void enable_fiq(void)
{
    __asm__ ("PUSH {R0,R1}");
    __asm__ ("MRS  R0, cpsr");
    __asm__ ("BIC  R1, R0, #0x40");
    __asm__ ("MSR  cpsr, R1");
    __asm__ ("POP  {R0, R1}");
}

 

리눅스 커널 5.0 에서도 arch_local_irq_disable 함수에서 DAIF 레지스터중 I레지스터를 1로 setting 하여 인터럽트를 막는 코드가 있는데 이 때 I 레지스터 역시 같은 의미 이다. (I = IRQ, F = FIQ)  

https://kjt9109.tistory.com/entry/archlocalirqdisable?category=825873

 

인터럽트를 활성화 했으니

 

이제 UART와 인터럽트를 연결해줘야 한다.

void Hal_uart_init(void)
{
    //Enable UART
    Uart->uartcr.bits.UARTEN = 0;
    Uart->uartcr.bits.TXE = 1;
    Uart->uartcr.bits.RXE = 1;
    Uart->uartcr.bits.UARTEN = 1;

     Uart->uartimsc.bits.RXIM = 1;

    Hal_interrupt_enable(UART_INTERRUPT0);
    Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0); //UART_INTERRUPT0 = 44
}

Hal_interrupt_enable을 활성화 한다음

Hal_interrupt_register_handler를 통해 이전에 초기화 해줬던 sHandlers에 interrupt_handler를 등록해 준다.

이 때 UART_INTERRUPT0 = 44 이므로 sHandlers[44] = interrupt_handler 주소를 등록하게 된다.

void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num)
{
    sHandlers[interrupt_num] = handler;
}

Interrupt_handler는 입력받은 문자 값을 바로 terminal에 띄워준다.

 

static void interrupt_handler(void)
{
    uint8_t ch = Hal_uart_get_char();
    Hal_uart_put_char(ch);
}

 

정리하면 uart interrupt가 발생하면 interrupt_handler가 호출하여 문자(ch) 값을 띄워준다.

 

그러면 이제 마지막으로 interrupt가 발생하면 Interrupt_handler가 호출 되게 하면 된다.

 

앞서 enable_irq() 함수를 통해 CPSR 레지스터에서 IRQ 레지스터를 enable 시켰다.

 

즉 IRQ_Handler를 통해 Hal_interrupt_run_handler가 호출이 되게 하면 된다.

 

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

 point는 __attribute__ ((interrupt ("IRQ"))) 코드인데  interrupt IRQ는 IRQ 핸들러에 

진입하는 코드와 나가는 코드를 자동으로 만들어 준다.

 

실제 disassembly를 보면

(gdb) disas /m Irq_Handler
Dump of assembler code for function Irq_Handler:
6	{
   0x000001e8 <+0>:	sub	lr, lr, #4
   0x000001ec <+4>:	push	{r0, r1, r2, r3, r4, r11, r12, lr}
   0x000001f0 <+8>:	add	r11, sp, #28

7	    Hal_interrupt_run_handler();
   0x000001f4 <+12>:	bl	0x474 <Hal_interrupt_run_handler>

8	}
   0x000001f8 <+16>:	nop	{0}
   0x000001fc <+20>:	sub	sp, r11, #28
   0x00000200 <+24>:	ldm	sp!, {r0, r1, r2, r3, r4, r11, r12, pc}^

 

0x000001e8 에서 sub lr, lr, #4 실행 하는데 

lr 은 복귀주소 레지스터이며 앞서 부록에 설명 했던 Low Interrupt Latency 기능을 실행 한 것이다.

만약 함수 앞에 __attribute__ ((interrupt ("IRQ"))) 를 넣지 않으면

sub lr, lr.#4 가 생성 되지 않으며 익셉션 핸들러를 수행하고 복귀하지 못해 오동작을 하게 된다.

 

익셉션 핸들러를 만들었으니 이제 이 익셉션 핸들러 함수를 등록해주자

 

앞서 Entry.S 에서 Vector_start에 Exception Vector table 순서에 맞게 함수를 등록해 줬다.

 

 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

 

IRQ Interrupt가 발생하면 ARM 코어는 PC 를 강제적으로 offset 0x18로 강제로 변경 된다.

따라서 irq_handler_addr에 IRQ_Handler를 등록하면 된다.

 

	irq_handler_addr: 	.word Irq_Handler
	fiq_handler_addr:	.word Fiq_Handler

 

 

지금까지 작업을 정리하면 다음과 같다.

 

1.  Main 함수에 while문을 넣어 강제 종료되지 않게 함.

2.  Generic Interrupt Controller 에 Interrupt Register를 Enable 한다.

3.  CPSR에 EAIF 레지스터에서 I,F 레지스터비트들을 0으로 clear 하여 인터럽트를 활성화한다.

4.  UART interrupt를 활성화 한 다음 IRQ _Handler에 UART Interrupt Handler를 연결한다.

5. IRQ Vector Table 주소에 IRQ_Handler를 연결한다.