diff --git a/19-interrupts-irqs/README.md b/19-interrupts-irqs/README.md index 4d744e0..e9b67ec 100644 --- a/19-interrupts-irqs/README.md +++ b/19-interrupts-irqs/README.md @@ -2,9 +2,6 @@ **Goal: Finish the interrupts implementation and CPU timer** -Remap the PIC -------------- - When the CPU boots, the PIC maps IRQs 0-7 to INT 0x8-0xF and IRQs 8-15 to INT 0x70-0x77. This conflicts with the ISRs we programmed last lesson. Since we programmed ISRs 0-31, @@ -14,7 +11,8 @@ The PICs are communicated with via I/O ports (see lesson 15). The Master PIC has command 0x20 and data 0x21, while the slave has command 0xA0 and data 0xA1. -The code for remapping the PICs is obfuscated, so check +The code for remapping the PICs is weird and includes +some masks, so check [this article](http://www.osdev.org/wiki/PIC) if you're curious. Otherwise, just look at `cpu/isr.c`, new code after we set the IDT gates for the ISRs. After that, we add the IDT gates for IRQs. @@ -30,5 +28,10 @@ We then create this `irq_common_stub` which is very similar to the ISR one. It is located at the top of `interrupt.asm`, and it also defines a new `[extern irq_handler]` -Now back to C code. -- Write the `irq_handler` in C: +Now back to C code, to write the `irq_handler()` in `isr.c`. It sends some +EOIs to the PICs and calls the appropriate handler, which is stored in an array +named `interrupt_handlers` and defined at the top of the file. The new structs +are defined in `isr.h`. We will also use a simple function to register +the interrupt handlers. + +That was a lot of work, but now we can define our first IRQ handler! diff --git a/20-interrupts-timer/Makefile b/20-interrupts-timer/Makefile new file mode 120000 index 0000000..6e0ac08 --- /dev/null +++ b/20-interrupts-timer/Makefile @@ -0,0 +1 @@ +../19-interrupts-irqs/Makefile \ No newline at end of file diff --git a/20-interrupts-timer/README.md b/20-interrupts-timer/README.md new file mode 100644 index 0000000..8cfa0f8 --- /dev/null +++ b/20-interrupts-timer/README.md @@ -0,0 +1,21 @@ + +Everything is now ready to test our hardware interrupts. + +Timer +----- + +The timer is easy to configure. First we'll declare an `init_timer()` on `cpu/timer.h` and +implement it on `cpu/timer.c`. It is just a matter of computing the clock frequency and +sending the bytes to the appropriate ports. + +## se printa gibberish, pq? mirar primero si se arregla con un kprint_int +## yo tenia una funcion que printaba enteros??!!! pero al rever. hacerla ahora bien y +## en el proximo episodio limpiar codigo y crear una libc + + +Finally, go back to the `kernel/kernel.c` and do two things. Enable interrupts again +(very important!) and then initialize the timer interrupt. + +Go `make run` and you'll see the clock ticking! Unfortunately we are not printing the correct values +on screen, so we'll go ahead to `drivers/screen.c` and add a new `kprint_int()` method, also declaring +it on `drivers/screen.h` diff --git a/20-interrupts-timer/boot b/20-interrupts-timer/boot new file mode 120000 index 0000000..7948077 --- /dev/null +++ b/20-interrupts-timer/boot @@ -0,0 +1 @@ +../19-interrupts-irqs/boot \ No newline at end of file diff --git a/20-interrupts-timer/cpu/idt.c b/20-interrupts-timer/cpu/idt.c new file mode 100644 index 0000000..999d0c9 --- /dev/null +++ b/20-interrupts-timer/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)); +} diff --git a/20-interrupts-timer/cpu/idt.h b/20-interrupts-timer/cpu/idt.h new file mode 100644 index 0000000..27bfac5 --- /dev/null +++ b/20-interrupts-timer/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 diff --git a/20-interrupts-timer/cpu/interrupt.asm b/20-interrupts-timer/cpu/interrupt.asm new file mode 100644 index 0000000..bf83b7a --- /dev/null +++ b/20-interrupts-timer/cpu/interrupt.asm @@ -0,0 +1,425 @@ +; Defined in isr.c +[extern isr_handler] +[extern irq_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 + +; Common IRQ code. Identical to ISR code except for the 'call' +; and the 'pop ebx' +irq_common_stub: + pusha + mov ax, ds + push eax + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + call irq_handler ; Different than the ISR code + pop ebx ; Different than the ISR code + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + popa + add esp, 8 + sti + iret + +; 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 +; IRQs +global irq0 +global irq1 +global irq2 +global irq3 +global irq4 +global irq5 +global irq6 +global irq7 +global irq8 +global irq9 +global irq10 +global irq11 +global irq12 +global irq13 +global irq14 +global irq15 + +; 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 + +; IRQ handlers +irq0: + cli + push byte 0 + push byte 32 + jmp irq_common_stub + +irq1: + cli + push byte 1 + push byte 33 + jmp irq_common_stub + +irq2: + cli + push byte 2 + push byte 34 + jmp irq_common_stub + +irq3: + cli + push byte 3 + push byte 35 + jmp irq_common_stub + +irq4: + cli + push byte 4 + push byte 36 + jmp irq_common_stub + +irq5: + cli + push byte 5 + push byte 37 + jmp irq_common_stub + +irq6: + cli + push byte 6 + push byte 38 + jmp irq_common_stub + +irq7: + cli + push byte 7 + push byte 39 + jmp irq_common_stub + +irq8: + cli + push byte 8 + push byte 40 + jmp irq_common_stub + +irq9: + cli + push byte 9 + push byte 41 + jmp irq_common_stub + +irq10: + cli + push byte 10 + push byte 42 + jmp irq_common_stub + +irq11: + cli + push byte 11 + push byte 43 + jmp irq_common_stub + +irq12: + cli + push byte 12 + push byte 44 + jmp irq_common_stub + +irq13: + cli + push byte 13 + push byte 45 + jmp irq_common_stub + +irq14: + cli + push byte 14 + push byte 46 + jmp irq_common_stub + +irq15: + cli + push byte 15 + push byte 47 + jmp irq_common_stub + diff --git a/20-interrupts-timer/cpu/isr.c b/20-interrupts-timer/cpu/isr.c new file mode 100644 index 0000000..4687c24 --- /dev/null +++ b/20-interrupts-timer/cpu/isr.c @@ -0,0 +1,140 @@ +#include "isr.h" +#include "idt.h" +#include "../drivers/screen.h" +#include "../kernel/util.h" +#include "../drivers/ports.h" + +isr_t interrupt_handlers[256]; + +/* 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); + + // Remap the PIC + port_byte_out(0x20, 0x11); + port_byte_out(0xA0, 0x11); + port_byte_out(0x21, 0x20); + port_byte_out(0xA1, 0x28); + port_byte_out(0x21, 0x04); + port_byte_out(0xA1, 0x02); + port_byte_out(0x21, 0x01); + port_byte_out(0xA1, 0x01); + port_byte_out(0x21, 0x0); + port_byte_out(0xA1, 0x0); + + // Install the IRQs + set_idt_gate(32, (u32)irq0); + set_idt_gate(33, (u32)irq1); + set_idt_gate(34, (u32)irq2); + set_idt_gate(35, (u32)irq3); + set_idt_gate(36, (u32)irq4); + set_idt_gate(37, (u32)irq5); + set_idt_gate(38, (u32)irq6); + set_idt_gate(39, (u32)irq7); + set_idt_gate(40, (u32)irq8); + set_idt_gate(41, (u32)irq9); + set_idt_gate(42, (u32)irq10); + set_idt_gate(43, (u32)irq11); + set_idt_gate(44, (u32)irq12); + set_idt_gate(45, (u32)irq13); + set_idt_gate(46, (u32)irq14); + set_idt_gate(47, (u32)irq15); + + 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"); +} + +void register_interrupt_handler(u8 n, isr_t handler) { + interrupt_handlers[n] = handler; +} + +void irq_handler(registers_t r) { + /* If the irq involves the slave (IRQ > 7), send an EOI to it */ + if (r.int_no >= 40) port_byte_out(0xA0, 0x20); + port_byte_out(0x20, 0x20); /* Send EOI to master */ + /* Handle the interrupt in a more modular way */ + if (interrupt_handlers[r.int_no] != 0) { + isr_t handler = interrupt_handlers[r.int_no]; + handler(r); + } +} diff --git a/20-interrupts-timer/cpu/isr.h b/20-interrupts-timer/cpu/isr.h new file mode 100644 index 0000000..6673ca7 --- /dev/null +++ b/20-interrupts-timer/cpu/isr.h @@ -0,0 +1,88 @@ +#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(); +/* IRQ definitions */ +extern void irq0(); +extern void irq1(); +extern void irq2(); +extern void irq3(); +extern void irq4(); +extern void irq5(); +extern void irq6(); +extern void irq7(); +extern void irq8(); +extern void irq9(); +extern void irq10(); +extern void irq11(); +extern void irq12(); +extern void irq13(); +extern void irq14(); +extern void irq15(); + +#define IRQ0 32 +#define IRQ1 33 +#define IRQ2 34 +#define IRQ3 35 +#define IRQ4 36 +#define IRQ5 37 +#define IRQ6 38 +#define IRQ7 39 +#define IRQ8 40 +#define IRQ9 41 +#define IRQ10 42 +#define IRQ11 43 +#define IRQ12 44 +#define IRQ13 45 +#define IRQ14 46 +#define IRQ15 47 + +/* 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); + +typedef void (*isr_t)(registers_t); +void register_interrupt_handler(u8 n, isr_t handler); + +#endif diff --git a/20-interrupts-timer/cpu/timer.c b/20-interrupts-timer/cpu/timer.c new file mode 100644 index 0000000..3d7ef1b --- /dev/null +++ b/20-interrupts-timer/cpu/timer.c @@ -0,0 +1,27 @@ +#include "timer.h" +#include "../drivers/screen.h" +#include "isr.h" + +u32 tick = 0; + +static void timer_callback(registers_t regs) { + tick++; + kprint("Tick: "); + kprint(tick); + kprint("\n"); +} + +void init_timer(u32 freq) { + /* Install the function we just wrote */ + register_interrupt_handler(IRQ0, timer_callback); + + /* Get the PIT value: hardware clock at 1193180 Hz */ + u32 divisor = 1193180 / freq; + u8 low = (u8)(divisor & 0xFF); + u8 high = (u8)( (divisor >> 8) & 0xFF); + /* Send the command */ + port_byte_out(0x43, 0x36); /* Command port */ + port_byte_out(0x40, low); + port_byte_out(0x40, high); +} + diff --git a/20-interrupts-timer/cpu/timer.h b/20-interrupts-timer/cpu/timer.h new file mode 100644 index 0000000..9c9ca66 --- /dev/null +++ b/20-interrupts-timer/cpu/timer.h @@ -0,0 +1,8 @@ +#ifndef TIMER_H +#define TIMER_H + +#include "../kernel/util.h" + +void init_timer(u32 freq); + +#endif diff --git a/20-interrupts-timer/cpu/types.h b/20-interrupts-timer/cpu/types.h new file mode 100644 index 0000000..992a502 --- /dev/null +++ b/20-interrupts-timer/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 diff --git a/20-interrupts-timer/drivers/ports.c b/20-interrupts-timer/drivers/ports.c new file mode 100644 index 0000000..f015689 --- /dev/null +++ b/20-interrupts-timer/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)); +} diff --git a/20-interrupts-timer/drivers/ports.h b/20-interrupts-timer/drivers/ports.h new file mode 100644 index 0000000..0b7fa2c --- /dev/null +++ b/20-interrupts-timer/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 diff --git a/20-interrupts-timer/drivers/screen.c b/20-interrupts-timer/drivers/screen.c new file mode 100644 index 0000000..02d4dd7 --- /dev/null +++ b/20-interrupts-timer/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; } diff --git a/20-interrupts-timer/drivers/screen.h b/20-interrupts-timer/drivers/screen.h new file mode 100644 index 0000000..c175fd6 --- /dev/null +++ b/20-interrupts-timer/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 diff --git a/20-interrupts-timer/kernel/kernel.c b/20-interrupts-timer/kernel/kernel.c new file mode 100644 index 0000000..fba228a --- /dev/null +++ b/20-interrupts-timer/kernel/kernel.c @@ -0,0 +1,9 @@ +#include "../cpu/isr.h" +#include "../cpu/timer.h" + +void main() { + isr_install(); + + asm volatile("sti"); + init_timer(50); +} diff --git a/20-interrupts-timer/kernel/util.c b/20-interrupts-timer/kernel/util.c new file mode 100644 index 0000000..d7c5da7 --- /dev/null +++ b/20-interrupts-timer/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" */ +} diff --git a/20-interrupts-timer/kernel/util.h b/20-interrupts-timer/kernel/util.h new file mode 100644 index 0000000..3375159 --- /dev/null +++ b/20-interrupts-timer/kernel/util.h @@ -0,0 +1,10 @@ +#ifndef UTIL_H +#define UTIL_H + +#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[]); + +#endif