mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Boot to C in 64-bit long mode
This commit is contained in:
parent
53f1295927
commit
7d736cd76d
14 changed files with 668 additions and 46 deletions
243
kernel/arch/x86_64/boot.asm
Normal file
243
kernel/arch/x86_64/boot.asm
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
; Nadir stage-1 boot sector.
|
||||
;
|
||||
; Runs in 16-bit real mode at 0x7C00 (Ring 0, CPL 0, BIOS hands us full
|
||||
; privilege; privileged instructions used: cli, lgdt, mov cr0).
|
||||
; Fault behavior: none possible here beyond a failed disk read, which prints
|
||||
; an error and halts. There is no IDT yet, so any CPU exception would
|
||||
; triple-fault and reset the machine.
|
||||
;
|
||||
; Job: enable A20, load the kernel image (LBA 1..N) to 0x10000, install a
|
||||
; flat GDT, enter 32-bit protected mode and jump to the stage-2 entry.
|
||||
;
|
||||
; Memory map used by the whole boot chain:
|
||||
; 0x007C00 this sector (512 bytes)
|
||||
; 0x010000 kernel image (loaded here, linked here)
|
||||
; 0x70000 PML4 | 0x71000 PDPT | 0x72000 PD (built by stage 2)
|
||||
; 0x90000 stack top (real-mode stack grows down from 0x7C00 instead)
|
||||
; 0xB8000 VGA text buffer
|
||||
|
||||
bits 16
|
||||
org 0x7C00
|
||||
|
||||
%ifndef KERNEL_SECTORS
|
||||
%error "KERNEL_SECTORS not defined - build with make"
|
||||
%endif
|
||||
|
||||
KERNEL_LOAD_SEG equ 0x1000 ; ES for load target 0x1000:0x0000 = 0x10000
|
||||
STACK_TOP equ 0x7C00 ; real-mode stack grows down from here
|
||||
|
||||
start:
|
||||
cli
|
||||
xor ax, ax
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov ss, ax
|
||||
mov sp, STACK_TOP
|
||||
sti
|
||||
mov [boot_drive], dl ; BIOS passes the boot drive in DL
|
||||
|
||||
mov si, msg_loading
|
||||
call puts
|
||||
|
||||
call enable_a20
|
||||
call read_drive_params
|
||||
call load_kernel
|
||||
|
||||
mov si, msg_entering
|
||||
call puts
|
||||
|
||||
cli
|
||||
lgdt [gdt_ptr]
|
||||
mov eax, cr0
|
||||
or eax, 1
|
||||
mov cr0, eax
|
||||
jmp 0x08:prot32
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; puts: print NUL-terminated string at DS:SI via BIOS teletype.
|
||||
; Clobbers AX, BX, SI.
|
||||
puts:
|
||||
push ax
|
||||
push bx
|
||||
mov ah, 0x0E
|
||||
mov bh, 0
|
||||
.loop:
|
||||
lodsb
|
||||
test al, al
|
||||
jz .done
|
||||
int 0x10
|
||||
jmp .loop
|
||||
.done:
|
||||
pop bx
|
||||
pop ax
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; enable_a20: try BIOS, keyboard-controller and fast-A20 methods in turn.
|
||||
; Each is harmless if A20 is already on.
|
||||
enable_a20:
|
||||
mov ax, 0x2401 ; BIOS: enable A20
|
||||
int 0x15
|
||||
call kbd_wait ; keyboard controller: pulse output port
|
||||
mov al, 0xD1
|
||||
out 0x64, al
|
||||
call kbd_wait
|
||||
mov al, 0xDF
|
||||
out 0x60, al
|
||||
call kbd_wait
|
||||
in al, 0x92 ; fast A20 (skip if already set or absent)
|
||||
test al, 0x02
|
||||
jnz .done
|
||||
or al, 0x02
|
||||
out 0x92, al
|
||||
.done:
|
||||
ret
|
||||
|
||||
kbd_wait:
|
||||
in al, 0x64
|
||||
test al, 0x02
|
||||
jnz kbd_wait
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; read_drive_params: query sectors/track and head count via int 0x13 AH=08.
|
||||
; Falls back to floppy defaults (18 spt, 2 heads) on failure or nonsense.
|
||||
read_drive_params:
|
||||
xor ax, ax
|
||||
mov ds, ax ; re-establish DS (BIOS may clobber it)
|
||||
mov ah, 0x08
|
||||
mov dl, [boot_drive]
|
||||
int 0x13
|
||||
jc .defaults
|
||||
and cx, 0x3F ; CL[5:0] = max sector = sectors per track
|
||||
jz .defaults
|
||||
mov [spt], cl
|
||||
inc dh ; DH = max head -> head count
|
||||
jz .defaults
|
||||
mov [heads], dh
|
||||
ret
|
||||
.defaults:
|
||||
mov byte [spt], 18
|
||||
mov byte [heads], 2
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; lba_to_chs: convert LBA in AX to int 0x13 CHS in CH/CL/DH.
|
||||
; Clobbers AX, BX, CX, DX.
|
||||
lba_to_chs:
|
||||
xor dx, dx
|
||||
div word [spt] ; AX = LBA/spt, DX = sector offset
|
||||
inc dx ; sectors are 1-based
|
||||
push dx ; save sector number
|
||||
xor dx, dx
|
||||
div word [heads] ; AX = cylinder, DX = head
|
||||
mov dh, dl
|
||||
mov bx, ax ; BX = cylinder
|
||||
pop ax ; AL = sector
|
||||
mov cl, al
|
||||
mov ch, bl ; CH = cylinder bits 0-7
|
||||
shr bx, 2
|
||||
and bl, 0xC0
|
||||
or cl, bl ; CL[7:6] = cylinder bits 8-9
|
||||
ret
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; load_kernel: read KERNEL_SECTORS sectors starting at LBA 1 to 0x10000.
|
||||
; Halts with an error message after 3 failed retries per sector.
|
||||
;
|
||||
; NOTE: no register value is trusted across int 0x13 — SeaBIOS/real BIOSes
|
||||
; may clobber general registers (observed: BX zeroed) and disturb the
|
||||
; stack, so all loop state lives in memory and ES:BX/DS/DL are reloaded
|
||||
; from memory before every disk call.
|
||||
load_kernel:
|
||||
xor ax, ax
|
||||
mov ds, ax ; re-establish DS (BIOS may clobber it)
|
||||
mov ax, KERNEL_LOAD_SEG
|
||||
mov [buf_seg], ax
|
||||
mov word [buf_off], 0
|
||||
mov word [lba], 1
|
||||
mov ax, KERNEL_SECTORS
|
||||
mov [sectors_left], ax
|
||||
.next:
|
||||
mov ax, [lba]
|
||||
call lba_to_chs
|
||||
mov byte [tries], 3
|
||||
.attempt:
|
||||
mov ax, [buf_seg] ; reload buffer: ES:BX must not survive
|
||||
mov es, ax ; a BIOS call
|
||||
mov bx, [buf_off]
|
||||
mov dl, [boot_drive]
|
||||
mov ah, 0x02 ; read 1 sector to ES:BX
|
||||
mov al, 1
|
||||
int 0x13
|
||||
xor ax, ax
|
||||
mov ds, ax ; DS may not have survived either
|
||||
jnc .ok
|
||||
mov ah, 0x00 ; reset drive, then retry
|
||||
mov dl, [boot_drive]
|
||||
int 0x13
|
||||
xor ax, ax
|
||||
mov ds, ax
|
||||
dec byte [tries]
|
||||
jnz .attempt
|
||||
mov si, msg_disk_err
|
||||
call puts
|
||||
jmp halt
|
||||
.ok:
|
||||
add word [buf_off], 512 ; advance buffer past the sector
|
||||
jnc .no_carry
|
||||
add word [buf_seg], 0x1000 ; +64K on segment wrap
|
||||
.no_carry:
|
||||
inc word [lba]
|
||||
dec word [sectors_left]
|
||||
jnz .next
|
||||
ret
|
||||
|
||||
halt:
|
||||
cli
|
||||
.hang:
|
||||
hlt
|
||||
jmp .hang
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
bits 32
|
||||
prot32:
|
||||
mov ax, 0x10 ; flat data segment
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
mov esp, 0x90000
|
||||
mov eax, 0x10000 ; stage-2 entry at start of kernel image
|
||||
jmp eax
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
bits 16
|
||||
|
||||
; Flat GDT: null, 32-bit code, 32-bit data. (64-bit descriptors live in the
|
||||
; stage-2 image, which brings its own GDT.)
|
||||
gdt:
|
||||
dq 0x0000000000000000
|
||||
dq 0x00CF9A000000FFFF ; code32: base 0, 4GB, exec/read
|
||||
dq 0x00CF92000000FFFF ; data32: base 0, 4GB, read/write
|
||||
gdt_end:
|
||||
gdt_ptr:
|
||||
dw gdt_end - gdt - 1
|
||||
dd gdt
|
||||
|
||||
msg_loading db "Nadir: loading kernel...", 0x0D, 0x0A, 0
|
||||
msg_entering db "Nadir: entering protected mode", 0x0D, 0x0A, 0
|
||||
msg_disk_err db "Nadir: disk read error", 0x0D, 0x0A, 0
|
||||
|
||||
boot_drive db 0
|
||||
spt dw 18
|
||||
heads dw 2
|
||||
lba dw 0
|
||||
buf_seg dw 0 ; load buffer segment (starts 0x1000)
|
||||
buf_off dw 0 ; load buffer offset within segment
|
||||
sectors_left dw 0 ; sectors still to load
|
||||
tries db 0 ; retries left for current sector
|
||||
|
||||
times 510-($-$$) db 0
|
||||
dw 0xAA55
|
||||
76
kernel/arch/x86_64/console.c
Normal file
76
kernel/arch/x86_64/console.c
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/* Nadir console: VGA text-mode driver (see console.h). */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "console.h"
|
||||
|
||||
#define VGA_WIDTH 80
|
||||
#define VGA_HEIGHT 25
|
||||
#define VGA_ADDR 0xB8000
|
||||
#define COLOR_DEFAULT 0x0FU /* white on black */
|
||||
|
||||
/* Cursor position. Lives in BSS, zeroed by stage 2 before kmain runs. */
|
||||
static uint8_t cursor_row;
|
||||
static uint8_t cursor_col;
|
||||
|
||||
static volatile uint16_t *const vga = (uint16_t *)VGA_ADDR;
|
||||
|
||||
static void scroll(void)
|
||||
{
|
||||
for (uint8_t row = 1; row < VGA_HEIGHT; row++) {
|
||||
for (uint8_t col = 0; col < VGA_WIDTH; col++) {
|
||||
vga[(row - 1) * VGA_WIDTH + col] = vga[row * VGA_WIDTH + col];
|
||||
}
|
||||
}
|
||||
for (uint8_t col = 0; col < VGA_WIDTH; col++) {
|
||||
vga[(VGA_HEIGHT - 1) * VGA_WIDTH + col] =
|
||||
(uint16_t)((COLOR_DEFAULT << 8) | ' ');
|
||||
}
|
||||
}
|
||||
|
||||
void console_clear(void)
|
||||
{
|
||||
for (uint16_t i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
||||
vga[i] = (uint16_t)((COLOR_DEFAULT << 8) | ' ');
|
||||
}
|
||||
cursor_row = 0;
|
||||
cursor_col = 0;
|
||||
}
|
||||
|
||||
void console_putchar(char c)
|
||||
{
|
||||
if (c == '\n') {
|
||||
cursor_row++;
|
||||
cursor_col = 0;
|
||||
} else {
|
||||
vga[cursor_row * VGA_WIDTH + cursor_col] =
|
||||
(uint16_t)((COLOR_DEFAULT << 8) | c);
|
||||
cursor_col++;
|
||||
if (cursor_col >= VGA_WIDTH) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
}
|
||||
if (cursor_row >= VGA_HEIGHT) {
|
||||
scroll();
|
||||
cursor_row = VGA_HEIGHT - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void console_puts(const char *s)
|
||||
{
|
||||
while (*s != '\0') {
|
||||
console_putchar(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
void console_puthex64(uint64_t value)
|
||||
{
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
|
||||
console_puts("0x");
|
||||
for (int shift = 60; shift >= 0; shift -= 4) {
|
||||
console_putchar(digits[(value >> shift) & 0xFU]);
|
||||
}
|
||||
}
|
||||
128
kernel/arch/x86_64/entry.asm
Normal file
128
kernel/arch/x86_64/entry.asm
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
; Nadir stage-2 entry: 32-bit protected mode -> 64-bit long mode -> C.
|
||||
;
|
||||
; Entered via a near jump to 0x10000 with a flat code segment (Ring 0,
|
||||
; CPL 0 —still the privilege BIOS gave us; no userspace exists yet).
|
||||
; Privileged instructions used: cpuid, mov cr4/cr0, rdmsr/wrmsr, lgdt.
|
||||
; Fault behavior: no IDT is installed, so any exception here triple-faults
|
||||
; and resets the machine. The only graceful failure is a CPU without long
|
||||
; mode, which prints an error straight to VGA and halts.
|
||||
;
|
||||
; Steps: verify long mode exists, identity-map the first 1GB with 2MB
|
||||
; pages, enable PAE + LME + paging, reload a GDT with a 64-bit code
|
||||
; segment, zero BSS, then call the C entry point kmain (System V ABI).
|
||||
; Interrupts stay disabled for the whole sequence.
|
||||
|
||||
bits 32
|
||||
section .boot32
|
||||
global boot32_entry
|
||||
extern kmain
|
||||
extern __bss_start
|
||||
extern __bss_end
|
||||
|
||||
PML4 equ 0x70000
|
||||
PDPT equ 0x71000
|
||||
PD equ 0x72000
|
||||
STACK64_TOP equ 0x90000
|
||||
|
||||
boot32_entry:
|
||||
; --- CPUID: does this CPU do 64-bit long mode? ---
|
||||
mov eax, 0x80000000
|
||||
cpuid
|
||||
cmp eax, 0x80000001
|
||||
jb .no_longmode
|
||||
mov eax, 0x80000001
|
||||
cpuid
|
||||
test edx, 1 << 29 ; LM bit
|
||||
jz .no_longmode
|
||||
|
||||
; --- page tables: PML4 -> PDPT -> PD, 512 x 2MB identity pages ---
|
||||
mov edi, PML4
|
||||
xor eax, eax
|
||||
mov ecx, 0x1000 ; clear 16KB (tables + slack)
|
||||
rep stosd
|
||||
mov eax, PDPT
|
||||
or eax, 0x03 ; present + writable
|
||||
mov [PML4], eax
|
||||
mov eax, PD
|
||||
or eax, 0x03
|
||||
mov [PDPT], eax
|
||||
mov ecx, 512
|
||||
mov edi, PD
|
||||
mov eax, 0x83 ; present + writable + 2MB page
|
||||
.fill_pd:
|
||||
mov [edi], eax
|
||||
add eax, 0x200000
|
||||
add edi, 8
|
||||
loop .fill_pd
|
||||
|
||||
; --- enable long mode ---
|
||||
mov eax, PML4 ; page-walk root (CR3 was 0 until now)
|
||||
mov cr3, eax
|
||||
mov eax, cr4
|
||||
or eax, 1 << 5 ; PAE
|
||||
mov cr4, eax
|
||||
mov ecx, 0xC0000080 ; IA32_EFER
|
||||
rdmsr
|
||||
or eax, 1 << 8 ; LME
|
||||
wrmsr
|
||||
mov eax, cr0
|
||||
or eax, 1 << 31 ; PG: paging on, long mode active
|
||||
mov cr0, eax
|
||||
|
||||
lgdt [gdt64_ptr]
|
||||
jmp 0x18:long_entry ; far jump loads the 64-bit code segment
|
||||
|
||||
.no_longmode:
|
||||
mov edi, 0xB8000 ; VGA text buffer, line 0
|
||||
mov esi, err_nolm
|
||||
mov ah, 0x4F ; white on red
|
||||
.print:
|
||||
lodsb
|
||||
test al, al
|
||||
jz .die
|
||||
stosw
|
||||
jmp .print
|
||||
.die:
|
||||
cli
|
||||
.hang:
|
||||
hlt
|
||||
jmp .hang
|
||||
|
||||
err_nolm db "ERR: CPU has no 64-bit long mode", 0
|
||||
|
||||
; Own GDT: the stage-1 GDT is behind us, so bring null/code32/data32 plus
|
||||
; the 64-bit code+data segments this stage needs.
|
||||
align 8
|
||||
gdt64:
|
||||
dq 0x0000000000000000
|
||||
dq 0x00CF9A000000FFFF ; code32 (kept for completeness)
|
||||
dq 0x00CF92000000FFFF ; data32
|
||||
dq 0x00209A0000000000 ; code64: L=1, exec/read
|
||||
dq 0x0000920000000000 ; data64: read/write
|
||||
gdt64_end:
|
||||
gdt64_ptr:
|
||||
dw gdt64_end - gdt64 - 1
|
||||
dd gdt64
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
bits 64
|
||||
long_entry:
|
||||
mov ax, 0x20 ; 64-bit data segment
|
||||
mov ds, ax
|
||||
mov es, ax
|
||||
mov fs, ax
|
||||
mov gs, ax
|
||||
mov ss, ax
|
||||
mov rsp, STACK64_TOP
|
||||
|
||||
mov rdi, __bss_start ; zero BSS (uninitialized C statics)
|
||||
mov rcx, __bss_end
|
||||
sub rcx, rdi
|
||||
xor eax, eax
|
||||
rep stosb
|
||||
|
||||
call kmain ; first C code — does not return
|
||||
cli
|
||||
.hang:
|
||||
hlt
|
||||
jmp .hang
|
||||
48
kernel/arch/x86_64/link.ld
Normal file
48
kernel/arch/x86_64/link.ld
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/* Nadir kernel linker script.
|
||||
*
|
||||
* Links the whole kernel (stage-2 entry + C code) as one flat binary loaded
|
||||
* by the boot sector at 0x10000. Virtual addresses equal physical ones: the
|
||||
* stage-2 page tables identity-map the first 1GB, so linked addresses work
|
||||
* as-is the moment long mode is on.
|
||||
*
|
||||
* Layout rules: the stage-2 entry (.boot32) must be the very first bytes of
|
||||
* the image, BSS exposes __bss_start/__bss_end for runtime zeroing, and
|
||||
* toolchain metadata sections are discarded so the binary is pure code+data.
|
||||
*/
|
||||
|
||||
ENTRY(boot32_entry)
|
||||
OUTPUT_ARCH(i386:x86-64)
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x10000;
|
||||
|
||||
.boot : {
|
||||
*(.boot32)
|
||||
}
|
||||
|
||||
.text : {
|
||||
*(.text*)
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
|
||||
.bss : {
|
||||
__bss_start = .;
|
||||
*(.bss*)
|
||||
*(COMMON)
|
||||
__bss_end = .;
|
||||
}
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.note*)
|
||||
*(.comment*)
|
||||
*(.eh_frame*)
|
||||
}
|
||||
}
|
||||
28
kernel/core/kmain.c
Normal file
28
kernel/core/kmain.c
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/* Nadir kernel entry point: first C code to run.
|
||||
*
|
||||
* Called once from the stage-2 entry stub after the CPU is in 64-bit long
|
||||
* mode with a valid stack, zeroed BSS and identity-mapped memory. Runs at
|
||||
* Ring 0 (CPL 0), interrupts disabled, no IDT yet: any fault triple-faults,
|
||||
* so this function proves the whole boot chain simply by running.
|
||||
* It never returns; on return the caller halts the CPU.
|
||||
*/
|
||||
|
||||
#include "console.h"
|
||||
|
||||
/* A value that only comes out right if the CPU truly executes 64-bit
|
||||
* arithmetic: every nibble survives the shifts above 32 bits. */
|
||||
#define PROOF_VALUE 0x0123456789ABCDEFULL
|
||||
|
||||
void kmain(void)
|
||||
{
|
||||
console_clear();
|
||||
console_puts("Hello World from Nadir Kernel !\n");
|
||||
console_puts("Running C code in 64-bit long mode (Ring 0).\n");
|
||||
console_puts("64-bit proof value: ");
|
||||
console_puthex64(PROOF_VALUE);
|
||||
console_puts("\nHalting.\n");
|
||||
|
||||
for (;;) {
|
||||
__asm__ volatile("hlt");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue