인터럽트 사용하기

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

+ Recent posts