Chapter 6: Interrupt

1231·2024년 6월 13일

임베디드 OS 개발

목록 보기
4/8

GIC Registers

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 를 초기화하고 설정하는 작업.

UART Input Interrupt 연결

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 & Exception Vector 연결

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

0개의 댓글