Skip to content

Commit

Permalink
Merge pull request rust-osdev#314 from asensio-project/improve_logging
Browse files Browse the repository at this point in the history
Improve Logging
  • Loading branch information
phil-opp authored Jan 9, 2023
2 parents fffa7ce + 7475960 commit 94c71d5
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 175 deletions.
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ fn main() {
(97, 9),
(106, 9),
(115, 1),
(116, 1),
(117, 1),
];

let mut code = String::new();
Expand Down
66 changes: 64 additions & 2 deletions api/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ pub struct BootloaderConfig {
/// Configuration for changing the level of the filter of the messages that are shown in the
/// screen when booting. The default is 'Trace'.
pub log_level: LevelFilter,

/// Whether the bootloader should print log messages to the framebuffer when booting.
///
/// Enabled by default.
pub frame_buffer_logger_status: LoggerStatus,

/// Whether the bootloader should print log messages to the serial port when booting.
///
/// Enabled by default.
pub serial_logger_status: LoggerStatus,
}

impl BootloaderConfig {
Expand All @@ -39,7 +49,7 @@ impl BootloaderConfig {
0x3D,
];
#[doc(hidden)]
pub const SERIALIZED_LEN: usize = 116;
pub const SERIALIZED_LEN: usize = 118;

/// Creates a new default configuration with the following values:
///
Expand All @@ -53,6 +63,8 @@ impl BootloaderConfig {
mappings: Mappings::new_default(),
frame_buffer: FrameBuffer::new_default(),
log_level: LevelFilter::Trace,
frame_buffer_logger_status: LoggerStatus::Enable,
serial_logger_status: LoggerStatus::Enable,
}
}

Expand All @@ -67,6 +79,8 @@ impl BootloaderConfig {
kernel_stack_size,
frame_buffer,
log_level,
frame_buffer_logger_status,
serial_logger_status,
} = self;
let ApiVersion {
version_major,
Expand Down Expand Up @@ -147,7 +161,15 @@ impl BootloaderConfig {
},
);

concat_115_1(buf, (*log_level as u8).to_le_bytes())
let log_level = concat_115_1(buf, (*log_level as u8).to_le_bytes());

let frame_buffer_logger_status =
concat_116_1(log_level, (*frame_buffer_logger_status as u8).to_le_bytes());

concat_117_1(
frame_buffer_logger_status,
(*serial_logger_status as u8).to_le_bytes(),
)
}

/// Tries to deserialize a config byte array that was created using [`Self::serialize`].
Expand Down Expand Up @@ -267,6 +289,21 @@ impl BootloaderConfig {
Option::None => return Err("log_level invalid"),
};

let (&frame_buffer_logger_status, s) = split_array_ref(s);
let frame_buffer_logger_status =
LoggerStatus::from_u8(u8::from_le_bytes(frame_buffer_logger_status));
let frame_buffer_logger_status = match frame_buffer_logger_status {
Option::Some(status) => status,
Option::None => return Err("frame_buffer_logger_status invalid"),
};

let (&serial_logger_status, s) = split_array_ref(s);
let serial_logger_status = LoggerStatus::from_u8(u8::from_le_bytes(serial_logger_status));
let serial_logger_status = match serial_logger_status {
Option::Some(status) => status,
Option::None => return Err("serial_logger_status invalid"),
};

if !s.is_empty() {
return Err("unexpected rest");
}
Expand All @@ -277,6 +314,8 @@ impl BootloaderConfig {
mappings,
frame_buffer,
log_level,
frame_buffer_logger_status,
serial_logger_status,
})
}

Expand All @@ -288,6 +327,8 @@ impl BootloaderConfig {
kernel_stack_size: rand::random(),
frame_buffer: FrameBuffer::random(),
log_level: LevelFilter::Trace,
frame_buffer_logger_status: LoggerStatus::Enable,
serial_logger_status: LoggerStatus::Enable,
}
}
}
Expand Down Expand Up @@ -587,6 +628,27 @@ impl LevelFilter {
}
}

/// An enum for enabling or disabling the different methods for logging.
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum LoggerStatus {
/// This method of logging is disabled
Disable,
/// This method of logging is enabled
Enable,
}

impl LoggerStatus {
/// Converts an u8 into a Option<LoggerStatus>
pub fn from_u8(value: u8) -> Option<LoggerStatus> {
match value {
0 => Some(Self::Disable),
1 => Some(Self::Enable),
_ => None,
}
}
}

/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677
///
/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized,
Expand Down
24 changes: 20 additions & 4 deletions bios/stage-4/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::memory_descriptor::MemoryRegion;
use bootloader_api::{
config::LevelFilter,
config::{LevelFilter, LoggerStatus},
info::{FrameBufferInfo, PixelFormat},
};
use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion};
Expand Down Expand Up @@ -109,7 +109,12 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! {
};
let kernel = Kernel::parse(kernel_slice);

let framebuffer_info = init_logger(info.framebuffer, kernel.config.log_level);
let framebuffer_info = init_logger(
info.framebuffer,
kernel.config.log_level,
kernel.config.frame_buffer_logger_status,
kernel.config.serial_logger_status,
);

log::info!("4th Stage");
log::info!("{info:x?}");
Expand All @@ -126,7 +131,12 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! {
load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info);
}

fn init_logger(info: BiosFramebufferInfo, log_level: LevelFilter) -> FrameBufferInfo {
fn init_logger(
info: BiosFramebufferInfo,
log_level: LevelFilter,
frame_buffer_logger_status: LoggerStatus,
serial_logger_status: LoggerStatus,
) -> FrameBufferInfo {
let framebuffer_info = FrameBufferInfo {
byte_len: info.region.len.try_into().unwrap(),
width: info.width.into(),
Expand Down Expand Up @@ -155,7 +165,13 @@ fn init_logger(info: BiosFramebufferInfo, log_level: LevelFilter) -> FrameBuffer
)
};

bootloader_x86_64_common::init_logger(framebuffer, framebuffer_info, log_level);
bootloader_x86_64_common::init_logger(
framebuffer,
framebuffer_info,
log_level,
frame_buffer_logger_status,
serial_logger_status,
);

framebuffer_info
}
Expand Down
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ xmas-elf = "0.8.0"
raw-cpuid = "10.2.0"
rand = { version = "0.8.4", default-features = false }
rand_hc = "0.3.1"
uart_16550 = "0.2.18"

[dependencies.noto-sans-mono-bitmap]
version = "0.2.0"
Expand Down
154 changes: 154 additions & 0 deletions common/src/framebuffer.rs
Original file line number Diff line number Diff line change
@@ -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 '�' character requires the feature "unicode-specials".
pub const BACKUP_CHAR: char = '�';

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(())
}
}
Loading

0 comments on commit 94c71d5

Please sign in to comment.