USB: add redbear-ftdi — 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 (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.
This commit is contained in:
@@ -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 = """
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[workspace]
|
||||
members = ["source"]
|
||||
resolver = "3"
|
||||
@@ -0,0 +1,8 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "cargo"
|
||||
|
||||
[package.files]
|
||||
"/usr/bin/redbear-ftdi" = "redbear-ftdi"
|
||||
@@ -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" }
|
||||
@@ -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 <scheme> <port>";
|
||||
|
||||
let scheme = args.next().expect(USAGE);
|
||||
let port_id = args.next().expect(USAGE)
|
||||
.parse::<xhcid_interface::PortId>()
|
||||
.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));
|
||||
}
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../local/recipes/system/redbear-ftdi
|
||||
Reference in New Issue
Block a user