use alloc::{sync::Arc, vec, vec::Vec};
use core::{
sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
task::Poll,
};
use log;
use crate::{
common::{SDIO_TYPE_CFG, SDIO_TYPE_CFG_CMD_RSP, SDIO_TYPE_CFG_DATA_CFM, SDIO_TYPE_CFG_PRINT},
fdrv::{
consts::{
BLOCK_COUNT_MASK, ETH_P_PAE, MAX_PKT_LEN, RX_ALIGNMENT, RX_HWHRD_LEN,
SDIO_OTHER_INTERRUPT, SDIOWIFI_FUNC_BLOCKSIZE,
},
core::bus::{BusState, WifiBus},
},
};
pub static RX_WAKE_COUNT: AtomicU64 = AtomicU64::new(0);
static RX_DATA_CALLBACK: AtomicUsize = AtomicUsize::new(0);
static RX_DATA_PENDING: AtomicBool = AtomicBool::new(false);
pub fn register_rx_data_callback(cb: fn()) {
RX_DATA_CALLBACK.store(cb as usize, Ordering::Release);
}
fn invoke_rx_data_callback() {
if !RX_DATA_PENDING.swap(false, Ordering::AcqRel) {
return;
}
let ptr = RX_DATA_CALLBACK.load(Ordering::Acquire);
if ptr != 0 {
let cb: fn() = unsafe { core::mem::transmute(ptr) };
cb();
}
}
fn align_up(val: usize, align: usize) -> usize {
(val + align - 1) & !(align - 1)
}
pub fn start(bus: Arc<WifiBus>) {
log::debug!("[wifi-rx] thread starting");
crate::runtime::runtime().spawn_poll_task(
"wifi-rx",
alloc::boxed::Box::new(move |cx| {
if *bus.state.lock() == BusState::Down {
return Poll::Ready(());
}
if bus.rx.irq_pending.swap(false, Ordering::AcqRel) {
RX_WAKE_COUNT.fetch_add(1, Ordering::Relaxed);
}
process_rx_frames(&bus);
bus.rx.irq_waker.register(cx.waker());
bus.transport.unmask_card_irq();
invoke_rx_data_callback();
if bus.rx.irq_pending.swap(false, Ordering::AcqRel) {
process_rx_frames(&bus);
bus.transport.unmask_card_irq();
invoke_rx_data_callback();
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Pending
}),
);
}
fn read_block_count_with_retry(bus: &WifiBus, other_int_retries: &mut u32) -> (u8, bool) {
let intstatus = {
match bus.transport.read_byte(1, bus.transport.block_cnt_reg()) {
Ok(v) => v,
Err(e) => {
log::error!("[wifi-rx] read block_cnt failed: {:?}", e);
return (0, false);
}
}
};
if intstatus & SDIO_OTHER_INTERRUPT != 0 {
*other_int_retries += 1;
if *other_int_retries > 3 {
log::trace!(
"[wifi-rx] SDIO_OTHER_INTERRUPT persists after {} retries, giving up",
other_int_retries
);
return (0, false);
}
log::trace!(
"[wifi-rx] SDIO_OTHER_INTERRUPT (0x{:02x}), re-read",
intstatus
);
return (0, true); }
(intstatus, true)
}
fn resolve_rx_data_len(bus: &WifiBus, intstatus: u8) -> usize {
if !bus.transport.is_v3() {
return (intstatus & BLOCK_COUNT_MASK) as usize * SDIOWIFI_FUNC_BLOCKSIZE;
}
let read_bytemode_len = || -> usize {
match bus.transport.read_byte(1, bus.transport.bytemode_len_reg()) {
Ok(byte_len) => byte_len as usize * 4,
Err(e) => {
log::error!("[wifi-rx] read bytemode_len failed: {:?}", e);
0
}
}
};
let intmaskf2 = intstatus | (1 << 3);
if intmaskf2 > 120 {
if intmaskf2 == 127 {
read_bytemode_len()
} else {
(intstatus & 0x07) as usize * SDIOWIFI_FUNC_BLOCKSIZE
}
} else {
if intstatus == 120 {
read_bytemode_len()
} else {
(intstatus & 0x7F) as usize * SDIOWIFI_FUNC_BLOCKSIZE
}
}
}
fn read_fifo_data(bus: &WifiBus, data_len: usize) -> Option<Vec<u8>> {
let mut buf = vec![0u8; data_len];
let mut offset = 0;
while offset < data_len {
let chunk_len = core::cmp::min(data_len - offset, SDIOWIFI_FUNC_BLOCKSIZE);
if let Err(e) = bus.transport.read_fifo(
1,
bus.transport.rd_fifo_addr(),
&mut buf[offset..offset + chunk_len],
) {
log::error!(
"[wifi-rx] read_fifo failed at offset {}/{}: {:?}",
offset,
data_len,
e
);
return None;
}
offset += chunk_len;
}
Some(buf)
}
fn process_rx_frames(bus: &WifiBus) {
bus.transport.mask_card_irq();
let mut other_int_retries = 0u32;
loop {
if bus.rx.irq_pending.swap(false, Ordering::AcqRel) {
}
let (intstatus, should_continue) = read_block_count_with_retry(bus, &mut other_int_retries);
if !should_continue {
break;
}
if intstatus == 0 {
break;
}
other_int_retries = 0;
let data_len = resolve_rx_data_len(bus, intstatus);
if data_len == 0 {
break;
}
log::trace!(
"[wifi-rx] intstatus=0x{:02x}, data_len={} bytes, reading FIFO",
intstatus,
data_len
);
let Some(buf) = read_fifo_data(bus, data_len) else {
break;
};
dispatch_frames(bus, &buf);
}
}
struct HwRxHdrInfo {
decr_status: u8,
is_80211_npdu: bool,
}
struct AddrInfo<'a> {
da: &'a [u8],
sa: &'a [u8],
}
fn extract_hw_rxhdr_info(data_payload: &[u8]) -> HwRxHdrInfo {
const HWVECT_STATUS_OFFSET: usize = 36;
const FLAGS_OFFSET: usize = 48;
const DECR_UNENC: u8 = 0;
let decr_status = if data_payload.len() > HWVECT_STATUS_OFFSET {
(data_payload[HWVECT_STATUS_OFFSET] >> 2) & 0x07
} else {
DECR_UNENC
};
let flags_byte0 = if data_payload.len() > FLAGS_OFFSET {
data_payload[FLAGS_OFFSET]
} else {
0
};
let is_80211_npdu = (flags_byte0 >> 1) & 0x01 != 0;
HwRxHdrInfo {
decr_status,
is_80211_npdu,
}
}
fn is_80211_data_frame(fc0: u8) -> bool {
(fc0 & 0x0C) == 0x08
}
fn is_80211_mgmt_frame(fc0: u8) -> bool {
(fc0 & 0x0C) == 0x00
}
fn mgmt_subtype_name(fc0: u8) -> &'static str {
match (fc0 >> 4) & 0x0F {
0x0 => "AssocReq",
0x1 => "AssocResp",
0x2 => "ReassocReq",
0x3 => "ReassocResp",
0x4 => "ProbeReq",
0x5 => "ProbeResp",
0x8 => "Beacon",
0xA => "Disassoc",
0xB => "Auth",
0xC => "Deauth",
0xD => "Action",
_ => "Other",
}
}
fn handle_mgmt_frame(bus: &WifiBus, mpdu: &[u8], pkt_len: usize) {
let fc0 = mpdu[0];
let subtype = mgmt_subtype_name(fc0);
if pkt_len < 16 {
return;
}
let sa = &mpdu[10..16];
match (fc0 >> 4) & 0x0F {
0xB if pkt_len >= 30 => {
let alg = u16::from_le_bytes([mpdu[24], mpdu[25]]);
let seq = u16::from_le_bytes([mpdu[26], mpdu[27]]);
log::info!("[ap-rx] Auth from {:02x?}: alg={} seq={}", sa, alg, seq);
if alg == 0 && seq == 1 {
send_auth_response(bus, sa);
}
}
0x0 | 0x2 if pkt_len >= 28 => {
let cap = u16::from_le_bytes([mpdu[24], mpdu[25]]);
log::info!("[ap-rx] {} from {:02x?}: cap=0x{:04x}", subtype, sa, cap);
bus.ap
.assoc_queue
.lock()
.push_back(mpdu[..pkt_len].to_vec());
bus.ap.assoc_pollset.wake();
}
_ => {
log::trace!("[ap-rx] {} from {:02x?} (fc0=0x{:02x})", subtype, sa, fc0);
}
}
}
fn send_auth_response(bus: &WifiBus, dst: &[u8]) {
let ap_mac = match *bus.conn.sta_mac.lock() {
Some(m) => m,
None => {
log::warn!("[ap-rx] no AP mac, cannot send Auth Response");
return;
}
};
let mut frame = Vec::with_capacity(30);
frame.extend_from_slice(&[0xB0, 0x00]); frame.extend_from_slice(&[0x00, 0x00]); frame.extend_from_slice(dst); frame.extend_from_slice(&ap_mac); frame.extend_from_slice(&ap_mac); frame.extend_from_slice(&[0x00, 0x00]); frame.extend_from_slice(&0u16.to_le_bytes()); frame.extend_from_slice(&2u16.to_le_bytes()); frame.extend_from_slice(&0u16.to_le_bytes());
match crate::fdrv::thread::tx::enqueue_mgmt_frame(bus, frame) {
Ok(()) => log::info!("[ap-tx] Auth Response queued -> {:02x?}", dst),
Err(e) => log::warn!("[ap-tx] Auth Response enqueue failed: {:?}", e),
}
}
fn get_80211_header_len(fc0: u8, fc1: u8) -> usize {
let is_qos = (fc0 & 0x80) != 0;
let mut hdr_len: usize = if is_qos { 26 } else { 24 };
if (fc1 & 0x80) != 0 {
hdr_len += 4; }
hdr_len
}
fn parse_80211_addrs(mpdu: &[u8], fc1: u8, pkt_len: usize) -> Option<AddrInfo<'_>> {
let to_ds = fc1 & 0x01;
let from_ds = (fc1 >> 1) & 0x01;
let (da, sa): (&[u8], &[u8]) = match (to_ds, from_ds) {
(0, 0) => (&mpdu[4..10], &mpdu[10..16]),
(1, 0) => (&mpdu[16..22], &mpdu[10..16]),
(0, 1) => (&mpdu[4..10], &mpdu[16..22]),
_ => {
if pkt_len < 30 {
return None;
}
(&mpdu[16..22], &mpdu[24..30])
}
};
Some(AddrInfo { da, sa })
}
fn get_crypto_header_len(decr_status: u8) -> usize {
const DECR_CCMP128: u8 = 3;
const DECR_CCMP256: u8 = 4;
const DECR_GCMP128: u8 = 5;
const DECR_GCMP256: u8 = 6;
const DECR_TKIP: u8 = 2;
const DECR_WEP: u8 = 1;
const DECR_WAPI: u8 = 7;
match decr_status {
DECR_CCMP128 | DECR_CCMP256 | DECR_GCMP128 | DECR_GCMP256 => 8,
DECR_TKIP => 8,
DECR_WEP => 4,
DECR_WAPI => 18,
_ => 0,
}
}
fn extract_ethertype(mpdu: &[u8], ether_type_offset: usize, pkt_len: usize) -> Option<u16> {
if pkt_len < ether_type_offset + 2 {
log::trace!(
"[wifi-rx] MPDU too short for LLC/SNAP: pkt_len={}, need={}",
pkt_len,
ether_type_offset + 2
);
return None;
}
Some(u16::from_be_bytes([
mpdu[ether_type_offset],
mpdu[ether_type_offset + 1],
]))
}
fn process_eapol_frame(bus: &WifiBus, mpdu: &[u8], payload_start: usize, pkt_len: usize) {
if pkt_len <= payload_start {
return;
}
let raw_eapol = &mpdu[payload_start..];
let eapol = if raw_eapol.len() >= 4 {
let body_len = u16::from_be_bytes([raw_eapol[2], raw_eapol[3]]) as usize;
let actual_len = 4 + body_len;
if actual_len <= raw_eapol.len() {
raw_eapol[..actual_len].to_vec()
} else {
raw_eapol.to_vec()
}
} else {
raw_eapol.to_vec()
};
let mut queue = bus.rx.eapol_queue.lock();
queue.push_back(eapol);
drop(queue);
bus.rx.eapol_pollset.wake();
}
fn build_and_enqueue_eth_frame(
bus: &WifiBus,
mpdu: &[u8],
addr_info: &AddrInfo<'_>,
ether_type_offset: usize,
payload_start: usize,
pkt_len: usize,
) {
const DATA_RX_QUEUE_MAX: usize = 64;
if pkt_len <= payload_start {
return;
}
let payload = &mpdu[payload_start..];
let mut eth_frame = Vec::with_capacity(14 + payload.len());
eth_frame.extend_from_slice(addr_info.da);
eth_frame.extend_from_slice(addr_info.sa);
eth_frame.extend_from_slice(&mpdu[ether_type_offset..ether_type_offset + 2]);
eth_frame.extend_from_slice(payload);
let et = u16::from_be_bytes([mpdu[ether_type_offset], mpdu[ether_type_offset + 1]]);
log::trace!(
"[ap-rx] DATA from {:02x?} ethertype=0x{:04x} len={}",
addr_info.sa,
et,
eth_frame.len()
);
let mut queue = bus.rx.data_queue.lock();
if queue.len() >= DATA_RX_QUEUE_MAX {
queue.pop_front();
}
queue.push_back(eth_frame);
drop(queue);
bus.rx.data_pollset.wake();
RX_DATA_PENDING.store(true, Ordering::Release);
}
fn process_data_frame(bus: &WifiBus, data_payload: &[u8], pkt_len: usize, _mpdu_offset: usize) {
const MPDU_OFFSET: usize = 60;
if pkt_len < 24 || data_payload.len() < MPDU_OFFSET + pkt_len {
log::warn!("[wifi-rx] DATA frame too short for 802.11 header");
return;
}
let mpdu = &data_payload[MPDU_OFFSET..MPDU_OFFSET + pkt_len];
let fc0 = mpdu[0];
let fc1 = mpdu[1];
log::trace!(
"[wifi-rx] 80211 fc0=0x{:02x} fc1=0x{:02x} data={} mgmt={} pkt_len={}",
fc0,
fc1,
is_80211_data_frame(fc0),
is_80211_mgmt_frame(fc0),
pkt_len
);
if !is_80211_data_frame(fc0) {
if is_80211_mgmt_frame(fc0) {
handle_mgmt_frame(bus, mpdu, pkt_len);
}
return;
}
let hdr_len = get_80211_header_len(fc0, fc1);
let addr_info = match parse_80211_addrs(mpdu, fc1, pkt_len) {
Some(info) => info,
None => return,
};
let hw_info = extract_hw_rxhdr_info(data_payload);
if hw_info.is_80211_npdu {
return;
}
let crypto_hdr_len = get_crypto_header_len(hw_info.decr_status);
let llc_offset = hdr_len + crypto_hdr_len;
let ether_type_offset = llc_offset + 6;
let ethertype = match extract_ethertype(mpdu, ether_type_offset, pkt_len) {
Some(et) => et,
None => return,
};
let payload_start = llc_offset + 8;
log::trace!(
"[wifi-rx] DATA ethertype=0x{:04x} (EAPOL={}) hdr_len={} crypto={} pkt_len={}",
ethertype,
ethertype == ETH_P_PAE,
hdr_len,
crypto_hdr_len,
pkt_len
);
if ethertype == ETH_P_PAE {
process_eapol_frame(bus, mpdu, payload_start, pkt_len);
} else {
build_and_enqueue_eth_frame(
bus,
mpdu,
&addr_info,
ether_type_offset,
payload_start,
pkt_len,
);
}
}
fn process_cmd_rsp(bus: &WifiBus, msg_data: &[u8]) {
if msg_data.len() < 8 {
log::warn!(
"[wifi-rx] process_cmd_rsp: msg_data too short ({})",
msg_data.len()
);
return;
}
let msg_id = u16::from_le_bytes([msg_data[0], msg_data[1]]);
let expected_cfm = bus.cmd.expected_cfm_id.load(Ordering::Acquire);
if expected_cfm != 0 && msg_id == expected_cfm {
log::debug!("[wifi-rx] CFM match: msg_id=0x{:04x} -> rsp_queue", msg_id);
let mut queue = bus.cmd.rsp_queue.lock();
queue.push_back(msg_data.to_vec());
drop(queue);
bus.cmd.rsp_pollset.wake();
} else {
log::debug!(
"[wifi-rx] indication: msg_id=0x{:04x} (expected_cfm=0x{:04x}) -> ind_queue",
msg_id,
expected_cfm
);
let mut queue = bus.tx.ind_queue.lock();
queue.push_back(msg_data.to_vec());
drop(queue);
bus.tx.ind_pollset.wake();
}
}
fn process_print_frame(msg_data: &[u8]) {
if let Ok(s) = core::str::from_utf8(msg_data) {
log::info!("[fw-print] {}", s.trim_end_matches('\0'));
}
}
fn process_cfg_frame(bus: &WifiBus, msg_data: &[u8], cfg_subtype: u8) {
match cfg_subtype {
SDIO_TYPE_CFG_CMD_RSP => {
process_cmd_rsp(bus, msg_data);
}
SDIO_TYPE_CFG_DATA_CFM => {
log::debug!("[wifi-rx] DATA_CFM received, len={}", msg_data.len());
}
SDIO_TYPE_CFG_PRINT => {
process_print_frame(msg_data);
}
_ => {
log::warn!(
"[wifi-rx] unknown frame type=0x{:02x}, len={}",
cfg_subtype,
msg_data.len()
);
}
}
}
fn dispatch_frames(bus: &WifiBus, buf: &[u8]) {
let mut offset = 0;
while offset + 4 <= buf.len() {
let pkt_len = u16::from_le_bytes([buf[offset], buf[offset + 1]]) as usize;
if pkt_len == 0 || pkt_len > MAX_PKT_LEN as usize {
break;
}
let pkt_type = buf[offset + 2] & 0x7F;
let is_cfg = (pkt_type & SDIO_TYPE_CFG) == SDIO_TYPE_CFG;
if !(is_cfg && pkt_type == SDIO_TYPE_CFG_PRINT) {
log::trace!(
"[wifi-rx] frame off={} pkt_len={} type=0x{:02x} is_cfg={}",
offset,
pkt_len,
pkt_type,
is_cfg
);
}
if !is_cfg {
let aggr_len = pkt_len + RX_HWHRD_LEN;
let advance = align_up(aggr_len, RX_ALIGNMENT);
if offset + aggr_len > buf.len() {
log::warn!("[wifi-rx] DATA frame truncated at offset={}", offset);
break;
}
let data_payload = &buf[offset..offset + aggr_len];
process_data_frame(bus, data_payload, pkt_len, 60);
offset += advance;
} else {
let msg_start = offset + 4;
let msg_end = msg_start + pkt_len;
if msg_end > buf.len() {
log::warn!("[wifi-rx] CFG frame truncated at offset={}", offset);
break;
}
let msg_data = &buf[msg_start..msg_end];
let cfg_subtype = pkt_type;
let advance = align_up(pkt_len, RX_ALIGNMENT) + 4;
process_cfg_frame(bus, msg_data, cfg_subtype);
offset += advance;
}
}
}