mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Merge pull request #7 from Plexi09/feature/pmm-e820
E820 memory map + bitmap PMM + timestamped klog
This commit is contained in:
commit
5077695c70
10 changed files with 644 additions and 87 deletions
10
Makefile
10
Makefile
|
|
@ -20,7 +20,7 @@ CFLAGS := -std=c17 -ffreestanding -nostdlib -fno-builtin \
|
||||||
-fno-pic -fno-pie -fno-stack-protector \
|
-fno-pic -fno-pie -fno-stack-protector \
|
||||||
-Wall -Wextra -Werror -O2 -Iinclude
|
-Wall -Wextra -Werror -O2 -Iinclude
|
||||||
|
|
||||||
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/console.o $(BUILD)/idt.o $(BUILD)/pic.o $(BUILD)/pit.o $(BUILD)/serial.o $(BUILD)/keyboard.o
|
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/klog.o $(BUILD)/console.o $(BUILD)/idt.o $(BUILD)/pic.o $(BUILD)/pit.o $(BUILD)/serial.o $(BUILD)/keyboard.o $(BUILD)/pmm.o
|
||||||
|
|
||||||
all: $(IMG)
|
all: $(IMG)
|
||||||
|
|
||||||
|
|
@ -33,7 +33,13 @@ $(BUILD)/entry.o: $(ARCH_DIR)/entry.asm | $(BUILD)
|
||||||
$(BUILD)/isr.o: $(ARCH_DIR)/isr.asm | $(BUILD)
|
$(BUILD)/isr.o: $(ARCH_DIR)/isr.asm | $(BUILD)
|
||||||
$(NASM) -f elf64 $< -o $@
|
$(NASM) -f elf64 $< -o $@
|
||||||
|
|
||||||
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h include/pic.h include/pit.h include/serial.h include/keyboard.h | $(BUILD)
|
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h include/pic.h include/pit.h include/serial.h include/keyboard.h include/pmm.h include/e820.h include/klog.h | $(BUILD)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/klog.o: kernel/core/klog.c include/klog.h include/console.h include/pit.h | $(BUILD)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BUILD)/pmm.o: kernel/mm/pmm.c include/pmm.h include/e820.h | $(BUILD)
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
$(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h include/io.h | $(BUILD)
|
$(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h include/io.h | $(BUILD)
|
||||||
|
|
|
||||||
34
include/e820.h
Normal file
34
include/e820.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* Nadir BIOS E820 memory-map entry (int 0x15, eax=0xE820).
|
||||||
|
*
|
||||||
|
* Staged by the stage-1 boot sector in 16-bit real mode at Ring 0 (CPL 0)
|
||||||
|
* into low memory (0x5000 count, 0x5020 entries) while BIOS calls still
|
||||||
|
* work, then copied by the stage-2 entry into the .bss globals
|
||||||
|
* memmap_count / memmap_entries owned by the PMM. Runs under the
|
||||||
|
* identity-mapped first 1GB, so physical == virtual addresses here.
|
||||||
|
* Fault behavior: no IDT exists during staging/copy, so a bad pointer
|
||||||
|
* triple-faults; the entry count is clamped to E820_MAX on both ends.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NADIR_E820_H
|
||||||
|
#define NADIR_E820_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Max entries the boot sector stages and the kernel keeps. */
|
||||||
|
#define E820_MAX 64
|
||||||
|
|
||||||
|
/* Usable-RAM type in the E820 type field. */
|
||||||
|
#define E820_USABLE 1
|
||||||
|
|
||||||
|
/* One 24-byte BIOS E820 entry: byte-based [base, base+length) range.
|
||||||
|
* Preconditions: filled by BIOS int 0x15 or by the boot-sector fallback
|
||||||
|
* (single type-1 region at 1MB); length may be 0 (ignored by the PMM).
|
||||||
|
* Failure modes: none in C — a corrupt table only mis-marks frames. */
|
||||||
|
struct e820_entry {
|
||||||
|
uint64_t base;
|
||||||
|
uint64_t length;
|
||||||
|
uint32_t type;
|
||||||
|
uint32_t acpi_attr;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
#endif /* NADIR_E820_H */
|
||||||
48
include/klog.h
Normal file
48
include/klog.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Nadir boot logger: timestamped `[ 0.000000] sub: msg` lines.
|
||||||
|
*
|
||||||
|
* Runs in 64-bit long mode at Ring 0 (CPL 0) on the VGA console via
|
||||||
|
* console_putchar, which is polling-only and IRQ-safe by construction.
|
||||||
|
*
|
||||||
|
* Timestamp source: pit_ticks() / PIT_HZ (100Hz, 10ms per tick), integer
|
||||||
|
* math only. At 100Hz one
|
||||||
|
* tick is 10ms, so the 6-digit fraction only changes in steps of 10000
|
||||||
|
* (e.g. 0.010000); the last 4 digits are always zero by construction.
|
||||||
|
* Early boot note: IF=0 until kmain runs `sti`, so the PIT IRQ cannot fire
|
||||||
|
* and ticks stay frozen at 0.
|
||||||
|
*
|
||||||
|
* IF/IRQ safety: mainline only, never IRQ context. The PIT IRQ handler
|
||||||
|
* (pit_on_tick) must never call this. It only bumps the tick counter.
|
||||||
|
* Single-core kernel, so no locking is needed.
|
||||||
|
*
|
||||||
|
* Preconditions: console is usable (identity-mapped VGA); pit_ticks() is
|
||||||
|
* linked in (BSS-zeroed counter reads 0 before pit_init).
|
||||||
|
* Failure modes: none
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NADIR_KLOG_H
|
||||||
|
#define NADIR_KLOG_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* Print one full line: `[ 0.000000] subsystem: msg` + '\n'. */
|
||||||
|
void klog(const char *subsystem, const char *msg);
|
||||||
|
|
||||||
|
/* Piecewise line builder for values (PMM stats, uptime): prints the
|
||||||
|
* `[ 0.000000] subsystem: ` prefix with no newline; append with
|
||||||
|
* klog_str/dec/hex64, then close with klog_end() ('\n').
|
||||||
|
* Preconditions: calls nest as begin -> (str|dec|hex64)* -> end. */
|
||||||
|
void klog_begin(const char *subsystem);
|
||||||
|
|
||||||
|
/* Append a NUL-terminated string to the open line. */
|
||||||
|
void klog_str(const char *s);
|
||||||
|
|
||||||
|
/* Append an unsigned decimal value (no libc printf). */
|
||||||
|
void klog_dec(uint64_t v);
|
||||||
|
|
||||||
|
/* Append a 64-bit value as 0x-prefixed, zero-padded hexadecimal. */
|
||||||
|
void klog_hex64(uint64_t v);
|
||||||
|
|
||||||
|
/* Terminate the open line with '\n'. */
|
||||||
|
void klog_end(void);
|
||||||
|
|
||||||
|
#endif /* NADIR_KLOG_H */
|
||||||
53
include/pmm.h
Normal file
53
include/pmm.h
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
/* Nadir physical memory manager
|
||||||
|
*
|
||||||
|
* Runs in 64-bit long mode at Ring 0 (CPL 0) under the stage-2 identity
|
||||||
|
* map; physical addresses are used as-is. Called once from kmain with
|
||||||
|
* interrupts disabled (IF=0), before sti, so no IRQ handler can allocate
|
||||||
|
* concurrently. No faults are possible beyond a programming bug; with no
|
||||||
|
* IDT yet at the call point a wild access would triple-fault.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NADIR_PMM_H
|
||||||
|
#define NADIR_PMM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "e820.h"
|
||||||
|
|
||||||
|
/* 4KB frames over the identity-mapped first 1GB: hard capacity limit of
|
||||||
|
* this allocator (256K frames, 32KB bitmap in .bss). Regions above 1GB
|
||||||
|
* reported by E820 are ignored. */
|
||||||
|
#define PMM_FRAME_SIZE 4096U
|
||||||
|
#define PMM_MAX_BYTES 0x40000000ULL
|
||||||
|
#define PMM_MAX_FRAMES (PMM_MAX_BYTES / PMM_FRAME_SIZE)
|
||||||
|
|
||||||
|
/* Memory-map globals: filled by the stage-2 entry (copy from the
|
||||||
|
* stage-1 staging buffer) before kmain runs. pmm_init reads them. */
|
||||||
|
extern uint32_t memmap_count;
|
||||||
|
extern struct e820_entry memmap_entries[E820_MAX];
|
||||||
|
|
||||||
|
/* Build the bitmap from the E820 map: type-1 (usable) frames start free,
|
||||||
|
* everything else starts reserved, then the kernel image, page tables,
|
||||||
|
* staging buffer, low 1MB, stack and VGA regions are reserved.
|
||||||
|
* Preconditions: count <= E820_MAX, entries points at count valid
|
||||||
|
* entries, called once with IF=0 before any alloc/free.
|
||||||
|
* Failure modes: none — an empty/corrupt map just leaves 0 free frames. */
|
||||||
|
void pmm_init(uint32_t count, const struct e820_entry *entries);
|
||||||
|
|
||||||
|
/* Hand out one 4KB frame: returns its physical base address, or 0 when
|
||||||
|
* out of memory (frame 0 is always reserved, so 0 is never valid).
|
||||||
|
* Preconditions: pmm_init ran. Failure modes: returns 0 on OOM. */
|
||||||
|
uint64_t pmm_alloc_frame(void);
|
||||||
|
|
||||||
|
/* Return a frame from pmm_alloc_frame to the free pool. Silently ignores
|
||||||
|
* unaligned, out-of-range, or already-free addresses.
|
||||||
|
* Preconditions: pmm_init ran. Failure modes: none (no fault, no panic). */
|
||||||
|
void pmm_free_frame(uint64_t addr);
|
||||||
|
|
||||||
|
/* Live counters for stats printouts.
|
||||||
|
* Preconditions: pmm_init ran (0 before that). Failure modes: none. */
|
||||||
|
uint64_t pmm_total_count(void);
|
||||||
|
uint64_t pmm_free_count(void);
|
||||||
|
uint64_t pmm_usable_count(void);
|
||||||
|
|
||||||
|
#endif /* NADIR_PMM_H */
|
||||||
|
|
@ -10,6 +10,11 @@
|
||||||
; flat GDT, enter 32-bit protected mode and jump to the stage-2 entry.
|
; flat GDT, enter 32-bit protected mode and jump to the stage-2 entry.
|
||||||
;
|
;
|
||||||
; Memory map used by the whole boot chain:
|
; Memory map used by the whole boot chain:
|
||||||
|
; 0x005000 E820 staging: count byte at 0x5000, 24-byte entries from
|
||||||
|
; 0x5020 (64 max = 1536 bytes, ends 0x5620). Free: below the
|
||||||
|
; 0x7C00 sector, clear of the 0x10000 kernel load, the 0x70000
|
||||||
|
; tables and the 0x90000 stack. Stage 2 copies it into kernel
|
||||||
|
; .bss (memmap_count/memmap_entries) after BSS zeroing.
|
||||||
; 0x007C00 this sector (512 bytes)
|
; 0x007C00 this sector (512 bytes)
|
||||||
; 0x010000 kernel image (loaded here, linked here)
|
; 0x010000 kernel image (loaded here, linked here)
|
||||||
; 0x70000 PML4 | 0x71000 PDPT | 0x72000 PD (built by stage 2)
|
; 0x70000 PML4 | 0x71000 PDPT | 0x72000 PD (built by stage 2)
|
||||||
|
|
@ -25,6 +30,10 @@ org 0x7C00
|
||||||
|
|
||||||
KERNEL_LOAD_SEG equ 0x1000 ; ES for load target 0x1000:0x0000 = 0x10000
|
KERNEL_LOAD_SEG equ 0x1000 ; ES for load target 0x1000:0x0000 = 0x10000
|
||||||
STACK_TOP equ 0x7C00 ; real-mode stack grows down from here
|
STACK_TOP equ 0x7C00 ; real-mode stack grows down from here
|
||||||
|
E820_COUNT equ 0x5000 ; staged E820 entry count (one byte)
|
||||||
|
E820_BUF equ 0x5020 ; staged E820 entries (24 bytes each)
|
||||||
|
E820_MAX equ 64 ; cap: 64 * 24 = 1536 bytes, ends 0x5620
|
||||||
|
E820_ENTRY_SIZE equ 24
|
||||||
|
|
||||||
start:
|
start:
|
||||||
cli
|
cli
|
||||||
|
|
@ -40,8 +49,8 @@ start:
|
||||||
call puts
|
call puts
|
||||||
|
|
||||||
call enable_a20
|
call enable_a20
|
||||||
call read_drive_params
|
|
||||||
call load_kernel
|
call load_kernel
|
||||||
|
call query_e820
|
||||||
|
|
||||||
mov si, msg_entering
|
mov si, msg_entering
|
||||||
call puts
|
call puts
|
||||||
|
|
@ -73,18 +82,15 @@ puts:
|
||||||
ret
|
ret
|
||||||
|
|
||||||
; ---------------------------------------------------------------------------
|
; ---------------------------------------------------------------------------
|
||||||
; enable_a20: try BIOS, keyboard-controller and fast-A20 methods in turn.
|
; enable_a20: try the BIOS and fast-A20 methods in turn. Each is harmless
|
||||||
; Each is harmless if A20 is already on.
|
; if A20 is already on.
|
||||||
|
; WHY only two methods: the keyboard-controller pulse was dropped to free
|
||||||
|
; sector bytes for the E820 query above — every 64-bit CPU postdates
|
||||||
|
; chipsets with the 0x92 fast gate (or a BIOS that honors 0x2401), so the
|
||||||
|
; legacy AT pulse buys nothing on this kernel's hardware floor.
|
||||||
enable_a20:
|
enable_a20:
|
||||||
mov ax, 0x2401 ; BIOS: enable A20
|
mov ax, 0x2401 ; BIOS: enable A20
|
||||||
int 0x15
|
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)
|
in al, 0x92 ; fast A20 (skip if already set or absent)
|
||||||
test al, 0x02
|
test al, 0x02
|
||||||
jnz .done
|
jnz .done
|
||||||
|
|
@ -93,34 +99,11 @@ enable_a20:
|
||||||
.done:
|
.done:
|
||||||
ret
|
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.
|
; Floppy geometry is fixed at 18 sectors/track, 2 heads (the image only
|
||||||
; Falls back to floppy defaults (18 spt, 2 heads) on failure or nonsense.
|
; ever boots via -fda; see [spt]/[heads] inits below). The old int 0x13
|
||||||
read_drive_params:
|
; AH=08 probe was dropped to free sector bytes for the E820 query above.
|
||||||
xor ax, ax
|
; HDD generality wants EDD packet reads (AH=42), planned, not CHS probing.
|
||||||
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.
|
; lba_to_chs: convert LBA in AX to int 0x13 CHS in CH/CL/DH.
|
||||||
; Clobbers AX, BX, CX, DX.
|
; Clobbers AX, BX, CX, DX.
|
||||||
|
|
@ -193,6 +176,79 @@ load_kernel:
|
||||||
jnz .next
|
jnz .next
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
; ---------------------------------------------------------------------------
|
||||||
|
; query_e820: ask BIOS for the physical memory map (int 0x15, eax=0xE820,
|
||||||
|
; edx='SMAP') and stage it at E820_BUF with the entry count byte at
|
||||||
|
; E820_COUNT. Must run BEFORE the protected-mode switch as BIOS calls are
|
||||||
|
; unavailable after. Runs in 16-bit real mode at Ring 0; clobbers AX,
|
||||||
|
; BX, CX, DX, DI, ES.
|
||||||
|
;
|
||||||
|
; NOTE: like load_kernel, no register survives int 0x15 — SeaBIOS/real
|
||||||
|
; BIOSes may clobber general registers across the call, so the
|
||||||
|
; continuation (EBX) lives in memory ([e820_next]) and ECX/EDX/ES:DI are
|
||||||
|
; reloaded from immediates before every call. DS is saved on the stack
|
||||||
|
; across the call (push/pop restores OUR value even if BIOS clobbers it);
|
||||||
|
; it must NOT be rebuilt with xor ax,ax + mov ds,ax after the call, as
|
||||||
|
; that zeroes EAX's low half and breaks the 'SMAP' check below. DI is recomputed from
|
||||||
|
; the in-memory count each pass (index < 64, so the 8-bit multiply
|
||||||
|
; cannot overflow) instead of keeping a second pointer.
|
||||||
|
;
|
||||||
|
; Fallback choice: if no entry was staged (first query failed via carry
|
||||||
|
; or EAX magic mismatch), synthesize a single usable region at 1MB,
|
||||||
|
; length 127MB (type 1) — enough for the kernel + PMM to boot on an
|
||||||
|
; unknown box — and continue silently (no message: the sector has no
|
||||||
|
; bytes to spare). A mid-list failure keeps whatever entries were
|
||||||
|
; already staged. Documented here so the PMM knows the map may be
|
||||||
|
; synthetic on old hardware. Kept golfed on purpose: this sector is
|
||||||
|
; exactly 510 bytes, every instruction here displaced a boot message.
|
||||||
|
query_e820:
|
||||||
|
xor ax, ax
|
||||||
|
mov ds, ax ; re-establish DS
|
||||||
|
mov [E820_COUNT], al ; count byte = 0
|
||||||
|
.loop:
|
||||||
|
xor ax, ax
|
||||||
|
mov es, ax ; ES:DI rebuilt per call
|
||||||
|
mov al, [E820_COUNT]
|
||||||
|
mov cl, E820_ENTRY_SIZE
|
||||||
|
mul cl ; AX = index * 24
|
||||||
|
mov di, E820_BUF
|
||||||
|
add di, ax
|
||||||
|
mov ecx, E820_ENTRY_SIZE ; reload every call: BIOS owns nothing
|
||||||
|
mov edx, 0x534D4150
|
||||||
|
mov eax, 0xE820
|
||||||
|
mov ebx, [e820_next] ; continuation lives in memory
|
||||||
|
push ds ; DS survives on the stack, EAX intact
|
||||||
|
int 0x15
|
||||||
|
pop ds ; our DS back, whatever BIOS did
|
||||||
|
jc .done ; carry = end of list or call failed
|
||||||
|
cmp eax, 0x534D4150 ; BIOS must echo 'SMAP' in EAX
|
||||||
|
jne .done
|
||||||
|
cmp byte [E820_COUNT], E820_MAX ; buffer full: stop, keep what we have
|
||||||
|
jae .done
|
||||||
|
inc byte [E820_COUNT] ; entry already written by BIOS at ES:DI
|
||||||
|
mov [e820_next], ebx ; save continuation across the next call
|
||||||
|
test ebx, ebx ; EBX=0 ends the list
|
||||||
|
jnz .loop
|
||||||
|
.done:
|
||||||
|
cmp byte [E820_COUNT], 0
|
||||||
|
jne .ok
|
||||||
|
xor ax, ax ; fallback: ES:DI may not have survived
|
||||||
|
mov es, ax
|
||||||
|
mov di, E820_BUF
|
||||||
|
mov eax, 0x00100000
|
||||||
|
stosd ; base = 1MB
|
||||||
|
xor eax, eax
|
||||||
|
stosd ; base high = 0
|
||||||
|
mov eax, 0x07F00000
|
||||||
|
stosd ; length = 127MB
|
||||||
|
xor eax, eax
|
||||||
|
stosd ; length high = 0
|
||||||
|
inc eax
|
||||||
|
stosd ; type = 1 (usable)
|
||||||
|
inc byte [E820_COUNT] ; count was 0, now 1
|
||||||
|
.ok:
|
||||||
|
ret
|
||||||
|
|
||||||
halt:
|
halt:
|
||||||
cli
|
cli
|
||||||
.hang:
|
.hang:
|
||||||
|
|
@ -226,9 +282,9 @@ gdt_ptr:
|
||||||
dw gdt_end - gdt - 1
|
dw gdt_end - gdt - 1
|
||||||
dd gdt
|
dd gdt
|
||||||
|
|
||||||
msg_loading db "Nadir: loading kernel...", 0x0D, 0x0A, 0
|
msg_loading db "Nadir: load", 0x0D, 0x0A, 0
|
||||||
msg_entering db "Nadir: entering protected mode", 0x0D, 0x0A, 0
|
msg_entering db "Nadir: pmode", 0x0D, 0x0A, 0
|
||||||
msg_disk_err db "Nadir: disk read error", 0x0D, 0x0A, 0
|
msg_disk_err db "Nadir: disk err", 0x0D, 0x0A, 0
|
||||||
|
|
||||||
boot_drive db 0
|
boot_drive db 0
|
||||||
spt dw 18
|
spt dw 18
|
||||||
|
|
@ -238,6 +294,8 @@ buf_seg dw 0 ; load buffer segment (starts 0x1000)
|
||||||
buf_off dw 0 ; load buffer offset within segment
|
buf_off dw 0 ; load buffer offset within segment
|
||||||
sectors_left dw 0 ; sectors still to load
|
sectors_left dw 0 ; sectors still to load
|
||||||
tries db 0 ; retries left for current sector
|
tries db 0 ; retries left for current sector
|
||||||
|
e820_next dd 0 ; E820 continuation across int 0x15
|
||||||
|
; (image-zeroed; first call starts at 0)
|
||||||
|
|
||||||
times 510-($-$$) db 0
|
times 510-($-$$) db 0
|
||||||
dw 0xAA55
|
dw 0xAA55
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,14 @@
|
||||||
;
|
;
|
||||||
; Steps: verify long mode exists, identity-map the first 1GB with 2MB
|
; 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
|
; 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).
|
; segment, zero BSS, copy the BIOS memory map staged by stage 1 into
|
||||||
|
; kernel .bss, then call the C entry point kmain (System V ABI).
|
||||||
; Interrupts stay disabled for the whole sequence.
|
; Interrupts stay disabled for the whole sequence.
|
||||||
|
;
|
||||||
|
; WHY copy after zeroing: the map globals live in .bss, so zeroing first
|
||||||
|
; then copying keeps the staged entries. Zeroing after would wipe them;
|
||||||
|
; copying before is impossible (BSS addresses are only valid once paging
|
||||||
|
; and the zeroed state the C code expects are both in place).
|
||||||
|
|
||||||
bits 32
|
bits 32
|
||||||
section .boot32
|
section .boot32
|
||||||
|
|
@ -18,11 +24,17 @@ global boot32_entry
|
||||||
extern kmain
|
extern kmain
|
||||||
extern __bss_start
|
extern __bss_start
|
||||||
extern __bss_end
|
extern __bss_end
|
||||||
|
extern memmap_count
|
||||||
|
extern memmap_entries
|
||||||
|
|
||||||
PML4 equ 0x70000
|
PML4 equ 0x70000
|
||||||
PDPT equ 0x71000
|
PDPT equ 0x71000
|
||||||
PD equ 0x72000
|
PD equ 0x72000
|
||||||
STACK64_TOP equ 0x90000
|
STACK64_TOP equ 0x90000
|
||||||
|
MEMMAP_COUNT_PHYS equ 0x5000 ; dword staged by stage 1
|
||||||
|
MEMMAP_BUF_PHYS equ 0x5020 ; 24-byte E820 entries staged by stage 1
|
||||||
|
E820_MAX equ 64
|
||||||
|
E820_ENTRY_SIZE equ 24
|
||||||
|
|
||||||
boot32_entry:
|
boot32_entry:
|
||||||
; --- CPUID: does this CPU do 64-bit long mode? ---
|
; --- CPUID: does this CPU do 64-bit long mode? ---
|
||||||
|
|
@ -121,6 +133,25 @@ long_entry:
|
||||||
xor eax, eax
|
xor eax, eax
|
||||||
rep stosb
|
rep stosb
|
||||||
|
|
||||||
|
; --- copy staged E820 map into .bss globals (identity map covers
|
||||||
|
; both 0x5000 and .bss, and paging is already on) ---
|
||||||
|
; WHY byte + clamp: stage 1 stages a single count byte (64 max fits);
|
||||||
|
; the clamp is belt-and-braces against a corrupt staging byte.
|
||||||
|
movzx eax, byte [abs MEMMAP_COUNT_PHYS]
|
||||||
|
cmp eax, E820_MAX
|
||||||
|
jbe .map_count_ok
|
||||||
|
mov eax, E820_MAX
|
||||||
|
.map_count_ok:
|
||||||
|
mov [rel memmap_count], eax
|
||||||
|
test eax, eax
|
||||||
|
jz .map_done
|
||||||
|
mov rsi, MEMMAP_BUF_PHYS
|
||||||
|
lea rdi, [rel memmap_entries]
|
||||||
|
mov ecx, eax
|
||||||
|
imul ecx, ecx, E820_ENTRY_SIZE ; bytes = count * 24
|
||||||
|
rep movsb
|
||||||
|
.map_done:
|
||||||
|
|
||||||
call kmain ; first C code — does not return
|
call kmain ; first C code — does not return
|
||||||
cli
|
cli
|
||||||
.hang:
|
.hang:
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ SECTIONS
|
||||||
{
|
{
|
||||||
. = 0x10000;
|
. = 0x10000;
|
||||||
|
|
||||||
|
__kernel_start = .;
|
||||||
|
|
||||||
.boot : {
|
.boot : {
|
||||||
*(.boot32)
|
*(.boot32)
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +42,8 @@ SECTIONS
|
||||||
__bss_end = .;
|
__bss_end = .;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__kernel_end = .;
|
||||||
|
|
||||||
/DISCARD/ : {
|
/DISCARD/ : {
|
||||||
*(.note*)
|
*(.note*)
|
||||||
*(.comment*)
|
*(.comment*)
|
||||||
|
|
|
||||||
117
kernel/core/klog.c
Normal file
117
kernel/core/klog.c
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/* Nadir boot logger (see klog.h).
|
||||||
|
*
|
||||||
|
* The only backends are console_putchar (VGA polling)
|
||||||
|
* and pit_ticks (monotonic u64) — no hardware addresses, no port I/O, no
|
||||||
|
* serial dependency live here. Serial mirroring stays in kmain, which
|
||||||
|
* prints the same text without the timestamp prefix (kept simple).
|
||||||
|
*
|
||||||
|
* Timestamp math: secs = ticks / PIT_HZ, frac = (ticks % PIT_HZ) * 10000
|
||||||
|
* (1000000us/s divided by 100Hz). The field is 12 wide, space-padded on
|
||||||
|
* the left (`[ 0.000000]`); past 99999s it overflows the
|
||||||
|
* pad and just prints long.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "console.h"
|
||||||
|
#include "klog.h"
|
||||||
|
#include "pit.h"
|
||||||
|
|
||||||
|
/* Width of the `secs.frac` field between `[` and `]`. */
|
||||||
|
#define KLOG_TIME_WIDTH 12U
|
||||||
|
|
||||||
|
/* Print the `[ 0.000000] subsystem: ` prefix. Reads pit_ticks() live
|
||||||
|
* so piecewise lines and one-shot lines share one code path. */
|
||||||
|
static void klog_prefix(const char *subsystem)
|
||||||
|
{
|
||||||
|
uint64_t ticks = pit_ticks();
|
||||||
|
uint64_t secs = ticks / (uint64_t)PIT_HZ;
|
||||||
|
uint64_t frac = (ticks % (uint64_t)PIT_HZ) * 10000ULL;
|
||||||
|
char num[22];
|
||||||
|
int n = 0;
|
||||||
|
uint64_t v;
|
||||||
|
uint64_t div;
|
||||||
|
int digits;
|
||||||
|
uint64_t len;
|
||||||
|
|
||||||
|
/* Decimal seconds into num (reversed then flipped below). */
|
||||||
|
v = secs;
|
||||||
|
if (v == 0U) {
|
||||||
|
num[n++] = '0';
|
||||||
|
} else {
|
||||||
|
while (v != 0U && n < 20) {
|
||||||
|
num[n++] = (char)('0' + (v % 10U));
|
||||||
|
v /= 10U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Field length is digits + '.' + 6 frac digits; pad left with spaces
|
||||||
|
* to KLOG_TIME_WIDTH so short uptimes align */
|
||||||
|
digits = n;
|
||||||
|
len = (uint64_t)digits + 7U;
|
||||||
|
console_putchar('[');
|
||||||
|
while (len < (uint64_t)KLOG_TIME_WIDTH) {
|
||||||
|
console_putchar(' ');
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
while (n > 0) {
|
||||||
|
n--;
|
||||||
|
console_putchar(num[n]);
|
||||||
|
}
|
||||||
|
console_putchar('.');
|
||||||
|
/* Exactly 6 fraction digits, zero-padded (10ms ticks leave the last
|
||||||
|
* 4 as zeros — resolution limit, not a bug). */
|
||||||
|
div = 100000ULL;
|
||||||
|
while (div > 0U) {
|
||||||
|
console_putchar((char)('0' + ((frac / div) % 10U)));
|
||||||
|
div /= 10U;
|
||||||
|
}
|
||||||
|
console_puts("] ");
|
||||||
|
console_puts(subsystem);
|
||||||
|
console_puts(": ");
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog(const char *subsystem, const char *msg)
|
||||||
|
{
|
||||||
|
klog_begin(subsystem);
|
||||||
|
klog_str(msg);
|
||||||
|
klog_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog_begin(const char *subsystem)
|
||||||
|
{
|
||||||
|
klog_prefix(subsystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog_str(const char *s)
|
||||||
|
{
|
||||||
|
console_puts(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog_dec(uint64_t v)
|
||||||
|
{
|
||||||
|
char buf[20];
|
||||||
|
int n = 0;
|
||||||
|
|
||||||
|
if (v == 0U) {
|
||||||
|
console_putchar('0');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (v != 0U && n < (int)sizeof(buf)) {
|
||||||
|
buf[n++] = (char)('0' + (v % 10U));
|
||||||
|
v /= 10U;
|
||||||
|
}
|
||||||
|
while (n > 0) {
|
||||||
|
n--;
|
||||||
|
console_putchar(buf[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog_hex64(uint64_t v)
|
||||||
|
{
|
||||||
|
console_puthex64(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void klog_end(void)
|
||||||
|
{
|
||||||
|
console_putchar('\n');
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
* after every device is programmed; the main loop keeps IF=1 and sleeps
|
* after every device is programmed; the main loop keeps IF=1 and sleeps
|
||||||
* in `hlt` (which wakes on each IRQ) — never `cli`-parks, or timer ticks
|
* in `hlt` (which wakes on each IRQ) — never `cli`-parks, or timer ticks
|
||||||
* and keystrokes would stop. It never returns; on return the caller halts.
|
* and keystrokes would stop. It never returns; on return the caller halts.
|
||||||
|
*
|
||||||
|
* Every line goes through klog`[ 0.000000] subsystem: message`,
|
||||||
|
* time from pit_ticks()/PIT_HZ).
|
||||||
|
* Serial COM1 mirrors the same text without the timestamp prefix.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -16,34 +20,16 @@
|
||||||
#include "console.h"
|
#include "console.h"
|
||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
#include "keyboard.h"
|
#include "keyboard.h"
|
||||||
|
#include "klog.h"
|
||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
#include "pit.h"
|
#include "pit.h"
|
||||||
|
#include "pmm.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
|
|
||||||
/* A value that only comes out right if the CPU truly executes 64-bit
|
/* A value that only comes out right if the CPU truly executes 64-bit
|
||||||
* arithmetic: every nibble survives the shifts above 32 bits. */
|
* arithmetic: every nibble survives the shifts above 32 bits. */
|
||||||
#define PROOF_VALUE 0x0123456789ABCDEFULL
|
#define PROOF_VALUE 0x0123456789ABCDEFULL
|
||||||
|
|
||||||
/* Print an unsigned decimal via console_putchar (no libc printf). */
|
|
||||||
static void print_dec(uint64_t v)
|
|
||||||
{
|
|
||||||
char buf[20];
|
|
||||||
int n = 0;
|
|
||||||
|
|
||||||
if (v == 0) {
|
|
||||||
console_putchar('0');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (v != 0 && n < (int)sizeof(buf)) {
|
|
||||||
buf[n++] = (char)('0' + (v % 10U));
|
|
||||||
v /= 10U;
|
|
||||||
}
|
|
||||||
while (n > 0) {
|
|
||||||
n--;
|
|
||||||
console_putchar(buf[n]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mirror one echoed keystroke to COM1 when the UART looks present. */
|
/* Mirror one echoed keystroke to COM1 when the UART looks present. */
|
||||||
static void serial_echo(char c, int ok)
|
static void serial_echo(char c, int ok)
|
||||||
{
|
{
|
||||||
|
|
@ -70,8 +56,9 @@ void kmain(void)
|
||||||
uint64_t last_uptime;
|
uint64_t last_uptime;
|
||||||
|
|
||||||
console_clear();
|
console_clear();
|
||||||
console_puts("Hello World from Nadir Kernel !\n");
|
/* Boot banner: keeps the "64-bit" needle the CI boot checker greps
|
||||||
console_puts("Running C code in 64-bit long mode (Ring 0).\n");
|
* for (see .github/scripts/check_boot.py). */
|
||||||
|
klog("nadir", "Hello World from Nadir kernel (64-bit long mode, Ring 0)");
|
||||||
|
|
||||||
/* WHY this order: pic_remap must precede idt_install_irqs so IRQ
|
/* WHY this order: pic_remap must precede idt_install_irqs so IRQ
|
||||||
* gates land on valid remapped vectors 32-47 (pre-remap they would
|
* gates land on valid remapped vectors 32-47 (pre-remap they would
|
||||||
|
|
@ -80,48 +67,68 @@ void kmain(void)
|
||||||
* fires into a half-wired handler; pic_remap masks everything and
|
* fires into a half-wired handler; pic_remap masks everything and
|
||||||
* each device opts in via pic_unmask, so stray lines stay silent. */
|
* each device opts in via pic_unmask, so stray lines stay silent. */
|
||||||
idt_init();
|
idt_init();
|
||||||
console_puts("IDT installed (vectors 0-31).\n");
|
klog("idt", "exceptions 0-31 installed");
|
||||||
|
|
||||||
pic_remap();
|
pic_remap();
|
||||||
console_puts("PIC remapped (IRQ 0-15 -> vectors 32-47, masked).\n");
|
klog("pic", "remapped IRQ 0-15 to vectors 32-47");
|
||||||
|
|
||||||
idt_install_irqs();
|
idt_install_irqs();
|
||||||
console_puts("IRQ gates installed (vectors 32-47).\n");
|
klog("idt", "IRQ gates 32-47 installed");
|
||||||
|
|
||||||
serial_init();
|
serial_init();
|
||||||
serial_present = serial_ok();
|
serial_present = serial_ok();
|
||||||
console_puts("Serial COM1 38400 8N1 ready.\n");
|
klog("serial", "COM1 38400 8N1 ready");
|
||||||
if (serial_present != 0) {
|
if (serial_present != 0) {
|
||||||
serial_puts("Hello World from Nadir Kernel !\n");
|
serial_puts("Hello World from Nadir kernel\n");
|
||||||
serial_puts("Serial COM1 38400 8N1 ready.\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pit_init();
|
pit_init();
|
||||||
console_puts("PIT 100Hz on IRQ0 ready.\n");
|
klog("pit", "100Hz on IRQ0");
|
||||||
if (serial_present != 0) {
|
if (serial_present != 0) {
|
||||||
serial_puts("PIT 100Hz on IRQ0 ready.\n");
|
serial_puts("pit: 100Hz on IRQ0\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard_init();
|
keyboard_init();
|
||||||
console_puts("Keyboard PS/2 IRQ1 ready.\n");
|
klog("keyboard", "PS/2 ready on IRQ1");
|
||||||
if (serial_present != 0) {
|
if (serial_present != 0) {
|
||||||
serial_puts("Keyboard PS/2 IRQ1 ready.\n");
|
serial_puts("keyboard: PS/2 ready on IRQ1\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pmm_init only reads the staged E820 globals and writes
|
||||||
|
* its own .bss bitmap, so it runs
|
||||||
|
* after all devices are programmed but before sti, with IF=0, so no
|
||||||
|
* handler can observe a half-built allocator. Stats go through the
|
||||||
|
* polling VGA console, which is IRQ-safe by construction. */
|
||||||
|
pmm_init(memmap_count, memmap_entries);
|
||||||
|
klog_begin("pmm");
|
||||||
|
klog_str("E820 entries ");
|
||||||
|
klog_dec(memmap_count);
|
||||||
|
klog_str(", usable frames ");
|
||||||
|
klog_dec(pmm_usable_count());
|
||||||
|
klog_str(" / total frames ");
|
||||||
|
klog_dec(pmm_total_count());
|
||||||
|
klog_str(", free frames ");
|
||||||
|
klog_dec(pmm_free_count());
|
||||||
|
klog_str(", reserved ");
|
||||||
|
klog_dec(pmm_usable_count() - pmm_free_count());
|
||||||
|
klog_end();
|
||||||
|
|
||||||
/* Explicit opt-in for the two live lines; idempotent even though
|
/* Explicit opt-in for the two live lines; idempotent even though
|
||||||
* keyboard_init already unmasked IRQ1. */
|
* keyboard_init already unmasked IRQ1. */
|
||||||
pic_unmask(0);
|
pic_unmask(0);
|
||||||
pic_unmask(1);
|
pic_unmask(1);
|
||||||
|
|
||||||
__asm__ volatile("sti");
|
__asm__ volatile("sti");
|
||||||
console_puts("Interrupts enabled (IRQs 0-1).\n");
|
klog("nadir", "interrupts enabled (IRQs 0-1)");
|
||||||
if (serial_present != 0) {
|
if (serial_present != 0) {
|
||||||
serial_puts("Interrupts enabled (IRQs 0-1).\n");
|
serial_puts("nadir: interrupts enabled (IRQs 0-1)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
console_puts("64-bit proof value: ");
|
klog_begin("nadir");
|
||||||
console_puthex64(PROOF_VALUE);
|
klog_str("64-bit proof value: ");
|
||||||
console_puts("\nHalting in hlt loop (timer + keyboard live).\n");
|
klog_hex64(PROOF_VALUE);
|
||||||
|
klog_end();
|
||||||
|
klog("nadir", "halting in hlt loop (timer + keyboard live)");
|
||||||
|
|
||||||
/* Main loop: hlt sleeps until the next IRQ (IF=1 throughout), then
|
/* Main loop: hlt sleeps until the next IRQ (IF=1 throughout), then
|
||||||
* one poll pass prints uptime ~1/s and drains echoed keys. */
|
* one poll pass prints uptime ~1/s and drains echoed keys. */
|
||||||
|
|
@ -136,13 +143,15 @@ void kmain(void)
|
||||||
uint64_t tenths = (now / 10U) % 10U;
|
uint64_t tenths = (now / 10U) % 10U;
|
||||||
|
|
||||||
last_uptime = now;
|
last_uptime = now;
|
||||||
console_puts("uptime ");
|
klog_begin("nadir");
|
||||||
print_dec(secs);
|
klog_str("uptime ");
|
||||||
console_puts(".");
|
klog_dec(secs);
|
||||||
print_dec(tenths);
|
klog_str(".");
|
||||||
console_puts("s (ticks ");
|
klog_dec(tenths);
|
||||||
console_puthex64(now);
|
klog_str("s (ticks ");
|
||||||
console_puts(")\n");
|
klog_hex64(now);
|
||||||
|
klog_str(")");
|
||||||
|
klog_end();
|
||||||
if (serial_present != 0) {
|
if (serial_present != 0) {
|
||||||
serial_puts("uptime (see VGA for value)\n");
|
serial_puts("uptime (see VGA for value)\n");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
197
kernel/mm/pmm.c
Normal file
197
kernel/mm/pmm.c
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/* Nadir physical memory manager (PMM-only)
|
||||||
|
*
|
||||||
|
* very address it touches arrives as a plain
|
||||||
|
* physical range (E820 entries from the caller, reservation bounds from
|
||||||
|
* the linker/boot chain). Runs in 64-bit long mode at Ring 0 (CPL 0)
|
||||||
|
* under the stage-2 identity map, so physical addresses are dereferenced
|
||||||
|
* as-is. pmm_init runs once with IF=0 before sti; alloc/free never block
|
||||||
|
* and never touch hardware, so they are safe under any IF state.
|
||||||
|
* Fault behavior: no page fault is possible inside the first 1GB
|
||||||
|
* identity window; a caller passing a wild E820 pointer faults before we
|
||||||
|
* run, not in here. Bitmap: bit set = used/reserved, bit clear = free.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "e820.h"
|
||||||
|
#include "pmm.h"
|
||||||
|
|
||||||
|
/* Filled by the stage-2 entry (copy from the stage-1 staging buffer at
|
||||||
|
* 0x5000/0x5020) after BSS zeroing, before kmain runs. */
|
||||||
|
uint32_t memmap_count;
|
||||||
|
struct e820_entry memmap_entries[E820_MAX];
|
||||||
|
|
||||||
|
/* Kernel image bounds from the linker script (linked at 0x10000). */
|
||||||
|
extern char __kernel_start[];
|
||||||
|
extern char __kernel_end[];
|
||||||
|
|
||||||
|
/* One bit per 4KB frame over the first 1GB: 256K frames = 32KB in .bss. */
|
||||||
|
static uint8_t bitmap[PMM_MAX_FRAMES / 8U];
|
||||||
|
|
||||||
|
static uint64_t g_usable;
|
||||||
|
static uint64_t g_free;
|
||||||
|
|
||||||
|
/* WHY bit ops instead of a byte-per-frame array: 32KB of .bss keeps the
|
||||||
|
* kernel image small (sectors are loaded from floppy LBA 1..N, so every
|
||||||
|
* KB costs boot time); the shift/mask cost is negligible next to a VGA
|
||||||
|
* print. */
|
||||||
|
static void set_bit(uint64_t frame)
|
||||||
|
{
|
||||||
|
bitmap[frame / 8U] |= (uint8_t)(1U << (frame % 8U));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clear_bit(uint64_t frame)
|
||||||
|
{
|
||||||
|
bitmap[frame / 8U] &= (uint8_t)~(1U << (frame % 8U));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_bit(uint64_t frame)
|
||||||
|
{
|
||||||
|
return (bitmap[frame / 8U] & (uint8_t)(1U << (frame % 8U))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mark every frame in [base, base+len) reserved. Out-of-window and
|
||||||
|
* already-reserved frames are skipped, so overlapping/low regions are
|
||||||
|
* safe to reserve twice. */
|
||||||
|
static void reserve_range(uint64_t base, uint64_t len)
|
||||||
|
{
|
||||||
|
uint64_t end;
|
||||||
|
uint64_t frame;
|
||||||
|
|
||||||
|
if (len == 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
end = base + len;
|
||||||
|
if (end < base) {
|
||||||
|
end = UINT64_MAX;
|
||||||
|
}
|
||||||
|
if (base >= PMM_MAX_BYTES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (end > PMM_MAX_BYTES) {
|
||||||
|
end = PMM_MAX_BYTES;
|
||||||
|
}
|
||||||
|
for (frame = base / PMM_FRAME_SIZE; frame < end / PMM_FRAME_SIZE; frame++) {
|
||||||
|
if (test_bit(frame) == 0) {
|
||||||
|
set_bit(frame);
|
||||||
|
g_free--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pmm_init(uint32_t count, const struct e820_entry *entries)
|
||||||
|
{
|
||||||
|
uint64_t i;
|
||||||
|
uint64_t frame;
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
|
||||||
|
/* Start fully reserved; usable E820 frames are cleared below. This
|
||||||
|
* way unknown memory can never be handed out by accident. */
|
||||||
|
for (i = 0; i < sizeof(bitmap); i++) {
|
||||||
|
bitmap[i] = 0xFFU;
|
||||||
|
}
|
||||||
|
g_usable = 0;
|
||||||
|
g_free = 0;
|
||||||
|
|
||||||
|
if (entries == 0) {
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
if (count > E820_MAX) {
|
||||||
|
count = E820_MAX;
|
||||||
|
}
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (entries[i].type != E820_USABLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entries[i].length == 0U) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
start = entries[i].base;
|
||||||
|
end = start + entries[i].length;
|
||||||
|
if (end < start) {
|
||||||
|
end = UINT64_MAX;
|
||||||
|
}
|
||||||
|
if (start >= PMM_MAX_BYTES) {
|
||||||
|
continue; /* above the identity-mapped window: ignored */
|
||||||
|
}
|
||||||
|
if (end > PMM_MAX_BYTES) {
|
||||||
|
end = PMM_MAX_BYTES;
|
||||||
|
}
|
||||||
|
/* Align the range to whole frames */
|
||||||
|
start = (start + PMM_FRAME_SIZE - 1U) / PMM_FRAME_SIZE;
|
||||||
|
end = end / PMM_FRAME_SIZE;
|
||||||
|
for (frame = start; frame < end; frame++) {
|
||||||
|
if (test_bit(frame) != 0) {
|
||||||
|
clear_bit(frame);
|
||||||
|
g_usable++;
|
||||||
|
g_free++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Second pass: carve out every non-usable E820 range. WHY a separate
|
||||||
|
* pass instead of skipping overlaps up front: BIOS maps routinely
|
||||||
|
* stack a reserved entry (e.g. ACPI data) inside a larger usable one,
|
||||||
|
* and reserve_range is idempotent, so usable-then-reserved converges
|
||||||
|
* on the right bitmap whatever order the firmware lists. */
|
||||||
|
for (i = 0; i < count; i++) {
|
||||||
|
if (entries[i].type != E820_USABLE) {
|
||||||
|
reserve_range(entries[i].base, entries[i].length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reserve_range(0x00000000ULL, 0x00100000ULL);
|
||||||
|
reserve_range((uint64_t)(uintptr_t)__kernel_start,
|
||||||
|
(uint64_t)(uintptr_t)__kernel_end - (uint64_t)(uintptr_t)__kernel_start);
|
||||||
|
reserve_range(0x00070000ULL, 0x00004000ULL);
|
||||||
|
reserve_range(0x00005000ULL, 0x00000620ULL);
|
||||||
|
reserve_range(0x0008C000ULL, 0x00004000ULL);
|
||||||
|
reserve_range(0x000B8000ULL, 0x00001000ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t pmm_alloc_frame(void)
|
||||||
|
{
|
||||||
|
uint64_t frame;
|
||||||
|
|
||||||
|
for (frame = 0; frame < PMM_MAX_FRAMES; frame++) {
|
||||||
|
if (test_bit(frame) == 0) {
|
||||||
|
set_bit(frame);
|
||||||
|
g_free--;
|
||||||
|
return frame * PMM_FRAME_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; /* frame 0 is always reserved: 0 unambiguously means OOM */
|
||||||
|
}
|
||||||
|
|
||||||
|
void pmm_free_frame(uint64_t addr)
|
||||||
|
{
|
||||||
|
uint64_t frame;
|
||||||
|
|
||||||
|
if ((addr % PMM_FRAME_SIZE) != 0U) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (addr >= PMM_MAX_BYTES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame = addr / PMM_FRAME_SIZE;
|
||||||
|
if (test_bit(frame) != 0) {
|
||||||
|
clear_bit(frame);
|
||||||
|
g_free++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t pmm_total_count(void)
|
||||||
|
{
|
||||||
|
return PMM_MAX_FRAMES;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t pmm_free_count(void)
|
||||||
|
{
|
||||||
|
return g_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t pmm_usable_count(void)
|
||||||
|
{
|
||||||
|
return g_usable;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue