; ============================================================================ ; Pactor64 Kernel Entry — x86-64 Long Mode via Multiboot2 ; ; GRUB2 loads us in 32-bit protected mode. We transition to 64-bit long mode. ; Supports ring 3 (userspace) via GDT, TSS, SYSCALL/SYSRET. ; ============================================================================ ; === Multiboot2 Constants === MB2_MAGIC equ 0xE85250D6 MB2_ARCH equ 0 ; i386 MB2_HEADER_LEN equ mb2_header_end - mb2_header MB2_CHECKSUM equ -(MB2_MAGIC + MB2_ARCH + MB2_HEADER_LEN) ; === Page Table Addresses (must be 4KB aligned) === PML4_ADDR equ 0x1000 PDPT_ADDR equ 0x2000 PD_ADDR equ 0x3000 ; === Page Table Flags === PAGE_PRESENT equ 0x01 PAGE_WRITE equ 0x02 PAGE_SIZE equ 0x80 ; 2MB page (PS bit) ; === 64-bit GDT selectors === CODE64_SEL equ 0x08 DATA64_SEL equ 0x10 USER_CS_SEL equ 0x18 ; GDT index 3 (DPL=3 → selector 0x18|3 = 0x1B) USER_DS_SEL equ 0x20 ; GDT index 4 (DPL=3 → selector 0x20|3 = 0x23) TSS_SEL equ 0x28 ; GDT index 5 ; === MSRs === IA32_EFER equ 0xC0000080 EFER_LME equ 0x100 ; Long Mode Enable EFER_SCE equ 0x001 ; SYSCALL Enable ; === CR Bits === CR0_PE equ 0x00000001 CR0_PG equ 0x80000000 CR4_PAE equ 0x00000020 ; === VGA === VGA_BUFFER equ 0xB8000 ; === Serial === COM1 equ 0x3F8 section .multiboot align 8 mb2_header: dd MB2_MAGIC dd MB2_ARCH dd MB2_HEADER_LEN dd MB2_CHECKSUM ; End tag (required) dw 0 ; type dw 0 ; flags dd 8 ; size mb2_header_end: ; === 32-bit Entry Point (called by GRUB) === section .text bits 32 global _start extern kernel_main_64 _start: ; Save multiboot info pointer mov [mb2_info_ptr], ebx mov [mb2_magic_ptr], eax ; Disable interrupts cli ; Print 'P' to serial to confirm we got here mov al, 'P' call serial_out_32 ; === Step 1: Check CPUID for long mode support === mov eax, 0x80000000 cpuid cmp eax, 0x80000001 jb .no_long_mode mov eax, 0x80000001 cpuid test edx, (1 << 29) ; LM bit jz .no_long_mode mov al, 'L' call serial_out_32 ; === Step 2: Set up page tables for long mode === ; Identity-map first 1GB using 2MB pages ; PML4[0] → PDPT, PDPT[0] → PD, PD[0..511] → 2MB pages ; Clear page table area (4KB each) mov edi, PML4_ADDR mov ecx, 4096 * 4 / 4 ; 4 pages = 16KB xor eax, eax rep stosd ; PML4[0] = PDPT_ADDR | PRESENT | WRITE mov dword [PML4_ADDR], PDPT_ADDR | PAGE_PRESENT | PAGE_WRITE ; PDPT[0] = PD_ADDR | PRESENT | WRITE mov dword [PDPT_ADDR], PD_ADDR | PAGE_PRESENT | PAGE_WRITE ; PD[0..511] = 2MB pages, identity mapped mov edi, PD_ADDR mov eax, PAGE_PRESENT | PAGE_WRITE | PAGE_SIZE ; First 2MB page at 0 mov ecx, 512 .fill_pd: stosd push eax mov eax, 0 stosd ; High 32 bits = 0 pop eax add eax, 0x200000 ; Next 2MB page loop .fill_pd mov al, 'T' call serial_out_32 ; === Step 3: Load PML4 into CR3 === mov eax, PML4_ADDR mov cr3, eax ; === Step 4: Enable PAE (CR4.PAE) === mov eax, cr4 or eax, CR4_PAE mov cr4, eax ; === Step 5: Enable Long Mode (IA32_EFER.LME) === mov ecx, IA32_EFER rdmsr or eax, EFER_LME | EFER_SCE wrmsr ; === Step 6: Enable Paging (CR0.PG) === mov eax, cr0 or eax, CR0_PG mov cr0, eax mov al, 'M' call serial_out_32 ; === Step 7: Load initial 64-bit GDT and far jump === lgdt [gdt64_boot_ptr] jmp CODE64_SEL:long_mode_entry .no_long_mode: mov al, '!' call serial_out_32 cli hlt ; === 32-bit Serial Output (for boot debug) === serial_out_32: push edx push eax mov dx, COM1 + 5 .wait: in al, dx test al, 0x20 jz .wait pop eax mov dx, COM1 out dx, al pop edx ret ; === Boot GDT (minimal, used only for 32→64 mode switch) === align 8 gdt64_boot: dq 0 ; Null descriptor dq 0x00AF9A000000FFFF ; 64-bit code: L=1, D=0, P=1, S=1, E=1, R=1 dq 0x00CF92000000FFFF ; 64-bit data: P=1, S=1, W=1 gdt64_boot_ptr: dw gdt64_boot_ptr - gdt64_boot - 1 ; Limit dq gdt64_boot ; Base ; === Multiboot info (saved from GRUB) === section .data align 8 mb2_info_ptr: dq 0 mb2_magic_ptr: dq 0 ; ============================================================================ ; Full 64-bit GDT with ring 3 segments and TSS ; Loaded after entering long mode. Index layout: ; 0: Null (selector 0x00) ; 1: Kern code (selector 0x08) — DPL=0, L=1 ; 2: Kern data (selector 0x10) — DPL=0 ; 3: User code (selector 0x18) — DPL=3, L=1 ; 4: User data (selector 0x20) — DPL=3 ; 5: TSS (selector 0x28) — 16 bytes, filled at runtime ; ============================================================================ align 8 global gdt64_full gdt64_full: dq 0 ; Null (index 0) dq 0x00AF9A000000FFFF ; Kernel code (index 1, sel 0x08) dq 0x00CF92000000FFFF ; Kernel data (index 2, sel 0x10) dq 0x00AFFA000000FFFF ; User code (index 3, sel 0x18) dq 0x00CFF2000000FFFF ; User data (index 4, sel 0x20) ; TSS descriptor (index 5, sel 0x28) — 16 bytes, filled at runtime dq 0 dq 0 gdt64_full_ptr: dw gdt64_full_ptr - gdt64_full - 1 ; Limit = 55 dq gdt64_full ; Base ; ============================================================================ ; TSS (Task State Segment) for 64-bit mode ; Used for RSP0 (kernel stack on SYSCALL entry from ring 3) ; ============================================================================ align 16 global tss_struct tss_struct: dd 0 ; Reserved (offset 0) dq 0 ; RSP0 (offset 4) — filled at runtime dq 0 ; RSP1 (offset 12) dq 0 ; RSP2 (offset 20) dq 0 ; Reserved (offset 28) dq 0 ; IST1 (offset 36) dq 0 ; IST2 (offset 44) dq 0 ; IST3 (offset 52) dq 0 ; IST4 (offset 60) dq 0 ; IST5 (offset 68) dq 0 ; IST6 (offset 76) dq 0 ; IST7 (offset 84) dq 0 ; Reserved (offset 92) dw 0 ; Reserved (offset 100) dw 0 ; I/O Map Base (offset 102) — no I/O bitmap tss_struct_end: ; ============================================================================ ; 64-bit Long Mode Entry ; ============================================================================ section .text bits 64 long_mode_entry: ; === Fill TSS descriptor in the full GDT === ; TSS descriptor is at GDT index 5, offset 40 (5*8) ; Format: Limit[15:0], Base[15:0], Base[23:16], Access, Flags|Limit[19:16], ; Base[31:24], Base[63:32], Reserved lea rax, [rel tss_struct] mov word [rel gdt64_full + 40], (tss_struct_end - tss_struct - 1) ; Limit mov word [rel gdt64_full + 42], ax ; Base[15:0] shr rax, 16 mov byte [rel gdt64_full + 44], al ; Base[23:16] mov byte [rel gdt64_full + 45], 0x89 ; Access: P=1, DPL=0, Type=9 mov byte [rel gdt64_full + 46], 0x00 ; Flags + Limit[19:16] shr rax, 8 mov byte [rel gdt64_full + 47], al ; Base[31:24] lea rax, [rel tss_struct] shr rax, 32 mov dword [rel gdt64_full + 48], eax ; Base[63:32] mov dword [rel gdt64_full + 52], 0 ; Reserved ; === Load the full GDT === lgdt [rel gdt64_full_ptr] ; === Reload CS with a far return === push qword CODE64_SEL lea rax, [rel .cs_reload] push rax o64 retf .cs_reload: ; === Set up 64-bit data segments === mov ax, DATA64_SEL mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax ; === Set up kernel stack (16KB) === mov rsp, kernel_stack_top ; === Set RSP0 in TSS (kernel stack for SYSCALL entry from ring 3) === mov qword [rel tss_struct + 4], kernel_stack_top ; === Load TSS === mov ax, TSS_SEL ltr ax ; Zero all general-purpose registers xor rax, rax xor rbx, rbx xor rcx, rcx xor rdx, rdx xor rsi, rsi xor rdi, rdi xor rbp, rbp xor r8, r8 xor r9, r9 xor r10, r10 xor r11, r11 xor r12, r12 xor r13, r13 xor r14, r14 xor r15, r15 ; Print '6' to serial to confirm 64-bit mode mov al, '6' mov dx, COM1 out dx, al ; Pass multiboot info to kernel mov rdi, [mb2_info_ptr] mov rsi, [mb2_magic_ptr] call kernel_main_64 ; If kernel returns, halt cli hlt jmp $ ; === Kernel Stack (16KB) === section .bss align 16 global kernel_stack_top kernel_stack_bottom: resb 16384 ; 16KB stack kernel_stack_top: