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