From 0b2822cea1e7347016f0bf8c10a226143d3fc3f6 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Tue, 10 Dec 2024 13:54:26 +0200 Subject: [PATCH] x86: make com-port usable as a serial console --- kernel/src/arch/i686/mod.rs | 14 +- kernel/src/arch/x86/intrinsics.rs | 4 + kernel/src/arch/x86/peripherals/serial.rs | 173 +++++++++++++++++----- kernel/src/arch/x86_64/mod.rs | 13 +- userspace/arch/i686/inittab | 1 + userspace/arch/x86_64/inittab | 2 + 6 files changed, 150 insertions(+), 57 deletions(-) diff --git a/kernel/src/arch/i686/mod.rs b/kernel/src/arch/i686/mod.rs index 1f6c0ae0..fff637d4 100644 --- a/kernel/src/arch/i686/mod.rs +++ b/kernel/src/arch/i686/mod.rs @@ -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 diff --git a/kernel/src/arch/x86/intrinsics.rs b/kernel/src/arch/x86/intrinsics.rs index 93977f3a..a9ed001b 100644 --- a/kernel/src/arch/x86/intrinsics.rs +++ b/kernel/src/arch/x86/intrinsics.rs @@ -16,6 +16,10 @@ pub trait IoPortAccess { fn read(&self) -> T; /// Writes a value to the port fn write(&self, value: T); + + fn modify T>(&self, f: F) { + self.write(f(self.read())); + } } impl IoPort { diff --git a/kernel/src/arch/x86/peripherals/serial.rs b/kernel/src/arch/x86/peripherals/serial.rs index b7a021a0..6c7f94ed 100644 --- a/kernel/src/arch/x86/peripherals/serial.rs +++ b/kernel/src/arch/x86/peripherals/serial.rs @@ -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, lsr: IoPort, + ier: IoPort, + isr: IoPort, +} + +struct PortInner { + regs: IrqSafeSpinlock, } /// Single port of the COM port pair pub struct Port { - inner: IrqSafeSpinlock, + terminal: Arc>, + irq: Irq, } /// COM port pair @@ -23,28 +37,9 @@ pub struct Port { pub struct ComPort { port_a: Arc, port_b: Arc, - 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,14 +50,77 @@ 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 { + 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 + } + } +} + +impl TerminalOutput for PortInner { + fn write(&self, byte: u8) -> Result<(), Error> { + self.regs.lock().write(byte) } - fn is_terminal(&self) -> bool { - false + fn write_multiple(&self, bytes: &[u8]) -> Result { + 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, _vector: Option) -> 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" } } @@ -72,31 +130,64 @@ impl Device for Port { } unsafe fn init(self: Arc) -> Result<(), Error> { + DEVICE_REGISTRY + .serial_terminal + .register(self.terminal.clone(), Some((self.clone(), LogLevel::Debug))) + .ok(); + Ok(()) + } + + unsafe fn init_irq(self: Arc) -> 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 { + 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 { + 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, 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 diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 8e68d9c2..eacbce66 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -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()?; } diff --git a/userspace/arch/i686/inittab b/userspace/arch/i686/inittab index f324faec..44aa0df4 100644 --- a/userspace/arch/i686/inittab +++ b/userspace/arch/i686/inittab @@ -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 diff --git a/userspace/arch/x86_64/inittab b/userspace/arch/x86_64/inittab index 7a43701a..9c848816 100644 --- a/userspace/arch/x86_64/inittab +++ b/userspace/arch/x86_64/inittab @@ -1,2 +1,4 @@ init:1:wait:/sbin/rc default logd:1:once:/sbin/logd + +user:1:once:/sbin/login /dev/ttyS0