Skip to content
Snippets Groups Projects
Commit e4d16684 authored by Eemeli Lehtonen's avatar Eemeli Lehtonen
Browse files

initial framebuffer impl

parent d53fd6d7
Branches
No related tags found
No related merge requests found
#[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"
......@@ -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
# 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 target/log.txt -d int,guest_errors -no-reboot -no-shutdown \
-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
file target/hyperion/x86_64/iso/hyperion
target remote localhost:1234
symbol-file target/hyperion/x86_64/iso/hyperion
......@@ -4,3 +4,4 @@ TIMEOUT=0
:Hyperion
PROTOCOL=limine
KERNEL_PATH=boot:///hyperion
# KERNEL_CMDLINE=
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
});
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();
}
}
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())
}
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();
}
}
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())
}
#[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;
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);
}
}
}
......
......@@ -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());
loop {
unsafe {
core::arch::asm!("hlt");
// error handling test
// stack_overflow(79999999);
// unsafe {
// *(0xFFFFFFFFDEADC0DE as *mut u8) = 42;
// }
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);
}
}
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();
}
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();
}
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);
}
}
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');
}
// #[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
};
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()
}
}
pub mod font;
pub mod framebuffer;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment