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

  1. Wrap all `interface`/`endpoint` calls inside the `configuration()` block (e.g. within your class's `get_configuration_descriptors` implementation).
  2. Ensure you implement `UsbClass::get_configuration_descriptors` rather than writing interfaces into a fresh/bare DescriptorWriter.
  3. Check call ordering: `configuration()` must run first, then one or more `interface`/`interface_alt`, then `endpoint` calls.
  4. 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

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


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,           // iInterface

View on GitHub (pinned to 463a07b963)