Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.o
kernel

26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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 $@
8 changes: 1 addition & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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####
Expand Down
22 changes: 21 additions & 1 deletion kernel.asm → head.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,15 +37,34 @@ load_idt:
sti ;turn on interrupts
ret

load_gdt:
mov edx, [esp + 4]
lgdt [edx]
ret

keyboard_handler:
push ds
push es
push fs
push gs
pushad

call keyboard_handler_main

popad
pop ds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be in reverse order?

Copy link
Author

@mkilgore mkilgore Dec 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say you would be right. I'm guessing I never noticed this bug because all of those segments probably contain the same value everywhere in the program. but obviously it's still a bug and it would easily blow things up if/when features are added that make use of the segment registers (Such as using them for CPU-local data).

On that note it also appears the segments are also never assigned, so it's quite likely this code got lucky in other areas. A while back I had an issue stemming from something similar in my kernel, it's fixed here. The code in this PR doesn't reload the segments after the lgdt, but that's necessary to ensure they actually contain the correct values and also to ensure they start utilizing the newly defined segments.

pop es
pop fs
pop gs
iretd

start:
cli ;block interrupts
mov esp, stack_space
call kmain
cli
.loop:
hlt ;halt the CPU
jmp .loop

section .bss
resb 8192; 8KB for stack
Expand Down
115 changes: 104 additions & 11 deletions kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright (C) 2014 Arjun Sreedharan
* License: GPL version 2 or higher http://www.gnu.org/licenses/gpl.html
*/
#include <stdio.h>
#include "keyboard_map.h"

/* there are 25 lines each of 80 columns; each element takes 2 bytes */
Expand All @@ -15,37 +14,130 @@
#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

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;
/* 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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be sizeof(GDT) - 1 ?

load_gdt(&gdt_ptr);
}


struct IDT_entry{
unsigned short int offset_lowerbits;
unsigned short int selector;
unsigned char zero;
unsigned char type_attr;
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;
Expand Down Expand Up @@ -87,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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be sizeof(IDT) - 1 ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I think you're right, but FWIW it shouldn't matter that much since nothing should read past the end of the IDT or GDT (and if they did, are likely to read more than one byte).

idt_ptr.base = (unsigned long)IDT;

load_idt(idt_ptr);
load_idt(&idt_ptr);
}

void kb_init(void)
Expand Down Expand Up @@ -156,9 +247,11 @@ void kmain(void)
kprint_newline();
kprint_newline();

gdt_init();
idt_init();
kb_init();

while(1);
while(1)
asm volatile ("hlt");
}