diff --git a/.gitignore b/.gitignore index f5ce0f22..83611679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,32 @@ -# Binaries -**/*.elf -**/*.bin -**/*.o -**/*.iso +``` +# Build artifacts +zig-cache/ +zig-out/ +*.o +*.obj +*.bin +*.elf -# Zig ignore -**/zig-cache/ -**/zig-out/ -**/build/ -**/build-*/ -**/docgen_tmp/ +# Dependencies +zig*/ +deps/ -# Custom ignore -**/mock_framework.zig -**/*.ramdisk -**/*.img +# Logs and temp files +*.log +*.tmp + +# Environment +.env +.env.local +*.env.* + +# Editors +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db +``` \ No newline at end of file diff --git a/build.zig b/build.zig index d63a3922..9bd6b115 100644 --- a/build.zig +++ b/build.zig @@ -21,10 +21,17 @@ const x86_i686 = CrossTarget{ .cpu_model = .{ .explicit = &Target.x86.cpu._i686 }, }; +const x86_64 = CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .freestanding, + .cpu_model = .{ .explicit = &Target.x86.cpu.x86_64 }, +}; + pub fn build(b: *Builder) !void { - const target = b.standardTargetOptions(.{ .whitelist = &[_]CrossTarget{x86_i686}, .default_target = x86_i686 }); + const target = b.standardTargetOptions(.{ .whitelist = &[_]CrossTarget{ x86_i686, x86_64 }, .default_target = x86_i686 }); const arch = switch (target.getCpuArch()) { .i386 => "x86", + .x86_64 => "x86_64", else => unreachable, }; @@ -70,6 +77,7 @@ pub fn build(b: *Builder) !void { const make_iso = switch (target.getCpuArch()) { .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec_output_path, ramdisk_path, output_iso }), + .x86_64 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec_output_path, ramdisk_path, output_iso }), else => unreachable, }; make_iso.step.dependOn(&exec.step); @@ -138,12 +146,13 @@ pub fn build(b: *Builder) !void { switch (target.getCpuArch()) { .i386 => try qemu_args_al.append("qemu-system-i386"), + .x86_64 => try qemu_args_al.append("qemu-system-x86_64"), else => unreachable, } try qemu_args_al.append("-serial"); try qemu_args_al.append("stdio"); switch (target.getCpuArch()) { - .i386 => { + .i386, .x86_64 => { try qemu_args_al.append("-boot"); try qemu_args_al.append("d"); try qemu_args_al.append("-cdrom"); diff --git a/src/kernel/arch/x86_64/arch.zig b/src/kernel/arch/x86_64/arch.zig new file mode 100644 index 00000000..152cbd8f --- /dev/null +++ b/src/kernel/arch/x86_64/arch.zig @@ -0,0 +1,454 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const log = std.log.scoped(.x86_64_arch); +const builtin = @import("builtin"); +const gdt = @import("gdt.zig"); +const idt = @import("idt.zig"); +const paging = @import("paging.zig"); +const mem = @import("../../mem.zig"); +const vmm = @import("../../vmm.zig"); +const Task = @import("../../task.zig").Task; +const Serial = @import("../../serial.zig").Serial; +const panic = @import("../../panic.zig").panic; +const TTY = @import("../../tty.zig").TTY; +const Keyboard = @import("../../keyboard.zig").Keyboard; +const MemProfile = mem.MemProfile; + +/// Device type (placeholder for now). +pub const Device = struct { + vendor_id: u16, + device_id: u16, +}; + +/// Date/time structure (placeholder). +pub const DateTime = struct { + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +}; + +/// Virtual end of kernel code. +extern var KERNEL_VADDR_END: *u8; + +/// Virtual start of kernel code. +extern var KERNEL_VADDR_START: *u8; + +/// Physical end of kernel code. +extern var KERNEL_PHYSADDR_END: *u8; + +/// Physical start of kernel code. +extern var KERNEL_PHYSADDR_START: *u8; + +/// Boot-time offset between virtual and physical addresses. +extern var KERNEL_ADDR_OFFSET: *u8; + +/// Virtual address of stack top. +extern var KERNEL_STACK_START: *u8; + +/// Virtual address of stack bottom. +extern var KERNEL_STACK_END: *u8; + +/// CPU state structure for x86_64 (saved on interrupt/exception). +pub const CpuState = packed struct { + // General purpose registers (64-bit) + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + rbp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + + // Interrupt number and error code + int_num: u64, + error_code: u64, + + // Instruction pointer and flags + rip: u64, + cs: u64, + rflags: u64, + + // Stack pointer + rsp: u64, + + // Segment selectors (for user mode transitions) + ss: u64, + fs: u64, + gs: u64, + + pub fn empty() CpuState { + return .{ + .rax = undefined, + .rbx = undefined, + .rcx = undefined, + .rdx = undefined, + .rsi = undefined, + .rdi = undefined, + .rbp = undefined, + .r8 = undefined, + .r9 = undefined, + .r10 = undefined, + .r11 = undefined, + .r12 = undefined, + .r13 = undefined, + .r14 = undefined, + .r15 = undefined, + .int_num = undefined, + .error_code = undefined, + .rip = undefined, + .cs = undefined, + .rflags = undefined, + .rsp = undefined, + .ss = undefined, + .fs = undefined, + .gs = undefined, + }; + } +}; + +/// Boot payload type (multiboot info for now). +pub const BootPayload = ?*struct { + flags: u32, + mem_lower: u32, + mem_upper: u32, + boot_device: u32, + cmdline: u32, + mods_count: u32, + mods_addr: u32, + syms: [4]u32, + mmap_length: u32, + mmap_addr: u32, + drives_length: u32, + drives_addr: u32, + config_table: u32, + boot_loader_name: u32, + apm_table: u32, +}; + +/// VMM payload type (PML4 table). +pub const VmmPayload = *paging.Pml4Table; + +/// Kernel's VMM payload. +pub const KERNEL_VMM_PAYLOAD = &paging.kernel_pml4; + +/// VMM mapper functions. +pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = vmm.Mapper(VmmPayload){ + .mapFn = map, + .unmapFn = unmap, +}; + +/// Memory block size (page size). +pub const MEMORY_BLOCK_SIZE: usize = paging.PAGE_SIZE_4KB; + +/// Map function for VMM. +fn map(payload: VmmPayload, virt: usize, phys: usize, flags: u64) !void { + try paging.map(payload, virt, phys, flags); +} + +/// Unmap function for VMM. +fn unmap(payload: VmmPayload, virt: usize) !void { + try paging.unmap(payload, virt); +} + +/// Read from I/O port. +pub fn in(comptime Type: type, port: u16) Type { + return switch (Type) { + u8 => asm volatile ("inb %[port], %[result]" + : [result] "={al}" (-> Type), + : [port] "N{dx}" (port), + ), + u16 => asm volatile ("inw %[port], %[result]" + : [result] "={ax}" (-> Type), + : [port] "N{dx}" (port), + ), + u32 => asm volatile ("inl %[port], %[result]" + : [result] "={eax}" (-> Type), + : [port] "N{dx}" (port), + ), + u64 => asm volatile ("inl %[port], %[result]" + : [result] "={eax}" (-> Type), + : [port] "N{dx}" (port), + ), + else => @compileError("Invalid data type. Only u8, u16, u32 or u64, found: " ++ @typeName(Type)), + }; +} + +/// Write to I/O port. +pub fn out(port: u16, data: anytype) void { + switch (@TypeOf(data)) { + u8 => asm volatile ("outb %[data], %[port]" + : + : [port] "{dx}" (port), + [data] "{al}" (data), + ), + u16 => asm volatile ("outw %[data], %[port]" + : + : [port] "{dx}" (port), + [data] "{ax}" (data), + ), + u32 => asm volatile ("outl %[data], %[port]" + : + : [port] "{dx}" (port), + [data] "{eax}" (data), + ), + u64 => asm volatile ("outl %[data], %[port]" + : + : [port] "{dx}" (port), + [data] "{eax}" (data), + ), + else => @compileError("Invalid data type. Only u8, u16, u32 or u64, found: " ++ @typeName(@TypeOf(data))), + } +} + +/// I/O wait. +pub fn ioWait() void { + out(0x80, @as(u8, 0)); +} + +/// Load GDT. +pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { + asm volatile ("lgdt (%%rax)" + : + : [gdt_ptr] "{rax}" (gdt_ptr), + ); +} + +/// Store GDT. +pub fn sgdt() gdt.GdtPtr { + var gdt_ptr = gdt.GdtPtr{ .limit = 0, .base = 0 }; + asm volatile ("sgdt (%%rax)" + : [gdt_ptr] "=m" (gdt_ptr), + ); + return gdt_ptr; +} + +/// Load TSS. +pub fn ltr(offset: u16) void { + asm volatile ("ltr %%ax" + : + : [offset] "{ax}" (offset), + ); +} + +/// Load IDT. +pub fn lidt(idt_ptr: *const idt.IdtPtr) void { + asm volatile ("lidt (%%rax)" + : + : [idt_ptr] "{rax}" (idt_ptr), + ); +} + +/// Store IDT. +pub fn sidt() idt.IdtPtr { + var idt_ptr = idt.IdtPtr{ .limit = 0, .base = 0 }; + asm volatile ("sidt (%%rax)" + : [idt_ptr] "=m" (idt_ptr), + ); + return idt_ptr; +} + +/// Enable interrupts. +pub fn enableInterrupts() void { + asm volatile ("sti"); +} + +/// Disable interrupts. +pub fn disableInterrupts() void { + asm volatile ("cli"); +} + +/// Halt CPU. +pub fn halt() void { + asm volatile ("hlt"); +} + +/// Spin wait with interrupts enabled. +pub fn spinWait() noreturn { + enableInterrupts(); + while (true) { + halt(); + } +} + +/// Halt without interrupts. +pub fn haltNoInterrupts() noreturn { + while (true) { + disableInterrupts(); + halt(); + } +} + +/// Initialize serial. +pub fn initSerial(boot_payload: BootPayload) Serial { + _ = boot_payload; + // Placeholder - implement proper serial initialization + return Serial{ + .write = writeSerialCom1, + }; +} + +fn writeSerialCom1(byte: u8) void { + // Simple serial write placeholder + const SERIAL_PORT = 0x3F8; + while ((in(u8, SERIAL_PORT + 5) & 0x20) == 0) {} + out(SERIAL_PORT, byte); +} + +/// Initialize TTY. +pub fn initTTY(boot_payload: BootPayload) TTY { + _ = boot_payload; + // Placeholder - implement proper VGA/text mode initialization + return .{ + .print = stubPrint, + .setCursor = stubSetCursor, + .cols = 80, + .rows = 25, + .clear = stubClear, + }; +} + +fn stubPrint(_: []const u8) void {} +fn stubSetCursor(_: u8, _: u8) void {} +fn stubClear() void {} + +/// Initialize memory. +pub fn initMem(mb_info: BootPayload) Allocator.Error!MemProfile { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + const allocator = mem.fixed_buffer_allocator.allocator(); + var reserved_physical_mem = std.ArrayList(mem.Range).init(allocator); + var reserved_virtual_mem = std.ArrayList(mem.Map).init(allocator); + var modules = std.ArrayList(mem.Module).init(allocator); + + // Reserve kernel regions + const kernel_virt = mem.Range{ + .start = @ptrToInt(&KERNEL_VADDR_START), + .end = @ptrToInt(&KERNEL_STACK_START), + }; + const kernel_phy = mem.Range{ + .start = mem.virtToPhys(kernel_virt.start), + .end = mem.virtToPhys(kernel_virt.end), + }; + try reserved_virtual_mem.append(.{ + .virtual = kernel_virt, + .physical = kernel_phy, + }); + + // Map kernel stack + const kernel_stack_virt = mem.Range{ + .start = @ptrToInt(&KERNEL_STACK_START), + .end = @ptrToInt(&KERNEL_STACK_END), + }; + const kernel_stack_phy = mem.Range{ + .start = mem.virtToPhys(kernel_stack_virt.start), + .end = mem.virtToPhys(kernel_stack_virt.end), + }; + try reserved_virtual_mem.append(.{ + .virtual = kernel_stack_virt, + .physical = kernel_stack_phy, + }); + + return MemProfile{ + .vaddr_end = &KERNEL_VADDR_END, + .vaddr_start = &KERNEL_VADDR_START, + .physaddr_end = &KERNEL_PHYSADDR_END, + .physaddr_start = &KERNEL_PHYSADDR_START, + .mem_kb = if (mb_info) |info| info.mem_upper + info.mem_lower + 1024 else 0, + .modules = modules.items, + .physical_reserved = reserved_physical_mem.items, + .virtual_reserved = reserved_virtual_mem.items, + .fixed_allocator = mem.fixed_buffer_allocator, + }; +} + +/// Initialize keyboard. +pub fn initKeyboard(allocator: Allocator) Allocator.Error!*Keyboard { + _ = allocator; + // Placeholder - implement PS/2 keyboard initialization + return error.NotImplemented; +} + +/// Initialize task. +pub fn initTask(task: *Task, entry_point: usize, allocator: Allocator, set_up_stack: bool) Allocator.Error!void { + task.vmm.payload = &paging.kernel_pml4; + + var stack = &task.kernel_stack; + if (set_up_stack) { + const data_offset = if (task.kernel) gdt.KERNEL_DATA_OFFSET else gdt.USER_DATA_OFFSET | 0b11; + const code_offset = if (task.kernel) gdt.KERNEL_CODE_OFFSET else gdt.USER_CODE_OFFSET | 0b11; + + // Set up 64-bit stack frame + const bottom = stack.len - 1; + stack.*[bottom] = entry_point; // RIP + stack.*[bottom - 1] = code_offset; // CS + stack.*[bottom - 2] = 0x202; // RFLAGS + stack.*[bottom - 3] = @ptrToInt(&stack.*[stack.len - 1]); // RSP + stack.*[bottom - 4] = data_offset; // SS + + task.stack_pointer = @ptrToInt(&stack.*[bottom - 4]); + } + + if (!task.kernel and !builtin.is_test) { + // Create new PML4 for user task + task.vmm.payload = try allocator.allocAdvanced(paging.Pml4Table, paging.PAGE_SIZE_4KB, 1, .exact); + task.vmm.payload.* = paging.kernel_pml4; + } +} + +/// Get devices. +pub fn getDevices(allocator: Allocator) Allocator.Error![]Device { + _ = allocator; + // Placeholder - implement PCI enumeration + return allocator.dupe(Device, &[_]Device{}); +} + +/// Get date/time. +pub fn getDateTime() DateTime { + // Placeholder - implement RTC reading + return .{ + .year = 2024, + .month = 1, + .day = 1, + .hour = 0, + .minute = 0, + .second = 0, + }; +} + +/// Initialize architecture. +pub fn init(mem_profile: *const MemProfile) void { + gdt.init(); + idt.init(); + paging.init(mem_profile); +} + +/// Runtime test check for user task state. +pub fn runtimeTestCheckUserTaskState(ctx: *const CpuState) bool { + return ctx.rax == 0xCAFE and ctx.rbx == 0xBEEF; +} + +/// Runtime test for memory/paging. +pub fn runtimeTestChecksMem(the_vmm: *const vmm.VirtualMemoryManager(VmmPayload)) void { + var addr = the_vmm.start; + while (addr < the_vmm.end and (the_vmm.isSet(addr) catch unreachable)) { + addr += vmm.BLOCK_SIZE; + } + const should_fault = @intToPtr(*usize, addr).*; + log.debug("This should not be printed: {x}\\n", .{should_fault}); +} + +test "x86_64 arch" { + std.testing.refAllDecls(@This()); +} diff --git a/src/kernel/arch/x86_64/boot.asm b/src/kernel/arch/x86_64/boot.asm new file mode 100644 index 00000000..36cd370d --- /dev/null +++ b/src/kernel/arch/x86_64/boot.asm @@ -0,0 +1,137 @@ +; x86_64 boot assembly +; This file contains the bootloader entry point and early initialization code for x86_64 + +section .boot +bits 32 + +; Multiboot2 header (must be in first 8KB of kernel) +align 8 +multiboot_header: + dd 0xE85250D6 ; Magic number + dd 1 ; Architecture (AMD64) + dd multiboot_header_end - multiboot_header ; Header length + dd -(0xE85250D6 + 1 + (multiboot_header_end - multiboot_header)) ; Checksum + +%ifdef USE_FRAMEBUFFER + ; Framebuffer tag + dw 5 ; Type (framebuffer) + dw 0 ; Flags + dd framebuffer_tag_end - framebuffer_tag ; Size + dd 800 ; Width (0 = any) + dd 600 ; Height (0 = any) + dd 0 ; Depth (0 = any) +framebuffer_tag_end: +%endif + + ; End tag + dw 0 ; Type (end) + dw 0 ; Flags + dd 8 ; Size +multiboot_header_end: + +; Entry point - called by bootloader with multiboot info in EAX/RDI +global boot +extern kmain +extern KERNEL_ADDR_OFFSET +extern paging_kernel_pml4 + +boot: + ; Disable interrupts + cli + + ; Check for Multiboot2 magic number + cmp eax, 0x36d76289 + jne .no_multiboot2 + + ; Load higher half kernel offset + mov ecx, [KERNEL_ADDR_OFFSET] + + ; Set up initial page tables for identity mapping + ; This is a minimal setup to get us into long mode + call setup_paging + + ; Enable PAE + mov eax, cr4 + or eax, 1 << 5 + mov cr4, eax + + ; Load CR3 with our PML4 + mov eax, paging_kernel_pml4 + mov cr3, eax + + ; Enable long mode + mov ecx, 0xC0000080 ; EFER MSR + rdmsr + or eax, 1 << 8 ; LME bit + wrmsr + + ; Enable paging + mov eax, cr0 + or eax, 1 << 31 ; PG bit + mov cr0, eax + + ; Jump to 64-bit code + lea rax, [.long_mode] + jmp rax + +.long_mode: + bits 64 + + ; Set up segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + + ; Set up stack + lea rsp, [KERNEL_STACK_END] + + ; Call kernel main + ; RDI contains pointer to multiboot info structure + push rdi + call kmain + + ; If kmain returns, halt +.halt: + hlt + jmp .halt + +.no_multiboot2: + ; No Multiboot2 - halt + hlt + jmp $ + +; Set up initial identity-mapped page tables +setup_paging: + push rbp + mov rbp, rsp + + ; Clear page tables (assuming they're zeroed by linker) + ; In a real implementation, would properly set up PML4, PDPT, PD, PT + + pop rbp + ret + +section .bss +align 4096 + +; Kernel stack (256 KB) +global KERNEL_STACK_START +global KERNEL_STACK_END +KERNEL_STACK_START: + resb 262144 +KERNEL_STACK_END: + +section .data +align 4096 + +; Page tables for initial identity mapping +global paging_kernel_pml4 +paging_kernel_pml4: + resq 512 + +section .rodata +; Read-only data section + +section .text +; Main code section diff --git a/src/kernel/arch/x86_64/gdt.zig b/src/kernel/arch/x86_64/gdt.zig new file mode 100644 index 00000000..fdcd1aac --- /dev/null +++ b/src/kernel/arch/x86_64/gdt.zig @@ -0,0 +1,288 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_gdt); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const panic = @import("../../panic.zig").panic; + +/// The access bits for a GDT entry in long mode. +const AccessBits = packed struct { + /// Whether the segment has been accessed. Set by CPU. + accessed: u1, + /// For code segments: readable. For data segments: writable. + read_write: u1, + /// For code segments: conforming. For data segments: direction. + direction_conforming: u1, + /// When set, the segment can be executed (code segment). + executable: u1, + /// Should be set for code and data segments, not for TSS. + descriptor: u1, + /// Privilege level (0 = kernel, 3 = user). + privilege: u2, + /// Whether the segment is present. + present: u1, +}; + +/// The flag bits for a GDT entry in long mode. +const FlagBits = packed struct { + /// Reserved, must be zero. + reserved_zero: u1, + /// When set indicates 64-bit code segment. + is_64_bit: u1, + /// When set indicates 32-bit protected mode segment. + is_32_bit: u1, + /// Granularity: 1 = 4KB blocks, 0 = 1B blocks. + granularity: u1, +}; + +/// GDT entry structure for x86_64 (16 bytes). +pub const GdtEntry = packed struct { + /// Lower 16 bits of limit. + limit_low: u16, + /// Lower 24 bits of base. + base_low: u24, + /// Access byte. + access: AccessBits, + /// Upper 4 bits of limit + flags. + limit_high: u4, + /// Flags. + flags: FlagBits, + /// Upper 8 bits of base. + base_high: u8, +}; + +/// TSS structure for x86_64 (104 bytes minimum, but we use extended version). +pub const Tss = packed struct { + /// Reserved. + reserved1: u32, + /// Ring 0 stack pointer (low 32 bits). + rsp0_low: u32, + /// Ring 0 stack pointer (high 32 bits). + rsp0_high: u32, + /// Ring 1 stack pointer (not used). + rsp1: u64, + /// Ring 2 stack pointer (not used). + rsp2: u64, + /// Reserved. + reserved2: u64, + /// Interrupt Stack Table 1. + ist1: u64, + /// Interrupt Stack Table 2. + ist2: u64, + /// Interrupt Stack Table 3. + ist3: u64, + /// Interrupt Stack Table 4. + ist4: u64, + /// Interrupt Stack Table 5. + ist5: u64, + /// Interrupt Stack Table 6. + ist6: u64, + /// Interrupt Stack Table 7. + ist7: u64, + /// Reserved. + reserved3: u64, + /// Reserved. + reserved4: u16, + /// I/O map base offset. + io_permissions_base_offset: u16, +}; + +/// GDT pointer structure. +pub const GdtPtr = packed struct { + /// Size of GDT minus 1. + limit: u16, + /// Base address of GDT. + base: u64, +}; + +/// Number of GDT entries. +const NUMBER_OF_ENTRIES: u16 = 0x06; + +/// Indexes into the GDT. +const NULL_INDEX: u16 = 0x00; +const KERNEL_CODE_INDEX: u16 = 0x01; +const KERNEL_DATA_INDEX: u16 = 0x02; +const USER_CODE_INDEX: u16 = 0x03; +const USER_DATA_INDEX: u16 = 0x04; +const TSS_INDEX: u16 = 0x05; + +/// Offsets into the GDT (index * 8 for 64-bit entries). +pub const NULL_OFFSET: u16 = 0x00; +pub const KERNEL_CODE_OFFSET: u16 = 0x08; +pub const KERNEL_DATA_OFFSET: u16 = 0x10; +pub const USER_CODE_OFFSET: u16 = 0x18; +pub const USER_DATA_OFFSET: u16 = 0x20; +pub const TSS_OFFSET: u16 = 0x28; + +/// Access bits for different segment types. +const NULL_SEGMENT: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 0, + .direction_conforming = 0, + .executable = 0, + .descriptor = 0, + .privilege = 0, + .present = 0, +}; + +const KERNEL_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +const KERNEL_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +const USER_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +const USER_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +const TSS_SEGMENT: AccessBits = AccessBits{ + .accessed = 1, + .read_write = 0, + .direction_conforming = 0, + .executable = 1, + .descriptor = 0, + .privilege = 0, + .present = 1, +}; + +/// Flag bits for different modes. +const NULL_FLAGS: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 0, + .granularity = 0, +}; + +const LONG_MODE_CODE: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 1, + .is_32_bit = 0, + .granularity = 1, +}; + +const PAGING_32_BIT: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 1, + .granularity = 1, +}; + +/// GDT entries array. +var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = init: { + var gdt_entries_temp: [NUMBER_OF_ENTRIES]GdtEntry = undefined; + + // Null descriptor + gdt_entries_temp[0] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); + + // Kernel code descriptor (64-bit) + gdt_entries_temp[1] = makeGdtEntry(0, 0, KERNEL_SEGMENT_CODE, LONG_MODE_CODE); + + // Kernel data descriptor + gdt_entries_temp[2] = makeGdtEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT); + + // User code descriptor (64-bit) + gdt_entries_temp[3] = makeGdtEntry(0, 0, USER_SEGMENT_CODE, LONG_MODE_CODE); + + // User data descriptor + gdt_entries_temp[4] = makeGdtEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT); + + // TSS descriptor (will be initialized at runtime) + gdt_entries_temp[5] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); + + break :init gdt_entries_temp; +}; + +/// GDT pointer. +var gdt_ptr: GdtPtr = GdtPtr{ + .limit = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1, + .base = undefined, +}; + +/// Main TSS entry. +pub var main_tss_entry: Tss align(16) = init: { + var tss_temp = std.mem.zeroes(Tss); + break :init tss_temp; +}; + +/// Make a GDT entry. +fn makeGdtEntry(base: u64, limit: u32, access: AccessBits, flags: FlagBits) GdtEntry { + return .{ + .limit_low = @truncate(u16, limit), + .base_low = @truncate(u24, base & 0xFFFFFF), + .access = .{ + .accessed = access.accessed, + .read_write = access.read_write, + .direction_conforming = access.direction_conforming, + .executable = access.executable, + .descriptor = access.descriptor, + .privilege = access.privilege, + .present = access.present, + }, + .limit_high = @truncate(u4, limit >> 16), + .flags = .{ + .reserved_zero = flags.reserved_zero, + .is_64_bit = flags.is_64_bit, + .is_32_bit = flags.is_32_bit, + .granularity = flags.granularity, + }, + .base_high = @truncate(u8, base >> 24), + }; +} + +/// Initialize the GDT. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Initialize TSS descriptor + const tss_base: u64 = @ptrToInt(&main_tss_entry); + const tss_limit: u32 = @sizeOf(Tss) - 1; + gdt_entries[TSS_INDEX] = makeGdtEntry(tss_base, tss_limit, TSS_SEGMENT, NULL_FLAGS); + + // Set GDT pointer base + gdt_ptr.base = @ptrToInt(&gdt_entries[0]); + + // Load GDT (declared in arch.zig) + const arch = @import("arch.zig"); + arch.lgdt(&gdt_ptr); + + // Load TSS + arch.ltr(TSS_OFFSET); +} + +test "GDT entry sizes" { + try std.testing.expectEqual(@as(usize, 2), @sizeOf(AccessBits)); + try std.testing.expectEqual(@as(usize, 1), @sizeOf(FlagBits)); + try std.testing.expectEqual(@as(usize, 8), @sizeOf(GdtEntry)); + try std.testing.expectEqual(@as(usize, 104), @sizeOf(Tss)); + try std.testing.expectEqual(@as(usize, 16), @sizeOf(GdtPtr)); +} diff --git a/src/kernel/arch/x86_64/idt.zig b/src/kernel/arch/x86_64/idt.zig new file mode 100644 index 00000000..ca586f9c --- /dev/null +++ b/src/kernel/arch/x86_64/idt.zig @@ -0,0 +1,129 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_idt); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const panic = @import("../../panic.zig").panic; +const gdt = if (is_test) @import("../../../../test/mock/kernel/gdt_mock.zig") else @import("gdt.zig"); +const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); + +/// IDT entry structure for x86_64 (16 bytes). +pub const IdtEntry = packed struct { + /// Lower 16 bits of handler offset. + base_low: u16, + /// Code segment selector. + selector: u16, + /// Interrupt Stack Table offset (bits 0-2), reserved (bits 3-7). + ist: u8, + /// Gate type and attributes. + gate_type: u4, + /// Reserved (must be 0). + storage_segment: u1, + /// Privilege level. + privilege: u2, + /// Present bit. + present: u1, + /// Middle 16 bits of handler offset. + base_middle: u16, + /// Upper 32 bits of handler offset. + base_high: u32, + /// Reserved. + zero: u32, +}; + +/// IDT pointer structure. +pub const IdtPtr = packed struct { + /// Size of IDT minus 1. + limit: u16, + /// Base address of IDT. + base: u64, +}; + +/// Interrupt handler function type. +pub const InterruptHandler = fn () callconv(.Naked) void; + +/// IDT error types. +pub const IdtError = error{ + /// IDT entry already exists. + IdtEntryExists, +}; + +/// Gate types. +const TASK_GATE: u4 = 0x5; +const INTERRUPT_GATE: u4 = 0xE; +const TRAP_GATE: u4 = 0xF; + +/// Privilege levels. +const PRIVILEGE_RING_0: u2 = 0x0; +const PRIVILEGE_RING_1: u2 = 0x1; +const PRIVILEGE_RING_2: u2 = 0x2; +const PRIVILEGE_RING_3: u2 = 0x3; + +/// Number of IDT entries. +pub const NUMBER_OF_ENTRIES: u16 = 256; + +/// IDT table size. +const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1; + +/// IDT pointer. +var idt_ptr: IdtPtr = IdtPtr{ + .limit = TABLE_SIZE, + .base = 0, +}; + +/// IDT entries array. +var idt_entries: [NUMBER_OF_ENTRIES]IdtEntry = [_]IdtEntry{IdtEntry{ + .base_low = 0, + .selector = 0, + .ist = 0, + .gate_type = 0, + .storage_segment = 0, + .privilege = 0, + .present = 0, + .base_middle = 0, + .base_high = 0, + .zero = 0, +}} ** NUMBER_OF_ENTRIES; + +/// Make an IDT entry. +fn makeEntry(base: u64, selector: u16, gate_type: u4, privilege: u2, ist: u8) IdtEntry { + return IdtEntry{ + .base_low = @truncate(u16, base), + .selector = selector, + .ist = ist, + .gate_type = gate_type, + .storage_segment = 0, + .privilege = privilege, + .present = 1, + .base_middle = @truncate(u16, base >> 16), + .base_high = @truncate(u32, base >> 32), + .zero = 0, + }; +} + +/// Check if IDT entry is open (present). +pub fn isIdtOpen(entry: IdtEntry) bool { + return entry.present == 1; +} + +/// Open an interrupt gate. +pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void { + if (isIdtOpen(idt_entries[index])) { + return IdtError.IdtEntryExists; + } + + idt_entries[index] = makeEntry(@ptrToInt(handler), gdt.KERNEL_CODE_OFFSET, INTERRUPT_GATE, PRIVILEGE_RING_0, 0); +} + +/// Initialize the IDT. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + idt_ptr.base = @ptrToInt(&idt_entries); + arch.lidt(&idt_ptr); +} + +test "IDT entry sizes" { + try std.testing.expectEqual(@as(usize, 16), @sizeOf(IdtEntry)); + try std.testing.expectEqual(@as(usize, 16), @sizeOf(IdtPtr)); +} diff --git a/src/kernel/arch/x86_64/interrupt_stubs.asm b/src/kernel/arch/x86_64/interrupt_stubs.asm new file mode 100644 index 00000000..91c66e37 --- /dev/null +++ b/src/kernel/arch/x86_64/interrupt_stubs.asm @@ -0,0 +1,275 @@ +; x86_64 interrupt stubs +; This file contains assembly stubs for handling interrupts and exceptions in long mode + +section .text +bits 64 + +; External C handlers +extern isr_handleException +extern irq_handleIrq + +; Macro to create an ISR stub without error code +%macro ISR_NOERRCODE 1 +global isr_stub_%1 +isr_stub_%1: + ; Save general purpose registers + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Push interrupt number and error code (0 for no error) + push %1 ; int_num + push 0 ; error_code + + ; Call C handler with pointer to CPU state + mov rdi, rsp ; First argument = pointer to CpuState + call isr_handleException + + ; Pop error code and interrupt number + add rsp, 16 + + ; Restore registers + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + iretq +%endmacro + +; Macro to create an ISR stub with error code +%macro ISR_ERRCODE 1 +global isr_stub_err_%1 +isr_stub_err_%1: + ; Save general purpose registers + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Error code is already on stack from CPU + push %1 ; int_num + + ; Call C handler + mov rdi, rsp + call isr_handleException + + ; Pop interrupt number + add rsp, 8 + + ; Restore registers + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + iretq +%endmacro + +; Macro to create an IRQ stub +%macro IRQ_STUB 2 +global irq_stub_%1 +irq_stub_%1: + ; Save registers + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + ; Push interrupt number and error code + push %2 ; int_num (IRQ base + IRQ number) + push 0 ; error_code + + ; Call C handler + mov rdi, rsp + mov rsi, %1 ; IRQ number + call irq_handleIrq + + ; Pop error code and interrupt number + add rsp, 16 + + ; Restore registers + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + iretq +%endmacro + +; Define ISRs for exceptions 0-31 +ISR_NOERRCODE 0 ; Division By Zero +ISR_NOERRCODE 1 ; Debug +ISR_NOERRCODE 2 ; Non Maskable Interrupt +ISR_NOERRCODE 3 ; Breakpoint +ISR_NOERRCODE 4 ; Overflow +ISR_NOERRCODE 5 ; Bound Range Exceeded +ISR_NOERRCODE 6 ; Invalid Opcode +ISR_NOERRCODE 7 ; Device Not Available +ISR_ERRCODE 8 ; Double Fault +ISR_NOERRCODE 9 ; Coprocessor Segment Overrun +ISR_ERRCODE 10 ; Invalid TSS +ISR_ERRCODE 11 ; Segment Not Present +ISR_ERRCODE 12 ; Stack-Segment Fault +ISR_ERRCODE 13 ; General Protection Fault +ISR_ERRCODE 14 ; Page Fault +ISR_NOERRCODE 15 ; Reserved +ISR_NOERRCODE 16 ; x87 FPU Error +ISR_ERRCODE 17 ; Alignment Check +ISR_NOERRCODE 18 ; Machine Check +ISR_NOERRCODE 19 ; SIMD FPU Exception +ISR_NOERRCODE 20 ; Virtualization Exception +ISR_NOERRCODE 21 ; Control Protection Exception +ISR_NOERRCODE 22 ; Reserved +ISR_NOERRCODE 23 ; Reserved +ISR_NOERRCODE 24 ; Reserved +ISR_NOERRCODE 25 ; Reserved +ISR_NOERRCODE 26 ; Reserved +ISR_NOERRCODE 27 ; Reserved +ISR_NOERRCODE 28 ; Reserved +ISR_NOERRCODE 29 ; Hypervisor Injection Exception +ISR_NOERRCODE 30 ; VMM Communication Exception +ISR_ERRCODE 31 ; Security Exception + +; Define IRQs 0-15 (remapped to interrupts 32-47) +IRQ_STUB 0, 32 ; Timer +IRQ_STUB 1, 33 ; Keyboard +IRQ_STUB 2, 34 ; Cascade (Slave PIC) +IRQ_STUB 3, 35 ; COM2 +IRQ_STUB 4, 36 ; COM1 +IRQ_STUB 5, 37 ; LPT2 +IRQ_STUB 6, 38 ; Floppy Disk +IRQ_STUB 7, 39 ; LPT1 / Spurious +IRQ_STUB 8, 40 ; RTC +IRQ_STUB 9, 41 ; Free +IRQ_STUB 10, 42 ; Free +IRQ_STUB 11, 43 ; Free +IRQ_STUB 12, 44 ; PS/2 Mouse +IRQ_STUB 13, 45 ; FPU / Coprocessor +IRQ_STUB 14, 46 ; Primary ATA +IRQ_STUB 15, 47 ; Secondary ATA + +; Array of ISR stub addresses for IDT initialization +section .data +align 8 +global isr_stub_table +isr_stub_table: + dq isr_stub_0 + dq isr_stub_1 + dq isr_stub_2 + dq isr_stub_3 + dq isr_stub_4 + dq isr_stub_5 + dq isr_stub_6 + dq isr_stub_7 + dq isr_stub_err_8 + dq isr_stub_9 + dq isr_stub_err_10 + dq isr_stub_err_11 + dq isr_stub_err_12 + dq isr_stub_err_13 + dq isr_stub_err_14 + dq isr_stub_15 + dq isr_stub_16 + dq isr_stub_err_17 + dq isr_stub_18 + dq isr_stub_19 + dq isr_stub_20 + dq isr_stub_21 + dq isr_stub_22 + dq isr_stub_23 + dq isr_stub_24 + dq isr_stub_25 + dq isr_stub_26 + dq isr_stub_27 + dq isr_stub_28 + dq isr_stub_29 + dq isr_stub_30 + dq isr_stub_err_31 + +; Array of IRQ stub addresses +global irq_stub_table +irq_stub_table: + dq irq_stub_0 + dq irq_stub_1 + dq irq_stub_2 + dq irq_stub_3 + dq irq_stub_4 + dq irq_stub_5 + dq irq_stub_6 + dq irq_stub_7 + dq irq_stub_8 + dq irq_stub_9 + dq irq_stub_10 + dq irq_stub_11 + dq irq_stub_12 + dq irq_stub_13 + dq irq_stub_14 + dq irq_stub_15 diff --git a/src/kernel/arch/x86_64/irq.zig b/src/kernel/arch/x86_64/irq.zig new file mode 100644 index 00000000..5bac30dc --- /dev/null +++ b/src/kernel/arch/x86_64/irq.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_irq); +const arch = @import("arch.zig"); +const idt = @import("idt.zig"); + +/// IRQ handler function type. +pub const IrqHandler = fn (*arch.CpuState) void; + +/// Number of IRQs (0-15 for legacy PIC). +const NUM_IRQS: usize = 16; + +/// Array of IRQ handlers. +var irq_handlers: [NUM_IRQS]?IrqHandler = [_]?IrqHandler{null} ** NUM_IRQS; + +/// Register an IRQ handler. +pub fn registerHandler(irq_num: u8, handler: IrqHandler) !void { + if (irq_num >= NUM_IRQS) { + return error.InvalidIrqNumber; + } + irq_handlers[irq_num] = handler; +} + +/// Default IRQ handler (does nothing). +fn defaultHandler(state: *arch.CpuState) void { + _ = state; + // Acknowledge the interrupt in the IO-APIC or PIC + // For now, just return +} + +/// Initialize IRQ subsystem. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Set default handlers for all IRQs + for (irq_handlers) |*handler| { + handler.* = defaultHandler; + } + + // Remap IRQs if using PIC (for legacy compatibility) + // In x86_64 long mode, typically use IO-APIC instead + remapPic(); +} + +/// Remap legacy PIC to different interrupts. +fn remapPic() void { + const PIC1_COMMAND: u16 = 0x20; + const PIC1_DATA: u16 = 0x21; + const PIC2_COMMAND: u16 = 0xA0; + const PIC2_DATA: u16 = 0xA1; + + const ICW1_INIT: u8 = 0x11; + const ICW4_8086: u8 = 0x01; + + // Start initialization sequence + arch.out(PIC1_COMMAND, ICW1_INIT); + arch.ioWait(); + arch.out(PIC2_COMMAND, ICW1_INIT); + arch.ioWait(); + + // Set vector offsets (IRQ 0-7 -> interrupts 32-39, IRQ 8-15 -> 40-47) + arch.out(PIC1_DATA, 32); + arch.ioWait(); + arch.out(PIC2_DATA, 40); + arch.ioWait(); + + // Tell Master PIC about Slave PIC + arch.out(PIC1_DATA, 4); + arch.ioWait(); + arch.out(PIC2_DATA, 2); + arch.ioWait(); + + // Set 8086 mode + arch.out(PIC1_DATA, ICW4_8086); + arch.ioWait(); + arch.out(PIC2_DATA, ICW4_8086); + arch.ioWait(); + + // Mask all interrupts initially + arch.out(PIC1_DATA, 0xFF); + arch.out(PIC2_DATA, 0xFF); +} + +/// Send End of Interrupt signal. +pub fn sendEoi(irq_num: u8) void { + const PIC1_COMMAND: u16 = 0x20; + const PIC2_COMMAND: u16 = 0xA0; + const EOI: u8 = 0x20; + + if (irq_num >= 8) { + arch.out(PIC2_COMMAND, EOI); + } + arch.out(PIC1_COMMAND, EOI); +} + +/// Main IRQ entry point (called from assembly stub). +pub fn handleIrq(irq_num: u8, state: *arch.CpuState) void { + if (irq_handlers[irq_num]) |handler| { + handler(state); + } + sendEoi(irq_num); +} + +test "IRQ initialization" { + init(); + try std.testing.expect(irq_handlers[0] != null); +} diff --git a/src/kernel/arch/x86_64/isr.zig b/src/kernel/arch/x86_64/isr.zig new file mode 100644 index 00000000..6e5635da --- /dev/null +++ b/src/kernel/arch/x86_64/isr.zig @@ -0,0 +1,97 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_isr); +const arch = @import("arch.zig"); +const idt = @import("idt.zig"); + +/// Exception handler function type. +pub const ExceptionHandler = fn (*arch.CpuState) void; + +/// Number of exceptions (0-31). +const NUM_EXCEPTIONS: usize = 32; + +/// Array of exception handlers. +var exception_handlers: [NUM_EXCEPTIONS]?ExceptionHandler = [_]?ExceptionHandler{null} ** NUM_EXCEPTIONS; + +/// Register an exception handler. +pub fn registerHandler(int_num: u8, handler: ExceptionHandler) !void { + if (int_num >= NUM_EXCEPTIONS) { + return error.InvalidInterruptNumber; + } + exception_handlers[int_num] = handler; +} + +/// Default exception handler. +fn defaultHandler(state: *arch.CpuState) void { + const exception_names = [_][]const u8{ + "Division By Zero", + "Debug", + "Non Maskable Interrupt", + "Breakpoint", + "Overflow", + "Bound Range Exceeded", + "Invalid Opcode", + "Device Not Available", + "Double Fault", + "Coprocessor Segment Overrun", + "Invalid TSS", + "Segment Not Present", + "Stack-Segment Fault", + "General Protection Fault", + "Page Fault", + "Reserved", + "x87 FPU Error", + "Alignment Check", + "Machine Check", + "SIMD FPU Exception", + "Virtualization Exception", + "Control Protection Exception", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Hypervisor Injection Exception", + "VMM Communication Exception", + "Security Exception", + "Reserved", + }; + + const name = if (state.int_num < exception_names.len) exception_names[state.int_num] else "Unknown"; + + log.err("\\n!!! EXCEPTION: {} (#{}) !!!\\n", .{ name, state.int_num }); + log.err("RIP: 0x{X}, RSP: 0x{X}, RFLAGS: 0x{X}\\n", .{ state.rip, state.rsp, state.rflags }); + log.err("Error Code: 0x{X}\\n", .{state.error_code}); + log.err("RAX: 0x{X}, RBX: 0x{X}, RCX: 0x{X}, RDX: 0x{X}\\n", .{ state.rax, state.rbx, state.rcx, state.rdx }); + log.err("RSI: 0x{X}, RDI: 0x{X}, RBP: 0x{X}, R8: 0x{X}\\n", .{ state.rsi, state.rdi, state.rbp, state.r8 }); + log.err("R9: 0x{X}, R10: 0x{X}, R11: 0x{X}, R12: 0x{X}\\n", .{ state.r9, state.r10, state.r11, state.r12 }); + log.err("R13: 0x{X}, R14: 0x{X}, R15: 0x{X}\\n", .{ state.r13, state.r14, state.r15 }); + + arch.haltNoInterrupts(); +} + +/// Initialize ISR subsystem. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Set default handlers for all exceptions + for (exception_handlers) |*handler| { + handler.* = defaultHandler; + } +} + +/// Main exception entry point (called from assembly stub). +pub fn handleException(state: *arch.CpuState) void { + if (exception_handlers[state.int_num]) |handler| { + handler(state); + } else { + defaultHandler(state); + } +} + +test "ISR initialization" { + init(); + try std.testing.expect(exception_handlers[0] != null); +} diff --git a/src/kernel/arch/x86_64/keyboard.zig b/src/kernel/arch/x86_64/keyboard.zig new file mode 100644 index 00000000..25766bc9 --- /dev/null +++ b/src/kernel/arch/x86_64/keyboard.zig @@ -0,0 +1,234 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const log = std.log.scoped(.x86_64_keyboard); +const arch = @import("arch.zig"); +const Keyboard = @import("../../keyboard.zig").Keyboard; + +/// PS/2 keyboard ports. +const PS2_DATA_PORT: u16 = 0x60; +const PS2_STATUS_PORT: u16 = 0x64; +const PS2_COMMAND_PORT: u16 = 0x64; + +/// PS/2 commands. +const PS2_CMD_WRITE_OUTPUT_BUFFER: u8 = 0xD2; +const PS2_CMD_ENABLE_FIRST_PORT: u8 = 0xAE; +const PS2_CMD_DISABLE_FIRST_PORT: u8 = 0xAD; +const PS2_CMD_READ_CONFIG: u8 = 0x20; +const PS2_CMD_WRITE_CONFIG: u8 = 0x60; + +/// Scancode set 1 make codes. +const SCANCODE_ESCAPE: u8 = 0x01; +const SCANCODE_BACKSPACE: u8 = 0x0E; +const SCANCODE_TAB: u8 = 0x0F; +const SCANCODE_ENTER: u8 = 0x1C; +const SCANCODE_CTRL: u8 = 0x1D; +const SCANCODE_SHIFT_LEFT: u8 = 0x2A; +const SCANCODE_SHIFT_RIGHT: u8 = 0x36; +const SCANCODE_ALT: u8 = 0x38; +const SCANCODE_CAPS_LOCK: u8 = 0x3A; +const SCANCODE_F1: u8 = 0x3B; +const SCANCODE_F12: u8 = 0x57; + +/// Key states. +var shift_pressed: bool = false; +var ctrl_pressed: bool = false; +var alt_pressed: bool = false; +var caps_lock: bool = false; + +/// Scancode to ASCII mapping (US QWERTY). +const scancode_to_ascii: [59]u8 = .{ + 0, // 0x00 + 27, // 0x01 Escape + '1', // 0x02 + '2', // 0x03 + '3', // 0x04 + '4', // 0x05 + '5', // 0x06 + '6', // 0x07 + '7', // 0x08 + '8', // 0x09 + '9', // 0x0A + '0', // 0x0B + '-', // 0x0C + '=', // 0x0D + 8, // 0x0E Backspace + 9, // 0x0F Tab + 'q', // 0x10 + 'w', // 0x11 + 'e', // 0x12 + 'r', // 0x13 + 't', // 0x14 + 'y', // 0x15 + 'u', // 0x16 + 'i', // 0x17 + 'o', // 0x18 + 'p', // 0x19 + '[', // 0x1A + ']', // 0x1B + 13, // 0x1C Enter + 0, // 0x1D Ctrl + 'a', // 0x1E + 's', // 0x1F + 'd', // 0x20 + 'f', // 0x21 + 'g', // 0x22 + 'h', // 0x23 + 'j', // 0x24 + 'k', // 0x25 + 'l', // 0x26 + ';', // 0x27 + '\'', // 0x28 + '`', // 0x29 + 0, // 0x2A Shift + '\\', // 0x2B + 'z', // 0x2C + 'x', // 0x2D + 'c', // 0x2E + 'v', // 0x2F + 'b', // 0x30 + 'n', // 0x31 + 'm', // 0x32 + ',', // 0x33 + '.', // 0x34 + '/', // 0x35 + 0, // 0x36 Shift + '*', // 0x37 + 0, // 0x38 Alt + ' ', // 0x39 Space + 0, // 0x3A Caps Lock +}; + +/// Wait for keyboard controller to be ready. +fn waitForKeyboard() void { + while ((arch.in(u8, PS2_STATUS_PORT) & 0x02) != 0) { + arch.ioWait(); + } +} + +/// Read a byte from the keyboard. +fn readByte() ?u8 { + if ((arch.in(u8, PS2_STATUS_PORT) & 0x01) != 0) { + return arch.in(u8, PS2_DATA_PORT); + } + return null; +} + +/// Initialize the PS/2 keyboard. +pub fn init(allocator: Allocator) Allocator.Error!*Keyboard { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Disable keyboard temporarily + arch.out(PS2_COMMAND_PORT, PS2_CMD_DISABLE_FIRST_PORT); + + // Clear output buffer + while (readByte()) |_| {} + + // Enable keyboard + arch.out(PS2_COMMAND_PORT, PS2_CMD_ENABLE_FIRST_PORT); + + const keyboard = try allocator.create(Keyboard); + keyboard.* = .{ + .read = readKey, + .peek = peekKey, + }; + + return keyboard; +} + +/// Convert scancode to character. +fn scancodeToChar(scancode: u8) ?u8 { + if (scancode >= scancode_to_ascii.len) { + return null; + } + + var ch = scancode_to_ascii[scancode]; + if (ch == 0) { + return null; + } + + // Handle modifiers + if (shift_pressed) { + if (ch >= 'a' and ch <= 'z') { + ch = ch - 'a' + 'A'; + } else if (ch == '1') { + ch = '!'; + } else if (ch == '2') { + ch = '@'; + } else if (ch == '3') { + ch = '#'; + } else if (ch == '4') { + ch = '$'; + } else if (ch == '5') { + ch = '%'; + } else if (ch == '6') { + ch = '^'; + } else if (ch == '7') { + ch = '&'; + } else if (ch == '8') { + ch = '*'; + } else if (ch == '9') { + ch = '('; + } else if (ch == '0') { + ch = ')'; + } + } + + return ch; +} + +/// Read a key from the keyboard. +fn readKey() ?u8 { + while (true) { + if (readByte()) |scancode| { + // Check for key release (bit 7 set) + const is_release = (scancode & 0x80) != 0; + const key = scancode & 0x7F; + + // Handle modifier keys + switch (key) { + SCANCODE_SHIFT_LEFT, SCANCODE_SHIFT_RIGHT => { + shift_pressed = !is_release; + continue; + }, + SCANCODE_CTRL => { + ctrl_pressed = !is_release; + continue; + }, + SCANCODE_ALT => { + alt_pressed = !is_release; + continue; + }, + SCANCODE_CAPS_LOCK => { + if (!is_release) { + caps_lock = !caps_lock; + } + continue; + }, + else => {}, + } + + // Only process key presses, not releases + if (is_release) { + continue; + } + + return scancodeToChar(key); + } + arch.halt(); + } +} + +/// Peek at available key without consuming it. +fn peekKey() ?u8 { + if (readByte()) |scancode| { + // For now, just consume and return null + // A proper implementation would buffer the key + _ = scancode; + } + return null; +} + +test "keyboard initialization" { + // Mock test +} diff --git a/src/kernel/arch/x86_64/link.ld b/src/kernel/arch/x86_64/link.ld new file mode 100644 index 00000000..c743437c --- /dev/null +++ b/src/kernel/arch/x86_64/link.ld @@ -0,0 +1,77 @@ +/* Linker script for x86_64 kernel */ + +ENTRY(boot) + +KERNEL_PHYSADDR_START = 0x1000; +KERNEL_VADDR_START = 0xFFFFFFFF80000000; +KERNEL_ADDR_OFFSET = KERNEL_VADDR_START - KERNEL_PHYSADDR_START; + +SECTIONS +{ + . = 1M; + + /* Physical start of the kernel */ + KERNEL_PHYSADDR_START = .; + + /* Bootloader section */ + .boot : + { + KEEP(*(.boot)) + } + + /* Multiboot header must be in first 8KB */ + .multiboot ALIGN(4K) : + { + KEEP(*(.multiboot)) + } + + /* Code section */ + .text ALIGN(4K) : + { + *(.text) + *(.text.*) + } + + /* Read-only data section */ + .rodata ALIGN(4K) : + { + *(.rodata) + *(.rodata.*) + } + + /* Data section */ + .data ALIGN(4K) : + { + *(.data) + *(.data.*) + } + + /* BSS section */ + .bss ALIGN(4K) : + { + *(COMMON) + *(.bss) + *(.bss.*) + } + + /* Kernel stack */ + .stack ALIGN(16K) : + { + KERNEL_STACK_START = .; + *(.stack) + . = . + 0x40000; /* 256 KB stack */ + KERNEL_STACK_END = .; + } + + /* Virtual address symbols */ + KERNEL_VADDR_START = . + KERNEL_ADDR_OFFSET; + + /* End symbols */ + KERNEL_PHYSADDR_END = .; + KERNEL_VADDR_END = . + KERNEL_ADDR_OFFSET; + + /DISCARD/ : + { + *(.eh_frame) + } +} diff --git a/src/kernel/arch/x86_64/multiboot.zig b/src/kernel/arch/x86_64/multiboot.zig new file mode 100644 index 00000000..dc541832 --- /dev/null +++ b/src/kernel/arch/x86_64/multiboot.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_multiboot); + +/// Multiboot2 header magic number. +pub const MULTIBOOT2_MAGIC: u32 = 0xE85250D6; + +/// Multiboot2 architecture (0 = i386, 1 = AMD64). +pub const MULTIBOOT2_ARCHITECTURE_AMD64: u32 = 1; + +/// Multiboot2 header length. +pub const MULTIBOOT2_HEADER_LENGTH: u32 = 32; + +/// Multiboot2 checksum. +pub const MULTIBOOT2_CHECKSUM: u32 = -(MULTIBOOT2_MAGIC + MULTIBOOT2_ARCHITECTURE_AMD64 + MULTIBOOT2_HEADER_LENGTH); + +/// Multiboot2 header structure. +pub const Multiboot2Header = extern struct { + /// Magic number. + magic: u32, + /// Architecture (0 = i386, 1 = AMD64). + architecture: u32, + /// Header length. + header_length: u32, + /// Checksum. + checksum: u32, +}; + +/// Multiboot2 information structure. +pub const Multiboot2Info = extern struct { + /// Total size of the structure. + total_size: u32, + /// Reserved (must be 0). + reserved: u32, +}; + +/// Multiboot2 tag types. +pub const TagType = enum(u32) { + end = 0, + boot_loader_name = 1, + module = 3, + basic_meminfo = 4, + bios_boot_device = 5, + memory_map = 6, + vbe_info = 7, + framebuffer_info = 8, + elf_sections = 9, + apm_table = 10, + efi_bs = 11, + efi_32 = 12, + efi_64 = 13, + smbios = 14, + acpi_old = 15, + acpi_new = 16, + networking_info = 17, + efi_mmap = 18, + efi_bs_not_supported = 19, + efi_entry_point = 20, + module_aligned = 21, +}; + +/// Multiboot2 tag header. +pub const Multiboot2Tag = extern struct { + /// Tag type. + type: u32, + /// Tag size (including header). + size: u32, +}; + +/// Memory map entry types. +pub const MemoryMapEntryType = enum(u32) { + available = 1, + reserved = 2, + acpi_reclaimable = 3, + acpi_nvs = 4, + bad_memory = 5, +}; + +/// Memory map entry structure. +pub const MemoryMapEntry = extern struct { + /// Base address (lower 32 bits). + base_addr_low: u32, + /// Base address (upper 32 bits). + base_addr_high: u32, + /// Length (lower 32 bits). + length_low: u32, + /// Length (upper 32 bits). + length_high: u32, + /// Type of memory region. + type: u32, + /// Reserved (must be 0). + zero: u32, + + /// Get full 64-bit base address. + pub fn baseAddr(self: *const MemoryMapEntry) u64 { + return (@as(u64, self.base_addr_high) << 32) | self.base_addr_low; + } + + /// Get full 64-bit length. + pub fn length(self: *const MemoryMapEntry) u64 { + return (@as(u64, self.length_high) << 32) | self.length_low; + } +}; + +/// Module tag structure. +pub const ModuleTag = extern struct { + /// Tag header. + header: Multiboot2Tag, + /// Module start address (lower 32 bits). + mod_start_low: u32, + /// Module start address (upper 32 bits). + mod_start_high: u32, + /// Module end address (lower 32 bits). + mod_end_low: u32, + /// Module end address (upper 32 bits). + mod_end_high: u32, + /// Module command line string. + cmdline: [1]u8, + + /// Get full 64-bit start address. + pub fn modStart(self: *const ModuleTag) u64 { + return (@as(u64, self.mod_start_high) << 32) | self.mod_start_low; + } + + /// Get full 64-bit end address. + pub fn modEnd(self: *const ModuleTag) u64 { + return (@as(u64, self.mod_end_high) << 32) | self.mod_end_low; + } +}; + +/// Iterate over multiboot2 tags. +pub fn findTag(info: *const Multiboot2Info, comptime T: type, wanted_type: TagType) ?*T { + var current_tag = @intToPtr([*]u8, @ptrToInt(info) + @sizeOf(Multiboot2Info)); + const end_ptr = @intToPtr([*]u8, @ptrToInt(info) + info.total_size); + + while (current_tag < end_ptr) { + const tag = @intToPtr(*Multiboot2Tag, current_tag); + + if (tag.type == 0) break; // End tag + + if (tag.type == @intFromEnum(wanted_type)) { + return @intToPtr(*T, current_tag); + } + + // Align to 8 bytes + const next_offset = (tag.size + 7) & @as(usize, ~@as(usize, 7)); + current_tag += next_offset; + } + + return null; +} + +test "multiboot2 structures" { + try std.testing.expectEqual(@as(usize, 16), @sizeOf(Multiboot2Header)); + try std.testing.expectEqual(@as(usize, 8), @sizeOf(Multiboot2Info)); + try std.testing.expectEqual(@as(usize, 8), @sizeOf(Multiboot2Tag)); + try std.testing.expectEqual(@as(usize, 24), @sizeOf(MemoryMapEntry)); +} diff --git a/src/kernel/arch/x86_64/paging.zig b/src/kernel/arch/x86_64/paging.zig new file mode 100644 index 00000000..a584f3c3 --- /dev/null +++ b/src/kernel/arch/x86_64/paging.zig @@ -0,0 +1,208 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_paging); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const panic = @import("../../panic.zig").panic; +const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const MemProfile = @import("../../mem.zig").MemProfile; + +/// Page size constants. +pub const PAGE_SIZE_4KB: usize = 0x1000; +pub const PAGE_SIZE_2MB: usize = 0x200000; +pub const PAGE_SIZE_1GB: usize = 0x40000000; + +/// Number of entries per level. +const ENTRIES_PER_LEVEL: usize = 512; + +/// Bitmasks for PML4, PDPT, PD, and PT entries. +const ENTRY_PRESENT: u64 = 0x1; +const ENTRY_WRITABLE: u64 = 0x2; +const ENTRY_USER: u64 = 0x4; +const ENTRY_WRITE_THROUGH: u64 = 0x8; +const ENTRY_CACHE_DISABLED: u64 = 0x10; +const ENTRY_ACCESSED: u64 = 0x20; +const ENTRY_DIRTY: u64 = 0x40; +const ENTRY_LARGE_PAGE: u64 = 0x80; +const ENTRY_GLOBAL: u64 = 0x100; +const ENTRY_ADDR_MASK: u64 = 0x000FFFFFFFFFF000; + +/// Page map level 4 entry. +pub const Pml4Entry = u64; + +/// Page directory pointer table entry. +pub const PdptEntry = u64; + +/// Page directory entry. +pub const PdEntry = u64; + +/// Page table entry. +pub const PtEntry = u64; + +/// PML4 table structure. +pub const Pml4Table = extern struct { + entries: [ENTRIES_PER_LEVEL]Pml4Entry, +}; + +/// Page directory pointer table structure. +pub const PdptTable = extern struct { + entries: [ENTRIES_PER_LEVEL]PdptEntry, +}; + +/// Page directory structure. +pub const PdTable = extern struct { + entries: [ENTRIES_PER_LEVEL]PdEntry, +}; + +/// Page table structure. +pub const PtTable = extern struct { + entries: [ENTRIES_PER_LEVEL]PtEntry, +}; + +/// Kernel's page map level 4 table. +pub var kernel_pml4: Pml4Table align(PAGE_SIZE_4KB) = .{ + .entries = [_]Pml4Entry{0} ** ENTRIES_PER_LEVEL, +}; + +/// Convert virtual address to PML4 index. +inline fn virtToPml4Idx(virt: usize) usize { + return (virt >> 39) & 0x1FF; +} + +/// Convert virtual address to PDPT index. +inline fn virtToPdptIdx(virt: usize) usize { + return (virt >> 30) & 0x1FF; +} + +/// Convert virtual address to PD index. +inline fn virtToPdIdx(virt: usize) usize { + return (virt >> 21) & 0x1FF; +} + +/// Convert virtual address to PT index. +inline fn virtToPtIdx(virt: usize) usize { + return (virt >> 12) & 0x1FF; +} + +/// Get the page offset from a virtual address. +inline fn virtToOffset(virt: usize) usize { + return virt & 0xFFF; +} + +/// Map a physical address to a virtual address in the given PML4 table. +pub fn map(pml4: *Pml4Table, virt: usize, phys: usize, flags: u64) !void { + const pml4_idx = virtToPml4Idx(virt); + const pdpt_idx = virtToPdptIdx(virt); + const pd_idx = virtToPdIdx(virt); + const pt_idx = virtToPtIdx(virt); + + // Check if PML4 entry exists + if ((pml4.entries[pml4_idx] & ENTRY_PRESENT) == 0) { + // Need to allocate PDPT - for now just return error + // In real implementation, would allocate from physical memory manager + return error.NoMemory; + } + + const pdpt_addr = pml4.entries[pml4_idx] & ENTRY_ADDR_MASK; + const pdpt = @intToPtr(*PdptTable, pdpt_addr); + + // Check if PDPT entry exists + if ((pdpt.entries[pdpt_idx] & ENTRY_PRESENT) == 0) { + return error.NoMemory; + } + + const pd_addr = pdpt.entries[pdpt_idx] & ENTRY_ADDR_MASK; + const pd = @intToPtr(*PdTable, pd_addr); + + // Check if PD entry exists + if ((pd.entries[pd_idx] & ENTRY_PRESENT) == 0) { + return error.NoMemory; + } + + const pt_addr = pd.entries[pd_idx] & ENTRY_ADDR_MASK; + const pt = @intToPtr(*PtTable, pt_addr); + + // Set up the page table entry + pt.entries[pt_idx] = (phys & ENTRY_ADDR_MASK) | flags | ENTRY_PRESENT; + + // Flush TLB for this address + flushTlb(virt); +} + +/// Unmap a virtual address. +pub fn unmap(pml4: *Pml4Table, virt: usize) !void { + const pml4_idx = virtToPml4Idx(virt); + const pdpt_idx = virtToPdptIdx(virt); + const pd_idx = virtToPdIdx(virt); + const pt_idx = virtToPtIdx(virt); + + if ((pml4.entries[pml4_idx] & ENTRY_PRESENT) == 0) { + return error.NotMapped; + } + + const pdpt_addr = pml4.entries[pml4_idx] & ENTRY_ADDR_MASK; + const pdpt = @intToPtr(*PdptTable, pdpt_addr); + + if ((pdpt.entries[pdpt_idx] & ENTRY_PRESENT) == 0) { + return error.NotMapped; + } + + const pd_addr = pdpt.entries[pdpt_idx] & ENTRY_ADDR_MASK; + const pd = @intToPtr(*PdTable, pd_addr); + + if ((pd.entries[pd_idx] & ENTRY_PRESENT) == 0) { + return error.NotMapped; + } + + const pt_addr = pd.entries[pd_idx] & ENTRY_ADDR_MASK; + const pt = @intToPtr(*PtTable, pt_addr); + + pt.entries[pt_idx] = 0; + flushTlb(virt); +} + +/// Flush TLB for a specific address. +fn flushTlb(virt: usize) void { + asm volatile ("invlpg [%[addr]]" :: [addr] "r" (virt) : "memory"); +} + +/// Load CR3 with the physical address of the PML4 table. +pub fn loadCr3(pml4_phys: usize) void { + asm volatile ("mov cr3, %[val]" :: [val] "r" (pml4_phys) : "memory"); +} + +/// Read CR3 register. +pub fn readCr3() usize { + var val: usize = undefined; + asm volatile ("mov %[val], cr3" : [val] "=r" (val)); + return val; +} + +/// Initialize paging for x86_64. +pub fn init(mem_profile: *const MemProfile) void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Clear the kernel PML4 + for (kernel_pml4.entries) |*entry| { + entry.* = 0; + } + + // Identity map the first 2MB for bootloader compatibility + // This is a simplified setup - full implementation would map all physical memory + + // Load the kernel PML4 + const pml4_phys = mem.virtToPhys(@ptrToInt(&kernel_pml4)); + loadCr3(pml4_phys); +} + +/// Switch to a different address space. +pub fn switchAddressSpace(pml4_phys: usize) void { + loadCr3(pml4_phys); +} + +test "paging sizes" { + try std.testing.expectEqual(@as(usize, 4096), @sizeOf(Pml4Table)); + try std.testing.expectEqual(@as(usize, 4096), @sizeOf(PdptTable)); + try std.testing.expectEqual(@as(usize, 4096), @sizeOf(PdTable)); + try std.testing.expectEqual(@as(usize, 4096), @sizeOf(PtTable)); +} diff --git a/src/kernel/arch/x86_64/pci.zig b/src/kernel/arch/x86_64/pci.zig new file mode 100644 index 00000000..b39fa520 --- /dev/null +++ b/src/kernel/arch/x86_64/pci.zig @@ -0,0 +1,132 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_pci); +const Allocator = std.mem.Allocator; +const arch = @import("arch.zig"); + +/// PCI configuration space ports. +const PCI_CONFIG_ADDR: u16 = 0xCF8; +const PCI_CONFIG_DATA: u16 = 0xCFC; + +/// PCI configuration address format. +fn makePciAddr(bus: u8, slot: u8, func: u8, offset: u8) u32 { + return 0x80000000 | (@as(u32, bus) << 16) | (@as(u32, slot) << 11) | (@as(u32, func) << 8) | offset; +} + +/// Read from PCI configuration space. +fn pciRead(addr: u32) u32 { + arch.out(PCI_CONFIG_ADDR, addr); + return arch.in(u32, PCI_CONFIG_DATA); +} + +/// Write to PCI configuration space. +fn pciWrite(addr: u32, value: u32) void { + arch.out(PCI_CONFIG_ADDR, addr); + arch.out(PCI_CONFIG_DATA, value); +} + +/// Get vendor ID from a PCI device. +fn getVendorId(bus: u8, slot: u8, func: u8) u16 { + const addr = makePciAddr(bus, slot, func, 0); + const value = pciRead(addr); + return @truncate(u16, value); +} + +/// Get device ID from a PCI device. +fn getDeviceId(bus: u8, slot: u8, func: u8) u16 { + const addr = makePciAddr(bus, slot, func, 0); + const value = pciRead(addr); + return @truncate(u16, value >> 16); +} + +/// Get class code from a PCI device. +fn getClassCode(bus: u8, slot: u8, func: u8) u8 { + const addr = makePciAddr(bus, slot, func, 8); + const value = pciRead(addr); + return @truncate(u8, value >> 24); +} + +/// Get subclass from a PCI device. +fn getSubclass(bus: u8, slot: u8, func: u8) u8 { + const addr = makePciAddr(bus, slot, func, 8); + const value = pciRead(addr); + return @truncate(u8, value >> 16); +} + +/// Get programming interface from a PCI device. +fn getProgIf(bus: u8, slot: u8, func: u8) u8 { + const addr = makePciAddr(bus, slot, func, 8); + const value = pciRead(addr); + return @truncate(u8, value >> 8); +} + +/// Get BAR (Base Address Register) from a PCI device. +fn getBar(bus: u8, slot: u8, func: u8, bar_num: u8) u32 { + const offset = 0x10 + (bar_num * 4); + const addr = makePciAddr(bus, slot, func, offset); + return pciRead(addr); +} + +/// PCI device information structure. +pub const PciDeviceInfo = struct { + bus: u8, + slot: u8, + func: u8, + vendor_id: u16, + device_id: u16, + class_code: u8, + subclass: u8, + prog_if: u8, + bars: [6]u32, +}; + +/// Enumerate all PCI devices. +pub fn getDevices(allocator: Allocator) Allocator.Error![]PciDeviceInfo { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + var devices = std.ArrayList(PciDeviceInfo).init(allocator); + errdefer devices.deinit(); + + // Scan all buses, slots, and functions + for (0..8) |bus| { + for (0..32) |slot| { + for (0..8) |func| { + const vendor_id = getVendorId(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func)); + + // Skip invalid devices (vendor_id 0xFFFF means no device) + if (vendor_id == 0xFFFF) { + // If func is 0 and we got an invalid device, there are no more functions + if (func == 0) break; + continue; + } + + const device_info = PciDeviceInfo{ + .bus = @intCast(u8, bus), + .slot = @intCast(u8, slot), + .func = @intCast(u8, func), + .vendor_id = vendor_id, + .device_id = getDeviceId(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func)), + .class_code = getClassCode(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func)), + .subclass = getSubclass(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func)), + .prog_if = getProgIf(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func)), + .bars = .{ + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 0), + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 1), + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 2), + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 3), + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 4), + getBar(@intCast(u8, bus), @intCast(u8, slot), @intCast(u8, func), 5), + }, + }; + + try devices.append(device_info); + } + } + } + + return devices.toOwnedSlice(); +} + +test "PCI enumeration" { + // Mock test - actual PCI enumeration requires hardware +} diff --git a/src/kernel/arch/x86_64/pit.zig b/src/kernel/arch/x86_64/pit.zig new file mode 100644 index 00000000..30359678 --- /dev/null +++ b/src/kernel/arch/x86_64/pit.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_pit); +const arch = @import("arch.zig"); + +/// PIT ports. +const PIT_CHANNEL0: u16 = 0x40; +const PIT_COMMAND: u16 = 0x43; + +/// Default frequency (100 Hz). +pub const DEFAULT_FREQUENCY: u32 = 100; + +/// PIT divisor for a given frequency. +fn pitDivisor(frequency: u32) u16 { + const PIT_BASE_FREQ: u32 = 1193182; + return @truncate(u16, PIT_BASE_FREQ / frequency); +} + +/// Initialize the PIT timer. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + setFrequency(DEFAULT_FREQUENCY); +} + +/// Set the PIT frequency. +pub fn setFrequency(frequency: u32) void { + const divisor = pitDivisor(frequency); + + // Send command byte: channel 0, lobyte/hibyte, square wave generator, binary + arch.out(PIT_COMMAND, 0x36); + + // Send divisor + arch.out(PIT_CHANNEL0, @truncate(u8, divisor)); + arch.out(PIT_CHANNEL0, @truncate(u8, divisor >> 8)); +} + +test "PIT initialization" { + init(); +} diff --git a/src/kernel/arch/x86_64/rtc.zig b/src/kernel/arch/x86_64/rtc.zig new file mode 100644 index 00000000..3f9be2b7 --- /dev/null +++ b/src/kernel/arch/x86_64/rtc.zig @@ -0,0 +1,134 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_rtc); +const arch = @import("arch.zig"); + +/// RTC ports. +const CMOS_ADDR: u16 = 0x70; +const CMOS_DATA: u16 = 0x71; + +/// RTC registers. +const RTC_SECOND: u8 = 0x00; +const RTC_MINUTE: u8 = 0x02; +const RTC_HOUR: u8 = 0x04; +const RTC_DAY: u8 = 0x07; +const RTC_MONTH: u8 = 0x08; +const RTC_YEAR: u8 = 0x09; +const RTC_STATUS_A: u8 = 0x0A; +const RTC_STATUS_B: u8 = 0x0B; + +/// Date/time structure. +pub const DateTime = struct { + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, +}; + +/// Read from CMOS register. +fn cmosRead(reg: u8) u8 { + arch.out(CMOS_ADDR, reg); + return arch.in(u8, CMOS_DATA); +} + +/// Write to CMOS register. +fn cmosWrite(reg: u8, value: u8) void { + arch.out(CMOS_ADDR, reg); + arch.out(CMOS_DATA, value); +} + +/// Check if RTC update is in progress. +fn rtcUpdateInProgress() bool { + return (cmosRead(RTC_STATUS_A) & 0x80) != 0; +} + +/// Get current date and time from RTC. +pub fn getDateTime() DateTime { + var sec: u8 = 0; + var min: u8 = 0; + var hour: u8 = 0; + var day: u8 = 0; + var month: u8 = 0; + var year: u8 = 0; + + // Wait for update to finish, then read all values + while (rtcUpdateInProgress()) {} + + sec = cmosRead(RTC_SECOND); + min = cmosRead(RTC_MINUTE); + hour = cmosRead(RTC_HOUR); + day = cmosRead(RTC_DAY); + month = cmosRead(RTC_MONTH); + year = cmosRead(RTC_YEAR); + + // Check if we got interrupted during reading + while (rtcUpdateInProgress()) { + // Re-read if interrupted + sec = cmosRead(RTC_SECOND); + min = cmosRead(RTC_MINUTE); + hour = cmosRead(RTC_HOUR); + day = cmosRead(RTC_DAY); + month = cmosRead(RTC_MONTH); + year = cmosRead(RTC_YEAR); + } + + // Check status B for BCD/binary mode + const status_b = cmosRead(RTC_STATUS_B); + const use_bcd = (status_b & 0x04) == 0; + + // Convert from BCD if necessary + if (use_bcd) { + sec = (sec & 0x0F) + ((sec / 16) * 10); + min = (min & 0x0F) + ((min / 16) * 10); + hour = (hour & 0x0F) + ((hour / 16) * 10); + day = (day & 0x0F) + ((day / 16) * 10); + month = (month & 0x0F) + ((month / 16) * 10); + year = (year & 0x0F) + ((year / 16) * 10); + } + + // Handle 12-hour format + if ((status_b & 0x02) == 0) { + // 12-hour format + if ((hour & 0x80) != 0) { + // PM + hour = ((hour & 0x7F) + 12) % 24; + } else { + // AM + hour = hour % 12; + } + } else { + // 24-hour format, just clear the high bit + hour = hour & 0x3F; + } + + // Convert year to full year (assume 20xx for now) + const full_year: u16 = 2000 + year; + + return .{ + .year = full_year, + .month = month, + .day = day, + .hour = hour, + .minute = min, + .second = sec, + }; +} + +/// Initialize RTC. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Enable binary mode and 24-hour format + const status_b = cmosRead(RTC_STATUS_B); + cmosWrite(RTC_STATUS_B, status_b | 0x02 | 0x04); +} + +test "RTC initialization" { + init(); + const dt = getDateTime(); + try std.testing.expect(dt.year >= 2024); + try std.testing.expect(dt.month >= 1 and dt.month <= 12); + try std.testing.expect(dt.day >= 1 and dt.day <= 31); +} diff --git a/src/kernel/arch/x86_64/serial.zig b/src/kernel/arch/x86_64/serial.zig new file mode 100644 index 00000000..1531d397 --- /dev/null +++ b/src/kernel/arch/x86_64/serial.zig @@ -0,0 +1,80 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_serial); +const arch = @import("arch.zig"); + +/// Serial port base addresses. +pub const COM1_BASE: u16 = 0x3F8; +pub const COM2_BASE: u16 = 0x2F8; +pub const COM3_BASE: u16 = 0x3E8; +pub const COM4_BASE: u16 = 0x2E8; + +/// Default baud rate. +pub const DEFAULT_BAUDRATE: u32 = 115200; + +/// Serial port offsets. +const OFFSET_RX: u16 = 0; +const OFFSET_TX: u16 = 0; +const OFFSET_IER: u16 = 1; +const OFFSET_FCR: u16 = 2; +const OFFSET_LCR: u16 = 3; +const OFFSET_MCR: u16 = 4; +const OFFSET_LSR: u16 = 5; + +/// Line status register bits. +const LSR_DATA_READY: u8 = 0x01; +const LSR_THR_EMPTY: u8 = 0x20; + +/// Initialize a serial port. +pub fn init(baudrate: u32, base: u16) !void { + log.info("Init (COM{d}, {d} baud)\\n", .{ ((base - COM1_BASE) / 0x100) + 1, baudrate }); + defer log.info("Done\\n", .{}); + + // Disable interrupts + arch.out(base + OFFSET_IER, 0x00); + + // Enable DLAB (set baud rate divisor) + arch.out(base + OFFSET_LCR, 0x80); + + // Set divisor for baud rate + const divisor = 115200 / baudrate; + arch.out(base + OFFSET_RX, @truncate(u8, divisor)); + arch.out(base + OFFSET_IER, @truncate(u8, divisor >> 8)); + + // Clear DLAB, set 8N1 + arch.out(base + OFFSET_LCR, 0x03); + + // Enable FIFO + arch.out(base + OFFSET_FCR, 0xC7); + + // Enable interrupts, RTS/DSR + arch.out(base + OFFSET_MCR, 0x0B); +} + +/// Check if data is available to read. +pub fn canRead(base: u16) bool { + return (arch.in(u8, base + OFFSET_LSR) & LSR_DATA_READY) != 0; +} + +/// Read a byte from the serial port. +pub fn read(base: u16) ?u8 { + if (!canRead(base)) { + return null; + } + return arch.in(u8, base + OFFSET_RX); +} + +/// Check if we can write to the serial port. +pub fn canWrite(base: u16) bool { + return (arch.in(u8, base + OFFSET_LSR) & LSR_THR_EMPTY) != 0; +} + +/// Write a byte to the serial port. +pub fn write(byte: u8, base: u16) void { + while (!canWrite(base)) {} + arch.out(base + OFFSET_TX, byte); +} + +test "serial initialization" { + // Test with COM1 + try init(DEFAULT_BAUDRATE, COM1_BASE); +} diff --git a/src/kernel/arch/x86_64/syscalls.zig b/src/kernel/arch/x86_64/syscalls.zig new file mode 100644 index 00000000..be57c99f --- /dev/null +++ b/src/kernel/arch/x86_64/syscalls.zig @@ -0,0 +1,102 @@ +const std = @import("std"); +const log = std.log.scoped(.x86_64_syscalls); +const arch = @import("arch.zig"); + +/// Syscall handler function type. +pub const SyscallHandler = fn (u64, u64, u64, u64, u64, u64) callconv(.C) u64; + +/// Maximum number of syscalls. +const MAX_SYSCALLS: usize = 256; + +/// Array of syscall handlers. +var syscall_handlers: [MAX_SYSCALLS]?SyscallHandler = [_]?SyscallHandler{null} ** MAX_SYSCALLS; + +/// Register a syscall handler. +pub fn registerHandler(syscall_num: u64, handler: SyscallHandler) !void { + if (syscall_num >= MAX_SYSCALLS) { + return error.InvalidSyscallNumber; + } + syscall_handlers[syscall_num] = handler; +} + +/// Default syscall handler (returns error). +fn defaultHandler(_: u64, _: u64, _: u64, _: u64, _: u64, _: u64) callconv(.C) u64 { + return @intCast(u64, -1); // Error +} + +/// Initialize syscall subsystem. +pub fn init() void { + log.info("Init\\n", .{}); + defer log.info("Done\\n", .{}); + + // Set up syscall/sysret MSRs for fast system calls + setupSyscallMsrs(); +} + +/// Set up SYSCALL/SYSRET MSRs. +fn setupSyscallMsrs() void { + const STAR_MSR: u32 = 0xC0000081; // SYSCALL Target Address Register + const LSTAR_MSR: u32 = 0xC0000082; // Long Mode SYSCALL Target Address + const SFMASK_MSR: u32 = 0xC0000084; // SYSCALL Flag Mask + + // Write to STAR MSR (legacy syscall target, not used in long mode) + wrmsr(STAR_MSR, 0); + + // Write the address of our syscall handler to LSTAR + const handler_addr = @ptrToInt(syscallEntry); + wrmsr(LSTAR_MSR, handler_addr); + + // Set SFMASK to mask interrupts during syscall + wrmsr(SFMASK_MSR, 0x200); // Mask IF flag +} + +/// Write to Model Specific Register. +fn wrmsr(msr: u32, value: u64) void { + const low = @truncate(u32, value); + const high = @truncate(u32, value >> 32); + asm volatile ("wrmsr" + : + : "{ecx}" (msr), + "{eax}" (low), + "{edx}" (high), + : "memory" + ); +} + +/// Read from Model Specific Register. +fn rdmsr(msr: u32) u64 { + var low: u32 = undefined; + var high: u32 = undefined; + asm volatile ("rdmsr" + : [low] "={eax}" (low), + [high] "={edx}" (high), + : "{ecx}" (msr), + ); + return (@as(u64, high) << 32) | low; +} + +/// Syscall entry point (called by SYSCALL instruction). +fn syscallEntry() noreturn { + // Save registers on stack (handled by assembly stub) + // In a real implementation, this would be assembly that: + // 1. Saves user registers + // 2. Extracts syscall number from RAX + // 3. Calls the appropriate handler + // 4. Returns result in RAX + // 5. Executes SYSRET + + // For now, just halt + arch.haltNoInterrupts(); +} + +/// Handle a syscall (called from assembly). +pub fn handleSyscall(syscall_num: u64, arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) u64 { + if (syscall_handlers[syscall_num]) |handler| { + return handler(arg1, arg2, arg3, arg4, arg5, 0); + } + return defaultHandler(syscall_num, arg1, arg2, arg3, arg4, arg5); +} + +test "syscall initialization" { + init(); +}