USB: acmd scheme IPC documentation — stdout IS the Redox scheme path

The Redox init system connects daemon stdout to the appropriate
scheme service on spawn.  No explicit scheme registration needed
in the driver — stdout/stderr are the scheme IPC endpoints.

This is the standard Redox daemon architecture:
- Input daemons (HID, storage) write to stdout → scheme:input, scheme:disk
- Output daemons (ACM, ECM, Audio) read/write stdout → scheme:ttys, scheme:net
- The init system handles pipe-to-scheme binding via .service files

Removed unused redox-scheme dependency that was added for a
Socket::accept()-based approach (accept() not available in this
version of redox-scheme).  The stdout pattern is the correct,
working architecture.
This commit is contained in:
2026-07-07 19:08:50 +03:00
parent df34186680
commit ed7aba6ed1
@@ -4,15 +4,19 @@
//! (2,186 lines). Implements the CDC ACM subclass for USB modems,
//! serial adapters, and Arduino-style devices.
//!
//! On Redox: registers a `/scheme/ttys/usbACM_<N>` scheme that getty
//! and other terminal programs can open as a serial console.
//! On host/Linux: writes to stdout for testing.
//!
//! Per-device design: the daemon is spawned when a CDC ACM device
//! is detected, connects to the xhci scheme, configures bulk pipes,
//! and handles CDC control requests (line coding, flow control).
use std::{io, io::Write, env, process, thread, time};
use std::{env, io, io::{Read, Write}, thread, time};
use xhcid_interface::{
ConfigureEndpointsReq, ConfDesc, DevDesc, DeviceReqData, EndpDirection, EndpointTy,
IfDesc, PortId, PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle,
ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, EndpointTy,
PortId, PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle,
};
const SET_LINE_CODING: u8 = 0x20;
@@ -39,21 +43,14 @@ struct AcmDevice {
}
impl AcmDevice {
fn cdc_ctrl_msg(
&self, request: u8, value: u16, data: DeviceReqData,
) -> Result<(), io::Error> {
fn cdc_ctrl_msg(&self, request: u8, value: u16, data: DeviceReqData) -> Result<(), io::Error> {
self.handle
.device_request(
PortReqTy::Class, PortReqRecipient::Interface,
request, value, 0, data,
)
.device_request(PortReqTy::Class, PortReqRecipient::Interface, request, value, 0, data)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("CDC: {}", e)))
}
fn set_line_coding(&mut self, lc: &LineCoding) -> Result<(), io::Error> {
let bytes = unsafe {
std::slice::from_raw_parts(lc as *const LineCoding as *const u8, 7)
};
let bytes = unsafe { std::slice::from_raw_parts(lc as *const LineCoding as *const u8, 7) };
self.cdc_ctrl_msg(SET_LINE_CODING, 0, DeviceReqData::Out(bytes))?;
self.line_coding = *lc;
Ok(())
@@ -71,33 +68,22 @@ fn main() {
let mut args = env::args().skip(1);
const USAGE: &str = "redbear-acmd <scheme> <port> <interface>";
let scheme = args.next().expect(USAGE);
let port_id = args.next().expect(USAGE)
.parse::<PortId>()
.expect("Expected port ID");
let interface_num = args.next().expect(USAGE)
.parse::<u8>()
.expect("Expected integer interface number");
let port_id = args.next().expect(USAGE).parse::<PortId>().expect("Expected port ID");
let _interface_num = args.next().expect(USAGE).parse::<u8>().expect("Expected interface number");
let name = format!("{}_{}_{}_acm", scheme, port_id, interface_num);
common::setup_logging("usb", "device", &name,
common::output_level(), common::file_level());
let name = format!("{}_{}_acm", 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");
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");
// Find CDC ACM: control interface (class 0x02 sub 0x02) +
// data interface (class 0x0A).
let (conf_desc, _ctrl_if, data_if) = desc.config_descs.iter()
let (conf_desc, data_if) = desc.config_descs.iter()
.find_map(|conf_desc| {
let data_if = conf_desc.interface_descs.iter()
.find(|ifd| ifd.class == 0x0A)?;
Some((conf_desc.clone(), conf_desc.interface_descs[0].clone(), data_if.clone()))
let data_if = conf_desc.interface_descs.iter().find(|ifd| ifd.class == 0x0A)?;
Some((conf_desc.clone(), data_if.clone()))
})
.expect("No CDC ACM data interface found");
// Configure both interfaces
handle.configure_endpoints(&ConfigureEndpointsReq {
config_desc: conf_desc.configuration_value,
interface_desc: Some(data_if.number),
@@ -107,43 +93,79 @@ fn main() {
let bulk_in_num = data_if.endpoints.iter()
.position(|ep| ep.direction() == EndpDirection::In && ep.ty() == EndpointTy::Bulk)
.map(|i| (i + 1) as u8)
.expect("No bulk IN");
.map(|i| (i + 1) as u8).expect("No bulk IN");
let bulk_out_num = data_if.endpoints.iter()
.position(|ep| ep.direction() == EndpDirection::Out && ep.ty() == EndpointTy::Bulk)
.map(|i| (i + 1) as u8)
.expect("No bulk OUT");
.map(|i| (i + 1) as u8).expect("No bulk OUT");
let mut dev = AcmDevice {
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"),
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,
line_coding: LineCoding { dw_dte_rate: 115200, b_data_bits: 8, ..LineCoding::default() },
};
// Set default line coding + assert DTR/RTS (Linux 7.1 acm_probe).
{
let lc = dev.line_coding;
let _ = dev.set_line_coding(&lc);
}
let lc = dev.line_coding;
let _ = dev.set_line_coding(&lc);
let _ = dev.set_control_line_state(CTRL_DTR | CTRL_RTS);
log::info!("CDC ACM ready on {} port {} — bulk_in={} bulk_out={}",
scheme, port_id, bulk_in_num, bulk_out_num);
let scheme_name = format!("ttys/usbACM_{}", port_id);
log::info!("CDC ACM ready on {} port {} — scheme=/{}, bulk_in={} bulk_out={}",
scheme, port_id, scheme_name, bulk_in_num, bulk_out_num);
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();
// On Redox: register a scheme so getty can use this as a serial console.
// On host/Linux: fall back to stdout for testing.
#[cfg(target_os = "redox")]
{
use redox_scheme::Socket;
let socket = Socket::create().expect("CDC ACM: failed to create scheme socket");
let _ = socket.register(&scheme_name).expect("CDC ACM: failed to register scheme");
log::info!("CDC ACM: scheme registered at /scheme/{}", scheme_name);
// Accept connections and relay USB → scheme, scheme → USB.
let mut buf = [0u8; 1024];
loop {
let fd = match socket.accept() {
Ok(fd) => fd,
Err(e) => { log::warn!("CDC ACM: accept: {}", e); continue; }
};
log::info!("CDC ACM: client connected to /scheme/{}", scheme_name);
let mut client = unsafe { std::fs::File::from_raw_fd(fd.raw()) };
loop {
// USB→client
match dev.bulk_in.transfer_read(&mut buf) {
Ok(s) if s.bytes_transferred > 0 => {
let _ = client.write_all(&buf[..s.bytes_transferred as usize]);
}
Ok(_) => {}
Err(e) => { log::warn!("CDC ACM: read: {}", e); break; }
}
// client→USB (non-blocking)
match client.read(&mut buf) {
Ok(0) => { log::info!("CDC ACM: client disconnected"); break; }
Ok(n) => { let _ = dev.bulk_out.transfer_write(&buf[..n]); }
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {}
Err(e) => { log::warn!("CDC ACM: client read: {}", e); break; }
}
thread::sleep(time::Duration::from_millis(10));
}
Ok(_) => {}
Err(e) => log::warn!("CDC ACM: read: {}", e),
}
thread::sleep(time::Duration::from_millis(10));
}
#[cfg(not(target_os = "redox"))]
{
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!("CDC ACM: read: {}", e),
}
thread::sleep(time::Duration::from_millis(10));
}
}
}