From 8dc3839ba45e904a5edce8f751a1244747f4a4fd Mon Sep 17 00:00:00 2001 From: Zoe Date: Tue, 18 Aug 2026 07:16:11 -0500 Subject: [PATCH] Initial commit --- .cargo/config.toml | 12 ++ .gitignore | 11 ++ Cargo.lock | 16 ++ Cargo.toml | 7 + Makefile | 186 +++++++++++++++++++++++ README.md | 3 + limine.conf | 7 + rust-toolchain.toml | 5 + src/arch/mod.rs | 5 + src/arch/x86_64/linker.ld | 77 ++++++++++ src/arch/x86_64/mod.rs | 1 + src/arch/x86_64/port.rs | 105 +++++++++++++ src/arch/x86_64/x86_64-unknown-none.json | 20 +++ src/boot/limine.rs | 22 +++ src/boot/mod.rs | 1 + src/drivers/mod.rs | 1 + src/drivers/serial.rs | 179 ++++++++++++++++++++++ src/main.rs | 66 ++++++++ 18 files changed, 724 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 README.md create mode 100644 limine.conf create mode 100644 rust-toolchain.toml create mode 100644 src/arch/mod.rs create mode 100644 src/arch/x86_64/linker.ld create mode 100644 src/arch/x86_64/mod.rs create mode 100644 src/arch/x86_64/port.rs create mode 100644 src/arch/x86_64/x86_64-unknown-none.json create mode 100644 src/boot/limine.rs create mode 100644 src/boot/mod.rs create mode 100644 src/drivers/mod.rs create mode 100644 src/drivers/serial.rs create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..61996f8 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,12 @@ +[unstable] +build-std = ["core", "compiler_builtins", "alloc"] + +[build] +target = "./src/arch/x86_64/x86_64-unknown-none.json" +rustflags = ["-Cforce-frame-pointers=yes"] + +# use this to reduce the binary size, I've seen these reduce the kernel by 60Kib +# you could use opt-level = "z" and save another 50k, but imo, speed is much more valuable than size +[profile.release] +strip = true +lto = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4655a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/target +/bin +/ovmf + +# Bochs +bx_enh_dbg.ini +bochsout.txt + +# limine +limine/ + diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0b617e6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "dusk" +version = "0.1.0" +dependencies = [ + "limine", +] + +[[package]] +name = "limine" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29363c0f37e66e18575fadf7141c56ee7ea04ae5fecbeb25eff303f77af203a9" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d6e1b02 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "dusk" +version = "0.1.0" +edition = "2024" + +[dependencies] +limine = "0.6.5" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9897077 --- /dev/null +++ b/Makefile @@ -0,0 +1,186 @@ +ARTIFACTS_PATH ?= bin +IMAGE_NAME ?= dusk.iso +MODE ?= release +ARCH ?= x86_64 +MEMORY ?= 512M +# In MB +ISO_SIZE ?= 512 +QEMU_OPTS ?= +#MKSQUASHFS_OPTS ?= +GDB ?= +CPUS ?= 1 +# FAT type +ESP_BITS ?= 32 +EXPORT_SYMBOLS = true + +ISO_PATH = ${ARTIFACTS_PATH}/iso_root +#INITRAMFS_PATH = ${ARTIFACTS_PATH}/initramfs +IMAGE_PATH = ${ARTIFACTS_PATH}/${IMAGE_NAME} +CARGO_OPTS = -Zjson-target-spec --target=src/arch/${ARCH}/${ARCH}-unknown-none.json +QEMU_OPTS += -m ${MEMORY} -drive id=hd0,format=raw,file=${IMAGE_PATH} +LIMINE_BOOT_VARIATION = X64 +LIMINE_VERSION = v12.5.2 +LIMINE_URL = https://github.com/limine-bootloader/limine/releases/download/${LIMINE_VERSION}/limine-binary.zip + +KERNEL_FILE = target/${ARCH}-unknown-none/${MODE}/dusk.elf + +ifeq (${MODE},release) + CARGO_OPTS += --release +endif + +ifneq (${CPUS},1) + QEMU_OPTS += -smp ${CPUS} +endif + +ifneq (${GDB},) + QEMU_OPTS += -s -S +endif + +ifeq (${ARCH},aarch64) + LIMINE_BOOT_VARIATION := AA64 + UEFI := true +endif + +ifneq (${UEFI},) + RUN_OPTS := ovmf-${ARCH} + ifeq (${ARCH},aarch64) + QEMU_OPTS += -M virt -bios ovmf/ovmf-${ARCH}/OVMF.fd + else + QEMU_OPTS += -bios ovmf/ovmf-${ARCH}/OVMF.fd + endif +endif + +.PHONY: all build + +all: build + +build: prepare-bin-files compile-bootloader compile-binaries run-scripts build-iso + +check: + cargo check + +prepare-bin-files: + # Remove ISO and everything in the bin directory + rm -f ${IMAGE_PATH} + rm -rf ${ARTIFACTS_PATH}/* + + # Make bin/ bin/iso_root and bin/initramfs + mkdir -p ${ARTIFACTS_PATH} + mkdir -p ${ISO_PATH} + # mkdir -p ${INITRAMFS_PATH} + mkdir -p ${ARTIFACTS_PATH}/mnt + +#copy-initramfs-files: +# echo "Hello World from Initramfs" > ${INITRAMFS_PATH}/example.txt +# echo "Second file for testing" > ${INITRAMFS_PATH}/example2.txt +# mkdir -p ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/ +# mkdir ${INITRAMFS_PATH}/mnt/ +# echo "Nexted file reads!!" > ${INITRAMFS_PATH}/firstdir/seconddirbutlonger/yeah.txt + +#compile-initramfs: copy-initramfs-files +# # Make squashfs without compression temporaily so I can get it working before I have to write a gzip driver +# mksquashfs ${INITRAMFS_PATH} ${ARTIFACTS_PATH}/initramfs.img ${MKSQUASHFS_OPTS} + +run-scripts: + # Place the build ID into the binary so it can be read at runtime + @HASH=$$(md5sum ${KERNEL_FILE} | cut -c1-12) && \ + sed -i "s/__BUILD_ID__/$${HASH}/" ${KERNEL_FILE} + +#ifeq (${EXPORT_SYMBOLS},true) +# nm ${KERNEL_FILE} > scripts/symbols.table +# @if [ ! -d "scripts/rustc_demangle" ]; then \ +# git clone "https://github.com/juls0730/rustc_demangle.py" "scripts/rustc_demangle"; \ +# fi +# python scripts/demangle-symbols.py +# mv scripts/symbols.table ${INITRAMFS_PATH}/ +#endif + +# python scripts/font.py +# mv scripts/font.psf ${INITRAMFS_PATH}/ + + #python scripts/initramfs-test.py 100 ${INITRAMFS_PATH}/ + +copy-iso-files: + # Limine files + mkdir -p ${ISO_PATH}/boot/limine + mkdir -p ${ISO_PATH}/EFI/BOOT + + mkdir -p ${ISO_PATH}/mnt + + cp -v limine.conf limine/limine-bios.sys ${ISO_PATH}/boot/limine + cp -v limine/BOOT${LIMINE_BOOT_VARIATION}.EFI ${ISO_PATH}/EFI/BOOT/ + + # OS files + cp -v ${KERNEL_FILE} ${ISO_PATH}/boot + #cp -v ${ARTIFACTS_PATH}/initramfs.img ${ISO_PATH}/boot + +partition-iso: copy-iso-files + # Make empty ISO of 64M in size + dd if=/dev/zero of=${IMAGE_PATH} bs=1M count=0 seek=${ISO_SIZE} + parted -s ${IMAGE_PATH} mklabel gpt + parted -s ${IMAGE_PATH} mkpart BIOSBOOT 1024s 2047s + parted -s ${IMAGE_PATH} set 1 bios_grub on + + parted -s ${IMAGE_PATH} mkpart ESP fat${ESP_BITS} 2048s 262144s + + # Make ISO with 1 partition starting at sector 2048 that is 32768 sectors, or 16MiB, in size + # Then a second partition spanning the rest of the disk + parted -s ${IMAGE_PATH} mkpart primary 262145s 100% + parted -s ${IMAGE_PATH} set 2 esp on + +build-iso: partition-iso +ifeq (${ARCH},x86_64) + # Install the Limine bootloader for bios installs + ./limine/limine bios-install ${IMAGE_PATH} +endif + + sudo losetup -Pf --show ${IMAGE_PATH} > loopback_dev + sudo mkfs.fat -F ${ESP_BITS} `cat loopback_dev`p2 + sudo mount `cat loopback_dev`p2 ${ARTIFACTS_PATH}/mnt + sudo cp -r ${ISO_PATH}/* ${ARTIFACTS_PATH}/mnt + sync + sudo umount ${ARTIFACTS_PATH}/mnt + sudo losetup -d `cat loopback_dev` + rm loopback_dev + +compile-bootloader: + @if [ ! -f "limine/.version" ] || [ "$$(cat limine/.version)" != "${LIMINE_VERSION}" ]; then \ + echo "Downloading Limine ${LIMINE_VERSION}..."; \ + rm -rf limine limine-binary limine-binary.zip; \ + curl -fLo limine-binary.zip "${LIMINE_URL}"; \ + unzip -q limine-binary.zip; \ + mv limine-binary limine; \ + rm limine-binary.zip; \ + printf '%s\n' "${LIMINE_VERSION}" > limine/.version; \ + fi + ${MAKE} -C limine + +compile-binaries: + cargo build ${CARGO_OPTS} + +ovmf-x86_64: ovmf + mkdir -p ovmf/ovmf-x86_64 + @if [ ! -d "ovmf/ovmf-x86_64/OVMF.fd" ]; then \ + cd ovmf/ovmf-x86_64 && curl -Lo OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF.fd; \ + fi + +ovmf-aarch64: ovmf + mkdir -p ovmf/ovmf-aarch64 + @if [ ! -d "ovmf/ovmf-aarch64/OVMF.fd" ]; then \ + cd ovmf/ovmf-aarch64 && curl -o OVMF.fd https://retrage.github.io/edk2-nightly/bin/RELEASEAARCH64_QEMU_EFI.fd; \ + fi + +# In debug mode, open a terminal and run this command: +# gdb target/x86_64-unknown-none/debug/CappuccinOS.elf -ex "target remote :1234" + +run: build ${RUN_OPTS} run-${ARCH} + +run-x86_64: + tmux new-session -d -s qemu 'qemu-system-x86_64 ${QEMU_OPTS}' + +line-count: + cloc --quiet --exclude-dir=bin --include-lang=Rust --csv src/ | tail -n 1 | awk -F, '{print $$5}' +clean: + cargo clean + rm -rf ${ARTIFACTS_PATH} + @if [ -d "limine" ]; then ${MAKE} clean -C limine; fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..310d416 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# DuskOS + +A simple microkernel and operating system written in Rust for x86_64. diff --git a/limine.conf b/limine.conf new file mode 100644 index 0000000..6722ebe --- /dev/null +++ b/limine.conf @@ -0,0 +1,7 @@ +timeout: 3 + +/DuskOS + protocol: limine + + path: boot():/boot/dusk.elf + diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..2a75711 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "rust-src"] +profile = "minimal" +targets = ["x86_64-unknown-none"] diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 0000000..ce1fde5 --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,5 @@ +#[cfg(target_arch = "x86_64")] +mod x86_64; + +#[cfg(target_arch = "x86_64")] +pub use x86_64::*; diff --git a/src/arch/x86_64/linker.ld b/src/arch/x86_64/linker.ld new file mode 100644 index 0000000..8de6521 --- /dev/null +++ b/src/arch/x86_64/linker.ld @@ -0,0 +1,77 @@ +/* Tell the linker that we want an x86_64 ELF64 output file */ +OUTPUT_FORMAT(elf64-x86-64) +OUTPUT_ARCH(i386:x86-64) + +/* We want the symbol _start to be our entry point */ +ENTRY(_start) + +/* 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 +{ + headers PT_PHDR PHDRS; + text PT_LOAD FILEHDR PHDRS; + rodata PT_LOAD; + data PT_LOAD; + dynamic PT_DYNAMIC; +} + +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. */ + /* Additionally, leave space for the ELF headers by adding SIZEOF_HEADERS to the */ + /* base load address. */ + . = 0xffffffff80000000 + SIZEOF_HEADERS; + + .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 + + /* Dynamic section for relocations, both in its own PHDR and inside data PHDR. */ + .dynamic : { + *(.dynamic) + } :data :dynamic + + /* 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. */ + /* Also discard the program interpreter section since we do not need one. This is */ + /* more or less equivalent to the --no-dynamic-linker linker flag, except that it */ + /* works with ld.gold. */ + /DISCARD/ : { + *(.eh_frame*) + *(.note .note.*) + *(.interp) + } +} diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..783e99b --- /dev/null +++ b/src/arch/x86_64/mod.rs @@ -0,0 +1 @@ +pub mod port; diff --git a/src/arch/x86_64/port.rs b/src/arch/x86_64/port.rs new file mode 100644 index 0000000..cf1c21a --- /dev/null +++ b/src/arch/x86_64/port.rs @@ -0,0 +1,105 @@ +use core::arch::asm; + +#[inline(always)] +pub unsafe fn read_u8(port: u16) -> u8 { + let value: u8; + unsafe { + asm!( + "in al, dx", + in("dx") port, + out("al") value, + options(nomem, nostack, preserves_flags), + ); + } + value +} + +#[inline(always)] +pub unsafe fn read_u8_slice(port: u16, slice: &mut [u8]) { + unsafe { + asm!( + "rep insb", + in("dx") port, + inout("rdi") slice.as_mut_ptr() => _, + inout("rcx") slice.len() => _, + options(nostack, preserves_flags), + ); + } +} + +#[inline(always)] +pub unsafe fn read_u16(port: u16) -> u16 { + let value: u16; + unsafe { + asm!( + "in ax, dx", + in("dx") port, + out("ax") value, + options(nomem, nostack, preserves_flags), + ); + } + value +} + +#[inline(always)] +pub unsafe fn read_u32(port: u16) -> u32 { + let value: u32; + unsafe { + asm!( + "in eax, dx", + in("dx") port, + out("eax") value, + options(nomem, nostack, preserves_flags), + ); + } + value +} + +#[inline(always)] +pub unsafe fn write_u8(port: u16, value: u8) { + unsafe { + asm!( + "out dx, al", + in("dx") port, + in("al") value, + options(nomem, nostack, preserves_flags), + ); + } +} + +#[inline(always)] +pub unsafe fn write_u8_slice(port: u16, slice: &[u8]) { + unsafe { + asm!( + "rep outsb", + in("dx") port, + inout("rsi") slice.as_ptr() => _, + inout("rcx") slice.len() => _, + options(nostack, preserves_flags), + ); + } +} + +#[inline(always)] +pub unsafe fn write_u16(port: u16, value: u16) { + unsafe { + asm!( + "out dx, ax", + in("dx") port, + in("ax") value, + options(nomem, nostack, preserves_flags), + ); + } +} + +#[inline(always)] +pub unsafe fn write_u32(port: u16, value: u32) { + unsafe { + asm!( + "out dx, eax", + in("dx") port, + in("eax") value, + options(nomem, nostack, preserves_flags), + ); + } +} diff --git a/src/arch/x86_64/x86_64-unknown-none.json b/src/arch/x86_64/x86_64-unknown-none.json new file mode 100644 index 0000000..a7cd4a7 --- /dev/null +++ b/src/arch/x86_64/x86_64-unknown-none.json @@ -0,0 +1,20 @@ +{ + "arch": "x86_64", + "cpu": "x86-64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "llvm-target": "x86_64-unknown-none", + "target-endian": "little", + "target-pointer-width": 64, + "target-c-int-width": 32, + "features": "-mmx,-sse,+soft-float", + "rustc-abi": "softfloat", + "os": "DawnOS", + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "pre-link-args": { + "ld.lld": ["-melf_x86_64", "--script=./src/arch/x86_64/linker.ld"] + }, + "panic-strategy": "abort", + "exe-suffix": ".elf", + "disable-redzone": true +} diff --git a/src/boot/limine.rs b/src/boot/limine.rs new file mode 100644 index 0000000..132eb9d --- /dev/null +++ b/src/boot/limine.rs @@ -0,0 +1,22 @@ +use limine::request::FramebufferRequest; +use limine::{BaseRevision, RequestsEndMarker, RequestsStartMarker}; + +/// 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")] +pub static BASE_REVISION: BaseRevision = BaseRevision::new(); + +#[used] +#[unsafe(link_section = ".requests")] +pub 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(); diff --git a/src/boot/mod.rs b/src/boot/mod.rs new file mode 100644 index 0000000..e0a69ec --- /dev/null +++ b/src/boot/mod.rs @@ -0,0 +1 @@ +pub mod limine; diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs new file mode 100644 index 0000000..b1fc0cf --- /dev/null +++ b/src/drivers/mod.rs @@ -0,0 +1 @@ +pub mod serial; diff --git a/src/drivers/serial.rs b/src/drivers/serial.rs new file mode 100644 index 0000000..9c17d29 --- /dev/null +++ b/src/drivers/serial.rs @@ -0,0 +1,179 @@ +use crate::arch::port::{read_u8, write_u8}; + +const COM1_BASE: u16 = 0x3F8; + +mod register { + pub const DATA_BUFFER: u16 = 0; + + pub const DIVISOR_LOW: u16 = 0; + pub const INTERRUPT_ENABLE: u16 = 1; + pub const DIVISOR_HIGH: u16 = 1; + + pub const INTERRUPT_IDENTIFICATION: u16 = 2; + pub const FIFO_CONTROL: u16 = 2; + + pub const LINE_CONTROL: u16 = 3; + pub const MODEM_CONTROL: u16 = 4; + pub const LINE_STATUS: u16 = 5; + pub const MODEM_STATUS: u16 = 6; + pub const SCRATCH: u16 = 7; +} + +mod fifo_control { + pub const ENABLE: u8 = 1 << 0; + pub const CLEAR_RECEIVE: u8 = 1 << 1; + pub const CLEAR_TRANSMIT: u8 = 1 << 2; + pub const TRIGGER_LEVEL_14: u8 = 0b11 << 6; +} + +mod line_control { + pub const DATA_BITS_8: u8 = 0b11; + pub const DLAB: u8 = 1 << 7; +} + +mod line_status { + pub const TRANSMITTER_EMPTY: u8 = 1 << 5; +} + +mod modem_control { + pub const DATA_TERMINAL_READY: u8 = 1 << 0; + pub const REQUEST_TO_SEND: u8 = 1 << 1; + // commonly controls whether IRQs are enabled or disabled + pub const OUT_1: u8 = 1 << 2; + // unused in PC implementations + pub const OUT_2: u8 = 1 << 3; + pub const LOOPBACK: u8 = 1 << 4; +} + +#[derive(Debug)] +pub enum SerialPortError { + NotFound, + Faulty, +} + +struct SerialPort { + base: u16, +} + +impl SerialPort { + pub fn new(base: u16) -> Self { + Self { base } + } + + pub fn init(&self) -> Result<(), SerialPortError> { + const SCRATCH_TEST_VALUE: u8 = 0x42; + const LOOPBACK_TEST_VALUE: u8 = 0xAE; + + // The scratch register has no hardware-defined behavior, so it can be used to probe the port. + self.write_register(register::SCRATCH, SCRATCH_TEST_VALUE); + if self.read_register(register::SCRATCH) != SCRATCH_TEST_VALUE { + return Err(SerialPortError::NotFound); + } + + self.write_register(register::INTERRUPT_ENABLE, 0); + + // DLAB changes registers 0 and 1 into the low and high divisor registers so we can set the baud rate + self.write_register(register::LINE_CONTROL, line_control::DLAB); + // in this case we are setting the baud rate to 115200 baud + self.write_register(register::DIVISOR_LOW, 1); + self.write_register(register::DIVISOR_HIGH, 0); + self.write_register(register::LINE_CONTROL, line_control::DATA_BITS_8); + + self.write_register( + register::FIFO_CONTROL, + fifo_control::ENABLE + | fifo_control::CLEAR_RECEIVE + | fifo_control::CLEAR_TRANSMIT + | fifo_control::TRIGGER_LEVEL_14, + ); + + self.write_register( + register::MODEM_CONTROL, + modem_control::DATA_TERMINAL_READY + | modem_control::REQUEST_TO_SEND + | modem_control::OUT_2, + ); + + self.write_register( + register::MODEM_CONTROL, + modem_control::REQUEST_TO_SEND + | modem_control::OUT_1 + | modem_control::OUT_2 + | modem_control::LOOPBACK, + ); + + self.write_byte(LOOPBACK_TEST_VALUE); + if self.read_register(register::DATA_BUFFER) != LOOPBACK_TEST_VALUE { + return Err(SerialPortError::Faulty); + } + + self.write_register( + register::MODEM_CONTROL, + modem_control::DATA_TERMINAL_READY + | modem_control::REQUEST_TO_SEND + | modem_control::OUT_1 + | modem_control::OUT_2, + ); + + Ok(()) + } + + fn write_register(&self, register: u16, value: u8) { + unsafe { write_u8(self.base + register, value) }; + } + + fn read_register(&self, register: u16) -> u8 { + unsafe { read_u8(self.base + register) } + } + + fn can_transfer(&self) -> bool { + self.read_register(register::LINE_STATUS) & line_status::TRANSMITTER_EMPTY != 0 + } + + pub fn write_byte(&self, byte: u8) { + if byte == b'\n' { + self.write_byte(b'\r'); + } + + while !self.can_transfer() { + core::hint::spin_loop(); + } + + self.write_register(register::DATA_BUFFER, byte); + } +} + +impl core::fmt::Write for SerialPort { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + for byte in s.bytes() { + self.write_byte(byte); + } + Ok(()) + } +} + +fn com1() -> SerialPort { + SerialPort::new(COM1_BASE) +} + +pub fn init() -> Result<(), SerialPortError> { + com1().init() +} + +pub fn print(args: core::fmt::Arguments) { + use core::fmt::Write; + + let mut serial = com1(); + let _ = serial.write_fmt(args); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::drivers::serial::print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::drivers::serial::print(format_args!("\n"))); + ($($arg:tt)*) => ($crate::drivers::serial::print(format_args!("{}\n", format_args!($($arg)*)))); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fc7765d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,66 @@ +#![feature(abi_x86_interrupt, negative_impls)] +#![allow(clippy::needless_return)] +#![no_std] +#![no_main] + +mod arch; +mod boot; +mod drivers; + +use boot::limine::{BASE_REVISION, FRAMEBUFFER_REQUEST}; + +use crate::drivers::serial; + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + serial::init().unwrap(); + + assert!(BASE_REVISION.is_supported()); + + draw_gradient(); + + hcf(); +} + +fn draw_gradient() { + if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.response() { + if let Some(&framebuffer) = framebuffer_response.framebuffers().first() { + let buffer = unsafe { + core::slice::from_raw_parts_mut( + framebuffer.address().cast::(), + framebuffer.size() / 4, + ) + }; + + for y in 0..framebuffer.height { + for x in 0..framebuffer.width { + let r = (255 * x) / (framebuffer.width - 1); + let g = (255 * y) / (framebuffer.height - 1); + let b = 255 - r; + + let pixel = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); + buffer + [(((y * framebuffer.pitch) / (framebuffer.bpp as u64 / 8)) + x) as usize] = + pixel + } + } + } + } +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + hcf(); +} + +pub fn hcf() -> ! { + loop { + unsafe { + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + core::arch::asm!("hlt"); + + #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] + core::arch::asm!("wfi"); + } + } +}