瀏覽代碼

feat(serial): add serial driver support for riscv64

greatbridf 7 月之前
父節點
當前提交
6dd58675ae

+ 1 - 4
crates/eonix_hal/src/arch/riscv64/trap/mod.rs

@@ -15,10 +15,7 @@ use riscv::register::stvec::TrapMode;
 use riscv::register::{scause, sepc, stval};
 use riscv::{
     asm::sfence_vma_all,
-    register::{
-        sie,
-        stvec::{self, Stvec},
-    },
+    register::stvec::{self, Stvec},
 };
 use sbi::SbiError;
 

+ 1 - 0
crates/eonix_hal/src/arch/x86_64/io.rs

@@ -1,5 +1,6 @@
 use core::arch::asm;
 
+#[derive(Clone, Copy)]
 pub struct Port8 {
     no: u16,
 }

+ 0 - 1
src/driver.rs

@@ -1,6 +1,5 @@
 pub mod ahci;
 pub mod e1000e;
-#[cfg(target_arch = "x86_64")]
 pub mod serial;
 
 #[cfg(target_arch = "riscv64")]

+ 1 - 8
src/driver/sbi_console.rs

@@ -29,12 +29,5 @@ pub fn init_console() {
 
     let console = Arc::new(SbiConsole);
     let terminal = Terminal::new(console.clone());
-    crate::kernel::console::set_console(terminal.clone()).expect("Failed to set console");
-
-    CharDevice::register(
-        make_device(4, 64),
-        Arc::from("sbi_console"),
-        CharDeviceType::Terminal(terminal),
-    )
-    .expect("Failed to register SBI console as a character device");
+    crate::kernel::console::set_console(terminal).expect("Failed to set console");
 }

+ 81 - 65
src/driver/serial.rs

@@ -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(())

+ 157 - 0
src/driver/serial/io.rs

@@ -0,0 +1,157 @@
+use super::SerialRegister;
+use core::ptr::NonNull;
+use eonix_hal::mm::ArchPhysAccess;
+use eonix_mm::address::{PAddr, PhysAccess};
+
+#[cfg(target_arch = "x86_64")]
+use eonix_hal::arch_exported::io::Port8;
+
+#[cfg(target_arch = "x86_64")]
+pub struct SerialIO {
+    tx_rx: Port8,
+    int_ena: Port8,
+    int_ident: Port8,
+    line_control: Port8,
+    modem_control: Port8,
+    line_status: Port8,
+    modem_status: Port8,
+    scratch: Port8,
+}
+
+#[cfg(target_arch = "x86_64")]
+impl SerialRegister for Port8 {
+    fn read(&self) -> u8 {
+        self.read()
+    }
+
+    fn write(&self, data: u8) {
+        self.write(data);
+    }
+}
+
+#[cfg(target_arch = "x86_64")]
+impl SerialIO {
+    /// Creates a new `SerialIO` instance with the given physical address.
+    ///
+    /// # Safety
+    /// This function is unsafe because it assumes that the provided `base` is a valid IO port
+    /// base for the serial port. The caller must ensure that this port base is correct.
+    pub unsafe fn new(base: u16) -> Self {
+        Self {
+            tx_rx: Port8::new(base),
+            int_ena: Port8::new(base + 1),
+            int_ident: Port8::new(base + 2),
+            line_control: Port8::new(base + 3),
+            modem_control: Port8::new(base + 4),
+            line_status: Port8::new(base + 5),
+            modem_status: Port8::new(base + 6),
+            scratch: Port8::new(base + 7),
+        }
+    }
+
+    pub fn tx_rx(&self) -> impl SerialRegister {
+        self.tx_rx
+    }
+
+    pub fn int_ena(&self) -> impl SerialRegister {
+        self.int_ena
+    }
+
+    pub fn int_ident(&self) -> impl SerialRegister {
+        self.int_ident
+    }
+
+    pub fn line_control(&self) -> impl SerialRegister {
+        self.line_control
+    }
+
+    pub fn modem_control(&self) -> impl SerialRegister {
+        self.modem_control
+    }
+
+    pub fn line_status(&self) -> impl SerialRegister {
+        self.line_status
+    }
+
+    pub fn modem_status(&self) -> impl SerialRegister {
+        self.modem_status
+    }
+
+    pub fn scratch(&self) -> impl SerialRegister {
+        self.scratch
+    }
+}
+
+#[cfg(target_arch = "riscv64")]
+pub struct SerialIO {
+    base_addr: NonNull<u8>,
+}
+
+#[cfg(target_arch = "riscv64")]
+unsafe impl Send for SerialIO {}
+
+#[cfg(target_arch = "riscv64")]
+unsafe impl Sync for SerialIO {}
+
+#[cfg(target_arch = "riscv64")]
+impl SerialRegister for NonNull<u8> {
+    fn read(&self) -> u8 {
+        // SAFETY: `self` is a valid pointer to the serial port register.
+        unsafe { self.as_ptr().read_volatile() }
+    }
+
+    fn write(&self, data: u8) {
+        // SAFETY: `self` is a valid pointer to the serial port register.
+        unsafe { self.as_ptr().write_volatile(data) }
+    }
+}
+
+#[cfg(target_arch = "riscv64")]
+impl SerialIO {
+    /// Creates a new `SerialIO` instance with the given physical address.
+    ///
+    /// # Safety
+    /// This function is unsafe because it assumes that the provided `base_addr` is a valid
+    /// physical address for the serial port. The caller must ensure that this address is correct
+    /// and that the memory at this address is accessible.
+    pub unsafe fn new(base_addr: PAddr) -> Self {
+        Self {
+            base_addr: unsafe {
+                // SAFETY: `base_addr` is a valid physical address for the serial port.
+                ArchPhysAccess::as_ptr(base_addr)
+            },
+        }
+    }
+
+    pub fn tx_rx(&self) -> impl SerialRegister {
+        self.base_addr
+    }
+
+    pub fn int_ena(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(1) }
+    }
+
+    pub fn int_ident(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(2) }
+    }
+
+    pub fn line_control(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(3) }
+    }
+
+    pub fn modem_control(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(4) }
+    }
+
+    pub fn line_status(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(5) }
+    }
+
+    pub fn modem_status(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(6) }
+    }
+
+    pub fn scratch(&self) -> impl SerialRegister {
+        unsafe { self.base_addr.add(7) }
+    }
+}

+ 1 - 0
src/lib.rs

@@ -135,6 +135,7 @@ async fn init_process(early_kstack: PRange) {
 
     #[cfg(target_arch = "riscv64")]
     {
+        driver::serial::init().unwrap();
         driver::virtio::init_virtio_devices();
         driver::e1000e::register_e1000e_driver();
         driver::ahci::register_ahci_driver();