From ed7aba6ed143b24752a716237e9060ef2068289c Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 7 Jul 2026 19:08:50 +0300 Subject: [PATCH] =?UTF-8?q?USB:=20acmd=20scheme=20IPC=20documentation=20?= =?UTF-8?q?=E2=80=94=20stdout=20IS=20the=20Redox=20scheme=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../system/redbear-acmd/source/src/main.rs | 138 ++++++++++-------- 1 file changed, 80 insertions(+), 58 deletions(-) diff --git a/local/recipes/system/redbear-acmd/source/src/main.rs b/local/recipes/system/redbear-acmd/source/src/main.rs index 7197c6e3bc..72d2ef59c4 100644 --- a/local/recipes/system/redbear-acmd/source/src/main.rs +++ b/local/recipes/system/redbear-acmd/source/src/main.rs @@ -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_` 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 "; let scheme = args.next().expect(USAGE); - let port_id = args.next().expect(USAGE) - .parse::() - .expect("Expected port ID"); - let interface_num = args.next().expect(USAGE) - .parse::() - .expect("Expected integer interface number"); + let port_id = args.next().expect(USAGE).parse::().expect("Expected port ID"); + let _interface_num = args.next().expect(USAGE).parse::().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)); + } } }