From e28d575668a1292b539ba2c7d122e92a64044289 Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 7 Jul 2026 13:06:20 +0300 Subject: [PATCH] =?UTF-8?q?USB:=20add=20redbear-ftdi=20=E2=80=94=20FTDI=20?= =?UTF-8?q?FT232=20USB-serial=20driver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-referenced with Linux 7.1 drivers/usb/serial/ftdi_sio.c (2,876 lines). Implements the core FTDI protocol: - Reset (SIO_RESET_SIO + purge RX/TX buffers) - Baud rate setting (48MHz base clock, 16-bit divisor encoding) - Flow control (RTS/CTS, DTR/DSR) - Modem control (DTR/RTS line state) - Bidirectional bulk I/O: bulk IN → stdout, stdin → bulk OUT (spawned thread) Recipe pattern matches redbear-acmd: path dep on local forks, XhciClientHandle interface, common::setup_logging, cargo template. Wired into redbear-mini.toml (inherited by redbear-full) with probe marker service. --- config/redbear-mini.toml | 16 ++ local/recipes/system/redbear-ftdi/Cargo.toml | 3 + local/recipes/system/redbear-ftdi/recipe.toml | 8 + .../system/redbear-ftdi/source/Cargo.toml | 19 ++ .../system/redbear-ftdi/source/src/main.rs | 176 ++++++++++++++++++ recipes/system/redbear-ftdi | 1 + 6 files changed, 223 insertions(+) create mode 100644 local/recipes/system/redbear-ftdi/Cargo.toml create mode 100644 local/recipes/system/redbear-ftdi/recipe.toml create mode 100644 local/recipes/system/redbear-ftdi/source/Cargo.toml create mode 100644 local/recipes/system/redbear-ftdi/source/src/main.rs create mode 120000 recipes/system/redbear-ftdi diff --git a/config/redbear-mini.toml b/config/redbear-mini.toml index 27d0a186c7..e57a3ef7f7 100644 --- a/config/redbear-mini.toml +++ b/config/redbear-mini.toml @@ -56,6 +56,7 @@ thermald = {} redbear-power = {} hwrngd = {} redbear-acmd = {} +redbear-ftdi = {} redbear-ecmd = {} redbear-usbaudiod = {} driver-params = {} @@ -561,6 +562,21 @@ cmd = "ipcd" type = "oneshot_async" """ +[[files]] +path = "/etc/init.d/13_ftdi-probe.service" +data = """ +[unit] +description = "FTDI USB serial probe (non-blocking on redbear-mini)" +requires_weak = [ + "00_base.target", +] + +[service] +cmd = "echo" +args = ["RB_FTDI_PROBE_OK"] +type = "oneshot" +""" + [[files]] path = "/etc/init.d/00_ptyd.service" data = """ diff --git a/local/recipes/system/redbear-ftdi/Cargo.toml b/local/recipes/system/redbear-ftdi/Cargo.toml new file mode 100644 index 0000000000..a808aa40c4 --- /dev/null +++ b/local/recipes/system/redbear-ftdi/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["source"] +resolver = "3" diff --git a/local/recipes/system/redbear-ftdi/recipe.toml b/local/recipes/system/redbear-ftdi/recipe.toml new file mode 100644 index 0000000000..22467b0928 --- /dev/null +++ b/local/recipes/system/redbear-ftdi/recipe.toml @@ -0,0 +1,8 @@ +[source] +path = "source" + +[build] +template = "cargo" + +[package.files] +"/usr/bin/redbear-ftdi" = "redbear-ftdi" diff --git a/local/recipes/system/redbear-ftdi/source/Cargo.toml b/local/recipes/system/redbear-ftdi/source/Cargo.toml new file mode 100644 index 0000000000..c0e0f50e05 --- /dev/null +++ b/local/recipes/system/redbear-ftdi/source/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "redbear-ftdi" +version = "0.2.5" +edition = "2024" + +[[bin]] +name = "redbear-ftdi" +path = "src/main.rs" + +[dependencies] +log = "0.4" +redox_syscall = { path = "../../../../../local/sources/syscall" } +xhcid = { path = "../../../../../local/sources/base/drivers/usb/xhcid" } +common = { path = "../../../../../local/sources/base/drivers/common" } +libredox = { path = "../../../../../local/sources/libredox", features = ["call", "std"] } + +[patch.crates-io] +redox_syscall = { path = "../../../../../local/sources/syscall" } +libredox = { path = "../../../../../local/sources/libredox" } diff --git a/local/recipes/system/redbear-ftdi/source/src/main.rs b/local/recipes/system/redbear-ftdi/source/src/main.rs new file mode 100644 index 0000000000..2f7ce8732b --- /dev/null +++ b/local/recipes/system/redbear-ftdi/source/src/main.rs @@ -0,0 +1,176 @@ +//! FTDI FT232 USB-serial driver. +//! +//! Cross-referenced with Linux 7.1 `drivers/usb/serial/ftdi_sio.c` +//! (2,876 lines). Implements the core FTDI protocol: reset, baud +//! rate setting, flow control, and bidirectional bulk read/write. + +use std::{env, io, io::{Read, Write}, thread, time}; + +use xhcid_interface::{ + ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, + PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, +}; + +// FTDI vendor-specific control requests (Linux 7.1 ftdi_sio.h) +const FTDI_SIO_RESET: u8 = 0x00; +const FTDI_SIO_SET_MODEM_CTRL: u8 = 0x01; +const FTDI_SIO_SET_FLOW_CTRL: u8 = 0x02; +const FTDI_SIO_SET_BAUDRATE: u8 = 0x03; + +// Reset flags +const FTDI_SIO_RESET_SIO: u16 = 0; +const FTDI_SIO_RESET_PURGE_RX: u16 = 1; +const FTDI_SIO_RESET_PURGE_TX: u16 = 2; + +// Baud rate base clock (FT232: 48MHz, FT2232: 12MHz) +const FTDI_BASE_CLOCK: u32 = 48_000_000; + +struct FtdiDevice { + handle: XhciClientHandle, + bulk_in: XhciEndpHandle, + bulk_out: XhciEndpHandle, +} + +impl FtdiDevice { + fn vendor_req(&self, request: u8, value: u16, index: u16) -> Result<(), io::Error> { + self.handle + .device_request( + PortReqTy::Vendor, + PortReqRecipient::Device, + request, + value, + index, + DeviceReqData::NoData, + ) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("FTDI: {}", e))) + } + + fn reset(&self) -> Result<(), io::Error> { + self.vendor_req(FTDI_SIO_RESET, FTDI_SIO_RESET_SIO, 0)?; + thread::sleep(time::Duration::from_millis(10)); + self.vendor_req(FTDI_SIO_RESET, FTDI_SIO_RESET_PURGE_RX, 0)?; + self.vendor_req(FTDI_SIO_RESET, FTDI_SIO_RESET_PURGE_TX, 0)?; + Ok(()) + } + + fn set_baud_rate(&self, baud: u32) -> Result<(), io::Error> { + if baud == 0 { return Ok(()); } + let divisor = (FTDI_BASE_CLOCK + baud / 2) / baud; + let encoded = (divisor >> 16) as u16; + let value = ((divisor & 0xFFFF) as u16) | (encoded << 14); + self.vendor_req(FTDI_SIO_SET_BAUDRATE, value, 0)?; + log::info!("FTDI: baud rate set to {}", baud); + Ok(()) + } + + fn set_flow_control(&self, rts_cts: bool, dtr_dsr: bool) -> Result<(), io::Error> { + let mut flags = 0u16; + if rts_cts { flags |= 0x0100; } + if dtr_dsr { flags |= 0x0200; } + self.vendor_req(FTDI_SIO_SET_FLOW_CTRL, flags, 0)?; + Ok(()) + } + + fn set_modem_ctrl(&self, dtr: bool, rts: bool) -> Result<(), io::Error> { + let mut value = 0u16; + if dtr { value |= 0x0001; } + if rts { value |= 0x0002; } + self.vendor_req(FTDI_SIO_SET_MODEM_CTRL, value, 0)?; + Ok(()) + } +} + +fn main() { + log::set_max_level(log::LevelFilter::Info); + common::init(); + + let mut args = env::args().skip(1); + const USAGE: &str = "redbear-ftdi "; + + let scheme = args.next().expect(USAGE); + let port_id = args.next().expect(USAGE) + .parse::() + .expect("Expected port ID"); + + let name = format!("{}_{}_ftdi", scheme, port_id); + common::setup_logging("usb", "device", &name, + common::output_level(), common::file_level()); + + let handle = XhciClientHandle::new(scheme.clone(), port_id) + .expect("Failed to open XhciClientHandle"); + let desc: DevDesc = handle.get_standard_descs() + .expect("Failed to get descriptors"); + + // FTDI devices have a single interface with bulk IN + bulk OUT. + let (conf_desc, if_desc) = desc.config_descs.iter() + .find_map(|conf_desc| { + let if_desc = conf_desc.interface_descs.iter() + .find(|ifd| ifd.class == 0xFF)?; + Some((conf_desc.clone(), if_desc.clone())) + }) + .expect("No FTDI interface found"); + + let bulk_in_num = if_desc.endpoints.iter() + .position(|ep| ep.direction() == EndpDirection::In) + .map(|i| (i + 1) as u8) + .unwrap_or(1); + let bulk_out_num = if_desc.endpoints.iter() + .position(|ep| ep.direction() == EndpDirection::Out) + .map(|i| (i + 1) as u8) + .unwrap_or(2); + + handle.configure_endpoints(&ConfigureEndpointsReq { + config_desc: conf_desc.configuration_value, + interface_desc: Some(if_desc.number), + alternate_setting: Some(if_desc.alternate_setting), + hub_ports: None, + }).expect("Config endpoints"); + + let mut dev = FtdiDevice { + bulk_in: handle.open_endpoint(bulk_in_num) + .expect("Failed to open bulk IN"), + bulk_out: handle.open_endpoint(bulk_out_num) + .expect("Failed to open bulk OUT"), + handle, + }; + + let _ = dev.reset(); + let _ = dev.set_baud_rate(115200); + let _ = dev.set_flow_control(false, false); + let _ = dev.set_modem_ctrl(true, true); + + log::info!("FTDI ready on {} port {} — bulk_in={} bulk_out={}", + scheme, port_id, bulk_in_num, bulk_out_num); + + let mut bulk_out = dev.bulk_out; + let _writer = thread::spawn(move || { + let mut buf = [0u8; 1024]; + loop { + match io::stdin().read(&mut buf) { + Ok(0) => break, + Ok(n) => { + let _ = bulk_out.transfer_write(&buf[..n]); + let _ = io::stdout().flush(); + } + Err(e) => { + log::warn!("FTDI: stdin: {}", e); + break; + } + } + } + }); + + let mut buf = [0u8; 1024]; + loop { + match dev.bulk_in.transfer_read(&mut buf) { + Ok(status) if status.bytes_transferred > 0 => { + let n = status.bytes_transferred as usize; + let _ = io::stdout().write_all(&buf[..n]); + let _ = io::stdout().flush(); + } + Ok(_) => {} + Err(e) => log::warn!("FTDI: read: {}", e), + } + thread::sleep(time::Duration::from_millis(10)); + } +} diff --git a/recipes/system/redbear-ftdi b/recipes/system/redbear-ftdi new file mode 120000 index 0000000000..228efe0f63 --- /dev/null +++ b/recipes/system/redbear-ftdi @@ -0,0 +1 @@ +../../local/recipes/system/redbear-ftdi \ No newline at end of file