x86: make com-port usable as a serial console

This commit is contained in:
Mark Poliakov 2024-12-10 13:54:26 +02:00
parent 1ad90ce181
commit 0b2822cea1
6 changed files with 150 additions and 57 deletions

View File

@ -5,6 +5,7 @@ use abi::error::Error;
use alloc::sync::Arc; use alloc::sync::Arc;
use boot::multiboot::MultibootInfo; use boot::multiboot::MultibootInfo;
use device_api::{ use device_api::{
device::Device,
interrupt::{IpiDeliveryTarget, IpiMessage, Irq}, interrupt::{IpiDeliveryTarget, IpiMessage, Irq},
ResetDevice, ResetDevice,
}; };
@ -159,14 +160,9 @@ impl I686 {
phys::init_from_iter(multiboot_info.memory_map_iter(), |_, _, _| Ok(()))?; phys::init_from_iter(multiboot_info.memory_map_iter(), |_, _, _| Ok(()))?;
debug::init(); debug::init();
devfs::init();
let com1_3 = Arc::new(ComPort::new( let com1_3 = ComPort::setup(0x3F8, 0x3E8, Irq::External(ISA_IRQ_OFFSET + 4))?;
0x3F8,
0x3E8,
Irq::External(ISA_IRQ_OFFSET + 4),
));
debug::add_sink(com1_3.port_a().clone(), LogLevel::Debug);
unsafe { init_gdt() }; unsafe { init_gdt() };
unsafe { exception::init_exceptions() }; unsafe { exception::init_exceptions() };
@ -190,7 +186,6 @@ impl I686 {
Cpu::init_local(Some(0), cpu_data); Cpu::init_local(Some(0), cpu_data);
runtime::init_task_queue(); runtime::init_task_queue();
devfs::init();
// Register the PCI drivers // Register the PCI drivers
// TODO make this implicit init // TODO make this implicit init
@ -243,6 +238,9 @@ impl I686 {
if let Err(error) = Rtc::setup() { if let Err(error) = Rtc::setup() {
log::error!("RTC init error: {error:?}"); log::error!("RTC init error: {error:?}");
} }
if let Err(error) = unsafe { com1_3.port_a().clone().init_irq() } {
log::error!("COM port IRQ init error: {error:?}");
}
// Setup text framebuffer // Setup text framebuffer
// TODO check if video mode is set from boot info // TODO check if video mode is set from boot info

View File

@ -16,6 +16,10 @@ pub trait IoPortAccess<T> {
fn read(&self) -> T; fn read(&self) -> T;
/// Writes a value to the port /// Writes a value to the port
fn write(&self, value: T); fn write(&self, value: T);
fn modify<F: FnOnce(T) -> T>(&self, f: F) {
self.write(f(self.read()));
}
} }
impl<T> IoPort<T> { impl<T> IoPort<T> {

View File

@ -1,21 +1,35 @@
//! Driver for x86 COM ports //! Driver for x86 COM ports
use abi::error::Error; use abi::{error::Error, io::TerminalOptions};
use alloc::sync::Arc; use alloc::sync::Arc;
use device_api::{device::Device, interrupt::Irq, serial::SerialDevice}; use device_api::{
use libk::debug::DebugSink; device::Device,
interrupt::{InterruptHandler, Irq},
};
use libk::{
debug::{DebugSink, LogLevel},
device::{external_interrupt_controller, manager::DEVICE_REGISTRY},
vfs::{Terminal, TerminalInput, TerminalOutput},
};
use libk_util::sync::IrqSafeSpinlock; use libk_util::sync::IrqSafeSpinlock;
use crate::arch::x86::intrinsics::{IoPort, IoPortAccess}; use crate::arch::x86::intrinsics::{IoPort, IoPortAccess};
// Single port // Single port
struct Inner { struct Regs {
dr: IoPort<u8>, dr: IoPort<u8>,
lsr: IoPort<u8>, lsr: IoPort<u8>,
ier: IoPort<u8>,
isr: IoPort<u8>,
}
struct PortInner {
regs: IrqSafeSpinlock<Regs>,
} }
/// Single port of the COM port pair /// Single port of the COM port pair
pub struct Port { pub struct Port {
inner: IrqSafeSpinlock<Inner>, terminal: Arc<Terminal<PortInner>>,
irq: Irq,
} }
/// COM port pair /// COM port pair
@ -23,28 +37,9 @@ pub struct Port {
pub struct ComPort { pub struct ComPort {
port_a: Arc<Port>, port_a: Arc<Port>,
port_b: Arc<Port>, port_b: Arc<Port>,
irq: Irq,
} }
impl DebugSink for Port { impl Regs {
fn putc(&self, c: u8) -> Result<(), Error> {
self.send_byte(c)
}
fn puts(&self, s: &str) -> Result<(), Error> {
let mut inner = self.inner.lock();
for b in s.bytes() {
inner.write(b)?;
}
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
impl Inner {
fn write(&mut self, byte: u8) -> Result<(), Error> { fn write(&mut self, byte: u8) -> Result<(), Error> {
while self.lsr.read() & Port::LSR_THRE == 0 { while self.lsr.read() & Port::LSR_THRE == 0 {
core::hint::spin_loop(); core::hint::spin_loop();
@ -55,48 +50,144 @@ impl Inner {
} }
} }
impl SerialDevice for Port { impl PortInner {
fn send_byte(&self, byte: u8) -> Result<(), Error> { fn handle_irq(&self) -> Option<u8> {
let mut inner = self.inner.lock(); let (status, value) = {
inner.write(byte) let inner = self.regs.lock();
let status = inner.isr.read();
let value = inner.dr.read();
(status, value)
};
if status & Port::ISR_IRQ_MASK != 0 {
Some(value)
} else {
None
}
}
} }
fn is_terminal(&self) -> bool { impl TerminalOutput for PortInner {
fn write(&self, byte: u8) -> Result<(), Error> {
self.regs.lock().write(byte)
}
fn write_multiple(&self, bytes: &[u8]) -> Result<usize, Error> {
let mut regs = self.regs.lock();
for &b in bytes {
regs.write(b)?;
}
Ok(bytes.len())
}
}
impl DebugSink for Port {
fn putc(&self, c: u8) -> Result<(), Error> {
self.terminal.putc_to_output(c).ok();
Ok(())
}
fn puts(&self, s: &str) -> Result<(), Error> {
self.terminal.write_to_output(s.as_bytes()).ok();
Ok(())
}
fn supports_control_sequences(&self) -> bool {
true
}
}
// impl SerialDevice for Port {
// fn send_byte(&self, byte: u8) -> Result<(), Error> {
// let mut inner = self.inner.lock();
// inner.write(byte)
// }
//
// fn is_terminal(&self) -> bool {
// false
// }
// }
impl InterruptHandler for Port {
fn handle_irq(self: Arc<Self>, _vector: Option<usize>) -> bool {
let inner = self.terminal.output();
if let Some(byte) = inner.handle_irq() {
self.terminal.write_to_input(byte);
true
} else {
false false
} }
} }
fn display_name(&self) -> &str {
"x86 COM port IRQ"
}
}
impl Device for Port { impl Device for Port {
fn display_name(&self) -> &'static str { fn display_name(&self) -> &'static str {
"COM port" "COM port"
} }
unsafe fn init(self: Arc<Self>) -> Result<(), Error> { unsafe fn init(self: Arc<Self>) -> Result<(), Error> {
DEVICE_REGISTRY
.serial_terminal
.register(self.terminal.clone(), Some((self.clone(), LogLevel::Debug)))
.ok();
Ok(())
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
let intc = external_interrupt_controller()?;
// TODO check that the same IRQ is not bound already for another port
intc.register_irq(self.irq, Default::default(), self.clone())?;
intc.enable_irq(self.irq)?;
let regs = self.terminal.output().regs.lock();
regs.ier.modify(|v| v | Self::IER_RXDA);
Ok(()) Ok(())
} }
} }
impl Port { impl Port {
const LSR_THRE: u8 = 1 << 5; const LSR_THRE: u8 = 1 << 5;
const IER_RXDA: u8 = 1 << 0;
const ISR_IRQ_MASK: u8 = 3 << 1;
const fn new(base: u16) -> Self { fn new(base: u16, irq: Irq) -> Result<Self, Error> {
Self { let input = TerminalInput::with_capacity(64)?;
inner: IrqSafeSpinlock::new(Inner { let output = PortInner {
regs: IrqSafeSpinlock::new(Regs {
dr: IoPort::new(base), dr: IoPort::new(base),
lsr: IoPort::new(base + 5), lsr: IoPort::new(base + 5),
ier: IoPort::new(base + 1),
isr: IoPort::new(base + 2),
}), }),
} };
let terminal = Terminal::from_parts(TerminalOptions::const_default(), input, output);
Ok(Self {
terminal: Arc::new(terminal),
irq,
})
} }
} }
impl ComPort { impl ComPort {
/// Constructs a COM port pair /// Constructs a COM port pair
pub fn new(port_a: u16, port_b: u16, irq: Irq) -> Self { fn new(port_a: u16, port_b: u16, irq: Irq) -> Result<Self, Error> {
Self { Ok(Self {
port_a: Arc::new(Port::new(port_a)), port_a: Arc::new(Port::new(port_a, irq)?),
port_b: Arc::new(Port::new(port_b)), port_b: Arc::new(Port::new(port_b, irq)?),
irq, })
} }
pub fn setup(port_a: u16, port_b: u16, irq: Irq) -> Result<Arc<Self>, Error> {
let this = Arc::new(Self::new(port_a, port_b, irq)?);
unsafe { this.port_a().clone().init() }?;
Ok(this)
} }
/// Returns a reference to the A port of this COM pair /// Returns a reference to the A port of this COM pair

View File

@ -6,7 +6,7 @@ use abi::error::Error;
use acpi::{AcpiAllocator, AcpiHandlerImpl}; use acpi::{AcpiAllocator, AcpiHandlerImpl};
use alloc::{boxed::Box, sync::Arc}; use alloc::{boxed::Box, sync::Arc};
use apic::{ioapic::IoApic, local::LocalApic}; use apic::{ioapic::IoApic, local::LocalApic};
use device_api::interrupt::Irq; use device_api::{device::Device, interrupt::Irq};
use git_version::git_version; use git_version::git_version;
use kernel_arch_x86::{ use kernel_arch_x86::{
cpuid::{self, CpuFeatures, EcxFeatures, EdxFeatures}, cpuid::{self, CpuFeatures, EcxFeatures, EdxFeatures},
@ -336,13 +336,7 @@ impl X86_64 {
debug::init(); debug::init();
let com1_3 = Arc::new(ComPort::new( let com1_3 = ComPort::setup(0x3F8, 0x3E8, Irq::External(ISA_IRQ_OFFSET + 4))?;
0x3F8,
0x3E8,
Irq::External(ISA_IRQ_OFFSET + 4),
));
debug::add_sink(com1_3.port_a().clone(), LogLevel::Debug);
// TODO register platform devices // TODO register platform devices
DEVICE_REGISTRY.display.set_callback(|device, node| { DEVICE_REGISTRY.display.set_callback(|device, node| {
@ -391,6 +385,9 @@ impl X86_64 {
if let Err(error) = Rtc::setup() { if let Err(error) = Rtc::setup() {
log::error!("RTC init error: {error:?}"); log::error!("RTC init error: {error:?}");
} }
if let Err(error) = unsafe { com1_3.port_a().clone().init_irq() } {
log::error!("COM port IRQ init error: {error:?}");
}
PciBusManager::setup_bus_devices()?; PciBusManager::setup_bus_devices()?;
} }

View File

@ -2,3 +2,4 @@ init:1:wait:/sbin/rc default
logd:1:once:/sbin/logd logd:1:once:/sbin/logd
user:1:once:/sbin/login /dev/tty0 user:1:once:/sbin/login /dev/tty0
user:1:once:/sbin/login /dev/ttyS0

View File

@ -1,2 +1,4 @@
init:1:wait:/sbin/rc default init:1:wait:/sbin/rc default
logd:1:once:/sbin/logd logd:1:once:/sbin/logd
user:1:once:/sbin/login /dev/ttyS0