Compare commits
2 Commits
c7e9833819
...
7c45406c23
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c45406c23 | |||
|
|
e4cc638a8d |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,3 +5,5 @@ release/
|
|||||||
debug_grub/
|
debug_grub/
|
||||||
*_output.txt
|
*_output.txt
|
||||||
snippet.*
|
snippet.*
|
||||||
|
qemu.log
|
||||||
|
iso_output.txt
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ project(ClaudeOS C ASM)
|
|||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
|
||||||
# We are building a kernel, so we don't want standard libraries
|
# 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_ASM_FLAGS "${CMAKE_ASM_FLAGS} -m32 -fno-pie -fno-pic")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -m32 -nostdlib -no-pie")
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Create initial build system
|
- [x] Create initial build system
|
||||||
- [x] Setup a simple kernel that writes `Hello, world` to Qemu debug port
|
- [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)
|
- [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
|
- [x] Update the kernel to correctly setup the GDT
|
||||||
|
- [x] Create an interrupt handler.
|
||||||
|
- [ ] Implement a PIC 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 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 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.
|
- [ ] 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
|
add_executable(kernel
|
||||||
boot.S
|
boot.S
|
||||||
|
gdt_flush.S
|
||||||
|
gdt.c
|
||||||
|
idt.c
|
||||||
|
interrupts.S
|
||||||
kernel.c
|
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 <multiboot2.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include "gdt.h"
|
||||||
|
#include "idt.h"
|
||||||
|
|
||||||
static inline void outb(uint16_t port, uint8_t val)
|
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
|
#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 kernel_main(uint32_t magic, uint32_t addr) {
|
||||||
(void)addr; // Unused for now
|
(void)addr; // Unused for now
|
||||||
|
|
||||||
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
|
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC && magic != MULTIBOOT_BOOTLOADER_MAGIC) {
|
||||||
offset_print("Invalid magic number: ");
|
offset_print("Invalid magic number: ");
|
||||||
// I don't have hex print yet, but I can print something generic
|
print_hex(magic);
|
||||||
offset_print("Unknown\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset_print("Booting...\n");
|
||||||
|
|
||||||
|
init_gdt();
|
||||||
|
offset_print("GDT initialized\n");
|
||||||
|
|
||||||
|
init_idt();
|
||||||
|
offset_print("IDT initialized\n");
|
||||||
|
|
||||||
offset_print("Hello, world\n");
|
offset_print("Hello, world\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,6 @@ if [ ! -f "$RELEASE_DIR/claude-os.iso" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "$RELEASE_DIR/claude-os.img" ]; then
|
|
||||||
echo "Error: claude-os.img not found!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Testing ISO image..."
|
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
|
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
|
if grep -q "Hello, world" iso_output.txt; then
|
||||||
|
|||||||
Reference in New Issue
Block a user