x86: make com-port usable as a serial console
This commit is contained in:
parent
1ad90ce181
commit
0b2822cea1
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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
|
||||
|
@ -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()?;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -1,2 +1,4 @@
|
||||
init:1:wait:/sbin/rc default
|
||||
logd:1:once:/sbin/logd
|
||||
|
||||
user:1:once:/sbin/login /dev/ttyS0
|
||||
|
Loading…
x
Reference in New Issue
Block a user