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

  1. Call `interface(...)` (or `interface_alt(...)`) before the first `endpoint()` call for that interface.
  2. Move `endpoint()` calls inside the interface block, before `end_interfaces()`.
  3. Verify the code runs in `get_configuration_descriptors`, not another descriptor callback.
  4. 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

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


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)