commit af15d43c8a8b0ce62980d2a643a2d37dd7d589d2 Author: Garritt McCune Date: Sun Nov 11 14:19:19 2018 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a079651 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +obj/ +iso/ \ No newline at end of file diff --git a/include/gdt.h b/include/gdt.h new file mode 100644 index 0000000..324838d --- /dev/null +++ b/include/gdt.h @@ -0,0 +1,7 @@ +#ifndef __GDT_H +#define __GDT_H + +void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran); +void gdt_install(); + +#endif \ No newline at end of file diff --git a/include/idt.h b/include/idt.h new file mode 100644 index 0000000..6185a86 --- /dev/null +++ b/include/idt.h @@ -0,0 +1,7 @@ +#ifndef __IDT_H +#define __IDT_H + +void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags); +void idt_install(); + +#endif \ No newline at end of file diff --git a/include/scrn.h b/include/scrn.h new file mode 100644 index 0000000..7973238 --- /dev/null +++ b/include/scrn.h @@ -0,0 +1,10 @@ +#ifndef __SCRN_H +#define __SCRN_H + +extern void cls(); +extern void putch(unsigned char c); +extern void puts(unsigned char *str); +extern void settextcolor(unsigned char forecolor, unsigned char backcolor); +extern void init_video(); + +#endif \ No newline at end of file diff --git a/include/system.h b/include/system.h new file mode 100644 index 0000000..0e513f9 --- /dev/null +++ b/include/system.h @@ -0,0 +1,30 @@ +#ifndef __SYSTEM_H +#define __SYSTEM_H + +/* MAIN.C */ +extern unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count); +extern unsigned char *memset(unsigned char *dest, unsigned char val, int count); +extern unsigned short *memsetw(unsigned short *dest, unsigned short val, int count); +extern int strlen(const char *str); +extern unsigned char inportb (unsigned short _port); +extern void outportb (unsigned short _port, unsigned char _data); +/* ISRS.C */ +void isrs_install(); +/* This defines what the stack looks like after an ISR was running */ +struct regs +{ + unsigned int gs, fs, es, ds; /* pushed the segs last */ + unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax; /* pushed by 'pusha' */ + unsigned int int_no, err_code; /* our 'push byte #' and ecodes do this */ + unsigned int eip, cs, eflags, useresp, ss; /* pushed by the processor automatically */ +}; +/* IRQ.C */ +void irq_install_handler(int irq, void (*handler)(struct regs *r)); +void irq_uninstall_handler(int irq); +void irq_install(); +/* TIMER.C */ +void timer_handler(struct regs *r); +void timer_install(); +/* KB.C */ +void kb_install(); +#endif \ No newline at end of file diff --git a/linker.ld b/linker.ld new file mode 100644 index 0000000..1699422 --- /dev/null +++ b/linker.ld @@ -0,0 +1,25 @@ +OUTPUT_FORMAT("binary") +ENTRY(start) +phys = 0x00100000; +SECTIONS +{ + .text phys : AT(phys) { + code = .; + *(.text) + *(.rodata) + . = ALIGN(4096); + } + .data : AT(phys + (data - code)) + { + data = .; + *(.data) + . = ALIGN(4096); + } + .bss : AT(phys + (bss - code)) + { + bss = .; + *(.bss) + . = ALIGN(4096); + } + end = .; +} \ No newline at end of file diff --git a/makefile b/makefile new file mode 100644 index 0000000..e9dac62 --- /dev/null +++ b/makefile @@ -0,0 +1,48 @@ +GCCPARAMS = -m32 -fno-pie -nostdlib -fstrength-reduce -fomit-frame-pointer -finline-functions -fno-builtin -nostdinc +ASPARAMS = -f elf32 +LDPARAMS = -m elf_i386 + +#BUILD_NUMBER_FILE=build-number.txt + +objects = obj/start.o \ + obj/main.o \ + obj/scrn.o \ + obj/gdt.o \ + obj/idt.o \ + obj/isrs.o \ + obj/irq.o \ + obj/timer.o \ + obj/kb.o + +run: kernel.iso + qemu-system-x86_64 -cdrom kernel.iso & + +obj/%.o: src/%.c + mkdir -p $(@D) + gcc $(GCCPARAMS) -c -o $@ $< + +obj/%.o: src/%.asm + mkdir -p $(@D) + nasm $(ASPARAMS) -o $@ $< + +kernel.bin: linker.ld $(objects) + mkdir obj/bin + ld $(LDPARAMS) -T $< -o obj/bin/$@ $(objects) +#https://www.linuxjournal.com/content/add-auto-incrementing-build-number-your-build-process +#include buildnumber.mak + +kernel.iso: kernel.bin + mkdir iso + mkdir iso/boot + mkdir iso/boot/grub + cp obj/bin/kernel.bin iso/boot/kernel.bin + echo 'menuentry SDOS {' > iso/boot/grub/grub.cfg + echo ' multiboot /boot/kernel.bin' >> iso/boot/grub/grub.cfg + echo ' boot' >> iso/boot/grub/grub.cfg + echo '}' >> iso/boot/grub/grub.cfg + grub-mkrescue --output=kernel.iso iso + rm -rf iso + +.PHONY: clean +clean: + rm -rf obj kernel.iso \ No newline at end of file diff --git a/readme b/readme new file mode 100644 index 0000000..41722bc --- /dev/null +++ b/readme @@ -0,0 +1,10 @@ +SDOS +==== + +SDOS or Slow and Dirty Operating System is a hobby +operating system written purely for the experience. +The name comes from QDOS, or Quick and Dirty Operating +System that ws the core of what would become MS-DOS. +Nothing about this project is fast but the code will +be quite dirty as I learn more about writing an OS. +Therefore, slow and dirty perfectly describes this project. \ No newline at end of file diff --git a/src/gdt.c b/src/gdt.c new file mode 100644 index 0000000..ed67935 --- /dev/null +++ b/src/gdt.c @@ -0,0 +1,76 @@ +#include "../include/system.h" + +/* Defines a GDT entry. We say packed, because it prevents the +* compiler from doing things that it thinks is best: Prevent +* compiler "optimization" by packing */ +struct gdt_entry +{ + unsigned short limit_low; + unsigned short base_low; + unsigned char base_middle; + unsigned char access; + unsigned char granularity; + unsigned char base_high; +} __attribute__((packed)); + +/* Special pointer which includes the limit: The max bytes +* taken up by the GDT, minus 1. Again, this NEEDS to be packed */ +struct gdt_ptr +{ + unsigned short limit; + unsigned int base; +} __attribute__((packed)); + +/* Our GDT, with 3 entries, and finally our special GDT pointer */ +struct gdt_entry gdt[3]; +struct gdt_ptr gp; + +/* This will be a function in start.asm. We use this to properly +* reload the new segment registers */ +extern void gdt_flush(); +/* Setup a descriptor in the Global Descriptor Table */ +void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran) +{ + /* Setup the descriptor base address */ + gdt[num].base_low = (base & 0xFFFF); + gdt[num].base_middle = (base >> 16) & 0xFF; + gdt[num].base_high = (base >> 24) & 0xFF; + + /* Setup the descriptor limits */ + gdt[num].limit_low = (limit & 0xFFFF); + gdt[num].granularity = ((limit >> 16) & 0x0F); + + /* Finally, set up the granularity and access flags */ + gdt[num].granularity |= (gran & 0xF0); + gdt[num].access = access; +} + +/* Should be called by main. This will setup the special GDT +* pointer, set up the first 3 entries in our GDT, and then +* finally call gdt_flush() in our assembler file in order +* to tell the processor where the new GDT is and update the +* new segment registers */ +void gdt_install() +{ + /* Setup the GDT pointer and limit */ + gp.limit = (sizeof(struct gdt_entry) * 3) - 1; + gp.base = &gdt; + + /* Our NULL descriptor */ + gdt_set_gate(0, 0, 0, 0, 0); + + /* The second entry is our Code Segment. The base address + * is 0, the limit is 4GBytes, it uses 4KByte granularity, + * uses 32-bit opcodes, and is a Code Segment descriptor. + * Please check the table above in the tutorial in order + * to see exactly what each value means */ + gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); + + /* The third entry is our Data Segment. It's EXACTLY the + * same as our code segment, but the descriptor type in + * this entry's access byte says it's a Data Segment */ + gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); + + /* Flush out the old GDT and install the new changes! */ + gdt_flush(); +} \ No newline at end of file diff --git a/src/idt.c b/src/idt.c new file mode 100644 index 0000000..f4ba965 --- /dev/null +++ b/src/idt.c @@ -0,0 +1,59 @@ +#include "../include/system.h" + +/* Defines an IDT entry */ +struct idt_entry +{ + unsigned short base_lo; + unsigned short sel; /* Our kernel segment goes here! */ + unsigned char always0; /* This will ALWAYS be set to 0! */ + unsigned char flags; /* Set using the above table! */ + unsigned short base_hi; +} __attribute__((packed)); + +struct idt_ptr +{ + unsigned short limit; + unsigned int base; +} __attribute__((packed)); + +/* Declare an IDT of 256 entries. Although we will only use the +* first 32 entries in this tutorial, the rest exists as a bit +* of a trap. If any undefined IDT entry is hit, it normally +* will cause an "Unhandled Interrupt" exception. Any descriptor +* for which the 'presence' bit is cleared (0) will generate an +* "Unhandled Interrupt" exception */ +struct idt_entry idt[256]; +struct idt_ptr idtp; + +/* This exists in 'start.asm', and is used to load our IDT */ +extern void idt_load(); +/* Use this function to set an entry in the IDT. Alot simpler +* than twiddling with the GDT ;) */ +void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags) +{ + /* The interrupt routine's base address */ + idt[num].base_lo = (base & 0xFFFF); + idt[num].base_hi = (base >> 16) & 0xFFFF; + + /* The segment or 'selector' that this IDT entry will use + * is set here, along with any access flags */ + idt[num].sel = sel; + idt[num].always0 = 0; + idt[num].flags = flags; +} + +/* Installs the IDT */ +void idt_install() +{ + /* Sets the special IDT pointer up, just like in 'gdt.c' */ + idtp.limit = (sizeof (struct idt_entry) * 256) - 1; + idtp.base = &idt; + + /* Clear out the entire IDT, initializing it to zeros */ + memset(&idt, 0, sizeof(struct idt_entry) * 256); + + /* Add any new ISRs to the IDT here using idt_set_gate */ + + /* Points the processor's internal register to the new IDT */ + idt_load(); +} \ No newline at end of file diff --git a/src/irq.c b/src/irq.c new file mode 100644 index 0000000..ffa8695 --- /dev/null +++ b/src/irq.c @@ -0,0 +1,123 @@ +#include "../include/system.h" + +/* These are own ISRs that point to our special IRQ handler +* instead of the regular 'fault_handler' function */ +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(); + +/* This array is actually an array of function pointers. We use +* this to handle custom IRQ handlers for a given IRQ */ +void *irq_routines[16] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* This installs a custom IRQ handler for the given IRQ */ +void irq_install_handler(int irq, void (*handler)(struct regs *r)) +{ + irq_routines[irq] = handler; +} + +/* This clears the handler for a given IRQ */ +void irq_uninstall_handler(int irq) +{ + irq_routines[irq] = 0; +} + +/* Normally, IRQs 0 to 7 are mapped to entries 8 to 15. This +* is a problem in protected mode, because IDT entry 8 is a +* Double Fault! Without remapping, every time IRQ0 fires, +* you get a Double Fault Exception, which is NOT actually +* what's happening. We send commands to the Programmable +* Interrupt Controller (PICs - also called the 8259's) in +* order to make IRQ0 to 15 be remapped to IDT entries 32 to +* 47 */ +void irq_remap(void) +{ + outportb(0x20, 0x11); + outportb(0xA0, 0x11); + outportb(0x21, 0x20); + outportb(0xA1, 0x28); + outportb(0x21, 0x04); + outportb(0xA1, 0x02); + outportb(0x21, 0x01); + outportb(0xA1, 0x01); + outportb(0x21, 0x0); + outportb(0xA1, 0x0); +} + +/* We first remap the interrupt controllers, and then we install +* the appropriate ISRs to the correct entries in the IDT. This +* is just like installing the exception handlers */ +void irq_install() +{ + irq_remap(); + + idt_set_gate(32, (unsigned)irq0, 0x08, 0x8E); + idt_set_gate(33, (unsigned)irq1, 0x08, 0x8E); + idt_set_gate(34, (unsigned)irq2, 0x08, 0x8E); + idt_set_gate(35, (unsigned)irq3, 0x08, 0x8E); + idt_set_gate(36, (unsigned)irq4, 0x08, 0x8E); + idt_set_gate(37, (unsigned)irq5, 0x08, 0x8E); + idt_set_gate(38, (unsigned)irq6, 0x08, 0x8E); + idt_set_gate(39, (unsigned)irq7, 0x08, 0x8E); + idt_set_gate(40, (unsigned)irq8, 0x08, 0x8E); + idt_set_gate(41, (unsigned)irq9, 0x08, 0x8E); + idt_set_gate(42, (unsigned)irq10, 0x08, 0x8E); + idt_set_gate(43, (unsigned)irq11, 0x08, 0x8E); + idt_set_gate(44, (unsigned)irq12, 0x08, 0x8E); + idt_set_gate(45, (unsigned)irq13, 0x08, 0x8E); + idt_set_gate(46, (unsigned)irq14, 0x08, 0x8E); + idt_set_gate(47, (unsigned)irq15, 0x08, 0x8E); +} + +/* Each of the IRQ ISRs point to this function, rather than +* the 'fault_handler' in 'isrs.c'. The IRQ Controllers need +* to be told when you are done servicing them, so you need +* to send them an "End of Interrupt" command (0x20). There +* are two 8259 chips: The first exists at 0x20, the second +* exists at 0xA0. If the second controller (an IRQ from 8 to +* 15) gets an interrupt, you need to acknowledge the +* interrupt at BOTH controllers, otherwise, you only send +* an EOI command to the first controller. If you don't send +* an EOI, you won't raise any more IRQs */ +void irq_handler(struct regs *r) +{ + /* This is a blank function pointer */ + void (*handler)(struct regs *r); + + /* Find out if we have a custom handler to run for this + * IRQ, and then finally, run it */ + handler = irq_routines[r->int_no - 32]; + if (handler) + { + handler(r); + } + + /* If the IDT entry that was invoked was greater than 40 + * (meaning IRQ8 - 15), then we need to send an EOI to + * the slave controller */ + if (r->int_no >= 40) + { + outportb(0xA0, 0x20); + } + + /* In either case, we need to send an EOI to the master + * interrupt controller too */ + outportb(0x20, 0x20); +} \ No newline at end of file diff --git a/src/isrs.c b/src/isrs.c new file mode 100644 index 0000000..c0aac1c --- /dev/null +++ b/src/isrs.c @@ -0,0 +1,142 @@ +#include "../include/system.h" + +/* These are function prototypes for all of the exception +* handlers: The first 32 entries in the IDT are reserved +* by Intel, and are designed to service 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(); + +/* This is a very repetitive function... it's not hard, it's +* just annoying. As you can see, we set the first 32 entries +* in the IDT to the first 32 ISRs. We can't use a for loop +* for this, because there is no way to get the function names +* that correspond to that given entry. We set the access +* flags to 0x8E. This means that the entry is present, is +* running in ring 0 (kernel level), and has the lower 5 bits +* set to the required '14', which is represented by 'E' in +* hex. */ +void isrs_install() +{ + idt_set_gate(0, (unsigned)isr0, 0x08, 0x8E); + idt_set_gate(1, (unsigned)isr1, 0x08, 0x8E); + idt_set_gate(2, (unsigned)isr2, 0x08, 0x8E); + idt_set_gate(3, (unsigned)isr3, 0x08, 0x8E); + idt_set_gate(4, (unsigned)isr4, 0x08, 0x8E); + idt_set_gate(5, (unsigned)isr5, 0x08, 0x8E); + idt_set_gate(6, (unsigned)isr6, 0x08, 0x8E); + idt_set_gate(7, (unsigned)isr7, 0x08, 0x8E); + idt_set_gate(8, (unsigned)isr8, 0x08, 0x8E); + idt_set_gate(9, (unsigned)isr9, 0x08, 0x8E); + idt_set_gate(10, (unsigned)isr10, 0x08, 0x8E); + idt_set_gate(11, (unsigned)isr11, 0x08, 0x8E); + idt_set_gate(12, (unsigned)isr12, 0x08, 0x8E); + idt_set_gate(13, (unsigned)isr13, 0x08, 0x8E); + idt_set_gate(14, (unsigned)isr14, 0x08, 0x8E); + idt_set_gate(15, (unsigned)isr15, 0x08, 0x8E); + idt_set_gate(16, (unsigned)isr16, 0x08, 0x8E); + idt_set_gate(17, (unsigned)isr17, 0x08, 0x8E); + idt_set_gate(18, (unsigned)isr18, 0x08, 0x8E); + idt_set_gate(19, (unsigned)isr19, 0x08, 0x8E); + idt_set_gate(20, (unsigned)isr20, 0x08, 0x8E); + idt_set_gate(21, (unsigned)isr21, 0x08, 0x8E); + idt_set_gate(22, (unsigned)isr22, 0x08, 0x8E); + idt_set_gate(23, (unsigned)isr23, 0x08, 0x8E); + idt_set_gate(24, (unsigned)isr24, 0x08, 0x8E); + idt_set_gate(25, (unsigned)isr25, 0x08, 0x8E); + idt_set_gate(26, (unsigned)isr26, 0x08, 0x8E); + idt_set_gate(27, (unsigned)isr27, 0x08, 0x8E); + idt_set_gate(28, (unsigned)isr28, 0x08, 0x8E); + idt_set_gate(29, (unsigned)isr29, 0x08, 0x8E); + idt_set_gate(30, (unsigned)isr30, 0x08, 0x8E); + idt_set_gate(31, (unsigned)isr31, 0x08, 0x8E); +} + +/* This is a simple string array. It contains the message that +* corresponds to each and every exception. We get the correct +* message by accessing like: +* exception_message[interrupt_number] */ +unsigned char *exception_messages[] = +{ + "Division By Zero", + "Debug", + "Non Maskable Interrupt", + "Breakpoint", + "Into Detected Overflow", + "Out of Bounds", + "Invalid Opcode", + "No Coprocessor", + "Double Fault", + "Coprocessor Segement Overrun", + "Bad TSS", + "Segment Not Present", + "Stack Fault", + "General Protection Fault", + "Page Fault", + "Unkown Interrupt", + "Coprocessor Fault", + "Alignment Check", + "Machine Check", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved" +}; + +/* All of our Exception handling Interrupt Service Routines will +* point to this function. This will tell us what exception has +* happened! Right now, we simply halt the system by hitting an +* endless loop. All ISRs disable interrupts while they are being +* serviced as a 'locking' mechanism to prevent an IRQ from +* happening and messing up kernel data structures */ +void fault_handler(struct regs *r) +{ + /* Is this a fault whose number is from 0 to 31? */ + if (r->int_no < 32) + { + /* Display the description for the Exception that occurred. + * In this tutorial, we will simply halt the system using an + * infinite loop */ + puts(exception_messages[r->int_no]); + puts(" Exception. System Halted!\n"); + for (;;); + } +} \ No newline at end of file diff --git a/src/kb.c b/src/kb.c new file mode 100644 index 0000000..f9fd7a8 --- /dev/null +++ b/src/kb.c @@ -0,0 +1,83 @@ +#include "../include/system.h" +//http://www.osdever.net/bkerndev/Docs/keyboard.htm +/* KBDUS means US Keyboard Layout. This is a scancode table +* used to layout a standard US keyboard. I have left some +* comments in to give you an idea of what key is what, even +* though I set it's array index to 0. You can change that to +* whatever you want using a macro, if you wish! */ +unsigned char kbdus[128] = +{ + 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', /* 9 */ + '9', '0', '-', '=', '\b', /* Backspace */ + '\t', /* Tab */ + 'q', 'w', 'e', 'r', /* 19 */ + 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* Enter key */ + 0, /* 29 - Control */ + 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', /* 39 */ + '\'', '`', 0, /* Left shift */ + '\\', 'z', 'x', 'c', 'v', 'b', 'n', /* 49 */ + 'm', ',', '.', '/', 0, /* Right shift */ + '*', + 0, /* Alt */ + ' ', /* Space bar */ + 0, /* Caps lock */ + 0, /* 59 - F1 key ... > */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, /* < ... F10 */ + 0, /* 69 - Num lock*/ + 0, /* Scroll Lock */ + 0, /* Home key */ + 0, /* Up Arrow */ + 0, /* Page Up */ + '-', + 0, /* Left Arrow */ + 0, + 0, /* Right Arrow */ + '+', + 0, /* 79 - End key*/ + 0, /* Down Arrow */ + 0, /* Page Down */ + 0, /* Insert Key */ + 0, /* Delete Key */ + 0, 0, 0, + 0, /* F11 Key */ + 0, /* F12 Key */ + 0, /* All other keys are undefined */ +}; +/* Handles the keyboard interrupt */ +void keyboard_handler(struct regs *r) +{ + unsigned char scancode; + + /* Read from the keyboard's data buffer */ + scancode = inportb(0x60); + + /* If the top bit of the byte we read from the keyboard is + * set, that means that a key has just been released */ + if (scancode & 0x80) + { + /* You can use this one to see if the user released the + * shift, alt, or control keys... */ + } + else + { + /* Here, a key was just pressed. Please note that if you + * hold a key down, you will get repeated key press + * interrupts. */ + + /* Just to show you how this works, we simply translate + * the keyboard scancode into an ASCII value, and then + * display it to the screen. You can get creative and + * use some flags to see if a shift is pressed and use a + * different layout, or you can add another 128 entries + * to the above layout to correspond to 'shift' being + * held. If shift is held using the larger lookup table, + * you would add 128 to the scancode when you look for it */ + putch(kbdus[scancode]); + } +} + +void kb_install() +{ + irq_install_handler(1, keyboard_handler); +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..cf42942 --- /dev/null +++ b/src/main.c @@ -0,0 +1,97 @@ +#include "../include/system.h" +#include "../include/gdt.h" +#include "../include/idt.h" +#include "../include/scrn.h" + +unsigned char *memcpy(unsigned char *dest, const unsigned char *src, int count) +{ + /* Add code here to copy 'count' bytes of data from 'src' to + * 'dest', finally return 'dest' */ + const char *sp = (const char *)src; + char *dp = (char *)dest; + for(; count != 0; count--) *dp++ = *sp++; + return dest; +} + +unsigned char *memset(unsigned char *dest, unsigned char val, int count) +{ + /* Add code here to set 'count' bytes in 'dest' to 'val'. + * Again, return 'dest' */ + char *temp = (char *)dest; + for( ; count != 0; count--) *temp++ = val; + return dest; +} + +unsigned short *memsetw(unsigned short *dest, unsigned short val, int count) +{ + /* Same as above, but this time, we're working with a 16-bit + * 'val' and dest pointer. Your code can be an exact copy of + * the above, provided that your local variables if any, are + * unsigned short */ + unsigned short *temp = (unsigned short *)dest; + for( ; count != 0; count--) *temp++ = val; + return dest; +} + +int strlen(const char *str) +{ + /* This loops through character array 'str', returning how + * many characters it needs to check before it finds a 0. + * In simple words, it returns the length in bytes of a string */ + //size_t retval; + unsigned int retval; + for(retval = 0; *str != '\0'; str++) retval++; + return retval; +} + +/* We will use this later on for reading from the I/O ports to get data +* from devices such as the keyboard. We are using what is called +* 'inline assembly' in these routines to actually do the work */ +unsigned char inportb (unsigned short _port) +{ + unsigned char rv; + __asm__ __volatile__ ("inb %1, %0" : "=a" (rv) : "dN" (_port)); + return rv; +} + +/* We will use this to write to I/O ports to send bytes to devices. This +* will be used in the next tutorial for changing the textmode cursor +* position. Again, we use some inline assembly for the stuff that simply +* cannot be done in C */ +void outportb (unsigned short _port, unsigned char _data) +{ + __asm__ __volatile__ ("outb %1, %0" : : "dN" (_port), "a" (_data)); +} + +extern char __BUILD_DATE; +extern char __BUILD_NUMBER; + +/* This is a very simple main() function. All it does is sit in an +* infinite loop. This will be like our 'idle' loop */ +void main() +{ + /* You would add commands after here */ + init_video(); + puts("Video initialized.\n"); + gdt_install(); + puts("Global Descriptor Table initialized.\n"); + idt_install(); + puts("Interrupt Descriptor Table initialized.\n"); + isrs_install(); + puts("Interrupt Service Routines installed.\n"); + irq_install(); + __asm__ __volatile__ ("sti"); + puts("Interrupt Requests now allowed.\n"); + //timer_install(); + //puts("Installed Timer.\n"); + kb_install(); + puts("Keyboard installed.\n"); + puts("SDOS version 0.0.0.1 initialized.\n"); + //puts(&__BUILD_DATE); + //puts(&__BUILD_NUMBER); + //puts("Divide by zero check:\n"); + //puts(5 / 0); + /* ...and leave this loop in. There is an endless loop in + * 'start.asm' also, if you accidentally delete this next line */ + for (;;); +} \ No newline at end of file diff --git a/src/scrn.c b/src/scrn.c new file mode 100644 index 0000000..459a8a3 --- /dev/null +++ b/src/scrn.c @@ -0,0 +1,158 @@ +#include "../include/system.h" + +/* These define our textpointer, our background and foreground +* colors (attributes), and x and y cursor coordinates */ +unsigned short *textmemptr; +int attrib = 0x0F; +int csr_x = 0, csr_y = 0; + +/* Scrolls the screen */ +void scroll(void) +{ + unsigned blank, temp; + + /* A blank is defined as a space... we need to give it + * backcolor too */ + blank = 0x20 | (attrib << 8); + + /* Row 25 is the end, this means we need to scroll up */ + if(csr_y >= 25) + { + /* Move the current text chunk that makes up the screen + * back in the buffer by a line */ + temp = csr_y - 25 + 1; + memcpy (textmemptr, textmemptr + temp * 80, (25 - temp) * 80 * 2); + + /* Finally, we set the chunk of memory that occupies + * the last line of text to our 'blank' character */ + memsetw (textmemptr + (25 - temp) * 80, blank, 80); + csr_y = 25 - 1; + } +} + +/* Updates the hardware cursor: the little blinking line +* on the screen under the last character pressed! */ +void move_csr(void) +{ + unsigned temp; + + /* The equation for finding the index in a linear + * chunk of memory can be represented by: + * Index = [(y * width) + x] */ + temp = csr_y * 80 + csr_x; + + /* This sends a command to indicies 14 and 15 in the + * CRT Control Register of the VGA controller. These + * are the high and low bytes of the index that show + * where the hardware cursor is to be 'blinking'. To + * learn more, you should look up some VGA specific + * programming documents. A great start to graphics: + * http://www.brackeen.com/home/vga */ + outportb(0x3D4, 14); + outportb(0x3D5, temp >> 8); + outportb(0x3D4, 15); + outportb(0x3D5, temp); +} + +/* Clears the screen */ +void cls() +{ + unsigned blank; + int i; + + /* Again, we need the 'short' that will be used to + * represent a space with color */ + blank = 0x20 | (attrib << 8); + + /* Sets the entire screen to spaces in our current + * color */ + for(i = 0; i < 25; i++) + memsetw (textmemptr + i * 80, blank, 80); + + /* Update out virtual cursor, and then move the + * hardware cursor */ + csr_x = 0; + csr_y = 0; + move_csr(); +} + +/* Puts a single character on the screen */ +void putch(unsigned char c) +{ + unsigned short *where; + unsigned att = attrib << 8; + + /* Handle a backspace, by moving the cursor back one space */ + if(c == 0x08) + { + if(csr_x != 0) csr_x--; + } + /* Handles a tab by incrementing the cursor's x, but only + * to a point that will make it divisible by 8 */ + else if(c == 0x09) + { + csr_x = (csr_x + 8) & ~(8 - 1); + } + /* Handles a 'Carriage Return', which simply brings the + * cursor back to the margin */ + else if(c == '\r') + { + csr_x = 0; + } + /* We handle our newlines the way DOS and the BIOS do: we + * treat it as if a 'CR' was also there, so we bring the + * cursor to the margin and we increment the 'y' value */ + else if(c == '\n') + { + csr_x = 0; + csr_y++; + } + /* Any character greater than and including a space, is a + * printable character. The equation for finding the index + * in a linear chunk of memory can be represented by: + * Index = [(y * width) + x] */ + else if(c >= ' ') + { + where = textmemptr + (csr_y * 80 + csr_x); + *where = c | att; /* Character AND attributes: color */ + csr_x++; + } + + /* If the cursor has reached the edge of the screen's width, we + * insert a new line in there */ + if(csr_x >= 80) + { + csr_x = 0; + csr_y++; + } + + /* Scroll the screen if needed, and finally move the cursor */ + scroll(); + move_csr(); +} + +/* Uses the above routine to output a string... */ +void puts(unsigned char *text) +{ + int i; + + for (i = 0; i < strlen(text); i++) + { + putch(text[i]); + } +} + +/* Sets the forecolor and backcolor that we will use */ +void settextcolor(unsigned char forecolor, unsigned char backcolor) +{ + /* Top 4 bytes are the background, bottom 4 bytes + * are the foreground color */ + attrib = (backcolor << 4) | (forecolor & 0x0F); +} + +/* Sets our text-mode VGA pointer, then clears the screen for us */ +void init_video(void) +{ + textmemptr = (unsigned short *)0xB8000; + cls(); +} \ No newline at end of file diff --git a/src/start.asm b/src/start.asm new file mode 100644 index 0000000..db0d752 --- /dev/null +++ b/src/start.asm @@ -0,0 +1,443 @@ +; This is the kernel's entry point. We could either call main here, +; or we can use this to setup the stack or other nice stuff, like +; perhaps setting up the GDT and segments. Please note that interrupts +; are disabled at this point: More on interrupts later! +[BITS 32] +global start +start: + mov esp, _sys_stack ; This points the stack to our new stack area + jmp stublet + +; This part MUST be 4byte aligned, so we solve that issue using 'ALIGN 4' +ALIGN 4 +mboot: + ; Multiboot macros to make a few lines later more readable + MULTIBOOT_PAGE_ALIGN equ 1<<0 + MULTIBOOT_MEMORY_INFO equ 1<<1 + MULTIBOOT_AOUT_KLUDGE equ 1<<16 + MULTIBOOT_HEADER_MAGIC equ 0x1BADB002 + MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEMORY_INFO | MULTIBOOT_AOUT_KLUDGE + MULTIBOOT_CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS) + EXTERN code, bss, end + + ; This is the GRUB Multiboot header. A boot signature + dd MULTIBOOT_HEADER_MAGIC + dd MULTIBOOT_HEADER_FLAGS + dd MULTIBOOT_CHECKSUM + + ; AOUT kludge - must be physical addresses. Make a note of these: + ; The linker script fills in the data for these ones! + dd mboot + dd code + dd bss + dd end + dd start + +; This is an endless loop here. Make a note of this: Later on, we +; will insert an 'extern _main', followed by 'call _main', right +; before the 'jmp $'. +stublet: + extern main + call main + jmp $ +; This will set up our new segment registers. We need to do +; something special in order to set CS. We do what is called a +; far jump. A jump that includes a segment as well as an offset. +; This is declared in C as 'extern void gdt_flush();' +global gdt_flush ; Allows the C code to link to this +extern gp ; Says that 'gp' is in another file +gdt_flush: + lgdt [gp] ; Load the GDT with our '_gp' which is a special pointer + mov ax, 0x10 ; 0x10 is the offset in the GDT to our data segment + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + jmp 0x08:flush2 ; 0x08 is the offset to our code segment: Far jump! +flush2: + ret ; Returns back to the C code! +; Loads the IDT defined in '_idtp' into the processor. +; This is declared in C as 'extern void idt_load();' +global idt_load +extern idtp +idt_load: + lidt [idtp] + ret + +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 + +; 0: Divide By Zero Exception +isr0: + cli + push byte 0 ; A normal ISR stub that pops a dummy error code to keep a + push byte 0 ; uniform stack frame + 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: Breakpoint Exception +isr3: + cli + push byte 0 + push byte 3 + jmp isr_common_stub +; 4: Into Detected Overflow 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: No Coprocessor 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 ; Note that we DON'T push a value on the stack in this one! + ; It pushes one already! Use this type of stub for exceptions + ; that pop error codes! + 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 +isr10: + cli + push byte 10 + jmp isr_common_stub +; 11: Segement Not Present Exception +isr11: + cli + push byte 11 + jmp isr_common_stub +; 12: Stack Fault Exception +isr12: + cli + push byte 12 + jmp isr_common_stub +; 13: General Protection Fault Exception +isr13: + cli + push byte 13 + jmp isr_common_stub +; 14: Page Fault Exception +isr14: + cli + push byte 14 + jmp isr_common_stub +; 15: Unkown Interrupt Exception +isr15: + cli + push byte 0 + push byte 15 + jmp isr_common_stub +; 16: Coprocessor Fault Exception +isr16: + cli + push byte 0 + push byte 16 + jmp isr_common_stub +; 17: Aligment 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 - 31: Reserved Exceptions +isr19: + cli + push byte 0 + push byte 19 + jmp isr_common_stub +isr20: + cli + push byte 0 + push byte 20 + jmp isr_common_stub +isr21: + cli + push byte 0 + push byte 21 + jmp isr_common_stub +isr22: + cli + push byte 0 + push byte 22 + jmp isr_common_stub +isr23: + cli + push byte 0 + push byte 23 + jmp isr_common_stub +isr24: + cli + push byte 0 + push byte 24 + jmp isr_common_stub +isr25: + cli + push byte 0 + push byte 25 + jmp isr_common_stub +isr26: + cli + push byte 0 + push byte 26 + jmp isr_common_stub +isr27: + cli + push byte 0 + push byte 27 + jmp isr_common_stub +isr28: + cli + push byte 0 + push byte 28 + jmp isr_common_stub +isr29: + cli + push byte 0 + push byte 29 + jmp isr_common_stub +isr30: + cli + push byte 0 + push byte 30 + jmp isr_common_stub +isr31: + cli + push byte 0 + push byte 31 + jmp isr_common_stub + +; We call a C function in here. We need to let the assembler know +; that 'fault_handler' exists in another file +extern fault_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 + push ds + push es + push fs + push gs + mov ax, 0x10 ; Load the Kernel Data Segment descriptor! + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov eax, esp ; Push us the stack + push eax + mov eax, fault_handler + call eax ; A special call, preserves the 'eip' register + pop eax + pop gs + pop fs + pop es + pop ds + popa + add esp, 8 ; Cleans up the pushed error code and pushed ISR number + iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP! + + 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 + + ; 32: IRQ0 + irq0: + cli + push byte 0 ; Note that these don't push an error code on the stack: + push byte 32 ; We need to push a dummy error code + jmp irq_common_stub + irq1: + cli + push byte 0 + push byte 33 + jmp irq_common_stub + irq2: + cli + push byte 0 + push byte 34 + jmp irq_common_stub + irq3: + cli + push byte 0 + push byte 35 + jmp irq_common_stub + irq4: + cli + push byte 0 + push byte 36 + jmp irq_common_stub + irq5: + cli + push byte 0 + push byte 37 + jmp irq_common_stub + irq6: + cli + push byte 0 + push byte 38 + jmp irq_common_stub + irq7: + cli + push byte 0 + push byte 39 + jmp irq_common_stub + irq8: + cli + push byte 0 + push byte 40 + jmp irq_common_stub + irq9: + cli + push byte 0 + push byte 41 + jmp irq_common_stub + irq10: + cli + push byte 0 + push byte 42 + jmp irq_common_stub + irq11: + cli + push byte 0 + push byte 43 + jmp irq_common_stub + irq12: + cli + push byte 0 + push byte 44 + jmp irq_common_stub + irq13: + cli + push byte 0 + push byte 45 + jmp irq_common_stub + irq14: + cli + push byte 0 + push byte 46 + jmp irq_common_stub + ; 47: IRQ15 + irq15: + cli + push byte 0 + push byte 47 + jmp irq_common_stub + + extern irq_handler + + ; This is a stub that we have created for IRQ based ISRs. This calls + ; 'irq_handler' in our C code. We need to create this in an 'irq.c' + irq_common_stub: + pusha + push ds + push es + push fs + push gs + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov eax, esp + push eax + mov eax, irq_handler + call eax + pop eax + pop gs + pop fs + pop es + pop ds + popa + add esp, 8 + iret + +; Here is the definition of our BSS section. Right now, we'll use +; it just to store the stack. Remember that a stack actually grows +; downwards, so we declare the size of the data before declaring +; the identifier '_sys_stack' +SECTION .bss + resb 8192 ; This reserves 8KBytes of memory here +_sys_stack: \ No newline at end of file diff --git a/src/timer.c b/src/timer.c new file mode 100644 index 0000000..96ee21b --- /dev/null +++ b/src/timer.c @@ -0,0 +1,31 @@ +#include "../include/system.h" +//http://www.osdever.net/bkerndev/Docs/pit.htm +/* This will keep track of how many ticks that the system +* has been running for */ +int timer_ticks = 0; + +/* Handles the timer. In this case, it's very simple: We +* increment the 'timer_ticks' variable every time the +* timer fires. By default, the timer fires 18.222 times +* per second. Why 18.222Hz? Some engineer at IBM must've +* been smoking something funky */ +void timer_handler(struct regs *r) +{ + /* Increment our 'tick count' */ + timer_ticks++; + + /* Every 18 clocks (approximately 1 second), we will + * display a message on the screen */ + if (timer_ticks % 18 == 0) + { + puts("One second has passed\n"); + } +} + +/* Sets up the system clock by installing the timer handler +* into IRQ0 */ +void timer_install() +{ + /* Installs 'timer_handler' to IRQ0 */ + irq_install_handler(0, timer_handler); +} \ No newline at end of file