diff --git a/.cargo/config.toml b/.cargo/config.toml index b2570e105ae3efc2a2616570deb567a155b69425..c04d9e59909d4e93da02fb84aa9c70d46c85d0c3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,8 @@ -#[build] -#target = "x86_64-unknown-none" +[alias] +debug = "run -- --debug" + +[build] +target = "x86_64-unknown-none" [target.x86_64-unknown-none] runner = "./.cargo/runner.sh" diff --git a/.cargo/runner.sh b/.cargo/runner.sh index 5914a40ca832eeb293638ee09e78f146e92fb401..b7c557b4cd615ee6aa9947d866208d16374fefd4 100755 --- a/.cargo/runner.sh +++ b/.cargo/runner.sh @@ -4,6 +4,8 @@ set -xe +echo $@ + LIMINE_GIT_URL="https://github.com/limine-bootloader/limine.git" ISO_DIR=target/hyperion/x86_64/iso KERNEL=$1 @@ -21,7 +23,8 @@ cd - # Copy the needed files into an ISO image. mkdir -p $ISO_DIR -cp $KERNEL cfg/limine.cfg target/limine/limine{.sys,-cd.bin,-cd-efi.bin} $ISO_DIR +cp cfg/limine.cfg target/limine/limine{.sys,-cd.bin,-cd-efi.bin} $ISO_DIR +cp $KERNEL $ISO_DIR/hyperion xorriso -as mkisofs \ -b limine-cd.bin \ @@ -33,9 +36,37 @@ xorriso -as mkisofs \ # For the image to be bootable on BIOS systems, we must run `limine-deploy` on it. target/limine/limine-deploy $KERNEL.iso -# Run the created image with QEMU. -qemu-system-x86_64 \ - -machine q35 -cpu qemu64 -M smm=off \ - -D target/log.txt -d int,guest_errors -no-reboot -no-shutdown \ - -serial stdio \ - $KERNEL.iso +# A hack to detect if the kernel is a testing kernel +# Cargo test binary generates a 'random id' for testing binaries +if [ "$(basename $KERNEL)" = "hyperion" ]; then + # Run the created image with QEMU. + qemu-system-x86_64 \ + -machine q35 \ + -cpu qemu64 \ + -M smm=off \ + -d int,guest_errors,cpu_reset \ + -no-reboot \ + -serial stdio \ + $KERNEL.iso + #-s -S \ + #-no-shutdown \ + #-D target/log.txt \ +else + set +e + # Run the created image with QEMU. + qemu-system-x86_64 \ + -machine q35 \ + -cpu qemu64 \ + -M smm=off \ + -d int,guest_errors,cpu_reset \ + -device isa-debug-exit,iobase=0xf4,iosize=0x04 \ + -no-reboot \ + -serial stdio \ + -display none \ + $KERNEL.iso + #-no-shutdown \ + #-D target/log.txt \ + + [ $? -ne 33 ] && exit 1 + exit 0 +fi diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000000000000000000000000000000000000..fa861110fe456dcf5b884388197d5ee8e4632e0c --- /dev/null +++ b/.gdbinit @@ -0,0 +1,5 @@ +file target/hyperion/x86_64/iso/hyperion + +target remote localhost:1234 + +symbol-file target/hyperion/x86_64/iso/hyperion diff --git a/cfg/limine.cfg b/cfg/limine.cfg index 1777d21e9ef695df6a3a46ba4f16d936389bd62f..fd84e951bfc26b9fe605807bffd8b7678fd12090 100644 --- a/cfg/limine.cfg +++ b/cfg/limine.cfg @@ -4,3 +4,4 @@ TIMEOUT=0 :Hyperion PROTOCOL=limine KERNEL_PATH=boot:///hyperion + # KERNEL_CMDLINE= diff --git a/src/arch/x86_64/gdt.rs b/src/arch/x86_64/gdt.rs new file mode 100644 index 0000000000000000000000000000000000000000..052643f04205884ab17a6269c6fc41ae041f9bd2 --- /dev/null +++ b/src/arch/x86_64/gdt.rs @@ -0,0 +1,60 @@ +use super::idt::DOUBLE_FAULT_IST; +use spin::Lazy; +use x86_64::{ + instructions::tables::load_tss, + registers::segmentation::{Segment, CS, SS}, + structures::{ + gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}, + tss::TaskStateSegment, + }, + VirtAddr, +}; + +// + +pub fn init() { + GDT.0.load(); + + unsafe { + CS::set_reg(GDT.1.kc); + SS::set_reg(GDT.1.kd); + load_tss(GDT.1.tss); + } +} + +// + +struct SegmentSelectors { + kc: SegmentSelector, + kd: SegmentSelector, + tss: SegmentSelector, +} + +static GDT: Lazy<(GlobalDescriptorTable, SegmentSelectors)> = Lazy::new(|| { + let mut gdt = GlobalDescriptorTable::new(); + let sel = SegmentSelectors { + kc: gdt.add_entry(Descriptor::kernel_code_segment()), + kd: gdt.add_entry(Descriptor::kernel_data_segment()), + tss: gdt.add_entry(Descriptor::tss_segment(&TSS)), + }; + // gdt.add_entry(Descriptor::user_code_segment()); + // gdt.add_entry(Descriptor::user_data_segment()); + (gdt, sel) +}); + +static TSS: Lazy<TaskStateSegment> = Lazy::new(|| { + let mut tss = TaskStateSegment::new(); + tss.interrupt_stack_table[DOUBLE_FAULT_IST as usize] = { + static mut STACK: [u8; 4096 * 5] = [0; 4096 * 5]; + + let stack_range = unsafe { STACK }.as_ptr_range(); + VirtAddr::from_ptr(stack_range.end) + }; + tss.privilege_stack_table[0] = { + static mut STACK: [u8; 4096 * 5] = [0; 4096 * 5]; + + let stack_range = unsafe { STACK }.as_ptr_range(); + VirtAddr::from_ptr(stack_range.end) + }; + tss +}); diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs new file mode 100644 index 0000000000000000000000000000000000000000..6866baa600893a2b20aaee0fede23cdcab84031b --- /dev/null +++ b/src/arch/x86_64/idt.rs @@ -0,0 +1,55 @@ +use crate::println; +use spin::Lazy; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; + +// + +pub static DOUBLE_FAULT_IST: u16 = 1; + +// + +pub fn init() { + IDT.load(); +} + +// + +extern "x86-interrupt" fn breakpoint(stack: InterruptStackFrame) { + println!("INT: Breakpoint\n{stack:#?}") +} + +extern "x86-interrupt" fn double_fault(stack: InterruptStackFrame, ec: u64) -> ! { + // SAFETY: Unlocking the Mutex is safe if this is the only CPU running + // + // This CPU might have locked the COM1 writer and then stack-overflowed before unlocking it but + // we won't return anyways, so lets just unlock it + unsafe { + // TODO: This won't be safe when multiple CPUs are running + crate::qemu::unlock(); + } + panic!("INT: Double fault ({ec})\n{stack:#?}") +} + +// + +static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint); + unsafe { + idt.double_fault + .set_handler_fn(double_fault) + .set_stack_index(DOUBLE_FAULT_IST); + } + idt +}); + +// + +#[cfg(test)] +mod tests { + #[test_case] + fn breakpoint() { + // breakpoint instruction + x86_64::instructions::interrupts::int3(); + } +} diff --git a/src/arch/x86_64/limine/framebuffer.rs b/src/arch/x86_64/limine/framebuffer.rs new file mode 100644 index 0000000000000000000000000000000000000000..70df236452e161ef9752b0b8ec75fda6f3ec0799 --- /dev/null +++ b/src/arch/x86_64/limine/framebuffer.rs @@ -0,0 +1,37 @@ +use crate::{ + println, + video::framebuffer::{Framebuffer, FBO}, +}; +use core::{ops::Deref, slice}; +use limine::{LimineFramebuffer, LimineFramebufferRequest, LimineFramebufferResponse}; +use spin::{Lazy, Mutex, MutexGuard}; + +// + +pub fn init() { + static FB_REQ: LimineFramebufferRequest = LimineFramebufferRequest::new(0); + + let fbo = FB_REQ + .get_response() + .get() + .into_iter() + .flat_map(|resp| resp.framebuffers().into_iter()) + .find_map(|fb| { + if fb.bpp != 32 { + return None; + } + + let buf = unsafe { slice::from_raw_parts_mut(fb.address.as_ptr()?, fb.size()) }; + Some(Framebuffer { + buf, + width: fb.width as _, + height: fb.height as _, + pitch: fb.pitch as _, + }) + }); + + if let Some(fbo) = fbo { + FBO.call_once(|| Mutex::new(fbo)); + } + println!("Global framebuffer {:#?}", FBO.get()) +} diff --git a/src/arch/x86_64/limine/mod.rs b/src/arch/x86_64/limine/mod.rs index ef9274aceb65802760e45470b8bafa801a6a9c00..032aa5ea4b50beb1940861d6828d2ee82bfdba1a 100644 --- a/src/arch/x86_64/limine/mod.rs +++ b/src/arch/x86_64/limine/mod.rs @@ -1,52 +1,36 @@ -use core::fmt::{self, Arguments, Write}; -use limine::{LimineTerminalRequest, LimineTerminalResponse}; -use spin::{Lazy, Mutex, MutexGuard, Once}; +use super::{gdt, idt}; // -#[no_mangle] -pub extern "C" fn _start() -> ! { - *crate::BOOTLOADER.lock() = "Limine"; - crate::kernel_main() -} +pub use term::_print; // -struct Writer(pub &'static LimineTerminalResponse); +mod framebuffer; +mod term; -unsafe impl Send for Writer {} +// -impl Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - let mut write = self.0.write().ok_or(fmt::Error)?; +#[no_mangle] +pub extern "C" fn _start() -> ! { + x86_64::instructions::interrupts::disable(); + *crate::BOOTLOADER.lock() = "Limine"; - for term in self.0.terminals() { - write(term, s); - } + framebuffer::init(); - Ok(()) - } -} + // the initial terminal logger crashes if used after initializing GDT and IDT + crate::log::disable_term(); -static TERMINALS: LimineTerminalRequest = LimineTerminalRequest::new(0); -static WRITER: Once<Mutex<Writer>> = Once::new(); + gdt::init(); + idt::init(); -fn get() -> Result<MutexGuard<'static, Writer>, fmt::Error> { - WRITER.try_call_once(|| { - Ok(Mutex::new(Writer( - TERMINALS.get_response().get().ok_or(fmt::Error)?, - ))) - })?; - WRITER.get().ok_or(fmt::Error).map(|mutex| mutex.lock()) -} + x86_64::instructions::interrupts::enable(); -fn print(args: Arguments) -> Option<()> { - Some(()) + crate::kernel_main() } -#[doc(hidden)] -pub fn _print(args: Arguments) { - if let Ok(mut writer) = get() { - _ = writer.write_fmt(args) +pub fn done() -> ! { + loop { + x86_64::instructions::hlt(); } } diff --git a/src/arch/x86_64/limine/term.rs b/src/arch/x86_64/limine/term.rs new file mode 100644 index 0000000000000000000000000000000000000000..97446b77877f01611012ee0c0c732d57570d3181 --- /dev/null +++ b/src/arch/x86_64/limine/term.rs @@ -0,0 +1,42 @@ +use core::fmt::{self, Arguments, Write}; +use limine::{LimineTerminalRequest, LimineTerminalResponse}; +use spin::{Mutex, MutexGuard, Once}; + +// + +#[doc(hidden)] +pub fn _print(args: Arguments) { + if let Ok(mut writer) = get() { + _ = writer.write_fmt(args) + } +} + +// + +struct Writer(pub &'static LimineTerminalResponse); + +unsafe impl Send for Writer {} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + let mut write = self.0.write().ok_or(fmt::Error)?; + + for term in self.0.terminals() { + write(term, s); + } + + Ok(()) + } +} + +static TERMINALS: LimineTerminalRequest = LimineTerminalRequest::new(0); +static WRITER: Once<Mutex<Writer>> = Once::new(); + +fn get() -> Result<MutexGuard<'static, Writer>, fmt::Error> { + WRITER.try_call_once(|| { + Ok(Mutex::new(Writer( + TERMINALS.get_response().get().ok_or(fmt::Error)?, + ))) + })?; + WRITER.get().ok_or(fmt::Error).map(|mutex| mutex.lock()) +} diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 42f8285e42045437e0c818ec14c0073133b4e7b8..34c7127a46141758b8460ed11e2f1fdd8e05c7c1 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -1,15 +1,16 @@ #[cfg(feature = "multiboot1")] #[path = "multiboot1/mod.rs"] pub mod boot; - #[cfg(feature = "multiboot2")] #[path = "multiboot2/mod.rs"] pub mod boot; - #[cfg(feature = "bootboot")] #[path = "bootboot/mod.rs"] pub mod boot; - #[cfg(feature = "limine")] #[path = "limine/mod.rs"] pub mod boot; +pub use boot::*; + +pub mod gdt; +pub mod idt; diff --git a/src/log.rs b/src/log.rs index b8e024dcbb05b35200b265a395b8583574400978..92d39e76880e313c536abd326346dae62c151fa0 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,4 +1,7 @@ -use core::fmt::Arguments; +use core::{ + fmt::Arguments, + sync::atomic::{AtomicBool, Ordering}, +}; use spin::Lazy; // @@ -10,8 +13,26 @@ macro_rules! print { #[macro_export] macro_rules! println { - () => { $crate::log::_print(format_args!("\n")); }; - ($($t:tt)*) => { $crate::log::_print(format_args_nl!($($t)*)); }; + () => { $crate::log::_print(format_args!("\n")) }; + ($($t:tt)*) => { $crate::log::_print(format_args_nl!($($t)*)) }; +} + +// + +pub fn enable_term() { + LOGGER.term.store(true, Ordering::SeqCst); +} + +pub fn disable_term() { + LOGGER.term.store(false, Ordering::SeqCst); +} + +pub fn enable_qemu() { + LOGGER.qemu.store(true, Ordering::SeqCst); +} + +pub fn disable_qemu() { + LOGGER.qemu.store(false, Ordering::SeqCst); } // @@ -19,25 +40,25 @@ macro_rules! println { static LOGGER: Lazy<Logger> = Lazy::new(Logger::init); struct Logger { - term: bool, - qemu: bool, + term: AtomicBool, + qemu: AtomicBool, } impl Logger { fn init() -> Self { Logger { - term: true, - qemu: true, + term: true.into(), + qemu: true.into(), } } fn print(&self, args: Arguments) { - if self.term { - crate::arch::boot::_print(args); - } - if self.qemu { + if self.qemu.load(Ordering::SeqCst) { crate::qemu::_print(args); } + if self.term.load(Ordering::SeqCst) { + crate::arch::boot::_print(args); + } } } diff --git a/src/main.rs b/src/main.rs index 77827b89551ee834ce728191804b522f9f3f33f8..4d81da639e8ab8a3e2880d86b5ab32669c9c8087 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,29 +2,72 @@ #![no_main] #![feature(format_args_nl)] #![feature(abi_x86_interrupt)] +#![feature(custom_test_frameworks)] +#![feature(type_alias_impl_trait)] +#![test_runner(crate::testfw::test_runner)] +#![reexport_test_harness_main = "test_main"] + +// use spin::Mutex; +use crate::video::framebuffer::{Color, FBO}; + +// + #[path = "arch/x86_64/mod.rs"] pub mod arch; pub mod log; +pub mod panic; pub mod qemu; -// pub mod vga; +#[cfg(test)] +pub mod testfw; +pub mod video; -static BOOTLOADER: Mutex<&'static str> = Mutex::new("Hyperion"); +// -#[panic_handler] -fn panic_handler(_: &core::panic::PanicInfo) -> ! { - loop {} -} +/// Name of the kernel +pub static KERNEL: &'static str = if cfg!(test) { + "Hyperion-Testing" +} else { + "Hyperion" +}; + +/// Name of the detected bootloader +pub static BOOTLOADER: Mutex<&'static str> = Mutex::new(KERNEL); + +// fn kernel_main() -> ! { - println!("Hello from Hyperion"); - println!(" - Hyperion was booted with {}", BOOTLOADER.lock()); + println!("Hello from {KERNEL}"); + println!(" - {KERNEL} was booted with {}", BOOTLOADER.lock()); + + // error handling test + // stack_overflow(79999999); + // unsafe { + // *(0xFFFFFFFFDEADC0DE as *mut u8) = 42; + // } - loop { - unsafe { - core::arch::asm!("hlt"); - } + if let Some(fbo) = FBO.get() { + let mut fbo = fbo.lock(); + fbo.fill(40, 40, 40, 40, Color::RED); + fbo.fill(50, 50, 60, 40, Color::GREEN); + fbo.fill(5, 15, 80, 20, Color::BLUE); + } + + #[cfg(test)] + test_main(); + + arch::done(); +} + +fn stack_overflow(n: usize) { + if n == 0 { + return; + } else { + stack_overflow(n - 1); + } + unsafe { + core::ptr::read_volatile(&0 as *const i32); } } diff --git a/src/panic.rs b/src/panic.rs new file mode 100644 index 0000000000000000000000000000000000000000..d69b7998e0c087a58737629edd7786ab0bfd8165 --- /dev/null +++ b/src/panic.rs @@ -0,0 +1,18 @@ +use crate::arch::done; +use core::panic::PanicInfo; + +// + +#[cfg(not(feature = "tests"))] +#[panic_handler] +fn panic_handler(info: &PanicInfo) -> ! { + crate::println!("{info}"); + done(); +} + +#[cfg(feature = "tests")] +#[panic_handler] +fn panic_handler(info: &PanicInfo) -> ! { + crate::testfw::test_panic_handler(info); + done(); +} diff --git a/src/qemu.rs b/src/qemu.rs index d2c85f9e6540c4431149f7690b99aeaa30505fb6..cb30c3ac3d39da6d8a0db2a7558427464456a34e 100644 --- a/src/qemu.rs +++ b/src/qemu.rs @@ -1,19 +1,35 @@ -use core::fmt::{Arguments, Write}; +use core::{ + fmt::{Arguments, Write}, + sync::atomic::AtomicUsize, +}; use spin::{Lazy, Mutex}; use uart_16550::SerialPort; // +#[doc(hidden)] +pub fn _print(args: Arguments) { + if let Some(mut writer) = COM1.try_lock() { + // COM1_LOCKER.store(crate::THREAD, Ordering::SeqCst); + _ = writer.write_fmt(args); + } +} + +/// Unlocks the COM1 writer IF it is locked by this exact thread +pub unsafe fn unlock() { + // TODO: SMP + // if COM1_LOCKER.load(Ordering::SeqCst) != crate::THREAD { + // return; + // } + + COM1.force_unlock() +} + +// + +static COM1_LOCKER: AtomicUsize = AtomicUsize::new(0); static COM1: Lazy<Mutex<SerialPort>> = Lazy::new(|| { let mut port = unsafe { SerialPort::new(0x3f8) }; port.init(); Mutex::new(port) }); - -// - -#[doc(hidden)] -pub fn _print(args: Arguments) { - let mut writer = COM1.lock(); - writer.write_fmt(args).unwrap(); -} diff --git a/src/testfw.rs b/src/testfw.rs new file mode 100644 index 0000000000000000000000000000000000000000..a89324ce0f3d82bb6154664e4f3b4571b6230bcd --- /dev/null +++ b/src/testfw.rs @@ -0,0 +1,71 @@ +use crate::{arch::done, print, println}; +use core::{any::type_name, panic::PanicInfo}; +use x86_64::instructions::port::Port; + +// + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub trait TestCase { + fn run(&self); +} + +// + +impl<F: Fn()> TestCase for F { + fn run(&self) { + let name = type_name::<Self>(); + name.len(); + print!(" - {name:.<40}"); + self(); + println!("[ok]"); + } +} + +// + +pub fn exit_qemu(exit_code: QemuExitCode) { + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +pub fn test_runner(tests: &[&dyn TestCase]) { + println!("Running {} tests", tests.len()); + for test in tests { + // unsafe { + // core::intrinsics::r#try( + // move |_| test(), + // 0 as _, + // |_, _| { + // println!("[failed]\n"); + // }, + // ); + // } + + // TODO: core::panic::catch_unwind // https://github.com/rust-lang/rfcs/issues/2810 + + test.run(); + } + + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) { + println!("[failed]\n{info}\n"); + exit_qemu(QemuExitCode::Failed); +} + +#[cfg(test)] +mod tests { + #[test_case] + fn trivial() { + assert_eq!(0, 0); + } +} diff --git a/src/vga.rs b/src/vga.rs deleted file mode 100644 index 1c404904a5e17c622c1f5fd2ff643f6e3c476080..0000000000000000000000000000000000000000 --- a/src/vga.rs +++ /dev/null @@ -1,225 +0,0 @@ -use core::{ - fmt::{Arguments, Write}, - ops::{Deref, DerefMut}, -}; -use spin::{Mutex, MutexGuard}; -use volatile::Volatile; - -// - -pub struct Writer { - cursor: [usize; 2], - color: ColorCode, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -#[repr(C)] -pub enum Color { - #[default] - Black = 0, - Blue = 1, - Green = 2, - Cyan = 3, - Red = 4, - Magenta = 5, - Brown = 6, - LightGrey = 7, - DarkGrey = 8, - LightBlue = 9, - LightGreen = 10, - LightCyan = 11, - LightRed = 12, - Pink = 13, - Yellow = 14, - White = 15, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct ColorCode(u8); - -// - -impl Writer { - pub fn lock() -> MutexGuard<'static, Self> { - WRITER.lock() - } - - pub fn write_str(&mut self, s: &str) { - for byte in s.bytes() { - self.write_byte(byte); - } - } - - pub fn write_char(&mut self, c: char) { - self.write_str(c.encode_utf8(&mut [0; 4])) - } - - pub fn write_byte(&mut self, byte: u8) { - match byte { - // 'special' ascii chars - b'\r' => self.cursor[0] = 0, - b'\n' => self.new_line(), - b'\0' => self.clear(), - - // 'normal' ascii chars - byte => { - // line wrapping - if self.cursor[0] >= WIDTH { - self.new_line(); - } - - // insert the byte - self.set_char( - self.cursor, - Char { - byte, - color: self.color, - }, - ); - - // move the cursor - self.cursor[0] += 1; - } - } - } - - pub fn clear(&mut self) { - self.cursor = [0, 0]; - for row in 0..HEIGHT { - self.clear_row(row); - } - } - - /// SAFETY: Only one [`Writer`] should ever exist - const unsafe fn new() -> Self { - Self { - cursor: [0, 0], - color: ColorCode::new(Color::White, Color::Black), - } - } - - fn buffer(&self) -> &'static [[Volatile<Char>; WIDTH]; HEIGHT] { - // SAFETY: Only one [`Writer`] should ever exist - // then multiple immutable refs are allowed - unsafe { &*(0xB8000 as *const _) } - } - - fn buffer_mut(&mut self) -> &'static mut [[Volatile<Char>; WIDTH]; HEIGHT] { - // SAFETY: Only one [`Writer`] should ever exist - // then one mutable ref is allowed - unsafe { &mut *(0xB8000 as *mut _) } - } - - fn new_line(&mut self) { - if self.cursor[1] + 1 >= HEIGHT { - // move all rows upwards - for row in 0..HEIGHT - 1 { - for col in 0..WIDTH { - self.set_char([col, row], self.get_char([col, row + 1])); - } - } - } else { - // next row - self.cursor[1] += 1; - } - self.clear_row(HEIGHT - 1); - self.cursor[0] = 0; - } - - fn clear_row(&mut self, row: usize) { - self.fill_row( - row, - Char { - byte: b' ', - color: ColorCode::default(), - }, - ) - } - - fn fill_row(&mut self, row: usize, fill: Char) { - for col in 0..WIDTH { - self.set_char([col, row], fill); - } - } - - fn get_char(&self, cursor: [usize; 2]) -> Char { - self.buffer()[cursor[1]][cursor[0]].read() - } - - fn set_char(&mut self, cursor: [usize; 2], ch: Char) { - self.buffer_mut()[cursor[1]][cursor[0]].write(ch); - } -} - -impl ColorCode { - pub const fn new(fg: Color, bg: Color) -> ColorCode { - ColorCode((bg as u8) << 4 | (fg as u8)) - } -} - -impl Default for ColorCode { - fn default() -> Self { - Self::new(Color::White, Color::Black) - } -} - -// - -const WIDTH: usize = 80; -const HEIGHT: usize = 25; - -// - -/// SAFETY: safe, because this is the only Writer -static WRITER: Mutex<Writer> = Mutex::new(unsafe { Writer::new() }); - -// - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -struct Char { - // ascii - byte: u8, - - // foreground and background - color: ColorCode, -} - -// - -impl Write for Writer { - fn write_str(&mut self, s: &str) -> core::fmt::Result { - self.write_str(s); - Ok(()) - } -} - -impl Deref for Char { - type Target = Self; - - fn deref(&self) -> &Self::Target { - self - } -} - -impl DerefMut for Char { - fn deref_mut(&mut self) -> &mut Self::Target { - self - } -} - -// - -#[doc(hidden)] -pub fn _print(args: Arguments) { - let mut writer = WRITER.lock(); - writer.write_fmt(args).unwrap(); -} - -#[doc(hidden)] -pub fn _println(args: Arguments) { - let mut writer = WRITER.lock(); - writer.write_fmt(args).unwrap(); - writer.write_byte(b'\n'); -} diff --git a/src/video/font.rs b/src/video/font.rs new file mode 100644 index 0000000000000000000000000000000000000000..9d16b727050a01d25c5bdc96fd11ed8de200441d --- /dev/null +++ b/src/video/font.rs @@ -0,0 +1,29 @@ +// #[derive(Debug, Clone, Copy, Default)] +// pub struct FontChar { +// bitmap: [u8; 16], +// } + +pub static FONT: [[u8; 16]; 256] = { + let mut font = [[0u8; 16]; 256]; + + font[b'a' as usize] = [ + 0b_11111111, + 0b_11111111, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, + 0b_11000011, // + 0b_11111111, + 0b_11111111, + ]; + + font +}; diff --git a/src/video/framebuffer.rs b/src/video/framebuffer.rs new file mode 100644 index 0000000000000000000000000000000000000000..1f96c5cadbba64854b544a3b2d419e2c876948dc --- /dev/null +++ b/src/video/framebuffer.rs @@ -0,0 +1,73 @@ +use core::{ + fmt, + ops::{Deref, DerefMut}, +}; +use spin::{Mutex, Once}; + +// + +pub static FBO: Once<Mutex<Framebuffer>> = Once::new(); + +// + +pub struct Framebuffer { + pub buf: &'static mut [u8], + + pub width: usize, // not the pixels to the next row + pub height: usize, + pub pitch: usize, // pixels to the next row +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Color { + r: u8, + g: u8, + b: u8, +} + +// + +impl Framebuffer { + pub fn set(&mut self, x: usize, y: usize, color: Color) { + let spot = x * 4 + y * self.pitch; + self.buf[spot..spot + 4].copy_from_slice(&color.as_arr()[..]); + } + + pub fn fill(&mut self, x: usize, y: usize, w: usize, h: usize, color: Color) { + for yd in 0..h { + for xd in 0..w { + self.set(x + xd, y + yd, color); + } + } + } +} + +impl Color { + pub const WHITE: Color = Color::new(0xff, 0xff, 0xff); + + pub const RED: Color = Color::new(0xff, 0x00, 0x00); + pub const GREEN: Color = Color::new(0x00, 0xff, 0x00); + pub const BLUE: Color = Color::new(0x00, 0x00, 0xff); + + pub const fn new(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b } + } + + pub const fn as_u32(&self) -> u32 { + u32::from_ne_bytes([self.r, self.g, self.b, 0]) + } + + pub const fn as_arr(&self) -> [u8; 4] { + [self.r, self.g, self.b, 0] + } +} + +impl fmt::Debug for Framebuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Framebuffer") + .field("width", &self.width) + .field("height", &self.height) + .field("pitch", &self.pitch) + .finish_non_exhaustive() + } +} diff --git a/src/video/mod.rs b/src/video/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..27cbe8d094ff97edf519ebf411d2fa235aac65be --- /dev/null +++ b/src/video/mod.rs @@ -0,0 +1,2 @@ +pub mod font; +pub mod framebuffer;