fix: passthrough descriptors, SET_INTERFACE forwarding, and error mapping
- Forward Configuration, BOS, and DeviceQualifier descriptors to the real device in passthrough mode, preserving IADs, class-specific descriptors (audio, HID, DFU), and endpoint companions - Forward SET_INTERFACE to the physical device so alt settings are actually applied (critical for audio streaming) - Round up interrupt IN buffers to max_packet_size (matching bulk IN) - Map USB errors to proper Linux URB status codes: STALL→EPIPE(-32), cancelled→ENOENT(-2), timeout→ETIMEDOUT(-110) - Propagate UrbResponse.status instead of hardcoding 0 Fixes USB Audio Class 2 device detection (kernel error "Audio class v2/v3 interfaces need an interface association") and device reset loops caused by EPROTO being returned for benign STALL/cancel errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
02c3017679
commit
85146a12fe
3 changed files with 84 additions and 11 deletions
|
|
@ -402,6 +402,17 @@ impl UsbDevice {
|
||||||
}
|
}
|
||||||
Some(BOS) => {
|
Some(BOS) => {
|
||||||
debug!("Get BOS descriptor");
|
debug!("Get BOS descriptor");
|
||||||
|
if self.device_handler.is_some() {
|
||||||
|
let lock = self.device_handler.as_ref().unwrap();
|
||||||
|
let mut handler = lock.lock().unwrap();
|
||||||
|
return handler.handle_urb(UrbRequest {
|
||||||
|
ep,
|
||||||
|
transfer_buffer_length,
|
||||||
|
setup: setup_packet,
|
||||||
|
data: out_data,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
let mut desc = vec![
|
let mut desc = vec![
|
||||||
0x05, // bLength
|
0x05, // bLength
|
||||||
BOS as u8, // bDescriptorType: BOS
|
BOS as u8, // bDescriptorType: BOS
|
||||||
|
|
@ -417,6 +428,19 @@ impl UsbDevice {
|
||||||
}
|
}
|
||||||
Some(Configuration) => {
|
Some(Configuration) => {
|
||||||
debug!("Get configuration descriptor");
|
debug!("Get configuration descriptor");
|
||||||
|
// In passthrough mode, forward to the real device so that
|
||||||
|
// IADs, class-specific descriptors, etc. are preserved.
|
||||||
|
if self.device_handler.is_some() {
|
||||||
|
let lock = self.device_handler.as_ref().unwrap();
|
||||||
|
let mut handler = lock.lock().unwrap();
|
||||||
|
return handler.handle_urb(UrbRequest {
|
||||||
|
ep,
|
||||||
|
transfer_buffer_length,
|
||||||
|
setup: setup_packet,
|
||||||
|
data: out_data,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
// Standard Configuration Descriptor
|
// Standard Configuration Descriptor
|
||||||
let mut desc = vec![
|
let mut desc = vec![
|
||||||
0x09, // bLength
|
0x09, // bLength
|
||||||
|
|
@ -528,6 +552,17 @@ impl UsbDevice {
|
||||||
}
|
}
|
||||||
Some(DeviceQualifier) => {
|
Some(DeviceQualifier) => {
|
||||||
debug!("Get device qualifier descriptor");
|
debug!("Get device qualifier descriptor");
|
||||||
|
if self.device_handler.is_some() {
|
||||||
|
let lock = self.device_handler.as_ref().unwrap();
|
||||||
|
let mut handler = lock.lock().unwrap();
|
||||||
|
return handler.handle_urb(UrbRequest {
|
||||||
|
ep,
|
||||||
|
transfer_buffer_length,
|
||||||
|
setup: setup_packet,
|
||||||
|
data: out_data,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
// Device_Qualifier Descriptor
|
// Device_Qualifier Descriptor
|
||||||
let bcd_usb_qual = self.usb_version.to_bcd_be();
|
let bcd_usb_qual = self.usb_version.to_bcd_be();
|
||||||
let mut desc = vec![
|
let mut desc = vec![
|
||||||
|
|
@ -642,6 +677,17 @@ impl UsbDevice {
|
||||||
(0b00000001, Some(SetInterface)) => {
|
(0b00000001, Some(SetInterface)) => {
|
||||||
let intf_index = setup_packet.index as usize & 0xFF;
|
let intf_index = setup_packet.index as usize & 0xFF;
|
||||||
let alt = setup_packet.value as u8;
|
let alt = setup_packet.value as u8;
|
||||||
|
// Forward to physical device handler if present
|
||||||
|
if let Some(ref handler_lock) = self.device_handler {
|
||||||
|
let mut handler = handler_lock.lock().unwrap();
|
||||||
|
handler.handle_urb(UrbRequest {
|
||||||
|
ep,
|
||||||
|
transfer_buffer_length,
|
||||||
|
setup: setup_packet.clone(),
|
||||||
|
data: out_data.clone(),
|
||||||
|
..Default::default()
|
||||||
|
})?;
|
||||||
|
}
|
||||||
match self.interface_states.get(intf_index) {
|
match self.interface_states.get(intf_index) {
|
||||||
Some(state) => {
|
Some(state) => {
|
||||||
let mut inner = state.inner.write().await;
|
let mut inner = state.inner.write().await;
|
||||||
|
|
|
||||||
|
|
@ -305,13 +305,15 @@ impl UsbInterfaceHandler for NusbUsbHostInterfaceHandler {
|
||||||
} else if ep.attributes == EndpointAttributes::Interrupt as u8 {
|
} else if ep.attributes == EndpointAttributes::Interrupt as u8 {
|
||||||
// interrupt
|
// interrupt
|
||||||
if let Direction::In = ep.direction() {
|
if let Direction::In = ep.direction() {
|
||||||
// interrupt in
|
// interrupt in - round up to max_packet_size as required by USB spec
|
||||||
|
let mps = ep.max_packet_size.max(1) as usize;
|
||||||
|
let alloc_len = ((transfer_buffer_length as usize) + mps - 1) / mps * mps;
|
||||||
let mut endpoint = handle
|
let mut endpoint = handle
|
||||||
.endpoint::<nusb::transfer::Interrupt, nusb::transfer::In>(ep.address)
|
.endpoint::<nusb::transfer::Interrupt, nusb::transfer::In>(ep.address)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
std::io::Error::other(format!("Failed to open interrupt endpoint: {}", e))
|
std::io::Error::other(format!("Failed to open interrupt endpoint: {}", e))
|
||||||
})?;
|
})?;
|
||||||
let buffer = endpoint.allocate(transfer_buffer_length as usize);
|
let buffer = endpoint.allocate(alloc_len);
|
||||||
let completion = endpoint.transfer_blocking(buffer, timeout);
|
let completion = endpoint.transfer_blocking(buffer, timeout);
|
||||||
completion.status.map_err(|e| {
|
completion.status.map_err(|e| {
|
||||||
std::io::Error::new(std::io::ErrorKind::Other, format!("USB interrupt IN failed: {e}"))
|
std::io::Error::new(std::io::ErrorKind::Other, format!("USB interrupt IN failed: {e}"))
|
||||||
|
|
|
||||||
|
|
@ -540,6 +540,7 @@ pub async fn handle_urb_loop<T: AsyncReadExt + AsyncWriteExt + Unpin + Send + 's
|
||||||
|
|
||||||
match resp {
|
match resp {
|
||||||
Ok(urb_resp) => {
|
Ok(urb_resp) => {
|
||||||
|
let status = urb_resp.status as u32;
|
||||||
if out {
|
if out {
|
||||||
trace!("<-Wrote {transfer_buffer_length}");
|
trace!("<-Wrote {transfer_buffer_length}");
|
||||||
// For OUT transfers, actual_length reflects
|
// For OUT transfers, actual_length reflects
|
||||||
|
|
@ -547,7 +548,7 @@ pub async fn handle_urb_loop<T: AsyncReadExt + AsyncWriteExt + Unpin + Send + 's
|
||||||
// is not sent back on the wire.
|
// is not sent back on the wire.
|
||||||
UsbIpResponse::UsbIpRetSubmit {
|
UsbIpResponse::UsbIpRetSubmit {
|
||||||
header: header.clone(),
|
header: header.clone(),
|
||||||
status: 0,
|
status,
|
||||||
actual_length: transfer_buffer_length,
|
actual_length: transfer_buffer_length,
|
||||||
start_frame: urb_resp.start_frame,
|
start_frame: urb_resp.start_frame,
|
||||||
number_of_packets: urb_resp.iso_packet_descriptors.len() as u32,
|
number_of_packets: urb_resp.iso_packet_descriptors.len() as u32,
|
||||||
|
|
@ -557,17 +558,41 @@ pub async fn handle_urb_loop<T: AsyncReadExt + AsyncWriteExt + Unpin + Send + 's
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
trace!("<-Resp len={}", urb_resp.data.len());
|
trace!("<-Resp len={}", urb_resp.data.len());
|
||||||
UsbIpResponse::usbip_ret_submit_success(
|
UsbIpResponse::UsbIpRetSubmit {
|
||||||
&header,
|
header: header.clone(),
|
||||||
urb_resp.start_frame,
|
status,
|
||||||
urb_resp.data,
|
actual_length: urb_resp.data.len() as u32,
|
||||||
urb_resp.iso_packet_descriptors,
|
start_frame: urb_resp.start_frame,
|
||||||
)
|
number_of_packets: urb_resp.iso_packet_descriptors.len() as u32,
|
||||||
|
error_count: urb_resp.error_count,
|
||||||
|
transfer_buffer: urb_resp.data,
|
||||||
|
iso_packet_descriptors: urb_resp.iso_packet_descriptors,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Error handling URB seqnum={seqnum}: {err}");
|
let msg = err.to_string();
|
||||||
UsbIpResponse::usbip_ret_submit_fail(&header)
|
// Map USB errors to appropriate Linux URB status codes
|
||||||
|
let status = if msg.contains("stall") {
|
||||||
|
(-32i32) as u32 // EPIPE: endpoint stalled
|
||||||
|
} else if msg.contains("cancel") {
|
||||||
|
(-2i32) as u32 // ENOENT: URB was unlinked/cancelled
|
||||||
|
} else if msg.contains("timed out") || msg.contains("TimedOut") {
|
||||||
|
(-110i32) as u32 // ETIMEDOUT
|
||||||
|
} else {
|
||||||
|
(-71i32) as u32 // EPROTO: protocol error
|
||||||
|
};
|
||||||
|
warn!("Error handling URB seqnum={seqnum}: {err} (status={status})", status = status as i32);
|
||||||
|
UsbIpResponse::UsbIpRetSubmit {
|
||||||
|
header: header.clone(),
|
||||||
|
status,
|
||||||
|
actual_length: 0,
|
||||||
|
start_frame: 0,
|
||||||
|
number_of_packets: 0,
|
||||||
|
error_count: 0,
|
||||||
|
transfer_buffer: vec![],
|
||||||
|
iso_packet_descriptors: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue