pub trait ControlPipe {
    // Required methods
    fn max_packet_size(&self) -> usize;
    async fn setup(&mut self) -> [u8; 8];
    async fn data_out(
        &mut self,
        buf: &mut [u8],
        first: bool,
        last: bool
    ) -> Result<usize, EndpointError>;
    async fn data_in(
        &mut self,
        data: &[u8],
        first: bool,
        last: bool
    ) -> Result<(), EndpointError>;
    async fn accept(&mut self);
    async fn reject(&mut self);
    async fn accept_set_address(&mut self, addr: u8);
}
Expand description

USB control pipe trait.

The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single unit, and manages them together to implement the control pipe state machine.

The reason this is a separate trait instead of using EndpointOut/EndpointIn is that many USB peripherals treat the control pipe endpoints differently (different registers, different procedures), usually to accelerate processing in hardware somehow. A separate trait allows the driver to handle it specially.

The call sequences made by the USB stack to the ControlPipe are the following:

  • control in/out with len=0:
setup()
(...processing...)
accept() or reject()
  • control out for setting the device address:
setup()
(...processing...)
accept_set_address(addr) or reject()
  • control out with len != 0:
setup()
data_out(first=true, last=false)
data_out(first=false, last=false)
...
data_out(first=false, last=false)
data_out(first=false, last=true)
(...processing...)
accept() or reject()
  • control in with len != 0, accepted:
setup()
(...processing...)
data_in(first=true, last=false)
data_in(first=false, last=false)
...
data_in(first=false, last=false)
data_in(first=false, last=true)
(NO `accept()`!!! This is because calling `data_in` already implies acceptance.)
  • control in with len != 0, rejected:
setup()
(...processing...)
reject()

The driver is responsible for handling the status stage. The stack DOES NOT do zero-length calls to data_in or data_out for the status zero-length packet. The status stage should be triggered by either accept(), or data_in with last = true.

Note that the host can abandon a control request and send a new SETUP packet any time. If a SETUP packet arrives at any time during data_out, data_in, accept or reject, the driver must immediately return (with EndpointError::Disabled from data_in, data_out) to let the stack call setup() again to start handling the new control request. Not doing so will cause things to get stuck, because the host will never read/send the packet we’re waiting for.

Required Methods§

source

fn max_packet_size(&self) -> usize

Maximum packet size for the control pipe

source

async fn setup(&mut self) -> [u8; 8]

Read a single setup packet from the endpoint.

source

async fn data_out( &mut self, buf: &mut [u8], first: bool, last: bool ) -> Result<usize, EndpointError>

Read a DATA OUT packet into buf in response to a control write request.

Must be called after setup() for requests with direction of Out and length greater than zero.

source

async fn data_in( &mut self, data: &[u8], first: bool, last: bool ) -> Result<(), EndpointError>

Send a DATA IN packet with data in response to a control read request.

If last_packet is true, the STATUS packet will be ACKed following the transfer of data.

source

async fn accept(&mut self)

Accept a control request.

Causes the STATUS packet for the current request to be ACKed.

source

async fn reject(&mut self)

Reject a control request.

Sets a STALL condition on the pipe to indicate an error.

source

async fn accept_set_address(&mut self, addr: u8)

Accept SET_ADDRESS control and change bus address.

For most drivers this function should firstly call accept() and then change the bus address. However, there are peripherals (Synopsys USB OTG) that have reverse order.

Object Safety§

This trait is not object safe.

Implementors§