Rework SetupPacket - move from driver to embassy-usb-host#5882
Rework SetupPacket - move from driver to embassy-usb-host#5882leftger merged 5 commits intoembassy-rs:mainfrom
Conversation
b283907 to
582bfb9
Compare
There was a problem hiding this comment.
Pull request overview
Reworks USB host RequestType away from bitflags into a structured representation (Direction + ControlType + Recipient) and switches SetupPacket serialization to an explicit 8-byte wire encoding, removing the bitflags dependency from embassy-usb-driver.
Changes:
- Replace
RequestTypebitflags withRequestType { direction, control_type, recipient }plusto_bits/from_bitsand helper constructors. - Replace unsafe
SetupPacket::as_bytes()usage withSetupPacket::to_bytes()at call sites. - Update host stack code to construct request types via
device_to_host/host_to_deviceand removebitflagsdependency inembassy-usb-driver.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| embassy-usb-synopsys-otg/src/host.rs | Switch control transfers to pass serialized SETUP bytes via SetupPacket::to_bytes(). |
| embassy-usb-host/src/lib.rs | Update SETUP parsing to use new RequestType::from_bits. |
| embassy-usb-host/src/control.rs | Update control request construction to new RequestType API with ControlType/Recipient. |
| embassy-usb-host/src/class/uac/mod.rs | Update UAC class control transfers to new RequestType API. |
| embassy-usb-host/src/class/hub.rs | Update hub class control transfers to new RequestType API. |
| embassy-usb-driver/src/host.rs | Implement new Recipient, ControlType, RequestType, and SetupPacket::to_bytes() wire serialization. |
| embassy-usb-driver/Cargo.toml | Remove bitflags dependency from embassy-usb-driver. |
| embassy-stm32/src/usb/usb_host.rs | Switch STM32 control SETUP stage writes to SetupPacket::to_bytes(). |
| embassy-rp/src/usb/host.rs | Update RP host setup write to use RequestType::to_bits() instead of bits(). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Recipient of a USB control request. | ||
| /// | ||
| /// This is the 5-bit Recipient sub-field of `bmRequestType` | ||
| /// (USB 2.0 spec Table 9-2, bits 4..0). The discriminant of each variant | ||
| /// matches the on-wire value. | ||
| #[repr(u8)] | ||
| #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
| #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| pub enum Recipient { | ||
| /// The request is intended for the entire device. | ||
| Device = 0, | ||
| /// The request is intended for an interface. | ||
| Interface = 1, | ||
| /// The request is intended for an endpoint. | ||
| Endpoint = 2, | ||
| /// The recipient of the request is unspecified. | ||
| Other = 3, | ||
| /// Any reserved recipient value (4..=31). | ||
| Reserved = 4, | ||
| } |
There was a problem hiding this comment.
Recipient docs say each variant’s discriminant matches the on-wire bmRequestType recipient value, but Recipient::Reserved = 4 is used to represent any reserved recipient value (4..=31). from_bits() collapses all those values into Reserved, and to_bits() will re-encode them as 0b00100, so round-tripping is lossy and the discriminant claim is not accurate. Consider either (a) adjusting the docs to explicitly call out this lossiness / that Reserved encodes as 4, or (b) representing recipient as a raw 5-bit newtype (with constants for the standard recipients) so reserved values can be preserved.
There was a problem hiding this comment.
IDK if we should add 20-some reserved variants just for this
| pub fn as_bytes(&self) -> &[u8] { | ||
| // Safe because we know that the size of SetupPacket is 8 bytes. | ||
| unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, core::mem::size_of::<Self>()) } | ||
| /// Serialize this SETUP packet to its 8-byte wire format. |
There was a problem hiding this comment.
do you think it's possible to move SetupPacket out of embassy-usb-driver? ie make the driver work with just bytes.
The usb-host crate can have the nicer public api with parsing/unparsing.
Less api surface means less potential for breaking changes.
There was a problem hiding this comment.
I guess it's a bit weird that driver implementations need to bit-fiddle with the setup packet bytes, though 😅
There was a problem hiding this comment.
What if we did something in the middle? Keep SetupPacket in the driver, but let the trait method take &[u8; 8]? That way implementations could use SetupPacket to wrap/parse what they need out of the bytes, but it would be a utility instead of an integral part of the API.
There was a problem hiding this comment.
The SetupPacket would still be part of the embassy-usb-driver's API surface even as a utility right? The breakage potential is still the same I think.
I do wonder if a embassy-usb-util/embassy-usb-common library that both embassy-usb and embassy-usb-host could use might make sense here. A whole library for just one struct feels silly though 😅 but it would solve the API surface problem and the bit-fiddling problem.
There was a problem hiding this comment.
It's not like setup packets will suddenly change anyway, but it'd be possible to add v2, v3, ... versions of utilities until a breaking release could clear out the accumulated garbage. In the end it's not a big deal.
0e9ec5a to
b605f59
Compare
| // Now get the full string with the correct length | ||
| let packet = SetupPacket { | ||
| request_type: RequestType::IN | RequestType::TYPE_STANDARD | RequestType::RECIPIENT_DEVICE, | ||
| request_type: RequestType::device_to_host(ControlType::Standard, Recipient::Device), |
There was a problem hiding this comment.
It's a bit odd to see device_to_host and also Recipient::Device in the same line. Makes me wonder who the real recipient is. Though I'm not sure how to improve this since in is a keyword.
There was a problem hiding this comment.
TBH I find the names better than "request_in/request_out", although from a host perspective in/out is less bad than from a device's perspective :) I can rename these into RequestType::r#in/out if that's better (or request_in/request_out though a bit redundant), but I'm not sure if it is better.
Maybe control_in/control_out could work?
There was a problem hiding this comment.
Oh actually, what about RequestType::new(Direction::In, ControlType::Standard, Recipient::Device) ?
There was a problem hiding this comment.
What if we drop the constructor completely and construct the struct directly? It's a bit wordy, but there's no confusion about parameter positions.
1916cff to
7a2a5b6
Compare
d07b01b to
6923845
Compare
|
bender run |
RequestType was implemented using
bitflagswhich is not the right tool for the job for defining bitfields. This PR removes the dependency and hand-implements the type with proper one-of-many semantics on RequestType fields.