인터럽트 사용하기
1. 인터럽트 컨트롤러 초기화 코드 구현
2. 인터럽트 발생 하드웨어와 인터럽트 컨트롤러 연결
3. 익셉션 핸들러 구현하여 적절한 인터럽트 핸들러 호출
UART 인터럽트
- UART 하드웨어에서 인터럽트 발생 -> 컨트롤러로 전달 -> 인터럽트 컨트롤러는 ARM 코어로 전달.
인터럽트 컨트롤러
RealViewPB에서는 GIC general interrupt contller 하드웨어가 존재
레지스터 구조체
hal/rvpb/Interrupt.h
#ifndef HAL_RVPB_INTERRUPT_H_
#define HAL_RVPB_INTERRUPT_H_
typedef union CpuControl_t
{
uint32_t all;
struct {
uint32_t Enable:1; // 0
uint32_t reserved:31;
} bits;
} CpuControl_t;
typedef union PriorityMask_t
{
uint32_t all;
struct {
uint32_t Reserved:4; // 3:0
uint32_t Prioritymask:4; // 7:4
uint32_t reserved:24;
} bits;
} PriorityMask_t;
typedef union BinaryPoint_t
{
uint32_t all;
struct {
uint32_t Binarypoint:3; // 2:0
uint32_t reserved:29;
} bits;
} BinaryPoint_t;
typedef union InterruptAck_t
{
uint32_t all;
struct {
uint32_t InterruptID:10; // 9:0
uint32_t CPUsourceID:3; // 12:10
uint32_t reserved:19;
} bits;
} InterruptAck_t;
typedef union EndOfInterrupt_t
{
uint32_t all;
struct {
uint32_t InterruptID:10; // 9:0
uint32_t CPUsourceID:3; // 12:10
uint32_t reserved:19;
} bits;
} EndOfInterrupt_t;
typedef union RunningInterrupt_t
{
uint32_t all;
struct {
uint32_t Reserved:4; // 3:0
uint32_t Priority:4; // 7:4
uint32_t reserved:24;
} bits;
} RunningInterrupt_t;
typedef union HighestPendInter_t
{
uint32_t all;
struct {
uint32_t InterruptID:10; // 9:0
uint32_t CPUsourceID:3; // 12:10
uint32_t reserved:19;
} bits;
} HighestPendInter_t;
typedef union DistributorCtrl_t
{
uint32_t all;
struct {
uint32_t Enable:1; // 0
uint32_t reserved:31;
} bits;
} DistributorCtrl_t;
typedef union ControllerType_t
{
uint32_t all;
struct {
uint32_t IDlinesnumber:5; // 4:0
uint32_t CPUnumber:3; // 7:5
uint32_t reserved:24;
} bits;
} ControllerType_t;
typedef struct GicCput_t
{
CpuControl_t cpucontrol; //0x000
PriorityMask_t prioritymask; //0x004
BinaryPoint_t binarypoint; //0x008
InterruptAck_t interruptack; //0x00C
EndOfInterrupt_t endofinterrupt; //0x010
RunningInterrupt_t runninginterrupt; //0x014
HighestPendInter_t highestpendinter; //0x018
} GicCput_t;
typedef struct GicDist_t
{
DistributorCtrl_t distributorctrl; //0x000
ControllerType_t controllertype; //0x004
uint32_t reserved0[62]; //0x008-0x0FC
uint32_t reserved1; //0x100
uint32_t setenable1; //0x104
uint32_t setenable2; //0x108
uint32_t reserved2[29]; //0x10C-0x17C
uint32_t reserved3; //0x180
uint32_t clearenable1; //0x184
uint32_t clearenable2; //0x188
} GicDist_t;
#define GIC_CPU_BASE 0x1E000000 //CPU interface
#define GIC_DIST_BASE 0x1E001000 //distributor
#define GIC_PRIORITY_MASK_NONE 0xF
#define GIC_IRQ_START 32
#define GIC_IRQ_END 95
#endif /* HAL_RVPB_INTERRUPT_H_ */
GIC에서는
CPU interpace resister와 distribution resister 제공
두 레지스터 구조체의 베이스 주소가 있으니
hal/rvpb/Regs.c에 인스턴스 선언
#include <stdint.h>
#include "Uart.h"
#include "Interrupt.h"
volatile PL011_t* Uart = (PL011_t*)UART_BASE_ADDRESS0;
volatile GicCput_t* GicCpu = (GicCput_t*)GIC_CPU_BASE;
volatile GicDist_t* GicDist = (GicDist_t*)GIC_DIST_BASE;
공용 인터럽트 API로
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
인터럽트 함수 구현
Regs.c에 선언한 GicCpu, GicDist 인스턴스 가져와서
초기화, 활성, 비활성화, 등록, 핸들러 동작 하도록
비트를 이래저래 하는대
인클루드 하는 헤더중에
memio.h와 armcpu.h는 바로 뒤에 나온다.
enable 부분에서 enable1, 2가 있는데
GIC는 인터럽트 64개를 관리해서 setenable1, setenable2 레지스터에서 32개씩 관리
IRQ는 32번부터 시작 -> ID32 ~ ID95
UART는 IRQ ID 44 -> setenable1레지스터의 12번 비트에 연결
hal/rvpb/Interrupt.c
#include <stdint.h>
#include <stddef.h>
#include "memio.h"
#include "armcpu.h"
#include "Interrupt.h"
#include "HalInterrupt.h"
extern volatile GicCput_t* GicCpu;
extern volatile GicDist_t* GicDist;
static InterHdlr_fptr sHandlers[INTERRUPT_HANDLER_NUM];
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();
}
void Hal_interrupt_enable(uint32_t interrupt_num)
{
if ((interrupt_num < GIC_IRQ_START) || (GIC_IRQ_END < interrupt_num))
{
return;
}
uint32_t bit_num = interrupt_num - GIC_IRQ_START;
if(bit_num < GIC_IRQ_START)
{
SET_BIT(GicDist->setenable1, bit_num);
}
else
{
bit_num -= GIC_IRQ_START;
SET_BIT(GicDist->setenable2, bit_num);
}
}
void Hal_interrupt_disable(uint32_t interrupt_num)
{
if ((interrupt_num < GIC_IRQ_START) || (GIC_IRQ_END < interrupt_num))
{
return;
}
uint32_t bit_num = interrupt_num - GIC_IRQ_START;
if(bit_num < GIC_IRQ_START)
{
CLR_BIT(GicDist->setenable1, bit_num);
}
else
{
bit_num -= GIC_IRQ_START;
CLR_BIT(GicDist->setenable2, bit_num);
}
}
void Hal_interrupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num)
{
sHandlers[interrupt_num] = 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;
}
SET_BIT, CLR_BIT 매크로 구현을 위해
include/memio.h 구현
#define SET_BIT(p, n) ((p) |= (1 << (n)))
#define CLR_BIT(p, n) ((p) &= ~(1 << (n)))
armcpu.h, armcpu.c 를 작성하여 IRQ, FIQ 인에이블 디스에이블 구현
lib/armcpu.h
#ifndef LIB_ARMCPU_H_
#define LIB_ARMCPU_H_
void enable_irq(void);
void enable_fiq(void);
void disable_irq(void);
void disable_fiq(void);
#endif
cspr을 제어하기 위해서
인라인 어셈블리어 사용(장점 : 컴파일러가 레지스터 백업, 복구, 리턴처리를 자동으로 만듬)
<-> 완전 어셈블러어로 작성시 직접 다 구현해야함.
lib/armcpu.c
#include "armcpu.h"
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}");
}
void disable_irq(void)
{
__asm__ ("PUSH {r0, r1}");
__asm__ ("MRS r0, cpsr");
__asm__ ("ORR r1, r0, #0x80");
__asm__ ("MSR cpsr, r1");
__asm__ ("POP {r0, r1}");
}
void disable_fiq(void)
{
__asm__ ("PUSH {r0, r1}");
__asm__ ("MRS r0, cpsr");
__asm__ ("ORR r1, r0, #0x40");
__asm__ ("MSR cpsr, r1");
__asm__ ("POP {r0, r1}");
}
지금까지 GIC 설정했으니
인터럽트 사용하기
1. 인터럽트 컨트롤러 초기화 코드 구현 V
2. 인터럽트 발생 하드웨어와 인터럽트 컨트롤러 연결 <-
3. 익셉션 핸들러 구현하여 적절한 인터럽트 핸들러 호출
UART와 인터럽트 컨트롤러를 연결할 차례
uart 초기화 함수에서
uart 인터럽트를 인에이블하고, 인터럽트 헨들러를 등록하는데 뜬금없이
UART_INTERRUPT0 이게 보인다.
어디있나 찾아보니 hal/rvpb/Uart.h에 이미 정의되있더라.
hal/rvpb/Uart.c 코드
#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"
extern volatile PL011_t* Uart;
static void interrupt_handler();
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;
Hal_interrupt_enable(UART_INTERRUPT0);
Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}
void Hal_uart_put_char(uint8_t ch)
{
while(Uart->uartfr.bits.TXFF);
Uart->uartdr.all = (ch & 0xFF);
}
uint8_t Hal_uart_get_char(void)
{
uint8_t data;
while(Uart->uartfr.bits.RXFE);
//check for and error flag
if (Uart->uartdr.all & 0xFFFFFF00)
{
//clear the error
Uart->uartrsr.all = 0xFF;
return 0;
}
data = Uart->uartdr.bits.DATA;
return data;
}
static void interrupt_handler()
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
}
UART 하드웨어와 인터럽트 컨트롤러를 연결하면
main 함수도 수정
100회 풀링 방식 입력 대신 인터럽트 처리하도록 무한루프 돌리고
하드웨어 초기화 부분에 인터럽트 초기화 추가
#include <stdint.h>
#include "HalInterrupt.h"
#include "HalUart.h"
#include "stdio.h"
static void Hw_init(void);
static void Printf_test(void);
void main(void)
{
Hw_init();
uint32_t i = 100;
while(i--)
{
Hal_uart_put_char('N');
}
Hal_uart_put_char('\n');
putstr("Hello World!\n");
Printf_test();
while(1);
}
static void Hw_init(void)
{
Hal_interrupt_init();
Hal_uart_init();
}
static void Printf_test(void)
{
char* str = "printf pointer test";
char* nullptr = 0;
uint32_t i = 5;
debug_printf("%s\b", "Hello printf");
debug_printf("output string pointer: %s\n", str);
debug_printf("%s is null pointer, %u number\n", nullptr, 10);
debug_printf("%u=5\n", i);
debug_printf("dec=%u hex=%x\n", 0xff, 0xff);
debug_printf("print zero %u\n", 0);
}
ARM 코어가 인터럽트 받음(여기선 IRQ만 사용)
-> IRQ 익셉션 발생
-> IRQ 동작 모드로 전환
-> 익셉션 벡터 테이블의 IRQ 익셉션 벡터로 점프해서 실행
<-> 하지만 아직 IRQ 익셉션 벡터와 인터럽트 핸들러가 연결안됨
=> 익셉션 벡터 테이블 IRQ 익셉션 벡터와 인터럽트 컨트롤러 인터럽트 핸들러 연결할 차례
boot/Handler.c
익셉션 핸들러
__attribute__는 GCC의 컴파일러 확장기능 사용을 의미하는 지시어
__attribute__ ((interrupt ("IRQ")))는 IRQ 핸들러에 진입, 나오는 코드를 자동으로 만들어줌
* hal interrupt run handler는 현재 인터럽트 번호를 가져와 해당 인터럽트 핸들러 호출
#include <stdbool.h>
#include <stdint.h>
#include "HalInterrupt.h"
__attribute__ ((interrupt ("IRQ"))) void Irq_Handler(void)
{
Hal_interrupt_run_handler();
}
__attribute__ ((interrupt ("FIQ"))) void Fiq_Handler(void)
{
while(true);
}
boot/Entry.S 익셉션 벡터 테이블
irq_handler_addr과 fiq_handler_add을
Irq_Handler와 Fiq_Handler로 연결
reset_handler의 main을 통해 메인함수에 진입해서 동작하다가
arm 코어가 인터럽트 받으면 하드웨어적으로 irq 익셉션 발생해서 동작모드 바꾸고
irq handler로 넘어가 인터럽트 수행 후 돌아온다는 건가 싶다.
#include "ARMv7AR.h"
#include "MemoryMap.h"
.text
.code 32
.global vector_start
.global vector_end
vector_start:
LDR PC, reset_handler_addr
LDR PC, undef_handler_addr
LDR PC, svc_handler_addr
LDR PC, pftch_abt_handler_addr
LDR PC, data_abt_handler_addr
B .
LDR PC, irq_handler_addr
LDR PC, fiq_handler_addr
reset_handler_addr: .word reset_handler
undef_handler_addr: .word dummy_handler
svc_handler_addr: .word dummy_handler
pftch_abt_handler_addr: .word dummy_handler
data_abt_handler_addr: .word dummy_handler
irq_handler_addr: .word Irq_Handler
fiq_handler_addr: .word Fiq_Handler
vector_end:
reset_handler:
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_SVC
MSR cpsr, r1
LDR sp, =SVC_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_IRQ
MSR cpsr, r1
LDR sp, =IRQ_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_FIQ
MSR cpsr, r1
LDR sp, =FIQ_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_ABT
MSR cpsr, r1
LDR sp, =ABT_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_UND
MSR cpsr, r1
LDR sp, =UND_STACK_TOP
MRS r0, cpsr
BIC r1, r0, #0x1F
ORR r1, r1, #ARM_MODE_BIT_SYS
MSR cpsr, r1
LDR sp, =USRSYS_STACK_TOP
BL main
dummy_handler:
B .
.end
다 잘 작성한거 같은데
키보드 입력 안되길래 보니
Uart.c에서 입력 인터럽트 인에이블 시키는걸 빠트렸다.
Uart->uartimsc.bits.RXIM
그리고 계속 키보드 입력을 줘도 값이 밀렸엇는데 다시보니 최적화된 걸 안써서 그렇더라
수정한 hal/rvpb/Uart.c
#include <stdint.h>
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"
extern volatile PL011_t* Uart;
static void interrupt_handler(void);
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);
}
void Hal_uart_put_char(uint8_t ch)
{
while(Uart->uartfr.bits.TXFF);
Uart->uartdr.all = (ch & 0xFF);
}
uint8_t Hal_uart_get_char(void)
{
uint32_t data;
while(Uart->uartfr.bits.RXFE);
data = Uart->uartdr.all;
//check for and error flag
if (data & 0xFFFFFF00)
{
//clear the error
Uart->uartrsr.all = 0xFF;
return 0;
}
return (uint8_t)(data & 0xff);
}
static void interrupt_handler(void)
{
uint8_t ch = Hal_uart_get_char();
Hal_uart_put_char(ch);
}
이젠 uart 인터럽트도 잘 동작한다.
현재 소스트리
'컴퓨터과학 > os' 카테고리의 다른 글
navilos - 12. 태스크 (0) | 2022.08.22 |
---|---|
navilos - 11. 타이머로 delay 구현 (0) | 2022.08.21 |
나빌로스 하다가 찾은 문제 (0) | 2022.08.20 |
navilos - 9. UART 3 printf 구현하기 (0) | 2022.08.20 |
navilos - 8. UART 2 putstr,getchar (0) | 2022.08.20 |