Major log rework

This commit is contained in:
August 2025-10-02 01:03:58 -04:00
parent 0399650788
commit 8a87fa80b1
Signed by: shibedrill
GPG Key ID: 5FE0CB25945EFAA2
5 changed files with 399 additions and 111 deletions

View File

@ -176,4 +176,4 @@ command = "gdb"
args = ["${ARTIFACTDIR}/kernel", "--eval-command=target remote localhost:1234"]
[tasks.debug]
run_task = { name = ["debug-run", "debug-view"], fork = true, parallel = true }
run_task = { name = ["debug_run", "debug_view"], fork = true, parallel = true }

View File

@ -1,13 +1,342 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::log::LogSubscriber;
use x86_64::instructions::port::Port;
#![allow(dead_code)]
impl LogSubscriber for Port<u8> {
fn log_write(&mut self, msg: &str) {
for c in msg.chars() {
unsafe { self.write(c as u8) };
use enumflags2::{BitFlag, BitFlags};
use intbits::Bits;
use x86_64::instructions::port::Port;
use num_traits::FromPrimitive;
use num_derive::FromPrimitive;
/// Represents an x86 port-mapped serial port.
pub struct SerialPort {
base_port: Port<u8>,
interrupt_enable: Port<u8>,
interrupt_id_fifo_control: Port<u8>,
line_control: Port<u8>,
modem_control: Port<u8>,
line_status: Port<u8>,
modem_status: Port<u8>,
scratch: Port<u8>,
fifo_register: u8,
pub crlf: Crlf,
}
#[derive(PartialEq, Eq)]
pub enum Crlf {
Lf,
Crlf,
}
#[derive(FromPrimitive)]
#[repr(u8)]
pub enum DataBits {
Five = 0,
Six = 1,
Seven = 2,
Eight = 3,
}
#[derive(FromPrimitive)]
#[repr(u8)]
pub enum ParityBits {
None = 0b000,
Odd = 0b001,
Even = 0b011,
Mark = 0b101,
Space = 0b111,
}
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum InterruptEnable {
ModemStatus = 0b1000,
ReceiverLineStatus = 0b100,
TransmitterHoldingRegisterEmpty = 0b10,
ReceivedDataAvail = 0b1,
}
#[enumflags2::bitflags]
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ModemControl {
DataTerminalReady = 0b00000001,
RequestToSend = 0b00000010,
Out1 = 0b00000100,
Out2 = 0b00001000,
Loop = 0b00010000,
}
#[repr(u8)]
#[derive(FromPrimitive)]
pub enum TriggerLevel {
OneByte = 0b00,
FourBytes = 0b01,
EightBytes = 0b10,
FourteenBytes = 0b11,
}
#[repr(u8)]
#[derive(FromPrimitive)]
pub enum InterruptState {
ModemStatus = 0b00,
TransmitterEmpty = 0b01,
ReceivedDataAvail = 0b10,
ReceiverLineStatus = 0b11,
}
#[repr(u8)]
#[derive(FromPrimitive)]
pub enum FifoState {
NoFifo = 0b00,
FifoEnabledUnusable = 0b01,
FifoEnabled = 0b10,
}
#[repr(u8)]
#[enumflags2::bitflags]
#[derive(Clone, Copy, Debug)]
pub enum LineStatus {
DataReady = 0b00000001,
OverrunError = 0b00000010,
ParityError = 0b00000100,
FramingError = 0b00001000,
BreakIndicator = 0b00010000,
TransmitterHoldingRegisterEmpty = 0b00100000,
TransmitterEmpty = 0b01000000,
ImpendingError = 0b10000000,
}
#[repr(u8)]
#[enumflags2::bitflags]
#[derive(Clone, Copy, Debug)]
pub enum ModemStatus {
DeltaClearToSend = 0b00000001,
DeltaDataSetReady = 0b00000010,
TrailingEdgeRingIndicator = 0b00000100,
DeltaDataCarrierDetect = 0b00001000,
ClearToSend = 0b00010000,
DataSetReady = 0b00100000,
RingIndicator = 0b01000000,
DataCarrierDetect = 0b10000000,
}
impl SerialPort {
pub fn log_write(&mut self, msg: &str) {
if self.crlf == Crlf::Crlf {
for c in msg.chars() {
if c == '\n' {
self.write_char('\r');
}
self.write_char(c);
}
} else {
for c in msg.chars() {
self.write_char(c);
}
}
}
pub fn log_writeln(&mut self, msg: &str) {
self.log_write(msg);
self.log_write("\n");
}
/// Create a new port-mapped serial port with the base address `port_addr`.
pub fn new(port_addr: u16) -> Self {
let mut port = SerialPort {
base_port: Port::new(port_addr),
interrupt_enable: Port::new(port_addr + 1),
interrupt_id_fifo_control: Port::new(port_addr + 2),
line_control: Port::new(port_addr + 3),
modem_control: Port::new(port_addr + 4),
line_status: Port::new(port_addr + 5),
modem_status: Port::new(port_addr + 6),
scratch: Port::new(port_addr + 7),
fifo_register: 0,
crlf: Crlf::Crlf,
};
// ensure this is false
port.set_dlab(false);
port
}
fn get_line_control(&mut self) -> u8 {
unsafe { self.line_control.read() }
}
fn set_line_control(&mut self, value: u8) {
unsafe { self.line_control.write(value) };
}
fn get_dlab(&mut self) -> bool {
self.get_line_control().bit(7)
}
fn set_dlab(&mut self, dlab: bool) {
let mut new_lcr = unsafe {self.line_control.read()};
new_lcr.set_bit(7, dlab);
unsafe { self.line_control.write(new_lcr) };
}
/// Read a single character from the RX buffer.
pub fn read_char(&mut self) -> u8 {
unsafe { self.base_port.read() }
}
/// Write a single character to the TX buffer.
pub fn write_char(&mut self, c: char) {
unsafe { self.base_port.write(c as u8) };
}
/// Get the baud rate divisor.
pub fn get_divisor(&mut self) -> u16 {
self.set_dlab(true);
let result: u16 = unsafe { self.base_port.read() as u16 | ((self.interrupt_enable.read() as u16) << 8) as u16};
self.set_dlab(false);
result
}
/// Set the baud rate divisor.
pub fn set_divisor(&mut self, divisor: u16) {
self.set_dlab(true);
unsafe {
self.base_port.write((divisor & 0b11111111) as u8);
self.interrupt_enable.write(((divisor & 0b1111111100000000) >> 8) as u8);
}
self.set_dlab(false);
}
/// Get the number of bits in a character.
pub fn get_data_bits(&mut self) -> DataBits {
DataBits::from_u8(self.get_line_control().bits(0..=1)).unwrap()
}
/// Set the number of bits in a character.
pub fn set_data_bits(&mut self, bits: DataBits) {
let new_value = self.get_line_control().bits(2..=7) | bits as u8;
self.set_line_control(new_value);
}
/// Get the number of bits sent after each character.
/// A value of False means only 1 bit will be sent.
/// A value of True means 2 bits will be sent.
/// If the character length is 5 bits, a value of True means 1.5 bits will be sent.
pub fn get_stop_bits(&mut self) -> bool {
self.get_line_control().bit(2)
}
/// Set the number of bits sent after each character.
/// A value of False means only 1 bit will be sent.
/// A value of True means 2 bits will be sent.
/// If the character length is 5 bits, a value of True means 1.5 bits will be sent.
pub fn set_stop_bits(&mut self, bit: bool) {
let mut new_value = self.get_line_control();
new_value.set_bit(2, bit);
self.set_line_control(new_value);
}
/// Get the parity strategy.
pub fn get_parity_bits(&mut self) -> ParityBits {
ParityBits::from_u8(self.get_line_control().bits(3..=5) >> 3).unwrap()
}
/// Set the parity strategy.
pub fn set_parity_bits(&mut self, parity: ParityBits) {
let mut new_value = self.get_line_control();
new_value.set_bits(3..=5, parity as u8);
self.set_line_control(new_value);
}
/// Get the Interrupt Enable control register.
pub fn get_interrupt_enable(&mut self) -> BitFlags<InterruptEnable> {
InterruptEnable::from_bits(unsafe {self.interrupt_enable.read().bits(0..=3)}).unwrap()
}
/// Set the Interrupt Enable control register.
pub fn set_interrupt_enable(&mut self, interrupt: InterruptEnable) {
unsafe { self.interrupt_enable.write(interrupt as u8) };
}
/// Set the FIFO control register.
pub fn set_fifo_control(&mut self, value: u8) {
unsafe { self.interrupt_id_fifo_control.write(value) };
}
/// Clear the transmitter FIFO.
pub fn clear_tx_fifo(&mut self) {
let mut new_value = self.fifo_register;
new_value.set_bit(2, true);
self.set_fifo_control(new_value);
}
/// Clear the receiver FIFO.
pub fn clear_rx_fifo(&mut self) {
let mut new_value = self.fifo_register;
new_value.set_bit(1, true);
self.set_fifo_control(new_value);
}
/// Enable or disable FIFOs.
pub fn set_fifo_enable(&mut self, enable: bool) {
self.fifo_register.set_bit(0, enable);
self.set_fifo_control(self.fifo_register);
}
/// Get whether FIFOs were enabled using this API.
pub fn get_fifo_enable(&mut self) -> bool {
self.fifo_register.bit(0)
}
/// Set the DMA mode of the serial controller.
pub fn set_dma_mode(&mut self, enable: bool) {
self.fifo_register.set_bit(3, enable);
self.set_fifo_control(self.fifo_register);
}
/// Get whether DMA mode was enabled using this API.
pub fn get_dma_mode(&mut self) -> bool {
self.fifo_register.bit(3)
}
/// Get the Interrupt Trigger Level.
pub fn get_interrupt_trigger_level(&mut self) -> TriggerLevel {
TriggerLevel::from_u8(self.fifo_register.bits(6..=7) >> 6).unwrap()
}
/// Set the Interrupt Trigger Level.
pub fn set_interrupt_trigger_level(&mut self, level: TriggerLevel) {
self.fifo_register.set_bits(6..=7, level as u8);
self.set_fifo_control(self.fifo_register);
}
/// Get the Interrupt ID status register.
pub fn get_interrupt_id(&mut self) -> u8 {
unsafe { self.interrupt_id_fifo_control.read() }
}
/// Get the state of the FIFO buffers.
pub fn get_fifo_buffer_state(&mut self) -> FifoState {
FifoState::from_u8(self.get_interrupt_id().bits(6..=7) >> 6).unwrap()
}
/// Get whether a timeout interrupt is pending. Only used on UART 16550 chips, otherwise reserved.
pub fn get_timeout_pending(&mut self) -> bool {
self.get_interrupt_id().bit(3)
}
/// Get the Interrupt State of the controller.
pub fn get_interrupt_state(&mut self) -> InterruptState {
InterruptState::from_u8(self.get_interrupt_id().bits(1..=2) >> 1).unwrap()
}
/// Get whether an interrupt is pending.
pub fn get_interrupt_pending(&mut self) -> bool {
!self.get_interrupt_id().bit(0)
}
/// Set the Modem Control register flags.
/// This OVERWRITES all held flags. To set specific flags, first store the current
/// value using `get_modem_control()`, then modify the flags you want to change.
pub fn set_modem_control(&mut self, control: BitFlags<ModemControl>) {
unsafe { self.modem_control.write(control.bits() as u8) };
}
/// Get the Modem Control register flags.
pub fn get_modem_control(&mut self) -> BitFlags<ModemControl> {
ModemControl::from_bits(unsafe { self.modem_control.read() & 0b0001111 }).unwrap()
}
/// Get the Line Status Register flags.
pub fn get_line_status(&mut self) -> BitFlags<LineStatus> {
LineStatus::from_bits(unsafe {self.line_status.read()}).unwrap()
}
/// Get the Modem Status Register flags.
pub fn get_modem_status(&mut self) -> BitFlags<ModemStatus> {
ModemStatus::from_bits(unsafe {self.modem_status.read()}).unwrap()
}
}

View File

@ -1,123 +1,78 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
use core::fmt::Write;
use crate::constants::{LOG_DEFAULT_LEVEL, NEWLINE};
use crate::memory::alloc;
use alloc::boxed::*;
use alloc::string::*;
use alloc::vec::*;
use crate::memory::alloc::vec::Vec;
use crate::memory::alloc::boxed::Box;
use crate::constants::LOG_DEFAULT_LEVEL;
use crate::memory::alloc::string::String;
use lazy_static::lazy_static;
use spin::Mutex;
use crate::format;
pub struct Logger {
pub level: LogLevel,
devices: Vec<Box<dyn Fn(&str) + Send + Sync>>
}
lazy_static! {
pub static ref LOGGER: Mutex<Logger> = Mutex::new(Logger::new());
}
#[macro_export]
macro_rules! log_info {
($($arg:tt)*) => {
LOGGER.log(LogLevel::Info, &format!($($arg)*), file!(), line!(), column!())
LOGGER.lock().log(LogLevel::Info, &format!($($arg)*), file!(), line!(), column!())
};
}
#[macro_export]
macro_rules! log_trace {
($($arg:tt)*) => {
LOGGER.log(LogLevel::Trace, &format!($($arg)*), file!(), line!(), column!())
LOGGER.lock().log(LogLevel::Trace, &format!($($arg)*), file!(), line!(), column!())
};
}
#[macro_export]
#[allow(unused_macros)]
macro_rules! log_warning {
($($arg:tt)*) => {
LOGGER.log(LogLevel::Warning, &format!($($arg)*), file!(), line!(), column!())
LOGGER.lock().log(LogLevel::Warning, &format!($($arg)*), file!(), line!(), column!())
};
}
#[macro_export]
#[allow(unused_macros)]
macro_rules! log_error {
($($arg:tt)*) => {
LOGGER.log(LogLevel::Error, &format!($($arg)*), file!(), line!(), column!())
LOGGER.lock().log(LogLevel::Error, &format!($($arg)*), file!(), line!(), column!())
};
}
#[macro_export]
#[allow(unused_macros)]
macro_rules! log_critical {
($($arg:tt)*) => {
LOGGER.log(LogLevel::Critical, &format!($($arg)*), file!(), line!(), column!())
LOGGER.lock().log(LogLevel::Critical, &format!($($arg)*), file!(), line!(), column!())
};
}
pub struct Logger {
pub inner: Mutex<LoggerInner>,
}
impl Logger {
pub fn log(&self, level: LogLevel, msg: &str, file: &'static str, line: u32, column: u32) {
self.inner.lock().log(level, msg, file, line, column);
pub fn new() -> Self {
Logger { level: LOG_DEFAULT_LEVEL, devices: Vec::new() }
}
pub fn add_subscriber<T: LogSubscriber + Send + Sync + 'static>(&self, sub: T) {
self.inner
.lock()
.subscriber
.push(Mutex::new(alloc::boxed::Box::new(sub)));
pub fn log(&mut self, level: LogLevel, message: &str, file: &'static str, line: u32, column: u32) {
for dev in &self.devices {
let message = format!("{} {}:{},{}- {}", level.normalized_string(), file, line, column, message);
dev(&message)
}
}
pub fn set_level(&self, level: LogLevel) {
self.inner.lock().level = level;
pub fn add_device(&mut self, device: Box<dyn Fn(&str) + Send + Sync>) {
self.devices.push(device);
}
}
// The logger exists for the entire lifetime of the kernel.
pub static LOGGER: Logger = Logger {
inner: Mutex::new(LoggerInner::new()),
};
pub struct EnumParseError {}
pub struct LoggerInner {
pub level: LogLevel,
pub subscriber: Vec<Mutex<Box<dyn LogSubscriber>>>,
}
pub trait LogSubscriber: Send + Sync {
fn log_write(&mut self, msg: &str);
}
impl Default for LoggerInner {
fn default() -> Self {
Self::new()
}
}
impl LoggerInner {
pub const fn new() -> Self {
LoggerInner {
level: LOG_DEFAULT_LEVEL,
subscriber: Vec::new(),
}
}
// Calling log will sequentially acquire lock on all logging subscribers
// to write to them with a formatted log message.
pub fn log(&self, level: LogLevel, msg: &str, file: &'static str, line: u32, column: u32) {
// Nobody is EVER allowed to call log with the Disabled log level. It is a placeholder.
if level == LogLevel::Disabled {
// Nothing
} else if level <= self.level {
let level_string = String::from(level);
for sub in &self.subscriber {
let mut message = String::new();
write!(
&mut message,
"[{level_string}] {file}:{line},{column} - {msg}{NEWLINE}"
)
.unwrap();
sub.lock().log_write(&message);
}
}
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum LogLevel {
Disabled = 0,
@ -171,3 +126,16 @@ impl From<LogLevel> for String {
.into()
}
}
impl LogLevel {
fn normalized_string(&self) -> &str {
match self {
LogLevel::Critical => "[CRIT ]",
LogLevel::Error => "[ERROR]",
LogLevel::Info => "[INFO ]",
LogLevel::Trace => "[TRACE]",
LogLevel::Warning => "[WARN ]",
LogLevel::Disabled => "[DSBLD]",
}
}
}

View File

@ -21,17 +21,20 @@ mod process;
mod util;
use boot::{BASE_REVISION, params, *};
use constants::*;
use limine::firmware_type::FirmwareType;
use log::*;
use memory::alloc::{format, string::*, vec};
use memory::alloc::{format, vec, boxed::Box, string::{String, ToString}};
use arch::x86_64::interrupts::IDT;
use params::*;
#[allow(unused_imports)]
use lzma_rs::lzma_decompress;
use lazy_static::lazy_static;
use limine::firmware_type::FirmwareType;
use spin::{mutex::Mutex};
use crate::arch::x86_64::serial::SerialPort;
use crate::constants::*;
use crate::arch::x86_64::interrupts::IDT;
lazy_static! {
pub static ref SERIAL_3F8: Mutex<SerialPort> = Mutex::new(arch::x86_64::serial::SerialPort::new(0x3f8));
}
#[unsafe(no_mangle)]
unsafe extern "C" fn main() -> ! {
@ -41,15 +44,12 @@ unsafe extern "C" fn main() -> ! {
// Ensure IDT exists
IDT.load();
#[cfg(target_arch = "x86_64")]
let serial: x86_64::instructions::port::Port<u8> = x86_64::instructions::port::Port::new(0x3f8);
// Set up logging level from params
// Nothing we can do here if this fails since no log subscribers are initialized yet
if let Some(level) = PARAMS.get("-loglevel")
&& let Ok(parsed_level) = LogLevel::try_from(level.as_str())
{
LOGGER.set_level(parsed_level);
LOGGER.lock().level = parsed_level;
}
// Add subscribers to logger
if let Some(device) = PARAMS.get("-logdev") {
@ -59,8 +59,8 @@ unsafe extern "C" fn main() -> ! {
}
if log_device_list.contains(&"serial") {
// TODO: Set up device discovery
#[cfg(target_arch = "x86_64")]
LOGGER.add_subscriber(serial);
let serial_logger = |msg: &str| {SERIAL_3F8.lock().log_writeln(msg)};
LOGGER.lock().add_device(Box::new(serial_logger));
}
log_trace!("Configured kernel logging devices");
}
@ -135,14 +135,9 @@ unsafe extern "C" fn main() -> ! {
log_info!("Hypervisor: {:?}", string.identify());
}
panic!("Bailing");
SERIAL_3F8.lock().log_write("hi");
#[allow(unreachable_code)]
loop {
for _i in 0..50000000u64 {
crate::arch::asm::nop();
}
core::hint::black_box(());
log_trace!("Heartbeat");
arch::asm::nop();
}
}

View File

@ -13,6 +13,7 @@ pub enum Syscall {
Resolve,
Spawn,
Version,
Yield,
}
#[derive(FromPrimitive, Debug)]
@ -20,22 +21,17 @@ pub enum SyscallStatus {
Success,
NoSuchCall,
NoSuchProcess,
NoSuchServer,
NoSuchService,
NoPermission,
OutOfMemory,
Aborted,
Unspecified,
Unknown,
Unimplemented,
}
pub struct UnknownStatus {}
impl From<u64> for SyscallStatus {
fn from(value: u64) -> Self {
SyscallStatus::from_u64(value).unwrap_or(Self::Unknown)
}
}
//pub fn exit(code: u64) -> SyscallStatus {
// arch::syscall_impl::caller_syscall_1(Syscall::Exit as u64, code).into()
//}