From 31493c16d10e42da6fb58a92b55cf73b9d0fa64b Mon Sep 17 00:00:00 2001
From: Eemeli <eemeli.o.lehtonen@utu.fi>
Date: Mon, 30 Jan 2023 03:43:12 +0200
Subject: [PATCH] page frame allocator init

---
 .cargo/config.toml                      |   7 +-
 Cargo.toml                              |   4 -
 Makefile                                | 188 ++++++++++--------------
 {.cargo => old_cargo_configs}/runner.sh |   0
 qemu.mk                                 |  42 ++++++
 src/boot/limine/Makefile                |  43 ++++++
 src/boot/limine/mem.rs                  |  33 ++++-
 src/boot/limine/mod.rs                  |   1 +
 src/log.rs                              |  19 +--
 src/main.rs                             |   1 +
 src/mem.rs                              |  90 ------------
 src/mem/bump.rs                         |  90 ++++++++++++
 src/mem/map.rs                          |  17 +++
 src/mem/mod.rs                          |  21 +++
 src/mem/pfa.rs                          | 113 ++++++++++++++
 src/smp.rs                              |   2 +-
 src/term/escape/encode.rs               |  84 ++++++++---
 src/util/bitmap.rs                      | 142 ++++++++++++++++++
 src/{util.rs => util/fmt.rs}            |   0
 src/util/mod.rs                         |   2 +
 20 files changed, 655 insertions(+), 244 deletions(-)
 rename {.cargo => old_cargo_configs}/runner.sh (100%)
 create mode 100644 qemu.mk
 create mode 100644 src/boot/limine/Makefile
 delete mode 100644 src/mem.rs
 create mode 100644 src/mem/bump.rs
 create mode 100644 src/mem/map.rs
 create mode 100644 src/mem/mod.rs
 create mode 100755 src/mem/pfa.rs
 create mode 100644 src/util/bitmap.rs
 rename src/{util.rs => util/fmt.rs} (100%)
 create mode 100644 src/util/mod.rs

diff --git a/.cargo/config.toml b/.cargo/config.toml
index c04d9e5..b9360a8 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,8 +1,3 @@
-[alias]
-debug = "run -- --debug"
-
 [build]
+# this is so that rust-analyzer wouldn't fight me constantly
 target = "x86_64-unknown-none"
-
-[target.x86_64-unknown-none]
-runner = "./.cargo/runner.sh"
diff --git a/Cargo.toml b/Cargo.toml
index 96e7ffd..4bcd210 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,9 +13,6 @@ multiboot1 = []
 multiboot2 = []
 # Pick limine OR bootboot OR multiboot1 OR multiboot2, they conflict with eachother
 
-# [lib]
-# crate-type = ["staticlib"]
-
 [dependencies]
 spin = "0.9.4"
 volatile = "0.4.5"
@@ -29,6 +26,5 @@ rev = "c2fbc349419d4330b80e053019ad2fe504a61764"
 # version = "0.1.9" # 0.1.9 LimineFile struct has a bug
 optional = true
 
-
 [build-dependencies]
 image = "0.24.5"
diff --git a/Makefile b/Makefile
index 74ff7dc..4236c51 100644
--- a/Makefile
+++ b/Makefile
@@ -4,127 +4,99 @@
 # @file
 # @version 0.1
 
-ARCH          ?= x86_64
-#ARCH          ?= x86
-PROFILE       ?= debug
-#PROFILE       ?= release
-GDB           ?= false
-
-# binary config
-NASM          ?= nasm
-LD            ?= ld.lld
-OBJCOPY       ?= llvm-objcopy
-CARGO         ?= cargo
-#CARGO         ?= cargo-clif
+# config
+ARCH             ?= x86_64
+#ARCH             ?= x86
+PROFILE          ?= debug
+#PROFILE          ?= release
+GDB              ?= false
+BOOTLOADER       ?= limine
+KVM              ?= true
+
+# binaries
+NASM             ?= nasm
+LD               ?= ld.lld
+OBJCOPY          ?= llvm-objcopy
+CARGO            ?= cargo
+#CARGO            ?= cargo-clif
+XORRISO          ?= xorriso
+JQ               ?= jq
+QEMU_x86_64      ?= qemu-system-x86_64
+QEMU_x86         ?= qemu-system-i386
+QEMU             ?= ${QEMU_${ARCH}}
+
+# rust targets
+RUST_T_x86_64    := x86_64-unknown-none
 
 # common directories
-TARGET_DIR    ?= target
-HYPER_DIR     := ${TARGET_DIR}/hyperion/${ARCH}
-ARCH_DIR      := src/arch/${ARCH}
-CARGO_DIR      = ${TARGET_DIR}/${RUST_T_${ARCH}}/${PROFILE}
-
-# hyperion kernel lib
-RUST_T_x86_64 := x86_64-unknown-none
-RUST_F_debug  :=
-RUST_F_release:= --release
-CARGO_FLAGS   ?=
-CARGO_FLAGS   += ${RUST_F_${PROFILE}}
-CARGO_FLAGS   += --target=${RUST_T_${ARCH}}
-KERNEL_LIB    := ${CARGO_DIR}/libhyperion.a
-KERNEL_SRC    := $(filter-out %: ,$(file < ${CARGO_DIR}/libhyperion.d))
-${KERNEL_LIB} : ${KERNEL_SRC} Makefile Cargo.toml Cargo.lock
-	@echo "\n\033[32m--[[ building Hyperion lib ]]--\033[0m"
+TARGET_DIR       ?= target
+HYPER_DIR        := ${TARGET_DIR}/hyperion/${BOOTLOADER}/${ARCH}
+ARCH_DIR         := src/arch/${ARCH}
+BOOT_DIR         := src/boot
+CARGO_DIR        := ${TARGET_DIR}/${RUST_T_${ARCH}}/${PROFILE}
+ISO_DIR          := ${HYPER_DIR}/iso
+ISO_TESTING_DIR  := ${HYPER_DIR}/iso-testing
+
+# artefacts
+HYPERION         := ${HYPER_DIR}/hyperion.iso
+HYPERION_TESTING := ${HYPER_DIR}/hyperion-testing.iso
+
+# rust/cargo
+RUST_F_debug     :=
+RUST_F_release   := --release
+CARGO_FLAGS      ?=
+CARGO_FLAGS      += ${RUST_F_${PROFILE}}
+CARGO_FLAGS      += --target=${RUST_T_${ARCH}}
+CARGO_FLAGS      += --package=hyperion
+KERNEL           := ${CARGO_DIR}/hyperion
+KERNEL_TESTING   := ${KERNEL}-testing
+KERNEL_SRC       := $(filter-out %: ,$(file < ${CARGO_DIR}/hyperion.d))
+
+# gdb
+GDB_FLAGS        ?=
+GDB_FLAGS        += --eval-command="target remote localhost:1234"
+GDB_FLAGS        += --eval-command="symbol-file ${KERNEL}"
+
+# hyperion kernel compilation
+${KERNEL}: ${KERNEL_SRC} Makefile Cargo.toml Cargo.lock
+	@echo "\n\033[32m--[[ building Hyperion ]]--\033[0m"
 	${CARGO} build ${CARGO_FLAGS}
+	@touch ${KERNEL}
+
+${KERNEL_TESTING}: ${KERNEL_SRC} Makefile Cargo.toml Cargo.lock
+	@echo "\n\033[32m--[[ building Hyperion-Testing ]]--\033[0m"
+	@${CARGO} test --no-run # first one prints human readable errors
+	${CARGO} test --no-run --message-format=json ${CARGO_FLAGS} | \
+		jq -r "select(.profile.test == true) | .filenames[]" | \
+		xargs -I % cp "%" ${KERNEL_TESTING}
+	@touch ${KERNEL_TESTING}
+
+# ISO generation
+include ./${BOOT_DIR}/${BOOTLOADER}/Makefile
 
-# hyperion boot code
-# BOOT_SRC      := ${ARCH_DIR}/start.asm
-# BOOT_OBJ      := ${HYPER_DIR}/start.o
-# NASM_F_x86_64 := elf64
-# NASM_F_x86    := elf32
-# NASM_FLAGS    ?=
-# NASM_FLAGS    += ${BOOT_SRC}
-# NASM_FLAGS    += -o ${BOOT_OBJ}
-# NASM_FLAGS    += -f ${NASM_F_${ARCH}}
-# ${BOOT_OBJ} : ${BOOT_SRC} Makefile
-# 	@echo "\n\033[32m--[[ building Hyperion boot ]]--\033[0m"
-# 	mkdir -p ${HYPER_DIR}
-# 	${NASM} ${NASM_FLAGS}
-
-# hyperion kernel elf
-LD_SCRIPT     := ${ARCH_DIR}/link.ld
-KERNEL_ELF    := ${HYPER_DIR}/hyperion
-KERNEL_DEPS   := ${KERNEL_LIB} #${BOOT_OBJ}
-LD_M_x86_64   := elf_x86_64
-LD_M_x86      := elf_i386
-LD_FLAGS      ?=
-#LD_FLAGS      += --whole-archive
-LD_FLAGS      += ${KERNEL_DEPS}
-LD_FLAGS      += -o ${KERNEL_ELF}
-LD_FLAGS      += --gc-sections
-LD_FLAGS      += -T ${LD_SCRIPT}
-LD_FLAGS      += -m ${LD_M_${ARCH}}
-${KERNEL_ELF} : ${KERNEL_DEPS} ${LD_SCRIPT} Makefile
-	@echo "\n\033[32m--[[ building Hyperion kernel ]]--\033[0m"
-	mkdir -p ${HYPER_DIR}
-	${LD} ${LD_FLAGS}
-#	evil hack to satisfy qemu and grub:
-#	the entry format has to be x86 not x86_64
-	${OBJCOPY} -O elf32-i386 ${KERNEL_ELF}
-
-# hyperion iso
-HYPERION      := ${HYPER_DIR}/hyperion.iso
-ISO_DIR       := ${HYPER_DIR}/iso
-BOOT_DIR      := ${ISO_DIR}/boot
-GRUB_DIR      := ${BOOT_DIR}/grub
-${HYPERION} : ${KERNEL_ELF} cfg/grub.cfg Makefile
-	@echo "\n\033[32m--[[ building Hyperion iso ]]--\033[0m"
-	mkdir -p ${GRUB_DIR}
-	cp cfg/grub.cfg ${GRUB_DIR}
-	cp ${KERNEL_ELF} ${BOOT_DIR}/
-	grub-mkrescue /usr/lib/grub/i386-pc -o $@ ${ISO_DIR}
+# ISO running
+include ./qemu.mk
 
 # build alias
-build : ${KERNEL_ELF}
-
-# qemu direct kernel boot alias
-QEMU_x86_64   ?= qemu-system-x86_64
-QEMU_x86      ?= qemu-system-i386
-QEMU_FLAGS    ?=
-QEMU_FLAGS    += -serial stdio
-QEMU_FLAGS    += -s
-ifeq (${GDB},true)
-QEMU_FLAGS    += -S
-endif
-QEMU_FLAGS    += -enable-kvm
-QEMU_FLAGS    += -d cpu_reset,guest_errors
-#QEMU_FLAGS    += -M pc-i440fx-7.2
-#QEMU_FLAGS    += -device VGA,vgamem_mb=64
-QEMU_FLAGS    += -vga std
-QEMU_KERNEL   := -kernel ${KERNEL_ELF} -append qemu
-qemu : ${KERNEL_ELF}
-	${QEMU_${ARCH}} ${QEMU_FLAGS} ${QEMU_KERNEL}
-
-# qemu iso boot alias
-#QEMU_FLAGS    += -bios ${QEMU_OVMF}
-QEMU_OVMF     ?= /usr/share/ovmf/x64/OVMF.fd
-QEMU_ISO      := -drive format=raw,file=${HYPERION}
-qemu_iso : ${HYPERION}
-	${QEMU_${ARCH}} ${QEMU_FLAGS} ${QEMU_ISO}
+build: ${KERNEL}
+
+# bootable iso alias
+iso: ${HYPERION}
+
+reset-cargo-deps:
+	rm ${CARGO_DIR}/hyperion.d
 
 # connect gdb to qemu
-GDB_FLAGS     ?=
-GDB_FLAGS     += --eval-command="target remote localhost:1234"
-GDB_FLAGS     += --eval-command="symbol-file ${KERNEL_ELF}"
 gdb:
 	gdb ${GDB_FLAGS}
 
 # objdump
-objdump : ${KERNEL_ELF}
-	objdump -D ${KERNEL_ELF}
+objdump : ${KERNEL}
+	objdump -D ${KERNEL}
 
-readelf : ${KERNEL_ELF}
-	readelf --all ${KERNEL_ELF}
+readelf : ${KERNEL}
+	readelf --all ${KERNEL}
 
-.PHONY : build qemu objdump readelf
+.PHONY : build iso reset-cargo-deps run test gdb objdump readelf
 
 # end
diff --git a/.cargo/runner.sh b/old_cargo_configs/runner.sh
similarity index 100%
rename from .cargo/runner.sh
rename to old_cargo_configs/runner.sh
diff --git a/qemu.mk b/qemu.mk
new file mode 100644
index 0000000..51ff39d
--- /dev/null
+++ b/qemu.mk
@@ -0,0 +1,42 @@
+QEMU_FLAGS      ?=
+ifeq (${KVM},true)
+QEMU_FLAGS      += -enable-kvm
+endif
+ifeq (${GDB},true)
+QEMU_FLAGS      += -s -S
+endif
+QEMU_FLAGS      += -machine q35
+QEMU_FLAGS      += -cpu qemu64,+rdrand,+rdseed
+QEMU_FLAGS      += -smp 8
+QEMU_FLAGS      += -m 256m
+QEMU_FLAGS      += -M smm=off
+#QEMU_FLAGS      += -d int,guest_errors,cpu_reset
+QEMU_FLAGS      += -d int,guest_errors
+QEMU_FLAGS      += -no-reboot
+QEMU_FLAGS      += -serial stdio
+#QEMU_OVMF     ?= /usr/share/ovmf/x64/OVMF.fd
+#QEMU_FLAGS    += -bios ${QEMU_OVMF}
+QEMU_RUN_FLAGS  ?=
+QEMU_RUN_FLAGS  += ${QEMU_FLAGS}
+QEMU_TEST_FLAGS ?=
+QEMU_TEST_FLAGS += ${QEMU_FLAGS}
+QEMU_TEST_FLAGS += -device isa-debug-exit,iobase=0xf4,iosize=0x04
+QEMU_TEST_FLAGS += -display none
+QEMU_KERNEL     := -kernel ${KERNEL} -append qemu
+QEMU_DRIVE      := -drive format=raw,file
+
+# TODO: multiboot1 direct kernel boot
+
+# qemu normal run
+run: ${HYPERION}
+	@echo "\n\033[32m--[[ running Hyperion in QEMU ]]--\033[0m"
+	${QEMU} ${QEMU_RUN_FLAGS} ${QEMU_DRIVE}=${HYPERION}
+
+# run tests in qemu
+test: ${HYPERION_TESTING}
+	@echo "\n\033[32m--[[ running Hyperion-Testing in QEMU ]]--\033[0m"
+	${QEMU} ${QEMU_TEST_FLAGS} ${QEMU_DRIVE}=${HYPERION_TESTING};\
+	[ $$? -ne 33 ] && exit 1;\
+	exit 0
+
+		
diff --git a/src/boot/limine/Makefile b/src/boot/limine/Makefile
new file mode 100644
index 0000000..56a6592
--- /dev/null
+++ b/src/boot/limine/Makefile
@@ -0,0 +1,43 @@
+LIM_GIT   := "https://github.com/limine-bootloader/limine.git" 
+LIM_DIR   := ${TARGET_DIR}/limine
+LIM_FILES := ${LIM_DIR}/limine-cd-efi.bin ${LIM_DIR}/limine-cd.bin ${LIM_DIR}/limine.sys
+LIM_CFG   := cfg/limine.cfg
+
+XORRISO_FLAGS ?=
+XORRISO_FLAGS += -as mkisofs
+XORRISO_FLAGS += -b limine-cd.bin
+XORRISO_FLAGS += -no-emul-boot 
+XORRISO_FLAGS += -boot-load-size 4
+XORRISO_FLAGS += -boot-info-table
+XORRISO_FLAGS += --efi-boot limine-cd-efi.bin
+XORRISO_FLAGS += -efi-boot-part
+XORRISO_FLAGS += --efi-boot-image
+XORRISO_FLAGS += --protective-msdos-label
+
+# clone limine
+${LIM_FILES}:
+	@echo "\n\033[32m--[[ cloning Limine ]]--\033[0m"
+	git clone ${LIM_GIT} --depth=1 --branch v3.0-branch-binary ${LIM_DIR}
+	cd ${LIM_DIR}; make
+
+# create ISO
+${HYPERION}: ${KERNEL} ${LIM_CFG} ${LIM_FILES} Makefile
+	@echo "\n\033[32m--[[ creating Hyperion ISO ]]--\033[0m"
+	mkdir -p ${ISO_DIR}
+	cp ${LIM_CFG} ${LIM_FILES} ${ISO_DIR}
+	cp ${KERNEL} ${ISO_DIR}/hyperion
+
+	${XORRISO} ${XORRISO_FLAGS} ${ISO_DIR} -o ${HYPERION}
+
+	./${LIM_DIR}/limine-deploy ${HYPERION}
+
+# create testing ISO
+${HYPERION_TESTING}: ${KERNEL_TESTING} ${LIM_CFG} ${LIM_FILES} Makefile
+	@echo "\n\033[32m--[[ creating Hyperion-Testing ISO ]]--\033[0m"
+	mkdir -p ${ISO_TESTING_DIR}
+	cp ${LIM_CFG} ${LIM_FILES} ${ISO_TESTING_DIR}
+	cp ${KERNEL_TESTING} ${ISO_TESTING_DIR}/hyperion
+
+	${XORRISO} ${XORRISO_FLAGS} ${ISO_TESTING_DIR} -o ${HYPERION_TESTING}
+
+	./${LIM_DIR}/limine-deploy ${HYPERION_TESTING}
diff --git a/src/boot/limine/mem.rs b/src/boot/limine/mem.rs
index e8e41f5..40c8350 100644
--- a/src/boot/limine/mem.rs
+++ b/src/boot/limine/mem.rs
@@ -1,20 +1,16 @@
-use crate::mem::Memmap;
-use limine::{LimineMemmapRequest, LimineMemoryMapEntryType};
+use crate::{debug, mem::map::Memmap};
+use limine::{LimineMemmapEntry, LimineMemmapRequest, LimineMemoryMapEntryType, NonNullPtr};
+use spin::Lazy;
 
 //
 
 pub fn memmap() -> impl Iterator<Item = Memmap> {
-    static REQ: LimineMemmapRequest = LimineMemmapRequest::new(0);
-
     const DEFAULT_MEMMAP: Memmap = Memmap {
         base: u64::MAX,
         len: 0u64,
     };
 
-    REQ.get_response()
-        .get()
-        .into_iter()
-        .flat_map(|a| a.memmap())
+    memiter()
         .scan(DEFAULT_MEMMAP, |acc, memmap| {
             // TODO: zero init reclaimable regions
             if let LimineMemoryMapEntryType::Usable
@@ -34,3 +30,24 @@ pub fn memmap() -> impl Iterator<Item = Memmap> {
         })
         .flatten()
 }
+
+pub fn memtotal() -> u64 {
+    static TOTAL: Lazy<u64> = Lazy::new(|| {
+        memiter()
+            .filter(|memmap| {
+                memmap.typ != LimineMemoryMapEntryType::Reserved
+                    && memmap.typ != LimineMemoryMapEntryType::Framebuffer
+            })
+            .map(|memmap| memmap.len)
+            .sum::<u64>()
+    });
+    *TOTAL
+}
+
+fn memiter() -> impl Iterator<Item = &'static NonNullPtr<LimineMemmapEntry>> {
+    static REQ: LimineMemmapRequest = LimineMemmapRequest::new(0);
+    REQ.get_response()
+        .get()
+        .into_iter()
+        .flat_map(|a| a.memmap())
+}
diff --git a/src/boot/limine/mod.rs b/src/boot/limine/mod.rs
index 483e3e2..c5657a8 100644
--- a/src/boot/limine/mod.rs
+++ b/src/boot/limine/mod.rs
@@ -4,6 +4,7 @@ use crate::{arch, kernel_main};
 //
 
 pub use mem::memmap;
+pub use mem::memtotal;
 pub use term::_print;
 
 //
diff --git a/src/log.rs b/src/log.rs
index 2d7bfa9..3bf9fc5 100644
--- a/src/log.rs
+++ b/src/log.rs
@@ -95,19 +95,20 @@ pub fn _print_log(level: LogLevel, module: &str, args: Arguments) {
     //     print!("[{level:?}]: ")
     // } else {
     let level = match level {
-        LogLevel::None => " NONE  ",
-        LogLevel::Error => "\x1b[38;2;255;85;85m ERROR ",
-        LogLevel::Warn => "\x1b[38;2;255;255;85m WARN  ",
-        LogLevel::Info => "\x1b[38;2;85;255;85m INFO  ",
-        LogLevel::Debug => "\x1b[38;2;85;255;255m DEBUG ",
-        LogLevel::Trace => "\x1b[38;2;255;85;255m TRACE ",
-    };
+        LogLevel::None => " NONE  ".into(),
+        LogLevel::Error => " ERROR ".true_red(),
+        LogLevel::Warn => " WARN  ".true_yellow(),
+        LogLevel::Info => " INFO  ".true_green(),
+        LogLevel::Debug => " DEBUG ".true_cyan(),
+        LogLevel::Trace => " TRACE ".true_magenta(),
+    }
+    .with_reset(false);
 
     print!(
         "{}{level} {} {}: {args}",
         '['.true_grey(),
-        module.true_grey(),
-        ']'.true_grey(),
+        module.true_grey().with_reset(false),
+        ']'.reset_after(),
     )
     // }
 }
diff --git a/src/main.rs b/src/main.rs
index 7dbe95c..f478141 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,6 +8,7 @@
 #![feature(allocator_api)]
 #![feature(nonnull_slice_from_raw_parts)]
 #![feature(exclusive_range_pattern)]
+#![feature(let_chains)]
 #![test_runner(crate::testfw::test_runner)]
 #![reexport_test_harness_main = "test_main"]
 
diff --git a/src/mem.rs b/src/mem.rs
deleted file mode 100644
index af0077c..0000000
--- a/src/mem.rs
+++ /dev/null
@@ -1,90 +0,0 @@
-use crate::{boot, debug, error, util::NumberPostfix};
-use core::{
-    alloc::{GlobalAlloc, Layout},
-    ptr::null_mut,
-    sync::atomic::{AtomicU64, Ordering},
-};
-use spin::Mutex;
-
-//
-
-pub fn init() {
-    let mut usable = 0;
-
-    for Memmap { base, len } in boot::memmap() {
-        usable += len;
-        debug!("base: {base:#X} len: {len:#X} ({}B)", len.postfix_binary());
-
-        ALLOC.memory.store(base, Ordering::SeqCst);
-        *ALLOC.remaining.lock() = len;
-    }
-
-    debug!("Usable system memory: {}B", usable.postfix_binary());
-}
-
-//
-
-pub struct Memmap {
-    pub base: u64,
-    pub len: u64,
-}
-
-//
-
-#[global_allocator]
-static ALLOC: BumpAlloc = BumpAlloc {
-    memory: AtomicU64::new(0),
-    remaining: Mutex::new(0),
-};
-
-struct BumpAlloc {
-    memory: AtomicU64,
-    remaining: Mutex<u64>,
-}
-
-unsafe impl GlobalAlloc for BumpAlloc {
-    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
-        let memory = self.memory.load(Ordering::SeqCst);
-        let mut remaining = self.remaining.lock();
-
-        let top = memory + *remaining;
-        let Some(tmp) = top.checked_sub(layout.size() as u64) else {
-            error!("OUT OF MEMORY");
-            error!(
-                "ALLOC: size: {} align: {} top: {top} memory: {memory} remaining: {remaining}",
-                layout.size(),
-                layout.align()
-            );
-            return null_mut();
-        };
-        let new_top = tmp / layout.align() as u64 * layout.align() as u64;
-        let reservation = top - new_top;
-
-        if let Some(left) = remaining.checked_sub(reservation) {
-            *remaining = left;
-            (memory + left) as _
-        } else {
-            error!("OUT OF MEMORY");
-            error!(
-            "ALLOC: size: {} align: {} top: {top} new: {new_top} memory: {memory} remaining: {remaining}",
-            layout.size(),
-            layout.align()
-        );
-            null_mut()
-        }
-    }
-
-    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
-        // BUMP alloc is stupid and won't free the memory
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use alloc::vec::Vec;
-
-    #[test_case]
-    fn test_alloc() {
-        core::hint::black_box((0..64).map(|i| i * 2).collect::<Vec<_>>());
-    }
-}
diff --git a/src/mem/bump.rs b/src/mem/bump.rs
new file mode 100644
index 0000000..cd0b47c
--- /dev/null
+++ b/src/mem/bump.rs
@@ -0,0 +1,90 @@
+use super::map::Memmap;
+use crate::{boot, error};
+use core::{
+    alloc::{GlobalAlloc, Layout},
+    ptr::null_mut,
+};
+use spin::{Mutex, Once};
+
+//
+
+const MAX_BUMP_ALLOC: u64 = 2u64.pow(16); // 64KiB
+
+//
+
+pub fn init() {
+    let mut map = boot::memmap()
+        .min_by_key(|Memmap { len, .. }| *len)
+        .expect("No memory");
+
+    map.len = map.len.min(MAX_BUMP_ALLOC);
+
+    ALLOC.inner.call_once(|| BumpAllocInner {
+        remaining: Mutex::new(map.len),
+        map,
+    });
+}
+
+pub fn map() -> Option<Memmap> {
+    ALLOC.inner.get().map(|i| i.map)
+}
+
+//
+
+#[global_allocator]
+static ALLOC: BumpAllocator = BumpAllocator { inner: Once::new() };
+
+//
+
+struct BumpAllocator {
+    inner: Once<BumpAllocInner>,
+}
+
+struct BumpAllocInner {
+    map: Memmap,
+    remaining: Mutex<u64>,
+}
+
+//
+
+unsafe impl GlobalAlloc for BumpAllocator {
+    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
+        let Some(inner) = self.inner.get() else {
+            error!("Allocator used before init");
+            return null_mut();
+        };
+
+        let memory = inner.map.base;
+        let mut remaining = inner.remaining.lock();
+
+        let top = memory + *remaining;
+        let Some(tmp) = top.checked_sub(layout.size() as u64) else {
+            error!("OUT OF MEMORY");
+            error!(
+                "ALLOC: size: {} align: {} top: {top} memory: {memory} remaining: {remaining}",
+                layout.size(),
+                layout.align()
+                );
+            return null_mut();
+        };
+        let new_top = tmp / layout.align() as u64 * layout.align() as u64;
+        let reservation = top - new_top;
+
+        if let Some(left) = remaining.checked_sub(reservation) {
+            *remaining = left;
+            (memory + left) as _
+        } else {
+            error!("OUT OF MEMORY");
+            error!(
+                "ALLOC: size: {} align: {} top: {top} new: {new_top} memory: {memory} remaining: {remaining}",
+                layout.size(),
+                layout.align()
+            );
+            null_mut()
+        }
+    }
+
+    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
+        // BUMP alloc is stupid and won't free the memory
+    }
+}
diff --git a/src/mem/map.rs b/src/mem/map.rs
new file mode 100644
index 0000000..6681f94
--- /dev/null
+++ b/src/mem/map.rs
@@ -0,0 +1,17 @@
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Memmap {
+    pub base: u64,
+    pub len: u64,
+}
+
+//
+
+#[cfg(test)]
+mod tests {
+    use alloc::vec::Vec;
+
+    #[test_case]
+    fn test_alloc() {
+        core::hint::black_box((0..64).map(|i| i * 2).collect::<Vec<_>>());
+    }
+}
diff --git a/src/mem/mod.rs b/src/mem/mod.rs
new file mode 100644
index 0000000..08297bb
--- /dev/null
+++ b/src/mem/mod.rs
@@ -0,0 +1,21 @@
+use crate::{boot, debug, mem::map::Memmap, util::fmt::NumberPostfix};
+
+//
+
+pub mod map;
+
+// allocator
+pub mod bump;
+pub mod pfa;
+
+//
+
+pub fn init() {
+    let usable = boot::memmap().map(|Memmap { len, .. }| len).sum::<u64>();
+    let total = boot::memtotal();
+    debug!("Usable system memory: {}B", usable.postfix_binary());
+    debug!("Total system memory: {}B", total.postfix_binary());
+
+    bump::init();
+    pfa::init();
+}
diff --git a/src/mem/pfa.rs b/src/mem/pfa.rs
new file mode 100755
index 0000000..442644a
--- /dev/null
+++ b/src/mem/pfa.rs
@@ -0,0 +1,113 @@
+use super::map::Memmap;
+use crate::{
+    boot, debug,
+    log::{test_log_level, LogLevel},
+    mem::bump,
+    util::{bitmap::Bitmap, fmt::NumberPostfix},
+};
+use core::slice;
+use spin::{Mutex, Once};
+use x86_64::{align_down, align_up};
+
+//
+
+const PAGE_SIZE: u64 = 2u64.pow(12); // 4KiB pages
+
+// const PAGE_SIZE: u64 = 2u64.pow(21); // 2MiB pages
+
+static PFA: Once<Mutex<PageFrameAllocator>> = Once::new();
+
+//
+
+pub fn init() {
+    let mem_bottom = boot::memmap()
+        .map(|Memmap { base, .. }| base)
+        .min()
+        .expect("No memory");
+
+    let mem_top = boot::memmap()
+        .map(|Memmap { base, len }| base + len)
+        .max()
+        .expect("No memory");
+
+    // size in bytes
+    let bitmap_size = (mem_top - mem_bottom) / PAGE_SIZE / 8 + 1;
+    let bitmap_data = boot::memmap()
+        .find(|Memmap { len, .. }| *len >= bitmap_size)
+        .expect("No place to store PageFrameAllocator bitmap")
+        .base;
+
+    // SAFETY: this bitmap is going to be initialized before it is read from
+    let bitmap = unsafe { slice::from_raw_parts_mut(bitmap_data as _, bitmap_size as _) };
+    let mut bitmap = Bitmap::new(bitmap);
+    bitmap.fill(true); // initialized here
+
+    let bottom_page = align_up(mem_bottom, PAGE_SIZE) / PAGE_SIZE;
+
+    // free up some pages
+    for Memmap { mut base, mut len } in boot::memmap() {
+        if let Some(map) = bump::map() && map.base == base {
+            // skip the BumpAllocator spot
+            base += map.base;
+            len -= map.len;
+        }
+        if base == bitmap_data {
+            // skip the bitmap allocation spot
+            base += bitmap_data;
+            len -= bitmap_size;
+        }
+
+        let mut bottom = align_up(base, PAGE_SIZE);
+        let mut top = align_down(base + len, PAGE_SIZE);
+
+        if bottom >= top {
+            continue;
+        }
+
+        debug!(
+            "Free pages: {:#0X?} ({}B)",
+            bottom..top,
+            (top - bottom).postfix_binary()
+        );
+
+        bottom /= PAGE_SIZE;
+        top /= PAGE_SIZE;
+        bottom -= bottom_page;
+        top -= bottom_page;
+
+        for page in bottom..top {
+            #[cfg(debug_assertions)]
+            bitmap.set(page as _, false).unwrap();
+            #[cfg(not(debug_assertions))]
+            let _ = bitmap.set(page as _, false);
+        }
+    }
+
+    let free = bitmap.iter_false().count() as u64 * PAGE_SIZE;
+    let used = 0;
+    debug!("Free pages: ({}B)", free.postfix_binary());
+
+    PFA.call_once(|| {
+        Mutex::new(PageFrameAllocator {
+            bitmap,
+            free,
+            used,
+            bottom_page,
+        })
+    });
+}
+
+//
+
+pub struct PageFrameAllocator {
+    bitmap: Bitmap<'static>,
+    free: u64,
+    used: u64,
+    bottom_page: u64,
+}
+
+//
+
+impl PageFrameAllocator {
+    pub fn free_page(&mut self, addr: u64) {}
+}
diff --git a/src/smp.rs b/src/smp.rs
index aa926d2..4922dbd 100644
--- a/src/smp.rs
+++ b/src/smp.rs
@@ -1,4 +1,4 @@
-use crate::{arch, boot, debug};
+use crate::{boot, debug};
 use core::fmt::{self, Display, Formatter};
 
 //
diff --git a/src/term/escape/encode.rs b/src/term/escape/encode.rs
index 6eec9ea..a216d3a 100644
--- a/src/term/escape/encode.rs
+++ b/src/term/escape/encode.rs
@@ -2,51 +2,64 @@ use core::fmt;
 
 //
 
-pub trait EscapeEncoder {
-    fn with_escape_code<'a>(&'a self, code: &'a str) -> EncodedPart<'a, Self> {
-        EncodedPart { code, data: self }
+pub trait EscapeEncoder
+where
+    Self: Sized,
+{
+    fn with_escape_code<'a>(self, code: &'a str) -> EncodedPart<'a, Self> {
+        EncodedPart {
+            code,
+            data: self,
+            reset: true,
+        }
     }
 
-    fn true_red(&self) -> EncodedPart<Self> {
+    fn true_red(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;255;0;0m")
     }
 
-    fn true_green(&self) -> EncodedPart<Self> {
+    fn true_green(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;0;255;0m")
     }
 
-    fn true_blue(&self) -> EncodedPart<Self> {
+    fn true_blue(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;0;0;255m")
     }
 
-    fn true_cyan(&self) -> EncodedPart<Self> {
+    fn true_cyan(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;0;255;255m")
     }
 
-    fn true_magenta(&self) -> EncodedPart<Self> {
+    fn true_magenta(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;255;0;255m")
     }
 
-    fn true_yellow(&self) -> EncodedPart<Self> {
+    fn true_yellow(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;255;255;0m")
     }
 
-    fn true_black(&self) -> EncodedPart<Self> {
+    fn true_black(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;0;0;0m")
     }
 
-    fn true_white(&self) -> EncodedPart<Self> {
+    fn true_white(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;255;255;255m")
     }
 
-    fn true_grey(&self) -> EncodedPart<Self> {
+    fn true_grey(self) -> EncodedPart<'static, Self> {
         self.with_escape_code("\x1B[38;2;128;128;128m")
     }
+
+    fn reset_after(self) -> EncodedPart<'static, Self> {
+        self.with_escape_code("")
+    }
 }
 
-pub struct EncodedPart<'a, T: ?Sized> {
+#[derive(Clone, Copy)]
+pub struct EncodedPart<'a, T: Sized> {
     code: &'a str,
-    data: &'a T,
+    reset: bool,
+    data: T,
 }
 
 //
@@ -57,22 +70,57 @@ pub struct EncodedPart<'a, T: ?Sized> {
 
 impl<T> EscapeEncoder for T {}
 
-impl<'a, T> fmt::Display for EncodedPart<'a, T>
+impl<T> EncodedPart<'_, T> {
+    pub fn with_reset(mut self, reset: bool) -> Self {
+        self.reset = reset;
+        self
+    }
+
+    fn write_end(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if self.reset {
+            write!(f, "\x1B[m")?;
+        }
+        Ok(())
+    }
+}
+
+impl Default for EncodedPart<'static, ()> {
+    fn default() -> Self {
+        Self {
+            code: "",
+            data: (),
+            reset: true,
+        }
+    }
+}
+
+impl<T> From<T> for EncodedPart<'_, T> {
+    fn from(value: T) -> Self {
+        Self {
+            code: "",
+            data: value,
+            reset: false,
+        }
+    }
+}
+
+impl<T> fmt::Display for EncodedPart<'_, T>
 where
     T: fmt::Display,
 {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}{}\x1B[m", self.code, self.data)
+        write!(f, "{}{}", self.code, self.data)?;
+        self.write_end(f)
     }
 }
 
-impl<'a, T> fmt::Debug for EncodedPart<'a, T>
+impl<T> fmt::Debug for EncodedPart<'_, T>
 where
     T: fmt::Debug,
 {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(f, "{}", self.code)?;
         self.data.fmt(f)?;
-        write!(f, "\x1B[m")
+        self.write_end(f)
     }
 }
diff --git a/src/util/bitmap.rs b/src/util/bitmap.rs
new file mode 100644
index 0000000..1ff797d
--- /dev/null
+++ b/src/util/bitmap.rs
@@ -0,0 +1,142 @@
+#[derive(Debug, Default)]
+pub struct Bitmap<'a> {
+    data: &'a mut [u8],
+}
+
+//
+
+impl<'a> Bitmap<'a> {
+    pub fn new(data: &'a mut [u8]) -> Self {
+        Self { data }
+    }
+
+    #[must_use]
+    pub fn len(&self) -> usize {
+        self.data.len() * 8
+    }
+
+    #[must_use]
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    // pub fn resize(&mut self, bits: usize) {
+    //     let bytes = bits / 8 + 1;
+    //     self.data.resize(bytes, 0);
+    // }
+
+    pub fn fill(&mut self, val: bool) {
+        self.data.fill(if val { 0xFF } else { 0x0 })
+    }
+
+    #[must_use]
+    pub fn get(&self, n: usize) -> Option<bool> {
+        let (byte, _, mask) = self.bp(n);
+        let byte = *self.data.get(byte)?;
+
+        Some(byte & mask != 0)
+    }
+
+    #[must_use]
+    pub fn set(&mut self, n: usize, val: bool) -> Option<()> {
+        let (byte, bit, mask) = self.bp(n);
+        let byte = self.data.get_mut(byte)?;
+
+        // reset the bit
+        *byte &= !mask;
+
+        // set the bit
+        *byte |= (val as u8) << bit;
+
+        Some(())
+    }
+
+    /// iterator over indexes of 1 bits
+    pub fn iter_true(&self) -> impl Iterator<Item = usize> + '_ {
+        self.iter_bytes()
+            .enumerate()
+            .filter(|(_, byte)| *byte != 0)
+            .flat_map(|(i, byte)| {
+                (0..8)
+                    .enumerate()
+                    .filter(move |(_, bit)| byte & (1 << *bit) != 0)
+                    .map(move |(j, _)| i * 8 + j)
+            })
+    }
+
+    /// iterator over indexes of 0 bits
+    pub fn iter_false(&self) -> impl Iterator<Item = usize> + '_ {
+        self.iter_bytes()
+            .enumerate()
+            .filter(|(_, byte)| *byte != 0xFF)
+            .flat_map(|(i, byte)| {
+                (0..8)
+                    .enumerate()
+                    .filter(move |(_, bit)| byte & (1 << *bit) == 0)
+                    .map(move |(j, _)| i * 8 + j)
+            })
+    }
+
+    pub fn iter(&self) -> impl Iterator<Item = bool> + '_ {
+        self.iter_bytes()
+            .flat_map(|byte| (0..8).map(move |i| byte & (1 << i) != 0))
+    }
+
+    pub fn iter_bytes(&self) -> impl Iterator<Item = u8> + '_ {
+        self.data.iter().copied()
+    }
+
+    #[must_use]
+    fn bp(&self, n: usize) -> (usize, usize, u8) {
+        let bit = n % 8;
+        (n / 8, bit, 1 << bit)
+    }
+}
+
+//
+
+#[cfg(test)]
+mod tests {
+    use super::Bitmap;
+
+    #[test_case]
+    fn test_bitmap_iter_true() {
+        let mut bitmap = [0; 10];
+        let mut bitmap = Bitmap::new(&mut bitmap);
+
+        assert_eq!(bitmap.set(5, true), Some(()));
+        assert_eq!(bitmap.set(7, true), Some(()));
+        assert_eq!(bitmap.set(9, true), Some(()));
+        assert_eq!(bitmap.set(53, true), Some(()));
+        assert_eq!(bitmap.set(79, true), Some(()));
+        assert_eq!(bitmap.set(89, true), None);
+
+        let mut iter = bitmap.iter_true();
+        assert_eq!(iter.next(), Some(5));
+        assert_eq!(iter.next(), Some(7));
+        assert_eq!(iter.next(), Some(9));
+        assert_eq!(iter.next(), Some(53));
+        assert_eq!(iter.next(), Some(79));
+        assert_eq!(iter.next(), None);
+    }
+
+    #[test_case]
+    fn test_bitmap_iter_false() {
+        let mut bitmap = [0xFF; 10];
+        let mut bitmap = Bitmap::new(&mut bitmap);
+        assert_eq!(bitmap.set(5, false), Some(()));
+        assert_eq!(bitmap.set(7, false), Some(()));
+        assert_eq!(bitmap.set(9, false), Some(()));
+        assert_eq!(bitmap.set(53, false), Some(()));
+        assert_eq!(bitmap.set(79, false), Some(()));
+        assert_eq!(bitmap.set(89, false), None);
+
+        let mut iter = bitmap.iter_false();
+        assert_eq!(iter.next(), Some(5));
+        assert_eq!(iter.next(), Some(7));
+        assert_eq!(iter.next(), Some(9));
+        assert_eq!(iter.next(), Some(53));
+        assert_eq!(iter.next(), Some(79));
+        assert_eq!(iter.next(), None);
+    }
+}
diff --git a/src/util.rs b/src/util/fmt.rs
similarity index 100%
rename from src/util.rs
rename to src/util/fmt.rs
diff --git a/src/util/mod.rs b/src/util/mod.rs
new file mode 100644
index 0000000..3ca20ab
--- /dev/null
+++ b/src/util/mod.rs
@@ -0,0 +1,2 @@
+pub mod bitmap;
+pub mod fmt;
-- 
GitLab