···11-# Created by https://www.toptal.com/developers/gitignore/api/rust
22-# Edit at https://www.toptal.com/developers/gitignore?templates=rust
33-44-### Rust ###
55-# Generated by Cargo
66-# will have compiled files and executables
77-debug/
88-target/
99-1010-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
1111-# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
1212-Cargo.lock
1313-1414-# These are backup files generated by rustfmt
1515-**/*.rs.bk
1616-1717-# MSVC Windows builds of rustc generate these, which store debugging information
1818-*.pdb
1919-2020-# End of https://www.toptal.com/developers/gitignore/api/rust
2121-2222-2323-# Added by cargo
2424-2525-/target
2626-2727-2828-# Added by cargo
2929-#
3030-# already existing elements were commented out
3131-3232-#/target
3333-3434-3535-# Added by cargo
3636-#
3737-# already existing elements were commented out
3838-3939-#/target
4040-4141-4242-# Added by cargo
4343-#
4444-# already existing elements were commented out
4545-4646-#/target
4747-4848-4949-# Added by cargo
5050-#
5151-# already existing elements were commented out
5252-5353-#/target
11+/limine
22+/ovmf
33+*.iso
44+*.hdd
···11+Copyright (C) 2023-2024 mintsuki and contributors.
22+33+Permission to use, copy, modify, and/or distribute this software for any
44+purpose with or without fee is hereby granted.
55+66+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
77+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
88+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
99+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1010+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1111+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1212+PERFORMANCE OF THIS SOFTWARE.
+33
README.md
···11+# Limine Rust Template
22+33+This repository will demonstrate how to set up a basic kernel in Rust using Limine.
44+55+## How to use this?
66+77+### Dependencies
88+99+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.
1010+1111+All `make all*` targets depend on Rust.
1212+1313+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`.
1414+1515+### Architectural targets
1616+1717+The `KARCH` make variable determines the target architecture to build the kernel and image for.
1818+1919+The default `KARCH` is `x86_64`. Other options include: `aarch64`, `riscv64`, and `loongarch64`.
2020+2121+Other architectures will need to be enabled in kernel/rust-toolchain.toml
2222+2323+### Makefile targets
2424+2525+Running `make all` will compile the kernel (from the `kernel/` directory) and then generate a bootable ISO image.
2626+2727+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.
2828+2929+Running `make run` will build the kernel and a bootable ISO (equivalent to make all) and then run it using `qemu` (if installed).
3030+3131+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).
3232+3333+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.
···11+# Nuke built-in rules and variables.
22+MAKEFLAGS += -rR
33+.SUFFIXES:
44+55+# This is the name that our final executable will have.
66+# Change as needed.
77+override OUTPUT := kernel
88+99+# Convenience macro to reliably declare user overridable variables.
1010+override USER_VARIABLE = $(if $(filter $(origin $(1)),default undefined),$(eval override $(1) := $(2)))
1111+1212+# Target architecture to build for. Default to x86_64.
1313+$(call USER_VARIABLE,KARCH,x86_64)
1414+1515+ifeq ($(RUST_TARGET),)
1616+ override RUST_TARGET := $(KARCH)-unknown-none
1717+ ifeq ($(KARCH),riscv64)
1818+ override RUST_TARGET := riscv64gc-unknown-none-elf
1919+ endif
2020+endif
2121+2222+ifeq ($(RUST_PROFILE),)
2323+ override RUST_PROFILE := dev
2424+endif
2525+2626+override RUST_PROFILE_SUBDIR := $(RUST_PROFILE)
2727+ifeq ($(RUST_PROFILE),dev)
2828+ override RUST_PROFILE_SUBDIR := debug
2929+endif
3030+3131+# Default target.
3232+.PHONY: all
3333+all:
3434+ RUSTFLAGS="-C relocation-model=static" cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE)
3535+ cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/$$(cd target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR) && find -maxdepth 1 -perm -111 -type f) kernel
3636+3737+# Remove object files and the final executable.
3838+.PHONY: clean
3939+clean:
4040+ cargo clean
4141+ rm -rf kernel
4242+4343+.PHONY: distclean
4444+distclean: clean
+7
kernel/build.rs
···11+fn main() {
22+ let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
33+ // Tell cargo to pass the linker script to the linker..
44+ println!("cargo:rustc-link-arg=-Tlinker-{arch}.ld");
55+ // ..and to re-run if it changes.
66+ println!("cargo:rerun-if-changed=linker-{arch}.ld");
77+}
+63
kernel/linker-aarch64.ld
···11+/* Tell the linker that we want an aarch64 ELF64 output file */
22+OUTPUT_FORMAT(elf64-littleaarch64)
33+44+/* We want the symbol kmain to be our entry point */
55+ENTRY(kmain)
66+77+/* Define the program headers we want so the bootloader gives us the right */
88+/* MMU permissions; this also allows us to exert more control over the linking */
99+/* process. */
1010+PHDRS
1111+{
1212+ text PT_LOAD;
1313+ rodata PT_LOAD;
1414+ data PT_LOAD;
1515+}
1616+1717+SECTIONS
1818+{
1919+ /* We want to be placed in the topmost 2GiB of the address space, for optimisations */
2020+ /* and because that is what the Limine spec mandates. */
2121+ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
2222+ /* that is the beginning of the region. */
2323+ . = 0xffffffff80000000;
2424+2525+ .text : {
2626+ *(.text .text.*)
2727+ } :text
2828+2929+ /* Move to the next memory page for .rodata */
3030+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3131+3232+ .rodata : {
3333+ *(.rodata .rodata.*)
3434+ } :rodata
3535+3636+ /* Move to the next memory page for .data */
3737+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3838+3939+ .data : {
4040+ *(.data .data.*)
4141+4242+ /* Place the sections that contain the Limine requests as part of the .data */
4343+ /* output section. */
4444+ KEEP(*(.requests_start_marker))
4545+ KEEP(*(.requests))
4646+ KEEP(*(.requests_end_marker))
4747+ } :data
4848+4949+ /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
5050+ /* unnecessary zeros will be written to the binary. */
5151+ /* If you need, for example, .init_array and .fini_array, those should be placed */
5252+ /* above this. */
5353+ .bss : {
5454+ *(.bss .bss.*)
5555+ *(COMMON)
5656+ } :data
5757+5858+ /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
5959+ /DISCARD/ : {
6060+ *(.eh_frame*)
6161+ *(.note .note.*)
6262+ }
6363+}
+63
kernel/linker-loongarch64.ld
···11+/* Tell the linker that we want a loongarch64 ELF64 output file */
22+OUTPUT_FORMAT(elf64-loongarch)
33+44+/* We want the symbol kmain to be our entry point */
55+ENTRY(kmain)
66+77+/* Define the program headers we want so the bootloader gives us the right */
88+/* MMU permissions; this also allows us to exert more control over the linking */
99+/* process. */
1010+PHDRS
1111+{
1212+ text PT_LOAD;
1313+ rodata PT_LOAD;
1414+ data PT_LOAD;
1515+}
1616+1717+SECTIONS
1818+{
1919+ /* We want to be placed in the topmost 2GiB of the address space, for optimisations */
2020+ /* and because that is what the Limine spec mandates. */
2121+ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
2222+ /* that is the beginning of the region. */
2323+ . = 0xffffffff80000000;
2424+2525+ .text : {
2626+ *(.text .text.*)
2727+ } :text
2828+2929+ /* Move to the next memory page for .rodata */
3030+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3131+3232+ .rodata : {
3333+ *(.rodata .rodata.*)
3434+ } :rodata
3535+3636+ /* Move to the next memory page for .data */
3737+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3838+3939+ .data : {
4040+ *(.data .data.*)
4141+4242+ /* Place the sections that contain the Limine requests as part of the .data */
4343+ /* output section. */
4444+ KEEP(*(.requests_start_marker))
4545+ KEEP(*(.requests))
4646+ KEEP(*(.requests_end_marker))
4747+ } :data
4848+4949+ /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
5050+ /* unnecessary zeros will be written to the binary. */
5151+ /* If you need, for example, .init_array and .fini_array, those should be placed */
5252+ /* above this. */
5353+ .bss : {
5454+ *(.bss .bss.*)
5555+ *(COMMON)
5656+ } :data
5757+5858+ /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
5959+ /DISCARD/ : {
6060+ *(.eh_frame*)
6161+ *(.note .note.*)
6262+ }
6363+}
+66
kernel/linker-riscv64.ld
···11+/* Tell the linker that we want a riscv64 ELF64 output file */
22+OUTPUT_FORMAT(elf64-littleriscv)
33+44+/* We want the symbol kmain to be our entry point */
55+ENTRY(kmain)
66+77+/* Define the program headers we want so the bootloader gives us the right */
88+/* MMU permissions; this also allows us to exert more control over the linking */
99+/* process. */
1010+PHDRS
1111+{
1212+ text PT_LOAD;
1313+ rodata PT_LOAD;
1414+ data PT_LOAD;
1515+}
1616+1717+SECTIONS
1818+{
1919+ /* We want to be placed in the topmost 2GiB of the address space, for optimisations */
2020+ /* and because that is what the Limine spec mandates. */
2121+ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
2222+ /* that is the beginning of the region. */
2323+ . = 0xffffffff80000000;
2424+2525+ .text : {
2626+ *(.text .text.*)
2727+ } :text
2828+2929+ /* Move to the next memory page for .rodata */
3030+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3131+3232+ .rodata : {
3333+ *(.rodata .rodata.*)
3434+ } :rodata
3535+3636+ /* Move to the next memory page for .data */
3737+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3838+3939+ .data : {
4040+ *(.data .data.*)
4141+4242+ /* Place the sections that contain the Limine requests as part of the .data */
4343+ /* output section. */
4444+ KEEP(*(.requests_start_marker))
4545+ KEEP(*(.requests))
4646+ KEEP(*(.requests_end_marker))
4747+4848+ *(.sdata .sdata.*)
4949+ } :data
5050+5151+ /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
5252+ /* unnecessary zeros will be written to the binary. */
5353+ /* If you need, for example, .init_array and .fini_array, those should be placed */
5454+ /* above this. */
5555+ .bss : {
5656+ *(.sbss .sbss.*)
5757+ *(.bss .bss.*)
5858+ *(COMMON)
5959+ } :data
6060+6161+ /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
6262+ /DISCARD/ : {
6363+ *(.eh_frame*)
6464+ *(.note .note.*)
6565+ }
6666+}
+63
kernel/linker-x86_64.ld
···11+/* Tell the linker that we want an x86_64 ELF64 output file */
22+OUTPUT_FORMAT(elf64-x86-64)
33+44+/* We want the symbol kmain to be our entry point */
55+ENTRY(kmain)
66+77+/* Define the program headers we want so the bootloader gives us the right */
88+/* MMU permissions; this also allows us to exert more control over the linking */
99+/* process. */
1010+PHDRS
1111+{
1212+ text PT_LOAD;
1313+ rodata PT_LOAD;
1414+ data PT_LOAD;
1515+}
1616+1717+SECTIONS
1818+{
1919+ /* We want to be placed in the topmost 2GiB of the address space, for optimisations */
2020+ /* and because that is what the Limine spec mandates. */
2121+ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
2222+ /* that is the beginning of the region. */
2323+ . = 0xffffffff80000000;
2424+2525+ .text : {
2626+ *(.text .text.*)
2727+ } :text
2828+2929+ /* Move to the next memory page for .rodata */
3030+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3131+3232+ .rodata : {
3333+ *(.rodata .rodata.*)
3434+ } :rodata
3535+3636+ /* Move to the next memory page for .data */
3737+ . = ALIGN(CONSTANT(MAXPAGESIZE));
3838+3939+ .data : {
4040+ *(.data .data.*)
4141+4242+ /* Place the sections that contain the Limine requests as part of the .data */
4343+ /* output section. */
4444+ KEEP(*(.requests_start_marker))
4545+ KEEP(*(.requests))
4646+ KEEP(*(.requests_end_marker))
4747+ } :data
4848+4949+ /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
5050+ /* unnecessary zeros will be written to the binary. */
5151+ /* If you need, for example, .init_array and .fini_array, those should be placed */
5252+ /* above this. */
5353+ .bss : {
5454+ *(.bss .bss.*)
5555+ *(COMMON)
5656+ } :data
5757+5858+ /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
5959+ /DISCARD/ : {
6060+ *(.eh_frame*)
6161+ *(.note .note.*)
6262+ }
6363+}
···11+#![no_std]
22+#![no_main]
33+44+use core::arch::asm;
55+66+use limine::BaseRevision;
77+use limine::request::{FramebufferRequest, RequestsEndMarker, RequestsStartMarker};
88+99+/// Sets the base revision to the latest revision supported by the crate.
1010+/// See specification for further info.
1111+/// Be sure to mark all limine requests with #[used], otherwise they may be removed by the compiler.
1212+#[used]
1313+// The .requests section allows limine to find the requests faster and more safely.
1414+#[unsafe(link_section = ".requests")]
1515+static BASE_REVISION: BaseRevision = BaseRevision::new();
1616+1717+#[used]
1818+#[unsafe(link_section = ".requests")]
1919+static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
2020+2121+/// Define the stand and end markers for Limine requests.
2222+#[used]
2323+#[unsafe(link_section = ".requests_start_marker")]
2424+static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
2525+#[used]
2626+#[unsafe(link_section = ".requests_end_marker")]
2727+static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
2828+2929+#[unsafe(no_mangle)]
3030+unsafe extern "C" fn kmain() -> ! {
3131+ // All limine requests must also be referenced in a called function, otherwise they may be
3232+ // removed by the linker.
3333+ assert!(BASE_REVISION.is_supported());
3434+3535+ if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() {
3636+ if let Some(framebuffer) = framebuffer_response.framebuffers().next() {
3737+ for i in 0..100_u64 {
3838+ // Calculate the pixel offset using the framebuffer information we obtained above.
3939+ // We skip `i` scanlines (pitch is provided in bytes) and add `i * 4` to skip `i` pixels forward.
4040+ let pixel_offset = i * framebuffer.pitch() + i * 4;
4141+4242+ // Write 0xFFFFFFFF to the provided pixel offset to fill it white.
4343+ unsafe {
4444+ framebuffer
4545+ .addr()
4646+ .add(pixel_offset as usize)
4747+ .cast::<u32>()
4848+ .write(0xFFFFFFFF)
4949+ };
5050+ }
5151+ }
5252+ }
5353+5454+ hcf();
5555+}
5656+5757+#[panic_handler]
5858+fn rust_panic(_info: &core::panic::PanicInfo) -> ! {
5959+ hcf();
6060+}
6161+6262+fn hcf() -> ! {
6363+ loop {
6464+ unsafe {
6565+ #[cfg(target_arch = "x86_64")]
6666+ asm!("hlt");
6767+ #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
6868+ asm!("wfi");
6969+ #[cfg(target_arch = "loongarch64")]
7070+ asm!("idle 0");
7171+ }
7272+ }
7373+}
+10
limine.conf
···11+# Timeout in seconds that Limine will use before automatically booting.
22+timeout: 3
33+44+# The entry name that will be displayed in the boot menu.
55+/Limine Template
66+ # We use the Limine boot protocol.
77+ protocol: limine
88+99+ # Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located.
1010+ kernel_path: boot():/boot/kernel