├── .gitignore ├── GNUmakefile ├── LICENSE ├── README.md ├── kernel ├── .gitignore ├── GNUmakefile ├── get-deps ├── linker-aarch64.ld ├── linker-loongarch64.ld ├── linker-riscv64.ld ├── linker-x86_64.ld └── src │ └── main.cpp └── limine.conf /.gitignore: -------------------------------------------------------------------------------- 1 | /kernel-deps 2 | /limine 3 | /ovmf 4 | *.iso 5 | *.hdd 6 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | # Nuke built-in rules and variables. 2 | MAKEFLAGS += -rR 3 | .SUFFIXES: 4 | 5 | # Target architecture to build for. Default to x86_64. 6 | ARCH := x86_64 7 | 8 | # Default user QEMU flags. These are appended to the QEMU command calls. 9 | QEMUFLAGS := -m 2G 10 | 11 | override IMAGE_NAME := template-$(ARCH) 12 | 13 | # Toolchain for building the 'limine' executable for the host. 14 | HOST_CC := cc 15 | HOST_CFLAGS := -g -O2 -pipe 16 | HOST_CPPFLAGS := 17 | HOST_LDFLAGS := 18 | HOST_LIBS := 19 | 20 | .PHONY: all 21 | all: $(IMAGE_NAME).iso 22 | 23 | .PHONY: all-hdd 24 | all-hdd: $(IMAGE_NAME).hdd 25 | 26 | .PHONY: run 27 | run: run-$(ARCH) 28 | 29 | .PHONY: run-hdd 30 | run-hdd: run-hdd-$(ARCH) 31 | 32 | .PHONY: run-x86_64 33 | run-x86_64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso 34 | qemu-system-$(ARCH) \ 35 | -M q35 \ 36 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 37 | -cdrom $(IMAGE_NAME).iso \ 38 | $(QEMUFLAGS) 39 | 40 | .PHONY: run-hdd-x86_64 41 | run-hdd-x86_64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd 42 | qemu-system-$(ARCH) \ 43 | -M q35 \ 44 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 45 | -hda $(IMAGE_NAME).hdd \ 46 | $(QEMUFLAGS) 47 | 48 | .PHONY: run-aarch64 49 | run-aarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso 50 | qemu-system-$(ARCH) \ 51 | -M virt \ 52 | -cpu cortex-a72 \ 53 | -device ramfb \ 54 | -device qemu-xhci \ 55 | -device usb-kbd \ 56 | -device usb-mouse \ 57 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 58 | -cdrom $(IMAGE_NAME).iso \ 59 | $(QEMUFLAGS) 60 | 61 | .PHONY: run-hdd-aarch64 62 | run-hdd-aarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd 63 | qemu-system-$(ARCH) \ 64 | -M virt \ 65 | -cpu cortex-a72 \ 66 | -device ramfb \ 67 | -device qemu-xhci \ 68 | -device usb-kbd \ 69 | -device usb-mouse \ 70 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 71 | -hda $(IMAGE_NAME).hdd \ 72 | $(QEMUFLAGS) 73 | 74 | .PHONY: run-riscv64 75 | run-riscv64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso 76 | qemu-system-$(ARCH) \ 77 | -M virt \ 78 | -cpu rv64 \ 79 | -device ramfb \ 80 | -device qemu-xhci \ 81 | -device usb-kbd \ 82 | -device usb-mouse \ 83 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 84 | -cdrom $(IMAGE_NAME).iso \ 85 | $(QEMUFLAGS) 86 | 87 | .PHONY: run-hdd-riscv64 88 | run-hdd-riscv64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd 89 | qemu-system-$(ARCH) \ 90 | -M virt \ 91 | -cpu rv64 \ 92 | -device ramfb \ 93 | -device qemu-xhci \ 94 | -device usb-kbd \ 95 | -device usb-mouse \ 96 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 97 | -hda $(IMAGE_NAME).hdd \ 98 | $(QEMUFLAGS) 99 | 100 | .PHONY: run-loongarch64 101 | run-loongarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).iso 102 | qemu-system-$(ARCH) \ 103 | -M virt \ 104 | -cpu la464 \ 105 | -device ramfb \ 106 | -device qemu-xhci \ 107 | -device usb-kbd \ 108 | -device usb-mouse \ 109 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 110 | -cdrom $(IMAGE_NAME).iso \ 111 | $(QEMUFLAGS) 112 | 113 | .PHONY: run-hdd-loongarch64 114 | run-hdd-loongarch64: ovmf/ovmf-code-$(ARCH).fd $(IMAGE_NAME).hdd 115 | qemu-system-$(ARCH) \ 116 | -M virt \ 117 | -cpu la464 \ 118 | -device ramfb \ 119 | -device qemu-xhci \ 120 | -device usb-kbd \ 121 | -device usb-mouse \ 122 | -drive if=pflash,unit=0,format=raw,file=ovmf/ovmf-code-$(ARCH).fd,readonly=on \ 123 | -hda $(IMAGE_NAME).hdd \ 124 | $(QEMUFLAGS) 125 | 126 | 127 | .PHONY: run-bios 128 | run-bios: $(IMAGE_NAME).iso 129 | qemu-system-$(ARCH) \ 130 | -M q35 \ 131 | -cdrom $(IMAGE_NAME).iso \ 132 | -boot d \ 133 | $(QEMUFLAGS) 134 | 135 | .PHONY: run-hdd-bios 136 | run-hdd-bios: $(IMAGE_NAME).hdd 137 | qemu-system-$(ARCH) \ 138 | -M q35 \ 139 | -hda $(IMAGE_NAME).hdd \ 140 | $(QEMUFLAGS) 141 | 142 | ovmf/ovmf-code-$(ARCH).fd: 143 | mkdir -p ovmf 144 | curl -Lo $@ https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-code-$(ARCH).fd 145 | case "$(ARCH)" in \ 146 | aarch64) dd if=/dev/zero of=$@ bs=1 count=0 seek=67108864 2>/dev/null;; \ 147 | riscv64) dd if=/dev/zero of=$@ bs=1 count=0 seek=33554432 2>/dev/null;; \ 148 | esac 149 | 150 | limine/limine: 151 | rm -rf limine 152 | git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 153 | $(MAKE) -C limine \ 154 | CC="$(HOST_CC)" \ 155 | CFLAGS="$(HOST_CFLAGS)" \ 156 | CPPFLAGS="$(HOST_CPPFLAGS)" \ 157 | LDFLAGS="$(HOST_LDFLAGS)" \ 158 | LIBS="$(HOST_LIBS)" 159 | 160 | kernel-deps: 161 | ./kernel/get-deps 162 | touch kernel-deps 163 | 164 | .PHONY: kernel 165 | kernel: kernel-deps 166 | $(MAKE) -C kernel 167 | 168 | $(IMAGE_NAME).iso: limine/limine kernel 169 | rm -rf iso_root 170 | mkdir -p iso_root/boot 171 | cp -v kernel/bin-$(ARCH)/kernel iso_root/boot/ 172 | mkdir -p iso_root/boot/limine 173 | cp -v limine.conf iso_root/boot/limine/ 174 | mkdir -p iso_root/EFI/BOOT 175 | ifeq ($(ARCH),x86_64) 176 | cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin iso_root/boot/limine/ 177 | cp -v limine/BOOTX64.EFI iso_root/EFI/BOOT/ 178 | cp -v limine/BOOTIA32.EFI iso_root/EFI/BOOT/ 179 | xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \ 180 | -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ 181 | -apm-block-size 2048 --efi-boot boot/limine/limine-uefi-cd.bin \ 182 | -efi-boot-part --efi-boot-image --protective-msdos-label \ 183 | iso_root -o $(IMAGE_NAME).iso 184 | ./limine/limine bios-install $(IMAGE_NAME).iso 185 | endif 186 | ifeq ($(ARCH),aarch64) 187 | cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ 188 | cp -v limine/BOOTAA64.EFI iso_root/EFI/BOOT/ 189 | xorriso -as mkisofs -R -r -J \ 190 | -hfsplus -apm-block-size 2048 \ 191 | --efi-boot boot/limine/limine-uefi-cd.bin \ 192 | -efi-boot-part --efi-boot-image --protective-msdos-label \ 193 | iso_root -o $(IMAGE_NAME).iso 194 | endif 195 | ifeq ($(ARCH),riscv64) 196 | cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ 197 | cp -v limine/BOOTRISCV64.EFI iso_root/EFI/BOOT/ 198 | xorriso -as mkisofs -R -r -J \ 199 | -hfsplus -apm-block-size 2048 \ 200 | --efi-boot boot/limine/limine-uefi-cd.bin \ 201 | -efi-boot-part --efi-boot-image --protective-msdos-label \ 202 | iso_root -o $(IMAGE_NAME).iso 203 | endif 204 | ifeq ($(ARCH),loongarch64) 205 | cp -v limine/limine-uefi-cd.bin iso_root/boot/limine/ 206 | cp -v limine/BOOTLOONGARCH64.EFI iso_root/EFI/BOOT/ 207 | xorriso -as mkisofs -R -r -J \ 208 | -hfsplus -apm-block-size 2048 \ 209 | --efi-boot boot/limine/limine-uefi-cd.bin \ 210 | -efi-boot-part --efi-boot-image --protective-msdos-label \ 211 | iso_root -o $(IMAGE_NAME).iso 212 | endif 213 | rm -rf iso_root 214 | 215 | $(IMAGE_NAME).hdd: limine/limine kernel 216 | rm -f $(IMAGE_NAME).hdd 217 | dd if=/dev/zero bs=1M count=0 seek=64 of=$(IMAGE_NAME).hdd 218 | ifeq ($(ARCH),x86_64) 219 | PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 -m 1 220 | ./limine/limine bios-install $(IMAGE_NAME).hdd 221 | else 222 | PATH=$$PATH:/usr/sbin:/sbin sgdisk $(IMAGE_NAME).hdd -n 1:2048 -t 1:ef00 223 | endif 224 | mformat -i $(IMAGE_NAME).hdd@@1M 225 | mmd -i $(IMAGE_NAME).hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine 226 | mcopy -i $(IMAGE_NAME).hdd@@1M kernel/bin-$(ARCH)/kernel ::/boot 227 | mcopy -i $(IMAGE_NAME).hdd@@1M limine.conf ::/boot/limine 228 | ifeq ($(ARCH),x86_64) 229 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/limine-bios.sys ::/boot/limine 230 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTX64.EFI ::/EFI/BOOT 231 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTIA32.EFI ::/EFI/BOOT 232 | endif 233 | ifeq ($(ARCH),aarch64) 234 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTAA64.EFI ::/EFI/BOOT 235 | endif 236 | ifeq ($(ARCH),riscv64) 237 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTRISCV64.EFI ::/EFI/BOOT 238 | endif 239 | ifeq ($(ARCH),loongarch64) 240 | mcopy -i $(IMAGE_NAME).hdd@@1M limine/BOOTLOONGARCH64.EFI ::/EFI/BOOT 241 | endif 242 | 243 | .PHONY: clean 244 | clean: 245 | $(MAKE) -C kernel clean 246 | rm -rf iso_root $(IMAGE_NAME).iso $(IMAGE_NAME).hdd 247 | 248 | .PHONY: distclean 249 | distclean: 250 | $(MAKE) -C kernel distclean 251 | rm -rf iso_root *.iso *.hdd kernel-deps limine ovmf 252 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2023-2025 mintsuki and contributors. 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted. 5 | 6 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 7 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 8 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 9 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 10 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 11 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 12 | PERFORMANCE OF THIS SOFTWARE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Limine C++ Template 2 | 3 | This repository will demonstrate how to set up a basic kernel in C++ using Limine. 4 | 5 | ## How to use this? 6 | 7 | ### Dependencies 8 | 9 | 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. 10 | 11 | It is recommended to build this project using a standard UNIX-like system, using a Clang/LLVM toolchain capable of cross compilation. 12 | 13 | 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`. 14 | 15 | ### Architectural targets 16 | 17 | The `ARCH` make variable determines the target architecture to build the kernel and image for. 18 | 19 | The default `ARCH` is `x86_64`. Other options include: `aarch64`, `loongarch64`, and `riscv64`. 20 | 21 | ### Makefile targets 22 | 23 | Running `make all` will compile the kernel (from the `kernel/` directory) and then generate a bootable ISO image. 24 | 25 | 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. 26 | 27 | Running `make run` will build the kernel and a bootable ISO (equivalent to make all) and then run it using `qemu` (if installed). 28 | 29 | 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). 30 | 31 | For x86_64, the `run-bios` and `run-hdd-bios` targets are equivalent to their non `-bios` counterparts except that they boot `qemu` using the default SeaBIOS firmware instead of OVMF. 32 | -------------------------------------------------------------------------------- /kernel/.gitignore: -------------------------------------------------------------------------------- 1 | /compile_commands.json 2 | /.cache 3 | /freestnd-c-hdrs 4 | /freestnd-cxx-hdrs 5 | /cc-runtime 6 | /src/limine.h 7 | /bin-* 8 | /obj-* 9 | -------------------------------------------------------------------------------- /kernel/GNUmakefile: -------------------------------------------------------------------------------- 1 | # Nuke built-in rules and variables. 2 | MAKEFLAGS += -rR 3 | .SUFFIXES: 4 | 5 | # This is the name that our final executable will have. 6 | # Change as needed. 7 | override OUTPUT := kernel 8 | 9 | # Target architecture to build for. Default to x86_64. 10 | ARCH := x86_64 11 | 12 | # Install prefix; /usr/local is a good, standard default pick. 13 | PREFIX := /usr/local 14 | 15 | # Check if the architecture is supported. 16 | ifeq ($(filter $(ARCH),aarch64 loongarch64 riscv64 x86_64),) 17 | $(error Architecture $(ARCH) not supported) 18 | endif 19 | 20 | # User controllable C compiler command. 21 | CC := cc 22 | 23 | # User controllable C++ compiler command. 24 | CXX := c++ 25 | 26 | # User controllable C flags. 27 | CFLAGS := -g -O2 -pipe 28 | 29 | # User controllable C++ flags. We default to same as C flags. 30 | CXXFLAGS := $(CFLAGS) 31 | 32 | # User controllable C/C++ preprocessor flags. We set none by default. 33 | CPPFLAGS := 34 | 35 | ifeq ($(ARCH),x86_64) 36 | # User controllable nasm flags. 37 | NASMFLAGS := -F dwarf -g 38 | endif 39 | 40 | # User controllable linker flags. We set none by default. 41 | LDFLAGS := 42 | 43 | # Ensure the dependencies have been obtained. 44 | ifneq ($(shell ( test '$(MAKECMDGOALS)' = clean || test '$(MAKECMDGOALS)' = distclean ); echo $$?),0) 45 | ifeq ($(shell ( ! test -d freestnd-c-hdrs || ! test -d freestnd-cxx-hdrs || ! test -d cc-runtime || ! test -f src/limine.h ); echo $$?),0) 46 | $(error Please run the ./get-deps script first) 47 | endif 48 | endif 49 | 50 | # Check if CC is Clang. 51 | override CC_IS_CLANG := $(shell ! $(CC) --version 2>/dev/null | grep 'clang' >/dev/null 2>&1; echo $$?) 52 | 53 | # Check if CXX is Clang. 54 | override CXX_IS_CLANG := $(shell ! $(CXX) --version 2>/dev/null | grep 'clang' >/dev/null 2>&1; echo $$?) 55 | 56 | # Internal flags shared by both C and C++ compilers. 57 | override SHARED_FLAGS := \ 58 | -Wall \ 59 | -Wextra \ 60 | -nostdinc \ 61 | -ffreestanding \ 62 | -fno-stack-protector \ 63 | -fno-stack-check \ 64 | -fno-PIC \ 65 | -ffunction-sections \ 66 | -fdata-sections 67 | 68 | # Internal C/C++ preprocessor flags that should not be changed by the user. 69 | override CPPFLAGS_C := \ 70 | -I src \ 71 | -isystem freestnd-c-hdrs/$(ARCH)/include \ 72 | $(CPPFLAGS) \ 73 | -DLIMINE_API_REVISION=3 \ 74 | -MMD \ 75 | -MP 76 | 77 | # Internal C++ only preprocessor flags. 78 | override CPPFLAGS_CXX := \ 79 | -I src \ 80 | -isystem freestnd-c-hdrs/$(ARCH)/include \ 81 | -isystem freestnd-cxx-hdrs/$(ARCH)/include \ 82 | $(CPPFLAGS) \ 83 | -DLIMINE_API_REVISION=3 \ 84 | -MMD \ 85 | -MP 86 | 87 | ifeq ($(ARCH),x86_64) 88 | # Internal nasm flags that should not be changed by the user. 89 | override NASMFLAGS += \ 90 | -Wall 91 | endif 92 | 93 | # Architecture specific internal flags. 94 | ifeq ($(ARCH),x86_64) 95 | ifeq ($(CC_IS_CLANG),1) 96 | override CC += \ 97 | -target x86_64-unknown-none 98 | endif 99 | ifeq ($(CXX_IS_CLANG),1) 100 | override CXX += \ 101 | -target x86_64-unknown-none 102 | endif 103 | override SHARED_FLAGS += \ 104 | -m64 \ 105 | -march=x86-64 \ 106 | -mno-80387 \ 107 | -mno-mmx \ 108 | -mno-sse \ 109 | -mno-sse2 \ 110 | -mno-red-zone \ 111 | -mcmodel=kernel 112 | override LDFLAGS += \ 113 | -Wl,-m,elf_x86_64 114 | override NASMFLAGS += \ 115 | -f elf64 116 | endif 117 | ifeq ($(ARCH),aarch64) 118 | ifeq ($(CC_IS_CLANG),1) 119 | override CC += \ 120 | -target aarch64-unknown-none 121 | endif 122 | ifeq ($(CXX_IS_CLANG),1) 123 | override CXX += \ 124 | -target aarch64-unknown-none 125 | endif 126 | override SHARED_FLAGS += \ 127 | -mgeneral-regs-only 128 | override LDFLAGS += \ 129 | -Wl,-m,aarch64elf 130 | endif 131 | ifeq ($(ARCH),riscv64) 132 | ifeq ($(CC_IS_CLANG),1) 133 | override CC += \ 134 | -target riscv64-unknown-none 135 | override CFLAGS += \ 136 | -march=rv64imac 137 | else 138 | override CFLAGS += \ 139 | -march=rv64imac_zicsr_zifencei 140 | endif 141 | ifeq ($(CXX_IS_CLANG),1) 142 | override CXX += \ 143 | -target riscv64-unknown-none 144 | override CXXFLAGS += \ 145 | -march=rv64imac 146 | else 147 | override CXXFLAGS += \ 148 | -march=rv64imac_zicsr_zifencei 149 | endif 150 | override SHARED_FLAGS += \ 151 | -mabi=lp64 \ 152 | -mno-relax 153 | override LDFLAGS += \ 154 | -Wl,-m,elf64lriscv \ 155 | -Wl,--no-relax 156 | endif 157 | ifeq ($(ARCH),loongarch64) 158 | ifeq ($(CC_IS_CLANG),1) 159 | override CC += \ 160 | -target loongarch64-unknown-none 161 | endif 162 | ifeq ($(CXX_IS_CLANG),1) 163 | override CXX += \ 164 | -target loongarch64-unknown-none 165 | endif 166 | override SHARED_FLAGS += \ 167 | -march=loongarch64 \ 168 | -mabi=lp64s 169 | override LDFLAGS += \ 170 | -Wl,-m,elf64loongarch \ 171 | -Wl,--no-relax 172 | endif 173 | 174 | # Internal C flags that should not be changed by the user. 175 | override CFLAGS += \ 176 | -std=gnu11 \ 177 | $(SHARED_FLAGS) 178 | 179 | # Internal C++ flags that should not be changed by the user. 180 | override CXXFLAGS += \ 181 | -std=gnu++20 \ 182 | -fno-rtti \ 183 | -fno-exceptions \ 184 | $(SHARED_FLAGS) 185 | 186 | # Internal linker flags that should not be changed by the user. 187 | override LDFLAGS += \ 188 | -Wl,--build-id=none \ 189 | -nostdlib \ 190 | -static \ 191 | -z max-page-size=0x1000 \ 192 | -Wl,--gc-sections \ 193 | -T linker-$(ARCH).ld 194 | 195 | # Use "find" to glob all *.c, *.cpp, *.S, and *.asm files in the tree and obtain the 196 | # object and header dependency file names. 197 | override SRCFILES := $(shell find -L src cc-runtime/src -type f | LC_ALL=C sort) 198 | override CFILES := $(filter %.c,$(SRCFILES)) 199 | override CXXFILES := $(filter %.cpp,$(SRCFILES)) 200 | override ASFILES := $(filter %.S,$(SRCFILES)) 201 | ifeq ($(ARCH),x86_64) 202 | override NASMFILES := $(filter %.asm,$(SRCFILES)) 203 | endif 204 | override OBJ := $(addprefix obj-$(ARCH)/,$(CFILES:.c=.c.o) $(CXXFILES:.cpp=.cpp.o) $(ASFILES:.S=.S.o)) 205 | ifeq ($(ARCH),x86_64) 206 | override OBJ += $(addprefix obj-$(ARCH)/,$(NASMFILES:.asm=.asm.o)) 207 | endif 208 | override HEADER_DEPS := $(addprefix obj-$(ARCH)/,$(CFILES:.c=.c.d) $(CXXFILES:.cpp=.cpp.d) $(ASFILES:.S=.S.d)) 209 | 210 | # Default target. This must come first, before header dependencies. 211 | .PHONY: all 212 | all: bin-$(ARCH)/$(OUTPUT) 213 | 214 | # Include header dependencies. 215 | -include $(HEADER_DEPS) 216 | 217 | # Link rules for the final executable. 218 | bin-$(ARCH)/$(OUTPUT): GNUmakefile linker-$(ARCH).ld $(OBJ) 219 | mkdir -p "$$(dirname $@)" 220 | $(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJ) -o $@ 221 | 222 | # Compilation rules for *.c files. 223 | obj-$(ARCH)/%.c.o: %.c GNUmakefile 224 | mkdir -p "$$(dirname $@)" 225 | $(CC) $(CFLAGS) $(CPPFLAGS_C) -c $< -o $@ 226 | 227 | # Compilation rules for *.cpp files. 228 | obj-$(ARCH)/%.cpp.o: %.cpp GNUmakefile 229 | mkdir -p "$$(dirname $@)" 230 | $(CXX) $(CXXFLAGS) $(CPPFLAGS_CXX) -c $< -o $@ 231 | 232 | # Compilation rules for *.S files. 233 | obj-$(ARCH)/%.S.o: %.S GNUmakefile 234 | mkdir -p "$$(dirname $@)" 235 | $(CC) $(CFLAGS) $(CPPFLAGS_C) -c $< -o $@ 236 | 237 | ifeq ($(ARCH),x86_64) 238 | # Compilation rules for *.asm (nasm) files. 239 | obj-$(ARCH)/%.asm.o: %.asm GNUmakefile 240 | mkdir -p "$$(dirname $@)" 241 | nasm $(NASMFLAGS) $< -o $@ 242 | endif 243 | 244 | # Remove object files and the final executable. 245 | .PHONY: clean 246 | clean: 247 | rm -rf bin-$(ARCH) obj-$(ARCH) 248 | 249 | # Remove everything built and generated including downloaded dependencies. 250 | .PHONY: distclean 251 | distclean: 252 | rm -rf bin-* obj-* freestnd-c-hdrs freestnd-cxx-hdrs cc-runtime src/limine.h 253 | 254 | # Install the final built executable to its final on-root location. 255 | .PHONY: install 256 | install: all 257 | install -d "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)" 258 | install -m 644 bin-$(ARCH)/$(OUTPUT) "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)/$(OUTPUT)-$(ARCH)" 259 | 260 | # Try to undo whatever the "install" target did. 261 | .PHONY: uninstall 262 | uninstall: 263 | rm -f "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)/$(OUTPUT)-$(ARCH)" 264 | -rmdir "$(DESTDIR)$(PREFIX)/share/$(OUTPUT)" 265 | -------------------------------------------------------------------------------- /kernel/get-deps: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -ex 4 | 5 | srcdir="$(dirname "$0")" 6 | test -z "$srcdir" && srcdir=. 7 | 8 | cd "$srcdir" 9 | 10 | clone_repo_commit() { 11 | if test -d "$2/.git"; then 12 | git -C "$2" reset --hard 13 | git -C "$2" clean -fd 14 | if ! git -C "$2" checkout $3; then 15 | rm -rf "$2" 16 | fi 17 | else 18 | if test -d "$2"; then 19 | set +x 20 | echo "error: '$2' is not a Git repository" 21 | exit 1 22 | fi 23 | fi 24 | if ! test -d "$2"; then 25 | git clone $1 "$2" 26 | if ! git -C "$2" checkout $3; then 27 | rm -rf "$2" 28 | exit 1 29 | fi 30 | fi 31 | } 32 | 33 | download_by_hash() { 34 | DOWNLOAD_COMMAND="curl -Lo" 35 | if ! command -v "${DOWNLOAD_COMMAND%% *}" >/dev/null 2>&1; then 36 | DOWNLOAD_COMMAND="wget -O" 37 | if ! command -v "${DOWNLOAD_COMMAND%% *}" >/dev/null 2>&1; then 38 | set +x 39 | echo "error: Neither curl nor wget found" 40 | exit 1 41 | fi 42 | fi 43 | SHA256_COMMAND="sha256sum" 44 | if ! command -v "${SHA256_COMMAND%% *}" >/dev/null 2>&1; then 45 | SHA256_COMMAND="sha256" 46 | if ! command -v "${SHA256_COMMAND%% *}" >/dev/null 2>&1; then 47 | set +x 48 | echo "error: Cannot find sha256(sum) command" 49 | exit 1 50 | fi 51 | fi 52 | if ! test -f "$2" || ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then 53 | rm -f "$2" 54 | mkdir -p "$2" && rm -rf "$2" 55 | $DOWNLOAD_COMMAND "$2" $1 56 | if ! $SHA256_COMMAND "$2" | grep $3 >/dev/null 2>&1; then 57 | set +x 58 | echo "error: Cannot download file '$2' by hash" 59 | echo "incorrect hash:" 60 | $SHA256_COMMAND "$2" 61 | rm -f "$2" 62 | exit 1 63 | fi 64 | fi 65 | } 66 | 67 | clone_repo_commit \ 68 | https://codeberg.org/osdev/freestnd-c-hdrs.git \ 69 | freestnd-c-hdrs \ 70 | 4039f438fb1dc1064d8e98f70e1cf122f91b763b 71 | 72 | clone_repo_commit \ 73 | https://codeberg.org/osdev/freestnd-cxx-hdrs.git \ 74 | freestnd-cxx-hdrs \ 75 | 85096df5361a4d7ef2ce46947e555ec248c2858e 76 | 77 | clone_repo_commit \ 78 | https://codeberg.org/osdev/cc-runtime.git \ 79 | cc-runtime \ 80 | dae79833b57a01b9fd3e359ee31def69f5ae899b 81 | 82 | download_by_hash \ 83 | https://github.com/limine-bootloader/limine/raw/4687a182be23939c2d9f15db970382dc353ed956/limine.h \ 84 | src/limine.h \ 85 | 6879e626f34c1be25ac2f72bf43b083fc2b53887280bb0fcdaee790e258c6974 86 | -------------------------------------------------------------------------------- /kernel/linker-aarch64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want an aarch64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-littleaarch64) 3 | 4 | /* We want the symbol kmain to be our entry point */ 5 | ENTRY(kmain) 6 | 7 | /* Define the program headers we want so the bootloader gives us the right */ 8 | /* MMU permissions; this also allows us to exert more control over the linking */ 9 | /* process. */ 10 | PHDRS 11 | { 12 | limine_requests PT_LOAD; 13 | text PT_LOAD; 14 | rodata PT_LOAD; 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | /* Define a section to contain the Limine requests and assign it to its own PHDR */ 27 | .limine_requests : { 28 | KEEP(*(.limine_requests_start)) 29 | KEEP(*(.limine_requests)) 30 | KEEP(*(.limine_requests_end)) 31 | } :limine_requests 32 | 33 | /* Move to the next memory page for .text */ 34 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 35 | 36 | .text : { 37 | *(.text .text.*) 38 | } :text 39 | 40 | /* Move to the next memory page for .rodata */ 41 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 42 | 43 | .rodata : { 44 | *(.rodata .rodata.*) 45 | } :rodata 46 | 47 | /* C++ is a language that allows for global constructors. In order to obtain the */ 48 | /* address of the ".init_array" section we need to define a symbol for it. */ 49 | .init_array : { 50 | __init_array = .; 51 | KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 52 | KEEP(*(.init_array .ctors)) 53 | __init_array_end = .; 54 | } :rodata 55 | 56 | /* Move to the next memory page for .data */ 57 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 58 | 59 | .data : { 60 | *(.data .data.*) 61 | } :data 62 | 63 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 64 | /* unnecessary zeros will be written to the binary. */ 65 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 66 | /* above this. */ 67 | .bss : { 68 | *(.bss .bss.*) 69 | *(COMMON) 70 | } :data 71 | 72 | /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ 73 | /DISCARD/ : { 74 | *(.eh_frame*) 75 | *(.note .note.*) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /kernel/linker-loongarch64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want a loongarch64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-loongarch) 3 | 4 | /* We want the symbol kmain to be our entry point */ 5 | ENTRY(kmain) 6 | 7 | /* Define the program headers we want so the bootloader gives us the right */ 8 | /* MMU permissions; this also allows us to exert more control over the linking */ 9 | /* process. */ 10 | PHDRS 11 | { 12 | limine_requests PT_LOAD; 13 | text PT_LOAD; 14 | rodata PT_LOAD; 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | /* Define a section to contain the Limine requests and assign it to its own PHDR */ 27 | .limine_requests : { 28 | KEEP(*(.limine_requests_start)) 29 | KEEP(*(.limine_requests)) 30 | KEEP(*(.limine_requests_end)) 31 | } :limine_requests 32 | 33 | /* Move to the next memory page for .text */ 34 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 35 | 36 | .text : { 37 | *(.text .text.*) 38 | } :text 39 | 40 | /* Move to the next memory page for .rodata */ 41 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 42 | 43 | .rodata : { 44 | *(.rodata .rodata.*) 45 | } :rodata 46 | 47 | /* C++ is a language that allows for global constructors. In order to obtain the */ 48 | /* address of the ".init_array" section we need to define a symbol for it. */ 49 | .init_array : { 50 | __init_array = .; 51 | KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 52 | KEEP(*(.init_array .ctors)) 53 | __init_array_end = .; 54 | } :rodata 55 | 56 | /* Move to the next memory page for .data */ 57 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 58 | 59 | .data : { 60 | *(.data .data.*) 61 | } :data 62 | 63 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 64 | /* unnecessary zeros will be written to the binary. */ 65 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 66 | /* above this. */ 67 | .bss : { 68 | *(.bss .bss.*) 69 | *(COMMON) 70 | } :data 71 | 72 | /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ 73 | /DISCARD/ : { 74 | *(.eh_frame*) 75 | *(.note .note.*) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /kernel/linker-riscv64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want a riscv64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-littleriscv) 3 | 4 | /* We want the symbol kmain to be our entry point */ 5 | ENTRY(kmain) 6 | 7 | /* Define the program headers we want so the bootloader gives us the right */ 8 | /* MMU permissions; this also allows us to exert more control over the linking */ 9 | /* process. */ 10 | PHDRS 11 | { 12 | limine_requests PT_LOAD; 13 | text PT_LOAD; 14 | rodata PT_LOAD; 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | /* Define a section to contain the Limine requests and assign it to its own PHDR */ 27 | .limine_requests : { 28 | KEEP(*(.limine_requests_start)) 29 | KEEP(*(.limine_requests)) 30 | KEEP(*(.limine_requests_end)) 31 | } :limine_requests 32 | 33 | /* Move to the next memory page for .text */ 34 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 35 | 36 | .text : { 37 | *(.text .text.*) 38 | } :text 39 | 40 | /* Move to the next memory page for .rodata */ 41 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 42 | 43 | .rodata : { 44 | *(.rodata .rodata.*) 45 | } :rodata 46 | 47 | /* C++ is a language that allows for global constructors. In order to obtain the */ 48 | /* address of the ".init_array" section we need to define a symbol for it. */ 49 | .init_array : { 50 | __init_array = .; 51 | KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 52 | KEEP(*(.init_array .ctors)) 53 | __init_array_end = .; 54 | } :rodata 55 | 56 | /* Move to the next memory page for .data */ 57 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 58 | 59 | .data : { 60 | *(.data .data.*) 61 | *(.sdata .sdata.*) 62 | } :data 63 | 64 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 65 | /* unnecessary zeros will be written to the binary. */ 66 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 67 | /* above this. */ 68 | .bss : { 69 | *(.sbss .sbss.*) 70 | *(.bss .bss.*) 71 | *(COMMON) 72 | } :data 73 | 74 | /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ 75 | /DISCARD/ : { 76 | *(.eh_frame*) 77 | *(.note .note.*) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /kernel/linker-x86_64.ld: -------------------------------------------------------------------------------- 1 | /* Tell the linker that we want an x86_64 ELF64 output file */ 2 | OUTPUT_FORMAT(elf64-x86-64) 3 | 4 | /* We want the symbol kmain to be our entry point */ 5 | ENTRY(kmain) 6 | 7 | /* Define the program headers we want so the bootloader gives us the right */ 8 | /* MMU permissions; this also allows us to exert more control over the linking */ 9 | /* process. */ 10 | PHDRS 11 | { 12 | limine_requests PT_LOAD; 13 | text PT_LOAD; 14 | rodata PT_LOAD; 15 | data PT_LOAD; 16 | } 17 | 18 | SECTIONS 19 | { 20 | /* We want to be placed in the topmost 2GiB of the address space, for optimisations */ 21 | /* and because that is what the Limine spec mandates. */ 22 | /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */ 23 | /* that is the beginning of the region. */ 24 | . = 0xffffffff80000000; 25 | 26 | /* Define a section to contain the Limine requests and assign it to its own PHDR */ 27 | .limine_requests : { 28 | KEEP(*(.limine_requests_start)) 29 | KEEP(*(.limine_requests)) 30 | KEEP(*(.limine_requests_end)) 31 | } :limine_requests 32 | 33 | /* Move to the next memory page for .text */ 34 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 35 | 36 | .text : { 37 | *(.text .text.*) 38 | } :text 39 | 40 | /* Move to the next memory page for .rodata */ 41 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 42 | 43 | .rodata : { 44 | *(.rodata .rodata.*) 45 | } :rodata 46 | 47 | /* C++ is a language that allows for global constructors. In order to obtain the */ 48 | /* address of the ".init_array" section we need to define a symbol for it. */ 49 | .init_array : { 50 | __init_array = .; 51 | KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 52 | KEEP(*(.init_array .ctors)) 53 | __init_array_end = .; 54 | } :rodata 55 | 56 | /* Move to the next memory page for .data */ 57 | . = ALIGN(CONSTANT(MAXPAGESIZE)); 58 | 59 | .data : { 60 | *(.data .data.*) 61 | } :data 62 | 63 | /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */ 64 | /* unnecessary zeros will be written to the binary. */ 65 | /* If you need, for example, .init_array and .fini_array, those should be placed */ 66 | /* above this. */ 67 | .bss : { 68 | *(.bss .bss.*) 69 | *(COMMON) 70 | } :data 71 | 72 | /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ 73 | /DISCARD/ : { 74 | *(.eh_frame*) 75 | *(.note .note.*) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /kernel/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Set the base revision to 3, this is recommended as this is the latest 6 | // base revision described by the Limine boot protocol specification. 7 | // See specification for further info. 8 | 9 | namespace { 10 | 11 | __attribute__((used, section(".limine_requests"))) 12 | volatile LIMINE_BASE_REVISION(3); 13 | 14 | } 15 | 16 | // The Limine requests can be placed anywhere, but it is important that 17 | // the compiler does not optimise them away, so, usually, they should 18 | // be made volatile or equivalent, _and_ they should be accessed at least 19 | // once or marked as used with the "used" attribute as done here. 20 | 21 | namespace { 22 | 23 | __attribute__((used, section(".limine_requests"))) 24 | volatile limine_framebuffer_request framebuffer_request = { 25 | .id = LIMINE_FRAMEBUFFER_REQUEST, 26 | .revision = 0, 27 | .response = nullptr 28 | }; 29 | 30 | } 31 | 32 | // Finally, define the start and end markers for the Limine requests. 33 | // These can also be moved anywhere, to any .cpp file, as seen fit. 34 | 35 | namespace { 36 | 37 | __attribute__((used, section(".limine_requests_start"))) 38 | volatile LIMINE_REQUESTS_START_MARKER; 39 | 40 | __attribute__((used, section(".limine_requests_end"))) 41 | volatile LIMINE_REQUESTS_END_MARKER; 42 | 43 | } 44 | 45 | // GCC and Clang reserve the right to generate calls to the following 46 | // 4 functions even if they are not directly called. 47 | // Implement them as the C specification mandates. 48 | // DO NOT remove or rename these functions, or stuff will eventually break! 49 | // They CAN be moved to a different .cpp file. 50 | 51 | extern "C" { 52 | 53 | void *memcpy(void *__restrict dest, const void *__restrict src, std::size_t n) { 54 | std::uint8_t *__restrict pdest = static_cast(dest); 55 | const std::uint8_t *__restrict psrc = static_cast(src); 56 | 57 | for (std::size_t i = 0; i < n; i++) { 58 | pdest[i] = psrc[i]; 59 | } 60 | 61 | return dest; 62 | } 63 | 64 | void *memset(void *s, int c, std::size_t n) { 65 | std::uint8_t *p = static_cast(s); 66 | 67 | for (std::size_t i = 0; i < n; i++) { 68 | p[i] = static_cast(c); 69 | } 70 | 71 | return s; 72 | } 73 | 74 | void *memmove(void *dest, const void *src, std::size_t n) { 75 | std::uint8_t *pdest = static_cast(dest); 76 | const std::uint8_t *psrc = static_cast(src); 77 | 78 | if (src > dest) { 79 | for (std::size_t i = 0; i < n; i++) { 80 | pdest[i] = psrc[i]; 81 | } 82 | } else if (src < dest) { 83 | for (std::size_t i = n; i > 0; i--) { 84 | pdest[i-1] = psrc[i-1]; 85 | } 86 | } 87 | 88 | return dest; 89 | } 90 | 91 | int memcmp(const void *s1, const void *s2, std::size_t n) { 92 | const std::uint8_t *p1 = static_cast(s1); 93 | const std::uint8_t *p2 = static_cast(s2); 94 | 95 | for (std::size_t i = 0; i < n; i++) { 96 | if (p1[i] != p2[i]) { 97 | return p1[i] < p2[i] ? -1 : 1; 98 | } 99 | } 100 | 101 | return 0; 102 | } 103 | 104 | } 105 | 106 | // Halt and catch fire function. 107 | namespace { 108 | 109 | void hcf() { 110 | for (;;) { 111 | #if defined (__x86_64__) 112 | asm ("hlt"); 113 | #elif defined (__aarch64__) || defined (__riscv) 114 | asm ("wfi"); 115 | #elif defined (__loongarch64) 116 | asm ("idle 0"); 117 | #endif 118 | } 119 | } 120 | 121 | } 122 | 123 | // The following stubs are required by the Itanium C++ ABI (the one we use, 124 | // regardless of the "Itanium" nomenclature). 125 | // Like the memory functions above, these stubs can be moved to a different .cpp file, 126 | // but should not be removed, unless you know what you are doing. 127 | extern "C" { 128 | int __cxa_atexit(void (*)(void *), void *, void *) { return 0; } 129 | void __cxa_pure_virtual() { hcf(); } 130 | void *__dso_handle; 131 | } 132 | 133 | // Extern declarations for global constructors array. 134 | extern void (*__init_array[])(); 135 | extern void (*__init_array_end[])(); 136 | 137 | // The following will be our kernel's entry point. 138 | // If renaming kmain() to something else, make sure to change the 139 | // linker script accordingly. 140 | extern "C" void kmain() { 141 | // Ensure the bootloader actually understands our base revision (see spec). 142 | if (LIMINE_BASE_REVISION_SUPPORTED == false) { 143 | hcf(); 144 | } 145 | 146 | // Call global constructors. 147 | for (std::size_t i = 0; &__init_array[i] != __init_array_end; i++) { 148 | __init_array[i](); 149 | } 150 | 151 | // Ensure we got a framebuffer. 152 | if (framebuffer_request.response == nullptr 153 | || framebuffer_request.response->framebuffer_count < 1) { 154 | hcf(); 155 | } 156 | 157 | // Fetch the first framebuffer. 158 | limine_framebuffer *framebuffer = framebuffer_request.response->framebuffers[0]; 159 | 160 | // Note: we assume the framebuffer model is RGB with 32-bit pixels. 161 | for (std::size_t i = 0; i < 100; i++) { 162 | volatile std::uint32_t *fb_ptr = static_cast(framebuffer->address); 163 | fb_ptr[i * (framebuffer->pitch / 4) + i] = 0xffffff; 164 | } 165 | 166 | // We're done, just hang... 167 | hcf(); 168 | } 169 | -------------------------------------------------------------------------------- /limine.conf: -------------------------------------------------------------------------------- 1 | # Timeout in seconds that Limine will use before automatically booting. 2 | timeout: 3 3 | 4 | # The entry name that will be displayed in the boot menu. 5 | /Limine Template 6 | # We use the Limine boot protocol. 7 | protocol: limine 8 | 9 | # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located. 10 | path: boot():/boot/kernel 11 | --------------------------------------------------------------------------------