Explorar el Código

Lesson 18 - the IDT

Carlos Fenollosa hace 10 años
padre
commit
69a7c1c2a6

+ 46 - 0
18-interrupts/Makefile

@@ -0,0 +1,46 @@
+C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c)
+HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h)
+# Nice syntax for file extension replacement
+OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o} 
+
+# Change this if your cross-compiler is somewhere else
+CC = /usr/local/i386elfgcc/bin/i386-elf-gcc
+GDB = /usr/local/i386elfgcc/bin/i386-elf-gdb
+# -g: Use debugging symbols in gcc
+CFLAGS = -g
+
+# First rule is run by default
+os-image.bin: boot/bootsect.bin kernel.bin
+	cat $^ > os-image.bin
+
+# '--oformat binary' deletes all symbols as a collateral, so we don't need
+# to 'strip' them manually on this case
+kernel.bin: boot/kernel_entry.o ${OBJ}
+	i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
+
+# Used for debugging purposes
+kernel.elf: boot/kernel_entry.o ${OBJ}
+	i386-elf-ld -o $@ -Ttext 0x1000 $^ 
+
+run: os-image.bin
+	qemu-system-i386 -fda os-image.bin
+
+# Open the connection to qemu and load our kernel-object file with symbols
+debug: os-image.bin kernel.elf
+	qemu-system-i386 -s -fda os-image.bin -d guest_errors,int &
+	${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf"
+
+# Generic rules for wildcards
+# To make an object, always compile from its .c
+%.o: %.c ${HEADERS}
+	${CC} ${CFLAGS} -ffreestanding -c $< -o $@
+
+%.o: %.asm
+	nasm $< -f elf -o $@
+
+%.bin: %.asm
+	nasm $< -f bin -o $@
+
+clean:
+	rm -rf *.bin *.dis *.o os-image.bin *.elf
+	rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o

+ 80 - 0
18-interrupts/README.md

@@ -0,0 +1,80 @@
+*Concepts you may want to Google beforehand: C types and structs, include guards, type attributes: packed, extern, volatile, exceptions*
+
+**Goal: Set up the Interrupt Descriptor Table to handle CPU interrupts**
+
+This lesson and the following ones have been heavily inspired
+by [JamesM's tutorial](http://www.jamesmolloy.co.uk/tutorial_html/index.html)
+
+Data types
+----------
+
+First, we will define some special data types in `cpu/types.h`,
+which will help us uncouple data structures for raw bytes from chars and ints.
+It has been carefully placed on the `cpu/` folder, where we will
+put machine-dependent code from now on. Yes, the boot code
+is specifically x86 and is still on `boot/`, but let's leave
+that alone for now.
+
+Some of the already existing files have been changed to use
+the new `u8`, `u16` and `u32` data types.
+
+From now on, our C header files will also have include guards.
+
+
+Interrupts
+----------
+
+Interrupts are one of the main things that a kernel needs to 
+handle. We will implement it now, as soon as possible, to be able
+to receive keyboard input in future lessons.
+
+Another examples of interrupts are: divisions by zero, out of bounds,
+invalid opcodes, page faults, etc.
+
+Interrupts are handled on a vector, with entries which are
+similar to those of the GDT (lesson 9). However, instead of
+programming the IDT in assembly, we'll do it in C.
+
+`cpu/idt.h` defines how an idt entry is stored `idt_gate` (there need to be
+256 of them, even if null, or the CPU may panic) and the actual
+idt structure that the BIOS will load, `idt_register` which is 
+just a memory address and a size, similar to the GDT register.
+
+Finally, we define a couple variables to access those data structures
+from assembler code.
+
+`cpu/idt.c` just fills in every struct with a handler. 
+As you can see, it is a matter
+of setting the struct values and calling the `lidt` assembler command.
+
+
+ISRs
+----
+
+The Interrupt Service Routines run every time the CPU detects an 
+interrupt, which is usually fatal. 
+
+We will write just enough code to handle them, print an error message,
+and halt the CPU.
+
+On `cpu/isr.h` we define 32 of them, manually. They are declared as
+`extern` because they will be implemented in assembler, in `cpu/interrupt.asm`
+
+Before jumping to the assembler code, check out `cpu/isr.c`. As you can see,
+we define a function to install all isrs at once and load the IDT, a list of error
+messages, and the high level handler, which kprints some information. You
+can customize `isr_handler` to print/do whatever you want.
+
+Now to the low level which glues every `idt_gate` with its low-level and
+high-level handler. Open `cpu/interrupt.asm`. Here we define a common
+low level ISR code, which basically saves/restores the state and calls
+the C code, and then the actual ISR assembler functions which are referenced
+on `cpu/isr.h`
+
+Note how the `registers_t` struct is a representation of all the registers
+we pushed in `interrupt.asm`
+
+That's basically it. Now we need to reference `cpu/interrupt.asm` from our
+Makefile, and make the kernel install the ISRs and launch one of them.
+Notice how the CPU doesn't halt even though it would be good practice
+to do it after some interrupts.

+ 26 - 0
18-interrupts/boot/32bit_print.asm

@@ -0,0 +1,26 @@
+[bits 32] ; using 32-bit protected mode
+
+; this is how constants are defined
+VIDEO_MEMORY equ 0xb8000
+WHITE_OB_BLACK equ 0x0f ; the color byte for each character
+
+print_string_pm:
+    pusha
+    mov edx, VIDEO_MEMORY
+
+print_string_pm_loop:
+    mov al, [ebx] ; [ebx] is the address of our character
+    mov ah, WHITE_OB_BLACK
+
+    cmp al, 0 ; check if end of string
+    je print_string_pm_done
+
+    mov [edx], ax ; store character + attribute in video memory
+    add ebx, 1 ; next char
+    add edx, 2 ; next video memory position
+
+    jmp print_string_pm_loop
+
+print_string_pm_done:
+    popa
+    ret

+ 52 - 0
18-interrupts/boot/bootsect.asm

@@ -0,0 +1,52 @@
+; Identical to lesson 13's boot sector, but the %included files have new paths
+[org 0x7c00]
+KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel
+
+    mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot
+    mov bp, 0x9000
+    mov sp, bp
+
+    mov bx, MSG_REAL_MODE 
+    call print
+    call print_nl
+
+    call load_kernel ; read the kernel from disk
+    call switch_to_pm ; disable interrupts, load GDT,  etc. Finally jumps to 'BEGIN_PM'
+    jmp $ ; Never executed
+
+%include "boot/print.asm"
+%include "boot/print_hex.asm"
+%include "boot/disk.asm"
+%include "boot/gdt.asm"
+%include "boot/32bit_print.asm"
+%include "boot/switch_pm.asm"
+
+[bits 16]
+load_kernel:
+    mov bx, MSG_LOAD_KERNEL
+    call print
+    call print_nl
+
+    mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000
+    mov dh, 31 ; Our future kernel will be larger, make this big
+    mov dl, [BOOT_DRIVE]
+    call disk_load
+    ret
+
+[bits 32]
+BEGIN_PM:
+    mov ebx, MSG_PROT_MODE
+    call print_string_pm
+    call KERNEL_OFFSET ; Give control to the kernel
+    jmp $ ; Stay here when the kernel returns control to us (if ever)
+
+
+BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten
+MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
+MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0
+MSG_LOAD_KERNEL db "Loading kernel into memory", 0
+MSG_RETURNED_KERNEL db "Returned from kernel. Error?", 0
+
+; padding
+times 510 - ($-$$) db 0
+dw 0xaa55

+ 46 - 0
18-interrupts/boot/disk.asm

@@ -0,0 +1,46 @@
+; load 'dh' sectors from drive 'dl' into ES:BX
+disk_load:
+    pusha
+    ; reading from disk requires setting specific values in all registers
+    ; so we will overwrite our input parameters from 'dx'. Let's save it
+    ; to the stack for later use.
+    push dx
+
+    mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read'
+    mov al, dh   ; al <- number of sectors to read (0x01 .. 0x80)
+    mov cl, 0x02 ; cl <- sector (0x01 .. 0x11)
+                 ; 0x01 is our boot sector, 0x02 is the first 'available' sector
+    mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl')
+    ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS
+    ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2)
+    mov dh, 0x00 ; dh <- head number (0x0 .. 0xF)
+
+    ; [es:bx] <- pointer to buffer where the data will be stored
+    ; caller sets it up for us, and it is actually the standard location for int 13h
+    int 0x13      ; BIOS interrupt
+    jc disk_error ; if error (stored in the carry bit)
+
+    pop dx
+    cmp al, dh    ; BIOS also sets 'al' to the # of sectors read. Compare it.
+    jne sectors_error
+    popa
+    ret
+
+
+disk_error:
+    mov bx, DISK_ERROR
+    call print
+    call print_nl
+    mov dh, ah ; ah = error code, dl = disk drive that dropped the error
+    call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html
+    jmp disk_loop
+
+sectors_error:
+    mov bx, SECTORS_ERROR
+    call print
+
+disk_loop:
+    jmp $
+
+DISK_ERROR: db "Disk read error", 0
+SECTORS_ERROR: db "Incorrect number of sectors read", 0

+ 35 - 0
18-interrupts/boot/gdt.asm

@@ -0,0 +1,35 @@
+gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps
+    ; the GDT starts with a null 8-byte
+    dd 0x0 ; 4 byte
+    dd 0x0 ; 4 byte
+
+; GDT for code segment. base = 0x00000000, length = 0xfffff
+; for flags, refer to os-dev.pdf document, page 36
+gdt_code: 
+    dw 0xffff    ; segment length, bits 0-15
+    dw 0x0       ; segment base, bits 0-15
+    db 0x0       ; segment base, bits 16-23
+    db 10011010b ; flags (8 bits)
+    db 11001111b ; flags (4 bits) + segment length, bits 16-19
+    db 0x0       ; segment base, bits 24-31
+
+; GDT for data segment. base and length identical to code segment
+; some flags changed, again, refer to os-dev.pdf
+gdt_data:
+    dw 0xffff
+    dw 0x0
+    db 0x0
+    db 10010010b
+    db 11001111b
+    db 0x0
+
+gdt_end:
+
+; GDT descriptor
+gdt_descriptor:
+    dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
+    dd gdt_start ; address (32 bit)
+
+; define some constants for later use
+CODE_SEG equ gdt_code - gdt_start
+DATA_SEG equ gdt_data - gdt_start

+ 4 - 0
18-interrupts/boot/kernel_entry.asm

@@ -0,0 +1,4 @@
+[bits 32]
+[extern main] ; Define calling point. Must have same name as kernel.c 'main' function
+call main ; Calls the C function. The linker will know where it is placed in memory
+jmp $

+ 37 - 0
18-interrupts/boot/print.asm

@@ -0,0 +1,37 @@
+print:
+    pusha
+
+; keep this in mind:
+; while (string[i] != 0) { print string[i]; i++ }
+
+; the comparison for string end (null byte)
+start:
+    mov al, [bx] ; 'bx' is the base address for the string
+    cmp al, 0 
+    je done
+
+    ; the part where we print with the BIOS help
+    mov ah, 0x0e
+    int 0x10 ; 'al' already contains the char
+
+    ; increment pointer and do next loop
+    add bx, 1
+    jmp start
+
+done:
+    popa
+    ret
+
+
+
+print_nl:
+    pusha
+    
+    mov ah, 0x0e
+    mov al, 0x0a ; newline char
+    int 0x10
+    mov al, 0x0d ; carriage return
+    int 0x10
+    
+    popa
+    ret

+ 46 - 0
18-interrupts/boot/print_hex.asm

@@ -0,0 +1,46 @@
+; receiving the data in 'dx'
+; For the examples we'll assume that we're called with dx=0x1234
+print_hex:
+    pusha
+
+    mov cx, 0 ; our index variable
+
+; Strategy: get the last char of 'dx', then convert to ASCII
+; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N.
+; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40
+; Then, move the ASCII byte to the correct position on the resulting string
+hex_loop:
+    cmp cx, 4 ; loop 4 times
+    je end
+    
+    ; 1. convert last char of 'dx' to ascii
+    mov ax, dx ; we will use 'ax' as our working register
+    and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros
+    add al, 0x30 ; add 0x30 to N to convert it to ASCII "N"
+    cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F'
+    jle step2
+    add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7
+
+step2:
+    ; 2. get the correct position of the string to place our ASCII char
+    ; bx <- base address + string length - index of char
+    mov bx, HEX_OUT + 5 ; base + length
+    sub bx, cx  ; our index variable
+    mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx'
+    ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234
+
+    ; increment index and loop
+    add cx, 1
+    jmp hex_loop
+
+end:
+    ; prepare the parameter and call the function
+    ; remember that print receives parameters in 'bx'
+    mov bx, HEX_OUT
+    call print
+
+    popa
+    ret
+
+HEX_OUT:
+    db '0x0000',0 ; reserve memory for our new string

+ 22 - 0
18-interrupts/boot/switch_pm.asm

@@ -0,0 +1,22 @@
+[bits 16]
+switch_to_pm:
+    cli ; 1. disable interrupts
+    lgdt [gdt_descriptor] ; 2. load the GDT descriptor
+    mov eax, cr0
+    or eax, 0x1 ; 3. set 32-bit mode bit in cr0
+    mov cr0, eax
+    jmp CODE_SEG:init_pm ; 4. far jump by using a different segment
+
+[bits 32]
+init_pm: ; we are now using 32-bit instructions
+    mov ax, DATA_SEG ; 5. update the segment registers
+    mov ds, ax
+    mov ss, ax
+    mov es, ax
+    mov fs, ax
+    mov gs, ax
+
+    mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
+    mov esp, ebp
+
+    call BEGIN_PM ; 7. Call a well-known label with useful code

+ 17 - 0
18-interrupts/cpu/idt.c

@@ -0,0 +1,17 @@
+#include "idt.h"
+#include "../kernel/util.h"
+
+void set_idt_gate(int n, u32 handler) {
+    idt[n].low_offset = low_16(handler);
+    idt[n].sel = KERNEL_CS;
+    idt[n].always0 = 0;
+    idt[n].flags = 0x8E; 
+    idt[n].high_offset = high_16(handler);
+}
+
+void set_idt() {
+    idt_reg.base = (u32) &idt;
+    idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1;
+    /* Don't make the mistake of loading &idt -- always load &idt_reg */
+    __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg));
+}

+ 39 - 0
18-interrupts/cpu/idt.h

@@ -0,0 +1,39 @@
+#ifndef IDT_H
+#define IDT_H
+
+#include "types.h"
+
+/* Segment selectors */
+#define KERNEL_CS 0x08
+
+/* How every interrupt gate (handler) is defined */
+typedef struct {
+    u16 low_offset; /* Lower 16 bits of handler function address */
+    u16 sel; /* Kernel segment selector */
+    u8 always0;
+    /* First byte
+     * Bit 7: "Interrupt is present"
+     * Bits 6-5: Privilege level of caller (0=kernel..3=user)
+     * Bit 4: Set to 0 for interrupt gates
+     * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */
+    u8 flags; 
+    u16 high_offset; /* Higher 16 bits of handler function address */
+} __attribute__((packed)) idt_gate_t ;
+
+/* A pointer to the array of interrupt handlers.
+ * Assembly instruction 'lidt' will read it */
+typedef struct {
+    u16 limit;
+    u32 base;
+} __attribute__((packed)) idt_register_t;
+
+#define IDT_ENTRIES 256
+idt_gate_t idt[IDT_ENTRIES];
+idt_register_t idt_reg;
+
+
+/* Functions implemented in idt.c */
+void set_idt_gate(int n, u32 handler);
+void set_idt();
+
+#endif

+ 289 - 0
18-interrupts/cpu/interrupt.asm

@@ -0,0 +1,289 @@
+; Defined in isr.c
+[extern isr_handler]
+
+; Common ISR code
+isr_common_stub:
+    ; 1. Save CPU state
+	pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax
+	mov ax, ds ; Lower 16-bits of eax = ds.
+	push eax ; save the data segment descriptor
+	mov ax, 0x10  ; kernel data segment descriptor
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+	
+    ; 2. Call C handler
+	call isr_handler
+	
+    ; 3. Restore state
+	pop eax 
+	mov ds, ax
+	mov es, ax
+	mov fs, ax
+	mov gs, ax
+	popa
+	add esp, 8 ; Cleans up the pushed error code and pushed ISR number
+	sti
+	iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP
+	
+; We don't get information about which interrupt was caller
+; when the handler is run, so we will need to have a different handler
+; for every interrupt.
+; Furthermore, some interrupts push an error code onto the stack but others
+; don't, so we will push a dummy error code for those which don't, so that
+; we have a consistent stack for all of them.
+
+; First make the ISRs global
+global isr0
+global isr1
+global isr2
+global isr3
+global isr4
+global isr5
+global isr6
+global isr7
+global isr8
+global isr9
+global isr10
+global isr11
+global isr12
+global isr13
+global isr14
+global isr15
+global isr16
+global isr17
+global isr18
+global isr19
+global isr20
+global isr21
+global isr22
+global isr23
+global isr24
+global isr25
+global isr26
+global isr27
+global isr28
+global isr29
+global isr30
+global isr31
+
+; 0: Divide By Zero Exception
+isr0:
+    cli
+    push byte 0
+    push byte 0
+    jmp isr_common_stub
+
+; 1: Debug Exception
+isr1:
+    cli
+    push byte 0
+    push byte 1
+    jmp isr_common_stub
+
+; 2: Non Maskable Interrupt Exception
+isr2:
+    cli
+    push byte 0
+    push byte 2
+    jmp isr_common_stub
+
+; 3: Int 3 Exception
+isr3:
+    cli
+    push byte 0
+    push byte 3
+    jmp isr_common_stub
+
+; 4: INTO Exception
+isr4:
+    cli
+    push byte 0
+    push byte 4
+    jmp isr_common_stub
+
+; 5: Out of Bounds Exception
+isr5:
+    cli
+    push byte 0
+    push byte 5
+    jmp isr_common_stub
+
+; 6: Invalid Opcode Exception
+isr6:
+    cli
+    push byte 0
+    push byte 6
+    jmp isr_common_stub
+
+; 7: Coprocessor Not Available Exception
+isr7:
+    cli
+    push byte 0
+    push byte 7
+    jmp isr_common_stub
+
+; 8: Double Fault Exception (With Error Code!)
+isr8:
+    cli
+    push byte 8
+    jmp isr_common_stub
+
+; 9: Coprocessor Segment Overrun Exception
+isr9:
+    cli
+    push byte 0
+    push byte 9
+    jmp isr_common_stub
+
+; 10: Bad TSS Exception (With Error Code!)
+isr10:
+    cli
+    push byte 10
+    jmp isr_common_stub
+
+; 11: Segment Not Present Exception (With Error Code!)
+isr11:
+    cli
+    push byte 11
+    jmp isr_common_stub
+
+; 12: Stack Fault Exception (With Error Code!)
+isr12:
+    cli
+    push byte 12
+    jmp isr_common_stub
+
+; 13: General Protection Fault Exception (With Error Code!)
+isr13:
+    cli
+    push byte 13
+    jmp isr_common_stub
+
+; 14: Page Fault Exception (With Error Code!)
+isr14:
+    cli
+    push byte 14
+    jmp isr_common_stub
+
+; 15: Reserved Exception
+isr15:
+    cli
+    push byte 0
+    push byte 15
+    jmp isr_common_stub
+
+; 16: Floating Point Exception
+isr16:
+    cli
+    push byte 0
+    push byte 16
+    jmp isr_common_stub
+
+; 17: Alignment Check Exception
+isr17:
+    cli
+    push byte 0
+    push byte 17
+    jmp isr_common_stub
+
+; 18: Machine Check Exception
+isr18:
+    cli
+    push byte 0
+    push byte 18
+    jmp isr_common_stub
+
+; 19: Reserved
+isr19:
+    cli
+    push byte 0
+    push byte 19
+    jmp isr_common_stub
+
+; 20: Reserved
+isr20:
+    cli
+    push byte 0
+    push byte 20
+    jmp isr_common_stub
+
+; 21: Reserved
+isr21:
+    cli
+    push byte 0
+    push byte 21
+    jmp isr_common_stub
+
+; 22: Reserved
+isr22:
+    cli
+    push byte 0
+    push byte 22
+    jmp isr_common_stub
+
+; 23: Reserved
+isr23:
+    cli
+    push byte 0
+    push byte 23
+    jmp isr_common_stub
+
+; 24: Reserved
+isr24:
+    cli
+    push byte 0
+    push byte 24
+    jmp isr_common_stub
+
+; 25: Reserved
+isr25:
+    cli
+    push byte 0
+    push byte 25
+    jmp isr_common_stub
+
+; 26: Reserved
+isr26:
+    cli
+    push byte 0
+    push byte 26
+    jmp isr_common_stub
+
+; 27: Reserved
+isr27:
+    cli
+    push byte 0
+    push byte 27
+    jmp isr_common_stub
+
+; 28: Reserved
+isr28:
+    cli
+    push byte 0
+    push byte 28
+    jmp isr_common_stub
+
+; 29: Reserved
+isr29:
+    cli
+    push byte 0
+    push byte 29
+    jmp isr_common_stub
+
+; 30: Reserved
+isr30:
+    cli
+    push byte 0
+    push byte 30
+    jmp isr_common_stub
+
+; 31: Reserved
+isr31:
+    cli
+    push byte 0
+    push byte 31
+    jmp isr_common_stub
+
+

+ 92 - 0
18-interrupts/cpu/isr.c

@@ -0,0 +1,92 @@
+#include "isr.h"
+#include "idt.h"
+#include "../drivers/screen.h"
+#include "../kernel/util.h"
+
+/* Can't do this with a loop because we need the address
+ * of the function names */
+void isr_install() {
+    set_idt_gate(0, (u32)isr0);
+    set_idt_gate(1, (u32)isr1);
+    set_idt_gate(2, (u32)isr2);
+    set_idt_gate(3, (u32)isr3);
+    set_idt_gate(4, (u32)isr4);
+    set_idt_gate(5, (u32)isr5);
+    set_idt_gate(6, (u32)isr6);
+    set_idt_gate(7, (u32)isr7);
+    set_idt_gate(8, (u32)isr8);
+    set_idt_gate(9, (u32)isr9);
+    set_idt_gate(10, (u32)isr10);
+    set_idt_gate(11, (u32)isr11);
+    set_idt_gate(12, (u32)isr12);
+    set_idt_gate(13, (u32)isr13);
+    set_idt_gate(14, (u32)isr14);
+    set_idt_gate(15, (u32)isr15);
+    set_idt_gate(16, (u32)isr16);
+    set_idt_gate(17, (u32)isr17);
+    set_idt_gate(18, (u32)isr18);
+    set_idt_gate(19, (u32)isr19);
+    set_idt_gate(20, (u32)isr20);
+    set_idt_gate(21, (u32)isr21);
+    set_idt_gate(22, (u32)isr22);
+    set_idt_gate(23, (u32)isr23);
+    set_idt_gate(24, (u32)isr24);
+    set_idt_gate(25, (u32)isr25);
+    set_idt_gate(26, (u32)isr26);
+    set_idt_gate(27, (u32)isr27);
+    set_idt_gate(28, (u32)isr28);
+    set_idt_gate(29, (u32)isr29);
+    set_idt_gate(30, (u32)isr30);
+    set_idt_gate(31, (u32)isr31);
+
+    set_idt(); // Load with ASM
+}
+
+/* To print the message which defines every exception */
+char *exception_messages[] = {
+    "Division By Zero",
+    "Debug",
+    "Non Maskable Interrupt",
+    "Breakpoint",
+    "Into Detected Overflow",
+    "Out of Bounds",
+    "Invalid Opcode",
+    "No Coprocessor",
+
+    "Double Fault",
+    "Coprocessor Segment Overrun",
+    "Bad TSS",
+    "Segment Not Present",
+    "Stack Fault",
+    "General Protection Fault",
+    "Page Fault",
+    "Unknown Interrupt",
+
+    "Coprocessor Fault",
+    "Alignment Check",
+    "Machine Check",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved",
+    "Reserved"
+};
+
+void isr_handler(registers_t r) {
+    kprint("received interrupt: ");
+    char s[3];
+    int_to_ascii(r.int_no, s);
+    kprint(s);
+    kprint("\n");
+    kprint(exception_messages[r.int_no]);
+    kprint("\n");
+}

+ 51 - 0
18-interrupts/cpu/isr.h

@@ -0,0 +1,51 @@
+#ifndef ISR_H
+#define ISR_H
+
+#include "types.h"
+
+/* ISRs reserved for CPU exceptions */
+extern void isr0();
+extern void isr1();
+extern void isr2();
+extern void isr3();
+extern void isr4();
+extern void isr5();
+extern void isr6();
+extern void isr7();
+extern void isr8();
+extern void isr9();
+extern void isr10();
+extern void isr11();
+extern void isr12();
+extern void isr13();
+extern void isr14();
+extern void isr15();
+extern void isr16();
+extern void isr17();
+extern void isr18();
+extern void isr19();
+extern void isr20();
+extern void isr21();
+extern void isr22();
+extern void isr23();
+extern void isr24();
+extern void isr25();
+extern void isr26();
+extern void isr27();
+extern void isr28();
+extern void isr29();
+extern void isr30();
+extern void isr31();
+
+/* Struct which aggregates many registers */
+typedef struct {
+   u32 ds; /* Data segment selector */
+   u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */
+   u32 int_no, err_code; /* Interrupt number and error code (if applicable) */
+   u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */
+} registers_t;
+
+void isr_install();
+void isr_handler(registers_t r);
+
+#endif

+ 16 - 0
18-interrupts/cpu/types.h

@@ -0,0 +1,16 @@
+#ifndef TYPES_H
+#define TYPES_H
+
+/* Instead of using 'chars' to allocate non-character bytes,
+ * we will use these new type with no semantic meaning */
+typedef unsigned int   u32;
+typedef          int   s32;
+typedef unsigned short u16;
+typedef          short s16;
+typedef unsigned char  u8;
+typedef          char  s8;
+
+#define low_16(address) (u16)((address) & 0xFFFF)
+#define high_16(address) (u16)(((address) >> 16) & 0xFFFF)
+
+#endif

+ 37 - 0
18-interrupts/drivers/ports.c

@@ -0,0 +1,37 @@
+#include "ports.h"
+
+/**
+ * Read a byte from the specified port
+ */
+u8 port_byte_in (u16 port) {
+    u8 result;
+    /* Inline assembler syntax
+     * !! Notice how the source and destination registers are switched from NASM !!
+     *
+     * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x
+     * '"d" (port)': map the C variable '(port)' into e'd'x register
+     *
+     * Inputs and outputs are separated by colons
+     */
+    __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
+    return result;
+}
+
+void port_byte_out (u16 port, u8 data) {
+    /* Notice how here both registers are mapped to C variables and
+     * nothing is returned, thus, no equals '=' in the asm syntax 
+     * However we see a comma since there are two variables in the input area
+     * and none in the 'return' area
+     */
+    __asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port));
+}
+
+u16 port_word_in (u16 port) {
+    u16 result;
+    __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
+    return result;
+}
+
+void port_word_out (u16 port, u16 data) {
+    __asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port));
+}

+ 11 - 0
18-interrupts/drivers/ports.h

@@ -0,0 +1,11 @@
+#ifndef PORTS_H
+#define PORTS_H
+
+#include "../cpu/types.h"
+
+unsigned char port_byte_in (u16 port);
+void port_byte_out (u16 port, u8 data);
+unsigned short port_word_in (u16 port);
+void port_word_out (u16 port, u16 data);
+
+#endif

+ 139 - 0
18-interrupts/drivers/screen.c

@@ -0,0 +1,139 @@
+#include "screen.h"
+#include "ports.h"
+#include "../kernel/util.h"
+
+/* Declaration of private functions */
+int get_cursor_offset();
+void set_cursor_offset(int offset);
+int print_char(char c, int col, int row, char attr);
+int get_offset(int col, int row);
+int get_offset_row(int offset);
+int get_offset_col(int offset);
+
+/**********************************************************
+ * Public Kernel API functions                            *
+ **********************************************************/
+
+/**
+ * Print a message on the specified location
+ * If col, row, are negative, we will use the current offset
+ */
+void kprint_at(char *message, int col, int row) {
+    /* Set cursor if col/row are negative */
+    int offset;
+    if (col >= 0 && row >= 0)
+        offset = get_offset(col, row);
+    else {
+        offset = get_cursor_offset();
+        row = get_offset_row(offset);
+        col = get_offset_col(offset);
+    }
+
+    /* Loop through message and print it */
+    int i = 0;
+    while (message[i] != 0) {
+        offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
+        /* Compute row/col for next iteration */
+        row = get_offset_row(offset);
+        col = get_offset_col(offset);
+    }
+}
+
+void kprint(char *message) {
+    kprint_at(message, -1, -1);
+}
+
+
+/**********************************************************
+ * Private kernel functions                               *
+ **********************************************************/
+
+
+/**
+ * Innermost print function for our kernel, directly accesses the video memory 
+ *
+ * If 'col' and 'row' are negative, we will print at current cursor location
+ * If 'attr' is zero it will use 'white on black' as default
+ * Returns the offset of the next character
+ * Sets the video cursor to the returned offset
+ */
+int print_char(char c, int col, int row, char attr) {
+    unsigned char *vidmem = (unsigned char*) VIDEO_ADDRESS;
+    if (!attr) attr = WHITE_ON_BLACK;
+
+    /* Error control: print a red 'E' if the coords aren't right */
+    if (col >= MAX_COLS || row >= MAX_ROWS) {
+        vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E';
+        vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE;
+        return get_offset(col, row);
+    }
+
+    int offset;
+    if (col >= 0 && row >= 0) offset = get_offset(col, row);
+    else offset = get_cursor_offset();
+
+    if (c == '\n') {
+        row = get_offset_row(offset);
+        offset = get_offset(0, row+1);
+    } else {
+        vidmem[offset] = c;
+        vidmem[offset+1] = attr;
+        offset += 2;
+    }
+
+    /* Check if the offset is over screen size and scroll */
+    if (offset >= MAX_ROWS * MAX_COLS * 2) {
+        int i;
+        for (i = 1; i < MAX_ROWS; i++) 
+            memory_copy(get_offset(0, i) + VIDEO_ADDRESS,
+                        get_offset(0, i-1) + VIDEO_ADDRESS,
+                        MAX_COLS * 2);
+
+        /* Blank last line */
+        char *last_line = get_offset(0, MAX_ROWS-1) + VIDEO_ADDRESS;
+        for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0;
+
+        offset -= 2 * MAX_COLS;
+    }
+
+    set_cursor_offset(offset);
+    return offset;
+}
+
+int get_cursor_offset() {
+    /* Use the VGA ports to get the current cursor position
+     * 1. Ask for high byte of the cursor offset (data 14)
+     * 2. Ask for low byte (data 15)
+     */
+    port_byte_out(REG_SCREEN_CTRL, 14);
+    int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
+    port_byte_out(REG_SCREEN_CTRL, 15);
+    offset += port_byte_in(REG_SCREEN_DATA);
+    return offset * 2; /* Position * size of character cell */
+}
+
+void set_cursor_offset(int offset) {
+    /* Similar to get_cursor_offset, but instead of reading we write data */
+    offset /= 2;
+    port_byte_out(REG_SCREEN_CTRL, 14);
+    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
+    port_byte_out(REG_SCREEN_CTRL, 15);
+    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
+}
+
+void clear_screen() {
+    int screen_size = MAX_COLS * MAX_ROWS;
+    int i;
+    char *screen = VIDEO_ADDRESS;
+
+    for (i = 0; i < screen_size; i++) {
+        screen[i*2] = ' ';
+        screen[i*2+1] = WHITE_ON_BLACK;
+    }
+    set_cursor_offset(get_offset(0, 0));
+}
+
+
+int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); }
+int get_offset_row(int offset) { return offset / (2 * MAX_COLS); }
+int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; }

+ 19 - 0
18-interrupts/drivers/screen.h

@@ -0,0 +1,19 @@
+#ifndef SCREEN_H
+#define SCREEN_H
+
+#define VIDEO_ADDRESS 0xb8000
+#define MAX_ROWS 25
+#define MAX_COLS 80
+#define WHITE_ON_BLACK 0x0f
+#define RED_ON_WHITE 0xf4
+
+/* Screen i/o ports */
+#define REG_SCREEN_CTRL 0x3d4
+#define REG_SCREEN_DATA 0x3d5
+
+/* Public kernel API */
+void clear_screen();
+void kprint_at(char *message, int col, int row);
+void kprint(char *message);
+
+#endif

+ 11 - 0
18-interrupts/kernel/kernel.c

@@ -0,0 +1,11 @@
+#include "../drivers/screen.h"
+#include "util.h"
+#include "../cpu/isr.h"
+#include "../cpu/idt.h"
+
+void main() {
+    isr_install();
+    /* Test the interrupts */
+    __asm__ __volatile__("int $2");
+    __asm__ __volatile__("int $3");
+}

+ 30 - 0
18-interrupts/kernel/util.c

@@ -0,0 +1,30 @@
+#include "util.h"
+
+void memory_copy(char *source, char *dest, int nbytes) {
+    int i;
+    for (i = 0; i < nbytes; i++) {
+        *(dest + i) = *(source + i);
+    }
+}
+
+void memory_set(u8 *dest, u8 val, u32 len) {
+    u8 *temp = (u8 *)dest;
+    for ( ; len != 0; len--) *temp++ = val;
+}
+
+/**
+ * K&R implementation
+ */
+void int_to_ascii(int n, char str[]) {
+    int i, sign;
+    if ((sign = n) < 0) n = -n;
+    i = 0;
+    do {
+        str[i++] = n % 10 + '0';
+    } while ((n /= 10) > 0);
+
+    if (sign < 0) str[i++] = '-';
+    str[i] = '\0';
+
+    /* TODO: implement "reverse" */
+}

+ 5 - 0
18-interrupts/kernel/util.h

@@ -0,0 +1,5 @@
+#include "../cpu/types.h"
+
+void memory_copy(char *source, char *dest, int nbytes);
+void memory_set(u8 *dest, u8 val, u32 len);
+void int_to_ascii(int n, char str[]);