commit 6f1c6f131680ccaa7c0bf3ec8f6a0aab77d3d2fd Author: Daniel Hammer Date: Thu Feb 27 19:48:03 2025 +0400 GDT, page frame allocator diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6967fd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/kernel-deps +/limine +/ovmf +*.iso +*.hdd diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..896c677 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,67 @@ +{ + "files.associations": { + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdlib": "cpp", + "exception": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "limits": "cpp", + "memory": "cpp", + "new": "cpp", + "numbers": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ratio": "cpp", + "span": "cpp", + "string_view": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeinfo": "cpp", + "utility": "cpp", + "variant": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdio": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "algorithm": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "deque": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "iterator": "cpp", + "memory_resource": "cpp", + "random": "cpp", + "system_error": "cpp", + "format": "cpp", + "fstream": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "mutex": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stdfloat": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "cinttypes": "cpp" + } +} \ No newline at end of file diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..9751975 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,249 @@ +# Nuke built-in rules and variables. +MAKEFLAGS += -rR +.SUFFIXES: + +# Target architecture to build for. Default to x86_64. +ARCH := x86_64 + +# Default user QEMU flags. These are appended to the QEMU command calls. +QEMUFLAGS := -m 2G + +override IMAGE_NAME := template-$(ARCH) + +# Toolchain for building the 'limine' executable for the host. +HOST_CC := cc +HOST_CFLAGS := -g -O2 -pipe +HOST_CPPFLAGS := +HOST_LDFLAGS := +HOST_LIBS := + +.PHONY: all +all: $(IMAGE_NAME).iso + +.PHONY: all-hdd +all-hdd: $(IMAGE_NAME).hdd + +.PHONY: run +run: run-$(ARCH) + +.PHONY: run-hdd +run-hdd: run-hdd-$(ARCH) + +.PHONY: run-x86_64 +run-x86_64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso + qemu-system-$(ARCH) \ + -M q35 \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-x86_64 +run-hdd-x86_64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(ARCH) \ + -M q35 \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-aarch64 +run-aarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso + qemu-system-$(ARCH) \ + -M virt \ + -cpu cortex-a72 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-aarch64 +run-hdd-aarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(ARCH) \ + -M virt \ + -cpu cortex-a72 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-riscv64 +run-riscv64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso + qemu-system-$(ARCH) \ + -M virt \ + -cpu rv64 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-riscv64 +run-hdd-riscv64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(ARCH) \ + -M virt \ + -cpu rv64 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-loongarch64 +run-loongarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso + qemu-system-$(ARCH) \ + -M virt \ + -cpu la464 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-loongarch64 +run-hdd-loongarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(ARCH) \ + -M virt \ + -cpu la464 \ + -device ramfb \ + -device qemu-xhci \ + -device usb-kbd \ + -device usb-mouse \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + + +.PHONY: run-bios +run-bios: $(IMAGE_NAME).iso + qemu-system-$(ARCH) \ + -M q35 \ + -cdrom $(IMAGE_NAME).iso \ + -boot d \ + $(QEMUFLAGS) + +.PHONY: run-hdd-bios +run-hdd-bios: $(IMAGE_NAME).hdd + qemu-system-$(ARCH) \ + -M q35 \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +ovmf/ovmf-code-$(ARCH).fd: + mkdir -p ovmf + curl -Lo $@ https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-code-$(ARCH).fd + case "$(ARCH)" in \ + aarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=67108864 2>/dev/null;; \ + riscv64) dd if=/dev/zero of=$@ bs=1 count=0 seek=33554432 2>/dev/null;; \ + esac + +limine/limine: + rm -rf limine + git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 + $(MAKE) -C limine \ + CC="$(HOST_CC)" \ + CFLAGS="$(HOST_CFLAGS)" \ + CPPFLAGS="$(HOST_CPPFLAGS)" \ + LDFLAGS="$(HOST_LDFLAGS)" \ + LIBS="$(HOST_LIBS)" + +kernel-deps: + ./kernel/get-deps + touch kernel-deps + +.PHONY: kernel +kernel: kernel-deps + $(MAKE) -C kernel + +$(IMAGE_NAME).iso: limine/limine kernel + rm -rf iso_root + mkdir -p iso_root/boot + cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/ + mkdir -p iso_root/boot/limine + cp -v limine.conf iso_root/boot/limine/ + mkdir -p iso_root/EFI/BOOT +ifeq ($(ARCH),x86_64) + cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTX64.EFI iso_root/EFI/BOOT/ + cp -v limine/BOOTIA32.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ + -apm-block-size 2048 --efi-boot boot/limine/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + iso_root -o $(IMAGE_NAME).iso + ./limine/limine bios-install $(IMAGE_NAME).iso +endif +ifeq ($(ARCH),aarch64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTAA64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs -R -r -J \ + -hfsplus -apm-block-size 2048 \ + --efi-boot boot/limine/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + iso_root -o $(IMAGE_NAME).iso +endif +ifeq ($(ARCH),riscv64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTRISCV64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs -R -r -J \ + -hfsplus -apm-block-size 2048 \ + --efi-boot boot/limine/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + iso_root -o $(IMAGE_NAME).iso +endif +ifeq ($(ARCH),loongarch64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTLOONGARCH64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs -R -r -J \ + -hfsplus -apm-block-size 2048 \ + --efi-boot boot/limine/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + iso_root -o $(IMAGE_NAME).iso +endif + rm -rf iso_root + +$(IMAGE_NAME).hdd: limine/limine kernel + rm -f $(IMAGE_NAME).hdd + dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd + PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 +ifeq ($(ARCH),x86_64) + ./limine/limine bios-install $(IMAGE_NAME).hdd +endif + mformat -i $(IMAGE_NAME).hdd@@1M + mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine + mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot + mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine +ifeq ($(ARCH),x86_64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTX64.EFI ::/EFI/BOOT + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTIA32.EFI ::/EFI/BOOT +endif +ifeq ($(ARCH),aarch64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTAA64.EFI ::/EFI/BOOT +endif +ifeq ($(ARCH),riscv64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTRISCV64.EFI ::/EFI/BOOT +endif +ifeq ($(ARCH),loongarch64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTLOONGARCH64.EFI ::/EFI/BOOT +endif + +.PHONY: clean +clean: + $(MAKE) -C kernel clean + rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd + +.PHONY: distclean +distclean: + $(MAKE) -C kernel distclean + rm -rf iso_root *.iso *.hdd kernel-deps limine ovmf diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..295294b --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2023-2025 mintsuki and contributors. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c295075 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# OS220 + + +# Roadmap +[x] GDT +[ ] IDT/ISRs/Interrupts +[ ] Page frame allocator +[ ] Paging +[ ] Heap allocator +... \ No newline at end of file diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..4165cf5 --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,6 @@ +/freestnd-c-hdrs +/freestnd-cxx-hdrs +/cc-runtime* +/src/limine.h +/bin-* +/obj-* diff --git a/kernel/GNUmakefile b/kernel/GNUmakefile new file mode 100644 index 0000000..ca797d5 --- /dev/null +++ b/kernel/GNUmakefile @@ -0,0 +1,277 @@ +# Nuke built-in rules and variables. +MAKEFLAGS += -rR +.SUFFIXES: + +# This is the name that our final executable will have. +# Change as needed. +override OUTPUT := kernel + +# Target architecture to build for. Default to x86_64. +ARCH := x86_64 + +# Install prefix; /usr/local is a good, standard default pick. +PREFIX := /usr/local + +# Check if the architecture is supported. +ifeq ($(filter $(ARCH),aarch64 loongarch64 riscv64 x86_64),) + $(error Architecture $(ARCH) not supported) +endif + +# User controllable C compiler command. +CC := cc + +# User controllable C++ compiler command. +CXX := c++ + +# User controllable archiver command. +AR := ar + +# User controllable C flags. +CFLAGS := -g -O2 -pipe + +# User controllable C++ flags. We default to same as C flags. +CXXFLAGS := $(CFLAGS) + +# User controllable C/C++ preprocessor flags. We set none by default. +CPPFLAGS := + +ifeq ($(ARCH),x86_64) + # User controllable nasm flags. + NASMFLAGS := -F dwarf -g +endif + +# User controllable linker flags. We set none by default. +LDFLAGS := + +# Ensure the dependencies have been obtained. +ifneq ($(shell ( test '$(MAKECMDGOALS)' = clean || test '$(MAKECMDGOALS)' = distclean ); echo $$?),0) + ifeq ($(shell ( ! test -d freestnd-c-hdrs || ! test -d freestnd-cxx-hdrs || ! test -d cc-runtime || ! test -f src/limine.h ); echo $$?),0) + $(error Please run the ./get-deps script first) + endif +endif + +# Check if CC is Clang. +override CC_IS_CLANG := $(shell ! $(CC) --version 2>/dev/null | grep 'clang' >/dev/null 2>&1; echo $$?) + +# Check if CXX is Clang. +override CXX_IS_CLANG := $(shell ! $(CXX) --version 2>/dev/null | grep 'clang' >/dev/null 2>&1; echo $$?) + +# Internal flags shared by both C and C++ compilers. +override SHARED_FLAGS := \ + -Wall \ + -Wextra \ + -nostdinc \ + -ffreestanding \ + -fno-stack-protector \ + -fno-stack-check \ + -fno-PIC \ + -ffunction-sections \ + -fdata-sections + +# Internal C/C++ preprocessor flags that should not be changed by the user. +override CPPFLAGS_C := \ + -I src \ + -isystem freestnd-c-hdrs/$(ARCH)/include \ + $(CPPFLAGS) \ + -DLIMINE_API_REVISION=3 \ + -MMD \ + -MP + +# Internal C++ only preprocessor flags. +override CPPFLAGS_CXX := \ + -I src \ + -isystem freestnd-c-hdrs/$(ARCH)/include \ + -isystem freestnd-cxx-hdrs/$(ARCH)/include \ + $(CPPFLAGS) \ + -DLIMINE_API_REVISION=3 \ + -MMD \ + -MP + +ifeq ($(ARCH),x86_64) + # Internal nasm flags that should not be changed by the user. + override NASMFLAGS += \ + -Wall +endif + +# Architecture specific internal flags. +ifeq ($(ARCH),x86_64) + ifeq ($(CC_IS_CLANG),1) + override CC += \ + -target x86_64-unknown-none + endif + ifeq ($(CXX_IS_CLANG),1) + override CXX += \ + -target x86_64-unknown-none + endif + override SHARED_FLAGS += \ + -m64 \ + -march=x86-64 \ + -mno-80387 \ + -mno-mmx \ + -mno-sse \ + -mno-sse2 \ + -mno-red-zone \ + -mcmodel=kernel + override LDFLAGS += \ + -Wl,-m,elf_x86_64 + override NASMFLAGS += \ + -f elf64 +endif +ifeq ($(ARCH),aarch64) + ifeq ($(CC_IS_CLANG),1) + override CC += \ + -target aarch64-unknown-none + endif + ifeq ($(CXX_IS_CLANG),1) + override CXX += \ + -target aarch64-unknown-none + endif + override SHARED_FLAGS += \ + -mgeneral-regs-only + override LDFLAGS += \ + -Wl,-m,aarch64elf +endif +ifeq ($(ARCH),riscv64) + ifeq ($(CC_IS_CLANG),1) + override CC += \ + -target riscv64-unknown-none + override CFLAGS += \ + -march=rv64imac + else + override CFLAGS += \ + -march=rv64imac_zicsr_zifencei + endif + ifeq ($(CXX_IS_CLANG),1) + override CXX += \ + -target riscv64-unknown-none + override CXXFLAGS += \ + -march=rv64imac + else + override CXXFLAGS += \ + -march=rv64imac_zicsr_zifencei + endif + override SHARED_FLAGS += \ + -mabi=lp64 \ + -mno-relax + override LDFLAGS += \ + -Wl,-m,elf64lriscv \ + -Wl,--no-relax +endif +ifeq ($(ARCH),loongarch64) + ifeq ($(CC_IS_CLANG),1) + override CC += \ + -target loongarch64-unknown-none + endif + ifeq ($(CXX_IS_CLANG),1) + override CXX += \ + -target loongarch64-unknown-none + endif + override SHARED_FLAGS += \ + -march=loongarch64 \ + -mabi=lp64s + override LDFLAGS += \ + -Wl,-m,elf64loongarch \ + -Wl,--no-relax +endif + +# Internal C flags that should not be changed by the user. +override CFLAGS += \ + -std=gnu11 \ + $(SHARED_FLAGS) + +# Internal C++ flags that should not be changed by the user. +override CXXFLAGS += \ + -std=gnu++20 \ + -fno-rtti \ + -fno-exceptions \ + $(SHARED_FLAGS) + +# Internal linker flags that should not be changed by the user. +override LDFLAGS += \ + -Wl,--build-id=none \ + -nostdlib \ + -static \ + -z max-page-size=0x1000 \ + -Wl,--gc-sections \ + -T linker-$(ARCH).ld + +# Use "find" to glob all *.c, *.cpp, *.S, and *.asm files in the tree and obtain the +# object and header dependency file names. +override SRCFILES := $(shell cd src && find -L * -type f | LC_ALL=C sort) +override CFILES := $(filter %.c,$(SRCFILES)) +override CXXFILES := $(filter %.cpp,$(SRCFILES)) +override ASFILES := $(filter %.S,$(SRCFILES)) +ifeq ($(ARCH),x86_64) +override NASMFILES := $(filter %.asm,$(SRCFILES)) +endif +override OBJ := $(addprefix obj-$(ARCH)/,$(CFILES:.c=.c.o) $(CXXFILES:.cpp=.cpp.o) $(ASFILES:.S=.S.o)) +ifeq ($(ARCH),x86_64) +override OBJ += $(addprefix obj-$(ARCH)/,$(NASMFILES:.asm=.asm.o)) +endif +override HEADER_DEPS := $(addprefix obj-$(ARCH)/,$(CFILES:.c=.c.d) $(CXXFILES:.cpp=.cpp.d) $(ASFILES:.S=.S.d)) + +# Default target. This must come first, before header dependencies. +.PHONY: all +all: bin-$(ARCH)/$(OUTPUT) + +# Include header dependencies. +-include $(HEADER_DEPS) + +# Link rules for building the C compiler runtime. +cc-runtime-$(ARCH)/cc-runtime.a: GNUmakefile cc-runtime/* + rm -rf cc-runtime-$(ARCH) + cp -r cc-runtime cc-runtime-$(ARCH) + $(MAKE) -C cc-runtime-$(ARCH) -f cc-runtime.mk \ + CC="$(CC)" \ + AR="$(AR)" \ + CFLAGS="$(CFLAGS)" \ + CPPFLAGS='-isystem ../freestnd-c-hdrs/$(ARCH)/include -DCC_RUNTIME_NO_FLOAT' + +# Link rules for the final executable. +bin-$(ARCH)/$(OUTPUT): GNUmakefile linker-$(ARCH).ld $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a + mkdir -p "$$(dirname $@)" + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) cc-runtime-$(ARCH)/cc-runtime.a -o $@ + +# Compilation rules for *.c files. +obj-$(ARCH)/%.c.o: src/%.c GNUmakefile + mkdir -p "$$(dirname $@)" + $(CC) $(CFLAGS) $(CPPFLAGS_C) -c $< -o $@ + +# Compilation rules for *.cpp files. +obj-$(ARCH)/%.cpp.o: src/%.cpp GNUmakefile + mkdir -p "$$(dirname $@)" + $(CXX) $(CXXFLAGS) $(CPPFLAGS_CXX) -c $< -o $@ + +# Compilation rules for *.S files. +obj-$(ARCH)/%.S.o: src/%.S GNUmakefile + mkdir -p "$$(dirname $@)" + $(CC) $(CFLAGS) $(CPPFLAGS_C) -c $< -o $@ + +ifeq ($(ARCH),x86_64) +# Compilation rules for *.asm (nasm) files. +obj-$(ARCH)/%.asm.o: src/%.asm GNUmakefile + mkdir -p "$$(dirname $@)" + nasm $(NASMFLAGS) $< -o $@ +endif + +# Remove object files and the final executable. +.PHONY: clean +clean: + rm -rf bin-$(ARCH) obj-$(ARCH) cc-runtime-$(ARCH) + +# Remove everything built and generated including downloaded dependencies. +.PHONY: distclean +distclean: + rm -rf bin-* obj-* freestnd-c-hdrs freestnd-cxx-hdrs cc-runtime* src/limine.h + +# Install the final built executable to its final on-root location. +.PHONY: install +install: all + install -d "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)" + install -m 644 bin-$(ARCH)/$(OUTPUT) "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)/$(OUTPUT)-$(ARCH)" + +# Try to undo whatever the "install" target did. +.PHONY: uninstall +uninstall: + rm -f "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)/$(OUTPUT)-$(ARCH)" + -rmdir "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)" diff --git a/kernel/get-deps b/kernel/get-deps new file mode 100755 index 0000000..1dc36ce --- /dev/null +++ b/kernel/get-deps @@ -0,0 +1,85 @@ +#! /bin/sh + +set -ex + +srcdir="$(dirname "$0")" +test -z "$srcdir" && srcdir=. + +cd "$srcdir" + +clone_repo_commit() { + if test -d "$2/.git"; then + git -C "$2" reset --hard + git -C "$2" clean -fd + if ! git -C "$2" checkout $3; then + rm -rf "$2" + fi + else + if test -d "$2"; then + set +x + echo "error: '$2' is not a Git repository" + exit 1 + fi + fi + if ! test -d "$2"; then + git clone $1 "$2" + if ! git -C "$2" checkout $3; then + rm -rf "$2" + exit 1 + fi + fi +} + +download_by_hash() { + DOWNLOAD_COMMAND="curl -Lo" + if ! command -v $DOWNLOAD_COMMAND >/dev/null 2>&1; then + DOWNLOAD_COMMAND="wget -O" + if ! command -v $DOWNLOAD_COMMAND >/dev/null 2>&1; then + set +x + echo "error: Neither curl nor wget found" + exit 1 + fi + fi + SHA256_COMMAND="sha256sum" + if ! command -v $SHA256_COMMAND >/dev/null 2>&1; then + SHA256_COMMAND="sha256" + if ! command -v $SHA256_COMMAND >/dev/null 2>&1; then + set +x + echo "error: Cannot find sha256(sum) command" + exit 1 + fi + fi + if ! test -f "$2" || ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then + rm -f "$2" + mkdir -p "$2" && rm -rf "$2" + $DOWNLOAD_COMMAND "$2" $1 + if ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then + set +x + echo "error: Cannot download file '$2' by hash" + echo "incorrect hash:" + $SHA256_COMMAND "$2" + rm -f "$2" + exit 1 + fi + fi +} + +clone_repo_commit \ + https://github.com/osdev0/freestnd-c-hdrs.git \ + freestnd-c-hdrs \ + 21b59ecd6ef67bb32f893da8288ce08a324d1986 + +clone_repo_commit \ + https://github.com/osdev0/freestnd-cxx-hdrs.git \ + freestnd-cxx-hdrs \ + d604a4c5033a7a4d610ba41daab3c541be96b43e + +clone_repo_commit \ + https://github.com/osdev0/cc-runtime.git \ + cc-runtime \ + 576a01179f3298a4795b92f42c088f9f8800b56b + +download_by_hash \ + https://github.com/limine-bootloader/limine/raw/4687a182be23939c2d9f15db970382dc353ed956/limine.h \ + src/limine.h \ + 6879e626f34c1be25ac2f72bf43b083fc2b53887280bb0fcdaee790e258c6974 diff --git a/kernel/linker-aarch64.ld b/kernel/linker-aarch64.ld new file mode 100644 index 0000000..00ad9ec --- /dev/null +++ b/kernel/linker-aarch64.ld @@ -0,0 +1,77 @@ +/* Tell the linker that we want an aarch64 ELF64 output file */ +OUTPUT_FORMAT(elf64-littleaarch64) + +/* We want the symbol kmain to be our entry point */ +ENTRY(kmain) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions; this also allows us to exert more control over the linking */ +/* process. */ +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ + /* and because that is what the Limine spec mandates. */ + /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ + /* that is the beginning of the region. */ + . = 0xffffffff80000000; + + /* Define a section to contain the Limine requests and assign it to its own PHDR */ + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + /* Move to the next memory page for .text */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* C++ is a language that allows for global constructors. In order to obtain the */ + /* address of the ".init_array" section we need to define a symbol for it. */ + .init_array : { + __init_array = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array .ctors)) + __init_array_end = .; + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + } :data + + /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ + /* unnecessary zeros will be written to the binary. */ + /* If you need, for example, .init_array and .fini_array, those should be placed */ + /* above this. */ + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/kernel/linker-loongarch64.ld b/kernel/linker-loongarch64.ld new file mode 100644 index 0000000..a437cb7 --- /dev/null +++ b/kernel/linker-loongarch64.ld @@ -0,0 +1,77 @@ +/* Tell the linker that we want a loongarch64 ELF64 output file */ +OUTPUT_FORMAT(elf64-loongarch) + +/* We want the symbol kmain to be our entry point */ +ENTRY(kmain) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions; this also allows us to exert more control over the linking */ +/* process. */ +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ + /* and because that is what the Limine spec mandates. */ + /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ + /* that is the beginning of the region. */ + . = 0xffffffff80000000; + + /* Define a section to contain the Limine requests and assign it to its own PHDR */ + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + /* Move to the next memory page for .text */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* C++ is a language that allows for global constructors. In order to obtain the */ + /* address of the ".init_array" section we need to define a symbol for it. */ + .init_array : { + __init_array = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array .ctors)) + __init_array_end = .; + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + } :data + + /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ + /* unnecessary zeros will be written to the binary. */ + /* If you need, for example, .init_array and .fini_array, those should be placed */ + /* above this. */ + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/kernel/linker-riscv64.ld b/kernel/linker-riscv64.ld new file mode 100644 index 0000000..489ef29 --- /dev/null +++ b/kernel/linker-riscv64.ld @@ -0,0 +1,79 @@ +/* Tell the linker that we want a riscv64 ELF64 output file */ +OUTPUT_FORMAT(elf64-littleriscv) + +/* We want the symbol kmain to be our entry point */ +ENTRY(kmain) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions; this also allows us to exert more control over the linking */ +/* process. */ +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ + /* and because that is what the Limine spec mandates. */ + /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ + /* that is the beginning of the region. */ + . = 0xffffffff80000000; + + /* Define a section to contain the Limine requests and assign it to its own PHDR */ + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + /* Move to the next memory page for .text */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* C++ is a language that allows for global constructors. In order to obtain the */ + /* address of the ".init_array" section we need to define a symbol for it. */ + .init_array : { + __init_array = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array .ctors)) + __init_array_end = .; + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + *(.sdata .sdata.*) + } :data + + /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ + /* unnecessary zeros will be written to the binary. */ + /* If you need, for example, .init_array and .fini_array, those should be placed */ + /* above this. */ + .bss : { + *(.sbss .sbss.*) + *(.bss .bss.*) + *(COMMON) + } :data + + /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/kernel/linker-x86_64.ld b/kernel/linker-x86_64.ld new file mode 100644 index 0000000..ec5284a --- /dev/null +++ b/kernel/linker-x86_64.ld @@ -0,0 +1,77 @@ +/* Tell the linker that we want an x86_64 ELF64 output file */ +OUTPUT_FORMAT(elf64-x86-64) + +/* We want the symbol kmain to be our entry point */ +ENTRY(kmain) + +/* Define the program headers we want so the bootloader gives us the right */ +/* MMU permissions; this also allows us to exert more control over the linking */ +/* process. */ +PHDRS +{ + limine_requests PT_LOAD; + text PT_LOAD; + rodata PT_LOAD; + data PT_LOAD; +} + +SECTIONS +{ + /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ + /* and because that is what the Limine spec mandates. */ + /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ + /* that is the beginning of the region. */ + . = 0xffffffff80000000; + + /* Define a section to contain the Limine requests and assign it to its own PHDR */ + .limine_requests : { + KEEP(*(.limine_requests_start)) + KEEP(*(.limine_requests)) + KEEP(*(.limine_requests_end)) + } :limine_requests + + /* Move to the next memory page for .text */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* C++ is a language that allows for global constructors. In order to obtain the */ + /* address of the ".init_array" section we need to define a symbol for it. */ + .init_array : { + __init_array = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP(*(.init_array .ctors)) + __init_array_end = .; + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + } :data + + /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ + /* unnecessary zeros will be written to the binary. */ + /* If you need, for example, .init_array and .fini_array, those should be placed */ + /* above this. */ + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + } +} diff --git a/kernel/src/Common/Panic.cpp b/kernel/src/Common/Panic.cpp new file mode 100644 index 0000000..42d0f0d --- /dev/null +++ b/kernel/src/Common/Panic.cpp @@ -0,0 +1,18 @@ +#include "Panic.hpp" + +void Panic(const char *meditationString, System::Registers registers) { + kerr << "=========== Kernel panic ===========" << Kt::newline; + kerr << meditationString << Kt::newline; + + + while (true) { + #if defined (__x86_64__) + asm ("cli"); + asm ("hlt"); + #elif defined (__aarch64__) || defined (__riscv) + asm ("wfi"); + #elif defined (__loongarch64) + asm ("idle 0"); + #endif + } +} \ No newline at end of file diff --git a/kernel/src/Common/Panic.hpp b/kernel/src/Common/Panic.hpp new file mode 100644 index 0000000..febae56 --- /dev/null +++ b/kernel/src/Common/Panic.hpp @@ -0,0 +1,5 @@ +#pragma once +#include +#include + +void Panic(const char *meditationString, System::Registers registers); \ No newline at end of file diff --git a/kernel/src/Efi/UEFI.hpp b/kernel/src/Efi/UEFI.hpp new file mode 100644 index 0000000..87970cb --- /dev/null +++ b/kernel/src/Efi/UEFI.hpp @@ -0,0 +1,42 @@ +/* + * UEFI.hpp + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +namespace Efi { + typedef void* EFI_HANDLE; + + struct TableHeader { + std::uint64_t Signature; + std::uint32_t Revision; + std::uint32_t HeaderSize; + std::uint32_t CRC32; + std::uint32_t Reserved; + }__attribute__((packed)); + + struct SystemTable { + TableHeader Header; + void* FirmwareVendor; // Pointer to a CHAR16 string of the fw vendor name string + std::uint32_t FirmwareRevision; + + EFI_HANDLE ConsoleInHandle; + void* ConIn; + EFI_HANDLE ConsoleOutHandle; + void* ConOut; + + EFI_HANDLE StandardErrorHandle; + void* StdErr; + + // Jackpot + void *RuntimeServices; + + void *BootServices; + + std::uint64_t NumberOfTableEntries; + + void *ConfigurationTable; + }; +}; \ No newline at end of file diff --git a/kernel/src/Hal/GDT.asm b/kernel/src/Hal/GDT.asm new file mode 100644 index 0000000..777ba62 --- /dev/null +++ b/kernel/src/Hal/GDT.asm @@ -0,0 +1,31 @@ +; +; gdt.asm +; Copyright (c) 2025 Daniel Hammer +; + +[bits 64] +section .text ; Text/code section + +global ReloadSegments +global LoadGDT + +LoadGDT: + lgdt [rdi] ; Run LGDT on the contents of 1st C parameter + ret + +ReloadSegments: + push 0x08 ; CS descriptor + lea rax, [rel .reload_CS] + push rax + retfq +.reload_CS: + mov ax, 0x10 ; DS descriptor + + ; ds, es, fs, gs, ss are segment registers on x86_64 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ret \ No newline at end of file diff --git a/kernel/src/Hal/GDT.cpp b/kernel/src/Hal/GDT.cpp new file mode 100644 index 0000000..3ee2986 --- /dev/null +++ b/kernel/src/Hal/GDT.cpp @@ -0,0 +1,70 @@ +/* + * gdt.hpp +*/ + +#include "GDT.hpp" +#include "../KernelTerminal/terminal.hpp" + +// Limine loads a GDT of course, (CS = 0x28) but we will need to make a TSS someday... therefore we load our own now + +namespace Hal { + using namespace Kt; + + GDTPointer gdtPointer{}; + BasicGDT kernelGDT{}; + + void PrepareGDT() { + kout << "[Hal] GDT at " << base::hex << (uint64_t)&kernelGDT << "\n"; + kernelGDT = { + // Code segment offset 0x08 + // Data segment offset 0x10 + + // Not sure if having LimitLow set to 0xFFFF for the Null segment is kosher + {0xFFFF, 0, 0, 0x00, 0x00, 0}, + + // Kernel code/data + {0xFFFF, 0, 0, 0x9A, 0xA0, 0}, + {0xFFFF, 0, 0, 0x92, 0xA0, 0}, + + // User code/data + {0xFFFF, 0, 0, 0x9A, 0xA0, 0}, + {0xFFFF, 0, 0, 0x92, 0xA0, 0}, + + // One day this will point to our actual TSS + { + // Limit = sizeof(TSS) - 1 + 0, + + // Base = &TSS + 0, + 0, + + // Access byte = 0xFA + 0xFA, + + // Granularity = 0x00 + 0x00, + + 0x0 + } + }; + + gdtPointer = GDTPointer{ + .Size = sizeof(kernelGDT) - 1, + .GDTAddress = (uint64_t)&kernelGDT + }; + } + + // Helpers implemented in gdt.asm + extern "C" void LoadGDT(GDTPointer *gdtPointer); + extern "C" void ReloadSegments(); + + void BridgeLoadGDT() { + // Puts the GDT pointer structure into the GDTR + kout << "[Hal] Setting GDTR" << Kt::newline; + LoadGDT(&gdtPointer); + + kout << "[Hal] Reloading segments" << Kt::newline; + ReloadSegments(); + } +}; \ No newline at end of file diff --git a/kernel/src/Hal/GDT.hpp b/kernel/src/Hal/GDT.hpp new file mode 100644 index 0000000..1531701 --- /dev/null +++ b/kernel/src/Hal/GDT.hpp @@ -0,0 +1,62 @@ +/* + * gdt.hpp + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +using namespace std; + +// __attribute__((packed)) is the GCC extensions way of telling the compiler to ensure that it doesn't mess with these structures or add packing bytes +// for optimization because that would easily result in a triple fault. + +namespace Hal { + class GDTEntry { + public: + // Base/Limit are obsolete in Long mode because segmentation is no longer used + // Sadly we must still build and load the GDT during kernel init + + uint16_t LimitLow; + uint16_t BaseLow; + uint8_t BaseMiddle; + + // Determine which processor rings this segment can be used in + uint8_t AccessByte; + // Lower 4 bits are the higher 4 bits of limit + uint8_t GranularityByte; + + uint8_t BaseHigh; + + // 16 + 16 + 8 + 8 = 48 bits + }__attribute__((packed)); + + struct BasicGDT { + // Conventionally the first entry of the GDT has all values zeroed out. + GDTEntry Null; + + // Kernel code segment descriptor + GDTEntry KernelCode; + + // Kernel data segment descriptor + GDTEntry KernelData; + + // UM code segment descriptor + GDTEntry UserCode; + + // UM data segment descriptor + GDTEntry UserData; + + // Task State Segment + GDTEntry TSS; + }__attribute__((packed)); + + // Simple structure that tells the CPU the size of the GDT, and it's address + struct GDTPointer { + uint16_t Size; + uint64_t GDTAddress; + }__attribute__((packed)); + + void BridgeLoadGDT(); + void PrepareGDT(); +}; \ No newline at end of file diff --git a/kernel/src/KernelTerminal/terminal.cpp b/kernel/src/KernelTerminal/terminal.cpp new file mode 100644 index 0000000..47d1144 --- /dev/null +++ b/kernel/src/KernelTerminal/terminal.cpp @@ -0,0 +1,41 @@ +#include "terminal.hpp" +#include "../Libraries/flanterm/backends/fb.h" +#include "../Libraries/flanterm/flanterm.h" + +#include "../Libraries/string.hpp" + +namespace Kt { + flanterm_context *ctx; + + void Initialize(std::uint32_t *framebuffer, std::size_t width, std::size_t height, std::size_t pitch, + std::uint8_t red_mask_size, std::uint8_t red_mask_shift, + std::uint8_t green_mask_size, std::uint8_t green_mask_shift, + std::uint8_t blue_mask_size, std::uint8_t blue_mask_shift + ) + { + ctx = flanterm_fb_init( + NULL, + NULL, + framebuffer, + width, height, pitch, + red_mask_size, red_mask_shift, + green_mask_size, green_mask_shift, + blue_mask_size, blue_mask_shift, + NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, 0, 0, 1, + 0, 0, + 0 + ); + } + + void Putchar(char c) { + flanterm_write(ctx, &c, 1); + } + + void Print(const char *text) { + flanterm_write(ctx, text, Lib::strlen(text)); + } +}; \ No newline at end of file diff --git a/kernel/src/KernelTerminal/terminal.hpp b/kernel/src/KernelTerminal/terminal.hpp new file mode 100644 index 0000000..9f79bf6 --- /dev/null +++ b/kernel/src/KernelTerminal/terminal.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include + +#include + +namespace Kt +{ + constexpr char newline = '\n'; + + namespace screen + { + constexpr const char *clear = "\033[2J"; + constexpr const char *cursor_reset = "\033[H"; + }; + + void Initialize(std::uint32_t *framebuffer, std::size_t width, std::size_t height, std::size_t pitch, + std::uint8_t red_mask_size, std::uint8_t red_mask_shift, + std::uint8_t green_mask_size, std::uint8_t green_mask_shift, + std::uint8_t blue_mask_size, std::uint8_t blue_mask_shift); + void Putchar(char c); + void Print(const char *text); + + enum base + { + oct = 8, + dec = 10, + hex = 16 + }; + + inline base base_custom(int custom) + { + return (base)custom; + } + + class KernelOutStream + { + public: + base streamBaseType = base::dec; + + // C++ streaming operator like cout + friend KernelOutStream &operator<<(KernelOutStream &t, const char *string) + { + Print(string); + return t; + } + + // C++ streaming operator like cout + friend KernelOutStream &operator<<(KernelOutStream &t, const char chr) + { + Putchar(chr); + return t; + } + + friend KernelOutStream &operator<<(KernelOutStream &t, int number) + { + Print(Lib::int2basestr(number, t.streamBaseType)); + + return t; + } + + friend KernelOutStream &operator<<(KernelOutStream &t, std::uint32_t number) + { + Print(Lib::uint2basestr(number, t.streamBaseType)); + + return t; + } + + friend KernelOutStream &operator<<(KernelOutStream &t, std::uint64_t number) + { + Print(Lib::u64_2_basestr(number, t.streamBaseType)); + + return t; + } + + friend KernelOutStream &operator<<(KernelOutStream &t, base newBase) + { + t.streamBaseType = newBase; + + return t; + } + }; + + // This will be on the kernel entry point's stack. Which is totally fine since we don't ever exit from that function +}; + +extern Kt::KernelOutStream kout; + +namespace Kt +{ + class KernelErrorStream + { + public: + template + friend KernelErrorStream &operator<<(KernelErrorStream &t, T value) + { + kout << "\e[0;31m" << value << "\e[0m"; + + return t; + } + }; +}; + +extern Kt::KernelErrorStream kerr; \ No newline at end of file diff --git a/kernel/src/Libraries/flanterm b/kernel/src/Libraries/flanterm new file mode 160000 index 0000000..201100c --- /dev/null +++ b/kernel/src/Libraries/flanterm @@ -0,0 +1 @@ +Subproject commit 201100c968ad6db4f38632f9b6c3544854897788 diff --git a/kernel/src/Libraries/string.cpp b/kernel/src/Libraries/string.cpp new file mode 100644 index 0000000..bf973ed --- /dev/null +++ b/kernel/src/Libraries/string.cpp @@ -0,0 +1,81 @@ +#include "string.hpp" +#include +#include + +using namespace std; + +namespace Lib +{ + char output[1024]; + + // TODO make this buffer safe + char *int2basestr(int num, size_t radix) + { + char * str = (char *)&output; + + if (!num) { + return "0"; + } + + char base[] = "0123456789ABCDEFGH"; // IJKLMN .... + if (radix < 2 || radix > (sizeof(base) - 1)) + { // radix supported? + str[0] = '\0'; + return str; + } + + int si = 0; + if (num < 0) + { + str[si++] = '-'; + num = -num; + } + char tmp[256]; + int rdi = -1; + while (num) + { + tmp[++rdi] = base[num % radix]; + num /= radix; + } + while (rdi >= 0) + str[si++] = tmp[rdi--]; + str[si] = '\0'; + return str; + } + + char *u64_2_basestr(uint64_t num, size_t radix) + { + char * str = (char *)&output; + + if (!num) { + return "0"; + } + + char base[] = "0123456789ABCDEFGH"; // IJKLMN .... + + if (radix < 2 || radix > (sizeof(base) - 1)) + { // radix supported? + str[0] = '\0'; + return str; + } + + int si = 0; + + char tmp[256]; + int rdi = -1; + while (num) + { + tmp[++rdi] = base[num % radix]; + num /= radix; + } + while (rdi >= 0) + str[si++] = tmp[rdi--]; + str[si] = '\0'; + return str; + } + + char *uint2basestr(uint32_t num, size_t radix) + { + return u64_2_basestr((uint64_t)num, radix); + } +} \ No newline at end of file diff --git a/kernel/src/Libraries/string.hpp b/kernel/src/Libraries/string.hpp new file mode 100644 index 0000000..01d29e5 --- /dev/null +++ b/kernel/src/Libraries/string.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include + +namespace Lib { + inline int strlen(const char *string) { + int c = 0; + + while (*string != '\0') { + string++; + c++; + } + + return c; + } + + char *int2basestr(int num, size_t radix); + char *u64_2_basestr(uint64_t num, size_t radix); + char *uint2basestr(uint32_t num, size_t radix); +} \ No newline at end of file diff --git a/kernel/src/Memory/HHDM.hpp b/kernel/src/Memory/HHDM.hpp new file mode 100644 index 0000000..1a50402 --- /dev/null +++ b/kernel/src/Memory/HHDM.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + +namespace Memory { + extern std::uint64_t HHDMBase; + + inline uint64_t HHDM(uint64_t address) { + return HHDMBase + address; + } + + inline uint64_t HHDM(void* address) { + return HHDMBase + (uint64_t)address; + } + + inline uint64_t SubHHDM(uint64_t address) { + return address - HHDMBase; + } + + inline uint64_t SubHHDM(void* address) { + return SubHHDM((uint64_t)address); + } + + inline uint64_t IsHDDMVirtAddr(uint64_t address) { + if (address > HHDMBase) { + return true; + } + + return false; + } +}; \ No newline at end of file diff --git a/kernel/src/Memory/Memmap.cpp b/kernel/src/Memory/Memmap.cpp new file mode 100644 index 0000000..6ffdf89 --- /dev/null +++ b/kernel/src/Memory/Memmap.cpp @@ -0,0 +1,49 @@ +#include "Memmap.hpp" +#include +#include + +#include "PageAllocator.hpp" + +using namespace Kt; + +namespace Memory { + LargestSection Scan(limine_memmap_response* mmap) { + LargestSection currentLargestSection{}; + + for (size_t i = 0; i < mmap->entry_count; i++) { + auto entry = mmap->entries[i]; + + if (entry->base == 0) { + continue; + } + + if (entry->type == LIMINE_MEMMAP_USABLE) { + kout << "[Mem] Found conventional memory section (size = " << base::dec << entry->length << " bytes, address = 0x" << base::hex << (uint64_t)entry->base << ")" << newline; + + if (entry->length > currentLargestSection.size) { + currentLargestSection = { + .address = (uint64_t)entry->base, + .size = entry->length + }; + } + } + } + + [[unlikely]] if (currentLargestSection.address == 0) { + Panic("Couldn't find a usable memory section.", System::Registers{}); + } + + return currentLargestSection; + + // PageAllocator pa(currentLargestSection); + + // uint64_t alloc1 = (uint64_t)pa.Allocate(); + // uint64_t alloc2 = (uint64_t)pa.Allocate(); + // pa.Free((void *)alloc2); + // uint64_t alloc3 = (uint64_t)pa.Allocate(); + + // kout << "0x" << base::hex << alloc1 << "\n"; + // kout << "0x" << base::hex << alloc2 << "\n"; + // kout << "0x" << base::hex << alloc3 << "\n"; + } +}; \ No newline at end of file diff --git a/kernel/src/Memory/Memmap.hpp b/kernel/src/Memory/Memmap.hpp new file mode 100644 index 0000000..6c26e46 --- /dev/null +++ b/kernel/src/Memory/Memmap.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +using namespace std; + +namespace Memory { + // Shared + struct LargestSection { + uint64_t address; + size_t size; + }; + + LargestSection Scan(limine_memmap_response* mmap); +}; \ No newline at end of file diff --git a/kernel/src/Memory/PageAllocator.cpp b/kernel/src/Memory/PageAllocator.cpp new file mode 100644 index 0000000..2157a4f --- /dev/null +++ b/kernel/src/Memory/PageAllocator.cpp @@ -0,0 +1,131 @@ +#include "PageAllocator.hpp" +#include "Memmap.hpp" +#include "HHDM.hpp" + +#include + +namespace Memory +{ +#if defined(__x86_64__) || defined(__aarch64__) +#define PAGE_SIZE 4096 +#else +#error Sorry, Unimplemented! +#endif + + PageAllocator::PageAllocator(LargestSection section) + { + g_section = section; + + size_t pages = g_section.size / PAGE_SIZE; + Block *current_ref{&head}; + Block *start_block_ptr {(Block *)HHDM(section.address)}; + Block& start_block = *start_block_ptr; + + start_block = Block { + .size = section.size, + .next = nullptr + }; + + *start_block_ptr = start_block; + + current_ref->next = start_block_ptr; + + kout << "[Mem] PageAllocator created for memory section starting at 0x" << Kt::base::hex << section.address << Kt::newline; + } + + void* PageAllocator::RequestPage(bool phys) { + bool found_block = false; + + Block* current_block_ptr{head.next}; + Block* current_prev_ptr{(Block *)&head}; + + while (!found_block) { + if (current_block_ptr == nullptr) { + kerr << "A PageAllocator was forced to return a Null pointer. (1)" << Kt::newline; + return nullptr; + } + + if (current_prev_ptr == nullptr) { + kerr << "A PageAllocator was forced to return a Null pointer. (2)" << Kt::newline; + return nullptr; + } + + if (current_block_ptr->size >= 0x1000) { // 0x1000 == 4096 + current_block_ptr->size -= 0x1000; + + if (!current_block_ptr->size) { + current_prev_ptr->next = current_block_ptr->next; + return (void *)current_block_ptr; + } + + Block block_copy = *current_block_ptr; + + current_prev_ptr->next = (Block *)(uint64_t)current_block_ptr + 0x1000; + *current_prev_ptr->next = block_copy; + + if (phys) { + return (void *)SubHHDM(current_block_ptr); + } + + return (void *)current_block_ptr; + } else { + if (current_block_ptr->next != nullptr) { + current_prev_ptr = current_block_ptr; + current_block_ptr = current_block_ptr->next; + } else { + kerr << "A PageAllocator was forced to return a Null pointer. (3)" << Kt::newline; + return nullptr; + } + } + + if (current_block_ptr != nullptr) { + if (current_block_ptr->next == nullptr) { + kerr << "A PageAllocator was forced to return a Null pointer. (4)" << Kt::newline; + return nullptr; + } + + current_block_ptr = current_block_ptr->next; + } else + { + kerr << "A PageAllocator was forced to return a Null pointer. (5)" << Kt::newline; + return nullptr; + } + } + + return nullptr; + } + + void PageAllocator::Free(void *pagePtr) { + if (!IsHDDMVirtAddr((uint64_t)pagePtr)) { + pagePtr = (void *)HHDM(pagePtr); + } + + Block* fmrNext = head.next; + + Block* blockPtr = (Block *)pagePtr; + + head.next = blockPtr; + head.next->size = 0x1000; + + head.next->next = fmrNext; + } + + void PageAllocator::Stress() { + // ~410 MiB + for (size_t i = 0; i < 100000; i++) { + kout << "[Mem] Stress allocation " << Kt::base::dec << i << ": " << "0x" << Kt::base::hex << (uint64_t)Memory::KernelPFA->RequestPage(false) << Kt::newline; + } + } + + // Traverses the PageAllocator's linked list for debugging + void PageAllocator::Walk() { + Block* current = {head.next}; + size_t i{0}; + + while (current != nullptr) { + kout << "Block " << Kt::base::dec << i << " {" << current->size << " bytes & address 0x" << Kt::base::hex << (uint64_t)current << "}" << Kt::newline; + current = current->next; + i++; + } + } +}; \ No newline at end of file diff --git a/kernel/src/Memory/PageAllocator.hpp b/kernel/src/Memory/PageAllocator.hpp new file mode 100644 index 0000000..3f5f8c6 --- /dev/null +++ b/kernel/src/Memory/PageAllocator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "Memmap.hpp" + +namespace Memory { + class PageAllocator { + LargestSection g_section; + + struct Block { + size_t size; + Block* next; + }; + + Block head; + public: + PageAllocator(LargestSection section); + void* RequestPage(bool phys = false); + void Free(void *pagePtr); + + void Stress(); + void Walk(); + }; + + extern PageAllocator* KernelPFA; +}; \ No newline at end of file diff --git a/kernel/src/System/Registers.hpp b/kernel/src/System/Registers.hpp new file mode 100644 index 0000000..d02e631 --- /dev/null +++ b/kernel/src/System/Registers.hpp @@ -0,0 +1,12 @@ +#pragma once +#include + +using namespace std; + +#if defined (__x86_64__) +namespace System { + struct Registers { + + }; +}; +#endif \ No newline at end of file diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp new file mode 100644 index 0000000..9bc9a1b --- /dev/null +++ b/kernel/src/main.cpp @@ -0,0 +1,249 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Kt; + +namespace Memory { + PageAllocator* KernelPFA; + uint64_t HHDMBase; +}; + +KernelOutStream kout; +KernelErrorStream kerr; + +// Set the base revision to 3, this is recommended as this is the latest +// base revision described by the Limine boot protocol specification. +// See specification for further info. + +namespace { + +__attribute__((used, section(".limine_requests"))) +volatile LIMINE_BASE_REVISION(3); + +} + +// The Limine requests can be placed anywhere, but it is important that +// the compiler does not optimise them away, so, usually, they should +// be made volatile or equivalent, _and_ they should be accessed at least +// once or marked as used with the "used" attribute as done here. + +namespace { + +__attribute__((used, section(".limine_requests"))) +volatile limine_framebuffer_request framebuffer_request = { + .id = LIMINE_FRAMEBUFFER_REQUEST, + .revision = 0, + .response = nullptr +}; + +__attribute__((used, section(".limine_requests"))) +volatile limine_efi_system_table_request system_table_request = { + .id = LIMINE_EFI_SYSTEM_TABLE_REQUEST, + .revision = 0, + .response = nullptr +}; + +__attribute__((used, section(".limine_requests"))) +volatile limine_hhdm_request hhdm_request = { + .id = LIMINE_HHDM_REQUEST, + .revision = 0, + .response = nullptr +}; + +__attribute__((used, section(".limine_requests"))) +volatile limine_memmap_request memmap_request = { + .id = LIMINE_MEMMAP_REQUEST, + .revision = 0, + .response = nullptr +}; + +} + +// Finally, define the start and end markers for the Limine requests. +// These can also be moved anywhere, to any .cpp file, as seen fit. + +namespace { + +__attribute__((used, section(".limine_requests_start"))) +volatile LIMINE_REQUESTS_START_MARKER; + +__attribute__((used, section(".limine_requests_end"))) +volatile LIMINE_REQUESTS_END_MARKER; + +} + +// GCC and Clang reserve the right to generate calls to the following +// 4 functions even if they are not directly called. +// Implement them as the C specification mandates. +// DO NOT remove or rename these functions, or stuff will eventually break! +// They CAN be moved to a different .cpp file. + +extern "C" { + +void *memcpy(void *dest, const void *src, std::size_t n) { + std::uint8_t *pdest = static_cast(dest); + const std::uint8_t *psrc = static_cast(src); + + for (std::size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + + return dest; +} + +void *memset(void *s, int c, std::size_t n) { + std::uint8_t *p = static_cast(s); + + for (std::size_t i = 0; i < n; i++) { + p[i] = static_cast(c); + } + + return s; +} + +void *memmove(void *dest, const void *src, std::size_t n) { + std::uint8_t *pdest = static_cast(dest); + const std::uint8_t *psrc = static_cast(src); + + if (src > dest) { + for (std::size_t i = 0; i < n; i++) { + pdest[i] = psrc[i]; + } + } else if (src < dest) { + for (std::size_t i = n; i > 0; i--) { + pdest[i-1] = psrc[i-1]; + } + } + + return dest; +} + +int memcmp(const void *s1, const void *s2, std::size_t n) { + const std::uint8_t *p1 = static_cast(s1); + const std::uint8_t *p2 = static_cast(s2); + + for (std::size_t i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] < p2[i] ? -1 : 1; + } + } + + return 0; +} + +} + +// Halt and catch fire function. +namespace { + +void hcf() { + for (;;) { +#if defined (__x86_64__) + asm ("hlt"); +#elif defined (__aarch64__) || defined (__riscv) + asm ("wfi"); +#elif defined (__loongarch64) + asm ("idle 0"); +#endif + } +} + +} + +// The following stubs are required by the Itanium C++ ABI (the one we use, +// regardless of the "Itanium" nomenclature). +// Like the memory functions above, these stubs can be moved to a different .cpp file, +// but should not be removed, unless you know what you are doing. +extern "C" { + int __cxa_atexit(void (*)(void *), void *, void *) { return 0; } + void __cxa_pure_virtual() { hcf(); } + void *__dso_handle; +} + +// Extern declarations for global constructors array. +extern void (*__init_array[])(); +extern void (*__init_array_end[])(); + +// The following will be our kernel's entry point. +// If renaming kmain() to something else, make sure to change the +// linker script accordingly. +extern "C" void kmain() { + // Ensure the bootloader actually understands our base revision (see spec). + if (LIMINE_BASE_REVISION_SUPPORTED == false) { + hcf(); + } + + // Call global constructors. + for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) { + __init_array[i](); + } + + // Ensure we got a framebuffer. + if (framebuffer_request.response == nullptr + || framebuffer_request.response->framebuffer_count < 1) { + hcf(); + } + + // Fetch the first framebuffer. + limine_framebuffer *framebuffer{framebuffer_request.response->framebuffers[0]}; + + // Initialize the terminal + Kt::Initialize((uint32_t*)framebuffer->address, framebuffer->width, framebuffer->height, framebuffer->pitch, framebuffer->red_mask_size, framebuffer->red_mask_shift, + framebuffer->green_mask_size, framebuffer->green_mask_shift, + framebuffer->blue_mask_size, framebuffer->blue_mask_shift); + +#if defined (__x86_64__) + Hal::PrepareGDT(); + Hal::BridgeLoadGDT(); +#endif + + // RGB lines + for (std::size_t i = 500; i < 800; i++) { + volatile std::uint32_t *fb_ptr = static_cast(framebuffer->address); + fb_ptr[i * (framebuffer->pitch / 4) + (i - 5*5)] = 0xFF0000; // Red + fb_ptr[i * (framebuffer->pitch / 4) + (i - 10*5)] = 0x00FF00; // Green + fb_ptr[i * (framebuffer->pitch / 4) + (i - 15*5)] = 0x0000FF; // Blue + + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 20*5)] = 0xFF0000; // Red + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 25*5)] = 0x00FF00; // Green + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 30*5)] = 0x0000FF; // Blue + + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 35*5)] = 0xFF0000; // Red + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 40*5)] = 0x00FF00; // Green + // fb_ptr[i * (framebuffer->pitch / 4) + (i - 45*5)] = 0x0000FF; // Blue + } + + uint64_t hhdm_offset = hhdm_request.response->offset; + + kout << "[Mem] HHDM offset: 0x" << base::hex << hhdm_offset << newline; + + Memory::HHDMBase = hhdm_offset; + + if (!system_table_request. response || !system_table_request.response->address) { + kerr << "[Efi] EFI System Table not supported" << newline; + } else { + kout << "[Efi] EFI system table at 0x" << base::hex << (uint64_t)system_table_request.response->address << newline; + } + + if (memmap_request.response != nullptr) { + auto result = Memory::Scan(memmap_request.response); + auto allocator = Memory::PageAllocator(result); + + kout << "[Mem] Creating PageAllocator for system conventional memory" << newline; + Memory::KernelPFA = &allocator; + } else { + Panic("Guru Meditation Error: System memory map missing!", System::Registers{}); + } + + + hcf(); +} diff --git a/limine.conf b/limine.conf new file mode 100644 index 0000000..0093fad --- /dev/null +++ b/limine.conf @@ -0,0 +1,10 @@ +# Timeout in seconds that Limine will use before automatically booting. +timeout: 3 + +# The entry name that will be displayed in the boot menu. +/Limine Template + # We use the Limine boot protocol. + protocol: limine + + # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. + path: boot():/boot/kernel