#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) { init_serial(); klogs("Hello from kmain!\n"); demo_klog_functions(); 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"); }