use crate::sdio::{self, Sdio};
use crate::timer::Timer;
const SDPCM_HEADER_LEN: usize = 12;
const CDC_HEADER_LEN: usize = 16;
const CHANNEL_CONTROL: u8 = 0;
const CHANNEL_EVENT: u8 = 1;
const CHANNEL_DATA: u8 = 2;
const BDC_HEADER_LEN: usize = 4;
const BDC_FLAG_VERSION: u8 = 0x20;
const DATA_FC_BIT: u8 = 1 << 2;
pub const EVENT_SET_SSID: u16 = 0;
pub const EVENT_AUTH: u16 = 3;
pub const EVENT_ASSOC: u16 = 7;
pub const EVENT_LINK: u16 = 16;
pub const EVENT_PSK_SUP: u16 = 46;
pub const EVENT_ESCAN_RESULT: u16 = 69;
pub const EVENT_LINK_UP: u16 = 0x01;
pub const EVENT_SUP_COMPLETED: u32 = 6;
const WLC_UP: u32 = 2;
const WLC_SET_PASSIVE_SCAN: u32 = 49;
const WLC_SET_INFRA: u32 = 20;
const WLC_GET_BSSID: u32 = 23;
const WLC_SET_SSID: u32 = 26;
const WLC_SET_WSEC_PMK: u32 = 268;
const WLC_GET_VAR: u32 = 262;
const WLC_SET_VAR: u32 = 263;
const WSEC_AES: u32 = 4;
const WPA2_AUTH_PSK: u32 = 0x80;
const WSEC_PMK_PASSPHRASE: u16 = 0x0001;
const CLM_HEADER_LEN: usize = 12;
const CLM_FLAG_BASE: u16 = (1 << 12) | 0x0001;
const CLM_FLAG_BEGIN: u16 = 0x0002;
const CLM_FLAG_END: u16 = 0x0004;
const CLM_DOWNLOAD_TYPE: u16 = 2;
const CLM_CHUNK: usize = 1024;
const CDC_FLAG_SET: u16 = 0x02;
const CDC_FLAG_ERROR: u16 = 0x01;
const MAX_IOVAR_REQUEST: usize = 256;
const ESCAN_PARAMS_LEN: usize = 136;
const EVENT_MASK_LEN: usize = 16;
const DISABLED_EVENTS: [usize; 6] = [40, 44, 54, 71, 20, 124];
const SDIO_CORE_INTSTATUS: u32 = 0x20;
const SDIO_CORE_INTMASK: u32 = 0x24;
const INT_FCCHANGE: u32 = 1 << 5;
const INT_FRAME: u32 = 1 << 6;
const INT_MAILBOX: u32 = 1 << 7;
const CCCR_INT_PENDING: u32 = 0x05;
const CCCR_INT_ENABLE: u32 = 0x04;
const CCCR_FBR2_BLOCKSIZE: u32 = 0x210;
const F2_BLOCK_SIZE: u16 = 512;
const FN0: u32 = 0;
const MAX_FRAME: usize = 2048;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
Sdio(sdio::Error),
BadFrame,
CommandFailed(i32),
NoResponse,
NotAssociated,
TxBusy,
FrameTooLong,
}
impl From<sdio::Error> for Error {
fn from(error: sdio::Error) -> Self {
Error::Sdio(error)
}
}
pub struct Wifi {
sdio: Sdio,
tx_seq: u8,
tx_window: u8,
fc_mask: u8,
req_id: u16,
frame: [u8; MAX_FRAME],
rx_draining: bool,
}
impl Wifi {
pub fn new(mut sdio: Sdio, timer: &Timer) -> Result<Self, Error> {
sdio.cmd52_write(FN0, CCCR_FBR2_BLOCKSIZE, F2_BLOCK_SIZE as u8, timer)?;
sdio.cmd52_write(
FN0,
CCCR_FBR2_BLOCKSIZE + 1,
(F2_BLOCK_SIZE >> 8) as u8,
timer,
)?;
let core = sdio.sdio_core_base();
sdio.backplane_write32(
core + SDIO_CORE_INTMASK,
INT_FRAME | INT_MAILBOX | INT_FCCHANGE,
timer,
)?;
sdio.cmd52_write(FN0, CCCR_INT_ENABLE, 0b111, timer)?;
Ok(Self {
sdio,
tx_seq: 0,
tx_window: 0,
fc_mask: 0,
req_id: 0,
frame: [0; MAX_FRAME],
rx_draining: false,
})
}
pub fn load_clm(&mut self, clm: &[u8], timer: &Timer) -> Result<(), Error> {
let mut request = [0u8; 8 + CLM_HEADER_LEN + CLM_CHUNK];
request[..8].copy_from_slice(b"clmload\0");
let mut offset = 0;
while offset < clm.len() {
let end = (offset + CLM_CHUNK).min(clm.len());
let chunk = &clm[offset..end];
let mut flag = CLM_FLAG_BASE;
if offset == 0 {
flag |= CLM_FLAG_BEGIN;
}
if end == clm.len() {
flag |= CLM_FLAG_END;
}
request[8..10].copy_from_slice(&flag.to_le_bytes());
request[10..12].copy_from_slice(&CLM_DOWNLOAD_TYPE.to_le_bytes());
request[12..16].copy_from_slice(&(chunk.len() as u32).to_le_bytes());
request[16..20].copy_from_slice(&0u32.to_le_bytes()); request[20..20 + chunk.len()].copy_from_slice(chunk);
self.command(
WLC_SET_VAR,
true,
&request[..20 + chunk.len()],
&mut [],
timer,
)?;
offset = end;
}
Ok(())
}
pub fn get_iovar(&mut self, name: &str, out: &mut [u8], timer: &Timer) -> Result<usize, Error> {
let mut request = [0u8; MAX_IOVAR_REQUEST];
let len = iovar_request(name, &[], &mut request);
self.command(WLC_GET_VAR, false, &request[..len], out, timer)
}
pub fn set_iovar(&mut self, name: &str, value: &[u8], timer: &Timer) -> Result<(), Error> {
let mut request = [0u8; MAX_IOVAR_REQUEST];
let len = iovar_request(name, value, &mut request);
self.command(WLC_SET_VAR, true, &request[..len], &mut [], timer)?;
Ok(())
}
pub fn set_iovar_u32(&mut self, name: &str, value: u32, timer: &Timer) -> Result<(), Error> {
self.set_iovar(name, &value.to_le_bytes(), timer)
}
pub fn ioctl_get(&mut self, cmd: u32, out: &mut [u8], timer: &Timer) -> Result<usize, Error> {
self.command(cmd, false, &[], out, timer)
}
pub fn ioctl_set(&mut self, cmd: u32, value: &[u8], timer: &Timer) -> Result<(), Error> {
self.command(cmd, true, value, &mut [], timer)?;
Ok(())
}
pub fn ioctl_set_u32(&mut self, cmd: u32, value: u32, timer: &Timer) -> Result<(), Error> {
self.ioctl_set(cmd, &value.to_le_bytes(), timer)
}
fn enable_events(&mut self, timer: &Timer) -> Result<(), Error> {
let mut event_mask = [0xffu8; EVENT_MASK_LEN];
for event in DISABLED_EVENTS {
event_mask[event / 8] &= !(1u8 << (event % 8));
}
self.set_iovar("event_msgs", &event_mask, timer)
}
pub fn scan(
&mut self,
timer: &Timer,
mut on_result: impl FnMut(&ScanResult),
) -> Result<(), Error> {
self.enable_events(timer)?;
self.ioctl_set_u32(WLC_UP, 0, timer)?;
self.ioctl_set_u32(WLC_SET_PASSIVE_SCAN, 0, timer)?;
let mut params = [0u8; ESCAN_PARAMS_LEN];
fill_escan_params(&mut params);
self.set_iovar("escan", ¶ms, timer)?;
let start = timer.now_micros();
loop {
if timer.now_micros() - start > 8_000_000 {
return Ok(());
}
let Some(frame) = self.recv_frame(timer)? else {
timer.delay_ms(10);
continue;
};
if frame.channel != CHANNEL_EVENT {
continue;
}
let bdc = 4 + ((self.frame[frame.data_offset + 3] as usize) << 2);
let msg = frame.data_offset + bdc + 14 + 10;
if msg + 8 > frame.frame_len {
continue;
}
let event_type = u16::from_be_bytes([self.frame[msg + 6], self.frame[msg + 7]]);
if event_type != EVENT_ESCAN_RESULT {
continue;
}
let data = msg + 48;
if data + 12 > frame.frame_len {
continue;
}
let bss_count = u16::from_le_bytes([self.frame[data + 10], self.frame[data + 11]]);
if bss_count == 0 {
return Ok(());
}
if let Some(result) = self.parse_bss_info(data + 12, frame.frame_len) {
on_result(&result);
}
}
}
fn parse_bss_info(&self, bss: usize, frame_len: usize) -> Option<ScanResult> {
if bss + 80 > frame_len {
return None;
}
let mut result = ScanResult {
bssid: [0; 6],
ssid: [0; 32],
ssid_len: 0,
channel: 0,
rssi: 0,
};
result.bssid.copy_from_slice(&self.frame[bss + 8..bss + 14]);
let ssid_len = (self.frame[bss + 18] as usize).min(32);
result.ssid_len = ssid_len;
result.ssid[..ssid_len].copy_from_slice(&self.frame[bss + 19..bss + 19 + ssid_len]);
result.channel = self.frame[bss + 72];
result.rssi = i16::from_le_bytes([self.frame[bss + 78], self.frame[bss + 79]]);
Some(result)
}
pub fn join_wpa2(
&mut self,
ssid: &str,
passphrase: &str,
timer: &Timer,
) -> Result<[u8; 6], Error> {
self.start_join(ssid, passphrase, timer)?;
let start = timer.now_micros();
loop {
let mut bssid = [0u8; 6];
if self.ioctl_get(WLC_GET_BSSID, &mut bssid, timer).is_ok()
&& bssid.iter().any(|&b| b != 0)
{
return Ok(bssid);
}
if timer.now_micros() - start > 15_000_000 {
return Err(Error::NotAssociated);
}
timer.delay_ms(100);
}
}
pub fn start_join(&mut self, ssid: &str, passphrase: &str, timer: &Timer) -> Result<(), Error> {
self.enable_events(timer)?;
self.ioctl_set_u32(WLC_UP, 0, timer)?;
self.ioctl_set_u32(WLC_SET_INFRA, 1, timer)?;
self.set_iovar_u32("wsec", WSEC_AES, timer)?;
self.set_iovar_u32("wpa_auth", WPA2_AUTH_PSK, timer)?;
self.set_iovar_u32("sup_wpa", 1, timer)?;
let mut pmk = [0u8; 4 + 256];
pmk[0..2].copy_from_slice(&(passphrase.len() as u16).to_le_bytes());
pmk[2..4].copy_from_slice(&WSEC_PMK_PASSPHRASE.to_le_bytes());
pmk[4..4 + passphrase.len()].copy_from_slice(passphrase.as_bytes());
self.ioctl_set(WLC_SET_WSEC_PMK, &pmk, timer)?;
let mut ssid_param = [0u8; 4 + 32];
ssid_param[0..4].copy_from_slice(&(ssid.len() as u32).to_le_bytes());
ssid_param[4..4 + ssid.len()].copy_from_slice(ssid.as_bytes());
self.ioctl_set(WLC_SET_SSID, &ssid_param, timer)?;
Ok(())
}
pub fn poll_event(&mut self, timer: &Timer) -> Result<Option<Event>, Error> {
let Some(frame) = self.recv_frame(timer)? else {
return Ok(None);
};
if frame.channel != CHANNEL_EVENT {
return Ok(None);
}
let bdc = 4 + ((self.frame[frame.data_offset + 3] as usize) << 2);
let msg = frame.data_offset + bdc + 14 + 10;
if msg + 12 > frame.frame_len {
return Ok(None);
}
Ok(Some(Event {
flags: u16::from_be_bytes([self.frame[msg + 2], self.frame[msg + 3]]),
event_type: u16::from_be_bytes([self.frame[msg + 6], self.frame[msg + 7]]),
status: u32::from_be_bytes([
self.frame[msg + 8],
self.frame[msg + 9],
self.frame[msg + 10],
self.frame[msg + 11],
]),
}))
}
pub const MTU: usize = 1514;
pub fn send_ethernet(&mut self, frame: &[u8], timer: &Timer) -> Result<(), Error> {
if frame.len() > Self::MTU {
return Err(Error::FrameTooLong);
}
if self.tx_seq == self.tx_window || self.fc_mask & DATA_FC_BIT != 0 {
return Err(Error::TxBusy);
}
let total = SDPCM_HEADER_LEN + BDC_HEADER_LEN + frame.len();
let padded = total.next_multiple_of(4);
self.frame[..padded].fill(0);
self.frame[0..2].copy_from_slice(&(total as u16).to_le_bytes());
self.frame[2..4].copy_from_slice(&(!(total as u16)).to_le_bytes());
self.frame[4] = self.tx_seq;
self.frame[5] = CHANNEL_DATA;
self.frame[7] = SDPCM_HEADER_LEN as u8;
self.frame[SDPCM_HEADER_LEN] = BDC_FLAG_VERSION;
let data_at = SDPCM_HEADER_LEN + BDC_HEADER_LEN;
self.frame[data_at..data_at + frame.len()].copy_from_slice(frame);
self.sdio.f2_write(&self.frame[..padded], timer)?;
self.tx_seq = self.tx_seq.wrapping_add(1);
Ok(())
}
pub fn recv_ethernet(&mut self, out: &mut [u8], timer: &Timer) -> Result<Option<usize>, Error> {
let Some(frame) = self.recv_frame(timer)? else {
return Ok(None);
};
if frame.channel != CHANNEL_DATA {
return Ok(None);
}
let bdc = BDC_HEADER_LEN + ((self.frame[frame.data_offset + 3] as usize) << 2);
let start = frame.data_offset + bdc;
if start >= frame.frame_len {
return Ok(None);
}
let len = (frame.frame_len - start).min(out.len());
out[..len].copy_from_slice(&self.frame[start..start + len]);
Ok(Some(len))
}
fn command(
&mut self,
cmd: u32,
set: bool,
request: &[u8],
reply: &mut [u8],
timer: &Timer,
) -> Result<usize, Error> {
let payload_len = if set {
request.len()
} else {
request.len().max(reply.len())
};
let total = SDPCM_HEADER_LEN + CDC_HEADER_LEN + payload_len;
let padded = total.next_multiple_of(4);
if padded > MAX_FRAME {
return Err(Error::NoResponse);
}
self.req_id = self.req_id.wrapping_add(1);
let seq = self.tx_seq;
let req_id = self.req_id;
self.frame[..padded].fill(0);
self.frame[0..2].copy_from_slice(&(total as u16).to_le_bytes());
self.frame[2..4].copy_from_slice(&(!(total as u16)).to_le_bytes());
self.frame[4] = seq;
self.frame[5] = CHANNEL_CONTROL;
self.frame[7] = SDPCM_HEADER_LEN as u8;
self.frame[12..16].copy_from_slice(&cmd.to_le_bytes());
self.frame[16..20].copy_from_slice(&(payload_len as u32).to_le_bytes());
self.frame[20..22].copy_from_slice(&(if set { CDC_FLAG_SET } else { 0 }).to_le_bytes());
self.frame[22..24].copy_from_slice(&req_id.to_le_bytes());
let payload_at = SDPCM_HEADER_LEN + CDC_HEADER_LEN;
self.frame[payload_at..payload_at + request.len()].copy_from_slice(request);
self.sdio.f2_write(&self.frame[..padded], timer)?;
self.tx_seq = self.tx_seq.wrapping_add(1);
self.await_response(req_id, reply, timer)
}
fn await_response(
&mut self,
req_id: u16,
out: &mut [u8],
timer: &Timer,
) -> Result<usize, Error> {
let start = timer.now_micros();
loop {
if timer.now_micros() - start > 2_000_000 {
return Err(Error::NoResponse);
}
let Some(frame) = self.recv_frame(timer)? else {
timer.delay_ms(1);
continue;
};
if frame.channel != CHANNEL_CONTROL {
continue;
}
let cdc = frame.data_offset;
let id = u16::from_le_bytes([self.frame[cdc + 10], self.frame[cdc + 11]]);
if id != req_id {
continue;
}
let flags = u16::from_le_bytes([self.frame[cdc + 8], self.frame[cdc + 9]]);
let status = i32::from_le_bytes([
self.frame[cdc + 12],
self.frame[cdc + 13],
self.frame[cdc + 14],
self.frame[cdc + 15],
]);
if flags & CDC_FLAG_ERROR != 0 || status != 0 {
return Err(Error::CommandFailed(status));
}
let value_len = u32::from_le_bytes([
self.frame[cdc + 4],
self.frame[cdc + 5],
self.frame[cdc + 6],
self.frame[cdc + 7],
]) as usize;
let value_at = cdc + CDC_HEADER_LEN;
let available = frame.frame_len.saturating_sub(value_at);
let n = value_len.min(out.len()).min(available);
out[..n].copy_from_slice(&self.frame[value_at..value_at + n]);
return Ok(n);
}
}
fn frame_ready(&mut self, timer: &Timer) -> Result<bool, Error> {
if self.sdio.cmd52_read(FN0, CCCR_INT_PENDING, timer)? == 0 {
return Ok(false);
}
let core = self.sdio.sdio_core_base();
let status = self
.sdio
.backplane_read32(core + SDIO_CORE_INTSTATUS, timer)?;
self.sdio
.backplane_write32(core + SDIO_CORE_INTSTATUS, status, timer)?;
Ok(status & INT_FRAME != 0)
}
fn read_frame(&mut self, timer: &Timer) -> Result<Option<FrameInfo>, Error> {
self.sdio
.f2_read(&mut self.frame[..SDPCM_HEADER_LEN], timer)?;
let len = u16::from_le_bytes([self.frame[0], self.frame[1]]) as usize;
if len == 0 {
return Ok(None);
}
let len_check = u16::from_le_bytes([self.frame[2], self.frame[3]]);
if len_check != !(len as u16) || !(SDPCM_HEADER_LEN..=MAX_FRAME).contains(&len) {
return Err(Error::BadFrame);
}
let window = self.frame[9];
self.tx_window = if window.wrapping_sub(self.tx_seq) > 0x40 {
self.tx_seq.wrapping_add(2)
} else {
window
};
self.fc_mask = self.frame[8];
let channel = self.frame[5] & 0x0f;
let data_offset = self.frame[7] as usize;
if len > SDPCM_HEADER_LEN {
let rest = (len - SDPCM_HEADER_LEN).next_multiple_of(4);
self.sdio.f2_read(
&mut self.frame[SDPCM_HEADER_LEN..SDPCM_HEADER_LEN + rest],
timer,
)?;
}
Ok(Some(FrameInfo {
channel,
data_offset,
frame_len: len,
}))
}
fn recv_frame(&mut self, timer: &Timer) -> Result<Option<FrameInfo>, Error> {
if !self.rx_draining {
if !self.frame_ready(timer)? {
return Ok(None);
}
self.rx_draining = true;
}
match self.read_frame(timer)? {
Some(frame) => Ok(Some(frame)),
None => {
self.rx_draining = false;
Ok(None)
}
}
}
pub fn clear_interrupts(&mut self, timer: &Timer) -> Result<(), Error> {
let core = self.sdio.sdio_core_base();
let status = self
.sdio
.backplane_read32(core + SDIO_CORE_INTSTATUS, timer)?;
if status != 0 {
self.sdio
.backplane_write32(core + SDIO_CORE_INTSTATUS, status, timer)?;
}
Ok(())
}
pub fn free(self) -> Sdio {
self.sdio
}
}
fn iovar_request(name: &str, value: &[u8], out: &mut [u8]) -> usize {
let n = name.len();
out[..n].copy_from_slice(name.as_bytes());
out[n] = 0;
out[n + 1..n + 1 + value.len()].copy_from_slice(value);
n + 1 + value.len()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Event {
pub event_type: u16,
pub status: u32,
pub flags: u16,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ScanResult {
pub bssid: [u8; 6],
pub ssid: [u8; 32],
pub ssid_len: usize,
pub channel: u8,
pub rssi: i16,
}
impl ScanResult {
pub fn ssid(&self) -> &[u8] {
&self.ssid[..self.ssid_len]
}
}
fn fill_escan_params(out: &mut [u8]) {
out[..ESCAN_PARAMS_LEN].fill(0);
out[0..4].copy_from_slice(&1u32.to_le_bytes()); out[4..6].copy_from_slice(&1u16.to_le_bytes()); out[6..8].copy_from_slice(&0x1234u16.to_le_bytes()); out[44..50].fill(0xff); out[50] = 2; out[51] = 0; out[52..68].fill(0xff); out[68..70].copy_from_slice(&14u16.to_le_bytes()); out[70..72].copy_from_slice(&1u16.to_le_bytes()); let chanspecs: [u16; 14] = [
0x2b01, 0x2b02, 0x2b03, 0x2b04, 0x2e05, 0x2e06, 0x2e07, 0x2b08, 0x2b09, 0x2b0a, 0x2b0b,
0x2b0c, 0x2b0d, 0x2b0e,
];
for (i, chanspec) in chanspecs.iter().enumerate() {
out[72 + i * 2..74 + i * 2].copy_from_slice(&chanspec.to_le_bytes());
}
}
struct FrameInfo {
channel: u8,
data_offset: usize,
frame_len: usize,
}
#[cfg(feature = "smoltcp")]
use smoltcp::phy::{Device as PhyDevice, DeviceCapabilities, Medium, RxToken, TxToken};
#[cfg(feature = "smoltcp")]
use smoltcp::time::Instant;
#[cfg(feature = "smoltcp")]
pub struct WifiPhy<'a> {
wifi: Wifi,
timer: &'a Timer,
tx_scratch: [u8; Wifi::MTU],
}
#[cfg(feature = "smoltcp")]
impl<'a> WifiPhy<'a> {
pub fn new(wifi: Wifi, timer: &'a Timer) -> Self {
Self {
wifi,
timer,
tx_scratch: [0; Wifi::MTU],
}
}
pub fn free(self) -> Wifi {
self.wifi
}
}
#[cfg(feature = "smoltcp")]
impl PhyDevice for WifiPhy<'_> {
type RxToken<'t>
= WifiRxToken
where
Self: 't;
type TxToken<'t>
= WifiTxToken<'t>
where
Self: 't;
fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let mut rx = WifiRxToken {
buffer: [0; Wifi::MTU],
len: 0,
};
match self.wifi.recv_ethernet(&mut rx.buffer, self.timer) {
Ok(Some(len)) => rx.len = len,
Ok(None) => return None,
Err(_) => return None,
}
let tx = WifiTxToken {
wifi: &mut self.wifi,
timer: self.timer,
scratch: &mut self.tx_scratch,
};
Some((rx, tx))
}
fn transmit(&mut self, _timestamp: Instant) -> Option<Self::TxToken<'_>> {
Some(WifiTxToken {
wifi: &mut self.wifi,
timer: self.timer,
scratch: &mut self.tx_scratch,
})
}
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.medium = Medium::Ethernet;
caps.max_transmission_unit = Wifi::MTU;
caps.max_burst_size = Some(1);
caps
}
}
#[cfg(feature = "smoltcp")]
pub struct WifiRxToken {
buffer: [u8; Wifi::MTU],
len: usize,
}
#[cfg(feature = "smoltcp")]
impl RxToken for WifiRxToken {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
f(&self.buffer[..self.len])
}
}
#[cfg(feature = "smoltcp")]
pub struct WifiTxToken<'a> {
wifi: &'a mut Wifi,
timer: &'a Timer,
scratch: &'a mut [u8],
}
#[cfg(feature = "smoltcp")]
impl TxToken for WifiTxToken<'_> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let result = f(&mut self.scratch[..len]);
let _ = self.wifi.send_ethernet(&self.scratch[..len], self.timer);
result
}
}