From cd592cb0550146158ea6f9d90ba8afe9e1b06a92 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 15 Jul 2023 19:15:01 -0500 Subject: [PATCH] wpan: add files from cyw43 --- embassy-stm32-wpan/src/lib.rs | 3 +- embassy-stm32-wpan/src/mac/control.rs | 454 ++++++++++++++++++++++++ embassy-stm32-wpan/src/mac/driver.rs | 102 ++++++ embassy-stm32-wpan/src/mac/mod.rs | 92 +++++ embassy-stm32-wpan/src/mac/runner.rs | 489 ++++++++++++++++++++++++++ 5 files changed, 1139 insertions(+), 1 deletion(-) create mode 100644 embassy-stm32-wpan/src/mac/control.rs create mode 100644 embassy-stm32-wpan/src/mac/driver.rs create mode 100644 embassy-stm32-wpan/src/mac/runner.rs diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs index 57f0dc4fa..6836d7a8b 100644 --- a/embassy-stm32-wpan/src/lib.rs +++ b/embassy-stm32-wpan/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] -#![cfg_attr(feature = "ble", feature(async_fn_in_trait))] +#![cfg_attr(any(feature = "ble", feature = "mac"), feature(async_fn_in_trait))] +#![cfg_attr(feature = "mac", feature(type_alias_impl_trait, concat_bytes))] // This must go FIRST so that all the other modules see its macros. pub mod fmt; diff --git a/embassy-stm32-wpan/src/mac/control.rs b/embassy-stm32-wpan/src/mac/control.rs new file mode 100644 index 000000000..c67614dd6 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/control.rs @@ -0,0 +1,454 @@ +use core::cmp::{max, min}; + +use ch::driver::LinkState; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Timer}; + +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, EventSubscriber, Events}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType}; +use crate::structs::*; +use crate::{countries, events, PowerManagementMode}; + +#[derive(Debug)] +pub struct Error { + pub status: u32, +} + +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + events: &'a Events, + ioctl_state: &'a IoctlState, +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { + Self { + state_ch, + events: event_sub, + ioctl_state, + } + } + + pub async fn init(&mut self, clm: &[u8]) { + const CHUNK_SIZE: usize = 1024; + + debug!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; + } + + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + + debug!("Configuring misc stuff..."); + + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + + // read MAC addr. + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + debug!("mac addr: {:02x}", Bytes(&mac_addr)); + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + // set country takes some time, next ioctls fail if we don't wait. + Timer::after(Duration::from_millis(100)).await; + + // Set antenna to chip antenna + self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + + self.set_iovar_u32("bus:txglom", 0).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + //Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + //Timer::after(Duration::from_millis(100)).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); + + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + Timer::after(Duration::from_millis(100)).await; + + // set wifi up + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + Timer::after(Duration::from_millis(100)).await; + + self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + + Timer::after(Duration::from_millis(100)).await; + + self.state_ch.set_ethernet_address(mac_addr); + + debug!("INIT DONE"); + } + + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(86, 0, mode_num).await; + } + + pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 0).await; // wsec = open + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after(Duration::from_millis(100)).await; + + let mut pfi = PassphraseInfo { + len: passphrase.len() as _, + flags: 1, + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; // WLC_SET_WSEC_PMK + + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) + self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { + self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); + let mut subscriber = self.events.queue.subscriber().unwrap(); + // the actual join operation starts here + // we make sure to enable events before so we don't miss any + + // set_ssid + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; + + // to complete the join, we wait for a SET_SSID event + // we also save the AUTH status for the user, it may be interesting + let mut auth_status = 0; + let status = loop { + let msg = subscriber.next_message_pure().await; + if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { + auth_status = msg.header.status; + } else if msg.header.event_type == Event::SET_SSID { + // join operation ends with SET_SSID event + break msg.header.status; + } + }; + + self.events.mask.disable_all(); + if status == EStatus::SUCCESS { + // successful join + self.state_ch.set_link_state(LinkState::Up); + debug!("JOINED"); + Ok(()) + } else { + warn!("JOIN failed with status={} auth={}", status, auth_status); + Err(Error { status }) + } + } + + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + self.start_ap(ssid, "", Security::OPEN, channel).await; + } + + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; + } + + async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { + if security != Security::OPEN + && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) + { + panic!("Passphrase is too short or too long"); + } + + // Temporarily set wifi down + self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + + // Turn off APSTA mode + self.set_iovar_u32("apsta", 0).await; + + // Set wifi up again + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + // Turn on AP mode + self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + + // Set SSID + let mut i = SsidInfoWithIndex { + index: 0, + ssid_info: SsidInfo { + len: ssid.as_bytes().len() as _, + ssid: [0; 32], + }, + }; + i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); + self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; + + // Set channel number + self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + + // Set security + self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; + + if security != Security::OPEN { + self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK + + Timer::after(Duration::from_millis(100)).await; + + // Set passphrase + let mut pfi = PassphraseInfo { + len: passphrase.as_bytes().len() as _, + flags: 1, // WSEC_PASSPHRASE + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; + } + + // Change mutlicast rate from 1 Mbps to 11 Mbps + self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; + + // Start AP + self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<64>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { + debug!("set {} = {:02x}", name, Bytes(val)); + + let mut buf = [0; BUFSIZE]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + .await; + } + + // TODO this is not really working, it always returns all zeros. + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + debug!("get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = max(name.len() + 1, res.len()); + let res_len = self + .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len + } + + async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + let mut buf = val.to_le_bytes(); + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; + } + + async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + struct CancelOnDrop<'a>(&'a IoctlState); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.cancel_ioctl(); + } + } + + let ioctl = CancelOnDrop(self.ioctl_state); + let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await; + ioctl.defuse(); + + resp_len + } + + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. + pub async fn scan(&mut self) -> Scanner<'_> { + const SCANTYPE_PASSIVE: u8 = 1; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: 0, + ssid: [0; 32], + bssid: [0xff; 6], + bss_type: 2, + scan_type: SCANTYPE_PASSIVE, + nprobes: !0, + active_time: !0, + passive_time: !0, + home_time: !0, + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } +} + +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + /// wait for the next found network + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } +} diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs new file mode 100644 index 000000000..3171d61fe --- /dev/null +++ b/embassy-stm32-wpan/src/mac/driver.rs @@ -0,0 +1,102 @@ +#![no_std] +#![no_main] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] +#![deny(unused_must_use)] + +use core::slice; + +use embassy_net_driver_channel as ch; +use embedded_hal_1::digital::OutputPin; +use events::Events; +use ioctl::IoctlState; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +pub use crate::control::{Control, Error as ControlError}; +pub use crate::runner::Runner; +pub use crate::structs::BssInfo; + +const MTU: usize = 1514; + +pub struct State { + ioctl_state: IoctlState, + ch: ch::State, + events: Events, +} + +impl State { + pub fn new() -> Self { + Self { + ioctl_state: IoctlState::new(), + ch: ch::State::new(), + events: Events::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + // TODO +} + +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +pub async fn new<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + firmware: &[u8], +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); + + runner.init(firmware).await; + + ( + device, + Control::new(state_ch, &state.events, &state.ioctl_state), + runner, + ) +} + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} diff --git a/embassy-stm32-wpan/src/mac/mod.rs b/embassy-stm32-wpan/src/mac/mod.rs index 1af8fe6ba..e27f0ba73 100644 --- a/embassy-stm32-wpan/src/mac/mod.rs +++ b/embassy-stm32-wpan/src/mac/mod.rs @@ -1,9 +1,101 @@ pub mod commands; mod consts; +pub mod control; pub mod event; mod helpers; pub mod indications; mod macros; mod opcodes; pub mod responses; +pub mod runner; pub mod typedefs; + +use core::slice; + +use embassy_net_driver_channel as ch; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +pub use crate::mac::control::{Control, Error as ControlError}; +pub use crate::runner::Runner; +pub use crate::structs::BssInfo; +use crate::sub::mac::Mac; + +const MTU: usize = 1514; + +pub struct State { + ioctl_state: IoctlState, + ch: ch::State, + events: Events, +} + +impl State { + pub fn new() -> Self { + Self { + ioctl_state: IoctlState::new(), + ch: ch::State::new(), + events: Events::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + // TODO +} + +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +pub async fn new<'a, PWR, SPI>( + state: &'a mut State, + mac_subsystem: Mac, + firmware: &[u8], +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) { + let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); + + runner.init(firmware).await; + + ( + device, + Control::new(state_ch, &state.events, &state.ioctl_state), + runner, + ) +} + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} diff --git a/embassy-stm32-wpan/src/mac/runner.rs b/embassy-stm32-wpan/src/mac/runner.rs new file mode 100644 index 000000000..7f0bbd670 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/runner.rs @@ -0,0 +1,489 @@ +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_sync::pubsub::PubSubBehavior; + +use crate::sub::mac::Mac; + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, +} + +#[cfg(feature = "firmware-logs")] +impl Default for LogState { + fn default() -> Self { + Self { + addr: Default::default(), + last_idx: Default::default(), + buf: [0; 256], + buf_count: Default::default(), + } + } +} + +pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, + mac: Mac, + + ioctl_state: &'a IoctlState, + ioctl_id: u16, + sdpcm_seq: u8, + sdpcm_seq_max: u8, + + events: &'a Events, + + #[cfg(feature = "firmware-logs")] + log: LogState, +} + +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new( + ch: ch::Runner<'a, MTU>, + bus: Bus, + ioctl_state: &'a IoctlState, + events: &'a Events, + ) -> Self { + Self { + ch, + bus, + ioctl_state, + ioctl_id: 0, + sdpcm_seq: 0, + sdpcm_seq_max: 1, + events, + #[cfg(feature = "firmware-logs")] + log: LogState::default(), + } + } + + pub(crate) async fn init(&mut self, firmware: &[u8]) { + self.bus.init().await; + + #[cfg(feature = "firmware-logs")] + self.log_init().await; + + debug!("wifi init done"); + } + + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; + debug!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bus.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bus.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bus.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + + pub async fn run(mut self) -> ! { + let mut buf = [0; 512]; + loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + + if self.has_credit() { + let ioctl = self.ioctl_state.wait_pending(); + let tx = self.ch.tx_buf(); + let ev = self.bus.wait_for_event(); + + match select3(ioctl, tx, ev).await { + Either3::First(PendingIoctl { + buf: iobuf, + kind, + cmd, + iface, + }) => { + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; + self.check_status(&mut buf).await; + } + Either3::Second(packet) => { + trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + // There MUST be 2 bytes of padding between the SDPCM and BDC headers. + // And ONLY for data packets! + // No idea why, but the firmware will append two zero bytes to the tx'd packets + // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it + // be oversized and get dropped. + // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 + // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 + // ¯\_(ツ)_/¯ + const PADDING_SIZE: usize = 2; + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bdc_header = BdcHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] + .copy_from_slice(&bdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] + .copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); + self.check_status(&mut buf).await; + } + Either3::Third(()) => { + self.handle_irq(&mut buf).await; + } + } + } else { + warn!("TX stalled"); + self.bus.wait_for_event().await; + self.handle_irq(&mut buf).await; + } + } + } + + /// Wait for IRQ on F2 packet available + async fn handle_irq(&mut self, buf: &mut [u32; 512]) { + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + trace!("irq{}", FormatInterrupt(irq)); + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + self.check_status(buf).await; + } + + if irq & IRQ_DATA_UNAVAILABLE != 0 { + // TODO what should we do here? + warn!("IRQ DATA_UNAVAILABLE, clearing..."); + self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; + } + } + + /// Handle F2 events while status register is set + async fn check_status(&mut self, buf: &mut [u32; 512]) { + loop { + let status = self.bus.status(); + trace!("check status{}", FormatStatus(status)); + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); + self.rx(&mut slice8_mut(buf)[..len as usize]); + } else { + break; + } + } + } + + fn rx(&mut self, packet: &mut [u8]) { + let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { + return; + }; + + self.update_credit(&sdpcm_header); + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + match channel { + CHANNEL_TYPE_CONTROL => { + let Some((cdc_header, response)) = CdcHeader::parse(payload) else { + return; + }; + trace!(" {:?}", cdc_header); + + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {}", cdc_header.status as i32); + } + + self.ioctl_state.ioctl_done(response); + } + } + CHANNEL_TYPE_EVENT => { + let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { + warn!("BDC event, incomplete header"); + return; + }; + + let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { + warn!("BDC event, incomplete data"); + return; + }; + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + Bytes(&event_packet.hdr.oui), + Bytes(BROADCOM_OUI) + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + let evt_type = events::Event::from(event_packet.msg.event_type as u8); + debug!( + "=== EVENT {:?}: {:?} {:02x}", + evt_type, + event_packet.msg, + Bytes(evt_data) + ); + + if self.events.mask.is_enabled(evt_type) { + let status = event_packet.msg.status; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { + return; + }; + let Some(bss_info) = BssInfo::parse(bss_info) else { + return; + }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; + + // this intentionally uses the non-blocking publish immediate + // publish() is a deadlock risk in the current design as awaiting here prevents ioctls + // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event + // (if they are actively awaiting the queue) + self.events.queue.publish_immediate(events::Message::new( + Status { + event_type: evt_type, + status, + }, + event_payload, + )); + } + } + CHANNEL_TYPE_DATA => { + let Some((_, packet)) = BdcHeader::parse(payload) else { + return; + }; + trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), + } + } + _ => {} + } + } + + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; + } + self.sdpcm_seq_max = sdpcm_seq_max; + } + } + + fn has_credit(&self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + + async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: sdpcm_seq, + channel_and_flags: CHANNEL_TYPE_CONTROL, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd, + len: data.len() as _, + flags: kind as u16 | (iface as u16) << 12, + id: self.ioctl_id, + status: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..total_len / 4]).await; + } + + async fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + + // Check it isn't already reset + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + block_for(Duration::from_millis(1)); + + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + } + + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; + + let base = core.base_addr(); + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + + Timer::after(Duration::from_millis(1)).await; + + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + Timer::after(Duration::from_millis(1)).await; + } + + async fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } +}