embassy-rs/embassy · error

the hub change must be taken before a port change is taken

Error message

the hub change must be taken before a port change is taken

What it means

In the USB hub class, hub-level and port-level change bits live in one bitmap; the hub change bit (idx 0, bit 0) must be cleared (taken) before any port change bit is taken. take_port_change panics if it encounters bit 0 first, enforcing the driver's documented retrieval order.

Solutions

  1. Always take the hub change (via the hub-level change accessor) before calling take_port_change.
  2. Restructure the interrupt/status handler to process hub events first, then port events.
  3. Clear/re-read the full status bitmap in one pass so the hub bit is consumed in order.

Example fix

// before
let port_change = hub.take_port_change(port).await; // hub bit still set
// after
let hub_change = hub.take_hub_change().await;
if hub_change { hub.handle_hub_event().await; }
let port_change = hub.take_port_change(port).await;
Defensive patterns

Strategy: validation

Validate before calling

// Enforce ordering before reading port changes:
// only call take_port_change after take_hub_change has consumed the hub bit.
let hub_change = hub.take_hub_change().await;
if hub_change {
    hub.process_hub_status().await;
}
let port_change = hub.take_port_change(port).await;

Prevention

When it happens

Trigger: Calling take_port_change when the hub-level change bit is still set (i.e. hub change was never taken via the hub-level API first).

Common situations: Handling port interrupts before reading the hub status; a driver rewrite that polls port changes directly; resuming after reset where the hub change bit was re-set but the handler only queries ports.

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/bc2a129237035ac4. Report an issue: GitHub.

Appendix: source

Thrown at embassy-usb-host/src/class/hub.rs:444

                hub_change = true;
            }
        }
        hub_change
    }
    /// Returns the 0-based port number of the first port that has a status change, consuming it.
    /// Returns `None` if no port has a status change.
    ///
    /// ### Panic
    /// Panics if the hub status change has not been taken.
    fn take_port_change(&mut self) -> Option<u8> {
        self.0
            .iter_mut()
            .enumerate()
            .find(|(_, v)| v.trailing_zeros() < u8::BITS)
            .map(|(idx, v)| {
                let bit = v.trailing_zeros() as usize;
                if idx == 0 && bit == 0 {
                    panic!("the hub change must be taken before a port change is taken");
                }
                *v &= !(1 << bit);
                // The first port is in idx 0 bit 1.
                // This code starts port numbers at 0, so it needs a `- 1`.
                // On the other hand, the usb spec starts port numbers at 1,
                // so this number will be increased by 1 for `SetupPacket`.
                (bit + idx * 8 - 1) as u8
            })
    }
}

/// USB Hub class descriptor (USB 2.0 §11.23.2.1)
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HubDescriptor {
    /// Number of ports.
    pub port_num: u8,
    /// Characteristics bitmap.

View on GitHub (pinned to 463a07b963)