diff --git a/kernel/cpu/Makefile b/kernel/cpu/Makefile index 535c5b6..45c3653 100644 --- a/kernel/cpu/Makefile +++ b/kernel/cpu/Makefile @@ -1,4 +1,4 @@ -OBJS = multiboot.asm.o kernel_loader.asm.o ports.asm.o +OBJS = multiboot.asm.o kernel_loader.asm.o ports.asm.o gdt.asm.o ASM = nasm ASMFLAGS = -f elf diff --git a/kernel/cpu/gdt.asm b/kernel/cpu/gdt.asm new file mode 100644 index 0000000..f1eab18 --- /dev/null +++ b/kernel/cpu/gdt.asm @@ -0,0 +1,15 @@ +global gdt_flush ; for drivers/gdt.c + +section .text +gdt_flush: + mov eax, [esp+4] ; get address of gdt_ptr_t + lgdt [eax] ; Load GDT + mov ax, 0x10 ; offset in the GDT of the data segment + mov ds, ax ; Load data segment selectors + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + jmp 0x08:.flush ; offset in the GDT of the code segment +.flush: + ret \ No newline at end of file diff --git a/kernel/drivers/Makefile b/kernel/drivers/Makefile index eb3c82b..6c3e3c3 100644 --- a/kernel/drivers/Makefile +++ b/kernel/drivers/Makefile @@ -1,4 +1,4 @@ -OBJS = tty.o +OBJS = tty.o gdt.o CC = i686-elf-gcc # cross-compiler CFLAGS = -m32 -fno-stack-protector -ffreestanding -Wall -Wextra -Werror -g -c diff --git a/kernel/drivers/gdt.c b/kernel/drivers/gdt.c new file mode 100644 index 0000000..6b5d7ea --- /dev/null +++ b/kernel/drivers/gdt.c @@ -0,0 +1,104 @@ +#include "gdt.h" +#include "ports.h" + +// Internal method +extern void gdt_flush(uint32_t); // Defined on cpu/gdt.asm +static gdt_entry_t construct_null_entry(); +static gdt_entry_t construct_entry(gdt_access_t access); +static void init_gdt(); + +gdt_entry_t gdt_entries[5]; +gdt_ptr_t gdt_ptr; + + +// This method will be called by the kernel +void gdt_setup() { + init_gdt(); +} + +static void init_gdt() { + gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1; + gdt_ptr.base = (uint32_t)&gdt_entries; + + gdt_entry_t null_segment = construct_null_entry(); + gdt_entry_t kernel_mode_code_segment = construct_entry( + (struct gdt_access){ + .type = GDT_CODE_TYPE_EXEC_READ, + .dt = GDT_CODE_AND_DATA_DESCRIPTOR, + .dpl = GDT_RING0, + .p = GDT_SEGMENT_PRESENT + } + ); + gdt_entry_t kernel_mode_data_segment = construct_entry( + (struct gdt_access){ + .type = GDT_DATA_TYPE_READ_WRITE, + .dt = GDT_CODE_AND_DATA_DESCRIPTOR, + .dpl = GDT_RING0, + .p = GDT_SEGMENT_PRESENT + } + ); + gdt_entry_t user_mode_code_segment = construct_entry( + (struct gdt_access){ + .type = GDT_CODE_TYPE_EXEC_READ, + .dt = GDT_CODE_AND_DATA_DESCRIPTOR, + .dpl = GDT_RING3, + .p = GDT_SEGMENT_PRESENT + } + ); + gdt_entry_t user_mode_data_segment = construct_entry( + (struct gdt_access){ + .type = GDT_DATA_TYPE_READ_WRITE, + .dt = GDT_CODE_AND_DATA_DESCRIPTOR, + .dpl = GDT_RING3, + .p = GDT_SEGMENT_PRESENT + } + ); + + gdt_entries[0] = null_segment; + gdt_entries[1] = kernel_mode_code_segment; + gdt_entries[2] = kernel_mode_data_segment; + gdt_entries[3] = user_mode_code_segment; + gdt_entries[4] = user_mode_data_segment; + + gdt_flush((uint32_t)&gdt_ptr); +} + +static gdt_entry_t construct_entry(gdt_access_t access) { + gdt_entry_t entry = (struct gdt_entry_struct) { + .base_low = GDT_BASE & 0xFFFF, + .base_middle = (GDT_BASE >> 16) % 0xFF, + .base_high = (GDT_BASE >> 24) & 0xFF, + .limit_low = (GDT_LIMIT & 0xFFFF), + .access = access, + .granularity = (struct gdt_granularity) { + .g = GDT_GRANULARITY_4K, + .d = GDT_OPERAND_SIZE_32, + .zero = 0, + .seglen = GDT_SEGMENT_LENGTH + } + }; + return entry; +} + +// The only difference is in the access +static gdt_entry_t construct_null_entry() { + gdt_entry_t null_entry = (struct gdt_entry_struct) { + .base_low = 0, + .base_middle = 0, + .base_high = 0, + .limit_low = 0, + .access = (struct gdt_access) { + .p = 0, + .dpl = 0, + .dt = 0, + .type = 0 + }, + .granularity = (struct gdt_granularity) { + .g = 0, + .d = 0, + .zero = 0, + .seglen = 0 + } + }; + return null_entry; +} \ No newline at end of file diff --git a/kernel/drivers/gdt.h b/kernel/drivers/gdt.h new file mode 100644 index 0000000..015012d --- /dev/null +++ b/kernel/drivers/gdt.h @@ -0,0 +1,138 @@ +/************************************** + * iceOS Kernel * + * Developed by Marco 'icebit' Cetica * + * (c) 2019 * + * Released under GPLv3 * + * https://github.com/ice-bit/iceOS * + ***************************************/ +#ifndef _GDT_H_ +#define _GDT_H_ +/* + * First a bit of theory: + * GDT(Global Descriptor Table) is a complex data structure used in x86 systems + * to define memory areas. + * Technically speaking GDT is formed by an array of 8-bytes segment descriptors, + * the first descriptor of the GDT is always a NULL one and CANNNOT be used to allocate + * memory; so we need at least two descriptors(plus the null descriptor) to successfully allocate + * data on our memory. + * In x86 architecture there're two methods to provide virtual memory: Segmentation and Paging: + * With the first one every memory access is done within his own segment, so each address(of a process) + * is added to the segment's base address and checked against the segment's length. + * With paging, however, the address space is split into blocks(usually of 4KiB) called pages. + * Each page can be mapped to physical memory or it can be unmapped(to create virtual memory). + * Segmentation is a built-in functionality of x86 architecture, so to get around this we need to + * define our own GDT. A cool thing segmentation can do for us is to set Ring Level: + * a privilege level to allow our process to run in a 'unprivileged' mode(called user mode, ring 3) + * and to allow drivers(or kernel related stuff) to run in a 'supervisor-mode'(called kernel mode, ring 0). + * Usually bootloader(such as GRUB) sets up GDT for us, the problem is that we cannot known where it is or + * if it is has been overwritten during some other tasks. So it's a good idea to implement + * a new GDT ourself. + */ + +#include + +/* Those values were taken from Intel's developer manual */ + +// GDT fields +#define GDT_BASE 0x00000000 +#define GDT_LIMIT 0xFFFFFFFF +// GDT granularity +#define GDT_SEGMENT_LENGTH 0xf +#define GDT_OPERAND_SIZE_16 0 +#define GDT_OPERAND_SIZE_32 1 +#define GDT_GRANULARITY_1K 0 +#define GDT_GRANULARITY_4K 1 +// GDT access type fields +#define GDT_DATA_TYPE_READ_ONLY 0x0 +#define GDT_DATA_TYPE_READ_ONLY_ACCESSED 0x1 +#define GDT_DATA_TYPE_READ_WRITE 0x2 +#define GDT_DATA_TYPE_READ_WRITE_ACCESSED 0x3 +#define GDT_DATA_TYPE_READ_ONLY_EXPAND_DOWN 0x4 +#define GDT_DATA_TYPE_READ_ONLY_EXPAND_DOWN_ACCESSED 0x5 +#define GDT_DATA_TYPE_EXEC_ONLY 0x8 +#define GDT_CODE_TYPE_EXEC_ONLY_ACCESSED 0x9 +#define GDT_CODE_TYPE_EXEC_READ 0xA +#define GDT_CODE_TYPE_EXEC_READ_ACCESSED 0xB +#define GDT_CODE_TYPE_EXEC_CONFORMING 0xC +#define GDT_CODE_TYPE_EXEC_CONFORMING_ACCESSED 0xD +#define GDT_CODE_TYPE_EXEC_READ_CONFORMING 0xE +#define GDT_CODE_TYPE_EXEC_READ_CONFORMING_ACCESSED 0xF +// Descriptor type fields +#define GDT_SYSTEM_DESCRIPTOR 0 +#define GDT_CODE_AND_DATA_DESCRIPTOR 1 +// GDT Ring number +#define GDT_RING0 0 +#define GDT_RING1 1 +#define GDT_RING2 2 +#define GDT_RING3 3 +// 'Present' field +#define GDT_SEGMENT_NOT_PRESENT 0 +#define GDT_SEGMENT_PRESENT 1 + + +/* Global Descriptor Table (GDT) implementation */ +/* gdt_access is used to access portion of the GDT + * | 0 - 3 | 4 | 3 - 6 | 7 | + * | Type | DT | DPL | P | + * Type: Which type + * DT: descriptor type + * DPL: Kernel ring(0-3) + * P: is segment present? (bool) + */ +struct gdt_access { + uint8_t type:4; // 4 Byte + uint8_t dt:1; // 1 Byte + uint8_t dpl:2; // 2 Byte + uint8_t p:1; // 1 Byte +}__attribute__((packed)); +typedef struct gdt_access gdt_access_t; + +/* gdt_granularity is used to get portion of GDT entry + * | 0 - 3 | 4 | 5 | 6 | 7 | + * | seglen | 0 | D | G | + * seglen: segment length + * 0: Always zero + * D: Operand size (0 = 16 bit, 1 = 32 bit) + * G: granularity (0 = 1 Byte, 1 = 4KiB) + */ +struct gdt_granularity { + uint8_t seglen:4; + uint8_t zero:2; + uint8_t d:1; + uint8_t g:1; +}__attribute__((packed)); +typedef struct gdt_granularity gdt_gran_t; + +/* gdt_entry_struct contains the value of a single GDT entry + * Each slice is 64 bits. + * | 0 - 15 | 16 - 31 | 32 - 39 | 40 - 47 | 48 - 55 | 56 - 63 | + * | lim low| base low|base mid | access | gran | base hg | + * lim low: Lower 16 bits of the limit + * base low: Lower 16 bits of the base + * base mid: Next 8 bits of the base + * access: access flag, e.g. which ring this segment can be used in. + * gran. +*/ +struct gdt_entry_struct { + uint16_t limit_low; + uint16_t base_low; + uint8_t base_middle; + uint8_t base_high; + gdt_access_t access; + gdt_gran_t granularity; +}__attribute__((packed)); +typedef struct gdt_entry_struct gdt_entry_t; + +/* Also we have to define a pointer to the data structure + * This is needed to locate it later */ +struct gdt_ptr { + uint16_t limit; + uint16_t base; +}__attribute__((packed)); +typedef struct gdt_ptr gdt_ptr_t; + +/* GDT Kernel API */ +void gdt_setup(); + + +#endif \ No newline at end of file diff --git a/kernel/drivers/tty.c b/kernel/drivers/tty.c index a9399ab..f4c08e6 100644 --- a/kernel/drivers/tty.c +++ b/kernel/drivers/tty.c @@ -12,7 +12,7 @@ static uint32_t fb_row = 0; // Y void write_cell(int16_t i, uint8_t c, uint8_t fg, uint8_t bg) { uint8_t *fb = VGA_PTR; fb[i*2] = c; - fb[i*2 + 1] = ((bg & 0x0F) << 4) | (fg | 0x0F); + fb[i*2 + 1] = ((bg & 0x0F) << 4) | (fg & 0x0F); } void move_cursor(uint16_t pos) { @@ -71,7 +71,7 @@ void kprint(uint8_t *buf) { void init_prompt() { uint8_t *prompt = (uint8_t*)"\nuser@iceOS-$ "; - kprint_c(prompt, strlen(prompt), GREEN, BLACK); + kprint_c(prompt, strlen(prompt), LIGHT_GREEN, BLACK); } void clear_prompt() { diff --git a/kernel/kernel_main.c b/kernel/kernel_main.c index 84dd91b..18d9d5f 100644 --- a/kernel/kernel_main.c +++ b/kernel/kernel_main.c @@ -1,9 +1,11 @@ #include "drivers/tty.h" +#include "drivers/gdt.h" #include "libc/stdio.h" void kernel_main() { clear_prompt(); - init_prompt(); + init_prompt(); // Initialize frame buffer + gdt_setup(); // Setup Global Descriptor Table puts("Hello World!"); } \ No newline at end of file