From ed9d7a71451b09872aa721cd7c2da08d83ab89dc Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Tue, 3 Feb 2026 17:28:15 +0200 Subject: [PATCH] usb: add ft232 driver --- kernel/driver/bus/usb/src/class_driver/hub.rs | 4 +- kernel/driver/bus/usb/src/class_driver/mod.rs | 8 +- .../driver/bus/usb/src/class_driver/serial.rs | 311 ++++++++++++++++++ kernel/libk/src/device/char.rs | 8 +- kernel/libk/src/vfs/file/device.rs | 2 +- kernel/libk/src/vfs/file/mod.rs | 2 +- kernel/libk/src/vfs/terminal.rs | 57 +++- 7 files changed, 381 insertions(+), 11 deletions(-) create mode 100644 kernel/driver/bus/usb/src/class_driver/serial.rs diff --git a/kernel/driver/bus/usb/src/class_driver/hub.rs b/kernel/driver/bus/usb/src/class_driver/hub.rs index 983bde91..c10fa78f 100644 --- a/kernel/driver/bus/usb/src/class_driver/hub.rs +++ b/kernel/driver/bus/usb/src/class_driver/hub.rs @@ -377,8 +377,8 @@ impl UsbDeviceDriver for UsbHubDriver { "USB Hub" } - fn probe(&self, class: u8, subclass: u8, protocol: u8) -> bool { - let _ = protocol; + fn probe(&self, class: u8, subclass: u8, protocol: u8, vid: u16, pid: u16) -> bool { + let _ = (protocol, vid, pid); class == 0x09 && subclass == 0x00 } } diff --git a/kernel/driver/bus/usb/src/class_driver/mod.rs b/kernel/driver/bus/usb/src/class_driver/mod.rs index 52a093d2..9fb0d99a 100644 --- a/kernel/driver/bus/usb/src/class_driver/mod.rs +++ b/kernel/driver/bus/usb/src/class_driver/mod.rs @@ -12,13 +12,14 @@ use crate::{ mod hid; mod hub; mod mass_storage; +mod serial; #[async_trait] pub trait UsbDeviceDriver: Send + Sync { async fn run(self: Arc, device: Arc) -> Result<(), UsbError>; fn name(&self) -> &'static str; - fn probe(&self, class: u8, subclass: u8, protocol: u8) -> bool; + fn probe(&self, class: u8, subclass: u8, protocol: u8, vid: u16, pid: u16) -> bool; } #[async_trait] @@ -33,9 +34,11 @@ async fn spawn_device_driver(device: Arc) -> Result) pub fn register_default_class_drivers() { register_device_driver(Arc::new(hub::UsbHubDriver)); + register_device_driver(Arc::new(serial::FT232Driver)); register_interface_driver(Arc::new(hid::UsbHidKeyboardDriver)); register_interface_driver(Arc::new(hid::UsbHidMouseDriver)); register_interface_driver(Arc::new(mass_storage::UsbMassStorageDriverBulkOnly)); diff --git a/kernel/driver/bus/usb/src/class_driver/serial.rs b/kernel/driver/bus/usb/src/class_driver/serial.rs new file mode 100644 index 00000000..233d93ab --- /dev/null +++ b/kernel/driver/bus/usb/src/class_driver/serial.rs @@ -0,0 +1,311 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use alloc::{boxed::Box, collections::btree_map::BTreeMap, sync::Arc}; +use async_trait::async_trait; +use libk::{ + block, + error::Error, + fs::devfs, + vfs::{Terminal, TerminalInput, TerminalOutput}, +}; +use libk_util::sync::{IrqSafeSpinlock, spin_rwlock::IrqSafeRwLock}; +use yggdrasil_abi::{ + io::{FileMode, TerminalOptions, TerminalOutputOptions}, + process::ProcessId, +}; + +use crate::{ + class_driver::UsbDeviceDriver, + communication::UsbDirection, + device::{UsbDeviceAccess, UsbDeviceDetachHandler}, + error::UsbError, + info::UsbEndpointType, + pipe::{control::ControlTransferSetup, normal::UsbBulkOutPipeAccess}, +}; + +pub struct FT232Driver; +struct FT232Device { + device: Arc, + bulk_out: UsbBulkOutPipeAccess, + baud_rate: IrqSafeRwLock, +} + +pub struct UsbSerialDeviceWrapper { + device: Arc, + index: u32, + disconnected: AtomicBool, +} + +#[async_trait] +pub trait UsbSerialDevice: Send + Sync + 'static { + async fn setup(&self) -> Result<(), UsbError>; + + async fn set_baud_rate(&self, baud: u32) -> Result<(), UsbError>; + fn baud_rate(&self) -> u32; + + async fn write(&self, buffer: &[u8], options: TerminalOutputOptions) + -> Result; + + fn display_name(&self) -> &str; + fn device(&self) -> &Arc; +} + +#[async_trait] +impl UsbDeviceDetachHandler for UsbSerialDeviceWrapper { + async fn handle_device_detach(&self) { + self.disconnected.store(true, Ordering::Release); + log::info!("USB serial #{} disconnected", self.index); + remove_usb_serial(self.index); + } +} + +impl TerminalOutput for UsbSerialDeviceWrapper { + fn write(&self, byte: u8, options: &TerminalOutputOptions) -> Result<(), Error> { + self.write_multiple(&[byte], options)?; + Ok(()) + } + + fn write_multiple( + &self, + bytes: &[u8], + options: &TerminalOutputOptions, + ) -> Result { + if self.disconnected.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let result = block!(self.device.write(bytes, *options).await)?; + match result { + Ok(len) => Ok(len), + Err(UsbError::MemoryError(err) | UsbError::SystemError(err)) => Err(err), + Err(error) => { + log::warn!("{}: write error: {:?}", self.device.display_name(), error); + Err(Error::InvalidOperation) + } + } + } + + fn baud_rate(&self) -> u32 { + self.device.baud_rate() + } + + fn set_baud_rate(&self, baud: u32) -> Result<(), Error> { + if self.disconnected.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let result = block!(self.device.set_baud_rate(baud).await)?; + match result { + Ok(()) => Ok(()), + Err(UsbError::MemoryError(err) | UsbError::SystemError(err)) => Err(err), + Err(error) => { + log::warn!( + "{}: baud rate set error: {:?}", + self.device.display_name(), + error + ); + Err(Error::InvalidOperation) + } + } + } + + fn open(&self, pid: ProcessId) -> Result<(), Error> { + if self.disconnected.load(Ordering::Acquire) { + return Err(Error::InvalidOperation); + } + let _ = pid; + let result = block! { self.device.setup().await }?; + match result { + Ok(()) => Ok(()), + Err(UsbError::MemoryError(err) | UsbError::SystemError(err)) => Err(err), + Err(error) => { + log::warn!("{}: setup error: {:?}", self.device.display_name(), error); + Err(Error::InvalidOperation) + } + } + } +} + +impl FT232Device { + const FTDI_RESET: u8 = 0; + const FTDI_SET_BAUD_RATE: u8 = 3; + + async fn ftdi_reset(&self, port: u16) -> Result<(), UsbError> { + self.device + .control_pipe() + .control_transfer(ControlTransferSetup { + bm_request_type: 0b01000000, + b_request: Self::FTDI_RESET, + w_value: 0, + w_index: port, + w_length: 0, + }) + .await + } + + async fn ftdi_set_baud_rate(&self, port: u16, baud: u32) -> Result<(), UsbError> { + let w_value = match baud { + 300 => 0x2710, + 600 => 0x1388, + 1200 => 0x09C4, + 2400 => 0x04E2, + 4800 => 0x0271, + 9600 => 0x4138, + 19200 => 0x809C, + 38400 => 0xC04E, + 57600 => 0x0034, + 115200 => 0x001A, + 230400 => 0x000D, + 460800 => 0x4006, + 921600 => 0x8003, + _ => { + log::warn!("ft232: unsupported baud rate {baud}"); + return Err(UsbError::InvalidConfiguration); + } + }; + + self.device + .control_pipe() + .control_transfer(ControlTransferSetup { + bm_request_type: 0b01000000, + b_request: Self::FTDI_SET_BAUD_RATE, + w_value, + w_index: port, + w_length: 0, + }) + .await + } +} + +#[async_trait] +impl UsbSerialDevice for FT232Device { + async fn setup(&self) -> Result<(), UsbError> { + log::info!("ft232: setup"); + Ok(()) + } + + async fn set_baud_rate(&self, baud: u32) -> Result<(), UsbError> { + self.ftdi_set_baud_rate(0, baud).await?; + // *self.baud_rate.write() = baud; + log::info!("ft232: set baud rate {baud}"); + Ok(()) + } + fn baud_rate(&self) -> u32 { + *self.baud_rate.read() + } + + async fn write( + &self, + buffer: &[u8], + options: TerminalOutputOptions, + ) -> Result { + if options.contains(TerminalOutputOptions::NL_TO_CRNL) { + for &byte in buffer { + if byte == b'\n' { + self.bulk_out.write(b"\r").await?; + } + self.bulk_out.write(&[byte]).await?; + } + Ok(buffer.len()) + } else { + self.bulk_out.write(buffer).await + } + } + + fn display_name(&self) -> &str { + "FT232 Serial Converter" + } + fn device(&self) -> &Arc { + &self.device + } +} + +#[async_trait] +impl UsbDeviceDriver for FT232Driver { + async fn run(self: Arc, device: Arc) -> Result<(), UsbError> { + let interface = device.interface(0); + let endpoints = interface.endpoints(); + + let bulk_in = endpoints + .iter() + .find(|ep| ep.ty == UsbEndpointType::Bulk && ep.direction == UsbDirection::In) + .ok_or(UsbError::InvalidConfiguration)?; + let bulk_out = endpoints + .iter() + .find(|ep| ep.ty == UsbEndpointType::Bulk && ep.direction == UsbDirection::Out) + .ok_or(UsbError::InvalidConfiguration)?; + + let bulk_in = device + .open_bulk_in_pipe(bulk_in.number, bulk_in.max_packet_size as _) + .await?; + let bulk_out = device + .open_bulk_out_pipe(bulk_out.number, bulk_out.max_packet_size as _) + .await?; + + let ft232 = Arc::new(FT232Device { + device, + bulk_out, + baud_rate: IrqSafeRwLock::new(115200), + }); + ft232.ftdi_reset(0).await?; + let terminal = register_usb_serial(ft232)?; + + let mut buffer = [0; 3]; + loop { + let len = bulk_in.read(&mut buffer).await?; + if len < 2 { + continue; + } + for &byte in &buffer[2..len] { + terminal.write_to_input(byte); + } + } + } + + fn name(&self) -> &'static str { + "FT232 Serial Converter" + } + + fn probe(&self, class: u8, subclass: u8, protocol: u8, vid: u16, pid: u16) -> bool { + let _ = (class, subclass, protocol); + vid == 0x0403 && pid == 0x6001 + } +} + +static USB_SERIALS: IrqSafeSpinlock>>>> = + IrqSafeSpinlock::new(BTreeMap::new()); + +fn register_usb_serial( + serial: Arc, +) -> Result>>, UsbError> { + let mut serials = USB_SERIALS.lock(); + for i in 0..64 { + if serials.contains_key(&i) { + continue; + } + let wrapper = Arc::new(UsbSerialDeviceWrapper { + device: serial, + index: i, + disconnected: AtomicBool::new(false), + }); + wrapper.device.device().set_detach_handler(wrapper.clone()); + let input = TerminalInput::with_capacity(64).expect("Couldn't allocate input buffer"); + let terminal = Arc::new(Terminal::from_parts( + TerminalOptions::const_default(), + input, + wrapper, + )); + serials.insert(i, terminal.clone()); + let name = alloc::format!("ttyUSB{i}"); + devfs::add_named_char_device(terminal.clone(), name.clone(), FileMode::new(0o600)).ok(); + return Ok(terminal); + } + Err(UsbError::DriverError) +} + +fn remove_usb_serial(index: u32) { + let serial = USB_SERIALS.lock().remove(&index); + if serial.is_none() { + log::warn!("usb-serial #{index} doesn't exist in the table"); + } + let name = alloc::format!("ttyUSB{index}"); + devfs::remove_node(name).ok(); +} diff --git a/kernel/libk/src/device/char.rs b/kernel/libk/src/device/char.rs index ad84bf53..63c2aa0e 100644 --- a/kernel/libk/src/device/char.rs +++ b/kernel/libk/src/device/char.rs @@ -41,11 +41,11 @@ pub trait CharDevice: Device + FileReadiness { Err(Error::NotImplemented) } - fn lock(&self, process: ProcessId) -> Result<(), Error> { + fn open(&self, process: ProcessId) -> Result<(), Error> { let _ = process; Ok(()) } - fn release(&self, process: ProcessId) -> Result<(), Error> { + fn close(&self, process: ProcessId) -> Result<(), Error> { let _ = process; Ok(()) } @@ -137,10 +137,10 @@ impl CharDevice for I2CDevice { } } - fn lock(&self, process: ProcessId) -> Result<(), Error> { + fn open(&self, process: ProcessId) -> Result<(), Error> { I2CDevice::lock(self, process) } - fn release(&self, process: ProcessId) -> Result<(), Error> { + fn close(&self, process: ProcessId) -> Result<(), Error> { I2CDevice::release(self, process) } } diff --git a/kernel/libk/src/vfs/file/device.rs b/kernel/libk/src/vfs/file/device.rs index c509b594..715962f0 100644 --- a/kernel/libk/src/vfs/file/device.rs +++ b/kernel/libk/src/vfs/file/device.rs @@ -108,6 +108,6 @@ impl CharFile { impl Drop for CharFile { fn drop(&mut self) { // TODO doesn't work with fork - self.device.release(self.pid).ok(); + self.device.close(self.pid).ok(); } } diff --git a/kernel/libk/src/vfs/file/mod.rs b/kernel/libk/src/vfs/file/mod.rs index 1d0813f1..430bf66b 100644 --- a/kernel/libk/src/vfs/file/mod.rs +++ b/kernel/libk/src/vfs/file/mod.rs @@ -256,7 +256,7 @@ impl File { ) -> Result, Error> { let pid = Thread::current().process_id(); - device.lock(pid)?; + device.open(pid)?; let read = opts.contains(OpenOptions::READ); let write = opts.contains(OpenOptions::WRITE); diff --git a/kernel/libk/src/vfs/terminal.rs b/kernel/libk/src/vfs/terminal.rs index 077b26b3..39d4f320 100644 --- a/kernel/libk/src/vfs/terminal.rs +++ b/kernel/libk/src/vfs/terminal.rs @@ -1,5 +1,6 @@ use core::{ mem::MaybeUninit, + ops::Deref, sync::atomic::{AtomicBool, Ordering}, task::{Context, Poll}, }; @@ -19,7 +20,7 @@ use yggdrasil_abi::{ device::{self, TerminalRequestVariant}, }, option::RequestValue, - process::{ProcessGroupId, Signal}, + process::{ProcessGroupId, ProcessId, Signal}, }; use crate::{device::char::CharDevice, task::process::Process}; @@ -82,6 +83,15 @@ pub trait TerminalOutput: Sync + Send { Err(Error::NotImplemented) } } + + fn open(&self, pid: ProcessId) -> Result<(), Error> { + let _ = pid; + Ok(()) + } + fn close(&self, pid: ProcessId) -> Result<(), Error> { + let _ = pid; + Ok(()) + } } struct InputBuffer { @@ -424,6 +434,13 @@ impl CharDevice for Terminal { fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result { self.handle_device_request(option, buffer, len) } + + fn open(&self, pid: ProcessId) -> Result<(), Error> { + self.output.open(pid) + } + fn close(&self, pid: ProcessId) -> Result<(), Error> { + self.output.close(pid) + } } impl FileReadiness for Terminal { @@ -438,6 +455,44 @@ impl Device for Terminal { } } +impl TerminalOutput for Arc { + fn write(&self, byte: u8, options: &TerminalOutputOptions) -> Result<(), Error> { + self.deref().write(byte, options) + } + fn write_multiple( + &self, + bytes: &[u8], + options: &TerminalOutputOptions, + ) -> Result { + self.deref().write_multiple(bytes, options) + } + + fn notify_readers(&self) { + self.deref().notify_readers(); + } + + fn size(&self) -> TerminalSize { + self.deref().size() + } + fn set_size(&self, size: TerminalSize) -> Result<(), Error> { + self.deref().set_size(size) + } + + fn baud_rate(&self) -> u32 { + self.deref().baud_rate() + } + fn set_baud_rate(&self, baud: u32) -> Result<(), Error> { + self.deref().set_baud_rate(baud) + } + + fn open(&self, pid: ProcessId) -> Result<(), Error> { + self.deref().open(pid) + } + fn close(&self, pid: ProcessId) -> Result<(), Error> { + self.deref().close(pid) + } +} + pub fn read_all(source: &mut RingBuffer, target: &mut [u8], eof: Option) -> usize { let mut pos = 0; while pos < target.len()