mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Merge pull request #1 from Plexi09/boot-to-c
Makes the main bootloader functional. Instead of a real mode "hello world" sector, the boot chain now loads the kernel from disk, switches through 32-bi protected mode into 64-bit long mode, and runs C code (kmain) that prints to the VGA console and halts. Old root boot.asm removed and replaced by kernel/arch/x86_64/boot.asm.
This commit is contained in:
commit
8c46ac48d9
14 changed files with 672 additions and 46 deletions
4
.github/scripts/check_boot.py
vendored
4
.github/scripts/check_boot.py
vendored
|
|
@ -18,8 +18,8 @@ import sys
|
||||||
import termios
|
import termios
|
||||||
import time
|
import time
|
||||||
|
|
||||||
IMAGE = "boot.bin"
|
IMAGE = sys.argv[1] if len(sys.argv) > 1 else "nadir.img"
|
||||||
NEEDLE = b"Nadir"
|
NEEDLE = b"64-bit"
|
||||||
BOOT_WAIT_S = 8
|
BOOT_WAIT_S = 8
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
|
|
@ -1,5 +1,7 @@
|
||||||
# Nadir Kernel CI. Runs on every pull request and every push to main.
|
# Nadir Kernel CI. Runs on every pull request and every push to main.
|
||||||
#
|
#
|
||||||
|
# Builds nadir.img with make, verifies the image, then boots it headless in
|
||||||
|
# QEMU and asserts the kernel's welcome message reaches the screen.
|
||||||
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
|
|
@ -20,7 +22,27 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install toolchain
|
- name: Install toolchain
|
||||||
|
# gcc, make and binutils ship with the runner; nasm and qemu do not.
|
||||||
run: sudo apt-get update && sudo apt-get install -y nasm qemu-system-x86
|
run: sudo apt-get update && sudo apt-get install -y nasm qemu-system-x86
|
||||||
|
|
||||||
- name: Build boot sector
|
- name: Build image
|
||||||
run: nasm -f bin boot.asm -o boot.bin
|
run: make
|
||||||
|
|
||||||
|
- name: Verify image
|
||||||
|
# Every check prints what it saw: a silent bare `test` once hid a
|
||||||
|
# failure here, so each assertion reports and names its own failure.
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
ls -l nadir.img
|
||||||
|
size=$(stat -c %s nadir.img)
|
||||||
|
echo "image size: $size bytes"
|
||||||
|
test "$size" -ge 1024 || { echo "FAIL: image smaller than sector+kernel minimum"; exit 1; }
|
||||||
|
test $(( (size - 512) % 512 )) -eq 0 || { echo "FAIL: kernel part is not sector-aligned"; exit 1; }
|
||||||
|
sig=$(dd if=nadir.img bs=1 skip=510 count=2 2>/dev/null | od -A n -t x1 | tr -d ' \n')
|
||||||
|
echo "boot signature: $sig"
|
||||||
|
test "$sig" = "55aa" || { echo "FAIL: missing 0xAA55 boot signature"; exit 1; }
|
||||||
|
grep -q "Nadir" nadir.img || { echo "FAIL: kernel payload missing from image"; exit 1; }
|
||||||
|
echo "OK: nadir.img valid, boot signature and payload present"
|
||||||
|
|
||||||
|
- name: Boot and check welcome message in QEMU
|
||||||
|
run: python3 .github/scripts/check_boot.py nadir.img
|
||||||
|
|
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -23,3 +23,9 @@ qemu*.log
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
.omo
|
.omo
|
||||||
.codegraph
|
.codegraph
|
||||||
|
|
||||||
|
# Local scripts
|
||||||
|
*.sh
|
||||||
|
|
||||||
|
# Python bytecode (CI boot check)
|
||||||
|
__pycache__/
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,11 @@ Please stick to the following rules if you plan to use AI in your contributions:
|
||||||
|
|
||||||
## Building and running
|
## Building and running
|
||||||
|
|
||||||
The current toolchain is two commands from the repository root. See the [README](README.md) for prerequisites and troubleshooting:
|
Build from the repository root. See the [README](README.md) for prerequisites and troubleshooting:
|
||||||
|
|
||||||
```
|
```
|
||||||
nasm -f bin boot.asm -o boot.bin
|
make # builds nadir.img (boot sector + kernel)
|
||||||
qemu-system-x86_64 -fda boot.bin
|
make run # boots it in QEMU
|
||||||
```
|
```
|
||||||
|
|
||||||
The toolchain is not yet finalized (candidates: `gcc` cross + `ld`, or `clang` + `lld` + `nasm`; target `x86-64-elf`).
|
The toolchain is not yet finalized (candidates: `gcc` cross + `ld`, or `clang` + `lld` + `nasm`; target `x86-64-elf`).
|
||||||
|
|
|
||||||
60
Makefile
Normal file
60
Makefile
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Nadir Kernel build.
|
||||||
|
#
|
||||||
|
# Toolchain: host gcc/binutils (x86-64) with strict freestanding flags.
|
||||||
|
# A dedicated x86-64-elf cross-compiler is the planned next step; until
|
||||||
|
# then CC/LD/OBJCOPY stay overridable so it drops in without edits.
|
||||||
|
# Requires GNU coreutils (truncate -s %N).
|
||||||
|
|
||||||
|
NASM ?= nasm
|
||||||
|
CC ?= gcc
|
||||||
|
LD ?= ld
|
||||||
|
OBJCOPY ?= objcopy
|
||||||
|
QEMU ?= qemu-system-x86_64
|
||||||
|
|
||||||
|
ARCH_DIR := kernel/arch/x86_64
|
||||||
|
BUILD := build
|
||||||
|
IMG := nadir.img
|
||||||
|
|
||||||
|
CFLAGS := -std=c17 -ffreestanding -nostdlib -fno-builtin \
|
||||||
|
-m64 -mno-red-zone -mno-mmx -mno-sse \
|
||||||
|
-fno-pic -fno-pie -fno-stack-protector \
|
||||||
|
-Wall -Wextra -Werror -O2 -Iinclude
|
||||||
|
|
||||||
|
OBJS := $(BUILD)/entry.o $(BUILD)/kmain.o $(BUILD)/console.o
|
||||||
|
|
||||||
|
all: $(IMG)
|
||||||
|
|
||||||
|
$(BUILD):
|
||||||
|
mkdir -p $(BUILD)
|
||||||
|
|
||||||
|
$(BUILD)/entry.o: $(ARCH_DIR)/entry.asm | $(BUILD)
|
||||||
|
$(NASM) -f elf64 $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h | $(BUILD)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h | $(BUILD)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/kernel.elf: $(OBJS) $(ARCH_DIR)/link.ld
|
||||||
|
$(LD) -m elf_x86_64 -T $(ARCH_DIR)/link.ld -o $@ $(OBJS)
|
||||||
|
|
||||||
|
$(BUILD)/kernel.bin: $(BUILD)/kernel.elf
|
||||||
|
$(OBJCOPY) -O binary $< $@
|
||||||
|
truncate -s %512 $@
|
||||||
|
|
||||||
|
# The boot sector needs the kernel size up front, so it builds last.
|
||||||
|
$(BUILD)/boot.bin: $(ARCH_DIR)/boot.asm $(BUILD)/kernel.bin | $(BUILD)
|
||||||
|
SECTORS=$$(( ($$(stat -c%s $(BUILD)/kernel.bin) + 511) / 512 )); \
|
||||||
|
$(NASM) -f bin -D KERNEL_SECTORS=$$SECTORS $< -o $@
|
||||||
|
|
||||||
|
$(IMG): $(BUILD)/boot.bin $(BUILD)/kernel.bin
|
||||||
|
cat $^ > $@
|
||||||
|
|
||||||
|
run: $(IMG)
|
||||||
|
$(QEMU) -fda $(IMG)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD) $(IMG)
|
||||||
|
|
||||||
|
.PHONY: all run clean
|
||||||
44
README.md
44
README.md
|
|
@ -5,37 +5,38 @@ The name Nadir comes from the Arabic word *naẓīr*. In astronomy it means the
|
||||||
|
|
||||||
## Status of the project
|
## Status of the project
|
||||||
|
|
||||||
The project is in its foundation phase and is not yet usable. Only the bootloader is currently being written.
|
The project is in its foundation phase and is not yet usable. The boot chain works: a BIOS boot sector loads the kernel, switches through 32-bit protected mode into 64-bit long mode, and runs C code (`kmain`) that prints to the screen. Next: interrupts (IDT), then memory management.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
You need two tools:
|
You need an assembler, a C toolchain, and an emulator:
|
||||||
|
|
||||||
- **nasm** to assemble `boot.asm` into a bootable image.
|
- **nasm** assembles the boot sector and the long-mode entry stub.
|
||||||
- **qemu-system-x86_64**, the emulator used to run it.
|
- **gcc**, **binutils** (`ld`, `objcopy`) and **make** build the C kernel (`gcc` pulls in `binutils` on most distros).
|
||||||
|
- **qemu-system-x86_64** runs the resulting image.
|
||||||
|
|
||||||
Install both from your package manager:
|
Install everything from your package manager:
|
||||||
|
|
||||||
**Debian / Ubuntu**
|
**Debian / Ubuntu**
|
||||||
```bash
|
```bash
|
||||||
sudo apt install nasm qemu-system-x86
|
sudo apt install nasm qemu-system-x86 gcc make
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fedora**
|
**Fedora**
|
||||||
```bash
|
```bash
|
||||||
sudo dnf install nasm qemu-system-x86
|
sudo dnf install nasm qemu-system-x86 gcc make
|
||||||
```
|
```
|
||||||
|
|
||||||
**Arch Linux**
|
**Arch Linux**
|
||||||
```bash
|
```bash
|
||||||
sudo pacman -S nasm qemu-desktop
|
sudo pacman -S nasm qemu-desktop gcc make
|
||||||
```
|
```
|
||||||
|
|
||||||
**macOS (Homebrew)**
|
**macOS (Homebrew)**
|
||||||
```bash
|
```bash
|
||||||
brew install nasm qemu
|
brew install nasm qemu gcc make
|
||||||
```
|
```
|
||||||
|
|
||||||
**Windows**
|
**Windows**
|
||||||
|
|
@ -47,8 +48,8 @@ I can only recommend using Linux distribution, but the Windows Subsystem for Lin
|
||||||
From the repository root:
|
From the repository root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nasm -f bin boot.asm -o boot.bin
|
make # builds nadir.img
|
||||||
qemu-system-x86_64 -fda boot.bin
|
make run # boots it in QEMU
|
||||||
```
|
```
|
||||||
|
|
||||||
A QEMU window opens and you should see this:
|
A QEMU window opens and you should see this:
|
||||||
|
|
@ -57,12 +58,12 @@ A QEMU window opens and you should see this:
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `-fda` boots the image as a BIOS floppy. QEMU's default firmware loads it at address `0x7C00` and executes it in 16-bit real mode.
|
- `nadir.img` is a raw floppy image: the 512-byte boot sector first, then the kernel. `-fda` boots it as a BIOS floppy; QEMU's default firmware loads the sector at address `0x7C00` and executes it in 16-bit real mode.
|
||||||
- `boot.bin` is a generated artifact and is ignored by git, so you need to build it first. `rm boot.bin` cleans up and it will regenerates on the next `nasm` run.
|
- `build/` and `nadir.img` are generated artifacts ignored by git. `make clean` removes them.
|
||||||
- There is no build script or Makefile yet. Those two commands are the whole toolchain.
|
- The C kernel builds with plain host `gcc` and freestanding flags for now (see `Makefile`); a dedicated `x86-64-elf` cross-compiler is the planned next step.
|
||||||
- Running over SSH or in a bare terminal? Add `-display curses` to render the boot screen as text instead of a window:
|
- Running over SSH or in a bare terminal? `make run` needs a window; boot headless instead:
|
||||||
```bash
|
```bash
|
||||||
qemu-system-x86_64 -fda boot.bin -display curses
|
qemu-system-x86_64 -fda nadir.img -display curses
|
||||||
```
|
```
|
||||||
Quit with `Ctrl-A` then `X`.
|
Quit with `Ctrl-A` then `X`.
|
||||||
|
|
||||||
|
|
@ -70,10 +71,19 @@ Notes:
|
||||||
|
|
||||||
| Symptom | Cause |
|
| Symptom | Cause |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `Boot failed: not a bootable disk` | `boot.bin` is not a valid boot sector: it must be exactly 512 bytes and end with the `0xAA55` boot signature. Re-run the `nasm` step and check it completes without errors. |
|
| `Boot failed: not a bootable disk` | The first 512 bytes of `nadir.img` are not a valid boot sector (they must end with the `0xAA55` signature). Re-run `make` and check it completes without errors. |
|
||||||
| Black screen / blinking cursor | The boot sector only speaks legacy BIOS (`int 0x10` text mode). This works out of the box with QEMU's default SeaBIOS firmware but will not boot under UEFI-only firmware (or UEFI-only real hardware) unless legacy/CSM boot is enabled. |
|
| Black screen / blinking cursor | The boot sector only speaks legacy BIOS (`int 0x10` text mode). This works out of the box with QEMU's default SeaBIOS firmware but will not boot under UEFI-only firmware (or UEFI-only real hardware) unless legacy/CSM boot is enabled. |
|
||||||
|
| QEMU reboots in a loop after `entering protected mode` (or before any C output) | A triple fault in the boot chain: the CPU crashed with no IDT installed and reset itself. Boot with `qemu-system-x86_64 -fda nadir.img -d int -no-reboot` and read the fault (`v=...`) plus registers from the log. |
|
||||||
| No window appears under WSL2 | Use the `-display curses` variant above. It should work in any terminal. |
|
| No window appears under WSL2 | Use the `-display curses` variant above. It should work in any terminal. |
|
||||||
|
|
||||||
|
## How booting works
|
||||||
|
|
||||||
|
1. The BIOS loads the 512-byte sector (`kernel/arch/x86_64/boot.asm`) at `0x7C00`: it enables A20, loads the kernel to `0x10000`, installs a flat GDT, and enters 32-bit protected mode.
|
||||||
|
2. The stage-2 entry (`kernel/arch/x86_64/entry.asm`) checks for long mode, identity-maps the first 1 GB with 2 MB pages, enables paging, jumps to 64-bit code, zeroes BSS, and calls `kmain`.
|
||||||
|
3. `kmain` (`kernel/core/kmain.c`) prints through the VGA text console (`kernel/arch/x86_64/console.c`, `include/console.h`) and halts.
|
||||||
|
|
||||||
|
The full memory map lives in the header comment of `kernel/arch/x86_64/boot.asm`.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Every contribution is welcome. Please fork and send a pull request if you wish to contribute. You will be credited at the end of this very file. Please see the [CONTRIBUTING](CONTRIBUTING.md) file for more information.
|
Every contribution is welcome. Please fork and send a pull request if you wish to contribute. You will be credited at the end of this very file. Please see the [CONTRIBUTING](CONTRIBUTING.md) file for more information.
|
||||||
|
|
|
||||||
22
boot.asm
22
boot.asm
|
|
@ -1,22 +0,0 @@
|
||||||
bits 16 ; 16 bit mode
|
|
||||||
org 0x7c00
|
|
||||||
|
|
||||||
boot:
|
|
||||||
mov si, message ; Point SI register to message
|
|
||||||
mov ah, 0x0e ; Set higher bits to the display character command
|
|
||||||
|
|
||||||
.loop:
|
|
||||||
lodsb ; Load the character within the AL register, and increment SI
|
|
||||||
cmp al, 0 ; Is the AL register a null byte?
|
|
||||||
je halt ; Jump to halt
|
|
||||||
int 0x10 ; Trigger video service interrupt
|
|
||||||
jmp .loop ; Loop again
|
|
||||||
|
|
||||||
halt:
|
|
||||||
hlt ; stop
|
|
||||||
|
|
||||||
message:
|
|
||||||
db "Hello World from Nadir Kernel !", 0x0a, 0x0d, 0x00
|
|
||||||
|
|
||||||
times 510-($-$$) db 0 ; 510 bytes of zeros
|
|
||||||
dw 0xAA55 ; boot signature
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 1.3 KiB |
27
include/console.h
Normal file
27
include/console.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* Nadir early console: VGA text-mode driver.
|
||||||
|
*
|
||||||
|
* Writes straight to the VGA text buffer at 0xB8000, which the stage-2
|
||||||
|
* page tables identity-map. Runs in 64-bit long mode at Ring 0 (CPL 0);
|
||||||
|
* no faults are possible beyond a programming bug. There is no IDT yet,
|
||||||
|
* so a wild pointer here triple-faults the machine.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NADIR_CONSOLE_H
|
||||||
|
#define NADIR_CONSOLE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Clear the screen and park the cursor at the top left. */
|
||||||
|
void console_clear(void);
|
||||||
|
|
||||||
|
/* Print one character. '\n' moves to the start of the next line; the
|
||||||
|
* screen scrolls up when the cursor runs past the last line. */
|
||||||
|
void console_putchar(char c);
|
||||||
|
|
||||||
|
/* Print a NUL-terminated string. */
|
||||||
|
void console_puts(const char *s);
|
||||||
|
|
||||||
|
/* Print a 64-bit value as 0x-prefixed, zero-padded hexadecimal. */
|
||||||
|
void console_puthex64(uint64_t value);
|
||||||
|
|
||||||
|
#endif /* NADIR_CONSOLE_H */
|
||||||
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