.
├── 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를 연결한다.
'Firmware > RTOS' 카테고리의 다른 글
임베디드 OS 개발 프로젝트 9(Timer) (0) | 2020.02.16 |
---|---|
임베디드 OS 개발 프로젝트 8(Uart Interrupt) (0) | 2020.02.15 |
임베디드 OS 개발 프로젝트 6(UART 송수신) (0) | 2020.01.27 |
임베디드 OS 개발 프로젝트 5 (0) | 2020.01.26 |
임베디드 OS 개발 프로젝트 A-1 (2) | 2020.01.25 |