embassy-rs/embassy · error

Ethernet PTP clock already started

Error message

Ethernet PTP clock already started

What it means

start_ptp() guards on the ptp_clock_taken flag: the Ethernet peripheral exposes a single PTP clock, and this instance already handed out a PtpClock from a previous call. The panic prevents two PtpClock handles from racing over the same hardware timestamping block; the faulting input is a duplicate start_ptp() call on the same Ethernet object.

Solutions

  1. Call start_ptp only once; keep and reuse the returned PtpClock.
  2. Drop the existing PtpClock before calling start_ptp again.
  3. Guard PTP startup behind a OnceCell/init flag in application code.

Example fix

// before
// each reconnect:
let ptp = eth.start_ptp(cfg);
// after
static PTP: OnceCell<PtpClock<...>> = OnceCell::new();
PTP.get_or_init(|| eth.start_ptp(cfg));
Defensive patterns

Strategy: validation

Validate before calling

static PTP_TAKEN: AtomicBool = AtomicBool::new(false);
fn start_ptp_once(eth: &mut Ethernet) -> PtpClock<...> {
    if PTP_TAKEN.load(Ordering::SeqCst) { panic!("start_ptp must only be called once"); }
    PTP_TAKEN.store(true, Ordering::SeqCst);
    eth.start_ptp(ptp_cfg)
}

Prevention

When it happens

Trigger: Calling start_ptp() twice on the same v2 Ethernet instance, or restarting PTP while the previous PtpClock has not been dropped.

Common situations: Network re-init on reconnect; two tasks both calling start_ptp; duplicated init sequences in different code paths.

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

Appendix: source

Thrown at embassy-stm32/src/eth/v2/mod.rs:582

        #[cfg(eth_v2)]
        {
            interrupt::ETH.unpend();
            unsafe { interrupt::ETH.enable() };
        }
        #[cfg(any(eth_v2a, eth_v2b))]
        {
            interrupt::ETH1.unpend();
            unsafe { interrupt::ETH1.enable() };
        }

        this
    }

    /// Start the Ethernet MAC PTP clock.
    #[cfg(feature = "ptp")]
    pub fn start_ptp(&mut self, config: PtpClockConfig) -> PtpClock<T> {
        if self.ptp_clock_taken {
            panic!("Ethernet PTP clock already started");
        }

        let clock = PtpClock::start(config);
        self.ptp_clock_taken = true;
        clock
    }
}

impl<'d, T: Instance, P: Phy> Drop for Ethernet<'d, T, P> {
    fn drop(&mut self) {
        let dma = T::regs().ethernet_dma();
        let mac = T::regs().ethernet_mac();
        let mtl = T::regs().ethernet_mtl();

        // Disable the TX DMA and wait for any previous transmissions to be completed
        ch0!(dma, dmac_tx_cr).modify(|w| w.set_st(false));
        while {
            let txqueue = ch0!(mtl, mtl_tx_qdr).read();

View on GitHub (pinned to 463a07b963)