From d4c0a6c3c650aaa0d18557dbb4c8c4a0902494b8 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 06:24:38 -0400 Subject: [PATCH 01/11] Remove stdio.h include We can't include the host machines stdio.h header in our kernel code. It's not actually being used, so removing this doesn't break anything. --- kernel.c | 1 - 1 file changed, 1 deletion(-) diff --git a/kernel.c b/kernel.c index 352a5c4..68c1425 100644 --- a/kernel.c +++ b/kernel.c @@ -2,7 +2,6 @@ * Copyright (C) 2014 Arjun Sreedharan * License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html */ -#include #include "keyboard_map.h" /* there are 25 lines each of 80 columns; each element takes 2 bytes */ From 234f40a8fb7ec287f77d0b013d55389663ea5914 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 06:31:57 -0400 Subject: [PATCH 02/11] Add a simple Makefile - Rename kernel.asm to fix conflict Create a simple Makefile to compile the kernel. 'kernel.asm' was changed to 'head.asm' to avoid naming conflict with 'kernel.c'. The Makefile generates .o files based on the filename, so 'kernel.asm' and 'kernel.c' would both compile to 'kernel.o'. A simple rename fixes the issue. --- Makefile | 26 ++++++++++++++++++++++++++ kernel.asm => head.asm | 0 2 files changed, 26 insertions(+) create mode 100644 Makefile rename kernel.asm => head.asm (100%) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7a27d49 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ + +CFLAGS += -ffreestanding + +SRCS := head.asm kernel.c +OBJS := $(foreach src,$(SRCS),$(basename $(src)).o) + +LINK_SCRIPT := link.ld + +KERN_BIN := kernel + +all: $(KERN_BIN) + +clean: + rm $(KERN_BIN) + rm $(OBJS) + +$(KERN_BIN): $(OBJS) + ld -m elf_i386 -T $(LINK_SCRIPT) -o $@ $(OBJS) + +# Rule for compiling nasm assembly +%.o: %.asm + nasm -f elf32 $< -o $@ + +# Rule for compiling C +%.o: %.c + gcc -m32 $(CFLAGS) -c $< -o $@ diff --git a/kernel.asm b/head.asm similarity index 100% rename from kernel.asm rename to head.asm From 642fe51958e9f3db23c2bee493425ff971be1cc8 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 06:34:19 -0400 Subject: [PATCH 03/11] Remove redundent cli in 'start' The bootloader will never call 'start' with interrupts enabled, so executing a cli here is redundent. --- head.asm | 1 - 1 file changed, 1 deletion(-) diff --git a/head.asm b/head.asm index 1c2fee7..4ce0b30 100644 --- a/head.asm +++ b/head.asm @@ -41,7 +41,6 @@ keyboard_handler: iretd start: - cli ;block interrupts mov esp, stack_space call kmain hlt ;halt the CPU From deb6b7afbe429158553461adecd0883c41bda2c7 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 06:35:40 -0400 Subject: [PATCH 04/11] Fix possiblity of code continuing on if `kmain` returns The 'hlt' instruction only halts the CPU until the next interrupt occurs. Beacuse of this, it's necessary to both use 'cli' before-hand to make-sure interrupts are off before we execute 'hlt', but also put the 'hlt' in a loop anyway to protect against nmi's, guarenteeing we don't continue. --- head.asm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/head.asm b/head.asm index 4ce0b30..50d336d 100644 --- a/head.asm +++ b/head.asm @@ -43,7 +43,10 @@ keyboard_handler: start: mov esp, stack_space call kmain + cli +.loop: hlt ;halt the CPU + jmp .loop section .bss resb 8192; 8KB for stack From 7b99635cb2e196538ceb1753b294fa242bc37e25 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 06:43:12 -0400 Subject: [PATCH 05/11] Apply attribute packed to IDT_entry C compilers are allowed to pad struct entries out, meaning that entry 2 may not come directly after entry 1, but instead empty space may be inbetween the two, for various reasons. gcc provides __attribute__((packed)) to guard against this, which when applied to a struct guarentees that the struct will not be compiled with any empty space. This is necessary for structs like IDT_entry, where they map to structure the hardware is going to expect in a very specefic setup with no padding. --- kernel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel.c b/kernel.c index 68c1425..3115c0c 100644 --- a/kernel.c +++ b/kernel.c @@ -35,7 +35,7 @@ struct IDT_entry{ unsigned char zero; unsigned char type_attr; unsigned short int offset_higherbits; -}; +} __attribute__((packed)); struct IDT_entry IDT[IDT_SIZE]; From 87e796eb891a639f7991c57b9e386d759de25c44 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 07:26:20 -0400 Subject: [PATCH 06/11] Create our own GDT on startup GRUB documentation says that we can't rely on the GDT that GRUB supplies us, and that it may not be valid to use. Since our IDT requires specifying a selector, we need to have our own GDT so we can supply a valid selector for the interrupt handler. --- head.asm | 6 ++++ kernel.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/head.asm b/head.asm index 50d336d..caada76 100644 --- a/head.asm +++ b/head.asm @@ -14,6 +14,7 @@ global keyboard_handler global read_port global write_port global load_idt +global load_gdt extern kmain ;this is defined in the c file extern keyboard_handler_main @@ -36,6 +37,11 @@ load_idt: sti ;turn on interrupts ret +load_gdt: + mov edx, [esp + 4] + lgdt [edx] + ret + keyboard_handler: call keyboard_handler_main iretd diff --git a/kernel.c b/kernel.c index 3115c0c..a25ed73 100644 --- a/kernel.c +++ b/kernel.c @@ -14,7 +14,30 @@ #define KEYBOARD_STATUS_PORT 0x64 #define IDT_SIZE 256 #define INTERRUPT_GATE 0x8e -#define KERNEL_CODE_SEGMENT_OFFSET 0x08 +#define KERNEL_CODE_SEGMENT_OFFSET KERNEL_CS + +#define GDT_SIZE 3 +#define GDT_MEM_LOW 0 +#define GDT_MEM_LEN 0xFFFFFFFF + +#define GDT_EXE 0x8 +#define GDT_READ 0x2 +#define GDT_WRITE 0x2 + +/* Kernel code always runs in ring 0 */ +#define DPL_KERNEL 0 + +/* GDT entry numbers */ +enum { + _GDT_NULL, + _KERNEL_CS, + _KERNEL_DS +}; + +/* GDT entry offsets */ +#define GDT_NULL (_GDT_NULL << 3) +#define KERNEL_CS (_KERNEL_CS << 3) +#define KERNEL_DS (_KERNEL_DS << 3) #define ENTER_KEY_CODE 0x1C @@ -29,6 +52,71 @@ unsigned int current_loc = 0; /* video memory begins at address 0xb8000 */ char *vidptr = (char*)0xb8000; +struct GDT_entry { + /* Low 8 bits of the "limit", or length of memory this descriptor refers to. */ + unsigned short limit_low; + unsigned short base_low; /* 'Low' 16-bits of the base */ + unsigned char base_middle; /* 'middle' 8 bits of the base */ + + unsigned char type :4; /* Flags for type of memory this descriptor describes */ + unsigned char one :1; + unsigned char dpl :2; /* Descriptor privilege level - Ring level */ + unsigned char present :1; /* 1 for any valid GDT entry */ + + unsigned char limit :4; /* Top 4 bytes of 'limit' */ + unsigned char avilable :1; + unsigned char zero :1; + unsigned char op_size :1; /* Selects between 16-bit and 32-bit */ + unsigned char gran :1; /* If this bit is set, then 'limit' is a count of 4K blocks, not bytes */ + + unsigned char base_high; /* High 8 bits of the base */ +} __attribute__((packed)); + +struct gdt_ptr { + unsigned short limit; + unsigned long base; +} __attribute__((packed)); + +extern void load_gdt(struct gdt_ptr *gdt_ptr); + +#define GDT_ENTRY(gdt_type, gdt_base, gdt_limit, gdt_dpl) \ + { \ + .limit_low = (((gdt_limit) >> 12) & 0xFFFF), \ + .base_low = ((gdt_base) & 0xFFFF), \ + .base_middle = (((gdt_base) >> 16) & 0xFF), \ + .type = gdt_type, \ + .one = 1, \ + .dpl = gdt_dpl, \ + .present = 1, \ + .limit = ((gdt_limit) >> 28), \ + .avilable = 0, \ + .zero = 0, \ + .op_size = 1, \ + .gran = 1, \ + .base_high = (((gdt_base) >> 24) & 0xFF), \ + } + +/* Define our GDT that we'll use - We know everything upfront, so we just + * initalize it with the correct settings. + * + * This sets up the NULL, entry, and then the kernel's CS and DS segments, + * which just span all 4GB of memory. */ +struct GDT_entry GDT[GDT_SIZE] = { + [_GDT_NULL] = { 0 /* NULL GDT entry - Required */ }, + [_KERNEL_CS] = GDT_ENTRY(GDT_EXE | GDT_READ, 0, 0xFFFFFFFF, DPL_KERNEL), + [_KERNEL_DS] = GDT_ENTRY(GDT_WRITE, 0, 0xFFFFFFFF, DPL_KERNEL) +}; + +void gdt_init(void) +{ + struct gdt_ptr gdt_ptr; + + gdt_ptr.base = (unsigned long)GDT; + gdt_ptr.limit = sizeof(GDT); + load_gdt(&gdt_ptr); +} + + struct IDT_entry{ unsigned short int offset_lowerbits; unsigned short int selector; @@ -155,6 +243,7 @@ void kmain(void) kprint_newline(); kprint_newline(); + gdt_init(); idt_init(); kb_init(); From d2b959a7800f3f49e0481ba2c25bf7672682a4d1 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 07:32:43 -0400 Subject: [PATCH 07/11] Implement idt_ptr struct to simplify code Like the 'gdt_ptr' struct, the 'idt_ptr' struct is a structure defining the specefic layout x86 wants for the IDT description. Using the structure like this lets us avoid all the bitshifting that used to be required. --- kernel.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/kernel.c b/kernel.c index a25ed73..2ef0dd2 100644 --- a/kernel.c +++ b/kernel.c @@ -45,7 +45,6 @@ extern unsigned char keyboard_map[128]; extern void keyboard_handler(void); extern char read_port(unsigned short port); extern void write_port(unsigned short port, unsigned char data); -extern void load_idt(unsigned long *idt_ptr); /* current cursor location */ unsigned int current_loc = 0; @@ -125,14 +124,20 @@ struct IDT_entry{ unsigned short int offset_higherbits; } __attribute__((packed)); +struct idt_ptr { + unsigned short limit; + unsigned long base; +} __attribute__((packed)); + +extern void load_idt(struct idt_ptr *idt_ptr); + struct IDT_entry IDT[IDT_SIZE]; void idt_init(void) { unsigned long keyboard_address; - unsigned long idt_address; - unsigned long idt_ptr[2]; + struct idt_ptr idt_ptr; /* populate IDT entry of keyboard's interrupt */ keyboard_address = (unsigned long)keyboard_handler; @@ -174,11 +179,10 @@ void idt_init(void) write_port(0xA1 , 0xff); /* fill the IDT descriptor */ - idt_address = (unsigned long)IDT ; - idt_ptr[0] = (sizeof (struct IDT_entry) * IDT_SIZE) + ((idt_address & 0xffff) << 16); - idt_ptr[1] = idt_address >> 16 ; + idt_ptr.limit = sizeof(IDT); + idt_ptr.base = (unsigned long)IDT; - load_idt(idt_ptr); + load_idt(&idt_ptr); } void kb_init(void) From ea847ebe57a56cf7f5e4120733c7190279bdcd3d Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 07:35:37 -0400 Subject: [PATCH 08/11] Insert hlt instruction into kmain loop Right now, kmain does a busy loop while we wait for interrupts. It's much more effecient to put a hlt instruction inside of the loop, which pauses the CPU until the next interrupt is recieved. This still requires a loop because execution will continue after the hlt instruction once we return from an interrupt. --- kernel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel.c b/kernel.c index 2ef0dd2..5758d43 100644 --- a/kernel.c +++ b/kernel.c @@ -251,6 +251,7 @@ void kmain(void) idt_init(); kb_init(); - while(1); + while(1) + asm volatile ("hlt"); } From 63c16295eaf630198d5c0f728017ab5943a51374 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 07:42:12 -0400 Subject: [PATCH 09/11] Back-up register values in keyboard interrupt handler Currently, we don't back up any of the registers before calling the actual interrupt routine. This will become a problem because the interrupt routine is going to trash these values, and they won't be restored by 'iretd'. The simple solution is to push all the segments and then all the registers onto the stack, and then pop them right before we return from the interrupt. --- head.asm | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/head.asm b/head.asm index caada76..53bcc7a 100644 --- a/head.asm +++ b/head.asm @@ -43,7 +43,19 @@ load_gdt: ret keyboard_handler: + push ds + push es + push fs + push gs + pushad + call keyboard_handler_main + + popad + pop ds + pop es + pop fs + pop gs iretd start: From 9d4c881a7828d4add9c337ff34c56f985ddfa9b2 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 08:02:22 -0400 Subject: [PATCH 10/11] Update README.md Since building now uses 'make', reflect that in the README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 5bcf17f..3498fab 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,7 @@ Kernel 201 - Let’s write a Kernel with keyboard and screen support ####Build commands#### ``` -nasm -f elf32 kernel.asm -o kasm.o -``` -``` -gcc -m32 -c kernel.c -o kc.o -``` -``` -ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o +make ``` ####Test on emulator#### From cade471f655f0f21342e3057384894f046905833 Mon Sep 17 00:00:00 2001 From: Matt Kilgore Date: Fri, 8 May 2015 08:04:24 -0400 Subject: [PATCH 11/11] Add .gitignore file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..41ae717 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +kernel +