embassy-rs/embassy · error
you can only call `endpoint` after…
Error message
you can only call `endpoint` after `interface/interface_alt`.
What it means
The DescriptorWriter tracks the endpoint count of the current interface via a mark set by `interface()`/`interface_alt()`. Calling `endpoint()` when no interface is active leaves the bNumEndpoints counter location unknown, so the writer panics to prevent emitting an invalid USB interface descriptor.
Solutions
- Call `interface(...)` (or `interface_alt(...)`) before the first `endpoint()` call for that interface.
- Move `endpoint()` calls inside the interface block, before `end_interfaces()`.
- Verify the code runs in `get_configuration_descriptors`, not another descriptor callback.
- Keep endpoint declarations paired 1:1 with interface blocks in custom UsbClass implementations.
Example fix
// before
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) {
w.endpoint(&ep_cfg)?; // no interface open
}
// after
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) {
w.configuration(1)?;
w.interface(0, 0, 1, 0xFF, 0, 0, None)?;
w.endpoint(&ep_cfg)?;
w.end_interfaces();
w.end_configuration();
Ok(())
} Defensive patterns
Strategy: validation
Validate before calling
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) -> Result<(), DescriptorError> {
w.configuration(1)?;
w.interface(0, 0, 1, 0xFF, 0, 0, None)?; // must precede any endpoint() call
w.endpoint(&EP_IN_DESC)?;
w.end_interfaces();
w.end_configuration();
Ok(())
} Prevention
- Never call endpoint() before interface()/interface_alt() on the same writer
- Count endpoints declared per interface and keep it <= 16 (bNumEndpoints is a nibble)
- Emit endpoints only between interface() and end_interfaces()
- Mirror the ordering used in embassy-usb example classes
When it happens
Trigger: Calling `DescriptorWriter::endpoint()` before any `interface()`/`interface_alt()` call on the writer, or after the interface was closed with `end_interfaces()`.
Common situations: Writing endpoints inside `get_string_descriptors` or device-descriptor paths instead of the configuration path; forgetting to open an interface before registering endpoints in a custom class; calling `endpoint` after ending the interface.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- you can only call `interface/interface_alt` after…
- called `capability` not between `bos` and `end_bos`.
- AHB frequency is too low
- USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but…
- USB clock should be 48Mhz but is
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/318d61e88e79d643.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-usb/src/descriptor.rs:262
/// Writes an endpoint descriptor.
///
/// # Arguments
///
/// * `endpoint` - Endpoint previously allocated with
/// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder).
/// * `synchronization_type` - The synchronization type of the endpoint.
/// * `usage_type` - The usage type of the endpoint.
/// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor.
pub fn endpoint(
&mut self,
endpoint: &EndpointInfo,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) {
match self.num_endpoints_mark {
Some(mark) => self.buf[mark] += 1,
None => panic!("you can only call `endpoint` after `interface/interface_alt`."),
};
let mut bm_attributes = endpoint.ep_type as u8;
// Synchronization types other than `NoSynchronization`,
// and usage types other than `DataEndpoint`
// are only allowed for isochronous endpoints.
if endpoint.ep_type != EndpointType::Isochronous {
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization);
assert_eq!(usage_type, UsageType::DataEndpoint);
} else {
if usage_type == UsageType::FeedbackEndpoint {
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization)
}
let synchronization_bm_attributes: u8 = (synchronization_type as u8) << 2;
let usage_bm_attributes: u8 = (usage_type as u8) << 4;
View on GitHub (pinned to 463a07b963)