From f6ba6aa117d8a25ee7b6848c0e5343ef57e648f8 Mon Sep 17 00:00:00 2001 From: Jenny Curle Date: Mon, 1 Apr 2019 01:38:50 +0100 Subject: [PATCH] Legacy OS files. Have an i686-elf-gcc & -ld in your PATH, and the Makefile will take care of the rest. --- boot.s | 46 ++++++ common.c | 72 +++++++++ descriptor_tables.c | 161 ++++++++++++++++++++ gdt.s | 28 ++++ headers/common.h | 26 ++++ headers/descriptor_tables.h | 114 ++++++++++++++ headers/isr.h | 40 +++++ headers/keyboard.h | 5 + headers/kheap.h | 16 ++ headers/paging.h | 47 ++++++ headers/screen.h | 45 ++++++ headers/serial.h | 62 ++++++++ headers/timer.h | 6 + interrupt.s | 159 +++++++++++++++++++ iso/boot/grub/menu.lst | 5 + iso/boot/grub/stage2_eltorito | Bin 0 -> 105522 bytes isr.c | 50 ++++++ keyboard.c | 48 ++++++ kheap.c | 40 +++++ link.ld | 33 ++++ main.c | 71 +++++++++ makefile | 52 +++++++ paging.c | 218 ++++++++++++++++++++++++++ screen.c | 277 ++++++++++++++++++++++++++++++++++ serial.c | 171 +++++++++++++++++++++ timer.c | 39 +++++ 26 files changed, 1831 insertions(+) create mode 100644 boot.s create mode 100644 common.c create mode 100644 descriptor_tables.c create mode 100644 gdt.s create mode 100644 headers/common.h create mode 100644 headers/descriptor_tables.h create mode 100644 headers/isr.h create mode 100644 headers/keyboard.h create mode 100644 headers/kheap.h create mode 100644 headers/paging.h create mode 100644 headers/screen.h create mode 100644 headers/serial.h create mode 100644 headers/timer.h create mode 100644 interrupt.s create mode 100644 iso/boot/grub/menu.lst create mode 100644 iso/boot/grub/stage2_eltorito create mode 100644 isr.c create mode 100644 keyboard.c create mode 100644 kheap.c create mode 100644 link.ld create mode 100644 main.c create mode 100644 makefile create mode 100644 paging.c create mode 100644 screen.c create mode 100644 serial.c create mode 100644 timer.c diff --git a/boot.s b/boot.s new file mode 100644 index 0000000..dece606 --- /dev/null +++ b/boot.s @@ -0,0 +1,46 @@ +; +; boot.s -- Kernel start location. Also defines multiboot header. +; Based on Bran's kernel development tutorial file start.asm +; + +MBOOT_PAGE_ALIGN equ 1<<0 ; Load kernel and modules on a page boundary +MBOOT_MEM_INFO equ 1<<1 ; Provide your kernel with memory info +MBOOT_HEADER_MAGIC equ 0x1BADB002 ; Multiboot Magic value +; NOTE: We do not use MBOOT_AOUT_KLUDGE. It means that GRUB does not +; pass us a symbol table. +MBOOT_HEADER_FLAGS equ MBOOT_PAGE_ALIGN | MBOOT_MEM_INFO +MBOOT_CHECKSUM equ -(MBOOT_HEADER_MAGIC + MBOOT_HEADER_FLAGS) + + +[BITS 32] ; All instructions should be 32-bit. + +[GLOBAL mboot] ; Make 'mboot' accessible from C. +[EXTERN code] ; Start of the '.text' section. +[EXTERN bss] ; Start of the .bss section. +[EXTERN end] ; End of the last loadable section. + +mboot: + dd MBOOT_HEADER_MAGIC ; GRUB will search for this value on each + ; 4-byte boundary in your kernel file + dd MBOOT_HEADER_FLAGS ; How GRUB should load your file / settings + dd MBOOT_CHECKSUM ; To ensure that the above values are correct + + dd mboot ; Location of this descriptor + dd code ; Start of kernel '.text' (code) section. + dd bss ; End of kernel '.data' section. + dd end ; End of kernel. + dd start ; Kernel entry point (initial EIP). + +[GLOBAL start] ; Kernel entry point. +[EXTERN kernel_main] ; This is the entry point of our C code + +start: + ; Load multiboot information: + push ebx + + ; Execute the kernel: + cli ; Disable interrupts. + call kernel_main ; call our main() function. + jmp $ ; Enter an infinite loop, to stop the processor + ; executing whatever rubbish is in the memory + ; after our kernel! diff --git a/common.c b/common.c new file mode 100644 index 0000000..75d463c --- /dev/null +++ b/common.c @@ -0,0 +1,72 @@ +#include "headers/common.h" +#include "headers/screen.h" +#include + +void memcpy(void *dest, void *src, unsigned int n) +{ + // Typecast src and dest addresses to (char *) + char *csrc = (char *)src; + char *cdest = (char *)dest; + + // Copy contents of src[] to dest[] + for (unsigned int i=0; i + + +// Lets us access our ASM functions from our C code. +extern void gdt_flush(uint32_t); +extern void idt_flush(uint32_t); +// Internal function prototypes. +static void init_gdt(); +static void gdt_set_gate(int,uint32_t,uint32_t,uint8_t,uint8_t); +static void init_idt(); +static void idt_set_gate(uint8_t,uint32_t,uint16_t,uint8_t); +gdt_entry_t gdt_entries[5]; +gdt_ptr_t gdt_ptr; +idt_entry_t idt_entries[256]; +idt_ptr_t idt_ptr; + +// extern the isr handler array so it can nullified on start +extern isr_t interrupt_handlers[]; + + +// Initialisation routine - zeroes all the interrupt service routines, +// initialises the GDT and IDT. +void init_descriptor_tables() +{ + // Initialise the global descriptor table. + init_gdt(); + init_idt(); + //nullify all the interrupt handlers + memset(&interrupt_handlers,0,sizeof(isr_t)*256); +} + +static void init_gdt() +{ + gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1; + gdt_ptr.base = (uint32_t)&gdt_entries; + + gdt_set_gate(0, 0, 0, 0, 0); // Null segment + gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment + gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment + gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment + gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment + + gdt_flush((uint32_t)&gdt_ptr); +} + +// Set the value of one GDT entry. +static void gdt_set_gate(int num, uint32_t base,uint32_t limit, uint8_t access, uint8_t gran) +{ + gdt_entries[num].base_low = (base & 0xFFFF); + gdt_entries[num].base_middle = (base >> 16) & 0xFF; + gdt_entries[num].base_high = (base >> 24) & 0xFF; + + gdt_entries[num].limit_low = (limit & 0xFFFF); + gdt_entries[num].granularity = (limit >> 16) & 0x0F; + + gdt_entries[num].granularity |= gran & 0xF0; + gdt_entries[num].access = access; +} + + +static void init_idt() +{ + idt_ptr.limit = sizeof(idt_entry_t) * 256 -1; + idt_ptr.base = (uint32_t)&idt_entries; + + memset(&idt_entries, 0, sizeof(idt_entry_t)*256); + + // Remap the irq table. + outb(0x20, 0x11); + outb(0xA0, 0x11); + outb(0x21, 0x20); + outb(0xA1, 0x28); + outb(0x21, 0x04); + outb(0xA1, 0x02); + outb(0x21, 0x01); + outb(0xA1, 0x01); + outb(0x21, 0x0); + outb(0xA1, 0x0); + + + + + idt_set_gate( 0, (uint32_t)isr0 , 0x08, 0x8E); + idt_set_gate( 1, (uint32_t)isr1 , 0x08, 0x8E); + idt_set_gate( 2, (uint32_t)isr2 , 0x08, 0x8E); + idt_set_gate( 3, (uint32_t)isr3 , 0x08, 0x8E); + idt_set_gate( 4, (uint32_t)isr4 , 0x08, 0x8E); + idt_set_gate( 5, (uint32_t)isr5 , 0x08, 0x8E); + idt_set_gate( 6, (uint32_t)isr6 , 0x08, 0x8E); + idt_set_gate( 7, (uint32_t)isr7 , 0x08, 0x8E); + idt_set_gate( 8, (uint32_t)isr8 , 0x08, 0x8E); + idt_set_gate( 9, (uint32_t)isr9 , 0x08, 0x8E); + idt_set_gate(10, (uint32_t)isr10, 0x08, 0x8E); + idt_set_gate(11, (uint32_t)isr11, 0x08, 0x8E); + idt_set_gate(12, (uint32_t)isr12, 0x08, 0x8E); + idt_set_gate(13, (uint32_t)isr13, 0x08, 0x8E); + idt_set_gate(14, (uint32_t)isr14, 0x08, 0x8E); + idt_set_gate(15, (uint32_t)isr15, 0x08, 0x8E); + idt_set_gate(16, (uint32_t)isr16, 0x08, 0x8E); + idt_set_gate(17, (uint32_t)isr17, 0x08, 0x8E); + idt_set_gate(18, (uint32_t)isr18, 0x08, 0x8E); + idt_set_gate(19, (uint32_t)isr19, 0x08, 0x8E); + idt_set_gate(20, (uint32_t)isr20, 0x08, 0x8E); + idt_set_gate(21, (uint32_t)isr21, 0x08, 0x8E); + idt_set_gate(22, (uint32_t)isr22, 0x08, 0x8E); + idt_set_gate(23, (uint32_t)isr23, 0x08, 0x8E); + idt_set_gate(24, (uint32_t)isr24, 0x08, 0x8E); + idt_set_gate(25, (uint32_t)isr25, 0x08, 0x8E); + idt_set_gate(26, (uint32_t)isr26, 0x08, 0x8E); + idt_set_gate(27, (uint32_t)isr27, 0x08, 0x8E); + idt_set_gate(28, (uint32_t)isr28, 0x08, 0x8E); + idt_set_gate(29, (uint32_t)isr29, 0x08, 0x8E); + idt_set_gate(30, (uint32_t)isr30, 0x08, 0x8E); + idt_set_gate(31, (uint32_t)isr31, 0x08, 0x8E); + + idt_set_gate(32,(uint32_t)irq0,0x08,0x8E); + idt_set_gate(33,(uint32_t)irq1,0x08,0x8E); + idt_set_gate(34,(uint32_t)irq2,0x08,0x8E); + idt_set_gate(35,(uint32_t)irq3,0x08,0x8E); + idt_set_gate(36,(uint32_t)irq4,0x08,0x8E); + idt_set_gate(37,(uint32_t)irq5,0x08,0x8E); + idt_set_gate(38,(uint32_t)irq6,0x08,0x8E); + idt_set_gate(39,(uint32_t)irq7,0x08,0x8E); + idt_set_gate(40,(uint32_t)irq8,0x08,0x8E); + idt_set_gate(41,(uint32_t)irq9,0x08,0x8E); + idt_set_gate(42,(uint32_t)irq10,0x08,0x8E); + idt_set_gate(43,(uint32_t)irq11,0x08,0x8E); + idt_set_gate(44,(uint32_t)irq12,0x08,0x8E); + idt_set_gate(45,(uint32_t)irq13,0x08,0x8E); + idt_set_gate(46,(uint32_t)irq14,0x08,0x8E); + idt_set_gate(47,(uint32_t)irq15,0x08,0x8E); + + + idt_flush((uint32_t)&idt_ptr); +} + +static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) +{ + idt_entries[num].base_lo = base & 0xFFFF; + idt_entries[num].base_hi = (base >> 16) & 0xFFFF; + + idt_entries[num].sel = sel; + idt_entries[num].always0 = 0; + // We must uncomment the OR below when we get to using user-mode. + // It sets the interrupt gate's privilege level to 3. + idt_entries[num].flags = flags /* | 0x60 */; +} + + + + diff --git a/gdt.s b/gdt.s new file mode 100644 index 0000000..36cc6ac --- /dev/null +++ b/gdt.s @@ -0,0 +1,28 @@ +; +; Gdt.s -- contains global descriptor table and interrupt descriptor table +; setup code. +; Based on code from Bran's kernel development tutorials. +; Rewritten for JamesM's kernel development tutorials. + +[GLOBAL gdt_flush] ; Allows the C code to call gdt_flush(). + +gdt_flush: + mov eax, [esp+4] ; Get the pointer to the GDT, passed as a parameter. + lgdt [eax] ; Load the new GDT pointer + + mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment + mov ds, ax ; Load all data segment selectors + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + jmp 0x08:.flush ; 0x08 is the offset to our code segment: Far jump! +.flush: + ret + +[GLOBAL idt_flush] ; Allows the C code to call idt_flush(). + +idt_flush: + mov eax, [esp+4] ; Get the pointer to the IDT, passed as a parameter. + lidt [eax] ; Load the IDT pointer. + ret diff --git a/headers/common.h b/headers/common.h new file mode 100644 index 0000000..b4e3eb0 --- /dev/null +++ b/headers/common.h @@ -0,0 +1,26 @@ +#ifndef COMMON_H +#define COMMON_H +#include + + + +//extern void memcpy(void *dest, void *src, unsigned int n); +//extern void memset(void *s, int c, unsigned int n); + +//void outb(u16int port, u8int value); +//u8int inb(u16int port); +//u16int inw(u16int port); +extern void memcpy(void *dest, void *src, unsigned int n); +extern void memset(void *s, int c, unsigned int n); +extern unsigned char inb(unsigned short port); +extern void outb(unsigned short port, unsigned char data); +extern unsigned short inw(unsigned short port); +extern void outw(unsigned short port, unsigned short data); +#endif + + +#define PANIC(msg) panic(msg, __FILE__, __LINE__); +#define ASSERT(b) ((b) ? (void)0 : panic_assert(__FILE__, __LINE__, #b)) + +extern void panic(const char *message, const char *file, uint32_t line); +extern void panic_assert(const char *file, uint32_t line, const char *desc); diff --git a/headers/descriptor_tables.h b/headers/descriptor_tables.h new file mode 100644 index 0000000..1f9f37a --- /dev/null +++ b/headers/descriptor_tables.h @@ -0,0 +1,114 @@ +// +// descriptor_tables.h - Defines the interface for initialising the GDT and IDT. +// Also defines needed structures. +// Based on code from Bran's kernel development tutorials. +// Rewritten for JamesM's kernel development tutorials. +// + +#include "common.h" +#include + + +// Initialisation function is publicly accessible. +void init_descriptor_tables(); + + +// This structure contains the value of one GDT entry. +// We use the attribute 'packed' to tell GCC not to change +// any of the alignment in the structure. +struct gdt_entry_struct +{ + uint16_t limit_low; // The lower 16 bits of the limit. + uint16_t base_low; // The lower 16 bits of the base. + uint8_t base_middle; // The next 8 bits of the base. + uint8_t access; // Access flags, determine what ring this segment can be used in. + uint8_t granularity; + uint8_t base_high; // The last 8 bits of the base. +} __attribute__((packed)); + +typedef struct gdt_entry_struct gdt_entry_t; + +// This struct describes a GDT pointer. It points to the start of +// our array of GDT entries, and is in the format required by the +// lgdt instruction. +struct gdt_ptr_struct +{ + uint16_t limit; // The upper 16 bits of all selector limits. + uint32_t base; // The address of the first gdt_entry_t struct. +} __attribute__((packed)); + +typedef struct gdt_ptr_struct gdt_ptr_t; + +// A struct describing an interrupt gate. +struct idt_entry_struct +{ + uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires. + uint16_t sel; // Kernel segment selector. + uint8_t always0; // This must always be zero. + uint8_t flags; // More flags. See documentation. + uint16_t base_hi; // The upper 16 bits of the address to jump to. +} __attribute__((packed)); + +typedef struct idt_entry_struct idt_entry_t; + +// A struct describing a pointer to an array of interrupt handlers. +// This is in a format suitable for giving to 'lidt'. +struct idt_ptr_struct +{ + uint16_t limit; + uint32_t base; // The address of the first element in our idt_entry_t array. +} __attribute__((packed)); + +typedef struct idt_ptr_struct idt_ptr_t; + +// These extern directives let us access the addresses of our ASM ISR handlers. +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(); + +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(); + diff --git a/headers/isr.h b/headers/isr.h new file mode 100644 index 0000000..27d270b --- /dev/null +++ b/headers/isr.h @@ -0,0 +1,40 @@ +// +// isr.h -- Interface and structures for high level interrupt service routines. +// Part of this code is modified from Bran's kernel development tutorials. +// Rewritten for JamesM's kernel development tutorials. +// + +#include "common.h" +#include + +typedef struct registers +{ + uint32_t ds; // Data segment selector + uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // Pushed by pusha. + uint32_t int_no, err_code; // Interrupt number and error code (if applicable) + uint32_t eip, cs, eflags, useresp, ss; // Pushed by the processor automatically. +} registers_t; + +#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 + + +// enables registeration of callbacks for interrupts or irqs +// for irqs to ease confusin use the #defines ast the first param +typedef void (*isr_t)(registers_t*); +void register_interrupt_handler(uint8_t n, isr_t handler); + diff --git a/headers/keyboard.h b/headers/keyboard.h new file mode 100644 index 0000000..7b358a3 --- /dev/null +++ b/headers/keyboard.h @@ -0,0 +1,5 @@ +#pragma once + +#include "common.h" + +void init_keyboard(void); diff --git a/headers/kheap.h b/headers/kheap.h new file mode 100644 index 0000000..d617fab --- /dev/null +++ b/headers/kheap.h @@ -0,0 +1,16 @@ +#pragma once +#include "common.h" +#include +#include + + +uint32_t kmalloc(size_t sz, int align, uint32_t *phys); + +//Allocate a chunk of memory, with size sz. +uint32_t kmalloc_a(size_t sz); + +//Allocate a chunk of memory, with size sz. The physical address is returned as phys. +uint32_t kmalloc_p(size_t sz, uint32_t *phys); + +//Allocate a chunk of memory, with size sz. The physical address is returned as phys. The memory must be page aligned. +uint32_t kmalloc_ap(size_t sz, uint32_t *phys); diff --git a/headers/paging.h b/headers/paging.h new file mode 100644 index 0000000..07aaad5 --- /dev/null +++ b/headers/paging.h @@ -0,0 +1,47 @@ +#pragma once + +#include "common.h" +#include "isr.h" +#include + +typedef struct page +{ + uint32_t present : 1; // page present in memory + uint32_t rw : 1; // ro if clear, rw if set + uint32_t user : 1; // supervisor level only if clear + uint32_t accessed : 1; // has been accessed since last referesh + uint32_t dirty : 1; // has been written to since last refresh + uint32_t unused : 7; // reserved and unused bits + uint32_t frame : 20; // frame address (right shift 12 bits) +} page_t; + +typedef struct page_table +{ + page_t pages[1024]; +} page_table_t; + +typedef struct +{ + // array of pointers to pagetables + page_table_t *tables[1024]; + // array of pointers to tables above but at the *physical* loc + // so they can be loaded into cr3 + uint32_t tablesPhysical[1024]; + + uint32_t physicalAddr; +} page_directory_t; + +// sets up paging +void initialize_paging(void); + +// causes the specified page directory to be loaded +// into the cr3 register +void switch_page_directory(page_directory_t *new); + +// retrieves a pointer to the page requires +// if make == 1, if the pagetable in which page +// should reside inst there create it +page_t *get_page(uint32_t address, int make, page_directory_t *dir); + +// handler for page faults +void page_fault(registers_t *regs); diff --git a/headers/screen.h b/headers/screen.h new file mode 100644 index 0000000..fedf48e --- /dev/null +++ b/headers/screen.h @@ -0,0 +1,45 @@ +#ifndef __SCREEN_H +#define __SCREEN_H +#define VIDEO_ADDRESS 0xb8000 +#define GREEN_ON_BLACK 0x02 + + +#define FB_COMMAND_PORT 0x3D4 +#define FB_DATA_PORT 0x3D5 +// The I/O port commands +#define FB_HIGH_BYTE_COMMAND 14 +#define FB_LOW_BYTE_COMMAND 15 + +enum vga_colors { + BLACK = 0, + BLUE = 1, + GREEN = 2, + CYAN = 3, + RED = 4, + MAGENTA = 5, + BROWN = 6, + LIGHT_GREY = 7, + DARK_GREY = 8, + LIGHT_BLUE = 9, + LIGHT_GREEN = 10, + LIGHT_CYAN = 11, + LIGHT_RED = 12, + LIGHT_MAGENTA = 13, + LIGHT_BROWN = 14, + WHITE = 15, + +}; + +extern void set_current_color(enum vga_colors color); +extern void printchar(const char character); +extern void print(const char string[]); +extern void handle_scrolling(); +extern void clear(); +extern void set_cursor(int row, int col); +extern void itoa(int num, char *string2); +extern void tohex(int val, char *string); +extern void kprintf(const char *string, ...); // simple printf style function +void zerostring(char *string); // zero out a string needs refactor +extern unsigned int strlen(const char *string); +// end funciton prototypes +#endif diff --git a/headers/serial.h b/headers/serial.h new file mode 100644 index 0000000..5f0858d --- /dev/null +++ b/headers/serial.h @@ -0,0 +1,62 @@ +#ifndef __SERIAL_H +#define __SERIAL_H + + +#define SERIAL_COM1_BASE 0x3F8 // com1 base port + + +extern void init_serial(); + + +/** serial_configure_baud_rate: +* Sets the speed of the data being sent. The default speed of a serial +* port is 115200 bits/s. The argument is a divisor of that number, hence +* the resulting speed becomes (115200 / divisor) bits/s. +* +* @param com The COM port to configure +* @param divisor The divisor +*/ + +extern void serial_configure_baud_rate(unsigned short com, unsigned short divisor); + +// configures line of a serial port +extern void serial_configure_line(unsigned short com); + + + +// Bit: | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 | +// Content: | lvl | bs | r | dma | clt | clr | e | +// 0xc7 +/* +*Enables FIFO +*Clear both receiver and transmission FIFO queues +*Use 14 bytes as size of queue +*/ +extern void serial_configure_buffers(unsigned short com); + +// write data into the serial port +extern void serial_write(unsigned short com, char data); + + +// configure the modem of our serial port +// Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +// Content: | r | r | af | lb | ao2 | ao1 | rts | dtr | +extern void serial_configure_modem(unsigned short com); + + +/** serial_is_transmit_fifo_empty: +* Checks whether the transmit FIFO queue is empty or not for the given COM +* port. +* +* @param com The COM port +* @return 0 if the transmit FIFO queue is not empty +* 1 if the transmit FIFO queue is empty +*/ +extern int serial_is_trasmit_fifo_empty(unsigned short com); + + +extern void serial_print(char *string); // print out over a serial port + +// printf style func for serial port +void serial_printf(char *format, ...); +#endif diff --git a/headers/timer.h b/headers/timer.h new file mode 100644 index 0000000..ba0dfff --- /dev/null +++ b/headers/timer.h @@ -0,0 +1,6 @@ +#pragma once + +#include "common.h" +#include + +void init_timer(uint32_t frequency); diff --git a/interrupt.s b/interrupt.s new file mode 100644 index 0000000..1ef3aad --- /dev/null +++ b/interrupt.s @@ -0,0 +1,159 @@ +; +; interrupt.s -- Contains interrupt service routine wrappers. +; Based on Bran's kernel development tutorials. +; Rewritten for JamesM's kernel development tutorials. + +; This macro creates a stub for an ISR which does NOT pass it's own +; error code (adds a dummy errcode byte). +%macro ISR_NOERRCODE 1 + global isr%1 + isr%1: + cli ; Disable interrupts firstly. + push byte 0 ; Push a dummy error code. + push byte %1 ; Push the interrupt number. + jmp isr_common_stub ; Go to our common handler code. +%endmacro + +; This macro creates a stub for an ISR which passes it's own +; error code. +%macro ISR_ERRCODE 1 + global isr%1 + isr%1: + cli ; Disable interrupts. + push byte %1 ; Push the interrupt number + jmp isr_common_stub +%endmacro + +%macro IRQ 2 + global irq%1 + irq%1: + cli + push byte 0 + push byte %2 + jmp irq_common_stub +%endmacro + + + +ISR_NOERRCODE 0 +ISR_NOERRCODE 1 +ISR_NOERRCODE 2 +ISR_NOERRCODE 3 +ISR_NOERRCODE 4 +ISR_NOERRCODE 5 +ISR_NOERRCODE 6 +ISR_NOERRCODE 7 +ISR_ERRCODE 8 +ISR_NOERRCODE 9 +ISR_ERRCODE 10 +ISR_ERRCODE 11 +ISR_ERRCODE 12 +ISR_ERRCODE 13 +ISR_ERRCODE 14 +ISR_NOERRCODE 15 +ISR_NOERRCODE 16 +ISR_ERRCODE 17 +ISR_NOERRCODE 18 +ISR_NOERRCODE 19 +ISR_NOERRCODE 20 +ISR_NOERRCODE 21 +ISR_NOERRCODE 22 +ISR_NOERRCODE 23 +ISR_NOERRCODE 24 +ISR_NOERRCODE 25 +ISR_NOERRCODE 26 +ISR_NOERRCODE 27 +ISR_NOERRCODE 28 +ISR_NOERRCODE 29 +ISR_ERRCODE 30 +ISR_NOERRCODE 31 +IRQ 0, 32 +IRQ 1, 33 +IRQ 2, 34 +IRQ 3, 35 +IRQ 4, 36 +IRQ 5, 37 +IRQ 6, 38 +IRQ 7, 39 +IRQ 8, 40 +IRQ 9, 41 +IRQ 10, 42 +IRQ 11, 43 +IRQ 12, 44 +IRQ 13, 45 +IRQ 14, 46 +IRQ 15, 47 + + + +; In isr.c +extern isr_handler + +; This is our common ISR stub. It saves the processor state, sets +; up for kernel mode segments, calls the C-level fault handler, +; and finally restores the stack frame. +isr_common_stub: + 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 ; load the kernel data segment descriptor + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + push esp ; registers_t *r + + ; c following sys v requires df to be clear on func entry + cld + + call isr_handler + + pop eax ; remove our pointer + pop eax ; reload the original data segment descriptor + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + + popa ; Pops edi,esi,ebp... + add esp, 8 + iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP + + +; In isr.c +extern irq_handler + +; our common irq stub. it saves the processor state, sets +; up for kernel mode segments, calls the c level fault handler +; and finally restores the stack frame. +irq_common_stub: + pusha ; push edi,esi,ebp,esp,ebx,edx,ecx,eax + + mov ax, ds ; lower 16-bits of eax = ds + push eax ; save the segment descriptor + + mov ax, 0x10 ; load the kernel data segment descriptor + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + push esp + cld + + call irq_handler + + + pop ebx ; remove esp + pop ebx ; reload the original ds descriptor different than the isr code + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + + popa + add esp, 8 ;; cleans up the pushed error code and isr number + iret ; pops cs eip eflags ss and esp + diff --git a/iso/boot/grub/menu.lst b/iso/boot/grub/menu.lst new file mode 100644 index 0000000..387d721 --- /dev/null +++ b/iso/boot/grub/menu.lst @@ -0,0 +1,5 @@ +defualt=0 +timeout=0 + +title ProjectRED +kernel /boot/kernel.elf diff --git a/iso/boot/grub/stage2_eltorito b/iso/boot/grub/stage2_eltorito new file mode 100644 index 0000000000000000000000000000000000000000..9e1617cee78bb8f1ab6838837c9a1f36d729c9fd GIT binary patch literal 105522 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 I8%}5UKb>zEqW}N^ literal 0 HcmV?d00001 diff --git a/isr.c b/isr.c new file mode 100644 index 0000000..163a05c --- /dev/null +++ b/isr.c @@ -0,0 +1,50 @@ +// +// isr.c -- High level interrupt service routines and interrupt request handlers. +// Part of this code is modified from Bran's kernel development tutorials. +// Rewritten for JamesM's kernel development tutorials. +// + +#include "headers/common.h" +#include "headers/isr.h" +#include "headers/screen.h" +#include + +isr_t interrupt_handlers[256]; + + +// This gets called from our ASM interrupt handler stub. +void isr_handler(registers_t *regs) +{ + //kprintf("recieved interrupt: %d\n",regs->int_no); + + if(interrupt_handlers[regs->int_no] != 0) + { + isr_t handler = interrupt_handlers[regs->int_no]; + handler(regs); + } +} + +void irq_handler(registers_t *regs) +{ + //kprintf("recieved irq: %d\n",regs->int_no); + // send an EOI signal to pics if this + // interrupt involved the slave + if(regs->int_no >= 40) + { + outb(0xA0,0x20); + } + // send reset siginal to master. (as well as slave, if neccessary). + outb(0x20,0x20); + + if(interrupt_handlers[regs->int_no] != 0) + { + isr_t handler = interrupt_handlers[regs->int_no]; + handler(regs); + } +} + + +void register_interrupt_handler(uint8_t n, isr_t handler) +{ + interrupt_handlers[n] = handler; +} diff --git a/keyboard.c b/keyboard.c new file mode 100644 index 0000000..0f6075e --- /dev/null +++ b/keyboard.c @@ -0,0 +1,48 @@ +#include "headers/isr.h" +#include "headers/common.h" +#include "headers/screen.h" +#include +#include +// make an array for the keystate +// aswell as a struct for the state of toggle keys +// and other ones such as shift ctrl etc + +// add polling of status bits to recieve input properly +extern uint8_t current_color; +static void keyboard_callback(registers_t *regs) +{ + regs->eax++; // lul + uint8_t scancode = inb(0x60); // read from the 8042 PS/2 data port + + static int i = 0; + static int j = 0; + j = j%15; + current_color = j++; + kprintf("keyboard event 0x%x : %d\n",scancode,scancode); + i++; + + + // check for key presses like shift + // else use a lookup table to print it dependant on the state + + +} + + + +void init_keyboard(void) +{ + // register our keyboard callback + register_interrupt_handler(IRQ1,&keyboard_callback); + + // poll until buffer is empty + bool full = true; + while(full) + { + full = inb(0x64) & 1; // poll bit 1 of status reg + } + + // send a scancode change request + outb(0x60,0xF0); //scan code change + outb(0x60,0x2); // set 2 +} diff --git a/kheap.c b/kheap.c new file mode 100644 index 0000000..37faec3 --- /dev/null +++ b/kheap.c @@ -0,0 +1,40 @@ +#include "headers/kheap.h" +#include +#include + +// end is defined in the linker +extern uint32_t end; +uint32_t placement_address = (uint32_t)&end; + +uint32_t kmalloc(size_t sz, int align, uint32_t *phys) +{ + if( align == 1 && (placement_address & 0xFFFFF000)) // if not page aligned + { + placement_address &= 0xFFFFF000; + placement_address += 0x1000; + } + if(phys) + { + *phys = placement_address; + } + uint32_t tmp = placement_address; + placement_address += sz; + return tmp; +} + + + +uint32_t kmalloc_a(size_t sz) +{ + return kmalloc(sz,1,0); +} + +uint32_t kmalloc_p(size_t sz, uint32_t *phys) +{ + return kmalloc(sz,0,phys); +} + +uint32_t kmalloc_ap(size_t sz, uint32_t *phys) +{ + return kmalloc(sz,1,phys); +} diff --git a/link.ld b/link.ld new file mode 100644 index 0000000..bbb2ee0 --- /dev/null +++ b/link.ld @@ -0,0 +1,33 @@ +/* Link.ld -- Linker script for the kernel - ensure everything goes in the */ +/* Correct place. */ +/* Original file taken from Bran's Kernel Development */ +/* tutorials: http://www.osdever.net/bkerndev/index.php. */ + +ENTRY(start) +SECTIONS +{ + + .text 0x100000 : + { + code = .; _code = .; __code = .; + *(.text) + . = ALIGN(4096); + } + + .data : + { + data = .; _data = .; __data = .; + *(.data) + *(.rodata) + . = ALIGN(4096); + } + + .bss : + { + bss = .; _bss = .; __bss = .; + *(.bss) + . = ALIGN(4096); + } + + end = .; _end = .; __end = .; +} diff --git a/main.c b/main.c new file mode 100644 index 0000000..4e3160b --- /dev/null +++ b/main.c @@ -0,0 +1,71 @@ +// add something that instructs the compiler to ignore unused vars +// for ones that we aernt using yet +#include "headers/descriptor_tables.h" +#include "headers/screen.h" +#include "headers/serial.h" +#include "headers/timer.h" +#include "headers/keyboard.h" +#include "headers/paging.h" +#include +#include + + +void gpt(registers_t *regs) +{ + regs->eax++; + PANIC("General protection fault"); +} + +extern uint8_t current_color; +int kernel_main(void) +{ + register_interrupt_handler(13, &gpt); + + //Prepare the screen, and make sure the colors are set proper. + clear(); + set_current_color(WHITE); + + print("Intializing kernel...\n"); + + //Initialize and test the serial port. + init_serial(); + serial_print("Serial logging started!\n"); + print("Serial Initialized.\n"); + + // Initialise all the ISRs and segmentation + init_descriptor_tables(); + print("Descriptor tables Initialized.\n"); + + //Prepare paging. TODO: Finish Michael's implementation + //initialize_paging(); + print("Paging ready.\n"); + + //Get the timer ready + //init_timer(0); + + //Prepare the keyboard for taking input.# + asm("sti"); + init_keyboard(); //<--- come back to after basic memory allocation + print("Keyboard ready.\n"); + + //This should cause a page fault and thus a panic. + + volatile uint32_t *ptr = (uint32_t*)NULL; + kprintf("(0x%x)*ptr = %x\n",ptr,&ptr); + + //Print our fancy colored message. + set_current_color(GREEN); + print("(c)"); + set_current_color(WHITE); + print("Project"); + set_current_color(RED); + print("RED"); + set_current_color(WHITE); + print(", 2019\n"); + + + + //Hang. + for(;;) {} + return 0; +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..ea2d8a6 --- /dev/null +++ b/makefile @@ -0,0 +1,52 @@ +C_SOURCES = $(wildcard *.c) +ASM_SOURCES = $(wildcard *.s) +HEADERS = $(wildcard headers/*.h) +#OBJECTS = loader.o ${C_SOURCES:.c=.o} +#OBJECTS = ${C_SOURCES:.c=.o ASM_SOURCES:.s=.o} +OBJECTS = ${ASM_SOURCES:.s=.o} ${C_SOURCES:.c=.o} +#OBJECTS = ${wildcard *.o} +CC = i686-elf-gcc +CFLAGS = -masm=intel -m32 -ffreestanding -fno-builtin -fno-stack-protector \ + -isystem=/usr/include -Wall -Wextra -Werror -c +LDFLAGS = -T link.ld -melf_i386 +AS = nasm +ASFLAGS = -f elf32 + +everything: all + rm -f os.iso + cp kernel.elf iso/boot/kernel.elf + genisoimage -R \ + -b boot/grub/stage2_eltorito \ + -no-emul-boot \ + -A os \ + -input-charset utf8 \ + -quiet \ + -boot-info-table \ + -o os.iso \ + iso + +all: kernel.elf + +kernel.elf: $(OBJECTS) + i686-elf-ld $(LDFLAGS) $(OBJECTS) -o kernel.elf + +os.iso: kernel.elf + cp kernel.elf iso/boot/kernel.elf + genisoimage -R \ + -b boot/grub/stage2_eltorito \ + -no-emul-boot \ + -A os \ + -input-charset utf8 \ + -quiet \ + -boot-info-table \ + -o os.iso \ + iso + +%.o: %.c ${HEADERS} + $(CC) $(CFLAGS) $< -o $@ +%.o: %.s + $(AS) $(ASFLAGS) $< -o $@ + + +clean: + rm *.o diff --git a/paging.c b/paging.c new file mode 100644 index 0000000..7713690 --- /dev/null +++ b/paging.c @@ -0,0 +1,218 @@ + #include "headers/paging.h" +#include "headers/kheap.h" +#include "headers/screen.h" +#include "headers/serial.h" +#include + +// The kernel's page directory +page_directory_t *kernel_directory=0; + +// The current page directory; +page_directory_t *current_directory=0; + + +// a bitset of frames - used or free +uint32_t *frames; +uint32_t nframes; + +// defined in kheap.c +extern uint32_t placement_address; + +// Macros for bitset algo's +#define INDEX_FROM_BIT(a) (a/(8*4)) +#define OFFSET_FROM_BIT(a) (a%(8*4)) + +// set a bit in the frames bitset +static void set_frame(uint32_t frame_addr) +{ + uint32_t frame = frame_addr/0x1000; + uint32_t idx = INDEX_FROM_BIT(frame); + uint32_t off = OFFSET_FROM_BIT(frame); + frames[idx] &= ~(0x1 << off); +} + +// clear frame in a bitset +static void clear_frame(uint32_t frame_addr) +{ + uint32_t frame = frame_addr/0x1000; + uint32_t idx = INDEX_FROM_BIT(frame); + uint32_t off = OFFSET_FROM_BIT(frame); + frames[idx] &= ~(0x1 << off); +} + +/*// test if a bit is set +static u32int test_frame(uint32_t frame_addr) +{ + uint32_t frame = frame_addr/0x1000; + uint32_t idx = INDEX_FROM_BIT(frame); + uint32_t off = OFFSET_FROM_BIT(frame); + return (frames[idx] & (0x1 << off)); +} */ + +// find the first free frame +static uint32_t first_frame() +{ + uint32_t i, j; + for(i = 0; i < INDEX_FROM_BIT(nframes); i++) + { + if(frames[i] != 0xFFFFFFFF) // nothing free exit early + { + // atleast one bit is free + for(j = 0; j < 32; j++) + { + uint32_t toTest = 0x1 << j; + if(!(frames[i]&toTest)) + { + return i*4*8+j; + } + } + } + } + return -1; +} + +// alloc a frame +void alloc_frame(page_t *page, int is_kernel, int is_writeable) +{ + if(page->frame != 0) + { + return; // frame allready alloced + } + else + { + uint32_t idx = first_frame(); // idx now index for first free frame + if(idx == (uint32_t)-1) + { + PANIC("No free frames!"); + } + set_frame(idx*0x1000); // frame is now ours + page->present = 1; // mark as present + page->rw = (is_writeable)?1:0; // should page be w + page->user = (is_kernel)?0:1; // should page be user-mode + page->frame = idx; + } +} + +// function to dealloc a frame +void free_frame(page_t * page) +{ + uint32_t frame; + if(!(frame=page->frame)) + { + return; // page didnt actually have a alloced frame + } + else + { + clear_frame(frame); // free the frame + page->frame = 0x0; // page has no frame + } +} + +void initialize_paging() +{ + //size of physcial mem for now we assume it is 16MB + uint32_t mem_end_page = 0x1000000; + + nframes = mem_end_page / 0x1000; + frames = (uint32_t*)kmalloc(INDEX_FROM_BIT(nframes),0,0); + memset(frames,0,INDEX_FROM_BIT(nframes)); + + // lets make a page directory <--- cont here + kernel_directory = (page_directory_t*)kmalloc_a(sizeof(page_directory_t)); + memset(kernel_directory, 0, sizeof(page_directory_t)); + current_directory = kernel_directory; + + + // my need to identify map (phys addr = virt addr from + // 0x0 to end of used mem so this can be accessed transparently + // as if paging isnt enabled + uint32_t i = 0; + while(i < placement_address) + { + // read but not writeable from user space + alloc_frame(get_page(i,1,kernel_directory),0,0); + i += 0x1000; + } + + // before we enable paging set our page fault handler + register_interrupt_handler(14, &page_fault); + + + // enable paging! <-- should have a seperate function + // for the first time this is done and not constantly renable it + switch_page_directory(kernel_directory); + print("Paging enabled!\n"); +} + + +void switch_page_directory(page_directory_t *dir) +{ + current_directory = dir; + asm volatile("mov cr3, %0":: "r"(&dir->tablesPhysical)); + uint32_t cr0; + asm volatile("mov %0, cr0": "=r"(cr0)); + serial_printf("cr0 = %x\n",cr0); + cr0 |= 0x80000000; // Enable paging! + asm volatile("mov cr0, %0":: "r"(cr0)); +} + + +page_t *get_page(uint32_t address, int make, page_directory_t *dir) +{ + // turn address into an index + address /= 0x1000; + + // find the page table that contains this address + uint32_t table_idx = address / 1024; + if(dir->tables[table_idx]) // if this page is allready assigned + { + return &dir->tables[table_idx]->pages[address%1024]; + } + else if(make) + { + uint32_t tmp; + dir->tables[table_idx] = (page_table_t*)kmalloc_ap(sizeof(page_table_t), &tmp); + memset(dir->tables[table_idx], 0, 0x1000); + dir->tablesPhysical[table_idx] = tmp | 0x7; // PRESENT, RW, US. + return &dir->tables[table_idx]->pages[address%1024]; + } + else + { + return 0; + } +} + + +// fix issue with page fault passing see +// Problem: Interrupt handlers corrupt interrupted state +// & Problem 3: regs var must called by reference instead of by value in the irq and isr handlers +void page_fault(registers_t *regs) +{ + // a page fault has occured + // faulting address is in cr2 + uint32_t faulting_address; + asm volatile("mov %0, cr2" : "=r" (faulting_address)); + + // the error codes gives us details of what happened + int present = !(regs->err_code & 0x1); // Page not present + int rw = regs->err_code & 0x2; // Write operation? + int us = regs->err_code & 0x4; // Processor was in user-mode? + int reserved = regs->err_code & 0x8; // Overwritten CPU-reserved bits of page entry? + int id = regs->err_code & 0x10; // Caused by an instruction fetch? + + + // output an error message + print("Page fault! ( "); + if(present) { print("present "); } + else if(rw) { print("read-only "); } + else if(us) { print("user-mode "); } + else if(id) { print("instr-fetch "); } + else if(reserved) {print("reserved "); } + kprintf(") at 0x%x\n",faulting_address); + PANIC("Page fault"); +} + + + + + diff --git a/screen.c b/screen.c new file mode 100644 index 0000000..161286f --- /dev/null +++ b/screen.c @@ -0,0 +1,277 @@ +#include "headers/screen.h" +#include "headers/common.h" + +#include +#include +#include +#include + +volatile uint16_t* vga_buffer = (uint16_t*) 0xB8000; + +uint8_t current_color; + +void set_current_color(enum vga_colors color) { + current_color = color; +} + +/* Entries of the VGA Buffer take the form BBBBFFFFCCCCCCCC + * BBBB = Background Color + * FFFF = Foreground Color + * CCCCCCCC = Character code +*/ + +//Get the 8 color bits from the above enumerator +static inline uint8_t vga_entry_color(enum vga_colors fg, enum vga_colors bg) { + return fg | bg << 4; //Shift the background to the left, then OR with fg to add them together. +} +//Apply the color bits to make a printed character +static inline uint16_t vga_entry(unsigned char uc, uint8_t color) { + return (uint16_t) uc | (uint16_t) color << 8; //Same here - shift the color to the left, then OR with the character to append. +} + +int i; // offset to framebuffer +uint8_t row; // current row between 1-25 +uint8_t col; // current col between 1-80 +short calc = 0; // random var for various calcs must be initialized or cleaned after func calls +// ^ attempt at packing may need calc to be an int however... + +// simple printf style function(needs testing with errenous data) +// add tab support and %p specifier +// also add support for printing the register state +void kprintf(const char *string, ...) // look up how this is properly done +{ + int j = 0; + int num = 0; // number to hold our stuff :) + char string2[11] = {0}; // string for printing ints :) + + /* list stuff :) */ + va_list ap; + // find how many things we are parsing + + va_start(ap, string); + + while(string[j] != 0) + { + if(string[j] == '%') // if a format specifier + { + if(string[j+1] == 'd') // print a decimal + { + num = va_arg(ap, int); + itoa(num, string2); // account for negatives... + print(string2); + zerostring(string2); + j += 2; + } + + else if(string[j+1] == 'x') // print as hex + { + num = va_arg(ap, int); + tohex(num, string2); + print(string2); + zerostring(string2); + j += 2; + } + + else if(string[j+1] == 's') // print a string + { + print(va_arg(ap, char*)); + j += 2; + } + + else if(string[j+1] == 'c') // print a char + { + char c = va_arg(ap,int); + printchar(c); + j += 2; + } + + else // unknown format string + { + //serial_printf("[ERROR]attempted to parse unknown format string\n"); + return; // prevent undefined behaviour + } + } + + else // is a regular character + { + printchar(string[j]); + j++; + } + } + va_end(ap); // clean up variable length list +} + +void printchar(char character) { + + switch (character) { + case '\n': { //For newline, move to next row and focus on the first character. + col = 0; + row++; + break; + } + default: { // All other characters simply get written to the buffer. + const size_t i = (row * 80) + col; //i = index + vga_buffer[i] = ((uint16_t) current_color << 8) | character; + col++; + } + } + + if(col > 79) // keep track of row and column for scrolling and newlines + { + col = 0; + row++; + } + + if(row > 25) + { + handle_scrolling(); + } +} + + +void print(const char *string) { + unsigned int len = strlen(string); // may be better to pass len as an arg + unsigned int j = 0; + while(j < len) + { + printchar(string[j]); + j++; + } +} + +// Fill screen with spaces, effectively clearing the screen. +void clear() { + unsigned char *vga_memory = (unsigned char *) vga_buffer; + for(int j = 0; j < 4001; j = j + 2) // 80 * 25 * 2 =4000(end of framebuffer) + { + vga_memory[j] = 0x20; // assign a space to current pos + vga_memory[j + 1] = 0x00; // clear the attribute + } + row = 0; + col = 0; + i = 0; + +} + + // note learn how inline asm works properly +void set_cursor(int row, int col) { + unsigned short position=(row*80) + col; + outb(0x3D4, 0X0F); + outb(0x3D5, (unsigned char)(position&0xff)); + outb(0x3D4, 0x0E); + outb(0x3D5, (unsigned char)((position>>8)&0xFF)); +} + + +// scroll the screen when it gets to the last line +void handle_scrolling(){ + int x = 160; // start of 2nd line + + unsigned char *vga_memory = (unsigned char *) vga_buffer; + + + + while(x < 4001) { // max each char is 2 apart + vga_memory[x-160] = vga_memory[x]; //Move every character up a line + vga_memory[x-159] = vga_memory[x+1]; //Move the character attributes too + x += 2; //Move to next character + } + x = 3840; //Start of the last line + + for(i = 1920; i < 2001; i++) { // Memory locations for the characters in the last line + vga_memory[x] = 0; //Set current character to 0 (null) + x += 2; // Move to next character + } + + i = 3840; // set i to start of last line + row = 24; // set row to 25 so when a \n is printed the screen will scroll +} + + +void itoa(int num, char *string2) {// convert from int to ascii eqiv + + int j = 0; // counter + calc = 0; //Result of a calculation + bool flag_negative = false; + + + if(num == 0) { // catch 0 + string2[0] = '0'; + string2[1] = '\0'; // null term + return; + } + + if(num < 0) { + flag_negative = true; + num = -num; //invert it so it calcs + } + + while(num != 0) {// while we can still divide + + calc = num % 10; // get raminder in calc + num = num / 10; // div by 10 + calc += 48; // convert to ascii + //printchar(calc); + string2[j] = calc; + j++; // inc amount of chars to print + + } + if(flag_negative) { + string2[j] = '-'; + j += 1; + } + + for(int i=0; i> 4; // shr val four so that it loses char + if(temp >= 10) { + temp += 7; // if higher than 10 convert to letter + } + temp += 48; //convert to ascii + string[j] = temp; + } +} + +// Fill the string pointer with 0 (null) characters +void zerostring(char *string) { + unsigned int len = strlen(string); + for(unsigned int i = 0; i <= len; i++) + { + string[i] = '\0'; + } +} + + +unsigned int strlen(const char *string) { + unsigned int len = 0; + while(string[len]) + { + len++; + } + + return len; +} + +// print out registers for debugging +/*void writeregs(void) +{ + unsigned int Register = 0; + + + + +}*/ diff --git a/serial.c b/serial.c new file mode 100644 index 0000000..137b557 --- /dev/null +++ b/serial.c @@ -0,0 +1,171 @@ +#include "headers/common.h" +#include "headers/screen.h" // for conversion functions(maybye put in seperate header) +#include + +#define SERIAL_DATA_PORT(base) (base) +#define SERIAL_FIFO_COMMAND_PORT(base) (base + 2) +#define SERIAL_LINE_COMMAND_PORT(base) (base + 3) +#define SERIAL_MODEM_COMMAND_PORT(base) (base + 4) +#define SERIAL_LINE_STATUS_PORT(base) (base + 5) +#define SERIAL_COM1_BASE 0x3F8 // com1 base port + +// SERIAL_LINE_ENABLE_DLAB: +// tells the serial port to expect high byte first +// then the low order byte follows + +#define SERIAL_LINE_ENABLE_DLAB 0x80 + + +/** serial_configure_baud_rate: +* Sets the speed of the data being sent. The default speed of a serial +* port is 115200 bits/s. The argument is a divisor of that number, hence +* the resulting speed becomes (115200 / divisor) bits/s. +* +* @param com The COM port to configure +* @param divisor The divisor +*/ + +void serial_configure_baud_rate(unsigned short com, unsigned short divisor) +{ + outb(SERIAL_LINE_COMMAND_PORT(com), + SERIAL_LINE_ENABLE_DLAB); + outb(SERIAL_DATA_PORT(com), + (divisor >> 8) & 0x00ff); + outb(SERIAL_DATA_PORT(com), + divisor & 0x00ff); +} + +// configures line of a serial port +void serial_configure_line(unsigned short com) +{ + /* Bit: | 7 | 6 | 5 4 3 | 2 | 1 0 | + * Content: | d | b | prty | s | dl | + * Value: | 0 | 0 | 0 0 0 | 0 | 1 1 | = 0x03 + */ + outb(SERIAL_LINE_COMMAND_PORT(com), 0x03); + +} + + + +// Bit: | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 | +// Content: | lvl | bs | r | dma | clt | clr | e | +// 0xc7 +/* +*Enables FIFO +*Clear both receiver and transmission FIFO queues +*Use 14 bytes as size of queue +*/ +void serial_configure_buffers(unsigned short com) +{ + outb(SERIAL_FIFO_COMMAND_PORT(com), 0xc7); + +} + +// configure the modem of our serial port +// Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | +// Content: | r | r | af | lb | ao2 | ao1 | rts | dtr | +void serial_configure_modem(unsigned short com) +{ + outb(SERIAL_MODEM_COMMAND_PORT(com), 0x3); + +} + +/** serial_is_transmit_fifo_empty: +* Checks whether the transmit FIFO queue is empty or not for the given COM +* port. +* +* @param com The COM port +* @return 0 if the transmit FIFO queue is not empty +* 1 if the transmit FIFO queue is empty +*/ +int serial_is_trasmit_fifo_empty(unsigned short com) +{ + return inb(SERIAL_LINE_STATUS_PORT(com)) & 0x20; +} + +void serial_write(unsigned short com, char data) +{ + + while(serial_is_trasmit_fifo_empty(com) == 0); // wait till write finished + outb(com, data); +} + + +void serial_print(char *string) +{ + int j = 0; + while(string[j] != 0) + { + serial_write(0x3f8, string[j]); + j++; + } +} + +void serial_printf(const char *format, ...) // printf style func for serial port +{ + int j = 0; + int num; // number to hold our stuff :) + char string2[10] = {0}; // string for printing ints :) + /* list stuff :) */ + va_list ap; + va_start(ap, format); + + + while(format[j] != 0) + { + if(format[j] == '%') // if a format specifier + { + if(format[j+1] == 'd') + { + num = va_arg(ap, int); + itoa(num, string2); + serial_print(string2); + zerostring(string2); + j += 2; + } + + else if(format[j+1] == 'x') + { + num = va_arg(ap, int); + tohex(num, string2); + serial_print(string2); + zerostring(string2); + j += 2; + } + + else if(format[j+1] == 's') + { + serial_print(va_arg(ap, char* )); + j += 2; + } + + else // unknown format string + { + serial_print("[ERROR]attempted to parse unknown format string\n"); + return; // prevent undefined behaviour + } + } + + else // is a regular character + { + serial_write(0x3f8,format[j]); + j++; + } + } + va_end(ap); // clean up variable length list +} + +// init our serial com1 port for debuggin purpoes(rest are general use functions for other ports) +#define PORT 0x3f8 /* COM1 */ + +void init_serial() +{ + outb(PORT + 1, 0x00); // Disable all interrupts + outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) + outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud + outb(PORT + 1, 0x00); // (hi byte) + outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit + outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold + outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set +} diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..9346269 --- /dev/null +++ b/timer.c @@ -0,0 +1,39 @@ +#include "headers/timer.h" +#include "headers/isr.h" +#include "headers/screen.h" +#include + +uint32_t tick = 0; +extern uint8_t current_color; +static void timer_callback(registers_t *regs) +{ + regs->eax++; // get it to compile fix this + // later(regs is not really needed) + tick++; + static int j = 0; + j = j%15; + current_color = j++; + kprintf("%d\n", tick); +} + +void init_timer(uint32_t frequency) +{ + // firstly register our timer callback + register_interrupt_handler(IRQ0, &timer_callback); + + // the vaue sent to the pit is its clock divisor + // (1193180 Hz) the divisor must be small enough + // to fit into 16 bits + uint32_t divisor = 1193180 / frequency; + + // send the command byte + outb(0x43, 0x36); + + // divsor is set in bytes so serialize it + uint8_t l = (uint8_t)(divisor & 0xff); + uint8_t h = (uint8_t)( (divisor>>8) & 0xff); + + // send the freq divisor + outb(0x40, l); + outb(0x40, h); +}