타나기
타나기 월드
타나기
전체 방문자
오늘
어제
  • 분류 전체보기 (90)
    • ⚙️ Rust (20)
      • 👀 Tutorial (20)
    • 🗿 Embedded (11)
      • 🐧 OS for ARM (11)
    • 💻 Study (37)
      • 🧩 알고리즘 (37)
    • 🏄🏽‍♂️ Life (21)
      • 🍚 타나구루망 (20)
      • 💡 Light (1)

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
타나기

타나기 월드

[ARM/OS 만들기] 6. OS의 인터럽트 (interrupt)
🗿 Embedded/🐧 OS for ARM

[ARM/OS 만들기] 6. OS의 인터럽트 (interrupt)

2022. 5. 18. 21:50

6. Interrupt

  • 인터럽트는 임베디드 시스템을 표함한 모든 컴퓨팅 시스템의 꽃이다.
  • 컴퓨팅 시스템은 외부와의 상호작용으로 인터럽트를 이용한다.
  • 인터럽트는 처리하기 위해 인터럽트 컨트롤러를 사용하는 법을 알고, 인터럽트 컨트롤러를 초기화하고 사용하는 코드를 작성해야 한다.
  • 익셉션 핸들러에서 적절한 인터럽트 핸들러를 호출하면 인터럽트 처리가 완료된다.
  • 우선 main 함수의 맨 끝에 무한루프를 삽입해 OS를 멈춰놓고, 인터럽트를 테스트해 보자.

6.1 인터럽트 컨트롤러

  • RealViewPB에는 Generic Interrupt Contoller라는 이름의 인터럽트 컨트롤러 하드웨어가 달려있다.
  • 먼저 GIC의 레지스터 구조체를 만든다.
  • hal/rvpb/Interrupt.h 파일에 작성한다.
  • GIC에 관한 내용은 Spec의 4.11.2장에 나와있다.
  • 앞부분만 조금 살펴 보자면 아래의 이미지와 같다.
  • GIC는 크게 두 그룹으로 구분된다.
  • 하나는 CPU Interface registers이고, 다른 하나는 distributor registers다.
  • 실제로 GIC는 작성한 레지스터보다 훨씬 더 많은 레지스터를 가지고 있지만, 대부분 사용하지 않는 것이기 때문에 적당한 추상과 과정을 거쳤다.
  • 이제 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 DistributorCtrl_t*  GicDist_t   = (GicDist_t*)GIC_DIST_BASE;
  • 이제 공용 API를 제작할 차례이다.
  • UART와 같이, 제조사와 상관 없이 동작하도록 API를 만들어 주자.
  • hal/HalInterrupt.h 파일에 작성한다.
  • 초기화, 활성화, 비활성화, 핸들러 등록 등의 API를 작성한다.
#ifndef HAL_HALINTERRUPT_H_
#define HAL_INTERRUPT_H_

#define INTERRUPT_HANDLER_NUM 255

typedef void (*InterHdlr_fptr)(void);

void Hal_interupt_init(void);
void Hal_interupt_enable(uint32_t interrupt_num);
void Hal_interupt_disable(uint32_t interrupt_num);
void Hal_interupt_register_handler(InterHdlr_fptr handler, uint32_t interrupt_num);
void Hal_interupt_run_handler(void);

#endif
  • 활성화, 비활성화 함수는 인자로 인터럽트의 번호를 할당 받는다.
  • 앞서 제작한 Uart는 44번이다. 따라서 Hal_interrupt_enable()에 44를 전달하면 UART 인터럽트를 키고, disable 함수에 전달하면 끄게 된다.
  • ARM은 모든 인터럽를 IRQ와 FIQ 핸들러로 처리하기 때문에, 개별 이터럽트의 핸들러를 구분해야 한다.
  • 그럼 이제 위에 선언한 함수를 구현해 보자.
  • 구현 위치는 hal/rvpb/Interrupt.c 이다.
#include "stdint.h"
#include "memio.h"
#include "Interrpt.h"
#include "HalInterrupt.h"
#include "armcpu.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;

    
}
  • static InterHdlr_fptr sHandlers[INTERRUPT_HANDLER_NUM]
    • 인터럽트 핸들러 이고, 255로 선언 되었다. 함수 포인터이다.
  • Hal_interrupt_init()
    • 이 함수는 스위치를 겨는 동작을 한다.
    • Priority mask 레지스터를 이용해 키고 끈다. 
    • 위의 설명에 나와있듯이, 4번부터 7번비트까지가 유효한 비트이다.
    • 0x0 으로 설정하게 되면 모든 비트를 마스크 한다.
    • 0xF로 설정하게 되면 인터럽트의 우선순위가 0x0~0xE인 인터럽트를 허용한다.
    • 위의 코드에서는 모든 인터럽트를 허용한다. 
  • GIC는 64개의 인터럽트를 관리할 수 있다.
    • setenable1 과 setenable2로 관리한다.
    • IRQ의 시작 번호는 32이다.
    • GIC는 0번부터 15번까지를 Software Interrupt를 위해 사용하고, 16~31를 GIC software interrupt를 위해 reserve해 놓았다.
    • 나머지가 IRQ Interrupt이다. 각 레지스터의 개별 비트를 IRQ ID32 부터 IRQ ID 95까지 연결했다.
  • 시작번호가 32번부터 이기 때문에 오프셋을 구하기 위해선, ID에서 32를 빼고, 그래도 32보다 크다면 다시 32를 빼면 된다.
    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);
    }
  • 나머지는 레지스터를 등록하고, 실행하는 코드이다.

6.2 UART 입력과 인터럽트 연결

  • 작성한 인터럽트와 하드웨어를 연결해보자.
  • UART에 연결해 보도록 하자.
  • 아래와 같이 Uart.c 파일을 수정하고 추가해 준다
#include "stdint.h"
#include "Uart.h"
#include "HalUart.h"
#include "HalInterrupt.h"

extern volatile PL011_t* 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;

   // Enable input interrupt
   Uart->uartimsc.bits.RXIM = 1;

   // Register Uart interrupt handler.
   Hal_interrupt_enable(UART_INTERRUPT0);
   Hal_interrupt_register_handler(interrupt_handler, UART_INTERRUPT0);
}

static void interrupt_handler(){
   uint8_t ch = Hal_uart_get_char();
   Hal_uart_put_char();
}
  • UART 입력이 발생하게 되면, interrupt_handler() 함수를 코어가 자동으로 실행한다.
  • 위와 같이 코딩해 놓았기 때문에, 현재는 그저 입력받은 함수를 다시 출력하는 역할을 수행한다.
  • 이제 Main.c 파일에서 하드웨어 초기화 코드를 수정한다.
static void Hw_init(void){
    Hal_interrupt_init();
    Hal_uart_init();
}
  • 보는 바와 같이 interrupt를 먼저 초기화 해 주어야 한다.
  • 인터럽트가 먼저 초기화가 되어야 uart에서 사용할 수 있기 때문이다.

6.3 IRQ 인셉션 벡터 연결.

  • 6장에서 다룬 내용은 다음과 같다.
    • main()함수를 무한루프를 주어 종료하지 않도록 설정.
    • 인터럽트 컨트롤러 초기화
    • cspr의 IRQ 마스크를 해제
    • uart 인터럽트 핸들러를 인터럽트 컨트롤러에 등록
    • 인터럽트 컨트롤러와 uart 하드웨어 초기화 순서 조정.
  • 이제 마지막으로 IRQ 익셉션 벡터와 인터럽트 컨트롤러의 인터럽트 핸들러를 연결하는 작업이다.
  • 그러므로 익셉션 핸들러를 만들도록 한다. @boot/Handler.c
#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);
}
  • __attribute__ 는 GCC의 컴파일러 확장 기능을 사용하겠다는 지시어이다.
  • ("IRQ") 와 ("FIQ")는 ARM 전용 지시어이다.
  • 해당 구문을 통해 핸들러에 진입하는 코드와 나가는 코드를 자동으로 만둘어 준다.
  • 이제 Entry.S 파일에 있는 핸들러를 수정해 준다.
  • irq_handler_addr: .word dummy_handler 의 dummy_handler 부분을 Irq_Handler 로 변경한다.
  • fiq_handler_addr: .word dummy_handler 의 dummy_handler 부분을 Fiq_Handler 로 변경한다.
  • 이제 잘 작동 하는지 실행 시켜 보자
tanagy@DESKTOP-3IQ83O6:/mnt/c/Folder/study/EmbededOS/chapter_6$ ./qemu_start.sh 
shared memfd open() failed: Function not implemented
pulseaudio: pa_context_connect() failed
pulseaudio: Reason: Connection refused
pulseaudio: Failed to initialize PA contextaudio: Could not init `pa' audio driver
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default
alsa: Could not initialize DAC
alsa: Failed to open `default':
alsa: Reason: No such file or directory
ALSA lib confmisc.c:767:(parse_card) cannot find card '0'
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1246:(snd_func_refer) error evaluating name
ALSA lib conf.c:4528:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:5007:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2495:(snd_pcm_open_noupdate) Unknown PCM default
alsa: Could not initialize DAC
alsa: Failed to open `default':
alsa: Reason: No such file or directory
audio: Failed to create voice `lm4549.out'
NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
Hello World!!
This is a Test!!
This is a Decimal : 1234
This is a Hex : 85
  • 와 같이 나오며, uart로 입력이 가능하게 된다!

6.6 요약

  • 이번 장에서는 인터럽트 컨트롤러를 사용해 키보드 입력을 받았다. 인터럽트는 펌웨어의 기능을 구현하는데 매우 핵심적이고 중요한 요소이다. 이제 다음장에선 타이머를 다뤄, 시간을 제어해 보도록 하자.
저작자표시 (새창열림)

'🗿 Embedded > 🐧 OS for ARM' 카테고리의 다른 글

[ARM/OS 만들기] 8. Task 컨트롤  (0) 2022.05.24
[ARM/OS 만들기] 7. 타이머(timer) 초기화, 동작 개발  (0) 2022.05.20
[ARM/OS 만들기] 5. Uart 모듈 개발.  (0) 2022.05.07
[ARM/OS 만들기] 4. 부팅하기!  (0) 2022.05.07
[ARM/OS 만들기] 3. 시작하기.  (0) 2022.05.06
    '🗿 Embedded/🐧 OS for ARM' 카테고리의 다른 글
    • [ARM/OS 만들기] 8. Task 컨트롤
    • [ARM/OS 만들기] 7. 타이머(timer) 초기화, 동작 개발
    • [ARM/OS 만들기] 5. Uart 모듈 개발.
    • [ARM/OS 만들기] 4. 부팅하기!
    타나기
    타나기
    #include<all>

    티스토리툴바