Move to ring 3

This commit is contained in:
2024-08-06 12:10:03 +02:00
parent c5ab6aaef1
commit cf7a7303e3
11 changed files with 456 additions and 175 deletions

View File

@@ -54,6 +54,11 @@ $(BUILD_DIR)/myos.bin: $(OBJECTS)
-include $(DEPENDS)
$(BUILD_DIR)/kernel/interrupts.o: $(SOURCE_DIR)/kernel/interrupts.c Makefile
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) -mgeneral-regs-only -mno-red-zone $< -o $@
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile
@mkdir -p $(@D)
$(CC) -c $(CFLAGS) $< -o $@
@@ -61,3 +66,4 @@ $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c Makefile
$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.S Makefile
@mkdir -p $(@D)
$(AS) $(ASFLAGS) $< -o $@

3
interrupts.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
__attribute__((interrupt)) void interrupt_handler_1(struct interrupt_frame* frame);

View File

@@ -16,3 +16,6 @@ static inline struct str str_slice(struct str s, size_t begin, size_t end)
.len = s.len - begin - end,
};
}
#define CSTR_(x) #x
#define CSTR(x) CSTR_(x)

54
src/kernel/gdt.c Normal file
View File

@@ -0,0 +1,54 @@
#include "gdt.h"
#include "libc.h"
#include "str.h"
inline uint16_t gdt_segment_selector(uint16_t index, uint16_t table_indicator, uint16_t priv_level)
{
return (index << 3)
| ((table_indicator << 2) & 0b100)
| ((priv_level << 0) & 0b011);
}
struct gdt_table_entry gdt_encode_entry(struct gdt_entry_content content)
{
if (content.limit > GDT_LIMIT_MAX)
panic(str_attach("GDT cannot encode limits larger than " CSTR(GDT_LIMIT_MAX)));
struct gdt_table_entry entry;
entry.data[GDT_SDESC_LIMIT_0] = (content.limit >> 0) & 0xFF;
entry.data[GDT_SDESC_LIMIT_1] = (content.limit >> 8) & 0xFF;
entry.data[GDT_SDESC_LIMIT_2] = (content.limit >> 16) & 0xFF;
entry.data[GDT_SDESC_BASE_0] = (content.base >> 0) & 0xFF;
entry.data[GDT_SDESC_BASE_1] = (content.base >> 8) & 0xFF;
entry.data[GDT_SDESC_BASE_2] = (content.base >> 16) & 0xFF;
entry.data[GDT_SDESC_BASE_3] = (content.base >> 24) & 0xFF;
entry.data[GDT_SDESC_ACCESSBYTE] = content.access_byte;
entry.data[GDT_SDESC_FLAGS] |= (content.flags << 4);
return entry;
}
void gdt_set(uint16_t size, const struct gdt_table_entry base[], uint32_t offset)
{
base += offset;
/* the lgdt instruction requires a packed alignment */
struct __attribute__((packed)) gdtr {
uint16_t size;
uint32_t base;
} gdt = {
.size = size,
.base = (uint32_t)base,
};
__asm__ volatile (
"lgdt %[gdt]"
:
: [gdt] "m"(gdt)
);
}

110
src/kernel/gdt.h Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include <stdint.h>
#define GDT_LIMIT_MAX 0xFFFFF
struct gdt_table_entry {
volatile uint8_t data[8];
};
_Static_assert(sizeof(struct gdt_table_entry) == 8);
struct gdt_entry_content {
volatile uint32_t base;
volatile uint32_t limit;
volatile uint8_t access_byte;
volatile uint8_t flags;
};
enum gdt_segment_descriptor_index {
GDT_SDESC_BASE_0 = 2,
GDT_SDESC_BASE_1 = 3,
GDT_SDESC_BASE_2 = 4,
GDT_SDESC_BASE_3 = 7,
GDT_SDESC_LIMIT_0 = 0,
GDT_SDESC_LIMIT_1 = 1,
GDT_SDESC_LIMIT_2 = 6,
GDT_SDESC_ACCESSBYTE = 5,
GDT_SDESC_FLAGS = 6,
};
enum gdt_access_flag : uint8_t {
GDT_ACCESS_ACCESSED = (1<<0),
GDT_ACCESS_RW = (1<<1),
GDT_ACCESS_DC = (1<<2),
GDT_ACCESS_EXEC = (1<<3),
GDT_ACCESS_DESCRIPTOR = (1<<4),
GDT_ACCESS_DPL_0 = (0<<5),
GDT_ACCESS_DPL_1 = (1<<5),
GDT_ACCESS_DPL_2 = (2<<5),
GDT_ACCESS_DPL_3 = (3<<5),
GDT_ACCESS_PRESENT = (1<<7),
};
enum gdt_flags : uint8_t {
GDT_RESERVED = (1<<0),
GDT_LONG = (1<<1),
GDT_SIZE = (1<<2),
GDT_GRANULARITY = (1<<3),
};
void gdt_set(uint16_t limit, const struct gdt_table_entry base[], uint32_t offset);
struct gdt_table_entry gdt_encode_entry(struct gdt_entry_content content);
// only compiles with O1 or higher ¯\_(ツ)_/¯, might have to make this a macro
static inline void gdt_reload_granular(uint32_t code,
uint32_t data,
uint32_t extra_segment,
uint32_t general_segment_1,
uint32_t general_segment_2,
uint32_t stack_segment)
{
__asm__ volatile (
// Far jump to reload the CS register
"jmp %[cs], $reload_CS\n"
"reload_CS:\n"
"movw %[ds], %%ax\n"
"movw %%ax, %%ds\n"
"movw %[es], %%ax\n"
"movw %%ax, %%es\n"
"movw %[fs], %%ax\n"
"movw %%ax, %%fs\n"
"movw %[gs], %%ax\n"
"movw %%ax, %%gs\n"
"movw %[ss], %%ax\n"
"movw %%ax, %%ss\n"
: // No output operands
: [cs] "i"(code),
[ds] "i"(data),
[es] "i"(extra_segment),
[fs] "i"(general_segment_1),
[gs] "i"(general_segment_2),
[ss] "i"(stack_segment)
: "ax" // Clobbered register
);
}
static inline void gdt_reload(uint32_t data, uint32_t code)
{
__asm__ volatile (
// Far jump to reload the CS register
"jmp %[cs], $reload_CS\n"
"reload_CS:\n"
// Load data segment into AX and then move it to all segment registers
"movw %[ds], %%ax\n"
"movw %%ax, %%ds\n"
"movw %%ax, %%es\n"
"movw %%ax, %%fs\n"
"movw %%ax, %%gs\n"
"movw %%ax, %%ss\n"
: // No output operands
: [ds] "i"(data),
[cs] "i"(code)
: "ax" // Clobbered register
);
}

43
src/kernel/idt.c Normal file
View File

@@ -0,0 +1,43 @@
#include <stdint.h>
#include "idt.h"
struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type)
{
struct idt_gate_descriptor output = {0};
output.offset_0 = offset & 0xFFFF;
offset >>= 16;
output.offset_1 = offset & 0xFFFF;
output.segment_selector = segment_selector;
output.ist = ist;
output.flags = type | IDT_DPL_0 | IDT_PRESENT;
return output;
}
/*
* size: one less than the idt in bytes
* offset: linear address of the idt
* */
void idt_load(struct idt_gate_descriptor table[], uint16_t size)
{
struct __attribute__((packed)) {
uint16_t size;
uint32_t offset;
} idt = {
.size = size,
.offset = (uint32_t)table,
};
_Static_assert(sizeof idt.size == 2);
_Static_assert(sizeof idt.offset == 4);
_Static_assert(sizeof idt == 6);
__asm__ volatile (
"lidt %[idt]"
:
: [idt] "m"(idt)
);
}

41
src/kernel/idt.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <stdint.h>
struct __attribute__((packed)) idt_gate_descriptor {
uint16_t offset_0;
uint16_t segment_selector;
uint8_t ist;
/* `flags`
=======
bit 0-3: gate type
bit 4: unused
bit 5-6: dpl
bit 7: present bit
DPL is ignored by hardware interupts and present bit must be 1 */
uint8_t flags;
uint16_t offset_1;
};
enum idt_flags : uint8_t {
IDT_GATE_TYPE_TASK_GATE = 0x5,
IDT_GATE_TYPE_INTERUPT_GATE_16 = 0x6,
IDT_GATE_TYPE_TRAP_GATE_16 = 0x7,
IDT_GATE_TYPE_INTERUPT_GATE_32 = 0xE,
IDT_GATE_TYPE_TRAP_GATE_32 = 0xF,
IDT_DPL_0 = 0 << 5,
IDT_DPL_1 = 1 << 5,
IDT_DPL_2 = 2 << 5,
IDT_DPL_3 = 3 << 5,
IDT_PRESENT = 1 << 7,
};
struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type);
void idt_load(struct idt_gate_descriptor table[], uint16_t size);

21
src/kernel/interrupts.c Normal file
View File

@@ -0,0 +1,21 @@
#include <stdint.h>
#include "libc.h"
#ifdef __x86_64__
typedef unsigned long long int uword_t;
#else
typedef unsigned int uword_t;
#endif
struct __attribute__((packed)) interrupt_frame {
uword_t ip;
uword_t cs;
uword_t flags;
uword_t sp;
uword_t ss;
};
__attribute__((interrupt)) void interrupt_handler_1(struct interrupt_frame* frame)
{
(void)frame;
}

View File

@@ -2,166 +2,88 @@
#include <stddef.h>
#include <stdint.h>
#include "gdt.h"
#include "tss.h"
#include "idt.h"
// Future user-space
#include "libc.h"
#include "tty.h"
#define STR_(x) #x
#define STR(x) STR_(x)
// TOOD: clean up this
typedef void(*func_t)(void*);
// TODO: remove
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
static void ring3_mode(uint32_t, uint32_t, uint32_t, uint32_t, func_t);
static void user_mode_code(void*);
void gdt_setup_flat_model()
{
}
/* copied and only slightly modified from an osdev wiki article with poor
taste, TODO: change
----------------------------------------------*/
static void user_mode_code(void*)
{
printf(str_attach("hello world :)\n"));
}
static void ring3_mode(uint32_t udata_offset, uint32_t udata_rpl, uint32_t ucode_offset, uint32_t ucode_rpl, func_t callback)
{
const uint32_t udata = udata_offset | udata_rpl;
const uint32_t ucode = ucode_offset | ucode_rpl;
__asm__ volatile (
"mov %[udata], %%ax\n"
"mov %%ax, %%ds\n"
"mov %%ax, %%es\n"
"mov %%ax, %%fs\n"
"mov %%ax, %%gs\n"
"push %%ax\n"
"mov %%esp, %%eax\n"
"push %%eax\n" // current esp
"pushf\n" // eflags
"push %[ucode]\n"
"push %[callback]\n" // instruction address to return to
"iret"
:
: [udata] "m"(udata),
[ucode] "m"(ucode),
[callback] "m"(callback)
: "eax"
);
}
/*-----------------------------------------------*/
/**
* GDT Setup
* =========
* Kernel entrypoint
* =================
* The kernel entrypoints sets up the GDT, TSS and IDT and moves to ring 3
*/
#define GDT_LIMIT_MAX 0xFFFFF
struct gdt_table_entry {
uint8_t data[8];
};
enum gdt_segment_descriptor_index : size_t {
GDT_SDESC_BASE_0 = 2,
GDT_SDESC_BASE_1 = 3,
GDT_SDESC_BASE_2 = 4,
GDT_SDESC_BASE_3 = 7,
GDT_SDESC_LIMIT_0 = 0,
GDT_SDESC_LIMIT_1 = 1,
GDT_SDESC_LIMIT_2 = 6,
GDT_SDESC_ACCESSBYTE = 5,
GDT_SDESC_FLAGS = 6,
};
enum gdt_access_flag : uint8_t {
GDT_ACCESS_ACCESSED = (1<<0),
GDT_ACCESS_RW = (1<<1),
GDT_ACCESS_DC = (1<<2),
GDT_ACCESS_EXEC = (1<<3),
GDT_ACCESS_DESCRIPTOR = (1<<4),
GDT_ACCESS_DPL_0 = (0<<5),
GDT_ACCESS_DPL_1 = (1<<5),
GDT_ACCESS_DPL_2 = (2<<5),
GDT_ACCESS_DPL_3 = (3<<5),
GDT_ACCESS_PRESENT = (1<<7),
};
enum gdt_flags : uint8_t {
GDT_RESERVED = (1<<0),
GDT_LONG = (1<<1),
GDT_SIZE = (1<<2),
GDT_GRANULARITY = (1<<3),
};
static inline uint16_t gdt_segment_selector(uint16_t index, uint16_t table_indicator, uint16_t priv_level)
void kernel_main(void)
{
return (index << 3)
| ((table_indicator << 2) & 0b100)
| ((priv_level << 0) & 0b011);
}
struct gdt_entry_content {
uint32_t base;
uint32_t limit;
uint8_t access_byte;
uint8_t flags;
};
static void gdt_encode_entry(struct gdt_table_entry* target, struct gdt_entry_content content)
{
if (content.limit > GDT_LIMIT_MAX)
panic(str_attach("GDT cannot encode limits larger than " STR(GDT_LIMIT_MAX)));
// encode the limit
target->data[GDT_SDESC_LIMIT_0] = (content.limit >> 0) & 0xFF;
target->data[GDT_SDESC_LIMIT_1] = (content.limit >> 8) & 0xFF;
target->data[GDT_SDESC_LIMIT_2] = (content.limit >> 16) & 0xFF;
// encode the base
target->data[GDT_SDESC_BASE_0] = (content.base >> 0) & 0xFF;
target->data[GDT_SDESC_BASE_1] = (content.base >> 8) & 0xFF;
target->data[GDT_SDESC_BASE_2] = (content.base >> 16) & 0xFF;
target->data[GDT_SDESC_BASE_3] = (content.base >> 24) & 0xFF;
// encode the access byte
target->data[GDT_SDESC_ACCESSBYTE] = content.access_byte;
// encode the flags
target->data[GDT_SDESC_FLAGS] |= (content.flags << 4);
}
static void gdt_set(uint16_t limit, uint32_t base, uint32_t offset)
{
base += offset;
/* the lgdt instruction requires a packed alignment */
struct __attribute__((packed)) gdtr {
uint16_t limit;
uint32_t base;
};
static struct gdtr gdtr;
gdtr.limit = limit;
gdtr.base = base;
__asm__ volatile (
"lgdt %[gdtr]"
:
: [gdtr] "m"(gdtr)
);
}
// only compiles with O1 or higher ¯\_(ツ)_/¯, might have to make this a macro
static inline void gdt_reload(uint32_t code,
uint32_t data,
uint32_t extra_segment,
uint32_t general_segment_1,
uint32_t general_segment_2,
uint32_t stack_segment)
{
__asm__ volatile (
// Far jump to reload the CS register
"jmp %[cs], $reload_CS\n"
"reload_CS:\n"
// Load data segment into AX and then move it to all segment registers
//"movw %[ds], %%ax\n"
"movw %[ds], %%ds\n"
"movw %[es], %%es\n"
"movw %[fs], %%fs\n"
"movw %[gs], %%gs\n"
"movw %[ss], %%ss\n"
: // No output operands
: [cs] "i"(code),
[ds] "i"(data),
[es] "i"(extra_segment),
[fs] "i"(general_segment_1),
[gs] "i"(general_segment_2),
[ss] "i"(stack_segment)
: "ax" // Clobbered register
);
}
// sets a flat model
void test_gdt()
{
// disable interupts
__asm__ volatile("cli");
static struct gdt_table_entry gdt_table[6];
size_t i=0;
_Static_assert(sizeof(gdt_table) == 0x30);
static struct tss tss = {0};
// Null Descriptor
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){0});
enum segment_index : size_t {
null_segment = 0,
kernel_code_segment = 1,
kernel_data_segment = 2,
user_code_segment = 3,
user_data_segment = 4,
task_state_segment = 5,
segment_count,
};
// Kernel mode code segment
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){
static struct gdt_table_entry gdt[segment_count];
_Static_assert(sizeof(gdt) == 0x30);
gdt[null_segment] = gdt_encode_entry((struct gdt_entry_content){0});
/* kernel */
gdt[kernel_code_segment] = gdt_encode_entry((struct gdt_entry_content){
.base = 0,
.limit = GDT_LIMIT_MAX,
.access_byte = GDT_ACCESS_RW
@@ -172,8 +94,7 @@ void test_gdt()
.flags = GDT_SIZE
| GDT_GRANULARITY});
// Kernel mode data segment
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){
gdt[kernel_data_segment] = gdt_encode_entry((struct gdt_entry_content){
.base = 0,
.limit = GDT_LIMIT_MAX,
.access_byte = GDT_ACCESS_RW
@@ -183,8 +104,8 @@ void test_gdt()
.flags = GDT_SIZE
| GDT_GRANULARITY});
// User mode code segment
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){
/* user */
gdt[user_code_segment] = gdt_encode_entry((struct gdt_entry_content) {
.base = 0,
.limit = GDT_LIMIT_MAX,
.access_byte = GDT_ACCESS_RW
@@ -195,8 +116,7 @@ void test_gdt()
.flags = GDT_SIZE
| GDT_GRANULARITY});
// User mode data segment
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){
gdt[user_data_segment] = gdt_encode_entry((struct gdt_entry_content) {
.base = 0,
.limit = GDT_LIMIT_MAX,
.access_byte = GDT_ACCESS_RW
@@ -206,9 +126,9 @@ void test_gdt()
.flags = GDT_SIZE
| GDT_GRANULARITY});
// Task state segment
gdt_encode_entry(&gdt_table[i++], (struct gdt_entry_content){
.base = &tss,
/* tss */
gdt[task_state_segment] = gdt_encode_entry((struct gdt_entry_content) {
.base = (uint32_t)&tss,
.limit = sizeof(tss)-1,
.access_byte = GDT_ACCESS_ACCESSED
| GDT_ACCESS_EXEC
@@ -216,25 +136,31 @@ void test_gdt()
| GDT_ACCESS_PRESENT,
.flags = 0});
gdt_set(0, 0, 0);
const uint16_t code_segment = 0x8; // placeholder
const uint16_t data_segment = 0x10; // placeholder
gdt_reload(code_segment, data_segment);
gdt_set(sizeof gdt, gdt, 0);
gdt_reload(kernel_data_segment * sizeof (struct gdt_table_entry),
kernel_code_segment * sizeof (struct gdt_table_entry));
/* Setup the TSS */
tss.ss0 = kernel_data_segment * sizeof (struct gdt_table_entry);
tss.esp0 = 0; /* TODO: set kernel stack pointer */
tss_load(task_state_segment * sizeof (struct gdt_table_entry));
/* Setup the IDT */
//static struct idt_gate_descriptor idt[256] = {0};
//struct idt_gate_descriptor idt_encode_descriptor(uint32_t offset, uint16_t segment_selector, uint8_t ist, enum idt_flags type);
//idt[80] = idt_encode_descriptor(interrupt_handler_1, kernel_code_segment, 0, IDT_GATE_TYPE_INTERUPT_GATE_32);
//idt_load(idt, sizeof idt);
/* Finally go to ring 3 */
ring3_mode(user_data_segment * sizeof (struct gdt_table_entry), 3,
user_code_segment * sizeof (struct gdt_table_entry), 3,
user_mode_code);
__asm__ volatile("sti");
}
/**
* Kernel entrypoint
* =================
*/
void kernel_main(void)
{
terminal_clear();
for (size_t i = 0; i < VGA_HEIGHT / 2 + 1; i++) {
printf(str_attach("hello {u32}!\n"), i);
}
}
// TODO: remove
#pragma GCC diagnostic pop

6
src/kernel/tss.c Normal file
View File

@@ -0,0 +1,6 @@
#include "tss.h"
void tss_init(struct tss* t)
{
(void)t;
}

68
src/kernel/tss.h Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include <stdint.h>
struct __attribute__((packed)) tss {
/* Contains the Segment Selector for the TSS of the previous task, with
hardware task switching these form a kind of backward linked list. */
uint16_t link;
uint16_t link_reserved;
/* the stack pointer to load when changing to kernel mode */
uint32_t esp0;
/* the stack segment pointer to load when changing to kernel mode */
uint16_t ss0;
uint16_t ss0_reserved;
/* everything under here is unused in software task switching */
struct __attribute__((packed)) {
/* esp and ss 1 and 2 would be used when switching to ring 1 and 2 */
uint32_t esp1;
uint16_t ss1;
uint16_t ss1_reserved;
uint32_t esp2;
uint16_t ss2;
uint16_t ss2_reserved;
uint32_t cr3;
uint32_t eip;
uint32_t eflags;
uint32_t eax;
uint32_t ecx;
uint32_t edx;
uint32_t ebx;
uint32_t esp;
uint32_t ebp;
uint32_t esi;
uint32_t edi;
uint16_t es;
uint16_t es_reserved;
uint16_t cs;
uint16_t cs_reserved;
uint16_t ss;
uint16_t ss_reserved;
uint16_t ds;
uint16_t ds_reserved;
uint16_t fs;
uint16_t fs_reserved;
uint16_t gs;
uint16_t gs_reserved;
uint16_t ldtr;
uint16_t ldtr_reserved;
uint16_t reserved;
uint16_t iopb;
uint32_t ssp;
} unused;
};
/* offset: the offset in bytes of the tss descriptor in the gdt table */
static inline void tss_load(uint32_t offset)
{
__asm__ volatile (
"mov %[offset], %%ax\n\t"
"ltr %%ax"
:
: [offset] "m"(offset)
: "ax");
}