diff --git a/boot.asm b/boot.asm index bea5f84..874ed1f 100644 --- a/boot.asm +++ b/boot.asm @@ -11,7 +11,7 @@ boot: mov [disk],dl mov ah, 0x2 ;read sectors - mov al, 6 ;sectors to read + mov al, 64 ;sectors to read mov ch, 0 ;cylinder idx mov dh, 0 ;head idx mov cl, 2 ;sector idx @@ -30,22 +30,37 @@ boot: mov gs, ax mov ss, ax jmp CODE_SEG:boot2 + +; access(8) +; P:present(1) DPL:descriptor_privilege_level(2) +; S:descriptor_type(1) E:executable(1) DC:direction/conforming(1) +; RW:readable/writable(1) A:accessed(1) +; flags(4) +; G:granularity(1) DB:size(1) L:long_mode_code(1) Reserved(1) + gdt_start: - dq 0x0 + dq 0x0000000000000000 ; null descriptor + gdt_code: - dw 0xFFFF - dw 0x0 - db 0x0 - db 10011010b - db 11001111b - db 0x0 + dw 0xFFFF ; limit_0_15(16) + dw 0x0000 ; base_0_15(16) + db 0x00 ; base_16_23(8) + db 10011010b ; P(1)=1=present DPL(2)=00=ring0 S(1)=1=non_system E(1)=1=code + ; DC(1)=0=same_ring RW(1)=1=readable A(1)=0=not_accessed + db 11001111b ; G(1)=1=page, DB(1)=1=32b, L(1)=0=non_64b Reserved(1)=0=_ + ; limit_16_19(4)=1111 + db 0x00 ; base_24_31(8) + gdt_data: - dw 0xFFFF - dw 0x0 - db 0x0 - db 10010010b - db 11001111b - db 0x0 + dw 0xFFFF ; limit_0_15(16) + dw 0x0000 ; base_0_15(16) + db 0x00 ; base_16_23(8) + db 10010010b ; P(1)=1=present DPL(2)=00=ring0 S(1)=1=non_system E(1)=0=data + ; DC(1)=0=grows_up RW(1)=1=rw A(1)=0=not_accessed + db 11001111b ; G(1)=1=page DB(1)=1=32b_sp L(1)=0=_ Reserved(1)=0=_ + ; limit_16_19(4)=1111 + db 0x00 ; base_24_31(8) + gdt_end: gdt_pointer: dw gdt_end - gdt_start @@ -82,4 +97,4 @@ section .bss align 4 kernel_stack_bottom: equ $ resb 16384 ; 16 KB -kernel_stack_top: +kernel_stack_top: \ No newline at end of file diff --git a/do.sh b/do.sh new file mode 100644 index 0000000..e655f26 --- /dev/null +++ b/do.sh @@ -0,0 +1,29 @@ +#!/bin/sh +set -e + +# TODO: Check the compiler does x86 + +if [ "$1" = "build" ]; then + shift + + printf "Assembling bootloader...\n" + nasm -f elf32 boot.asm -o boot.o + + printf "Compiling...\n" + gcc \ + -save-temps \ + -std=c99 -m32 \ + -fno-pic \ + -mgeneral-regs-only \ + -ffreestanding -nostdlib \ + -Wall -Wextra -Wpedantic \ + kernel.c boot.o \ + -o kernel.bin \ + -T linker.ld +fi + +if [ "$1" = "boot" ]; then + printf "Booting...\n\n" + MACHINE="-machine pc -cpu 486" + qemu-system-i386 $MACHINE -net none -serial stdio -drive file=kernel.bin,index=0,if=floppy,format=raw +fi \ No newline at end of file diff --git a/kernel.c b/kernel.c index 8d1bdfd..1d66482 100644 --- a/kernel.c +++ b/kernel.c @@ -1,13 +1,488 @@ +#include +#include #include +bool terminate = false; + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +static inline void outb(uint16_t port, uint8_t val) +{ + __asm__ volatile ("outb %b0, %w1" : : "a"(val), "Nd"(port) : "memory"); +} + +static inline uint8_t inb(uint16_t port) +{ + uint8_t ret; + __asm__ volatile ("inb %w1, %b0" : "=a"(ret) : "Nd"(port) : "memory"); + return ret; +} + +static inline void io_wait(void) +{ + outb(0x80, 0); +} + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +#define PORT 0x3f8 // COM1 + +void init_serial(void) +{ + // Enable "data available" interrupt. + outb(PORT + 1, 0x01); +} + +// Use only when there _is_ something to read. +char read_serial() +{ + return inb(PORT); +} + +int is_transmit_empty() +{ + return inb(PORT + 5) & 0x20; +} + +void write_serial(char a) +{ + while (is_transmit_empty() == 0); + outb(PORT, a); +} + +#undef PORT + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +/* + * Convert numbers to hexadecimal chars. + */ + +char nibble_to_hex(int n) +{ + if (n>=0 && n<10) return (n+48); + else if (n>=10 && n<16) return (n+55+32); + else return '?'; +} + +void int_to_hex(char *hex, uint64_t a, size_t size) +{ + int i = 0; + int divisor = size << 3; + while (divisor != 0) { + divisor -= 4; + hex[i] = nibble_to_hex((a >> divisor) & 0xF); + i += 1; + } +} + +void u8_to_hex (char *s, uint8_t a) { int_to_hex(s, a, 1); } +void u16_to_hex(char *s, uint16_t a) { int_to_hex(s, a, 2); } +void u32_to_hex(char *s, uint32_t a) { int_to_hex(s, a, 4); } +void u64_to_hex(char *s, uint64_t a) { int_to_hex(s, a, 8); } + +/* + * Logging functions. + */ + +// Use serial for log output. +#define PUTCHAR write_serial + +// Sends buffer of a given size to the log. +void klog(char *buff, int size) +{ + for (int i=0; i= 8) outb(PIC2_COMMAND, 0x20); + outb(PIC1_COMMAND, 0x20); +} + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +struct gdt_descriptor +{ + uint16_t limit; + uint32_t base; +} __attribute__((packed)); + +void sgdt(struct gdt_descriptor *gdtd) +{ + __asm__ volatile ("sgdt %0" : : "m"(*gdtd) : "memory"); +} + +void klog_info_about_gdt(void) +{ + struct gdt_descriptor gdtd; + sgdt(&gdtd); + + klogs("**\n GDT info:\n"); + klogl_u32(gdtd.base); + klogl_u16(gdtd.limit); + + // For each (8 bytes long) entry in the table... + for (uint32_t b = gdtd.base; b < gdtd.base + gdtd.limit; b += 8) { + // ... log each byte, in hex. + for (uint32_t i = b; i < b + 8; ++i) { + klog_u8(*(uint8_t*)i); + } + klogs("\n"); + } +} + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +struct idt_gate_descriptor +{ + 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 +} __attribute__((packed)); + +#define PIC_REMAP_OFFSET 0x20 + +// The IDT begins with gates for the exceptions; we remap the PIC to start at +// some point after them. For the PIC IRQs that's 16 gates more. If we define +// our software interrupts... we'll have to make room. +#define IDT_NUM_GATES (PIC_REMAP_OFFSET + 16) + +__attribute__((aligned(0x10))) +struct idt_gate_descriptor idt[IDT_NUM_GATES] = {0}; + +static inline void lidt(void) +{ + struct { + uint16_t limit; + void *base; + } __attribute__((packed)) src = { IDT_NUM_GATES * 8 - 1, idt }; + __asm__ ("lidt %0" : : "m"(src) ); +} + +struct interrupt_frame; // Not defined yet, but we need a pointer to it. + +#define GATE_F void (*f)(struct interrupt_frame *) +#define GATE_WITH_ERR_F void (*f)(struct interrupt_frame *, uint32_t) +#define FOR_INTR 0x8e /* Present, DPL 0, 32b interrupt gate */ +#define FOR_TRAP 0x8f /* Present, DPL 0, 32b trap gate */ + +struct idt_gate_descriptor gate(uint8_t type_attributes, uint32_t f) +{ + return (struct idt_gate_descriptor){ + .type_attributes = type_attributes, + .selector = 0x0008, // Our code segment in GDT, ring0 requested. + .offset_1 = (uint16_t)(f & 0xffff), + .offset_2 = (uint16_t)((f>>16) & 0xffff), + .zero = 0x00 + }; +} + +void klog_idt_gate_update(int n) +{ + klogs(" "); klog_u8(n); +} + +void set_irq_handler(size_t irq, GATE_F) +{ + idt[irq + PIC_REMAP_OFFSET] = gate(FOR_INTR, (uint32_t)f); + klog_idt_gate_update(irq); +} + +void set_exception_with_err_handler(size_t number, GATE_WITH_ERR_F) +{ + idt[number] = gate(FOR_TRAP, (uint32_t)f); + klog_idt_gate_update(number); +} + +void set_exception_handler(size_t number, GATE_F) +{ + idt[number] = gate(FOR_TRAP, (uint32_t)f); + klog_idt_gate_update(number); +} + +#undef FOR_TRAP +#undef FOR_INTR +#undef GATE_WITH_ERR_F +#undef GATE_F + +void pic_clear_irq(uint8_t irq) +{ + uint16_t port; + uint8_t value; + if(irq < 8) { + port = PIC1_DATA; + } else { + port = PIC2_DATA; irq -= 8; + } + value = inb(port) & ~(1 << irq); + outb(port, value); +} + +void pic_mask_all_irqs(void) +{ + outb(PIC1_DATA, 0xff); outb(PIC2_DATA, 0xff); +} + +static inline void sti(void) +{ + __asm__ volatile ("sti"); +} + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +void panic(void) +{ + klogs("Panic: halt."); + __asm__ volatile ("cli; hlt;"); +} + +__attribute__((interrupt)) +void exception_with_err_handler(struct interrupt_frame *frame, uint32_t error_code) +{ + (void)frame; + klogs("\n(generic handler) Exception with err "); klogl_u32(error_code); + panic(); +} + +__attribute__((interrupt)) +void exception_handler(struct interrupt_frame *frame) +{ + (void)frame; + klogs("\n(generic handler) Exception\n"); + panic(); +} + +__attribute__((interrupt)) +void exception_de(struct interrupt_frame *frame) +{ + (void)frame; + klogs("\nException: Divide Error.\n"); + panic(); +} + +__attribute__((interrupt)) +void exception_gp(struct interrupt_frame *frame, uint32_t error_code) +{ + (void)frame; + klogs("\nException: General Protection; error code "); klogl_u32(error_code); + panic(); +} + +__attribute__((interrupt)) +void irq1_handler(struct interrupt_frame *frame) +{ + (void)frame; + uint8_t k = inb(0x60); + klogs("Key: "); klogl_u8(k); + if (k == /* q */ 0x90) terminate = true; + pic_end_of_irq(1); +} + +__attribute__((interrupt)) +void irq4_handler(struct interrupt_frame *frame) +{ + (void)frame; + uint8_t c = read_serial(); + write_serial(c); // Echo. + + // Testing stuff when receiving some chars. + if (c == 'q') terminate = true; + if (c == '0') + __asm__ volatile ("mov $0, %bl; div %bl"); // Division by zero. + if (c == 'f') + __asm__ volatile ("int $13;"); // General Protection fault. + + pic_end_of_irq(4); +} + +void init_interrupts(void) +{ + klogs("**\n Init interrupts\n"); + + klogs("Preparing PIC.\n"); + pic_mask_all_irqs(); + pic_remap(PIC_REMAP_OFFSET); + + klogs("Init exceptions.\n"); +#define A(n) set_exception_handler(n, &exception_handler) +#define B(n) set_exception_with_err_handler(n, &exception_with_err_handler) + set_exception_handler(0, &exception_de); + A(1); A(2); A(3); A(4); A(5); A(6); A(7); B(8); A(9); + B(10); B(11); B(12); + set_exception_with_err_handler(13, &exception_gp); + B(14); A(15); A(16); B(17); A(18); A(19); + A(20); B(21); A(28); B(29); B(30); +#undef B +#undef A + + klogs("\nInit IRQs.\n"); + + // Assign handlers to hardware interrupts. + // Use the IRQ number here (the offset is applied elsewhere). + set_irq_handler(1, &irq1_handler); // Keyboard. + set_irq_handler(4, &irq4_handler); // Serial. + klogs("\n"); + + // Make our IDT the active one. + lidt(); + + // Unmask hardware interrupts that we're ready to handle. + pic_clear_irq(1); + pic_clear_irq(4); + + // Start accepting IRQs (that is... from the PIC). + sti(); +} + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// + void kmain(void) { - const uint16_t color = 0x0F00; - const char *hello = "Hello C world!"; - volatile uint16_t *vga = (volatile uint16_t *)0xb8000; + init_serial(); + klogs("Hello from kmain!\n"); + demo_klog_functions(); - for (int i = 0; i < 16; ++i) - { + init_interrupts(); + + const uint16_t color = 0x7e00; + volatile uint16_t *vga = (volatile uint16_t *)0xb8000; + const char *hello = "Hello! Please, see serial output."; + + for (int i = 0; hello[i] != 0; ++i) { vga[i + 80] = color | (uint16_t)hello[i]; } -} + + klog_info_about_gdt(); + + klogs("\n\n**\n Ready!\n"); + klogs( + "(accepting input from keyboard)\n" + " type q to return from kmain\n" + " key info sent to serial\n" + "(accepting input from serial, with echo)\n" + " send q to return from kmain\n" + " send f to invoke GPF\n" + " send 0 to trigger divide by zero\n" + ); + while (!terminate) { + __asm__ volatile ("hlt"); + } + + klogs("\nkmain returning now... o/\n"); +} \ No newline at end of file