UART 인터럽트의 흐름은 다음과 같다.
UART Interrupt -> Interrupt Controller -> ARM CPU -> Exception Handler
인터럽트를 처리하기 위해서는 먼저 인터럽트 컨트롤러를 초기화하고 사용하는 코드를 작성하여야 한다. 그 후, 인터럽트를 발생시키는 하드웨어와 인터럽트 컨트롤러를 연결한다.
RealViewPB 에는 Generic Interrupt Controller, GIC 라는 인터럽트 컨트롤러가 존재한다.
이 GIC 는 두가지로 나누어지는데,
Distributor와 CPU Interface 이다.
GIC Distributor
인터럽트들을 우선순위에 따라 CPU Interface 에 제공하는 역할을 수행한다.
GICD 로 불림.
Offset: PB-A8 GIC0 distributor registers 0x1E001000
GIC CPU Interface
연결되어 있는 프로세서에 전달할때 인터럽트의 우선순위에 따른 마스킹과 preemption handling 을 수행한다. 설정된 우선순위보다 높은 우선순위를 가진 인터럽트만 프로세서에 전달한다.
GICC로 불림.
Offset: PB-A8 GIC0 CPU interface registers 0x1E000000
Register 구조체화
hal/rvpb/Interrupt.h
생략된 곳은 데이터시트를 참고해서 작성하면 된다.
11 typedef union CpuControl_t
12 {
13 uint32_t all;
14 struct {
15 uint32_t Enable:1; // 0
16 uint32_t reserved:31;
17 } bits;
18 } CpuControl_t;
...
79 typedef union DistributorCtrl_t
80 {
81 uint32_t all;
82 struct {
83 uint32_t Enable:1; // 0
84 uint32_t reserved:31;
85 } bits;
86 } DistributorCtrl_t;
...
100 typedef struct GicCput_t
101 {
102 CpuControl_t cpucontrol; //0x000
103 PriorityMask_t prioritymask; //0x004
104 BinaryPoint_t binarypoint; //0x008
105 InterruptAck_t interruptack; //0x00C
106 EndOfInterrupt_t endofinterrupt; //0x010
107 RunningInterrupt_t runninginterrupt; //0x014
108 HighestPendInter_t highestpendinter; //0x018
109 } GicCput_t;
110
111 typedef struct GicDist_t
112 {
113 DistributorCtrl_t distributorctrl; //0x000
114 ControllerType_t controllertype; //0x004
115 uint32_t reserved0[62]; //0x008-0x0FC
116 uint32_t reserved1; //0x100
117 uint32_t setenable1; //0x104
118 uint32_t setenable2; //0x108
119 uint32_t reserved2[29]; //0x10C-0x17C
120 uint32_t reserved3; //0x180
121 uint32_t clearenable1; //0x184
122 uint32_t clearenable2; //0x188
123 } GicDist_t;
125 #define GIC_CPU_BASE 0x1E000000 //CPU interface
126 #define GIC_DIST_BASE 0x1E001000 //distributor
127
128 #define GIC_PRIORITY_MASK_NONE 0xF
129
130 #define GIC_IRQ_START 32
131 #define GIC_IRQ_END 95
hal/rvpb/Regs.h
GICC 와 GICD BASE 추가
3 #include "Interrupt.h"
4
...
6 volatile GicCput_t *GicCpu = (GicCput_t*)GIC_CPU_BASE;
7 volatile GicDist_t *GicDist = (GicDist_t*)GIC_DIST_BASE;
공용 인터페이스 추가
hal/HalInterrupt.h
4 #define INTERRUPT_HANDLER_NUM 255
5
6 typedef void (*InterHdlr_fptr)(void); //interrupt handler function pointer type definition
7
8 void Hal_interrupt_init(void); //초기화 함수
9 void Hal_interrupt_enable(uint32_t interrupt_num); //인터럽트 활성화 함수
10 void Hal_interrupt_disable(uint32_t interrupt_num); //인터럽트 비활성화 함수
11 void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num); //connects the handler with the specific interrupt
12 void Hal_interrupt_run_handler(void); //인터럽트 핸들러 호출 함수
ex) UART 인터럽트 ID 는 44번 이므로, Hal_interrupt_enable(44) 를 통해 인터럽트를 활성화할 수 있다.
hal/rvpb/Interrupt.c
1 #include "stdint.h"
2 #include "memio.h"
3 #include "HalInterrupt.h"
4 #include "Interrupt.h"
5 #include "armcpu.h"
6
7 //defined in Regs.h
8 extern volatile GicCput_t *GicCpu;
9 extern volatile GicDist_t *GicDist;
10
11
12 static InterHdlr_fptr sHandlers[INTERRUPT_HANDLER_NUM]; //인터럽트 핸들러 array
13
14 void Hal_interrupt_init(void) {
15 GicCpu->cpucontrol.bits.Enable = 1;
16 GicCpu->prioritymask.bits.Prioritymask = GIC_PRIORITY_MASK_NONE; //0XF, does not mask any of the interrupt request
17 GicDist->distributorctrl.bits.Enable = 1;
18
19 for(uint32_t i = 0; i <= INTERRUPT_HANDLER_NUM; i++) {
20 sHandlers[i] = NULL;
21 }
22
23 enable_irq(); //disable IRQ masking in cspr
24 // disable_fiq(); //enable FIQ masking in cspr
25 }
26
27 void Hal_interrupt_enable(uint32_t interrupt_num) { //enable interrupt_num interrupt
28 if((interrupt_num < GIC_IRQ_START) || (GIC_IRQ_END < interrupt_num)) {
29 return; //
30 }
31
32 uint32_t bit_num = interrupt_num - GIC_IRQ_START; //setenable1 -> 0~31:32~63 setenable2 -> 0~31:64~95
33
34 if(bit_num < GIC_IRQ_START) { //interrupt num is within the setenable1 range.
35 SET_BIT(GicDist->setenable1,bit_num); //GicDis->setsenable1 의 bit_num 필드를 1로 SET 함.
36 }
37 else {
38 bit_num -= GIC_IRQ_START;
39 SET_BIT(GicDist->setenable2,bit_num);
40 }
41
42 }
... Hal_interrupt_disable 의 경우 enable 의 반대로 작성하면 되므로 생략.
61 void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num) { //인터럽트 ID 에 해당하는 인터럽트 핸들러 함수 등록
62 sHandlers[interrupt_num] = handler;
63 }
64
65 void Hal_interrupt_run_handler(void) { //인터럽트 발생시 실행.
66 uint32_t interrupt_num = GicCpu->interruptack.bits.InterruptID;
//interruptack.bits.InterruptID 에서 현재 대기중인 Interrupt ID 추출 가능.
67
68 if(sHandlers[interrupt_num] != NULL) {
69 sHandlers[interrupt_num]();
70 }
71
72 GicCpu->endofinterrupt.bits.InterruptID = interrupt_num;
73 }
include/memio.h
1 #define SET_BIT(p,n) ((p) |= (1 << (n)))
2 #define CLR_BIT(p,n) ((p) &= !(1 << (n)))
이제 enable/disable_irq/fiq, CSPR 의 IRQ/FIQ 마스크를 비활성화하는 함수.
lib/armcpu.h
1 #ifndef LIB_ARMCPU_H
2 #define LIB_ARMCPU_H
3
4 void enable_irq(void);
5 void disable_irq(void);
6 void enable_fiq(void);
7 void disable_fiq(void);
8
9 #endif //LIB_ARMCPU_H
lib/armcpu.c
_ asm _ 인라인 어셈블리어를 사용하면 스택에 레지스터를 백업 및 복구하는 코드와 리턴 처리하는 코드를 컴파일러가 자동으로 생성한다.
1 #include "armcpu.h"
2
3 void enable_irq(void) {
4 __asm__ ("PUSH {r0, r1}");
5 __asm__ ("MRS r0, cpsr"); //move register from special register
6 __asm__ ("BIC r1, r0, #0x80"); //bit clear, 0x80 은 CSPR 의 IRQ 마스크 비트, 0을 쓴다.
7 __asm__ ("MSR cpsr, r1");
8 __asm__ ("POP {r0, r1}");
9 }
10
11
12 void disable_irq(void) {
13 __asm__ ("PUSH {r0, r1}");
14 __asm__ ("MRS r0, cpsr");
15 __asm__ ("ORR r1, r0, #0x80"); //1을 쓴다
16 __asm__ ("MSR cpsr, r1");
17 __asm__ ("POP {r0, r1}");
18 }
19
20 void enable_fiq(void) {
21 __asm__ ("PUSH {r0, r1}");
22 __asm__ ("MRS r0, cpsr");
23 __asm__ ("BIC r1, r0, #0x40"); //0x40 은 FIQ의 마스크 비트
24 __asm__ ("MSR cpsr, r1");
25 __asm__ ("POP {r0, r1}");
26 }
27
28
29 void disable_fiq(void) {
30 __asm__ ("PUSH {r0, r1}");
31 __asm__ ("MRS r0, cpsr");
32 __asm__ ("ORR r1, r0, #0x80");
33 __asm__ ("MSR cpsr, r1");
34 __asm__ ("POP {r0, r1}");
35 }
여기까지 GIC 를 초기화하고 설정하는 작업.
hal/rvpb/Uart.c 추가
8 static void interrupt_handler(void);
10 void Hal_uart_init(void) {
17 //Enable input interrupt
18 Uart->uartimsc.bits.RXIM = 1; //Interrutpt Mask Set/Clear bit, Receive Interrupt Mask
20
21 //Register UART interrupt
22 Hal_interrupt_enable(UART_INTERRUPT0);
23 Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
24 }
...
48 static void interrupt_handler(void) {
49 uint8_t ch = Hal_uart_get_char();
50 Hal_uart_put_char(ch);
51 }
boot/Main.c
이제 Uart_init 을 실행하기 위해서는 Hal_interrupt_init, GIC 를 먼저 초기화해야한다. 따라서 다음과 같은 순서로 Hw_init 함수를 수정한다.
28 static void Hw_init(void) {
29 Hal_interrupt_init();
30 Hal_uart_init();
31 }
Interrupt 발생 -> IRQ 익셉션 발생 -> 익셉션 벡터 테이블의 IRQ 익셉션 벡터로 점프
이제 Exception Handler 의 Interrupt Exception Vector 와 GIC의 인터럽트 핸들러를 연결하여야 한다.
boot/Handler.c
1 #include "stdbool.h"
2 #include "stdint.h"
3 #include "HalInterrupt.h"
4
5 __attribute__((interrupt ("IRQ"))) void Irq_handler(void) { //IRQ entry code auto set, GCC builtin feature
6 Hal_interrupt_run_handler();
7 }
8
9 __attribute__ ((interrupt ("FIQ"))) void Fiq_handler(void) {
10 while(true);
11 }
~
Exception Vector 에 연결
boot/Entry.S
25 irq_handler_addr: .word Irq_handler
26 fiq_handler_addr: .word Fiq_handler