Compare commits

...

3 Commits

Author SHA1 Message Date
386d64ddd5 sad: death of panicless *+ smth
Give in in favor of str and utf8 with perfect valid state and panic
bailout everywhere in std.

Define some more Boot Info Format tags.
2026-02-21 23:06:50 +01:00
e6b231488d feat: misc
Nicer kernel, now can fetch multiboot2 boot info
2026-02-21 22:12:23 +01:00
a2dfc5c775 fix: vt display being rendered +1 in x and y 2026-02-21 21:01:01 +01:00
13 changed files with 330 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
[build]
target = ".cargo/target-i386.json"
target = ".cargo/target-protected-i386.json"
[unstable]
build-std = ["core", "compiler_builtins"]

View File

@@ -1,4 +1,4 @@
ENTRY(start)
ENTRY(start_stub)
SECTIONS {
. = 1M;

7
Cargo.lock generated
View File

@@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "bios"
version = "0.1.0"
@@ -10,5 +16,6 @@ version = "0.1.0"
name = "oxide"
version = "0.1.0"
dependencies = [
"ascii",
"bios",
]

View File

@@ -6,18 +6,9 @@ members = ["deps/bios", "members/oxide"]
bios = { path = "deps/bios" }
[profile.release]
opt-level = "z"
codegen-units = 1
lto = "fat"
panic = "abort"
[profile.dev]
panic = "abort"
[profile.release.package.microloader-mbr]
codegen-units = 1
opt-level = "z"
[profile.release.package.microloader-vba]
codegen-units = 1
opt-level = "z"

View File

@@ -7,9 +7,11 @@ RUST_DEP := Cargo.toml Cargo.lock $(shell find .cargo -type f) $(shell find deps
KERNELFILE = $(OXIDE_MULTIBOOT)
$(KERNELFILE): $(shell find members/oxide -type f) $(RUST_DEP)
cargo b -r --target=.cargo/target-protected-i386.json --package oxide
cargo b -r --package oxide
BOOTLOADER ?= grub
# Must not contain '/' with current limitations
CMDLINE ?=
include mk/make_bootloader/$(BOOTLOADER).mk
.PHONY: clean \
@@ -24,6 +26,6 @@ run-bootloader-qemu-nographic: $(OUTIMG)
qemu-system-x86_64 -nographic -drive format=raw,file=$(OUTIMG)
# one day qemu will support multiboot2
run-qemu: $(KERNELFILE)
qemu-system-x86_64 -kernel $(KERNELFILE)
qemu-system-x86_64 -kernel $(KERNELFILE) -append $(CMDLINE)
run-qemu-nographic: $(KERNELFILE)
qemu-system-x86_64 -nographic -kernel $(KERNELFILE)
qemu-system-x86_64 -nographic -kernel $(KERNELFILE) -append $(CMDLINE)

View File

@@ -2,6 +2,6 @@ set timeout=2
set default=0
menuentry "OxideOS" {
multiboot2 /boot/kernel.bin
multiboot2 /boot/kernel.bin $args
boot
}

View File

@@ -10,4 +10,5 @@ test = false
bench = false
[dependencies]
ascii = { version = "1.1.0", default-features = false }
bios = { workspace = true }

View File

@@ -2,36 +2,48 @@
#![no_main]
#![deny(clippy::float_arithmetic)]
#![feature(
arbitrary_self_types,
arbitrary_self_types_pointers,
associated_type_defaults,
const_convert,
const_range,
const_trait_impl,
const_convert,
decl_macro,
generic_const_exprs,
associated_type_defaults
ptr_metadata
)]
/// Triggers a linker failure if this reaches that phase
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
unsafe extern "C" {
static __PANIC_HANDLER_WAS_LINKED__DO_NOT_DEFINE: extern "C" fn() -> !;
}
// 2026-02-21: Officially the end of panicless OxideOS, did you know `Debug for str` has panic
// code paths :pensive:. At least compile ones, maybe runtime impossible but still not compiled
// away.
unsafe { asm!("ud2", "hlt", options(noreturn)) };
unsafe { __PANIC_HANDLER_WAS_LINKED__DO_NOT_DEFINE() };
// unsafe extern "C" {
// static __PANIC_HANDLER_WAS_LINKED__DO_NOT_DEFINE: extern "C" fn() -> !;
// }
// unsafe { __PANIC_HANDLER_WAS_LINKED__DO_NOT_DEFINE() };
}
use core::{arch::asm, hint::black_box, panic::PanicInfo};
use core::{arch::asm, fmt::Write as _, hint::black_box, panic::PanicInfo};
use crate::vga::{ColorNibble, VgaBuffer, VgaColor};
use crate::{
multiboot2::bif,
vga::{ColorNibble, VgaBuffer, VgaColor},
};
pub mod multiboot2;
pub mod start_stub;
pub mod vga;
#[used]
#[unsafe(link_section = ".multiboot2_header")]
static HEADER: multiboot2::Header = multiboot2::Header::new();
#[unsafe(no_mangle)]
fn start() -> ! {
extern "C" fn start(boot_info: &bif::FixedPart) -> ! {
let mut vga = unsafe { VgaBuffer::<vga::text_modes::VGAText2>::new_uninitialized() };
vga.write_at(0, 1, b'H');
@@ -42,12 +54,51 @@ fn start() -> ! {
vt.set_color(VgaColor::new(ColorNibble::BLUE, ColorNibble::GREEN));
vt.put_at(10, 5);
vt.slide();
vt.write(b"testinggg");
// loop {
// for x in b'a'..b'z' {
// vt.write(&[x]);
// }
vt.write(b"testinggg\ntags:\n");
let tags = bif::TagsIter::new(boot_info);
for tag in tags {
sleep_short();
let _ = write!(&mut vt, "{}: ", tag.typ());
let Some(tag) = tag.tag() else {
vt.write(b"unknown tag\n");
continue;
};
// this single line pulls like half of the fmt module which means ~ 7.5KiB of BS
let _ = writeln!(&mut vt, "{tag:?}");
// tbh I prefer these, I need to like, write u8 chars, on the TTY level like I really care
// about writting binary, still need to differenciate a slice of u8's and a asciistr,
// AsciiChar looks innteresting in std and could make astr = [AsciiChar], the thing I don't
// like is that disply is straight up against non-utf8 output, so I'd have to make my own
// raw-display?? but then Im going all against rust, no Display or Derive std macros
// either...
//
// match tag {
// bif::TagTagged::BootCommandLine(_) => vt.write(b"BootCommandLine\n"),
// bif::TagTagged::BasicMemoryInformation(_) => vt.write(b"BasicMemoryInformation\n"),
// bif::TagTagged::BIOSBootDevice(_) => vt.write(b"BIOSBootDevice\n"),
// bif::TagTagged::Terminate(_) => vt.write(b"Terminate\n"),
// bif::TagTagged::BootLoaderName(_) => vt.write(b"BootLoaderName\n"),
// bif::TagTagged::ImageLoadBasePhyAddr(_) => vt.write(b"ImageLoadBasePhyAddr\n"),
// }
//
//
// Somebody else, body else, body elseeee, e- e- e- elsee
//
// I don't know what to do, 'cause when you are around then I'm aaaaaalll over youuuu
//
// I always think about it, I can't play it coooool
//
// Blazing in your heeeeell, he- he- he- heeeellllll~
//
}
for _ in 0..=30 {
sleep_short();
}
for _ in 0..=5 {
sleep_short();
@@ -63,6 +114,7 @@ fn start() -> ! {
fn sleep_short() {
let mut n = 0_u64;
while n < 100_000_000 {
#[allow(clippy::unit_arg)]
black_box(n += 1);
}
}

View File

@@ -39,4 +39,218 @@ impl Default for Header {
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum HeaderArchitecture {
ProtectedI386 = 0,
MIPS32 = 4,
}
pub mod bif {
//! # Boot Information Format
//!
//! <https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html>
use core::ptr;
pub trait TagType {
const ID: u32 = 4;
}
pub mod types {
use ascii::AsciiStr;
macro tagset($taggedvis:vis $taggedname:ident { $( $typname:ident ($typn:expr) { $($typprop:ident : $typpropty:ty),* $(,)? } ),* $(,)? }) {
$(
#[repr(C)]
#[derive(Debug)]
pub struct $typname { $(pub $typprop: $typpropty),* }
impl super::TagType for $typname {
const ID: u32 = $typn;
}
)*
#[derive(Debug)]
$taggedvis enum $taggedname<'a> {
$( $typname(&'a $typname) ),*
}
}
// All these AsciiStr are actually CStr's
tagset! { pub TagTagged {
Terminate(0) {},
BootCommandLine(1) {
string: AsciiStr,
},
BootLoaderName(2) {
string: AsciiStr,
},
BasicMemoryInformation(4) {
mem_lower: u32,
mem_upper: u32,
},
BIOSBootDevice(5) {
biosdev: u32,
partition: u32,
sub_partition: u32,
},
MemoryMap(6) {
entry_size: u32,
entry_version: u32,
entries: [u8], // "varies"?
},
FramebufferInfo(8) {
addr: u64,
pitch: u32,
width: u32,
height: u32,
bpp: u8,
typ: u8,
_reserved: u8,
color_info: [u8],
},
ELFSymbols(9) {
num: u16,
entsize: u16,
shndx: u16,
_reserved: u16,
section_headers: [u8], // "varies"?
},
APMTable(10) {
version: u16,
cseg: u16,
offset: u32,
cseg_16: u16,
dseg: u16,
flags: u16,
cseg_len: u16,
cseg_16_len: u16,
dseg_len: u16,
},
ACPIoldRSDP(14) {
rsdpv1_old: [u8],
},
ImageLoadBasePhyAddr(21) {
load_base_addr: u32,
},
}}
}
pub use types::TagTagged;
#[repr(C)]
pub struct TagHead {
typ: u32,
size: u32,
__end: (),
}
impl TagHead {
pub fn typ(&self) -> u32 {
self.typ
}
pub fn size(&self) -> u32 {
self.size
}
pub fn aligned8_size(&self) -> u32 {
(self.size + 7) & !7
}
/// Gets the reference of the next laying in memory tag, considering this one's size.
///
/// # Safety
///
/// Assumes there's a contiguous one in memory.
pub unsafe fn next(&self) -> &Self {
unsafe { &*ptr::from_ref(self).byte_add(self.aligned8_size() as usize) }
}
pub fn tag<'a>(&self) -> Option<TagTagged<'a>> {
let typ = self.typ;
let size = self.size;
let contents_addr = ptr::addr_of!(self.__end).cast::<()>();
macro typ_match(match $var:ident { $($name:ident)* } fat { $($fatname:ident)* }) {
match $var {
$(
types::$name::ID => Some(TagTagged::$name(unsafe {
&*core::ptr::from_raw_parts::<types::$name>(
contents_addr,
(),
)
}))
),*,
$(
types::$fatname::ID => Some(TagTagged::$fatname(unsafe {
&*core::ptr::from_raw_parts::<types::$fatname>(
contents_addr,
size as usize - core::mem::size_of::<Self>(),
)
}))
),*,
_ => None,
}
}
typ_match!(match typ {
Terminate
BasicMemoryInformation
BIOSBootDevice
APMTable
ImageLoadBasePhyAddr
} fat {
BootCommandLine
BootLoaderName
MemoryMap
FramebufferInfo
ELFSymbols
ACPIoldRSDP
})
}
}
// ---
/// Represents the fixed part of the BIF
#[repr(C, align(8))]
pub struct FixedPart {
pub total_size: u32,
_reserved: u32,
__end: (),
}
/// Iterator for the tags of a [`FixedPart`]
pub struct TagsIter<'a> {
rem_size: u32,
next_ref: &'a TagHead,
}
impl<'a> TagsIter<'a> {
pub fn new(fixed: &FixedPart) -> Self {
let rem_size = fixed.total_size - core::mem::size_of::<FixedPart>() as u32;
let next_ptr = unsafe { &*core::ptr::addr_of!(fixed.__end).cast() };
Self {
rem_size,
next_ref: next_ptr,
}
}
}
impl<'a> Iterator for TagsIter<'a> {
type Item = &'a TagHead;
fn next(&mut self) -> Option<&'a TagHead> {
if self.rem_size > 0 {
let this_ref = self.next_ref;
self.rem_size -= this_ref.aligned8_size();
// SAFETY: if there were no tags left we'd reach rem_size and not use this referece
// any further
self.next_ref = unsafe { this_ref.next() };
Some(this_ref)
} else {
None
}
}
}
}

View File

@@ -0,0 +1,16 @@
//! <https://www.gnu.org/software/grub/manual/multiboot2/multiboot.html>
use core::arch::naked_asm;
#[cfg(target_arch = "x86")]
#[unsafe(no_mangle)]
#[unsafe(naked)]
unsafe extern "C" fn start_stub() -> ! {
// In cdecl, protected i386, the first argument is passed in the stack
// Sadly need to call bcs stack expectations, I wanna make a extern natural compiled thing for
// this.
const _: extern "C" fn(&crate::multiboot2::bif::FixedPart) -> ! = crate::start;
naked_asm!("push ebx", "call {}", sym crate::start);
}

View File

@@ -54,7 +54,7 @@ impl<M: TextMode> VgaBuffer<M> {
where
[[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize]:,
{
unsafe { core::mem::transmute(Self::VGA_ADDR) }
unsafe { &mut *Self::VGA_ADDR.cast() }
}
}

View File

@@ -1,3 +1,5 @@
use core::fmt::Write;
use crate::vga::{ColorNibble, TextMode, VgaBuffer, VgaColor};
pub struct Writer<M: TextMode> {
@@ -20,7 +22,7 @@ where
pub fn carry(&mut self) {
self.pos.0 = 0;
if self.pos.1 >= M::HEIGHT {
if self.pos.1 + 1 >= M::HEIGHT {
self.slide();
} else {
self.pos.1 += 1;
@@ -28,7 +30,7 @@ where
}
pub fn next_cell(&mut self) {
if self.pos.0 >= M::WIDTH {
if self.pos.0 + 1 >= M::WIDTH {
self.carry();
} else {
self.pos.0 += 1;
@@ -94,3 +96,13 @@ where
}
}
}
impl<M: TextMode> Write for Writer<M>
where
[[(u8, u8); M::WIDTH as usize]; M::HEIGHT as usize]:,
{
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write(s.as_bytes());
Ok(())
}
}

View File

@@ -6,4 +6,5 @@ $(OUTIMG): $(KERNELFILE) $(GRUB_CFG)
mkdir -p $(GRUB_DIR)/boot/grub
cp $(KERNELFILE) $(GRUB_DIR)/boot/kernel.bin
cp $(GRUB_CFG) $(GRUB_DIR)/boot/grub/grub.cfg
sed -i 's/$$args/$(CMDLINE)/' $(GRUB_DIR)/boot/grub/grub.cfg
grub-mkrescue -o $@ $(GRUB_DIR)