OS/OS from Scratch

[OS] 20. Interrupt-timer

jschang 2021. 7. 18. 00:27

서론

이번 강의에서는 CPU Timer와 키보드 Interrupt를 구현하겠습니다. 해당 Github 강의는 다음과 같습니다.

https://github.com/cfenollosa/os-tutorial/tree/master/20-interrupts-timer

 

cfenollosa/os-tutorial

How to create an OS from scratch. Contribute to cfenollosa/os-tutorial development by creating an account on GitHub.

github.com


이론

PIT (Programmable Interval Timer)

PIT chip(문서)은 oscillator, prescaler, frequency dividers로 구성되며, frequency divider은 Timer가 IRQ0와 같은 외부 Circuitry를 제어할 수 있게 합니다. PIT의 oscillator는 약 1.193180 MHz로 작동하는데, freqeuncy divider는 이 진동수를 divider로 나누어서 더 느린 진동수로 작동하게 합니다. 입력 진동수에 따라 펄스가 발생할 때마다 counter를 감소시키고, counter가 0이 되면 counter가 리셋됩니다. 예를 들어 입력 진동수가 200Hz이고, counter가 10번마다 리셋되면, 출력 진동수는 200/10=20Hz가 되는 것입니다. PIT의 frequency divider는 크기가 16bit이며, 0부터 65535 사이의 값을 가질 수 있습니다.

PIT의 출력은 3개의 Channel로 구성되며, 이중 Channel 0는 IRQ0에 연결됩니다. PIT chip의 command 레지스터는 0x43 I/O port이며, Channel 0의 data  I/O port는 0x40이며, frequency divider를 넘겨줍니다. 0x43 port에는 다음 데이터를 통해 명령을 줄 수 있습니다.

Bits         Usage
6 and 7      Select channel :
                0 0 = Channel 0
                0 1 = Channel 1
                1 0 = Channel 2
                1 1 = Read-back command (8254 only)
4 and 5      Access mode :
                0 0 = Latch count value command
                0 1 = Access mode: lobyte only
                1 0 = Access mode: hibyte only
                1 1 = Access mode: lobyte/hibyte
1 to 3       Operating mode :
                0 0 0 = Mode 0 (interrupt on terminal count)
                0 0 1 = Mode 1 (hardware re-triggerable one-shot)
                0 1 0 = Mode 2 (rate generator)
                0 1 1 = Mode 3 (square wave generator)
                1 0 0 = Mode 4 (software triggered strobe)
                1 0 1 = Mode 5 (hardware triggered strobe)
                1 1 0 = Mode 2 (rate generator, same as 010b)
                1 1 1 = Mode 3 (square wave generator, same as 011b)
0            BCD/Binary mode: 0 = 16-bit binary, 1 = four-digit BCD

본 강의에서는 00110110b ((Channel0, Access Mode: lo/hi byte, Operating Mode: square wave generator, 16bit-binary) 설정을 사용합니다.

Keyboard Interrupt

키보드의 Interrupt은 IRQ1을 통해 처리합니다. 키보드 입력이 주어지면 PIC는 key가 눌리거나(key down) 땠을 때의(key up) scancode를 0x60 I/O port에 저장합니다. 이때 key up의 scancode는 key down의 scancode + 0x80의 값을 가집니다. 본 강의에서는 입력으로 주어진 scancode를 ASCII code로 변환해 출력하는 것을 구현합니다.


코드

본 강의의 코드는 이론을 그대로 코드를 구현한 것이므로 설명은 주석으로 대체하겠습니다.

cpu/timer.h

#ifndef TIMER_H
#define TIMER_H

#include "../kernel/util.h"

void init_timer(u32 freq);

#endif

cpu/timer.c

#include "timer.h"
#include "../drivers/screen.h"
#include "../kernel/util.h"
#include "isr.h"

u32 tick = 0;

static void timer_callback(registers_t regs) {
    tick++;
    kprint("Tick: ");

    char tick_ascii[256];
    int_to_ascii(tick, tick_ascii);
    kprint(tick_ascii);
    kprint("\n");
}

void init_timer(u32 freq) {
    // Timer는 IRQ0 이용 
    // timer_callback IRQ0에 설치
    register_interrupt_handler(IRQ0, timer_callback);

    // PIT reload value 설정
    // divisor가 0이 되면 reload 됨
    u32 divisor = 1193180 / freq;
    u8 low = (u8)(divisor & 0xFF);
    u8 high = (u8)((divisor >> 8) & 0xFF);

    // 명령 전송
    // 0x43: Command Port
    // 0x36=00110110b: 16bit (Channel0, Access Mode: lo/hi byte
    // , Operating Mode: square wave generator, 16bit-binary)
    port_byte_out(0x43, 0x36);
    // 0x40: Channel 0 data port
    // PIT reload value의 하위 byte
    port_byte_out(0x40, low);
    // PIT reload value의 상위 byte
    port_byte_out(0x40, high);
}

drivers/keyboard.h

#include "../cpu/types.h"

void init_keyboard();

drivers/keyboard.c

#include "keyboard.h"
#include "ports.h"
#include "../cpu/isr.h"
#include "screen.h"

static void keyboard_callback(registers_t regs) {
    // PIC가 scancode를 0x60 port에 저장
    u8 scancode = port_byte_in(0x60);
    char *sc_ascii;
    int_to_ascii(scancode, sc_ascii);
    kprint(sc_ascii);
    kprint(", ");
    // scancode에 해당하는 문자 출력
    print_letter(scancode);
    kprint("\n");
}

void init_keyboard() {
    register_interrupt_handler(IRQ1, keyboard_callback);
}

void print_letter(u8 scancode) {
    switch(scancode) {
        case 0x0:
            kprint("ERROR");
            break;
        case 0x1:
            kprint("ESC");
            break;
        case 0x2:
            kprint("1");
            break;
        case 0x3:
            kprint("2");
            break;
        case 0x4:
            kprint("3");
            break;
        case 0x5:
            kprint("4");
            break;
        case 0x6:
            kprint("5");
            break;
        case 0x7:
            kprint("6");
            break;
        case 0x8:
            kprint("7");
            break;
        case 0x9:
            kprint("8");
            break;
        case 0x0A:
            kprint("9");
            break;
        case 0x0B:
            kprint("0");
            break;
        case 0x0C:
            kprint("-");
            break;
        case 0x0D:
            kprint("+");
            break;
        case 0x0E:
            kprint("Backspace");
            break;
        case 0x0F:
            kprint("Tab");
            break;
        case 0x10:
            kprint("Q");
            break;
        case 0x11:
            kprint("W");
            break;
        case 0x12:
            kprint("E");
            break;
        case 0x13:
            kprint("R");
            break;
        case 0x14:
            kprint("T");
            break;
        case 0x15:
            kprint("Y");
            break;
        case 0x16:
            kprint("U");
            break;
        case 0x17:
            kprint("I");
            break;
        case 0x18:
            kprint("O");
            break;
        case 0x19:
            kprint("P");
            break;
		case 0x1A:
			kprint("[");
			break;
		case 0x1B:
			kprint("]");
			break;
		case 0x1C:
			kprint("ENTER");
			break;
		case 0x1D:
			kprint("LCtrl");
			break;
		case 0x1E:
			kprint("A");
			break;
		case 0x1F:
			kprint("S");
			break;
        case 0x20:
            kprint("D");
            break;
        case 0x21:
            kprint("F");
            break;
        case 0x22:
            kprint("G");
            break;
        case 0x23:
            kprint("H");
            break;
        case 0x24:
            kprint("J");
            break;
        case 0x25:
            kprint("K");
            break;
        case 0x26:
            kprint("L");
            break;
        case 0x27:
            kprint(";");
            break;
        case 0x28:
            kprint("'");
            break;
        case 0x29:
            kprint("`");
            break;
		case 0x2A:
			kprint("LShift");
			break;
		case 0x2B:
			kprint("\\");
			break;
		case 0x2C:
			kprint("Z");
			break;
		case 0x2D:
			kprint("X");
			break;
		case 0x2E:
			kprint("C");
			break;
		case 0x2F:
			kprint("V");
			break;
        case 0x30:
            kprint("B");
            break;
        case 0x31:
            kprint("N");
            break;
        case 0x32:
            kprint("M");
            break;
        case 0x33:
            kprint(",");
            break;
        case 0x34:
            kprint(".");
            break;
        case 0x35:
            kprint("/");
            break;
        case 0x36:
            kprint("Rshift");
            break;
        case 0x37:
            kprint("Keypad *");
            break;
        case 0x38:
            kprint("LAlt");
            break;
        case 0x39:
            kprint("Spc");
            break;
        default:
            // Keyup은 Keydown+0x80
            if(scancode<=0x7f) {
                kprint("Unknown key down");
            } else if(0x80<=scancode && scancode<=0x39+0x80) {
                kprint("key up ");
                print_letter(scancode-0x80);
            } else {
                kprint("Unknown key up");
            }
            break;
    }
}

kernels/kernel.c

#include "../drivers/screen.h"
#include "../drivers/keyboard.h"
#include "../cpu/isr.h"
#include "../cpu/timer.h"
#include "util.h"

void main() {
    kprint("\nKernel Start\n");

    isr_install();
    
    asm volatile("sti");
    // Timer
    init_timer(50);
    // Keyboard
    init_keyboard();
}

실행 결과

Timer

CPU timer를 테스트한 결과 Tick이 계속해서 발생하는 것을 확인할 수 있습니다.

CPU Timer 결과

Keyboard

키보드 입력을 테스트한 결과 입력한 키가 출력되는 것을 볼 수 있습니다.

키보드 입력 결과

댓글수0