aa2185152a
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)
262 lines
10 KiB
Rust
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));
|
|
}
|
|
}
|
|
}
|