Skip to main content

esp_radio/ieee802154/
raw.rs

1use alloc::collections::VecDeque as Queue;
2
3use esp_hal::{handler, interrupt::Priority, peripherals::IEEE802154};
4use esp_phy::{PhyClockGuard, PhyInitGuard};
5use esp_sync::NonReentrantMutex;
6
7use super::{
8    frame::{
9        FRAME_SIZE,
10        FRAME_VERSION_1,
11        FRAME_VERSION_2,
12        frame_get_version,
13        frame_is_ack_required,
14    },
15    hal::*,
16    pib::*,
17};
18use crate::{
19    radio_clocks::{clocks_ll::enable_ieee802154, deinit_radio_clocks, init_radio_clocks},
20    sys::include::{
21        ieee802154_coex_event_t,
22        ieee802154_coex_event_t_IEEE802154_IDLE,
23        ieee802154_coex_event_t_IEEE802154_LOW,
24        ieee802154_coex_event_t_IEEE802154_MIDDLE,
25    },
26};
27
28const PHY_ENABLE_VERSION_PRINT: u8 = 1;
29
30/// ACK receive timeout in microseconds (200ms), matching the C driver's
31/// `receive_ack_timeout_timer_start(200000)`.
32const ACK_TIMEOUT_US: u32 = 200_000;
33
34static mut RX_BUFFER: [u8; FRAME_SIZE] = [0u8; FRAME_SIZE];
35
36struct PendingTx {
37    frame: *const u8,
38    cca: bool,
39}
40
41// Safety: PendingTx holds a raw pointer to a caller-managed buffer that remains
42// valid for the lifetime of the pending operation (transmit_buffer in Ieee802154).
43unsafe impl Send for PendingTx {}
44
45struct IeeeState {
46    state: Ieee802154State,
47    rx_queue: Queue<RawReceived>,
48    rx_queue_size: usize,
49    pending_tx: Option<PendingTx>,
50    /// Stores the last received ACK frame (populated during
51    /// isr_handle_ack_rx_done and stop_rx_ack). Cleared at the start of
52    /// each transmit.
53    ack_frame: Option<RawReceived>,
54}
55
56static STATE: NonReentrantMutex<IeeeState> = NonReentrantMutex::new(IeeeState {
57    state: Ieee802154State::Idle,
58    rx_queue: Queue::new(),
59    rx_queue_size: 10,
60    pending_tx: None,
61    ack_frame: None,
62});
63
64unsafe extern "C" {
65    fn bt_bb_v2_init_cmplx(print_version: u8); // from libbtbb.a
66
67    fn bt_bb_set_zb_tx_on_delay(time: u16); // from libbtbb.a
68
69    fn esp_coex_ieee802154_ack_pti_set(event: ieee802154_coex_event_t); // from ???
70
71    fn esp_coex_ieee802154_txrx_pti_set(event: ieee802154_coex_event_t); // from ???
72
73    fn esp_coex_ieee802154_coex_break_notify(); // from coex lib
74}
75
76#[derive(Debug, Clone, Copy, PartialEq)]
77enum Ieee802154State {
78    Idle,
79    Receive,
80    Transmit,
81    TxAck,
82    RxAck,
83    TxEnhAck,
84}
85
86#[allow(unused)]
87#[derive(Debug, Clone, Copy, PartialEq)]
88enum Ieee802154TxRxScene {
89    Idle,
90    Tx,
91    Rx,
92    TxAt,
93    RxAt,
94}
95
96/// A raw payload received on some channel
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
98#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99#[instability::unstable]
100pub struct RawReceived {
101    /// Payload
102    pub data: [u8; FRAME_SIZE],
103    /// Receiver channel
104    pub channel: u8,
105}
106
107/// Gates off the 802.15.4 modem clocks and de-initializes the radio clocks
108/// when dropped.
109///
110/// Must be dropped only after the PHY guards: PHY teardown still requires the
111/// modem clocks.
112#[derive(Debug)]
113#[cfg_attr(feature = "defmt", derive(defmt::Format))]
114pub(crate) struct RadioClockGuard;
115
116impl Drop for RadioClockGuard {
117    fn drop(&mut self) {
118        enable_ieee802154(false);
119        deinit_radio_clocks();
120    }
121}
122
123pub(crate) fn esp_ieee802154_enable(
124    radio: IEEE802154<'_>,
125) -> (PhyClockGuard<'_>, PhyInitGuard<'_>, RadioClockGuard) {
126    init_radio_clocks();
127    let phy_clock_guard = esp_phy::enable_phy_clock();
128    enable_ieee802154(true);
129
130    let phy_init_guard = esp_phy::enable_phy();
131
132    esp_btbb_enable();
133    ieee802154_mac_init(radio);
134
135    info!("date={:x}", mac_date());
136    (phy_clock_guard, phy_init_guard, RadioClockGuard)
137}
138
139fn esp_btbb_enable() {
140    unsafe { bt_bb_v2_init_cmplx(PHY_ENABLE_VERSION_PRINT) };
141}
142
143fn ieee802154_mac_init(radio: IEEE802154<'_>) {
144    #[cfg(soc_has_wifi)]
145    unsafe {
146        unsafe extern "C" {
147            static mut coex_pti_tab_ptr: u32;
148            static coex_pti_tab: u8;
149        }
150
151        // Manually set `coex_pti_tab_ptr` pointing to `coex_pti_tab`
152        core::ptr::addr_of_mut!(coex_pti_tab_ptr).write_volatile(&coex_pti_tab as *const _ as u32);
153    }
154
155    ieee802154_pib_init();
156
157    enable_events(Event::mask());
158    disable_events(Event::Timer0Overflow | Event::Timer1Overflow);
159
160    enable_tx_abort_events(
161        TxAbortReason::RxAckTimeout
162            | TxAbortReason::TxCoexBreak
163            | TxAbortReason::TxSecurityError
164            | TxAbortReason::CcaFailed
165            | TxAbortReason::CcaBusy,
166    );
167    enable_rx_abort_events(RxAbortReason::TxAckTimeout | RxAbortReason::TxAckCoexBreak);
168
169    set_ed_sample_mode(EdSampleMode::Avg);
170
171    unsafe { esp_coex_ieee802154_ack_pti_set(ieee802154_coex_event_t_IEEE802154_MIDDLE) };
172    ieee802154_set_txrx_pti(Ieee802154TxRxScene::Idle);
173
174    unsafe {
175        bt_bb_set_zb_tx_on_delay(50); // set tx on delay for libbtbb.a
176    }
177    set_rx_on_delay(50);
178
179    // memset(s_rx_frame, 0, sizeof(s_rx_frame));
180    // s_ieee802154_state = IEEE802154_STATE_IDLE;
181
182    radio.bind_mac_interrupt(zb_mac_handler);
183}
184
185fn ieee802154_set_txrx_pti(txrx_scene: Ieee802154TxRxScene) {
186    match txrx_scene {
187        Ieee802154TxRxScene::Idle => {
188            unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_IDLE) };
189        }
190        Ieee802154TxRxScene::Tx | Ieee802154TxRxScene::Rx => {
191            unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_LOW) };
192        }
193        Ieee802154TxRxScene::TxAt | Ieee802154TxRxScene::RxAt => {
194            unsafe { esp_coex_ieee802154_txrx_pti_set(ieee802154_coex_event_t_IEEE802154_MIDDLE) };
195        }
196    }
197}
198
199fn tx_init(state: &mut IeeeState, frame: *const u8) {
200    stop_current_operation_inner(state);
201
202    ieee802154_pib_update();
203    set_transmit_security(false);
204
205    state.ack_frame = None;
206
207    set_tx_addr(frame);
208
209    if frame_is_ack_required(unsafe { core::slice::from_raw_parts(frame.add(1), *frame as usize) })
210    {
211        // set rx pointer for ack frame
212        set_next_rx_buffer();
213    }
214}
215
216pub(crate) fn set_queue_size(rx_queue_size: usize) {
217    STATE.with(|state| {
218        state.rx_queue_size = rx_queue_size;
219    });
220}
221
222/// Pointer to the current TX frame (stored for ACK handling)
223static mut TX_FRAME: *const u8 = core::ptr::null();
224
225pub fn ieee802154_transmit(frame: *const u8, cca: bool) -> i32 {
226    STATE.with(|state| {
227        // TX deferral: don't abort in-flight frame reception or ACK transmission.
228        // Matches the C driver's ieee802154_transmit() which defers to pending_tx.
229        if state.state == Ieee802154State::TxAck
230            || state.state == Ieee802154State::TxEnhAck
231            || (state.state == Ieee802154State::Receive && is_current_rx_frame())
232        {
233            // Defer: store pending TX and enable all RX abort events so we
234            // know when the current operation finishes.
235            state.pending_tx = Some(PendingTx { frame, cca });
236            enable_rx_abort_events(RxAbortReason::all());
237            return;
238        }
239
240        state.pending_tx = None;
241        transmit_internal(state, frame, cca);
242    });
243
244    0 // ESP_OK
245}
246
247fn transmit_internal(state: &mut IeeeState, frame: *const u8, cca: bool) {
248    unsafe { TX_FRAME = frame };
249
250    tx_init(state, frame);
251
252    ieee802154_set_txrx_pti(Ieee802154TxRxScene::Tx);
253
254    if cca {
255        set_cmd(Command::CcaTxStart);
256    } else {
257        set_cmd(Command::TxStart);
258    }
259    state.state = Ieee802154State::Transmit;
260}
261
262pub fn ieee802154_receive() -> i32 {
263    STATE.with(|state| {
264        if state.state == Ieee802154State::Receive || state.state == Ieee802154State::TxAck {
265            // already in rx or tx_ack state, don't abort current operation
266            return;
267        }
268
269        rx_init(state);
270        enable_rx();
271
272        state.state = Ieee802154State::Receive;
273    });
274
275    0 // ESP-OK
276}
277
278pub fn ieee802154_poll() -> Option<RawReceived> {
279    STATE.with(|state| state.rx_queue.pop_front())
280}
281
282/// Get the ACK frame received in response to the last transmission.
283/// Returns `None` if no ACK was received (e.g., frame didn't require ACK,
284/// or ACK was not received before timeout).
285/// The C driver passes this via `esp_ieee802154_transmit_done(frame, ack, ack_info)`.
286pub fn get_ack_frame() -> Option<RawReceived> {
287    STATE.with(|state| state.ack_frame)
288}
289
290fn rx_init(state: &mut IeeeState) {
291    stop_current_operation_inner(state);
292    ieee802154_pib_update();
293}
294
295fn enable_rx() {
296    set_next_rx_buffer();
297    ieee802154_set_txrx_pti(Ieee802154TxRxScene::Rx);
298
299    set_cmd(Command::RxStart);
300
301    // ieee802154_state = IEEE802154_STATE_RX;
302}
303
304fn stop_current_operation_inner(state: &mut IeeeState) {
305    event_end_process();
306    match state.state {
307        Ieee802154State::Idle => {
308            set_cmd(Command::Stop);
309        }
310        Ieee802154State::Receive => {
311            stop_rx(state);
312        }
313        Ieee802154State::TxAck => {
314            stop_tx_ack();
315        }
316        Ieee802154State::Transmit | Ieee802154State::TxEnhAck => {
317            stop_tx(state);
318        }
319        Ieee802154State::RxAck => {
320            stop_rx_ack(state);
321        }
322    }
323}
324
325fn stop_rx(state: &mut IeeeState) {
326    set_cmd(Command::Stop);
327
328    let evts = events();
329    if evts & Event::RxDone != 0 {
330        receive_done(state);
331    }
332
333    clear_events(Event::RxDone | Event::RxAbort | Event::RxSfdDone);
334}
335
336fn stop_tx_ack() {
337    set_cmd(Command::Stop);
338
339    // Frame was already copied to queue in isr_handle_rx_done.
340    // Don't call receive_done again (that caused the "Receive queue full" bug).
341
342    clear_events(Event::AckTxDone | Event::RxAbort | Event::TxSfdDone);
343}
344
345fn stop_tx(state: &mut IeeeState) {
346    set_cmd(Command::Stop);
347
348    let evts = events();
349
350    if state.state == Ieee802154State::TxEnhAck {
351        // Frame was already copied in isr_handle_rx_done, no need to call receive_done
352        clear_events(Event::AckTxDone as u16);
353    } else if (evts & Event::TxDone != 0)
354        && (!frame_is_ack_required(unsafe {
355            core::slice::from_raw_parts(TX_FRAME.add(1), *TX_FRAME as usize)
356        }) || !rx_auto_ack())
357    {
358        // tx is done, no ack needed
359        super::tx_done();
360    } else {
361        super::tx_failed();
362    }
363
364    clear_events(Event::TxDone | Event::TxAbort | Event::TxSfdDone);
365}
366
367fn stop_rx_ack(state: &mut IeeeState) {
368    set_cmd(Command::Stop);
369
370    let evts = events();
371
372    timer0_stop();
373    disable_events(Event::Timer0Overflow as u16);
374
375    if evts & Event::AckRxDone != 0 {
376        // Capture the received ACK frame
377        state.ack_frame = Some(RawReceived {
378            data: unsafe { RX_BUFFER },
379            channel: freq_to_channel(freq()),
380        });
381        super::tx_done();
382    } else {
383        super::tx_failed();
384    }
385
386    clear_events(Event::AckRxDone | Event::RxSfdDone | Event::TxAbort);
387}
388
389fn receive_done(state: &mut IeeeState) {
390    unsafe {
391        if state.rx_queue.len() < state.rx_queue_size {
392            let item = RawReceived {
393                data: RX_BUFFER,
394                channel: freq_to_channel(freq()),
395            };
396            state.rx_queue.push_back(item);
397        } else {
398            warn!("Receive queue full");
399        }
400    }
401}
402
403fn set_next_rx_buffer() {
404    set_rx_addr(core::ptr::addr_of_mut!(RX_BUFFER).cast());
405}
406
407pub fn set_promiscuous(enable: bool) {
408    ieee802154_pib_set_promiscuous(enable);
409}
410
411pub fn set_auto_ack_tx(enable: bool) {
412    ieee802154_pib_set_auto_ack_tx(enable);
413}
414
415pub fn set_auto_ack_rx(enable: bool) {
416    ieee802154_pib_set_auto_ack_rx(enable);
417}
418
419pub fn set_enhance_ack_tx(enable: bool) {
420    ieee802154_pib_set_enhance_ack_tx(enable);
421}
422
423pub fn set_coordinator(enable: bool) {
424    ieee802154_pib_set_coordinator(enable);
425}
426
427pub fn set_rx_when_idle(enable: bool) {
428    ieee802154_pib_set_rx_when_idle(enable);
429}
430
431pub fn set_tx_power(power: i8) {
432    ieee802154_pib_set_tx_power(power);
433}
434
435pub fn set_channel(channel: u8) {
436    ieee802154_pib_set_channel(channel);
437}
438
439#[allow(unused)]
440pub fn set_pending_mode(mode: PendingMode) {
441    ieee802154_pib_set_pending_mode(mode);
442}
443
444#[allow(unused)]
445pub fn set_multipan_enable(mask: u8) {
446    set_multipan_enable_mask(mask);
447}
448
449pub fn set_short_address(index: u8, address: u16) {
450    ieee802154_pib_set_short_address(index, address);
451}
452
453pub fn set_extended_address(index: u8, address: [u8; IEEE802154_FRAME_EXT_ADDR_SIZE]) {
454    ieee802154_pib_set_extended_address(index, address);
455}
456
457pub fn set_cca_theshold(cca_threshold: i8) {
458    ieee802154_pib_set_cca_theshold(cca_threshold);
459}
460
461pub fn set_cca_mode(mode: CcaMode) {
462    ieee802154_pib_set_cca_mode(mode);
463}
464
465pub fn set_panid(index: u8, id: u16) {
466    ieee802154_pib_set_panid(index, id);
467}
468
469/// Stop timers and clear security at the start of event processing,
470/// matching C driver 5.5.2's event_end_process.
471#[inline(always)]
472fn event_end_process() {
473    set_transmit_security(false);
474    timer0_stop();
475    // timer1 is not currently used; would be stopped here if we add timed TX/RX
476}
477
478fn next_operation_inner(state: &mut IeeeState) {
479    // Set state to Idle before dispatching the next operation.
480    // This prevents stop_current_operation_inner (called from tx_init)
481    // from seeing a stale TxAck state and calling stop_tx_ack, which
482    // would duplicate-queue the already-delivered received frame.
483    state.state = Ieee802154State::Idle;
484
485    if let Some(pending) = state.pending_tx.take() {
486        // Restore RX abort events to normal (matching C driver's next_operation)
487        disable_rx_abort_events(RxAbortReason::all());
488        enable_rx_abort_events(RxAbortReason::TxAckTimeout | RxAbortReason::TxAckCoexBreak);
489        // Clear any stale RX abort events created during deferral
490        clear_events(Event::RxAbort as u16);
491        transmit_internal(state, pending.frame, pending.cca);
492    } else if ieee802154_pib_get_rx_when_idle() {
493        enable_rx();
494        state.state = Ieee802154State::Receive;
495    }
496    // else state stays Idle
497}
498
499fn next_operation() {
500    STATE.with(next_operation_inner);
501}
502
503// FIXME: we shouldn't need this - we need to re-align the original driver with our port
504pub(crate) fn ensure_receive_enabled() {
505    // shouldn't be necessary but avoids a problem with rx stopping
506    // unexpectedly when used together with BLE
507    STATE.with(|state| {
508        if state.state == Ieee802154State::Receive {
509            set_cmd(Command::RxStart);
510        }
511    });
512}
513
514#[handler(priority = Priority::Priority1)]
515fn zb_mac_handler() {
516    trace!("ZB_MAC interrupt");
517
518    let events = events();
519    let rx_abort_reason = get_rx_abort_reason();
520    let tx_abort_reason = get_tx_abort_reason();
521
522    clear_events(events);
523
524    trace!("events = {:032b}", events);
525
526    let mut needs_next_operation = false;
527
528    // First phase RX abort processing (handles RX-state aborts)
529    if events & Event::RxAbort != 0 {
530        trace!("RxAbort phase 1");
531        isr_handle_rx_phase_rx_abort(rx_abort_reason, &mut needs_next_operation);
532    }
533
534    if events & Event::RxSfdDone != 0 {
535        trace!("rx sfd done");
536    }
537
538    if events & Event::TxSfdDone != 0 {
539        trace!("tx sfd done");
540    }
541
542    if events & Event::TxDone != 0 {
543        trace!("tx done");
544        isr_handle_tx_done(&mut needs_next_operation);
545    }
546
547    if events & Event::RxDone != 0 {
548        trace!("rx done");
549        isr_handle_rx_done(&mut needs_next_operation);
550    }
551
552    if events & Event::AckTxDone != 0 {
553        trace!("AckTxDone");
554        isr_handle_ack_tx_done(&mut needs_next_operation);
555    }
556
557    if events & Event::AckRxDone != 0 {
558        trace!("AckRxDone");
559        isr_handle_ack_rx_done(&mut needs_next_operation);
560    }
561
562    // Second phase RX abort processing (handles TX-ACK-state aborts)
563    if events & Event::RxAbort != 0 {
564        trace!("RxAbort phase 2");
565        isr_handle_tx_ack_phase_rx_abort(rx_abort_reason, &mut needs_next_operation);
566    }
567
568    if events & Event::TxAbort != 0 {
569        trace!("TxAbort");
570        isr_handle_tx_abort(tx_abort_reason, &mut needs_next_operation);
571    }
572
573    if events & Event::Timer0Overflow != 0 {
574        trace!("Timer0Overflow");
575        isr_handle_timer0_done(&mut needs_next_operation);
576    }
577
578    if needs_next_operation {
579        next_operation();
580    }
581}
582
583/// Handle TX done in ISR - matches C driver's isr_handle_tx_done
584fn isr_handle_tx_done(needs_next_op: &mut bool) {
585    event_end_process();
586
587    STATE.with(|state| {
588        if state.state == Ieee802154State::Transmit {
589            let tx_frame = unsafe { TX_FRAME };
590            let frame_data =
591                unsafe { core::slice::from_raw_parts(tx_frame.add(1), *tx_frame as usize) };
592
593            if frame_is_ack_required(frame_data) && rx_auto_ack() {
594                // Wait for ACK - start 200ms timeout timer matching C driver
595                state.state = Ieee802154State::RxAck;
596                receive_ack_timeout_timer_start(ACK_TIMEOUT_US);
597                *needs_next_op = false;
598            } else {
599                // TX complete, no ACK needed
600                super::tx_done();
601                *needs_next_op = true;
602            }
603        }
604    });
605}
606
607/// Handle RX done in ISR - matches C driver's isr_handle_rx_done
608fn isr_handle_rx_done(needs_next_op: &mut bool) {
609    event_end_process();
610    unsafe {
611        trace!(
612            "Received raw {:?}",
613            crate::fmt::Bytes(&*core::ptr::addr_of!(RX_BUFFER))
614        );
615
616        STATE.with(|state| {
617            let frm = if RX_BUFFER[0] >= FRAME_SIZE as u8 {
618                warn!("RX_BUFFER[0] {} is larger than frame size", RX_BUFFER[0]);
619                &RX_BUFFER[1..][..FRAME_SIZE - 1]
620            } else {
621                &RX_BUFFER[1..][..RX_BUFFER[0] as usize]
622            };
623
624            // Always copy RX_BUFFER immediately — we only have one buffer,
625            // and the hardware may overwrite it during auto-ACK or later RX.
626            // The C driver can defer because it has multiple RX buffers and
627            // advances the index in isr_handle_rx_done via next_rx_buffer().
628            receive_done(state);
629
630            if will_auto_send_ack(frm) {
631                // auto tx ack for frame version 0b00 and 0b01
632                // Frame data already copied above. Defer rx_available()
633                // notification until ACK completes (isr_handle_ack_tx_done).
634                ack_config_pending_bit(frm);
635                state.state = Ieee802154State::TxAck;
636                *needs_next_op = false;
637            } else if should_send_enhanced_ack(frm) {
638                // Enhanced ACK for frame version 0b10 - TODO: full enh-ack support
639                // Frame data already copied above.
640                state.state = Ieee802154State::TxEnhAck;
641                *needs_next_op = false;
642            } else {
643                // No ACK needed, notify immediately (data already copied above)
644                super::rx_available();
645                *needs_next_op = true;
646            }
647        });
648    }
649}
650
651/// Handle ACK TX done in ISR - matches C driver's isr_handle_ack_tx_done
652fn isr_handle_ack_tx_done(needs_next_op: &mut bool) {
653    // Frame was already copied to queue in isr_handle_rx_done (we must copy
654    // immediately because we only have one RX buffer). Now notify upper layer.
655    super::rx_available();
656    *needs_next_op = true;
657}
658
659/// Handle ACK RX done in ISR - matches C driver's isr_handle_ack_rx_done
660fn isr_handle_ack_rx_done(needs_next_op: &mut bool) {
661    timer0_stop();
662    disable_events(Event::Timer0Overflow as u16);
663    // Capture the received ACK frame before RX buffer is reused
664    STATE.with(|state| {
665        state.ack_frame = Some(RawReceived {
666            data: unsafe { RX_BUFFER },
667            channel: freq_to_channel(freq()),
668        });
669    });
670    // ACK received for our transmitted frame
671    super::tx_done();
672    *needs_next_op = true;
673}
674
675/// First phase RX abort - handles aborts while in RX state
676/// Matches C driver's isr_handle_rx_phase_rx_abort
677fn isr_handle_rx_phase_rx_abort(rx_abort_reason: u32, needs_next_op: &mut bool) {
678    event_end_process();
679    match rx_abort_reason {
680        // Stop reasons - do nothing
681        r if r == RxAbortReason::RxStop as u32
682            || r == RxAbortReason::TxAckStop as u32
683            || r == RxAbortReason::EdStop as u32 => {}
684
685        // RX errors while receiving - just need next operation
686        r if r == RxAbortReason::SfdTimeout as u32
687            || r == RxAbortReason::CrcError as u32
688            || r == RxAbortReason::InvalidLen as u32
689            || r == RxAbortReason::FilterFail as u32
690            || r == RxAbortReason::NoRss as u32
691            || r == RxAbortReason::UnexpectedAck as u32
692            || r == RxAbortReason::RxRestart as u32
693            || r == RxAbortReason::CoexBreak as u32 =>
694        {
695            *needs_next_op = true;
696        }
697        // TX ACK timeout/coex break/enhack error - handled in phase 2
698        r if r == RxAbortReason::TxAckTimeout as u32
699            || r == RxAbortReason::TxAckCoexBreak as u32
700            || r == RxAbortReason::EnhackSecurityError as u32 => {}
701
702        _ => {
703            warn!("Unexpected rx abort reason: {}", rx_abort_reason);
704            *needs_next_op = true;
705        }
706    }
707}
708
709/// Second phase RX abort - handles aborts while in TX_ACK state
710/// Matches C driver's isr_handle_tx_ack_phase_rx_abort
711fn isr_handle_tx_ack_phase_rx_abort(rx_abort_reason: u32, needs_next_op: &mut bool) {
712    event_end_process();
713
714    match rx_abort_reason {
715        // Most abort reasons during TX_ACK phase - do nothing
716        r if r == RxAbortReason::TxAckStop as u32
717            || r == RxAbortReason::RxStop as u32
718            || r == RxAbortReason::EdStop as u32
719            || r == RxAbortReason::SfdTimeout as u32
720            || r == RxAbortReason::CrcError as u32
721            || r == RxAbortReason::InvalidLen as u32
722            || r == RxAbortReason::FilterFail as u32
723            || r == RxAbortReason::NoRss as u32
724            || r == RxAbortReason::UnexpectedAck as u32
725            || r == RxAbortReason::RxRestart as u32
726            || r == RxAbortReason::CoexBreak as u32
727            || r == RxAbortReason::EdAbort as u32
728            || r == RxAbortReason::EdCoexReject as u32 => {}
729
730        // TX ACK timeout or coex break while sending ACK - notify upper layer
731        r if r == RxAbortReason::TxAckTimeout as u32
732            || r == RxAbortReason::TxAckCoexBreak as u32 =>
733        {
734            // Frame was already copied in isr_handle_rx_done, just notify
735            super::rx_available();
736            *needs_next_op = true;
737        }
738        // Enhanced ACK security error - notify upper layer
739        r if r == RxAbortReason::EnhackSecurityError as u32 => {
740            // Frame was already copied in isr_handle_rx_done, just notify
741            super::rx_available();
742            *needs_next_op = true;
743        }
744        _ => {
745            warn!(
746                "Unexpected rx abort reason in tx_ack phase: {}",
747                rx_abort_reason
748            );
749        }
750    }
751}
752
753/// Handle Timer0 overflow (ACK receive timeout) - matches C driver's isr_handle_timer0_done
754/// which calls ieee802154_rx_ack_timeout_callback
755fn isr_handle_timer0_done(needs_next_op: &mut bool) {
756    // Timer0 fired while waiting for ACK - TX failed with no ACK
757    timer0_stop();
758    disable_events(Event::Timer0Overflow as u16);
759    super::tx_failed();
760    *needs_next_op = true;
761}
762
763/// Handle TX abort - matches C driver's isr_handle_tx_abort
764fn isr_handle_tx_abort(tx_abort_reason: u32, needs_next_op: &mut bool) {
765    event_end_process();
766
767    match tx_abort_reason {
768        // RX ACK stop or TX stop - do nothing (handled by stop_current_operation)
769        r if r == TxAbortReason::RxAckStop as u32 || r == TxAbortReason::TxStop as u32 => {
770            // do nothing
771        }
772        // RX ACK errors while waiting for ACK - invalid ACK, need next_op
773        r if r == TxAbortReason::RxAckSfdTimeout as u32
774            || r == TxAbortReason::RxAckCrcError as u32
775            || r == TxAbortReason::RxAckInvalidLen as u32
776            || r == TxAbortReason::RxAckFilterFail as u32
777            || r == TxAbortReason::RxAckNoRss as u32
778            || r == TxAbortReason::RxAckCoexBreak as u32
779            || r == TxAbortReason::RxAckTypeNotAck as u32
780            || r == TxAbortReason::RxAckRestart as u32 =>
781        {
782            super::tx_failed();
783            *needs_next_op = true;
784        }
785        // RX ACK timeout - no ACK received
786        r if r == TxAbortReason::RxAckTimeout as u32 => {
787            timer0_stop();
788            disable_events(Event::Timer0Overflow as u16);
789            super::tx_failed();
790            *needs_next_op = true;
791        }
792        // TX coex break - notify coex manager
793        r if r == TxAbortReason::TxCoexBreak as u32 => {
794            unsafe { esp_coex_ieee802154_coex_break_notify() };
795            super::tx_failed();
796            *needs_next_op = true;
797        }
798        // TX security error
799        r if r == TxAbortReason::TxSecurityError as u32 => {
800            super::tx_failed();
801            *needs_next_op = true;
802        }
803        // CCA failed
804        r if r == TxAbortReason::CcaFailed as u32 => {
805            super::tx_failed();
806            *needs_next_op = true;
807        }
808        // CCA busy
809        r if r == TxAbortReason::CcaBusy as u32 => {
810            super::tx_failed();
811            *needs_next_op = true;
812        }
813        _ => {
814            warn!("Unexpected tx abort reason: {}", tx_abort_reason);
815        }
816    }
817}
818
819fn freq_to_channel(freq: u8) -> u8 {
820    (freq - 3) / 5 + 11
821}
822
823/// Provide the hardware with the frame-pending bit for the ACK it is about
824/// to send.
825///
826/// TODO: Revisit once a pending-address table is exposed by the driver.
827fn ack_config_pending_bit(frame: &[u8]) {
828    // The hardware inserts this value only into acks of version 0b00/0b01
829    // frames; 2015 enhanced ACKs carry it inside the software-built frame.
830    if frame_get_version(frame) <= FRAME_VERSION_1 {
831        // Until the driver exposes the pending-address table, claim pending
832        // for every poller in every mode: a spurious "pending" costs a
833        // sleepy device one idle receive window, while a wrong "nothing
834        // pending" makes it sleep through a frame queued for it.
835        set_pending_bit(true);
836    }
837}
838
839fn will_auto_send_ack(frame: &[u8]) -> bool {
840    frame_is_ack_required(frame) && frame_get_version(frame) <= FRAME_VERSION_1 && tx_auto_ack()
841}
842
843fn should_send_enhanced_ack(frame: &[u8]) -> bool {
844    frame_is_ack_required(frame) && frame_get_version(frame) <= FRAME_VERSION_2 && tx_enhance_ack()
845}
846
847/// Start the ACK receive timeout timer.
848/// Duration is in microseconds. The C driver uses 200000 (200ms).
849fn receive_ack_timeout_timer_start(duration: u32) {
850    enable_events(Event::Timer0Overflow as u16);
851    timer0_set_threshold(duration);
852    timer0_start();
853}