[OS] 20. Interrupt-timer
서론
이번 강의에서는 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이 계속해서 발생하는 것을 확인할 수 있습니다.

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