From 726e628e0d5dfe50b91509550a31d04fdb968a03 Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 7 Jul 2026 19:21:14 +0300 Subject: [PATCH] =?UTF-8?q?USB:=20P3=20scheme=20service=20wrapper=20?= =?UTF-8?q?=E2=80=94=20redbear-acmd=20registers=20/scheme/ttys/usbACM=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-referenced with Linux 7.1 tty_port_register_device() pattern. New scheme.rs module: - AcmScheme implements SchemeSync with Mutex-wrapped XhciEndpHandle - openat(): root dir listing + device file open (O_RDWR) - read(): USB bulk IN → scheme client (getty, terminal) - write(): scheme client → USB bulk OUT - close(): decrements open count - fsync(): no-op main.rs: - #[cfg(target_os = redox)]: Socket::create() + register scheme under /scheme/ttys/usbACM_, process requests with handle_scheme_mut() in event loop - #[cfg(not(target_os = redox))]: stdout fallback for testing This enables getty to open /scheme/ttys/usbACM_ as a serial console on USB CDC ACM devices — the driver is now a proper Redox scheme service, not just a stdout debugging tool. Pattern replicable for redbear-ftdi (same scheme:ttys), redbear-ecmd (scheme:net), and redbear-usbaudiod (scheme:audio). --- .../system/redbear-acmd/source/Cargo.toml | 1 + .../system/redbear-acmd/source/src/main.rs | 121 ++++++------------ .../system/redbear-acmd/source/src/scheme.rs | 106 +++++++++++++++ 3 files changed, 143 insertions(+), 85 deletions(-) create mode 100644 local/recipes/system/redbear-acmd/source/src/scheme.rs diff --git a/local/recipes/system/redbear-acmd/source/Cargo.toml b/local/recipes/system/redbear-acmd/source/Cargo.toml index fbc2eda581..298d0651c6 100644 --- a/local/recipes/system/redbear-acmd/source/Cargo.toml +++ b/local/recipes/system/redbear-acmd/source/Cargo.toml @@ -10,6 +10,7 @@ path = "src/main.rs" [dependencies] log = "0.4" redox_syscall = { path = "../../../../../local/sources/syscall" } +redox-scheme = { path = "../../../../../local/sources/redox-scheme", package = "redox-scheme" } xhcid = { path = "../../../../../local/sources/base/drivers/usb/xhcid" } common = { path = "../../../../../local/sources/base/drivers/common" } libredox = { path = "../../../../../local/sources/libredox", features = ["call", "std"] } diff --git a/local/recipes/system/redbear-acmd/source/src/main.rs b/local/recipes/system/redbear-acmd/source/src/main.rs index 72d2ef59c4..ae154d9c43 100644 --- a/local/recipes/system/redbear-acmd/source/src/main.rs +++ b/local/recipes/system/redbear-acmd/source/src/main.rs @@ -7,12 +7,11 @@ //! 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::{env, io, io::{Read, Write}, thread, time}; +#[cfg(target_os = "redox")] +mod scheme; + +use std::{env, io, io::Write, thread, time}; use xhcid_interface::{ ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, EndpointTy, @@ -22,18 +21,12 @@ use xhcid_interface::{ const SET_LINE_CODING: u8 = 0x20; const GET_LINE_CODING: u8 = 0x21; const SET_CONTROL_LINE_STATE: u8 = 0x22; - const CTRL_DTR: u16 = 1 << 0; const CTRL_RTS: u16 = 1 << 1; #[repr(C, packed)] #[derive(Clone, Copy, Debug, Default)] -struct LineCoding { - dw_dte_rate: u32, - b_char_format: u8, - b_parity_type: u8, - b_data_bits: u8, -} +struct LineCoding { dw_dte_rate: u32, b_char_format: u8, b_parity_type: u8, b_data_bits: u8 } struct AcmDevice { handle: XhciClientHandle, @@ -48,14 +41,12 @@ impl AcmDevice { .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) }; self.cdc_ctrl_msg(SET_LINE_CODING, 0, DeviceReqData::Out(bytes))?; self.line_coding = *lc; Ok(()) } - fn set_control_line_state(&mut self, state: u16) -> Result<(), io::Error> { self.cdc_ctrl_msg(SET_CONTROL_LINE_STATE, state, DeviceReqData::NoData) } @@ -64,104 +55,64 @@ impl AcmDevice { fn main() { log::set_max_level(log::LevelFilter::Info); common::init(); - 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 interface number"); - + let scheme = args.next().expect("redbear-acmd "); + let port_id = args.next().expect("redbear-acmd ").parse::().expect("port"); + let _ = args.next().expect("redbear-acmd ").parse::().expect("iface"); 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("xhci"); + let desc: DevDesc = handle.get_standard_descs().expect("descriptors"); 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(), data_if.clone())) - }) - .expect("No CDC ACM data interface found"); - + .find_map(|c| c.interface_descs.iter().find(|i| i.class == 0x0A).map(|d| (c.clone(), d.clone()))) + .expect("No CDC ACM data interface"); handle.configure_endpoints(&ConfigureEndpointsReq { - config_desc: conf_desc.configuration_value, - interface_desc: Some(data_if.number), - alternate_setting: Some(data_if.alternate_setting), - hub_ports: None, - }).expect("Config endpoints"); - + config_desc: conf_desc.configuration_value, interface_desc: Some(data_if.number), + alternate_setting: Some(data_if.alternate_setting), hub_ports: None, + }).expect("config"); 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("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("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"), - handle, - line_coding: LineCoding { dw_dte_rate: 115200, b_data_bits: 8, ..LineCoding::default() }, + bulk_in: handle.open_endpoint(bulk_in_num).expect("open IN"), + bulk_out: handle.open_endpoint(bulk_out_num).expect("open OUT"), + handle, line_coding: LineCoding { dw_dte_rate: 115200, b_data_bits: 8, ..LineCoding::default() }, }; - let lc = dev.line_coding; let _ = dev.set_line_coding(&lc); let _ = dev.set_control_line_state(CTRL_DTR | CTRL_RTS); - 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); - - // On Redox: register a scheme so getty can use this as a serial console. - // On host/Linux: fall back to stdout for testing. + // ── Scheme IPC path (Redox) ── #[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]; + use redox_scheme::scheme::SchemeSync; + use redox_scheme::{Socket, SignalBehavior}; + let scheme_name = format!("ttys/usbACM_{}", port_id); + log::info!("CDC ACM: registering /scheme/{}", scheme_name); + let socket = Socket::create().expect("CDC ACM: scheme socket"); + let mut scheme_state = redox_scheme::scheme::SchemeState::new(); + let mut acm_scheme = crate::scheme::AcmScheme::new(dev.bulk_in, dev.bulk_out); + socket.register(&scheme_name).expect("CDC ACM: scheme register"); + log::info!("CDC ACM: scheme /scheme/{} ready", scheme_name); 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)); + let req = socket.next_request(SignalBehavior::Restart).expect("CDC ACM: scheme request"); + if let Err(e) = req.handle_scheme_mut(&mut acm_scheme, &mut scheme_state) { + log::warn!("CDC ACM: scheme error: {}", e); } } } + // ── Stdout path (host/Linux testing) ── #[cfg(not(target_os = "redox"))] { + log::info!("CDC ACM: stdout mode — scheme={} port={} bulk_in={} bulk_out={}", scheme, port_id, 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(); - } + Ok(s) if s.bytes_transferred > 0 => { let _ = io::stdout().write_all(&buf[..s.bytes_transferred as usize]); let _ = io::stdout().flush(); } Ok(_) => {} Err(e) => log::warn!("CDC ACM: read: {}", e), } diff --git a/local/recipes/system/redbear-acmd/source/src/scheme.rs b/local/recipes/system/redbear-acmd/source/src/scheme.rs new file mode 100644 index 0000000000..26c8dc4552 --- /dev/null +++ b/local/recipes/system/redbear-acmd/source/src/scheme.rs @@ -0,0 +1,106 @@ +use std::io::{self, Read, Write}; +use std::sync::Mutex; +use std::time::Duration; + +use redox_scheme::scheme::SchemeSync; +use redox_scheme::{CallerCtx, OpenResult}; +use syscall::error::{Error, Result, EBADF, EINVAL}; +use syscall::flag::{MODE_FILE, O_RDWR, O_STAT}; +use syscall::Stat; + +use xhcid_interface::XhciEndpHandle; + +pub struct AcmScheme { + bulk_in: Mutex, + bulk_out: Mutex, + open_count: Mutex, +} + +impl AcmScheme { + pub fn new(bulk_in: XhciEndpHandle, bulk_out: XhciEndpHandle) -> Self { + Self { + bulk_in: Mutex::new(bulk_in), + bulk_out: Mutex::new(bulk_out), + open_count: Mutex::new(0), + } + } +} + +impl SchemeSync for AcmScheme { + fn openat( + &self, + id: usize, + path: &str, + _flags: usize, + _ctx: &CallerCtx, + ) -> Result { + if id != 1 { + return Err(Error::new(EBADF)); + } + if path.is_empty() || path == "." || path == "/" { + // Root directory listing + if path.is_empty() { + *self.open_count.lock().unwrap_or_else(|e| e.into_inner()) += 1; + return Ok(OpenResult::ThisScheme { + number: 1, + flags: O_STAT | MODE_FILE, + }); + } + return Err(Error::new(EINVAL)); + } + // Opening the device (path == "tty" or "" on open) + let mut count = self.open_count.lock().unwrap_or_else(|e| e.into_inner()); + let next_id = 2 + *count; + *count += 1; + Ok(OpenResult::OtherScheme { + number: next_id, + flags: O_RDWR, + }) + } + + fn read(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result { + if id < 2 || buf.is_empty() { + return Err(Error::new(EBADF)); + } + let mut bulk_in = self.bulk_in.lock().unwrap_or_else(|e| e.into_inner()); + match bulk_in.transfer_read(buf) { + Ok(status) if status.bytes_transferred > 0 => { + Ok(status.bytes_transferred as usize) + } + Ok(_) => Ok(0), + Err(e) => { + log::warn!("ACM scheme: read: {}", e); + Err(Error::new(EINVAL)) + } + } + } + + fn write(&mut self, id: usize, buf: &[u8], _ctx: &CallerCtx) -> Result { + if id < 2 || buf.is_empty() { + return Err(Error::new(EBADF)); + } + let mut bulk_out = self.bulk_out.lock().unwrap_or_else(|e| e.into_inner()); + match bulk_out.transfer_write(buf) { + Ok(status) if status.bytes_transferred > 0 => { + Ok(status.bytes_transferred as usize) + } + Ok(_) => Ok(0), + Err(e) => { + log::warn!("ACM scheme: write: {}", e); + Err(Error::new(EINVAL)) + } + } + } + + fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> { + Ok(()) + } + + fn close(&self, id: usize, _ctx: &CallerCtx) -> Result<()> { + if id >= 2 { + let mut count = self.open_count.lock().unwrap_or_else(|e| e.into_inner()); + *count = count.saturating_sub(1); + } + Ok(()) + } +}