From b80e6735cdceea74a3f0d2cb213efa3edab5c71f Mon Sep 17 00:00:00 2001 From: csd4ni3l Date: Fri, 13 Mar 2026 18:57:56 +0100 Subject: [PATCH] Build upon the limine Rust template and a working pipeline, display an orange background on load. --- .gitignore | 25 +--- GNUmakefile | 251 +++++++++++++++++++++++++++++++++++ README.md | 34 +++++ kernel/.gitignore | 2 + kernel/Cargo.lock | 25 ++++ kernel/Cargo.toml | 7 + kernel/GNUmakefile | 44 ++++++ kernel/build.rs | 7 + kernel/linker-aarch64.ld | 63 +++++++++ kernel/linker-loongarch64.ld | 63 +++++++++ kernel/linker-riscv64.ld | 66 +++++++++ kernel/linker-x86_64.ld | 63 +++++++++ kernel/rust-toolchain.toml | 8 ++ kernel/src/graphics.rs | 25 ++++ kernel/src/main.rs | 64 +++++++++ limine.conf | 10 ++ 16 files changed, 736 insertions(+), 21 deletions(-) create mode 100644 GNUmakefile create mode 100644 README.md create mode 100644 kernel/.gitignore create mode 100644 kernel/Cargo.lock create mode 100644 kernel/Cargo.toml create mode 100644 kernel/GNUmakefile create mode 100644 kernel/build.rs create mode 100644 kernel/linker-aarch64.ld create mode 100644 kernel/linker-loongarch64.ld create mode 100644 kernel/linker-riscv64.ld create mode 100644 kernel/linker-x86_64.ld create mode 100644 kernel/rust-toolchain.toml create mode 100644 kernel/src/graphics.rs create mode 100644 kernel/src/main.rs create mode 100644 limine.conf diff --git a/.gitignore b/.gitignore index ad67955..cd213a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,4 @@ -# Generated by Cargo -# will have compiled files and executables -debug -target - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb - -# Generated by cargo mutants -# Contains mutation testing data -**/mutants.out*/ - -# RustRover -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +/limine +/ovmf +*.iso +*.hdd diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..0cecac4 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,251 @@ +# Nuke built-in rules and variables. +MAKEFLAGS += -rR +.SUFFIXES: + +# Convenience macro to reliably declare user overridable variables. +override USER_VARIABLE = $(if $(filter $(origin $(1)),default undefined),$(eval override $(1) := $(2))) + +# Target architecture to build for. Default to x86_64. +$(call USER_VARIABLE,KARCH,x86_64) + +# Default user QEMU flags. These are appended to the QEMU command calls. +$(call USER_VARIABLE,QEMUFLAGS,-m 2G) + +override IMAGE_NAME := NeumannOS-$(KARCH) + +.PHONY: all +all: $(IMAGE_NAME).iso + +.PHONY: all-hdd +all-hdd: $(IMAGE_NAME).hdd + +.PHONY: run +run: run-$(KARCH) + +.PHONY: run-hdd +run-hdd: run-hdd-$(KARCH) + +.PHONY: run-x86_64 +run-x86_64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).iso + qemu-system-$(KARCH) \ + -M q35 \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-x86_64 +run-hdd-x86_64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(KARCH) \ + -M q35 \ + -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-aarch64 +run-aarch64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).iso + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-aarch64 +run-hdd-aarch64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-riscv64 +run-riscv64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).iso + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-riscv64 +run-hdd-riscv64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +.PHONY: run-loongarch64 +run-loongarch64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).iso + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -cdrom $(IMAGE_NAME).iso \ + $(QEMUFLAGS) + +.PHONY: run-hdd-loongarch64 +run-hdd-loongarch64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).hdd + qemu-system-$(KARCH) \ + -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-$(KARCH).fd,readonly=on \ + -drive if=pflash,unit=1,format=raw,file=ovmf/ovmf-vars-$(KARCH).fd \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + + +.PHONY: run-bios +run-bios: $(IMAGE_NAME).iso + qemu-system-$(KARCH) \ + -M q35 \ + -cdrom $(IMAGE_NAME).iso \ + -boot d \ + $(QEMUFLAGS) + +.PHONY: run-hdd-bios +run-hdd-bios: $(IMAGE_NAME).hdd + qemu-system-$(KARCH) \ + -M q35 \ + -hda $(IMAGE_NAME).hdd \ + $(QEMUFLAGS) + +ovmf/ovmf-code-$(KARCH).fd: + mkdir -p ovmf + curl -Lo $@ https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-code-$(KARCH).fd + case "$(KARCH)" in \ + aarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=67108864 2>/dev/null;; \ + loongarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=5242880 2>/dev/null;; \ + riscv64) dd if=/dev/zero of=$@ bs=1 count=0 seek=33554432 2>/dev/null;; \ + esac + +ovmf/ovmf-vars-$(KARCH).fd: + mkdir -p ovmf + curl -Lo $@ https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-vars-$(KARCH).fd + case "$(KARCH)" in \ + aarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=67108864 2>/dev/null;; \ + loongarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=5242880 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 + +.PHONY: kernel +kernel: + $(MAKE) -C kernel + +$(IMAGE_NAME).iso: limine/limine kernel + rm -rf iso_root + mkdir -p iso_root/boot + cp -v kernel/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 ($(KARCH),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 -b boot/limine/limine-bios-cd.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + --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 ($(KARCH),aarch64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTAA64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs \ + --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 ($(KARCH),riscv64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTRISCV64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs \ + --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 ($(KARCH),loongarch64) + cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ + cp -v limine/BOOTLOONGARCH64.EFI iso_root/EFI/BOOT/ + xorriso -as mkisofs \ + --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 + sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 +ifeq ($(KARCH),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-$(KARCH)/kernel ::/boot + mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine +ifeq ($(KARCH),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 ($(KARCH),aarch64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTAA64.EFI ::/EFI/BOOT +endif +ifeq ($(KARCH),riscv64) + mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTRISCV64.EFI ::/EFI/BOOT +endif +ifeq ($(KARCH),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: clean + $(MAKE) -C kernel distclean + rm -rf limine ovmf diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb07e4b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# NeumannOS +NeumannOS is an OS written from scratch, named after the famous mathematician, physicist, computer scientist and engineer Neumann János Lajos. + +The repo is based on the limine-rust-template. + +## How to use this? + +### Dependencies + +Any `make` command depends on GNU make (`gmake`) and is expected to be run using it. This usually means using `make` on most GNU/Linux distros, or `gmake` on other non-GNU systems. + +All `make all*` targets depend on Rust. + +Additionally, building an ISO with `make all` requires `xorriso`, and building a HDD/USB image with `make all-hdd` requires `sgdisk` (usually from `gdisk` or `gptfdisk` packages) and `mtools`. + +### Architectural targets + +The `KARCH` make variable determines the target architecture to build the kernel and image for. + +The default `KARCH` is `x86_64`. Other options include: `aarch64`, `riscv64`, and `loongarch64`. + +Other architectures will need to be enabled in kernel/rust-toolchain.toml + +### Makefile targets + +Running `make all` will compile the kernel (from the `kernel/` directory) and then generate a bootable ISO image. + +Running `make all-hdd` will compile the kernel and then generate a raw image suitable to be flashed onto a USB stick or hard drive/SSD. + +Running `make run` will build the kernel and a bootable ISO (equivalent to make all) and then run it using `qemu` (if installed). + +Running `make run-hdd` will build the kernel and a raw HDD image (equivalent to make all-hdd) and then run it using `qemu` (if installed). + +The `run-uefi` and `run-hdd-uefi` targets are equivalent to their non `-uefi` counterparts except that they boot `qemu` using a UEFI-compatible firmware. diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..6b51966 --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,2 @@ +/kernel +/target diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock new file mode 100644 index 0000000..7743811 --- /dev/null +++ b/kernel/Cargo.lock @@ -0,0 +1,25 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "limine" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6d2ee42712e7bd2c787365cd1dab06ef59a61becbf87bec7b32b970bd2594b" +dependencies = [ + "bitflags", +] + +[[package]] +name = "limine-rust-template" +version = "0.1.0" +dependencies = [ + "limine", +] diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..c7cdbe7 --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "limine-rust-template" +version = "0.1.0" +edition = "2024" + +[dependencies] +limine = "0.5" diff --git a/kernel/GNUmakefile b/kernel/GNUmakefile new file mode 100644 index 0000000..46e0f2b --- /dev/null +++ b/kernel/GNUmakefile @@ -0,0 +1,44 @@ +# 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 + +# Convenience macro to reliably declare user overridable variables. +override USER_VARIABLE = $(if $(filter $(origin $(1)),default undefined),$(eval override $(1) := $(2))) + +# Target architecture to build for. Default to x86_64. +$(call USER_VARIABLE,KARCH,x86_64) + +ifeq ($(RUST_TARGET),) + override RUST_TARGET := $(KARCH)-unknown-none + ifeq ($(KARCH),riscv64) + override RUST_TARGET := riscv64gc-unknown-none-elf + endif +endif + +ifeq ($(RUST_PROFILE),) + override RUST_PROFILE := dev +endif + +override RUST_PROFILE_SUBDIR := $(RUST_PROFILE) +ifeq ($(RUST_PROFILE),dev) + override RUST_PROFILE_SUBDIR := debug +endif + +# Default target. +.PHONY: all +all: + RUSTFLAGS="-C relocation-model=static" cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE) + cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/$$(cd target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR) && find -maxdepth 1 -perm -111 -type f) kernel + +# Remove object files and the final executable. +.PHONY: clean +clean: + cargo clean + rm -rf kernel + +.PHONY: distclean +distclean: clean diff --git a/kernel/build.rs b/kernel/build.rs new file mode 100644 index 0000000..b8d05be --- /dev/null +++ b/kernel/build.rs @@ -0,0 +1,7 @@ +fn main() { + let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + // Tell cargo to pass the linker script to the linker.. + println!("cargo:rustc-link-arg=-Tlinker-{arch}.ld"); + // ..and to re-run if it changes. + println!("cargo:rerun-if-changed=linker-{arch}.ld"); +} diff --git a/kernel/linker-aarch64.ld b/kernel/linker-aarch64.ld new file mode 100644 index 0000000..2c38122 --- /dev/null +++ b/kernel/linker-aarch64.ld @@ -0,0 +1,63 @@ +/* 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 +{ + 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; + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + + /* Place the sections that contain the Limine requests as part of the .data */ + /* output section. */ + KEEP(*(.requests_start_marker)) + KEEP(*(.requests)) + KEEP(*(.requests_end_marker)) + } :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..a233cc1 --- /dev/null +++ b/kernel/linker-loongarch64.ld @@ -0,0 +1,63 @@ +/* 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 +{ + 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; + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + + /* Place the sections that contain the Limine requests as part of the .data */ + /* output section. */ + KEEP(*(.requests_start_marker)) + KEEP(*(.requests)) + KEEP(*(.requests_end_marker)) + } :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..3b8de44 --- /dev/null +++ b/kernel/linker-riscv64.ld @@ -0,0 +1,66 @@ +/* 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 +{ + 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; + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + + /* Place the sections that contain the Limine requests as part of the .data */ + /* output section. */ + KEEP(*(.requests_start_marker)) + KEEP(*(.requests)) + KEEP(*(.requests_end_marker)) + + *(.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..417faa4 --- /dev/null +++ b/kernel/linker-x86_64.ld @@ -0,0 +1,63 @@ +/* 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 +{ + 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; + + .text : { + *(.text .text.*) + } :text + + /* Move to the next memory page for .rodata */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + /* Move to the next memory page for .data */ + . = ALIGN(CONSTANT(MAXPAGESIZE)); + + .data : { + *(.data .data.*) + + /* Place the sections that contain the Limine requests as part of the .data */ + /* output section. */ + KEEP(*(.requests_start_marker)) + KEEP(*(.requests)) + KEEP(*(.requests_end_marker)) + } :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/rust-toolchain.toml b/kernel/rust-toolchain.toml new file mode 100644 index 0000000..66a0077 --- /dev/null +++ b/kernel/rust-toolchain.toml @@ -0,0 +1,8 @@ +[toolchain] +channel = "nightly" +targets = [ + "x86_64-unknown-none", + # "aarch64-unknown-none", + # "riscv64gc-unknown-none-elf", + # "loongarch64-unknown-none", +] diff --git a/kernel/src/graphics.rs b/kernel/src/graphics.rs new file mode 100644 index 0000000..4b85a65 --- /dev/null +++ b/kernel/src/graphics.rs @@ -0,0 +1,25 @@ +use limine::framebuffer::Framebuffer; + +pub fn rgb(r: u8, g: u8, b: u8) -> u32 { + ((r as u32) << 16) | ((g as u32) << 8) | (b as u32) +} + +pub fn create_rect(framebuffer: &Framebuffer, x: u64, y: u64, width: u64, height: u64, color: u32) { + for fb_x in x..x+width { + for fb_y in y..y+height { + // Calculate the pixel offset using the framebuffer information we obtained above. + // We skip `i` scanlines (pitch is provided in bytes) and add `i * 4` to skip `i` pixels forward. + let bytes_per_pixel = framebuffer.bpp() as u64 / 8; + let pixel_offset = fb_y * framebuffer.pitch() + fb_x * bytes_per_pixel; + + // Write 0xFFFFFFFF to the provided pixel offset to fill it white. + unsafe { + framebuffer + .addr() + .add(pixel_offset as usize) + .cast::() + .write(color) + }; + } + } +} \ No newline at end of file diff --git a/kernel/src/main.rs b/kernel/src/main.rs new file mode 100644 index 0000000..8a1faca --- /dev/null +++ b/kernel/src/main.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +use core::arch::asm; + +use limine::BaseRevision; +use limine::request::{FramebufferRequest, RequestsEndMarker, RequestsStartMarker}; + +mod graphics; +use crate::graphics::*; + + +/// Sets the base revision to the latest revision supported by the crate. +/// See specification for further info. +/// Be sure to mark all limine requests with #[used], otherwise they may be removed by the compiler. +#[used] +// The .requests section allows limine to find the requests faster and more safely. +#[unsafe(link_section = ".requests")] +static BASE_REVISION: BaseRevision = BaseRevision::new(); + +#[used] +#[unsafe(link_section = ".requests")] +static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(); + +/// Define the stand and end markers for Limine requests. +#[used] +#[unsafe(link_section = ".requests_start_marker")] +static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new(); +#[used] +#[unsafe(link_section = ".requests_end_marker")] +static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new(); + +#[unsafe(no_mangle)] +unsafe extern "C" fn kmain() -> ! { + // All limine requests must also be referenced in a called function, otherwise they may be + // removed by the linker. + assert!(BASE_REVISION.is_supported()); + + if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() { + if let Some(framebuffer) = framebuffer_response.framebuffers().next() { + graphics::create_rect(&framebuffer, 0, 0, framebuffer.width(), framebuffer.height(), graphics::rgb(253, 129, 0)); + } + } + + hcf(); +} + +#[panic_handler] +fn rust_panic(_info: &core::panic::PanicInfo) -> ! { + hcf(); +} + +fn hcf() -> ! { + loop { + unsafe { + #[cfg(target_arch = "x86_64")] + asm!("hlt"); + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + asm!("wfi"); + #[cfg(target_arch = "loongarch64")] + asm!("idle 0"); + } + } +} diff --git a/limine.conf b/limine.conf new file mode 100644 index 0000000..e77e0f0 --- /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 NeumannOS + # We use the Limine boot protocol. + protocol: limine + + # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. + kernel_path: boot():/boot/kernel