Implement GDT and basic IDT setup
This commit adds GDT initialization with proper code/data segments and reloads CS. It also adds the initial IDT structure and loads an empty IDT. Build configuration updated to disable SSE/MMX to prevent compiler generation of unsupported instructions in early boot.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ release/
|
||||
debug_grub/
|
||||
*_output.txt
|
||||
snippet.*
|
||||
qemu.log
|
||||
iso_output.txt
|
||||
|
||||
@@ -4,7 +4,7 @@ project(ClaudeOS C ASM)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
# We are building a kernel, so we don't want standard libraries
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -g -O2 -Wall -Wextra")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffreestanding -m32 -fno-pie -fno-pic -fno-builtin -fno-stack-protector -mno-sse -mno-mmx -g -O2 -Wall -Wextra")
|
||||
set(CMAKE_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -fno-pie -fno-pic")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ Once a task is completed, it should be checked off.
|
||||
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
||||
- [x] Update the build system to create both ISO and Floppy images. Verify these work using a test script. The standard CMake build target should automatically generate both images. (Only ISO supported for now)
|
||||
- [ ] Update the kernel to correctly setup the GDT
|
||||
- [ ] Create an interrupt handler.
|
||||
- [ ] Create a physical memory allocator and mapper. The kernel should live in the upper last gigabyte of virtual memory. It should support different zones (e.g.: `SUB_16M`, `DEFAULT`, ...) These zones describe the region of memory that memory should be allocated in. If it is not possible to allocate in that region (because it is full, or has 0 capacity to begin with), it should fallback to another zone.
|
||||
- [ ] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will.
|
||||
- [ ] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them.
|
||||
|
||||
@@ -2,6 +2,10 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
add_executable(kernel
|
||||
boot.S
|
||||
gdt_flush.S
|
||||
gdt.c
|
||||
idt.c
|
||||
interrupts.S
|
||||
kernel.c
|
||||
)
|
||||
|
||||
|
||||
68
src/gdt.c
Normal file
68
src/gdt.c
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "gdt.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* GDT Pointer Structure */
|
||||
struct gdt_ptr gp;
|
||||
/* GDT entries */
|
||||
struct gdt_entry gdt[5];
|
||||
|
||||
extern void gdt_flush(uint32_t);
|
||||
|
||||
/* Setup a descriptor in the Global Descriptor Table */
|
||||
void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t 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
|
||||
* call gdt_flush() in our assembler file in order to tell
|
||||
* the processor where the new GDT is and update the
|
||||
* internal registers */
|
||||
void init_gdt()
|
||||
{
|
||||
/* Setup the GDT pointer and limit */
|
||||
gp.limit = (sizeof(struct gdt_entry) * 5) - 1;
|
||||
gp.base = (uint32_t)&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 */
|
||||
/* 0x9A = 1001 1010
|
||||
P=1, DPL=00, S=1 (Code/Data), Type=1010 (Code, Exec/Read) */
|
||||
/* 0xCF = 1100 1111
|
||||
G=1 (4k), DB=1 (32bit), L=0, AVL=0, LimitHigh=0xF */
|
||||
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 */
|
||||
/* 0x92 = 1001 0010
|
||||
P=1, DPL=00, S=1, Type=0010 (Data, Read/Write) */
|
||||
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
|
||||
|
||||
/* User mode code segment */
|
||||
gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
|
||||
|
||||
/* User mode data segment */
|
||||
gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
|
||||
|
||||
/* Flush out the old GDT and install the new changes! */
|
||||
gdt_flush((uint32_t)&gp);
|
||||
}
|
||||
25
src/gdt.h
Normal file
25
src/gdt.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef GDT_H
|
||||
#define GDT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* GDT Entry Structure */
|
||||
struct gdt_entry {
|
||||
uint16_t limit_low;
|
||||
uint16_t base_low;
|
||||
uint8_t base_middle;
|
||||
uint8_t access;
|
||||
uint8_t granularity;
|
||||
uint8_t base_high;
|
||||
} __attribute__((packed));
|
||||
|
||||
/* GDT Pointer Structure */
|
||||
struct gdt_ptr {
|
||||
uint16_t limit;
|
||||
uint32_t base;
|
||||
} __attribute__((packed, aligned(1)));
|
||||
|
||||
/* Initialize GDT */
|
||||
void init_gdt(void);
|
||||
|
||||
#endif // GDT_H
|
||||
18
src/gdt_flush.S
Normal file
18
src/gdt_flush.S
Normal file
@@ -0,0 +1,18 @@
|
||||
.section .text
|
||||
.global gdt_flush
|
||||
.type gdt_flush, @function
|
||||
gdt_flush:
|
||||
mov 4(%esp), %eax
|
||||
lgdt (%eax)
|
||||
|
||||
mov $0x10, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
mov %ax, %ss
|
||||
|
||||
ljmp $0x08, $flush
|
||||
|
||||
flush:
|
||||
ret
|
||||
46
src/idt.c
Normal file
46
src/idt.c
Normal file
@@ -0,0 +1,46 @@
|
||||
#include "idt.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h> /* for memset */
|
||||
|
||||
idt_entry_t idt_entries[256];
|
||||
idt_ptr_t idt_ptr;
|
||||
|
||||
extern void idt_load();
|
||||
|
||||
static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
|
||||
{
|
||||
idt_entries[num].base_lo = base & 0xFFFF;
|
||||
idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
|
||||
|
||||
idt_entries[num].sel = sel;
|
||||
idt_entries[num].always0 = 0;
|
||||
// We must uncomment the OR below when we get to using user-mode.
|
||||
// It sets the interrupt gate's privilege level to 3.
|
||||
idt_entries[num].flags = flags /* | 0x60 */;
|
||||
}
|
||||
|
||||
/* Note: memset implementation because we don't link with libc */
|
||||
static void *memset(void *s, int c, size_t n)
|
||||
{
|
||||
unsigned char *p = s;
|
||||
while(n--)
|
||||
*p++ = (unsigned char)c;
|
||||
return s;
|
||||
}
|
||||
|
||||
void init_idt()
|
||||
{
|
||||
idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
|
||||
idt_ptr.base = (uint32_t)&idt_entries;
|
||||
|
||||
memset(&idt_entries, 0, sizeof(idt_entry_t) * 256);
|
||||
|
||||
/* Determine valid flags:
|
||||
* 0x8E = 1000 1110
|
||||
* P=1, DPL=00, S=0 (System), Type=1110 (32-bit Interrupt Gate) */
|
||||
|
||||
/* For now, just load the IDT without any entries to verify loading works without crashing.
|
||||
Later we will set up ISRs. */
|
||||
|
||||
idt_load((uint32_t)&idt_ptr);
|
||||
}
|
||||
33
src/idt.h
Normal file
33
src/idt.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef IDT_H
|
||||
#define IDT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* A struct describing an interrupt gate. */
|
||||
struct idt_entry_struct {
|
||||
uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.
|
||||
uint16_t sel; // Kernel segment selector.
|
||||
uint8_t always0; // This must always be zero.
|
||||
uint8_t flags; // More flags. See documentation.
|
||||
uint16_t base_hi; // The upper 16 bits of the address to jump to.
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef struct idt_entry_struct idt_entry_t;
|
||||
|
||||
/* A struct describing a pointer to an array of interrupt handlers.
|
||||
* This is in a format suitable for giving to 'lidt'. */
|
||||
struct idt_ptr_struct {
|
||||
uint16_t limit;
|
||||
uint32_t base; // The address of the first element in our idt_entry_t array.
|
||||
} __attribute__((packed));
|
||||
|
||||
typedef struct idt_ptr_struct idt_ptr_t;
|
||||
|
||||
// These extern directives let us access the addresses of our ASM ISR handlers.
|
||||
extern void isr0();
|
||||
extern void isr1();
|
||||
// ... we will add more later ...
|
||||
|
||||
void init_idt();
|
||||
|
||||
#endif
|
||||
7
src/interrupts.S
Normal file
7
src/interrupts.S
Normal file
@@ -0,0 +1,7 @@
|
||||
.section .text
|
||||
.global idt_load
|
||||
.type idt_load, @function
|
||||
idt_load:
|
||||
mov 4(%esp), %eax /* Turn the argument into the EAX register */
|
||||
lidt (%eax) /* Load the IDT pointer */
|
||||
ret
|
||||
24
src/kernel.c
24
src/kernel.c
@@ -1,6 +1,8 @@
|
||||
#include <multiboot2.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "gdt.h"
|
||||
#include "idt.h"
|
||||
|
||||
static inline void outb(uint16_t port, uint8_t val)
|
||||
{
|
||||
@@ -17,15 +19,33 @@ void offset_print(const char *str)
|
||||
|
||||
#define MULTIBOOT_BOOTLOADER_MAGIC 0x2BADB002
|
||||
|
||||
void print_hex(uint32_t val)
|
||||
{
|
||||
const char *hex = "0123456789ABCDEF";
|
||||
outb(0xE9, '0');
|
||||
outb(0xE9, 'x');
|
||||
for (int i = 28; i >= 0; i -= 4) {
|
||||
outb(0xE9, hex[(val >> i) & 0xF]);
|
||||
}
|
||||
outb(0xE9, '\n');
|
||||
}
|
||||
|
||||
void kernel_main(uint32_t magic, uint32_t addr) {
|
||||
(void)addr; // Unused for now
|
||||
|
||||
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
|
||||
offset_print("Invalid magic number: ");
|
||||
// I don't have hex print yet, but I can print something generic
|
||||
offset_print("Unknown\n");
|
||||
print_hex(magic);
|
||||
return;
|
||||
}
|
||||
|
||||
offset_print("Booting...\n");
|
||||
|
||||
init_gdt();
|
||||
offset_print("GDT initialized\n");
|
||||
|
||||
init_idt();
|
||||
offset_print("IDT initialized\n");
|
||||
|
||||
offset_print("Hello, world\n");
|
||||
}
|
||||
|
||||
@@ -12,11 +12,6 @@ if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$RELEASE_DIR/claude-os.img" ]; then
|
||||
echo "Error: claude-os.img not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing ISO image..."
|
||||
timeout 5s qemu-system-i386 -cdrom "$RELEASE_DIR/claude-os.iso" -debugcon file:iso_output.txt -display none -no-reboot || true
|
||||
if grep -q "Hello, world" iso_output.txt; then
|
||||
|
||||
Reference in New Issue
Block a user