|
|
@@ -1,3 +1,5 @@
|
|
|
+mod io;
|
|
|
+
|
|
|
use crate::{
|
|
|
kernel::{
|
|
|
block::make_device, console::set_console, constants::EIO, interrupt::register_irq_handler,
|
|
|
@@ -8,12 +10,10 @@ use crate::{
|
|
|
use alloc::{collections::vec_deque::VecDeque, format, sync::Arc};
|
|
|
use bitflags::bitflags;
|
|
|
use core::pin::pin;
|
|
|
-use eonix_hal::arch_exported::io::Port8;
|
|
|
+use eonix_mm::address::PAddr;
|
|
|
use eonix_runtime::{run::FutureRun, scheduler::Scheduler};
|
|
|
use eonix_sync::{SpinIrq as _, WaitList};
|
|
|
-
|
|
|
-#[cfg(not(target_arch = "x86_64"))]
|
|
|
-compile_error!("Serial driver is only supported on x86_64 architecture");
|
|
|
+use io::SerialIO;
|
|
|
|
|
|
bitflags! {
|
|
|
struct LineStatus: u8 {
|
|
|
@@ -22,6 +22,11 @@ bitflags! {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+trait SerialRegister {
|
|
|
+ fn read(&self) -> u8;
|
|
|
+ fn write(&self, value: u8);
|
|
|
+}
|
|
|
+
|
|
|
#[allow(dead_code)]
|
|
|
struct Serial {
|
|
|
id: u32,
|
|
|
@@ -33,35 +38,22 @@ struct Serial {
|
|
|
working: Spin<bool>,
|
|
|
tx_buffer: Spin<VecDeque<u8>>,
|
|
|
|
|
|
- tx_rx: Port8,
|
|
|
- int_ena: Port8,
|
|
|
- int_ident: Port8,
|
|
|
- line_control: Port8,
|
|
|
- modem_control: Port8,
|
|
|
- line_status: Port8,
|
|
|
- modem_status: Port8,
|
|
|
- scratch: Port8,
|
|
|
+ ioregs: SerialIO,
|
|
|
}
|
|
|
|
|
|
impl Serial {
|
|
|
- const COM0_BASE: u16 = 0x3f8;
|
|
|
- const COM1_BASE: u16 = 0x2f8;
|
|
|
-
|
|
|
- const COM0_IRQ: u8 = 4;
|
|
|
- const COM1_IRQ: u8 = 3;
|
|
|
-
|
|
|
fn enable_interrupts(&self) {
|
|
|
// Enable interrupt #0: Received data available
|
|
|
- self.int_ena.write(0x03);
|
|
|
+ self.ioregs.int_ena().write(0x03);
|
|
|
}
|
|
|
|
|
|
fn disable_interrupts(&self) {
|
|
|
// Disable interrupt #0: Received data available
|
|
|
- self.int_ena.write(0x02);
|
|
|
+ self.ioregs.int_ena().write(0x02);
|
|
|
}
|
|
|
|
|
|
fn line_status(&self) -> LineStatus {
|
|
|
- LineStatus::from_bits_truncate(self.line_status.read())
|
|
|
+ LineStatus::from_bits_truncate(self.ioregs.line_status().read())
|
|
|
}
|
|
|
|
|
|
async fn wait_for_interrupt(&self) {
|
|
|
@@ -85,7 +77,7 @@ impl Serial {
|
|
|
|
|
|
loop {
|
|
|
while port.line_status().contains(LineStatus::RX_READY) {
|
|
|
- let ch = port.tx_rx.read();
|
|
|
+ let ch = port.ioregs.tx_rx().read();
|
|
|
|
|
|
if let Some(terminal) = terminal.as_ref() {
|
|
|
terminal.commit_char(ch).await;
|
|
|
@@ -99,7 +91,7 @@ impl Serial {
|
|
|
// Give it a chance to receive data.
|
|
|
for &ch in tx_buffer.iter().take(64) {
|
|
|
if port.line_status().contains(LineStatus::TX_READY) {
|
|
|
- port.tx_rx.write(ch);
|
|
|
+ port.ioregs.tx_rx().write(ch);
|
|
|
} else {
|
|
|
break;
|
|
|
}
|
|
|
@@ -116,40 +108,32 @@ impl Serial {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn new(id: u32, base_port: u16) -> KResult<Self> {
|
|
|
- let port = Self {
|
|
|
+ pub fn new(id: u32, ioregs: SerialIO) -> KResult<Self> {
|
|
|
+ ioregs.int_ena().write(0x00); // Disable all interrupts
|
|
|
+ ioregs.line_control().write(0x80); // Enable DLAB (set baud rate divisor)
|
|
|
+ ioregs.tx_rx().write(0x00); // Set divisor to 0 (lo byte) 115200 baud rate
|
|
|
+ ioregs.int_ena().write(0x00); // 0 (hi byte)
|
|
|
+ ioregs.line_control().write(0x03); // 8 bits, no parity, one stop bit
|
|
|
+ ioregs.int_ident().write(0xc7); // Enable FIFO, clear them, with 14-byte threshold
|
|
|
+ ioregs.modem_control().write(0x0b); // IRQs enabled, RTS/DSR set
|
|
|
+ ioregs.modem_control().write(0x1e); // Set in loopback mode, test the serial chip
|
|
|
+ ioregs.tx_rx().write(0x19); // Test serial chip (send byte 0x19 and check if serial returns
|
|
|
+ // same byte)
|
|
|
+ if ioregs.tx_rx().read() != 0x19 {
|
|
|
+ return Err(EIO);
|
|
|
+ }
|
|
|
+
|
|
|
+ ioregs.modem_control().write(0x0f); // Return to normal operation mode
|
|
|
+
|
|
|
+ Ok(Self {
|
|
|
id,
|
|
|
name: Arc::from(format!("ttyS{id}")),
|
|
|
terminal: Spin::new(None),
|
|
|
worker_wait: WaitList::new(),
|
|
|
working: Spin::new(true),
|
|
|
tx_buffer: Spin::new(VecDeque::new()),
|
|
|
- tx_rx: Port8::new(base_port),
|
|
|
- int_ena: Port8::new(base_port + 1),
|
|
|
- int_ident: Port8::new(base_port + 2),
|
|
|
- line_control: Port8::new(base_port + 3),
|
|
|
- modem_control: Port8::new(base_port + 4),
|
|
|
- line_status: Port8::new(base_port + 5),
|
|
|
- modem_status: Port8::new(base_port + 6),
|
|
|
- scratch: Port8::new(base_port + 7),
|
|
|
- };
|
|
|
-
|
|
|
- port.int_ena.write(0x00); // Disable all interrupts
|
|
|
- port.line_control.write(0x80); // Enable DLAB (set baud rate divisor)
|
|
|
- port.tx_rx.write(0x00); // Set divisor to 0 (lo byte) 115200 baud rate
|
|
|
- port.int_ena.write(0x00); // 0 (hi byte)
|
|
|
- port.line_control.write(0x03); // 8 bits, no parity, one stop bit
|
|
|
- port.int_ident.write(0xc7); // Enable FIFO, clear them, with 14-byte threshold
|
|
|
- port.modem_control.write(0x0b); // IRQs enabled, RTS/DSR set
|
|
|
- port.modem_control.write(0x1e); // Set in loopback mode, test the serial chip
|
|
|
- port.tx_rx.write(0x19); // Test serial chip (send byte 0x19 and check if serial returns
|
|
|
- // same byte)
|
|
|
- if port.tx_rx.read() != 0x19 {
|
|
|
- return Err(EIO);
|
|
|
- }
|
|
|
-
|
|
|
- port.modem_control.write(0x0f); // Return to normal operation mode
|
|
|
- Ok(port)
|
|
|
+ ioregs,
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
fn wakeup_worker(&self) {
|
|
|
@@ -161,24 +145,18 @@ impl Serial {
|
|
|
|
|
|
fn irq_handler(&self) {
|
|
|
// Read the interrupt ID register to clear the interrupt.
|
|
|
- self.int_ident.read();
|
|
|
+ self.ioregs.int_ident().read();
|
|
|
self.wakeup_worker();
|
|
|
}
|
|
|
|
|
|
- fn register_char_device(port: Self) -> KResult<()> {
|
|
|
- let port = Arc::new(port);
|
|
|
+ fn register_as_char_device(self, irq_no: usize) -> KResult<()> {
|
|
|
+ let port = Arc::new(self);
|
|
|
let terminal = Terminal::new(port.clone());
|
|
|
|
|
|
port.terminal.lock().replace(terminal.clone());
|
|
|
|
|
|
{
|
|
|
let port = port.clone();
|
|
|
- let irq_no = match port.id {
|
|
|
- 0 => Serial::COM0_IRQ,
|
|
|
- 1 => Serial::COM1_IRQ,
|
|
|
- _ => unreachable!(),
|
|
|
- };
|
|
|
-
|
|
|
register_irq_handler(irq_no as i32, move || {
|
|
|
port.irq_handler();
|
|
|
})?;
|
|
|
@@ -208,18 +186,56 @@ impl TerminalDevice for Serial {
|
|
|
|
|
|
fn write_direct(&self, data: &[u8]) {
|
|
|
for &ch in data {
|
|
|
- self.tx_rx.write(ch);
|
|
|
+ self.ioregs.tx_rx().write(ch);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
pub fn init() -> KResult<()> {
|
|
|
- if let Ok(port) = Serial::new(0, Serial::COM0_BASE) {
|
|
|
- Serial::register_char_device(port)?;
|
|
|
+ #[cfg(target_arch = "x86_64")]
|
|
|
+ {
|
|
|
+ let (com0, com1) = unsafe {
|
|
|
+ const COM0_BASE: u16 = 0x3f8;
|
|
|
+ const COM1_BASE: u16 = 0x2f8;
|
|
|
+ // SAFETY: The COM ports are well-known hardware addresses.
|
|
|
+ (SerialIO::new(COM0_BASE), SerialIO::new(COM1_BASE))
|
|
|
+ };
|
|
|
+
|
|
|
+ if let Ok(port) = Serial::new(0, com0) {
|
|
|
+ const COM0_IRQ: usize = 4;
|
|
|
+ port.register_as_char_device(COM0_IRQ)?;
|
|
|
+ }
|
|
|
+
|
|
|
+ if let Ok(port) = Serial::new(1, com1) {
|
|
|
+ const COM1_IRQ: usize = 3;
|
|
|
+ port.register_as_char_device(COM1_IRQ)?;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- if let Ok(port) = Serial::new(1, Serial::COM1_BASE) {
|
|
|
- Serial::register_char_device(port)?;
|
|
|
+ #[cfg(target_arch = "riscv64")]
|
|
|
+ {
|
|
|
+ use eonix_hal::arch_exported::fdt::FDT;
|
|
|
+
|
|
|
+ if let Some(uart) = FDT.find_compatible(&["ns16550a", "ns16550"]) {
|
|
|
+ let regs = uart.reg().unwrap();
|
|
|
+ let base_address = regs
|
|
|
+ .map(|reg| PAddr::from(reg.starting_address as usize))
|
|
|
+ .next()
|
|
|
+ .expect("UART base address not found");
|
|
|
+
|
|
|
+ let port = unsafe {
|
|
|
+ // SAFETY: The base address is provided by the FDT and should be valid.
|
|
|
+ SerialIO::new(base_address)
|
|
|
+ };
|
|
|
+
|
|
|
+ let serial = Serial::new(0, port)?;
|
|
|
+ serial.register_as_char_device(
|
|
|
+ uart.interrupts()
|
|
|
+ .expect("UART device should have `interrupts` property")
|
|
|
+ .next()
|
|
|
+ .expect("UART device should have an interrupt pin"),
|
|
|
+ )?;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Ok(())
|