embassy-rs/embassy · error
you can only call `interface/interface_alt` after…
Error message
you can only call `interface/interface_alt` after `configuration`.
What it means
embassy-usb's DescriptorWriter tracks the count of interfaces in the current USB configuration via a mark placed by `configuration()`. Calling `interface()` or `interface_alt()` before any configuration is active means there is no bNumInterfaces counter to increment, which is an invalid descriptor-building sequence, so the writer panics instead of emitting a corrupt descriptor.
Solutions
- Wrap all `interface`/`endpoint` calls inside the `configuration()` block (e.g. within your class's `get_configuration_descriptors` implementation).
- Ensure you implement `UsbClass::get_configuration_descriptors` rather than writing interfaces into a fresh/bare DescriptorWriter.
- Check call ordering: `configuration()` must run first, then one or more `interface`/`interface_alt`, then `endpoint` calls.
- Update to the latest embassy-usb version in case the API shape changed and your code targets an older pattern.
Example fix
// before
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) {
w.interface(0, 0, 2, 0xFF, 0, 0, None);
}
// after
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) {
w.configuration(1)?; // activate the configuration first
w.interface(0, 0, 2, 0xFF, 0, 0, None)?;
w.endpoint(...)?;
w.end_interfaces();
w.end_configuration();
Ok(())
} Defensive patterns
Strategy: validation
Validate before calling
// Only write interface descriptors inside the configuration callback:
fn get_configuration_descriptors(&self, w: &mut DescriptorWriter) -> Result<(), DescriptorError> {
w.configuration(1)?; // must precede any interface() call
w.interface(0, 0, 2, 0xFF, 0, 0, None)?;
w.end_interfaces();
w.end_configuration();
Ok(())
} Prevention
- Always open a configuration with w.configuration() before any interface/interface_alt call
- Only emit interface/endpoint descriptors inside get_configuration_descriptors
- Keep a strict ordering: configuration -> interface -> endpoint(s) -> end_interfaces -> end_configuration
- Unit-test custom UsbClass descriptor building with an in-memory writer
When it happens
Trigger: Calling `DescriptorWriter::interface()` or `interface_alt()` outside the closure passed to `UsbDeviceBuilder`/`UsbDevice`'s descriptor-building path, i.e. without a preceding `configuration()` call on the same writer.
Common situations: Hand-writing descriptor code in a custom `UsbClass`/handler and forgetting the `config.configuration(...)` wrapper; reordering calls so interfaces are written before the configuration descriptor; copying example code but moving `interface()` calls outside the configuration block.
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 `endpoint` 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/2972b19057e6a5f9.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-usb/src/descriptor.rs:220
/// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices
/// that do not conform to any class.
/// * `interface_sub_class` - Sub-class code. Depends on class.
/// * `interface_protocol` - Protocol code. Depends on class and sub-class.
/// * `interface_string` - Index of string descriptor describing this interface
pub fn interface_alt(
&mut self,
number: InterfaceNumber,
alternate_setting: u8,
interface_class: u8,
interface_sub_class: u8,
interface_protocol: u8,
interface_string: Option<StringIndex>,
) {
if alternate_setting == 0 {
match self.num_interfaces_mark {
Some(mark) => self.buf[mark] += 1,
None => {
panic!("you can only call `interface/interface_alt` after `configuration`.")
}
};
}
let str_index = interface_string.map_or(0, Into::into);
self.num_endpoints_mark = Some(self.position + 4);
self.write(
descriptor_type::INTERFACE,
&[
number.into(), // bInterfaceNumber
alternate_setting, // bAlternateSetting
0, // bNumEndpoints
interface_class, // bInterfaceClass
interface_sub_class, // bInterfaceSubClass
interface_protocol, // bInterfaceProtocol
str_index, // iInterfaceView on GitHub (pinned to 463a07b963)