Compare commits

...

5 Commits

Author SHA1 Message Date
50322be602
Remove CRLF bodge 2025-10-02 01:08:40 -04:00
249c1ae92c
Formatting 2025-10-02 01:05:14 -04:00
b3fe90ba25
Clippy lints 2025-10-02 01:04:58 -04:00
8a87fa80b1
Major log rework 2025-10-02 01:03:58 -04:00
0399650788
CPUID report log 2025-10-01 17:28:24 -04:00
8 changed files with 433 additions and 118 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;
#![allow(dead_code)]
use enumflags2::{BitFlag, BitFlags};
use intbits::Bits;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use x86_64::instructions::port::Port;
impl LogSubscriber for Port<u8> {
fn log_write(&mut self, msg: &str) {
for c in msg.chars() {
unsafe { self.write(c as u8) };
/// 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) };
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()) };
}
/// 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,7 +1,6 @@
// Copyright (c) 2025 shibedrill
// SPDX-License-Identifier: GPL-3.0-or-later
use crate::constants::NEWLINE;
use crate::format;
use crate::memory::alloc::string::String;
use crate::{LOGGER, LogLevel, log_trace};
@ -27,8 +26,7 @@ lazy_static! {
pub fn log_modules() {
if !MODULE_RESPONSE.modules().is_empty() {
let mut log_msg: String = String::from("Kernel modules list:");
log_msg.push_str(NEWLINE);
let mut log_msg: String = String::from("Kernel modules list:\n");
for module in MODULE_RESPONSE.modules() {
log_msg.push_str(&format!(
"\t{}",

View File

@ -8,8 +8,6 @@ use crate::format;
use crate::log::LogLevel;
use crate::memory::alloc::string::String;
pub static NEWLINE: &str = "\r\n";
#[bitflags]
#[repr(u8)]
#[derive(Debug, Clone, Copy)]

View File

@ -1,122 +1,92 @@
// 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::constants::LOG_DEFAULT_LEVEL;
use crate::format;
use crate::memory::alloc::boxed::Box;
use crate::memory::alloc::string::String;
use crate::memory::alloc::vec::Vec;
use lazy_static::lazy_static;
use spin::Mutex;
pub type LogDevice = dyn Fn(&str) + Send + Sync;
pub struct Logger {
pub level: LogLevel,
devices: Vec<Box<LogDevice>>,
}
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 add_subscriber<T: LogSubscriber + Send + Sync + 'static>(&self, sub: T) {
self.inner
.lock()
.subscriber
.push(Mutex::new(alloc::boxed::Box::new(sub)));
}
pub fn set_level(&self, level: LogLevel) {
self.inner.lock().level = level;
}
}
// 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 {
pub fn new() -> Self {
Logger {
level: LOG_DEFAULT_LEVEL,
subscriber: Vec::new(),
devices: 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);
}
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 add_device(&mut self, device: Box<dyn Fn(&str) + Send + Sync>) {
self.devices.push(device);
}
}
pub struct EnumParseError {}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum LogLevel {
@ -171,3 +141,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

@ -20,18 +20,27 @@ mod process;
#[macro_use]
mod util;
use arch::x86_64::interrupts::IDT;
use boot::{BASE_REVISION, params, *};
use constants::*;
use limine::firmware_type::FirmwareType;
use log::*;
use memory::alloc::{format, string::*, vec};
use memory::alloc::{
boxed::Box,
format,
string::{String, ToString},
vec,
};
use params::*;
#[allow(unused_imports)]
use lzma_rs::lzma_decompress;
use crate::arch::x86_64::serial::SerialPort;
use crate::constants::*;
use lazy_static::lazy_static;
use limine::firmware_type::FirmwareType;
use spin::mutex::Mutex;
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 +50,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 +65,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");
}
@ -124,14 +130,20 @@ unsafe extern "C" fn main() -> ! {
arch::x86_64::paging::get_mappings();
panic!("Bailing");
let cpuid = raw_cpuid::CpuId::new();
if let Some(string) = cpuid.get_processor_brand_string() {
log_info!("CPU brand string: {}", string.as_str());
}
if let Some(string) = cpuid.get_vendor_info() {
log_info!("CPU vendor: {}", string.as_str());
}
if let Some(string) = cpuid.get_hypervisor_info() {
log_info!("Hypervisor: {:?}", string.identify());
}
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

@ -3,7 +3,6 @@
use crate::boot::modules::MODULE_RESPONSE;
use crate::boot::params::EXECUTABLE_FILE_RESPONSE;
use crate::constants::NEWLINE;
use crate::{LOGGER, LogLevel, format, log_info, log_trace};
use alloc::string::String;
use free_list::{AllocError, FreeList, PAGE_SIZE, PageRange};
@ -121,7 +120,7 @@ pub fn log_memory() {
let mut unusable: u64 = 0;
for entry in MEMMAP_RESPONSE.entries() {
log_msg.push_str(&format!(
"{NEWLINE}\t0x{:x} bytes ({} pages) @ 0x{:x}: {}",
"\n\t0x{:x} bytes ({} pages) @ 0x{:x}: {}",
entry.length,
entry.length as usize / PAGE_SIZE,
entry.base,
@ -166,7 +165,7 @@ pub fn log_memory() {
log_trace!("{log_msg}");
let total = usable + reclaimable + hardware + unusable;
log_info!(
"Memory report: {NEWLINE}\tFree: 0x{usable:x}{NEWLINE}\tAvailable: 0x{reclaimable:x}{NEWLINE}\tUsable: 0x{:x}{NEWLINE}\tHardware: 0x{hardware:x}{NEWLINE}\tUnusable: 0x{unusable:x}{NEWLINE}\tTotal: 0x{total:x}",
"Memory report: \n\tFree: 0x{usable:x}\n\tAvailable: 0x{reclaimable:x}\n\tUsable: 0x{:x}\n\tHardware: 0x{hardware:x}\n\tUnusable: 0x{unusable:x}\n\tTotal: 0x{total:x}",
usable + reclaimable,
);
log_trace!("Deallocating available memory...");

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