feat(kernel): Add logging to the kernel #2
56
Cargo.lock
generated
56
Cargo.lock
generated
@@ -17,6 +17,12 @@ version = "1.0.98"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@@ -99,6 +105,21 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "conquer-once"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c6d3a9775a69f6d1fe2cc888999b67ed30257d3da4d2af91984e722f2ec918a"
|
||||||
|
dependencies = [
|
||||||
|
"conquer-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "conquer-util"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e763eef8846b13b380f37dfecda401770b0ca4e56e95170237bd7c25c7db3582"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
@@ -192,6 +213,10 @@ name = "kernel"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bootloader_api",
|
"bootloader_api",
|
||||||
|
"conquer-once",
|
||||||
|
"log",
|
||||||
|
"noto-sans-mono-bitmap",
|
||||||
|
"spinning_top",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -212,6 +237,16 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf"
|
checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
@@ -237,6 +272,12 @@ version = "2.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "noto-sans-mono-bitmap"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a27daf9557165efe1d09b52f97393bf6283cadb0a76fbe64a1061e15553a994a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -304,6 +345,12 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@@ -345,6 +392,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "spinning_top"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.104"
|
version = "2.0.104"
|
||||||
|
@@ -10,3 +10,17 @@ publish.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bootloader_api = { version = "0.11.10" }
|
bootloader_api = { version = "0.11.10" }
|
||||||
|
conquer-once = { version = "0.3.2", default-features = false }
|
||||||
|
log = "0.4.17"
|
||||||
|
spinning_top = "0.2.4"
|
||||||
|
|
||||||
|
[dependencies.noto-sans-mono-bitmap]
|
||||||
|
version = "0.2.0"
|
||||||
|
default-features = false
|
||||||
|
features = [
|
||||||
|
"regular",
|
||||||
|
"size_16",
|
||||||
|
"unicode-basic-latin",
|
||||||
|
# required for the fallback char '<27>'
|
||||||
|
"unicode-specials",
|
||||||
|
]
|
||||||
|
154
kernel/src/logger/framebuffer.rs
Normal file
154
kernel/src/logger/framebuffer.rs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
use bootloader_api::info::{FrameBufferInfo, PixelFormat};
|
||||||
|
use core::{fmt, ptr};
|
||||||
|
use font_constants::BACKUP_CHAR;
|
||||||
|
use noto_sans_mono_bitmap::{
|
||||||
|
get_raster, get_raster_width, FontWeight, RasterHeight, RasterizedChar,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Additional vertical space between lines
|
||||||
|
const LINE_SPACING: usize = 2;
|
||||||
|
/// Additional horizontal space between characters.
|
||||||
|
const LETTER_SPACING: usize = 0;
|
||||||
|
|
||||||
|
/// Padding from the border. Prevent that font is too close to border.
|
||||||
|
const BORDER_PADDING: usize = 1;
|
||||||
|
|
||||||
|
/// Constants for the usage of the [`noto_sans_mono_bitmap`] crate.
|
||||||
|
mod font_constants {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Height of each char raster. The font size is ~0.84% of this. Thus, this is the line height that
|
||||||
|
/// enables multiple characters to be side-by-side and appear optically in one line in a natural way.
|
||||||
|
pub const CHAR_RASTER_HEIGHT: RasterHeight = RasterHeight::Size16;
|
||||||
|
|
||||||
|
/// The width of each single symbol of the mono space font.
|
||||||
|
pub const CHAR_RASTER_WIDTH: usize = get_raster_width(FontWeight::Regular, CHAR_RASTER_HEIGHT);
|
||||||
|
|
||||||
|
/// Backup character if a desired symbol is not available by the font.
|
||||||
|
/// The '<27>' character requires the feature "unicode-specials".
|
||||||
|
pub const BACKUP_CHAR: char = '<27>';
|
||||||
|
|
||||||
|
pub const FONT_WEIGHT: FontWeight = FontWeight::Regular;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raster of the given char or the raster of [`font_constants::BACKUP_CHAR`].
|
||||||
|
fn get_char_raster(c: char) -> RasterizedChar {
|
||||||
|
fn get(c: char) -> Option<RasterizedChar> {
|
||||||
|
get_raster(
|
||||||
|
c,
|
||||||
|
font_constants::FONT_WEIGHT,
|
||||||
|
font_constants::CHAR_RASTER_HEIGHT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
get(c).unwrap_or_else(|| get(BACKUP_CHAR).expect("Should get raster of backup char."))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows logging text to a pixel-based framebuffer.
|
||||||
|
pub struct FrameBufferWriter {
|
||||||
|
framebuffer: &'static mut [u8],
|
||||||
|
info: FrameBufferInfo,
|
||||||
|
x_pos: usize,
|
||||||
|
y_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameBufferWriter {
|
||||||
|
/// Creates a new logger that uses the given framebuffer.
|
||||||
|
pub fn new(framebuffer: &'static mut [u8], info: FrameBufferInfo) -> Self {
|
||||||
|
let mut logger = Self {
|
||||||
|
framebuffer,
|
||||||
|
info,
|
||||||
|
x_pos: 0,
|
||||||
|
y_pos: 0,
|
||||||
|
};
|
||||||
|
logger.clear();
|
||||||
|
logger
|
||||||
|
}
|
||||||
|
|
||||||
|
fn newline(&mut self) {
|
||||||
|
self.y_pos += font_constants::CHAR_RASTER_HEIGHT.val() + LINE_SPACING;
|
||||||
|
self.carriage_return()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carriage_return(&mut self) {
|
||||||
|
self.x_pos = BORDER_PADDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erases all text on the screen. Resets `self.x_pos` and `self.y_pos`.
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.x_pos = BORDER_PADDING;
|
||||||
|
self.y_pos = BORDER_PADDING;
|
||||||
|
self.framebuffer.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn width(&self) -> usize {
|
||||||
|
self.info.width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn height(&self) -> usize {
|
||||||
|
self.info.height
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a single char to the framebuffer. Takes care of special control characters, such as
|
||||||
|
/// newlines and carriage returns.
|
||||||
|
fn write_char(&mut self, c: char) {
|
||||||
|
match c {
|
||||||
|
'\n' => self.newline(),
|
||||||
|
'\r' => self.carriage_return(),
|
||||||
|
c => {
|
||||||
|
let new_xpos = self.x_pos + font_constants::CHAR_RASTER_WIDTH;
|
||||||
|
if new_xpos >= self.width() {
|
||||||
|
self.newline();
|
||||||
|
}
|
||||||
|
let new_ypos =
|
||||||
|
self.y_pos + font_constants::CHAR_RASTER_HEIGHT.val() + BORDER_PADDING;
|
||||||
|
if new_ypos >= self.height() {
|
||||||
|
self.clear();
|
||||||
|
}
|
||||||
|
self.write_rendered_char(get_char_raster(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prints a rendered char into the framebuffer.
|
||||||
|
/// Updates `self.x_pos`.
|
||||||
|
fn write_rendered_char(&mut self, rendered_char: RasterizedChar) {
|
||||||
|
for (y, row) in rendered_char.raster().iter().enumerate() {
|
||||||
|
for (x, byte) in row.iter().enumerate() {
|
||||||
|
self.write_pixel(self.x_pos + x, self.y_pos + y, *byte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.x_pos += rendered_char.width() + LETTER_SPACING;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) {
|
||||||
|
let pixel_offset = y * self.info.stride + x;
|
||||||
|
let color = match self.info.pixel_format {
|
||||||
|
PixelFormat::Rgb => [intensity, intensity, intensity / 2, 0],
|
||||||
|
PixelFormat::Bgr => [intensity / 2, intensity, intensity, 0],
|
||||||
|
PixelFormat::U8 => [if intensity > 200 { 0xf } else { 0 }, 0, 0, 0],
|
||||||
|
other => {
|
||||||
|
// set a supported (but invalid) pixel format before panicking to avoid a double
|
||||||
|
// panic; it might not be readable though
|
||||||
|
self.info.pixel_format = PixelFormat::Rgb;
|
||||||
|
panic!("pixel format {:?} not supported in logger", other)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let bytes_per_pixel = self.info.bytes_per_pixel;
|
||||||
|
let byte_offset = pixel_offset * bytes_per_pixel;
|
||||||
|
self.framebuffer[byte_offset..(byte_offset + bytes_per_pixel)]
|
||||||
|
.copy_from_slice(&color[..bytes_per_pixel]);
|
||||||
|
let _ = unsafe { ptr::read_volatile(&self.framebuffer[byte_offset]) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for FrameBufferWriter {}
|
||||||
|
unsafe impl Sync for FrameBufferWriter {}
|
||||||
|
|
||||||
|
impl fmt::Write for FrameBufferWriter {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
for c in s.chars() {
|
||||||
|
self.write_char(c);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
83
kernel/src/logger/logger.rs
Normal file
83
kernel/src/logger/logger.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use bootloader_api::info::FrameBufferInfo;
|
||||||
|
use conquer_once::spin::OnceCell;
|
||||||
|
use core::fmt::Write;
|
||||||
|
use spinning_top::Spinlock;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// The global logger instance used for the `log` crate.
|
||||||
|
static LOGGER: OnceCell<LockedLogger> = OnceCell::uninit();
|
||||||
|
|
||||||
|
/// A logger instance protected by a spinlock.
|
||||||
|
struct LockedLogger {
|
||||||
|
framebuffer: Option<Spinlock<framebuffer::FrameBufferWriter>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LockedLogger {
|
||||||
|
/// Create a new instance that logs to the given framebuffer.
|
||||||
|
pub fn new(
|
||||||
|
framebuffer: &'static mut [u8],
|
||||||
|
info: FrameBufferInfo,
|
||||||
|
) -> Self {
|
||||||
|
let framebuffer = Some(Spinlock::new(framebuffer::FrameBufferWriter::new(framebuffer, info)));
|
||||||
|
|
||||||
|
LockedLogger {
|
||||||
|
framebuffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Force-unlocks the logger to prevent a deadlock.
|
||||||
|
///
|
||||||
|
/// ## Safety
|
||||||
|
/// This method is not memory safe and should be only used when absolutely necessary.
|
||||||
|
pub unsafe fn force_unlock(&self) {
|
||||||
|
if let Some(framebuffer) = &self.framebuffer {
|
||||||
|
unsafe { framebuffer.force_unlock() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl log::Log for LockedLogger {
|
||||||
|
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if let Some(framebuffer) = &self.framebuffer {
|
||||||
|
let mut framebuffer = framebuffer.lock();
|
||||||
|
writeln!(framebuffer, "{:5}: {}", record.level(), record.args()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize a text-based logger using the given pixel-based framebuffer as output.
|
||||||
|
pub fn init_logger(
|
||||||
|
framebuffer: &'static mut [u8],
|
||||||
|
info: FrameBufferInfo,
|
||||||
|
) {
|
||||||
|
let logger = LOGGER.get_or_init(move || {
|
||||||
|
LockedLogger::new(
|
||||||
|
framebuffer,
|
||||||
|
info,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
log::set_logger(logger).expect("logger already set");
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
log::set_max_level(log::LevelFilter::Debug);
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
log::set_max_level(log::LevelFilter::Info);
|
||||||
|
|
||||||
|
log::debug!("Framebuffer info: {:?}", info);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn force_unlock() {
|
||||||
|
unsafe {
|
||||||
|
LOGGER
|
||||||
|
.get()
|
||||||
|
.map(|l| l.force_unlock())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
4
kernel/src/logger/mod.rs
Normal file
4
kernel/src/logger/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod logger;
|
||||||
|
mod framebuffer;
|
||||||
|
|
||||||
|
pub use logger::*;
|
@@ -1,16 +1,35 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use bootloader_api::{entry_point, BootInfo};
|
use bootloader_api::{
|
||||||
|
config::{BootloaderConfig, Mapping},
|
||||||
|
entry_point, BootInfo
|
||||||
|
};
|
||||||
|
|
||||||
|
mod logger;
|
||||||
|
|
||||||
|
pub static BOOTLOADER_CONFIG: BootloaderConfig = {
|
||||||
|
let mut config = BootloaderConfig::new_default();
|
||||||
|
config.mappings.physical_memory = Some(Mapping::Dynamic);
|
||||||
|
config
|
||||||
|
};
|
||||||
|
|
||||||
entry_point!(kernel_main);
|
entry_point!(kernel_main);
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static mut BootInfo) -> ! {
|
||||||
|
let info = boot_info.framebuffer.as_ref().unwrap().info();
|
||||||
|
let buffer = boot_info.framebuffer.as_mut().unwrap().buffer_mut();
|
||||||
|
|
||||||
|
logger::init_logger(buffer, info);
|
||||||
|
|
||||||
|
log::info!("Hello World from KERNEL");
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
fn panic(info: &core::panic::PanicInfo) -> ! {
|
fn panic(info: &core::panic::PanicInfo) -> ! {
|
||||||
|
logger::force_unlock();
|
||||||
|
log::error!("{}", info);
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user