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

View File

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

View File

@ -1,21 +1,35 @@
//! Driver for x86 COM ports
use abi::error::Error;
use abi::{error::Error, io::TerminalOptions};
use alloc::sync::Arc;
use device_api::{device::Device, interrupt::Irq, serial::SerialDevice};
use libk::debug::DebugSink;
use device_api::{
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 crate::arch::x86::intrinsics::{IoPort, IoPortAccess};
// Single port
struct Inner {
struct Regs {
dr: IoPort<u8>,
lsr: IoPort<u8>,
ier: IoPort<u8>,
isr: IoPort<u8>,
}
struct PortInner {
regs: IrqSafeSpinlock<Regs>,
}
/// Single port of the COM port pair
pub struct Port {
inner: IrqSafeSpinlock<Inner>,
terminal: Arc<Terminal<PortInner>>,
irq: Irq,
}
/// COM port pair
@ -23,28 +37,9 @@ pub struct Port {
pub struct ComPort {
port_a: Arc<Port>,
port_b: Arc<Port>,
irq: Irq,
}
impl DebugSink for Port {
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 {
impl Regs {
fn write(&mut self, byte: u8) -> Result<(), Error> {
while self.lsr.read() & Port::LSR_THRE == 0 {
core::hint::spin_loop();
@ -55,48 +50,144 @@ impl Inner {
}
}
impl SerialDevice for Port {
fn send_byte(&self, byte: u8) -> Result<(), Error> {
let mut inner = self.inner.lock();
inner.write(byte)
impl PortInner {
fn handle_irq(&self) -> Option<u8> {
let (status, value) = {
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
}
}
fn display_name(&self) -> &str {
"x86 COM port IRQ"
}
}
impl Device for Port {
fn display_name(&self) -> &'static str {
"COM port"
}
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(())
}
}
impl Port {
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 {
Self {
inner: IrqSafeSpinlock::new(Inner {
fn new(base: u16, irq: Irq) -> Result<Self, Error> {
let input = TerminalInput::with_capacity(64)?;
let output = PortInner {
regs: IrqSafeSpinlock::new(Regs {
dr: IoPort::new(base),
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 {
/// Constructs a COM port pair
pub fn new(port_a: u16, port_b: u16, irq: Irq) -> Self {
Self {
port_a: Arc::new(Port::new(port_a)),
port_b: Arc::new(Port::new(port_b)),
irq,
fn new(port_a: u16, port_b: u16, irq: Irq) -> Result<Self, Error> {
Ok(Self {
port_a: Arc::new(Port::new(port_a, irq)?),
port_b: Arc::new(Port::new(port_b, 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

View File

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

View File

@ -2,3 +2,4 @@ init:1:wait:/sbin/rc default
logd:1:once:/sbin/logd
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
logd:1:once:/sbin/logd
user:1:once:/sbin/login /dev/ttyS0