Files
RedBear-OS/local/recipes/system/redbear-ecmd/source/src/main.rs
T
vasilito aa2185152a docs: comprehensive driver infrastructure assessment and cleanup
IMPLEMENTATION-MASTER-PLAN.md:
  - Updated Phase 2 Network Drivers to COMPLETE status
  - Added NETWORKING-IMPROVEMENT-PLAN.md to authority table
  - Comprehensive driver inventory (5 Ethernet, 7 storage, 4 USB, 4 GPU, etc.)
  - Linux 7.1 cross-references for every driver
  - Network stack completion summary (9,212 LoC, all protocols)
  - Updated e1000d/rtl8168d file statuses (was 'Truncated', now 'Complete')
  - Remaining smoltcp-dependent gaps documented

Archived stale docs:
  - C7-STATUS.md → archived/ (KF6/Plasma migration, completed)
  - BUILD-TOOLS-PORTING-PLAN.md → archived/ (superseded)
  - 0.2.5-GRAPHICS-FREEZE-PLAN.md → archived/ (version-specific)
2026-07-07 23:46:41 +03:00

262 lines
10 KiB
Rust

//! 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.
//! On Redox: registers /scheme/net/usbECM_<N> for netstack integration.
#[cfg(target_os = "redox")]
mod scheme;
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<XhciEndpHandle>,
}
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 <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!("{}_{}_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<u8> = None;
let mut data_iface: Option<u8> = 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;
}
}
}
}
}
// 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);
// ── Scheme IPC path (Redox) ──
#[cfg(target_os = "redox")]
{
use redox_scheme::scheme::{self, SchemeSync};
use redox_scheme::{RequestKind, Socket, SignalBehavior};
let scheme_name = format!("net/usbECM_{}", port_id);
log::info!("ECM: registering /scheme/{}", scheme_name);
let socket = Socket::create().expect("ECM: scheme socket");
let mut scheme_state = redox_scheme::scheme::SchemeState::new();
let mut ecm_scheme = crate::scheme::EcmScheme::new(dev.bulk_in, dev.bulk_out);
scheme::register_sync_scheme(&socket, &scheme_name, &mut ecm_scheme).expect("ECM: scheme register");
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(s) if s.bytes_transferred >= 8 => {
let b = buf[1];
match b {
CDC_NOTIFY_NETWORK_CONNECTION => log::info!("ECM: link {}", if u16::from_le_bytes([buf[2],buf[3]]) != 0 { "UP" } else { "DOWN" }),
CDC_NOTIFY_SPEED_CHANGE => log::info!("ECM: speed up={} down={}", u32::from_le_bytes([buf[8],buf[9],buf[10],buf[11]]), u32::from_le_bytes([buf[12],buf[13],buf[14],buf[15]])),
_ => log::debug!("ECM: notify type={:02X}", b),
}
}
Ok(_) => {}
Err(e) => log::warn!("ECM: int: {}", e),
}
thread::sleep(time::Duration::from_millis(100));
}
});
}
loop {
let request = match socket.next_request(SignalBehavior::Restart) {
Ok(Some(req)) => req,
Ok(None) => { log::info!("ECM: scheme socket closed"); break; }
Err(err) => { log::error!("ECM: scheme request error: {}", err); break; }
};
match request.kind() {
RequestKind::Call(call) => {
let response = call.handle_sync(&mut ecm_scheme, &mut scheme_state);
if let Err(err) = socket.write_response(response, SignalBehavior::Restart) {
log::error!("ECM: write response error: {}", err);
break;
}
}
_ => {}
}
}
}
// ── Stdout path (host/Linux testing) ──
#[cfg(not(target_os = "redox"))]
{
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; }
}
}
});
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(s) if s.bytes_transferred >= 8 => {
match buf[1] {
CDC_NOTIFY_NETWORK_CONNECTION => log::info!("ECM: link {}", if u16::from_le_bytes([buf[2],buf[3]]) != 0 { "UP" } else { "DOWN" }),
CDC_NOTIFY_SPEED_CHANGE => log::info!("ECM: speed up={} down={}", u32::from_le_bytes([buf[8],buf[9],buf[10],buf[11]]), u32::from_le_bytes([buf[12],buf[13],buf[14],buf[15]])),
_ => {}
}
}
Ok(_) => {} Err(e) => log::warn!("ECM: int: {}", e),
}
thread::sleep(time::Duration::from_millis(100));
}
});
}
let mut buf = [0u8; 2048];
loop {
match dev.bulk_in.transfer_read(&mut buf) {
Ok(status) if status.bytes_transferred > 0 => { let _ = io::stdout().write_all(&buf[..status.bytes_transferred as usize]); let _ = io::stdout().flush(); }
Ok(_) => {} Err(e) => log::warn!("ECM: read: {}", e),
}
thread::sleep(time::Duration::from_millis(1));
}
}
}