In the last tutorial, we discussed how to use exception handlers, in this tutorial, we are going to let the system timer work and give Skelix the ability of getting input from keyboard by installing two interrupt handlers in Skelix.
The programmable interval timer (PIT) that is usually a 8253/54 chip on your mother board which triggers an interrupt at a specified interval, we are going to use it for preemptive multitasking in later tutorials.
8253 has 3 independent counters controlled by ports 0x40~0x42. Each counter has a 16-bit COUNT register. Each counter may count in six different modes and also can be counted in BCD or binary.
The CONTROL register can be accessed via port 0x43. The format of control byte,
| Bits 7,6 | which counter we are going to select, 8253 has only 3 counters, so actually 11b only works in 8254 |
| Bits 5,4 | 11b means access selected counter in the order of LSB first, then MSB |
| Bits 3-1 | Count mode selection for countdown |
| Bit 0 | Count in 16-bit binary(=0) or 4 decimal BCD(=1) |
Based on Intel's Manual, "After power-up, the state of the 8254 is undefined. The Mode, count value, and output of all Counters are undefined. How each Counter operates is determined when it is programmed. Each Counter must be programmed before it can be used."
05/timer/timer.c
void timer_install(int hz) {
This function takes the frequency of ticking to initialize system timer.
unsigned int divisor = 1193180/hz;
outb(0x36, 0x43);
outb(divisor&0xff, 0x40);
outb(divisor>>8, 0x40);
We are going to uses counter 0, writes lower 8-bit first then higher 8-bit in binary mode. 1193180 is a fixed number for all timer counters which used as clock frequency input.
outb(inb(0x21)&0xfe, 0x21);
Clear the mask in PIC1 by clearing the bit 0 in port 0x21.
}
volatile unsigned int timer_ticks = 0;
do_timer(void) {void
int x , y;
++timer_ticks;
get_cursor(&x, &y);
set_cursor(71, 24);
kprintf(KPL_DUMP, "%x", timer_ticks);
set_cursor(x, y);
Print the how many times the timer has been ticking at the right bottom of the screen.
outb(0x20, 0x20);
}
Tells PIC1 this interrupt routine has finished, it can handle new incoming interrupts. Because the timer is connected to PIC1, so there is no need to use oub(0x20, 0xa0) to inform PIC2.
We have to modify some code in several existing files.
05/timer/include/isr.h
#define VALID_ISR (32+1)
We have one more valid ISR in table.
05/timer/isr.s
isr: .long divide_error, isr0x00, debug_exception, isr0x01
.long breakpoint, isr0x02, nmi, isr0x03
.long overflow, isr0x04, bounds_check, isr0x05
.long invalid_opcode, isr0x06, cop_not_avalid, isr0x07
.long double_fault, isr0x08, overrun, isr0x09
.long invalid_tss, isr0x0a, seg_not_present, isr0x0b
.long stack_exception, isr0x0c, general_protection, isr0x0d
.long page_fault, isr0x0e, reversed, isr0x0f
.long coprocessor_error, isr0x10, reversed, isr0x11
.long reversed, isr0x12, reversed, isr0x13
.long reversed, isr0x14, reversed, isr0x15
.long reversed, isr0x16, reversed, isr0x17
.long reversed, isr0x18, reversed, isr0x19
.long reversed, isr0x1a, reversed, isr0x1b
.long reversed, isr0x1c, reversed, isr0x1d
.long reversed, isr0x1e, reversed, isr0x1f
.long do_timer, isr0x20 # <<< HERE IT IS
In the same file,
isrNoError 0x16
isrNoError 0x17
isrNoError 0x18
isrNoError 0x19
isrNoError 0x1a
isrNoError 0x1b
isrNoError 0x1c
isrNoError 0x1d
isrNoError 0x1e
isrNoError 0x1f
isrNoError 0x20 # <<< HERE IT IS
Before finishing this, just let Skelix do something less boring, let's make it print a "wheel" at the left bottom of the screen.
05/timer/init.c
void
init(void) {
char wheel[] = {'\\', '|', '/', '-'};
int i = 0;
idt_install();
pic_install();
timer_install(100);
We let the timer interrupt 100 timers per second.
sti();
Don't forget to enable interrupts.
for (;;) {
__asm__ ("movb %%al, 0xb8000+160*24"::"a"(wheel[i]));
if (i == sizeof wheel)
i = 0;
else
++i;
}
}
Let's change our Makefile by adding new modules into KERNEL_OBJS.
05/timer/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kprintf.o exceptions.o
Timer is ticking an wheel is rolling.
Well, we have the ability of displaying some stuff on the screen in earlier tutorial, but what can we display? em......, at this moment nothing actually, so we are going to make Skelix accept input from keyboard.
Once a key is pressed, a 8-bit scan code will be send to your computer. For example, once 'a' is pressed, the scan code 0x1E (actually this value depends on the layout of your keyboard) will be send, when the key was released, the first bit of that scan code will be set, like 0x1E | 0x10 = 0x9E will be send in this case. For some special keys like Break, Home etc. will not be handled in this tutorial. So that is all you need to know to finish our code in this tutorial.
This is the keyboard map that I am using, if you are using some other layouts, you might need to get it somewhere else.
| Scan code | Key | Scan Code | Key | Scan Code | Key | Scan Code | Key | Scan Code | Key | Scan Code | Key |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 01 | ESC | 02 | 1! | 03 | 2@ | 04 | 3# | 05 | 4$ | 06 | 5% |
| 07 | 6^ | 08 | 7& | 09 | 8* | 0A | 9( | 0B | 0) | 0C | -_ |
| 0D | =+ | 0E | <-- | 0F | TAB | 10 | 11 | wW | 12 | eE | |
| 13 | rR | 14 | tT | 15 | yY | 16 | uU | 17 | iI | 18 | oO |
| 19 | pP | 1A | [{ | 1B | ]} | 1C | Enter | 1D | LCTL | 1E | aA |
| 1F | sS | 20 | dD | 21 | fF | 22 | gG | 23 | hH | 24 | jJ |
| 25 | kK | 26 | lL | 27 | ;: | 28 | '" | 29 | `~ | 2A | LSHT |
| 2B | \| | 2C | zZ | 2D | xX | 2E | cC | 2F | vV | 30 | bB |
| 31 | nN | 32 | mM | 33 | ,< | 34 | .> | 35 | /? | 36 | RSHT |
| 37 | ** | 38 | LALT | 39 | SPACE | 3B-44 | F1-F10 | 57 | F11 | 58 | F12 |
As we can see, Ctrl, Shift and Alt are all send as normal scan code, so we can recode these keys' state to ensure we display correct characters on screen.
As usual, we access keyboard controller via ports.
void
do_kb(void) {
int com;
(*key_way[0x80])(void) = {void
/*00*/unp, unp, pln, pln, pln, pln, pln, pln,
/*08*/pln, pln, pln, pln, pln, pln, pln, pln,
/*10*/pln, pln, pln, pln, pln, pln, pln, pln,
/*18*/pln, pln, pln, pln, pln, ctl, pln, pln,
/*20*/pln, pln, pln, pln, pln, pln, pln, pln,
/*28*/pln, pln, shf, pln, pln, pln, pln, pln,
/*30*/pln, pln, pln, pln, pln, pln, shf, pln,
/*38*/alt, pln, unp, fun, fun, fun, fun, fun,
/*40*/fun, fun, fun, fun, fun, unp, unp, unp,
/*48*/unp, unp, unp, unp, unp, unp, unp, unp,
/*50*/unp, unp, unp, unp, unp, unp, unp, fun,
/*58*/fun, unp, unp, unp, unp, unp, unp, unp,
/*60*/unp, unp, unp, unp, unp, unp, unp, unp,
/*68*/unp, unp, unp, unp, unp, unp, unp, unp,
/*70*/unp, unp, unp, unp, unp, unp, unp, unp,
/*78*/unp, unp, unp, unp, unp, unp, unp, unp,
};
This is an array of function pointers, when a scan code comes in, we use this map to tell what kind of key stroke we are processing, unp for unhandled keys,
pln for printable characters, ctl for Ctrl keys, shf for Shift keys, alt for Alt keys,
fun for function keys F1-F12.
com = 0;
scan_code = inb(0x60);
Reads scan code from 8042 output register 0x60.
(*key_way[scan_code&0x7f])();
0x7F is used to mask the scan code for telling this event is happening on which key, whether it is pressed or released, because in both situations the low 7-bit are the same.
Then execute the function located in array key_way.
/* key stroke has been handled */
outb((com=inb(0x61))|0x80, 0x61);
outb(com&0x7f, 0x61);
Actually after we read port 0x60 to get the scan code, the scan code will not be removed automatically, so we can read port 60 to get this scan code as many times as we want, but this feature also stop us from reading further key strokes, so we have to tell the keyboard controller this key event has been handled. To achieve it, we have to disable and re-enable it via the bit 7 of port 0x61. You may check the details of keyboard controller at here.
outb(0x20, 0x20);
}
void
kb_install(void) {
outb(inb(0x21)&0xfd, 0x21);
}
static unsigned char shf_p = 0;
static unsigned char ctl_p = 0;
static unsigned char alt_p = 0;
static unsigned char scan_code;
Stores current scan code and states of Ctrl, Shift and Alt keys.
/* printable char */
static void
pln(void) {
This function is used to display printable characters.
static const char key_map[0x3a][2] = {
/*00*/{0x0, 0x0}, {0x0, 0x0}, {'1', '!'}, {'2', '@'},
/*04*/{'3', '#'}, {'4', '$'}, {'5', '%'}, {'6', '^'},
/*08*/{'7', '&'}, {'8', '*'}, {'9', '('}, {'0', ')'},
/*0c*/{'-', '_'}, {'=', '+'}, {'\b','\b'},{'\t','\t'},
/*10*/{'q', 'Q'}, {'w', 'W'}, {'e', 'E'}, {'r', 'R'},
/*14*/{'t', 'T'}, {'y', 'Y'}, {'u', 'U'}, {'i', 'I'},
/*18*/{'o', 'O'}, {'p', 'P'}, {'[', '{'}, {']', '}'},
/*1c*/{'\n','\n'},{0x0, 0x0}, {'a', 'A'}, {'s', 'S'},
/*20*/{'d', 'D'}, {'f', 'F'}, {'g', 'G'}, {'h', 'H'},
/*24*/{'j', 'J'}, {'k', 'K'}, {'l', 'L'}, {';', ':'},
/*28*/{'\'','\"'},{'`', '~'}, {0x0, 0x0}, {'\\','|'},
/*2c*/{'z', 'Z'}, {'x', 'X'}, {'c', 'C'}, {'v', 'V'},
/*30*/{'b', 'B'}, {'n', 'N'}, {'m', 'M'}, {',', '<'},
/*34*/{'.', '>'}, {'/', '?'}, {0x0, 0x0}, {'*', '*'},
/*38*/{0x0, 0x0}, {' ', ' '} };
Defines printable letter table according to scan code map. key_map[?][0] is lowercase letter for Shift key not pressed,
and key_map[?][1] is for when Shift key has been pressed.
if (scan_code & 0x80)
return;
print_c(key_map[scan_code&0x7f][shf_p], WHITE, BLACK);
If the keys has been release, does nothing. Otherwise print the character on screen with black background and white foreground.
}
/* Ctrl */
static void
ctl(void) {
ctl_p ^= 0x1;
}
/* Alt */
static void
alt(void) {
alt_p ^= 0x1;
}
/* Shift */
static void
shf(void) {
shf_p ^= 0x1;
}
/* F1, F2 ~ F12 */
static void
fun(void) {
}
/* not implementated */
static void
unp(void) {
}
ctl, alt and shf functions just set the key state. Actually in this tutorial we just handle Shift key to output capital letters.
We are almost there, add this interrupt entry in isr table,
05/keyboard/isr.s
.long do_timer, isr0x20, do_kb, isr0x21 #<<<=== over here
In the same file just like timer ISR,
isrNoError 0x20
isrNoError 0x21 #<<<=== over here
Changes the constant value of valid ISRs,
05/keyboard/include/isr.h
#define VALID_ISR (32+2)
Finally, add kb_install to 05/keyboard/init.c.
timer_install(100);
kb_install(); /* here it is */
sti();
We use the same Makefile as the last one, just add the new modules to KERNEL_OBJS,
05/keyboard/Makefile
KERNEL_OBJS= load.o init.o isr.o timer.o libcc.o scr.o kprintf.o exceptions.o kb.o
Enjoy you typing...
Feel free to use my code. Please contact me if you have any questions.