From c7ed62eb30682c486e13bec64438fbbd087e8f9f Mon Sep 17 00:00:00 2001 From: vasilito Date: Tue, 7 Jul 2026 14:05:08 +0300 Subject: [PATCH] USB: P6-B real CDC ECM driver replacing 32-line stub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cross-referenced with Linux 7.1 drivers/net/usb/cdc_ether.c (984 lines) and include/uapi/linux/usb/cdc.h. Replaces the 32-line scan-and-symlink stub with a 230-line real class driver: Protocol (cdc.h:237-303): - SET_ETHERNET_PACKET_FILTER (0x43) — promiscuous + directed + broadcast + multicast filter matching Linux default (cdc_ether.c:70-80) - SET_ETHERNET_MULTICAST_FILTERS (0x40) — constants defined - SEND_ENCAPSULATED_COMMAND (0x00), GET_ENCAPSULATED_RESPONSE (0x01) Architecture: - Finds CDC ECM communications interface (class=02, subclass=06) - Data interface = ctrl_iface + 1 (Union descriptor pairing assumption) - Bulk IN + bulk OUT endpoints from data interface - Optional interrupt endpoint from control interface for CDC notifications - Bidirectional I/O: bulk IN → stdout (Rx), stdin → bulk OUT (Tx) - CDC notification handler: NETWORK_CONNECTION (link up/down), SPEED_CHANGE (tx/rx bitrates), generic notification logging Pattern matches redbear-acmd and redbear-ftdi: XhciClientHandle, path deps on local forks, common::setup_logging, cargo template. --- .../system/redbear-ecmd/source/Cargo.toml | 8 + .../system/redbear-ecmd/source/src/main.rs | 250 ++++++++++++++++-- 2 files changed, 232 insertions(+), 26 deletions(-) diff --git a/local/recipes/system/redbear-ecmd/source/Cargo.toml b/local/recipes/system/redbear-ecmd/source/Cargo.toml index 46a3745360..e47223399c 100644 --- a/local/recipes/system/redbear-ecmd/source/Cargo.toml +++ b/local/recipes/system/redbear-ecmd/source/Cargo.toml @@ -9,3 +9,11 @@ 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-ecmd/source/src/main.rs b/local/recipes/system/redbear-ecmd/source/src/main.rs index ac2bf6b218..479315d681 100644 --- a/local/recipes/system/redbear-ecmd/source/src/main.rs +++ b/local/recipes/system/redbear-ecmd/source/src/main.rs @@ -1,32 +1,230 @@ -use log::{info, LevelFilter}; -use std::fs; -use std::time::Duration; -struct StderrLogger; -impl log::Log for StderrLogger { - fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } - fn log(&self, r: &log::Record) { eprintln!("[{}] redbear-ecmd: {}", r.level(), r.args()); } - fn flush(&self) {} +//! USB CDC ECM (Ethernet Control Model) network driver. +//! +//! Cross-referenced with Linux 7.1 `drivers/net/usb/cdc_ether.c` +//! (984 lines) and `include/uapi/linux/usb/cdc.h`. +//! +//! Implements the CDC ECM subclass for USB Ethernet adapters: +//! control requests (packet filter, multicast), bidirectional +//! bulk I/O for Ethernet frames, and CDC notification handling. + +use std::{env, io, io::{Read, Write}, thread, time}; + +use xhcid_interface::{ + ConfigureEndpointsReq, DevDesc, DeviceReqData, EndpDirection, + PortReqRecipient, PortReqTy, XhciClientHandle, XhciEndpHandle, +}; + +// CDC ECM protocol constants (Linux 7.1 include/uapi/linux/usb/cdc.h) +const USB_CDC_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +const USB_CDC_GET_ENCAPSULATED_RESPONSE: u8 = 0x01; +const USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; +const USB_CDC_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; +const USB_CDC_GET_ETHERNET_STATISTIC: u8 = 0x44; + +// Packet filter bits (cdc.h:283-287) +const PACKET_TYPE_PROMISCUOUS: u16 = 1 << 0; +const PACKET_TYPE_ALL_MULTICAST: u16 = 1 << 1; +const PACKET_TYPE_DIRECTED: u16 = 1 << 2; +const PACKET_TYPE_BROADCAST: u16 = 1 << 3; +const PACKET_TYPE_MULTICAST: u16 = 1 << 4; + +// CDC notification types (cdc.h:300-303) +const CDC_NOTIFY_NETWORK_CONNECTION: u8 = 0x00; +const CDC_NOTIFY_SPEED_CHANGE: u8 = 0x2A; + +// USB class codes +const USB_CLASS_COMM: u8 = 0x02; +const USB_CDC_SUBCLASS_ECM: u8 = 0x06; + +struct EcmDevice { + _handle: XhciClientHandle, + bulk_in: XhciEndpHandle, + bulk_out: XhciEndpHandle, + int_ep: Option, } -fn scan() -> usize { - let mut n = 0; - let _ = fs::create_dir_all("/dev/net"); - if let Ok(dir) = fs::read_dir("/scheme/usb") { - for e in dir.flatten() { - if let Ok(c) = fs::read_to_string(e.path().join("config")) { - if c.contains("class=02") && (c.contains("subclass=06") || c.contains("subclass=0d")) { - let tgt = e.path(); - let lnk = format!("/dev/net/usb{}", n); - let _ = std::os::unix::fs::symlink(&tgt, &lnk); - n += 1; + +impl EcmDevice { + fn class_req(&self, handle: &XhciClientHandle, request: u8, value: u16, index: u16) -> Result<(), io::Error> { + handle + .device_request( + PortReqTy::Class, + PortReqRecipient::Interface, + request, + value, + index, + DeviceReqData::NoData, + ) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("CDC ECM: {}", e))) + } + + fn set_packet_filter(&self, handle: &XhciClientHandle, iface: u8, filter: u16) -> Result<(), io::Error> { + self.class_req(handle, USB_CDC_SET_ETHERNET_PACKET_FILTER, filter, u16::from(iface)) + } +} + +fn main() { + log::set_max_level(log::LevelFilter::Info); + common::init(); + + let mut args = env::args().skip(1); + const USAGE: &str = "redbear-ecmd "; + + let scheme = args.next().expect(USAGE); + let port_id = args.next().expect(USAGE) + .parse::() + .expect("Expected port ID"); + + let name = format!("{}_{}_ecm", 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"); + + // CDC ECM: find communications interface (class=02, subclass=06). + // The data interface is typically the next interface (Union descriptor + // pairing), carrying bulk IN + bulk OUT endpoints. + let mut ctrl_iface: Option = None; + let mut data_iface: Option = None; + let mut conf_val: u8 = 0; + + for conf_desc in desc.config_descs.iter() { + for ifd in conf_desc.interface_descs.iter() { + if ifd.class == USB_CLASS_COMM && ifd.sub_class == USB_CDC_SUBCLASS_ECM { + ctrl_iface = Some(ifd.number); + data_iface = Some(ifd.number + 1); + conf_val = conf_desc.configuration_value; + break; + } + } + if ctrl_iface.is_some() { break; } + } + + let ctrl_iface = ctrl_iface.expect("No CDC ECM communications interface found"); + let data_iface = data_iface.expect("No CDC ECM data interface found"); + + // Find bulk IN + bulk OUT on the data interface, and optional + // interrupt endpoint on the control interface. + let mut bulk_in_num = 0u8; + let mut bulk_out_num = 0u8; + let mut int_num = 0u8; + + for conf_desc in desc.config_descs.iter() { + for ifd in conf_desc.interface_descs.iter() { + if ifd.number == data_iface { + for ep in ifd.endpoints.iter() { + if ep.attributes & 0x03 == 0x02 { // bulk transfer type + match ep.direction() { + EndpDirection::In => bulk_in_num = ep.address & 0x0F, + EndpDirection::Out => bulk_out_num = ep.address & 0x0F, + _ => {} + } + } + } + } + if ifd.number == ctrl_iface { + for ep in ifd.endpoints.iter() { + if ep.attributes & 0x03 == 0x03 { // interrupt transfer type + int_num = ep.address & 0x0F; + } } } } } - n -} -fn main() { - log::set_logger(&StderrLogger).ok(); - log::set_max_level(LevelFilter::Info); - info!("redbear-ecmd: USB CDC ECM/NCM ethernet daemon"); - loop { let c = scan(); if c > 0 { info!("redbear-ecmd: {} usb net symlink(s)", c); } std::thread::sleep(Duration::from_secs(5)); } + + // Configure the data interface. + handle.configure_endpoints(&ConfigureEndpointsReq { + config_desc: conf_val, + interface_desc: Some(data_iface), + alternate_setting: Some(0), + hub_ports: None, + }).expect("Config data interface"); + + let mut dev = EcmDevice { + 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"), + int_ep: if int_num > 0 { + handle.open_endpoint(int_num).ok() + } else { + None + }, + _handle: handle, + }; + + // Set packet filter: promiscuous + directed + broadcast + multicast. + // Matches Linux 7.1 cdc_ether.c:70-80 default filter. + let filter = PACKET_TYPE_PROMISCUOUS | PACKET_TYPE_DIRECTED + | PACKET_TYPE_BROADCAST | PACKET_TYPE_MULTICAST; + let _ = dev.set_packet_filter(&dev._handle, ctrl_iface, filter); + + log::info!("CDC ECM ready on {} port {} — bulk_in={} bulk_out={} int={} filter={:04X}", + scheme, port_id, bulk_in_num, bulk_out_num, int_num, filter); + + // Spawn stdin→bulk OUT writer thread for Ethernet Tx. + let mut bulk_out = dev.bulk_out; + let _tx_thread = thread::spawn(move || { + let mut buf = [0u8; 2048]; + loop { + match io::stdin().read(&mut buf) { + Ok(0) => break, + Ok(n) => { let _ = bulk_out.transfer_write(&buf[..n]); } + Err(e) => { log::warn!("ECM: stdin: {}", e); break; } + } + } + }); + + // Optional: spawn notification reader for network connection / speed changes. + if let Some(mut int_ep) = dev.int_ep { + thread::spawn(move || { + let mut buf = [0u8; 16]; + loop { + match int_ep.transfer_read(&mut buf) { + Ok(status) if status.bytes_transferred >= 8 => { + let bm_request_type = buf[0]; + let b_notification = buf[1]; + let w_value = u16::from_le_bytes([buf[2], buf[3]]); + let w_index = u16::from_le_bytes([buf[4], buf[5]]); + let w_length = u16::from_le_bytes([buf[6], buf[7]]); + match b_notification { + CDC_NOTIFY_NETWORK_CONNECTION => { + let connected = w_value != 0; + log::info!("ECM: network connection: {}", if connected { "UP" } else { "DOWN" }); + } + CDC_NOTIFY_SPEED_CHANGE => { + let up: u32 = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]); + let down: u32 = u32::from_le_bytes([buf[12], buf[13], buf[14], buf[15]]); + log::info!("ECM: speed change: up={}bps down={}bps", up, down); + } + _ => { + log::debug!("ECM: notification type={:02X} request_type={:02X} value={:04X} index={:04X} len={}", + b_notification, bm_request_type, w_value, w_index, w_length); + } + } + } + Ok(_) => {} + Err(e) => log::warn!("ECM: interrupt: {}", e), + } + thread::sleep(time::Duration::from_millis(100)); + } + }); + } + + // Main loop: bulk IN → stdout (Ethernet Rx). + let mut buf = [0u8; 2048]; + 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!("ECM: read: {}", e), + } + thread::sleep(time::Duration::from_millis(1)); + } }