use alloc::{sync::Arc, vec, vec::Vec};
use core::{sync::atomic::Ordering, task::Poll};
use crate::{
common::SDIO_TYPE_CFG_CMD_RSP,
fdrv::{
consts::*,
core::bus::{BusState, WifiBus},
protocol::lmac_msg::*,
},
runtime::runtime,
};
pub(crate) fn current_time_ms() -> u64 {
crate::runtime::runtime().now_nanos() / 1_000_000
}
fn align_up(val: usize, align: usize) -> usize {
(val + align - 1) & !(align - 1)
}
fn build_cmd_frame(msg_id: u16, dest_task: u16, param: &[u8], is_v3: bool) -> Vec<u8> {
let lmac_len = 8 + param.len();
let raw_len = 4 + DUMMY_WORD_LEN + lmac_len;
let sdio_len = raw_len;
let aligned_len = align_up(raw_len, TX_ALIGNMENT);
let final_len = if !aligned_len.is_multiple_of(SDIOWIFI_FUNC_BLOCKSIZE) {
align_up(aligned_len + TAIL_LEN, SDIOWIFI_FUNC_BLOCKSIZE)
} else {
aligned_len
};
let mut buf = vec![0u8; final_len];
buf[0] = (sdio_len & 0xFF) as u8;
buf[1] = ((sdio_len >> 8) & 0x0F) as u8;
buf[2] = SDIO_TYPE_CFG_CMD_RSP;
buf[3] = if is_v3 {
crate::common::crc8_ponl_107(&buf[0..3])
} else {
0x00
};
let msg_offset = 4 + DUMMY_WORD_LEN;
buf[msg_offset..msg_offset + 2].copy_from_slice(&msg_id.to_le_bytes());
buf[msg_offset + 2..msg_offset + 4].copy_from_slice(&dest_task.to_le_bytes());
buf[msg_offset + 4..msg_offset + 6].copy_from_slice(&DRV_TASK_ID.to_le_bytes());
buf[msg_offset + 6..msg_offset + 8].copy_from_slice(&(param.len() as u16).to_le_bytes());
if !param.is_empty() {
buf[msg_offset + 8..msg_offset + 8 + param.len()].copy_from_slice(param);
}
buf
}
fn prepare_cmd_send(bus: &Arc<WifiBus>, expected_cfm_id: u16) {
{
let mut queue = bus.cmd.rsp_queue.lock();
if !queue.is_empty() {
log::warn!("[cmd_mgr] discarding {} stale CMD responses", queue.len());
queue.clear();
}
}
bus.cmd.rsp_error.store(false, Ordering::Release);
bus.cmd
.expected_cfm_id
.store(expected_cfm_id, Ordering::Release);
}
fn send_cmd_via_tx_thread(bus: &Arc<WifiBus>, frame: Vec<u8>) {
let mut cmd_slot = bus.cmd.pending.lock();
*cmd_slot = Some(frame);
bus.cmd.pending_flag.store(true, Ordering::Release);
drop(cmd_slot);
bus.tx.wake_pollset.wake();
}
fn validate_and_extract_cfm_param(rsp: &[u8], _expected_cfm_id: u16) -> Result<Vec<u8>, ()> {
if rsp.len() < 12 {
return Err(());
}
let msg = LmacMsg::from_le_bytes(rsp);
log::debug!(
"[cmd_mgr] CFM received: msg_id=0x{:04x}, param_len={}",
msg.id,
msg.param_len
);
let param_start = 12;
let param_end = param_start + msg.param_len as usize;
if rsp.len() >= param_end {
Ok(rsp[param_start..param_end].to_vec())
} else {
Ok(rsp[param_start..].to_vec())
}
}
fn try_get_cfm_from_queue(
bus: &Arc<WifiBus>,
expected_cfm_id: u16,
) -> Option<Result<Vec<u8>, CmdError>> {
let mut queue = bus.cmd.rsp_queue.lock();
let mut redirected = Vec::new();
while let Some(rsp) = queue.pop_front() {
if rsp.len() < LmacMsg::SIZE {
continue;
}
let msg = LmacMsg::from_le_bytes(&rsp);
if msg.id == expected_cfm_id {
let _ = redirected;
drop(queue);
if !redirected.is_empty() {
let mut ind = bus.tx.ind_queue.lock();
for r in redirected {
ind.push_back(r);
}
bus.tx.ind_pollset.wake();
}
return Some(Ok(rsp[LmacMsg::SIZE..].to_vec()));
} else {
redirected.push(rsp);
}
}
if !redirected.is_empty() {
let mut ind = bus.tx.ind_queue.lock();
for r in redirected {
ind.push_back(r);
}
bus.tx.ind_pollset.wake();
}
None
}
fn poll_cfm_response(
bus: &Arc<WifiBus>,
expected_cfm_id: u16,
out: &mut Option<Result<Vec<u8>, CmdError>>,
cx: &mut core::task::Context<'_>,
) -> Poll<()> {
if bus.cmd.rsp_error.load(Ordering::Acquire) || *bus.state.lock() == BusState::Down {
*out = Some(Err(CmdError::BusDown));
return Poll::Ready(());
}
if let Some(result) = try_get_cfm_from_queue(bus, expected_cfm_id) {
*out = Some(result);
return Poll::Ready(());
}
bus.cmd.rsp_pollset.register(cx.waker());
if let Some(result) = try_get_cfm_from_queue(bus, expected_cfm_id) {
*out = Some(result);
return Poll::Ready(());
}
Poll::Pending
}
pub fn send_cmd(
bus: &Arc<WifiBus>,
msg_id: u16,
dest_id: u16,
param: &[u8],
timeout_ms: u64,
) -> Result<Vec<u8>, CmdError> {
send_cmd_with_cfm_id(bus, msg_id, dest_id, param, msg_id + 1, timeout_ms)
}
pub fn send_cmd_with_cfm_id(
bus: &Arc<WifiBus>,
msg_id: u16,
dest_id: u16,
param: &[u8],
expected_cfm_id: u16,
timeout_ms: u64,
) -> Result<Vec<u8>, CmdError> {
if *bus.state.lock() == BusState::Down {
return Err(CmdError::BusDown);
}
let tout = if timeout_ms == 0 {
CMD_TX_TIMEOUT_DEFAULT_MS
} else {
timeout_ms
};
let frame = build_cmd_frame(msg_id, dest_id, param, bus.transport.is_v3());
prepare_cmd_send(bus, expected_cfm_id);
send_cmd_via_tx_thread(bus, frame);
let mut cfm: Option<Result<Vec<u8>, CmdError>> = None;
let result = match runtime().block_until(Some(tout), &mut |cx| {
poll_cfm_response(bus, expected_cfm_id, &mut cfm, cx)
}) {
Ok(()) => cfm.unwrap_or(Err(CmdError::Timeout)),
Err(_) => {
log::error!(
"[cmd_mgr] TIMEOUT waiting for cfm 0x{:04x} ({}ms)",
expected_cfm_id,
tout
);
Err(CmdError::Timeout)
}
};
bus.cmd.expected_cfm_id.store(0, Ordering::Release);
if let Err(e) = &result {
log::error!("[cmd_mgr] send_cmd 0x{:04x} error: {:?}", msg_id, e);
}
result
}
pub fn send_cmd_no_cfm(
bus: &Arc<WifiBus>,
msg_id: u16,
dest_id: u16,
param: &[u8],
) -> Result<(), CmdError> {
if *bus.state.lock() == BusState::Down {
return Err(CmdError::BusDown);
}
let frame = build_cmd_frame(msg_id, dest_id, param, bus.transport.is_v3());
{
let mut cmd_slot = bus.cmd.pending.lock();
*cmd_slot = Some(frame);
bus.cmd.pending_flag.store(true, Ordering::Release);
}
bus.tx.wake_pollset.wake();
Ok(())
}
fn poll_eapol(
bus: &Arc<WifiBus>,
out: &mut Option<Vec<u8>>,
cx: &mut core::task::Context<'_>,
) -> Poll<()> {
{
let mut queue = bus.rx.eapol_queue.lock();
if let Some(eapol) = queue.pop_front() {
queue.clear();
*out = Some(eapol);
return Poll::Ready(());
}
}
bus.rx.eapol_pollset.register(cx.waker());
{
let mut queue = bus.rx.eapol_queue.lock();
if let Some(eapol) = queue.pop_front() {
queue.clear();
*out = Some(eapol);
return Poll::Ready(());
}
}
Poll::Pending
}
pub fn wait_for_eapol(bus: &Arc<WifiBus>, timeout_ms: u64) -> Result<Vec<u8>, CmdError> {
let mut eapol: Option<Vec<u8>> = None;
match runtime().block_until(Some(timeout_ms), &mut |cx| poll_eapol(bus, &mut eapol, cx)) {
Ok(()) => match eapol {
Some(e) => Ok(e),
None => {
log::error!("[cmd_mgr] EAPOL wait returned empty");
Err(CmdError::Timeout)
}
},
Err(_) => {
log::error!("[cmd_mgr] EAPOL wait timed out");
Err(CmdError::Timeout)
}
}
}
struct EapolFrameLayout {
final_len: usize,
sdio_hdr_len: usize,
payload_len: usize,
}
fn calculate_eapol_frame_layout(eapol_len: usize) -> EapolFrameLayout {
const SDIO_HEADER_LEN: usize = 4;
const HOSTDESC_SIZE: usize = 28;
let payload_len = eapol_len;
let sdio_payload_len = HOSTDESC_SIZE + payload_len;
let sdio_hdr_len = SDIO_HEADER_LEN + sdio_payload_len;
let aligned = align_up(sdio_hdr_len, TX_ALIGNMENT);
let final_len = if !aligned.is_multiple_of(SDIOWIFI_FUNC_BLOCKSIZE) {
let with_tail = aligned + 4;
((with_tail / SDIOWIFI_FUNC_BLOCKSIZE) + 1) * SDIOWIFI_FUNC_BLOCKSIZE
} else {
aligned
};
EapolFrameLayout {
final_len,
sdio_hdr_len,
payload_len,
}
}
fn fill_sdio_header_for_eapol(buf: &mut [u8], layout: &EapolFrameLayout, is_v3: bool) {
buf[0] = (layout.sdio_hdr_len & 0xFF) as u8;
buf[1] = ((layout.sdio_hdr_len >> 8) & 0x0F) as u8;
buf[2] = 0x01; buf[3] = if is_v3 {
crate::common::crc8_ponl_107(&buf[0..3])
} else {
0x00
};
}
fn fill_hostdesc_for_eapol(
hd: &mut [u8],
payload_len: usize,
dst_mac: &[u8; 6],
src_mac: &[u8; 6],
vif_idx: u8,
sta_idx: u8,
) {
hd[0..2].copy_from_slice(&(payload_len as u16).to_le_bytes());
hd[4..8].copy_from_slice(&0x8000_0001u32.to_le_bytes());
hd[8..14].copy_from_slice(dst_mac);
hd[14..20].copy_from_slice(src_mac);
hd[20..22].copy_from_slice(&0x888Eu16.to_be_bytes());
hd[24] = vif_idx;
hd[25] = sta_idx;
}
fn build_eapol_frame_buffer(
dst_mac: &[u8; 6],
src_mac: &[u8; 6],
eapol: &[u8],
vif_idx: u8,
sta_idx: u8,
is_v3: bool,
) -> Vec<u8> {
const SDIO_HEADER_LEN: usize = 4;
const HOSTDESC_SIZE: usize = 28;
let layout = calculate_eapol_frame_layout(eapol.len());
let mut buf = vec![0u8; layout.final_len];
fill_sdio_header_for_eapol(&mut buf, &layout, is_v3);
fill_hostdesc_for_eapol(
&mut buf[SDIO_HEADER_LEN..SDIO_HEADER_LEN + HOSTDESC_SIZE],
layout.payload_len,
dst_mac,
src_mac,
vif_idx,
sta_idx,
);
let eth_start = SDIO_HEADER_LEN + HOSTDESC_SIZE;
buf[eth_start..eth_start + eapol.len()].copy_from_slice(eapol);
buf
}
fn perform_eapol_flow_control(bus: &WifiBus) -> Result<(), CmdError> {
if bus.transport.wait_flow_ctrl(50) {
Ok(())
} else {
log::error!("[cmd_mgr] EAPOL TX flow_ctrl timeout");
Err(CmdError::Timeout)
}
}
fn send_eapol_frame_to_sdio(bus: &Arc<WifiBus>, buf: &[u8]) -> Result<(), CmdError> {
let transport = &bus.transport;
transport.mask_card_irq();
if let Err(e) = perform_eapol_flow_control(bus) {
transport.unmask_card_irq();
return Err(e);
}
if let Err(e) = transport.write_fifo(1, transport.wr_fifo_addr(), buf) {
log::error!("[cmd_mgr] EAPOL TX write_fifo failed: {:?}", e);
transport.unmask_card_irq();
return Err(CmdError::SdioError);
}
bus.rx.irq_waker.wake();
transport.unmask_card_irq();
Ok(())
}
pub fn send_eapol_data_frame(
bus: &Arc<WifiBus>,
dst_mac: &[u8; 6],
src_mac: &[u8; 6],
eapol: &[u8],
vif_idx: u8,
sta_idx: u8,
) -> Result<(), CmdError> {
let buf = build_eapol_frame_buffer(
dst_mac,
src_mac,
eapol,
vif_idx,
sta_idx,
bus.transport.is_v3(),
);
send_eapol_frame_to_sdio(bus, &buf)
}