Control transfers
transfer runs one USB control transfer on the real device behind the host chip and returns the reply as a TransferOutcome.
It crosses the inter-chip link on dedicated messages, one at a time, bypassing every rewrite rule. It waits in the host chip's control queue beside the game PC's requests.
real device the box (host chip | device chip = the clone) game PC
HID report ---IN---> [ HID_IN ]--> renderer --> [ EMIT ]---interrupt-IN----> reads report
relayed <--OUT--- [ HID_OUT ]<-- relay <--------------- interrupt-OUT <-- writes report
control <-- EP0 -> [ CONTROL ]<-- proxy ------------------- EP0 <--------> class, vendor requests
^
+-- transfer(ep, setup) <== dedicated messages, past every ruleThe advanced control layer needs allow_imperfect_clones. With it off, the box replies Refused and never reaches the real device.
transferOne control transfer, blocking on the reply
fn transfer(&self, ep: u8, setup: Setup, out: &[u8]) -> Result<TransferOutcome>
fn transfer_timeout(&self, ep: u8, setup: Setup, out: &[u8], timeout: Duration) -> Result<TransferOutcome>
Blocks
| Parameter | Type | Description |
|---|---|---|
ep | u8 | 0 for EP0, or a control endpoint number the real device declares. |
setup | Setup | Eight-byte setup packet. |
out | &[u8] | OUT data stage, sent after the setup packet. Empty for an IN transfer. |
Ok(_) means the box replied. A TransferStatus other than Ok comes back inside the TransferOutcome, not as an error.
transfer uses DEFAULT_TRANSFER_TIMEOUT (1.5 s). The box gives up after ~800 ms. Keep transfer_timeout ≥800 ms.
A SET_CONFIGURATION or SET_INTERFACE sent here changes only the real device. The clone and the endpoints the host chip polls stay where the game PC set them.
use medius::{Device, Setup, TransferStatus};
let device = Device::find()?;
device.allow_imperfect_clones(true)?;
let reply = device.transfer(0, Setup::new(0x80, 0x06, 0x0100, 0x0000, 18), &[])?;
if reply.status == TransferStatus::Ok {
println!("device descriptor: {:02x?}", reply.data());
}On AsyncDevice transfer awaits the native reply
On AsyncDevice, transfer and transfer_timeout are futures, awaited like any query.
use futures::executor::block_on;
use medius::{AsyncDevice, Setup, TransferStatus};
let device = AsyncDevice::open("/dev/ttyACM0")?;
device.allow_imperfect_clones(true)?;
let reply = block_on(device.transfer(0, Setup::new(0x80, 0x06, 0x0100, 0x0000, 18), &[]))?;
if reply.status == TransferStatus::Ok {
println!("device descriptor: {:02x?}", reply.data());
}