diff --git a/Cargo.lock b/Cargo.lock index 525849772..e8d69b418 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,6 +629,7 @@ dependencies = [ "acpi", "align_ext", "aml", + "bit_field", "bitflags 1.3.2", "bitvec", "buddy_system_allocator", diff --git a/framework/jinux-frame/Cargo.toml b/framework/jinux-frame/Cargo.toml index ca17e207b..46fee0f40 100644 --- a/framework/jinux-frame/Cargo.toml +++ b/framework/jinux-frame/Cargo.toml @@ -21,6 +21,7 @@ inherit-methods-macro = { git = "https://github.com/jinzhao-dev/inherit-methods- tdx-guest = { path = "../libs/tdx-guest", optional = true } bitvec = { version = "1.0", default-features = false, features = ["alloc"] } static_assertions = "1.1.0" +bit_field = "0.10.1" [target.x86_64-custom.dependencies] x86_64 = "0.14.2" diff --git a/framework/jinux-frame/src/arch/x86/console.rs b/framework/jinux-frame/src/arch/x86/console.rs new file mode 100644 index 000000000..cbd019ba7 --- /dev/null +++ b/framework/jinux-frame/src/arch/x86/console.rs @@ -0,0 +1,112 @@ +use core::fmt::Write; + +use crate::sync::SpinLock; +use crate::trap::IrqLine; +use alloc::fmt; +use alloc::{sync::Arc, vec::Vec}; +use log::debug; +use spin::Once; +use trapframe::TrapFrame; + +use super::device::serial::SerialPort; + +#[inline] +pub fn print(args: fmt::Arguments) { + Stdout.write_fmt(args).unwrap(); +} + +pub type InputCallback = dyn Fn(u8) + Send + Sync + 'static; + +pub fn register_console_input_callback(f: &'static InputCallback) { + SERIAL_INPUT_CALLBACKS.lock_irq_disabled().push(Arc::new(f)); +} + +struct Stdout; + +impl Write for Stdout { + fn write_str(&mut self, s: &str) -> fmt::Result { + for &c in s.as_bytes() { + send(c); + } + Ok(()) + } +} + +bitflags::bitflags! { + struct LineSts: u8 { + const INPUT_FULL = 1; + const OUTPUT_EMPTY = 1 << 5; + } +} + +static CONSOLE_COM1_PORT: SerialPort = unsafe { SerialPort::new(0x3F8) }; + +static CONSOLE_IRQ_CALLBACK: Once> = Once::new(); +static SERIAL_INPUT_CALLBACKS: SpinLock>> = SpinLock::new(Vec::new()); + +/// Initializes the serial port. +pub(crate) fn init() { + CONSOLE_COM1_PORT.init(); +} + +pub(crate) fn callback_init() { + let mut irq = crate::arch::x86::kernel::pic::allocate_irq(4).unwrap(); + irq.on_active(handle_serial_input); + CONSOLE_IRQ_CALLBACK.call_once(|| SpinLock::new(irq)); +} + +pub(crate) fn register_console_callback(callback: F) +where + F: Fn(&TrapFrame) + Sync + Send + 'static, +{ + CONSOLE_IRQ_CALLBACK + .get() + .unwrap() + .lock_irq_disabled() + .on_active(callback); +} + +fn handle_serial_input(trap_frame: &TrapFrame) { + // debug!("keyboard interrupt was met"); + let lock = if let Some(lock) = SERIAL_INPUT_CALLBACKS.try_lock() { + lock + } else { + return; + }; + let received_char = receive_char().unwrap(); + debug!("receive char = {:?}", received_char); + for callback in lock.iter() { + callback(received_char); + } +} + +fn line_sts() -> LineSts { + LineSts::from_bits_truncate(CONSOLE_COM1_PORT.line_status.read()) +} + +/// Sends a byte on the serial port. +pub fn send(data: u8) { + match data { + 8 | 0x7F => { + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + CONSOLE_COM1_PORT.data.write(8); + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + CONSOLE_COM1_PORT.data.write(b' '); + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + CONSOLE_COM1_PORT.data.write(8); + } + _ => { + while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} + CONSOLE_COM1_PORT.data.write(data); + } + } +} + +/// Receives a byte on the serial port. non-blocking +pub fn receive_char() -> Option { + if line_sts().contains(LineSts::INPUT_FULL) { + Some(CONSOLE_COM1_PORT.data.read()) + } else { + None + } +} diff --git a/framework/jinux-frame/src/arch/x86/device/serial.rs b/framework/jinux-frame/src/arch/x86/device/serial.rs index 094802324..8e628e995 100644 --- a/framework/jinux-frame/src/arch/x86/device/serial.rs +++ b/framework/jinux-frame/src/arch/x86/device/serial.rs @@ -1,118 +1,63 @@ //! A port-mapped UART. Copied from uart_16550. use crate::arch::x86::device::io_port::{IoPort, ReadWriteAccess, WriteOnlyAccess}; -use crate::sync::SpinLock; -use crate::trap::IrqLine; -use alloc::{sync::Arc, vec::Vec}; -use log::debug; -use spin::Once; -use trapframe::TrapFrame; -bitflags::bitflags! { - struct LineSts: u8 { - const INPUT_FULL = 1; - const OUTPUT_EMPTY = 1 << 5; - } +/// A serial port. +/// +/// Serial ports are a legacy communications port common on IBM-PC compatible computers. +/// Ref: https://wiki.osdev.org/Serial_Ports +pub struct SerialPort { + pub data: IoPort, + pub int_en: IoPort, + pub fifo_ctrl: IoPort, + pub line_ctrl: IoPort, + pub modem_ctrl: IoPort, + pub line_status: IoPort, + pub modem_status: IoPort, } -const SERIAL_DATA_PORT: u16 = 0x3F8; - -static SERIAL_DATA: IoPort = unsafe { IoPort::new(SERIAL_DATA_PORT) }; -static SERIAL_INT_EN: IoPort = unsafe { IoPort::new(SERIAL_DATA_PORT + 1) }; -static SERIAL_FIFO_CTRL: IoPort = unsafe { IoPort::new(SERIAL_DATA_PORT + 2) }; -static SERIAL_LINE_CTRL: IoPort = unsafe { IoPort::new(SERIAL_DATA_PORT + 3) }; -static SERIAL_MODEM_CTRL: IoPort = - unsafe { IoPort::new(SERIAL_DATA_PORT + 4) }; -static SERIAL_LINE_STS: IoPort = unsafe { IoPort::new(SERIAL_DATA_PORT + 5) }; - -static CONSOLE_IRQ_CALLBACK: Once> = Once::new(); -#[allow(clippy::type_complexity)] -static SERIAL_INPUT_CALLBACKS: SpinLock>> = - SpinLock::new(Vec::new()); - -/// Initializes the serial port. -pub(crate) fn init() { - // Disable interrupts - SERIAL_INT_EN.write(0x00); - // Enable DLAB - SERIAL_LINE_CTRL.write(0x80); - // Set maximum speed to 38400 bps by configuring DLL and DLM - SERIAL_DATA.write(0x03); - SERIAL_INT_EN.write(0x00); - // Disable DLAB and set data word length to 8 bits - SERIAL_LINE_CTRL.write(0x03); - // Enable FIFO, clear TX/RX queues and - // set interrupt watermark at 14 bytes - SERIAL_FIFO_CTRL.write(0xC7); - // Mark data terminal ready, signal request to send - // and enable auxilliary output #2 (used as interrupt line for CPU) - SERIAL_MODEM_CTRL.write(0x0B); - // Enable interrupts - SERIAL_INT_EN.write(0x01); -} - -pub fn register_serial_input_callback(f: impl Fn(u8) + Send + Sync + 'static) { - SERIAL_INPUT_CALLBACKS.lock_irq_disabled().push(Arc::new(f)); -} - -pub(crate) fn callback_init() { - let mut irq = crate::arch::x86::kernel::pic::allocate_irq(4).unwrap(); - irq.on_active(handle_serial_input); - CONSOLE_IRQ_CALLBACK.call_once(|| SpinLock::new(irq)); -} - -pub(crate) fn register_serial_input_irq_handler(callback: F) -where - F: Fn(&TrapFrame) + Sync + Send + 'static, -{ - CONSOLE_IRQ_CALLBACK - .get() - .unwrap() - .lock_irq_disabled() - .on_active(callback); -} - -fn handle_serial_input(trap_frame: &TrapFrame) { - // debug!("keyboard interrupt was met"); - let lock = if let Some(lock) = SERIAL_INPUT_CALLBACKS.try_lock() { - lock - } else { - return; - }; - let received_char = receive_char().unwrap(); - debug!("receive char = {:?}", received_char); - for callback in lock.iter() { - callback(received_char); - } -} - -fn line_sts() -> LineSts { - LineSts::from_bits_truncate(SERIAL_LINE_STS.read()) -} - -/// Sends a byte on the serial port. -pub fn send(data: u8) { - match data { - 8 | 0x7F => { - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - SERIAL_DATA.write(8); - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - SERIAL_DATA.write(b' '); - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - SERIAL_DATA.write(8); - } - _ => { - while !line_sts().contains(LineSts::OUTPUT_EMPTY) {} - SERIAL_DATA.write(data); +impl SerialPort { + /// Create a serial port. + /// + /// # Safety + /// + /// User must ensure the `port` is valid serial port. + pub const unsafe fn new(port: u16) -> Self { + let data = IoPort::new(port); + let int_en = IoPort::new(port + 1); + let fifo_ctrl = IoPort::new(port + 2); + let line_ctrl = IoPort::new(port + 3); + let modem_ctrl = IoPort::new(port + 4); + let line_status = IoPort::new(port + 5); + let modem_status = IoPort::new(port + 6); + Self { + data, + int_en, + fifo_ctrl, + line_ctrl, + modem_ctrl, + line_status, + modem_status, } } -} -/// Receives a byte on the serial port. non-blocking -pub fn receive_char() -> Option { - if line_sts().contains(LineSts::INPUT_FULL) { - Some(SERIAL_DATA.read()) - } else { - None + pub fn init(&self) { + // Disable interrupts + self.int_en.write(0x00); + // Enable DLAB + self.line_ctrl.write(0x80); + // Set maximum speed to 38400 bps by configuring DLL and DLM + self.data.write(0x03); + self.int_en.write(0x00); + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + // Enable interrupts + self.int_en.write(0x01); } } diff --git a/framework/jinux-frame/src/arch/x86/mod.rs b/framework/jinux-frame/src/arch/x86/mod.rs index 852b796bb..672ca5251 100644 --- a/framework/jinux-frame/src/arch/x86/mod.rs +++ b/framework/jinux-frame/src/arch/x86/mod.rs @@ -1,4 +1,5 @@ pub mod boot; +pub mod console; pub(crate) mod cpu; pub mod device; pub mod iommu; @@ -10,20 +11,17 @@ pub(crate) mod pci; pub(crate) mod tdx_guest; pub(crate) mod timer; -use alloc::fmt; -use core::fmt::Write; use kernel::apic::ioapic; use log::{info, warn}; pub(crate) fn before_all_init() { enable_common_cpu_features(); - device::serial::init(); + console::init(); } pub(crate) fn after_all_init() { irq::init(); mm::init(); - device::serial::callback_init(); kernel::acpi::init(); match kernel::apic::init() { Ok(_) => { @@ -34,6 +32,7 @@ pub(crate) fn after_all_init() { kernel::pic::enable(); } } + console::callback_init(); timer::init(); match iommu::init() { Ok(_) => {} @@ -50,35 +49,6 @@ pub(crate) fn interrupts_ack() { } } -struct Stdout; - -impl Write for Stdout { - fn write_str(&mut self, s: &str) -> fmt::Result { - for &c in s.as_bytes() { - device::serial::send(c); - } - Ok(()) - } -} - -pub fn print(args: fmt::Arguments) { - Stdout.write_fmt(args).unwrap(); -} - -#[macro_export] -macro_rules! print { - ($fmt: literal $(, $($arg: tt)+)?) => { - $crate::arch::x86::print(format_args!($fmt $(, $($arg)+)?)) - } -} - -#[macro_export] -macro_rules! println { - ($fmt: literal $(, $($arg: tt)+)?) => { - $crate::arch::x86::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) - } -} - fn enable_common_cpu_features() { use x86_64::registers::{control::Cr4Flags, model_specific::EferFlags, xcontrol::XCr0Flags}; let mut cr4 = x86_64::registers::control::Cr4::read(); diff --git a/framework/jinux-frame/src/console.rs b/framework/jinux-frame/src/console.rs new file mode 100644 index 000000000..1cf48ad0a --- /dev/null +++ b/framework/jinux-frame/src/console.rs @@ -0,0 +1,19 @@ +use core::fmt::Arguments; + +pub fn print(args: Arguments) { + crate::arch::console::print(args); +} + +#[macro_export] +macro_rules! print { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!($fmt $(, $($arg)+)?)) + } +} + +#[macro_export] +macro_rules! println { + ($fmt: literal $(, $($arg: tt)+)?) => { + $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?)) + } +} diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index efe212cb2..0cd6e94fc 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -23,6 +23,7 @@ pub mod arch; pub mod boot; pub mod bus; pub mod config; +pub mod console; pub mod cpu; mod error; pub mod io_mem; diff --git a/services/libs/jinux-std/src/device/tty/driver.rs b/services/libs/jinux-std/src/device/tty/driver.rs index 8e53854c7..dbdec9d11 100644 --- a/services/libs/jinux-std/src/device/tty/driver.rs +++ b/services/libs/jinux-std/src/device/tty/driver.rs @@ -1,4 +1,4 @@ -pub use jinux_frame::arch::x86::device::serial::register_serial_input_callback; +pub use jinux_frame::arch::console::register_console_input_callback; use spin::Once; use crate::{ @@ -9,7 +9,7 @@ use crate::{ pub static TTY_DRIVER: Once> = Once::new(); pub(super) fn init() { - register_serial_input_callback(serial_input_callback); + register_console_input_callback(&serial_input_callback); let tty_driver = Arc::new(TtyDriver::new()); // FIXME: install n_tty into tty_driver? let n_tty = get_n_tty();