diff --git a/Makefile b/Makefile index 5161b01..280b57a 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,13 @@ CC=i686-elf-gcc .PHONY: iso run clean %.o: %.c - $(CC) -c $< -o $@ -std=gnu99 -ffreestanding -O2 -Wall -Wextra -pedantic -I. + $(CC) -c $< -o $@ -std=gnu99 -mgeneral-regs-only -ffreestanding -O2 -Wall -Wextra -pedantic -I. bootstrap.o: bootstrap.s i686-elf-as bootstrap.s -o bootstrap.o -kernel.bin: bootstrap.o kernel.o vga.o std/std.o - $(CC) -T linker.ld -o kernel.bin -ffreestanding -O2 -nostdlib bootstrap.o kernel.o vga.o std/std.o -lgcc +kernel.bin: bootstrap.o vga.o std/std.o cpu/idt.o cpu/pic.o kernel.o + $(CC) -T linker.ld -o $@ -ffreestanding -O2 -nostdlib -lgcc $^ iso: kernel.bin mkdir -p isodir/boot/grub diff --git a/bootstrap.s b/bootstrap.s index 1414ac8..327c491 100644 --- a/bootstrap.s +++ b/bootstrap.s @@ -100,7 +100,7 @@ _start: stack since (pushed 0 bytes so far), so the alignment has thus been preserved and the call is well defined. */ - call kernel_main + call kmain /* If the system has nothing more to do, put the computer into an @@ -108,7 +108,7 @@ _start: 1) Disable interrupts with cli (clear interrupt enable in eflags). They are already disabled by the bootloader, so this is not needed. Mind that you might later enable interrupts and return from - kernel_main (which is sort of nonsensical to do). + kmain (which is sort of nonsensical to do). 2) Wait for the next interrupt to arrive with hlt (halt instruction). Since they are disabled, this will lock up the computer. 3) Jump to the hlt instruction if it ever wakes up due to a diff --git a/cpu/idt.c b/cpu/idt.c new file mode 100644 index 0000000..6377cc9 --- /dev/null +++ b/cpu/idt.c @@ -0,0 +1,27 @@ +#include +#include +#include "cpu/idt.h" + + +struct interrupt_descriptor_32 idt[256]; +static size_t idt_ptr[2]; + +void idt_init() { + size_t idt_address = (size_t)idt; + idt_ptr[0] = (sizeof (struct interrupt_descriptor_32) * 256) + ((idt_address & 0xffff) << 16); + idt_ptr[1] = idt_address >> 16; + + __asm__ __volatile__( + "lidt %0\n\t" + "sti\n\t" : : "m"(idt_ptr) + ); +} + +void idt_register_handler(uint8_t interrupt, size_t address) { + idt[interrupt].offset_1 = address & 0xffff; + idt[interrupt].selector = KERNEL_CODE_SEGMENT_OFFSET; + idt[interrupt].zero = 0; + idt[interrupt].type_attributes = INTERRUPT_GATE; + idt[interrupt].offset_2 = (address & 0xffff0000) >> 16; +} + diff --git a/cpu/idt.h b/cpu/idt.h new file mode 100644 index 0000000..1aa60b4 --- /dev/null +++ b/cpu/idt.h @@ -0,0 +1,23 @@ +#ifndef _IDT_H +#define _IDT_H + +#include +#include + +#define KERNEL_CODE_SEGMENT_OFFSET 0x08 +#define INTERRUPT_GATE 0x8e + +// https://wiki.osdev.org/Interrupt_Descriptor_Table +struct __attribute__((__packed__)) interrupt_descriptor_32 { + uint16_t offset_1; // offset bits 0..15 + uint16_t selector; // a code segment selector in GDT or LDT + uint8_t zero; // unused, set to 0 + uint8_t type_attributes; // gate type, dpl, and p fields + uint16_t offset_2; // offset bits 16..31 +}; + +void idt_init(); +void idt_register_handler(uint8_t interrupt, size_t address); + +#endif + diff --git a/cpu/io.h b/cpu/io.h new file mode 100644 index 0000000..26b314c --- /dev/null +++ b/cpu/io.h @@ -0,0 +1,34 @@ +static inline void outb(const uint16_t port, const uint8_t val) { + asm volatile("outb %0, %1" : : "a" (val), "Nd" (port)); +} + +static inline void outw(const uint16_t port, const uint16_t val) { + asm volatile("outw %0, %1" : : "a" (val), "Nd" (port)); +} + +static inline void outl(const uint16_t port, const uint32_t val) { + asm volatile("outl %0, %1" : : "a" (val), "Nd" (port)); +} + +static inline uint8_t inb(const uint16_t port) { + volatile uint8_t ret; + asm volatile("inb %1, %0" : "=a" (ret) : "Nd" (port)); + return ret; +} + +static inline uint16_t inw(const uint16_t port) { + volatile uint16_t ret; + asm volatile("inw %1, %0" : "=a" (ret) : "Nd" (port)); + return ret; +} + +static inline uint32_t inl(const uint16_t port) { + volatile uint32_t ret; + asm volatile("inl %1, %0" : "=a" (ret) : "Nd" (port)); + return ret; +} + +static inline void io_wait() { + outb(0x80, 0); +} + diff --git a/cpu/pic.c b/cpu/pic.c new file mode 100644 index 0000000..7df471c --- /dev/null +++ b/cpu/pic.c @@ -0,0 +1,67 @@ +#include "cpu/pic.h" +#include "cpu/io.h" + +void pic_remap(uint8_t offset1, uint8_t offset2) { + uint8_t a1, a2; + + a1 = inb(PIC1_DATA); // save masks + a2 = inb(PIC2_DATA); + + outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4); // starts the initialization sequence (in cascade mode) + io_wait(); + outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4); + io_wait(); + outb(PIC1_DATA, offset1); // ICW2: Master PIC vector offset + io_wait(); + outb(PIC2_DATA, offset2); // ICW2: Slave PIC vector offset + io_wait(); + outb(PIC1_DATA, 4); // ICW3: tell Master PIC that there is a slave PIC at IRQ2 (0000 0100) + io_wait(); + outb(PIC2_DATA, 2); // ICW3: tell Slave PIC its cascade identity (0000 0010) + io_wait(); + + outb(PIC1_DATA, ICW4_8086); + io_wait(); + outb(PIC2_DATA, ICW4_8086); + io_wait(); + + outb(PIC1_DATA, a1); // restore saved masks. + outb(PIC2_DATA, a2); +} + +void pic_send_eoi(uint8_t irq) +{ + if(irq >= 8) + outb(PIC2_COMMAND,PIC_EOI); + + outb(PIC1_COMMAND,PIC_EOI); +} + +void pic_irq_set_mask(uint8_t IRQline) { + uint16_t port; + uint8_t value; + + if(IRQline < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + IRQline -= 8; + } + value = inb(port) | (1 << IRQline); + outb(port, value); +} + +void pic_irq_clear_mask(uint8_t IRQline) { + uint16_t port; + uint8_t value; + + if(IRQline < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; + IRQline -= 8; + } + value = inb(port) & ~(1 << IRQline); + outb(port, value); +} + diff --git a/cpu/pic.h b/cpu/pic.h new file mode 100644 index 0000000..15c62f3 --- /dev/null +++ b/cpu/pic.h @@ -0,0 +1,31 @@ +#ifndef _PIC_H +#define _PIC_H + +#include +#include + +#define PIC1 0x20 /* IO base address for master PIC */ +#define PIC2 0xA0 /* IO base address for slave PIC */ +#define PIC1_COMMAND PIC1 +#define PIC1_DATA (PIC1+1) +#define PIC2_COMMAND PIC2 +#define PIC2_DATA (PIC2+1) +#define PIC_EOI 0x20 /* End-of-interrupt command code */ +#define ICW1_ICW4 0x01 /* ICW4 (not) needed */ +#define ICW1_SINGLE 0x02 /* Single (cascade) mode */ +#define ICW1_INTERVAL4 0x04 /* Call address interval 4 (8) */ +#define ICW1_LEVEL 0x08 /* Level triggered (edge) mode */ +#define ICW1_INIT 0x10 /* Initialization - required! */ + +#define ICW4_8086 0x01 /* 8086/88 (MCS-80/85) mode */ +#define ICW4_AUTO 0x02 /* Auto (normal) EOI */ +#define ICW4_BUF_SLAVE 0x08 /* Buffered mode/slave */ +#define ICW4_BUF_MASTER 0x0C /* Buffered mode/master */ +#define ICW4_SFNM 0x10 /* Special fully nested (not) */ + +void pic_send_eoi(uint8_t irq); +void pic_remap(uint8_t offset1, uint8_t offset2); +void pic_irq_set_mask(uint8_t IRQline); +void pic_irq_clear_mask(uint8_t IRQline); + +#endif diff --git a/kernel.c b/kernel.c index 4646434..8766ebb 100644 --- a/kernel.c +++ b/kernel.c @@ -2,23 +2,43 @@ #include #include #include "vga.h" - -/* Check if the compiler thinks you are targeting the wrong operating system. */ +#include "cpu/pic.h" +#include "cpu/idt.h" + #if defined(__linux__) #error "You are not using a cross-compiler, you will most certainly run into trouble" #endif -/* This tutorial will only work for the 32-bit ix86 targets. */ #if !defined(__i386__) #error "This OS needs to be compiled with a ix86-elf compiler" #endif - -void kernel_main(void) + +struct interrupt_frame { - /* Initialize terminal interface */ + size_t ip; + size_t cs; + size_t flags; + size_t sp; + size_t ss; +} __attribute__((packed)); + +__attribute__((interrupt)) +static void isr0(struct interrupt_frame *frame) { + terminal_writestring("division by zero"); + pic_send_eoi(0); +} + +void kmain(void) { terminal_initialize(); - - /* Newline support is left as an exercise. */ + + terminal_writestring("start"); + + pic_remap(0x20, 0x28); + idt_register_handler(0, (size_t)isr0); + idt_init(); + + volatile int a = 0/0; + terminal_writestring("Hello, kernel World!\n"); }