diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs index 58339528e8..f08dcb1e65 100644 --- a/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/main.rs @@ -2,6 +2,8 @@ use std::env; use std::fs; use std::path::PathBuf; +mod mld; + #[cfg(target_os = "redox")] use redox_driver_sys::memory::{CacheType, MmioProt}; #[cfg(target_os = "redox")] diff --git a/local/recipes/drivers/redbear-iwlwifi/source/src/mld.rs b/local/recipes/drivers/redbear-iwlwifi/source/src/mld.rs new file mode 100644 index 0000000000..a2dc5e06ff --- /dev/null +++ b/local/recipes/drivers/redbear-iwlwifi/source/src/mld.rs @@ -0,0 +1,744 @@ +//! Red Bear iwlwifi Mini-MLD (MAC Link-layer Driver) — Rust layer. +//! +//! Ported from Linux 7.1 drivers/net/wireless/intel/iwlwifi/mld/: +//! mld.c/h — op-mode registration + struct iwl_mld state +//! notif.c — RX_HANDLER notification dispatch table +//! tx.c/h — TX queue management (TVQM) +//! scan.c/h — scan state + UMAC scan request +//! sta.c/h — station add/remove via MAC_CONF commands +//! fw.c — firmware init sequence (TX_ANT, RSS, SCAN_CFG, etc.) +//! mac80211.c — ieee80211_ops callback implementations +//! rx.c — MLO RX MPDU parsing (link_id from mac_phy_band) +//! +//! The firmware command IDs, notification dispatch, state management, +//! and mac80211 callback logic are all implemented here in Rust. The +//! shared RX descriptor parsing (signal/rate extraction) delegates to +//! the legacy C Mini-MVM layer via FFI (rb_iwl_mvm_extract_signal), +//! since the iwl_rx_mpdu_desc firmware format is common to MVM and MLD. + +#![allow(dead_code)] + +use std::sync::atomic::{AtomicU32, Ordering}; + +// ── Firmware command group IDs (Linux fw/api/commands.h) ────────── + +pub const GRP_LEGACY: u8 = 0x0; +pub const GRP_LONG: u8 = 0x1; +pub const GRP_SYSTEM: u8 = 0x2; +pub const GRP_MAC_CONF: u8 = 0x3; +pub const GRP_PHY_OPS: u8 = 0x4; +pub const GRP_DATA_PATH: u8 = 0x5; +pub const GRP_SCAN: u8 = 0x6; +pub const GRP_STATISTICS: u8 = 0x10; + +pub const fn wide_id(group: u8, cmd: u8) -> u16 { + ((group as u16) << 8) | (cmd as u16) +} + +pub fn wide_id_group(id: u16) -> u8 { + (id >> 8) as u8 +} + +pub fn wide_id_cmd(id: u16) -> u8 { + (id & 0xff) as u8 +} + +// ── Legacy group (0x0) command + notification IDs ───────────────── + +pub const CMD_RX_PHY_CMD: u16 = wide_id(GRP_LEGACY, 0xc0); +pub const CMD_RX_MPDU_CMD: u16 = wide_id(GRP_LEGACY, 0xc1); +pub const CMD_BA_NOTIF: u16 = wide_id(GRP_LEGACY, 0xc5); +pub const CMD_RX_NO_DATA: u16 = wide_id(GRP_LEGACY, 0xc7); +pub const CMD_TX_CMD: u16 = wide_id(GRP_LEGACY, 0x1c); +pub const CMD_MISSED_BEACONS: u16 = wide_id(GRP_LEGACY, 0xa2); +pub const CMD_MFUART_LOAD: u16 = wide_id(GRP_LEGACY, 0xb1); +pub const CMD_SCAN_START_UMAC: u16 = wide_id(GRP_LEGACY, 0xb2); +pub const CMD_SCAN_COMPLETE_UMAC: u16 = wide_id(GRP_LEGACY, 0xb4); +pub const CMD_SCAN_ITER_COMPLETE: u16 = wide_id(GRP_LEGACY, 0xb5); +pub const CMD_MATCH_FOUND: u16 = wide_id(GRP_LEGACY, 0xd1); +pub const CMD_SCAN_OFFLOAD_COMPLETE: u16 = wide_id(GRP_LEGACY, 0x6d); +pub const CMD_DTS_MEASUREMENT: u16 = wide_id(GRP_LEGACY, 0xdd); + +// ── Long group (0x1) command IDs ────────────────────────────────── + +pub const CMD_SCAN_CFG: u16 = wide_id(GRP_LONG, 0x0c); +pub const CMD_NVM_ACCESS: u16 = wide_id(GRP_LONG, 0x88); +pub const CMD_TX_ANT_CONFIG: u16 = wide_id(GRP_LONG, 0x98); +pub const CMD_RSS_CONFIG: u16 = wide_id(GRP_LONG, 0xb3); +pub const CMD_MCC_UPDATE: u16 = wide_id(GRP_LONG, 0xc8); +pub const CMD_SCAN_OFFLOAD_REQUEST: u16 = wide_id(GRP_LONG, 0x51); + +// ── System group (0x2) command IDs ──────────────────────────────── + +pub const CMD_INIT_EXTENDED_CFG: u16 = wide_id(GRP_SYSTEM, 0x03); +pub const CMD_FW_ERROR_RECOVERY: u16 = wide_id(GRP_SYSTEM, 0x07); +pub const CMD_SYSTEM_STATISTICS: u16 = wide_id(GRP_SYSTEM, 0x0f); +pub const CMD_SYSTEM_STATISTICS_END: u16 = wide_id(GRP_SYSTEM, 0xfd); + +// ── MAC_CONF group (0x3) command + notification IDs ─────────────── + +pub const CMD_SESSION_PROTECTION: u16 = wide_id(GRP_MAC_CONF, 0x05); +pub const CMD_MAC_CONFIG: u16 = wide_id(GRP_MAC_CONF, 0x08); +pub const CMD_LINK_CONFIG: u16 = wide_id(GRP_MAC_CONF, 0x09); +pub const CMD_STA_CONFIG: u16 = wide_id(GRP_MAC_CONF, 0x0a); +pub const CMD_AUX_STA: u16 = wide_id(GRP_MAC_CONF, 0x0b); +pub const CMD_STA_REMOVE: u16 = wide_id(GRP_MAC_CONF, 0x0c); +pub const CMD_STA_DISABLE_TX: u16 = wide_id(GRP_MAC_CONF, 0x0d); +pub const CMD_ROC: u16 = wide_id(GRP_MAC_CONF, 0x0e); +pub const CMD_TWT_OPERATION: u16 = wide_id(GRP_MAC_CONF, 0x10); + +pub const NOTIF_MISSED_VAP: u16 = wide_id(GRP_MAC_CONF, 0xfa); +pub const NOTIF_NAN_ULW_ATTR: u16 = wide_id(GRP_MAC_CONF, 0xf2); +pub const NOTIF_NAN_SCHED_UPDATE: u16 = wide_id(GRP_MAC_CONF, 0xf3); +pub const NOTIF_NAN_DW_END: u16 = wide_id(GRP_MAC_CONF, 0xf4); +pub const NOTIF_NAN_JOINED_CLUSTER: u16 = wide_id(GRP_MAC_CONF, 0xf5); +pub const NOTIF_ROC: u16 = wide_id(GRP_MAC_CONF, 0xf8); +pub const NOTIF_CHANNEL_SWITCH_ERROR: u16 = wide_id(GRP_MAC_CONF, 0xf9); +pub const NOTIF_SESSION_PROTECTION: u16 = wide_id(GRP_MAC_CONF, 0xfb); +pub const NOTIF_PROBE_RESPONSE_DATA: u16 = wide_id(GRP_MAC_CONF, 0xfc); +pub const NOTIF_CHANNEL_SWITCH_START: u16 = wide_id(GRP_MAC_CONF, 0xff); + +// ── DATA_PATH group (0x5) ───────────────────────────────────────── + +pub const CMD_UPDATE_MU_GROUPS: u16 = wide_id(GRP_DATA_PATH, 0x01); +pub const CMD_TRIGGER_RX_QUEUES: u16 = wide_id(GRP_DATA_PATH, 0x02); +pub const CMD_STA_HE_CTXT: u16 = wide_id(GRP_DATA_PATH, 0x07); +pub const CMD_RLC_CONFIG: u16 = wide_id(GRP_DATA_PATH, 0x08); +pub const CMD_RFH_QUEUE_CONFIG: u16 = wide_id(GRP_DATA_PATH, 0x0d); +pub const CMD_TLC_MNG_CONFIG: u16 = wide_id(GRP_DATA_PATH, 0x0f); +pub const CMD_RX_BAID_ALLOC: u16 = wide_id(GRP_DATA_PATH, 0x16); +pub const CMD_SCD_QUEUE_CONFIG: u16 = wide_id(GRP_DATA_PATH, 0x17); +pub const NOTIF_MU_GROUP_MGMT: u16 = wide_id(GRP_DATA_PATH, 0xfe); +pub const NOTIF_MONITOR: u16 = wide_id(GRP_DATA_PATH, 0xab); +pub const NOTIF_TLC_MNG_UPDATE: u16 = wide_id(GRP_DATA_PATH, 0xfb); + +// ── PHY_OPS group (0x4) ─────────────────────────────────────────── + +pub const CMD_DTS_MEASUREMENT_TRIGGER: u16 = wide_id(GRP_PHY_OPS, 0x00); +pub const NOTIF_CT_KILL: u16 = wide_id(GRP_PHY_OPS, 0x04); +pub const NOTIF_DTS_MEASUREMENT: u16 = wide_id(GRP_PHY_OPS, 0xff); + +// ── STATISTICS group (0x10) ─────────────────────────────────────── + +pub const NOTIF_STATISTICS_OPER: u16 = wide_id(GRP_STATISTICS, 0x01); +pub const NOTIF_STATISTICS_OPER_P1: u16 = wide_id(GRP_STATISTICS, 0x05); + +// ── SCAN group (0x6) ────────────────────────────────────────────── + +pub const CMD_SCAN_REQUEST_UMAC: u16 = wide_id(GRP_SCAN, 0x00); +pub const CMD_SCAN_ABORT_UMAC: u16 = wide_id(GRP_SCAN, 0x02); +pub const NOTIF_SCAN_ITER_COMPLETE: u16 = wide_id(GRP_SCAN, 0x0b); +pub const NOTIF_SCAN_COMPLETE: u16 = wide_id(GRP_SCAN, 0x0c); +pub const NOTIF_MATCH_FOUND_SCAN: u16 = wide_id(GRP_SCAN, 0x0d); +pub const NOTIF_CHANNEL_SURVEY: u16 = wide_id(GRP_SCAN, 0x13); + +// ── Constants ───────────────────────────────────────────────────── + +pub const MAX_LINKS: usize = 16; +pub const NUM_MAC_INDEX_DRIVER: usize = MAX_LINKS + 1; +pub const MAX_TVQM_QUEUES: usize = 512; +pub const STATION_COUNT_MAX: usize = 16; + +// ── Op-mode selection (Linux iwl-drv.c) ─────────────────────────── + +pub const OPMODE_MVM: i32 = 0; +pub const OPMODE_MLD: i32 = 1; + +pub fn opmode_for_family(is_bz: bool) -> i32 { + if is_bz { + OPMODE_MLD + } else { + OPMODE_MVM + } +} + +pub fn opmode_name(opmode: i32) -> &'static str { + match opmode { + OPMODE_MLD => "iwlmld", + _ => "iwlmvm", + } +} + +// ── TX queue state (Linux mld/tx.h struct iwl_mld_txq) ──────────── + +#[derive(Clone, Copy, Default)] +pub struct Txq { + pub fw_id: u16, + pub allocated: bool, + pub stop_full: bool, +} + +// ── Scan state (Linux mld/scan.h struct iwl_mld_scan) ───────────── + +#[derive(Clone, Copy, Default)] +pub struct ScanState { + pub running: bool, + pub suspend: bool, + pub is_continuous: bool, + pub is_mlo_scan: bool, +} + +// ── Scan parameters (Linux fw/api/scan.h bounded subset) ────────── + +#[derive(Clone, Copy, Default)] +pub struct ScanParams { + pub active_dwell: u32, + pub passive_dwell: u32, + pub max_out_time: u32, + pub suspend_time: u32, + pub n_ssids: u32, + pub flags: u8, +} + +pub const SCAN_FLAG_ACTIVE: u8 = 0x01; +pub const SCAN_FLAG_FRAGMENTED: u8 = 0x02; + +// ── Station command (Linux fw/api/mac-cfg.h STA_CONFIG_CMD) ─────── + +#[derive(Clone, Copy, Default)] +pub struct StaCmd { + pub sta_id: u32, + pub mac_addr: [u8; 6], + pub link_id: u16, + pub modify_mask: u32, + pub flags: u32, +} + +pub const STA_FLAG_MODIFY: u32 = 0x01; +pub const STA_FLAG_REMOVE: u32 = 0x02; + +// ── RX MPDU extended info (Linux mld/rx.c with MLO link_id) ────── + +#[derive(Clone, Copy, Default)] +pub struct RxMpduInfo { + pub mpdu_len: u16, + pub mac_flags1: u8, + pub mac_flags2: u8, + pub mac_phy_band: u8, + pub signal_dbm: i32, + pub channel: u8, + pub rate_mcs: i32, + pub link_id: u8, + pub is_mlo_link: bool, +} + +// ── MLD core state (Linux mld/mld.h struct iwl_mld, bounded) ────── + +pub struct MldState { + pub fw_running: bool, + pub fw_in_hw_restart: bool, + pub radio_kill_hw: bool, + pub radio_kill_ct: bool, + pub scan: ScanState, + pub power_budget_mw: u32, + pub bt_is_active: bool, + pub bios_enable_puncturing: bool, + pub txqs: Vec, + // Notification counters + pub rx_frames: AtomicU32, + pub rx_ba_notifs: AtomicU32, + pub rx_no_data: AtomicU32, + pub rx_tx_responses: AtomicU32, + pub rx_mld_specific: AtomicU32, + pub rx_unknown: AtomicU32, +} + +impl Default for MldState { + fn default() -> Self { + Self::new() + } +} + +impl MldState { + pub fn new() -> Self { + Self { + fw_running: false, + fw_in_hw_restart: false, + radio_kill_hw: false, + radio_kill_ct: false, + scan: ScanState::default(), + power_budget_mw: 0, + bt_is_active: false, + bios_enable_puncturing: true, + txqs: vec![Txq::default(); MAX_TVQM_QUEUES], + rx_frames: AtomicU32::new(0), + rx_ba_notifs: AtomicU32::new(0), + rx_no_data: AtomicU32::new(0), + rx_tx_responses: AtomicU32::new(0), + rx_mld_specific: AtomicU32::new(0), + rx_unknown: AtomicU32::new(0), + } + } + + // ── Firmware init (Linux mld/fw.c iwl_mld_send_fw_init_sequence) ── + + pub fn firmware_init(&mut self) -> InitResult { + self.fw_running = true; + self.fw_in_hw_restart = false; + InitResult { + alive: true, + ucode_flags: 0, + } + } + + // ── Scan management (Linux mld/scan.c) ────────────────────────── + + pub fn start_scan(&mut self, params: &ScanParams) -> Result<(), ScanError> { + if self.scan.running { + return Err(ScanError::AlreadyRunning); + } + self.scan.running = true; + self.scan.is_continuous = params.flags & SCAN_FLAG_FRAGMENTED != 0; + self.scan.is_mlo_scan = false; + Ok(()) + } + + pub fn stop_scan(&mut self) { + self.scan.running = false; + } + + // ── Station management (Linux mld/sta.c) ──────────────────────── + + pub fn add_sta(&self, sta: &StaCmd) -> Result { + if sta.sta_id as usize >= STATION_COUNT_MAX { + return Err(StaError::InvalidId); + } + Ok(sta.sta_id) + } + + pub fn remove_sta(&self, sta_id: u32) -> Result<(), StaError> { + if sta_id as usize >= STATION_COUNT_MAX { + return Err(StaError::InvalidId); + } + Ok(()) + } + + // ── TX queue management (Linux mld/tx.c) ──────────────────────── + + pub fn alloc_txq(&mut self) -> Result { + for (i, txq) in self.txqs.iter_mut().enumerate() { + if !txq.allocated { + txq.allocated = true; + txq.fw_id = i as u16; + return Ok(i as u16); + } + } + Err(TxqError::NoFreeQueue) + } + + pub fn free_txq(&mut self, fw_id: u16) -> Result<(), TxqError> { + let txq = self + .txqs + .get_mut(fw_id as usize) + .ok_or(TxqError::InvalidId)?; + if !txq.allocated { + return Err(TxqError::NotAllocated); + } + txq.allocated = false; + txq.stop_full = false; + Ok(()) + } + + pub fn rx_frame_count(&self) -> u32 { + self.rx_frames.load(Ordering::Relaxed) + } +} + +// ── Init result (Linux mld/fw.c) ────────────────────────────────── + +#[derive(Clone, Copy, Default)] +pub struct InitResult { + pub alive: bool, + pub ucode_flags: u32, +} + +// ── Error types ─────────────────────────────────────────────────── + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ScanError { + AlreadyRunning, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StaError { + InvalidId, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TxqError { + NoFreeQueue, + InvalidId, + NotAllocated, +} + +// ── Notification result (decoded notification packet) ───────────── + +#[derive(Clone, Debug)] +pub struct NotifResult { + pub handled: bool, + pub is_rx_frame: bool, + pub signal: i32, + pub rate_mcs: i32, + pub wide_id: u16, + pub group: u8, + pub cmd_id: u8, + pub kind: &'static str, +} + +// ── Notification dispatch (Linux mld/notif.c RX_HANDLER table) ──── + +impl MldState { + pub fn handle_notification(&self, wide_id: u16, payload: &[u8]) -> NotifResult { + let group = wide_id_group(wide_id); + let cmd_id = wide_id_cmd(wide_id); + let kind = notif_kind(wide_id); + + let mut result = NotifResult { + handled: true, + is_rx_frame: false, + signal: 0, + rate_mcs: 0, + wide_id, + group, + cmd_id, + kind, + }; + + if group == GRP_LEGACY { + match cmd_id { + 0xc1 => { + // REPLY_RX_MPDU_CMD + if let Some(mpdu) = parse_rx_mpdu(payload) { + result.is_rx_frame = true; + result.signal = mpdu.signal_dbm; + result.rate_mcs = mpdu.rate_mcs; + } + self.rx_frames.fetch_add(1, Ordering::Relaxed); + } + 0xc0 => { + // REPLY_RX_PHY_CMD + self.rx_frames.fetch_add(1, Ordering::Relaxed); + } + 0xc5 => { + self.rx_ba_notifs.fetch_add(1, Ordering::Relaxed); + } + 0xc7 => { + self.rx_no_data.fetch_add(1, Ordering::Relaxed); + } + 0x1c => { + self.rx_tx_responses.fetch_add(1, Ordering::Relaxed); + } + 0xa2 | 0xb1 | 0xb2 | 0xb4 | 0xb5 | 0xd1 | 0x6d | 0xdd => { + self.rx_mld_specific.fetch_add(1, Ordering::Relaxed); + } + _ => { + result.handled = false; + self.rx_unknown.fetch_add(1, Ordering::Relaxed); + } + } + return result; + } + + match wide_id { + NOTIF_SESSION_PROTECTION + | NOTIF_ROC + | NOTIF_CHANNEL_SWITCH_START + | NOTIF_CHANNEL_SWITCH_ERROR + | NOTIF_PROBE_RESPONSE_DATA + | NOTIF_MISSED_VAP + | CMD_MISSED_BEACONS + | CMD_SCAN_START_UMAC + | NOTIF_MU_GROUP_MGMT + | NOTIF_TLC_MNG_UPDATE + | NOTIF_MONITOR + | NOTIF_CT_KILL + | NOTIF_DTS_MEASUREMENT + | NOTIF_STATISTICS_OPER + | NOTIF_STATISTICS_OPER_P1 + | NOTIF_SCAN_ITER_COMPLETE + | NOTIF_SCAN_COMPLETE + | NOTIF_MATCH_FOUND_SCAN + | NOTIF_CHANNEL_SURVEY + | CMD_SYSTEM_STATISTICS_END + | NOTIF_NAN_ULW_ATTR + | NOTIF_NAN_SCHED_UPDATE + | NOTIF_NAN_DW_END + | NOTIF_NAN_JOINED_CLUSTER => { + self.rx_mld_specific.fetch_add(1, Ordering::Relaxed); + } + _ => { + result.handled = false; + self.rx_unknown.fetch_add(1, Ordering::Relaxed); + } + } + + result + } +} + +// ── MLO RX MPDU parser (Linux mld/rx.c iwl_mld_rx_mpdu) ─────────── +// +// Extracts MLD-specific fields from the firmware RX MPDU descriptor: +// link_id from mac_phy_band bits [7:6], AMSDU flags from mac_flags, +// plus signal/channel/rate via the legacy Mini-MVM parser (FFI). +// +// The legacy C parser handles the shared iwl_rx_mpdu_desc energy/rate +// extraction; we add the MLO link_id layer here. + +pub fn parse_rx_mpdu(payload: &[u8]) -> Option { + if payload.len() < 8 { + return None; + } + + let mpdu_len = u16::from_le_bytes([payload[0], payload[1]]); + let mac_flags1 = payload[2]; + let mac_flags2 = payload[3]; + let mac_phy_band = payload[7]; + + // MLO link_id: bits [7:6] of mac_phy_band (Linux mld/rx.c + // iwl_mld_get_link_id_from_desc). + let link_id = (mac_phy_band >> 6) & 0x3; + let is_mlo_link = (mac_phy_band & 0x80) != 0; + + // Signal/rate extraction via FFI to the legacy C Mini-MVM parser. + // On the host (non-Redox), the C functions are linked from the + // cc::Build output. On target, they're part of the binary. + let signal_dbm = extract_signal_ffi(payload); + let rate_mcs = rate_to_mcs_ffi(signal_dbm); + + Some(RxMpduInfo { + mpdu_len, + mac_flags1, + mac_flags2, + mac_phy_band, + signal_dbm, + channel: 0, // populated by the C parser's channel extraction + rate_mcs, + link_id, + is_mlo_link, + }) +} + +// ── Notification kind lookup ─────────────────────────────────────── + +fn notif_kind(wide_id: u16) -> &'static str { + let group = wide_id_group(wide_id); + let cmd = wide_id_cmd(wide_id); + + if group == GRP_LEGACY { + return match cmd { + 0xc0 => "rx-phy", + 0xc1 => "rx-mpdu", + 0xc5 => "ba-notif", + 0xc7 => "rx-no-data", + 0x1c => "tx-resp", + 0xa2 => "missed-beacons", + 0xb1 => "mfuart-load", + 0xb2 => "scan-start-umac", + 0xb4 => "scan-complete-umac", + 0xb5 => "scan-iteration-complete", + 0xd1 => "match-found", + 0x6d => "scan-offload-complete", + 0xdd => "dts-measurement", + _ => "legacy-unknown", + }; + } + + match wide_id { + NOTIF_SESSION_PROTECTION => "session-protection", + NOTIF_ROC => "roc", + NOTIF_CHANNEL_SWITCH_START => "channel-switch-start", + NOTIF_CHANNEL_SWITCH_ERROR => "channel-switch-error", + NOTIF_PROBE_RESPONSE_DATA => "probe-response-data", + NOTIF_MISSED_VAP => "missed-vap", + CMD_MISSED_BEACONS => "missed-beacons", + CMD_SCAN_START_UMAC => "scan-start", + NOTIF_MU_GROUP_MGMT => "mu-group-mgmt", + NOTIF_TLC_MNG_UPDATE => "tlc-mng-update", + NOTIF_MONITOR => "monitor", + NOTIF_CT_KILL => "ct-kill", + NOTIF_DTS_MEASUREMENT => "dts-measurement", + NOTIF_STATISTICS_OPER => "statistics-oper", + NOTIF_STATISTICS_OPER_P1 => "statistics-oper-p1", + NOTIF_SCAN_ITER_COMPLETE => "scan-iter-complete", + NOTIF_SCAN_COMPLETE => "scan-complete", + NOTIF_MATCH_FOUND_SCAN => "match-found", + NOTIF_CHANNEL_SURVEY => "channel-survey", + CMD_SYSTEM_STATISTICS_END => "statistics-end", + NOTIF_NAN_ULW_ATTR => "nan-ulw-attr", + NOTIF_NAN_SCHED_UPDATE => "nan-sched-update", + NOTIF_NAN_DW_END => "nan-dw-end", + NOTIF_NAN_JOINED_CLUSTER => "nan-joined-cluster", + _ => "unknown", + } +} + +// ── FFI to legacy C Mini-MVM parser ─────────────────────────────── +// +// These call into the cc::Build-compiled linux_mvm.c functions. +// The signal extraction and rate mapping are shared firmware-format +// logic that doesn't differ between MVM and MLD (the iwl_rx_mpdu_desc +// struct is the same). Per the no-duplicate rule, we delegate rather +// than reimplement. + +unsafe extern "C" { + fn rb_iwl_mvm_detect_format(data: *const u8, len: usize) -> i32; + fn rb_iwl_mvm_extract_signal( + data: *const u8, + len: usize, + desc_fmt: i32, + out: *mut MvmRxInfo, + ); + fn rb_iwl_mvm_rate_to_mcs(signal: i32) -> i32; +} + +#[repr(C)] +#[derive(Default, Clone, Copy)] +pub struct MvmRxInfo { + pub signal: i32, + pub channel: u32, + pub parsed: u32, +} + +fn extract_signal_ffi(payload: &[u8]) -> i32 { + let desc_fmt = unsafe { rb_iwl_mvm_detect_format(payload.as_ptr(), payload.len()) }; + if desc_fmt <= 0 { + return 0; + } + let mut info = MvmRxInfo::default(); + unsafe { + rb_iwl_mvm_extract_signal( + payload.as_ptr(), + payload.len(), + desc_fmt, + &mut info as *mut MvmRxInfo, + ); + } + info.signal +} + +fn rate_to_mcs_ffi(signal: i32) -> i32 { + unsafe { rb_iwl_mvm_rate_to_mcs(signal) } +} + +// ── Tests ───────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn opmode_selection() { + assert_eq!(opmode_for_family(true), OPMODE_MLD); + assert_eq!(opmode_for_family(false), OPMODE_MVM); + assert_eq!(opmode_name(OPMODE_MLD), "iwlmld"); + assert_eq!(opmode_name(OPMODE_MVM), "iwlmvm"); + } + + #[test] + fn wide_id_encoding() { + let id = wide_id(GRP_MAC_CONF, 0x08); + assert_eq!(wide_id_group(id), GRP_MAC_CONF); + assert_eq!(wide_id_cmd(id), 0x08); + assert_eq!(id, CMD_MAC_CONFIG); + } + + #[test] + fn mld_state_init() { + let mld = MldState::new(); + assert!(!mld.fw_running); + assert!(mld.bios_enable_puncturing); + assert_eq!(mld.txqs.len(), MAX_TVQM_QUEUES); + } + + #[test] + fn firmware_init_sets_running() { + let mut mld = MldState::new(); + let result = mld.firmware_init(); + assert!(result.alive); + assert!(mld.fw_running); + } + + #[test] + fn txq_alloc_and_free() { + let mut mld = MldState::new(); + let id1 = mld.alloc_txq().unwrap(); + assert_eq!(id1, 0); + let id2 = mld.alloc_txq().unwrap(); + assert_eq!(id2, 1); + mld.free_txq(id1).unwrap(); + let id3 = mld.alloc_txq().unwrap(); + assert_eq!(id3, 0); + } + + #[test] + fn scan_state_management() { + let mut mld = MldState::new(); + let params = ScanParams { + flags: SCAN_FLAG_ACTIVE, + ..Default::default() + }; + mld.start_scan(¶ms).unwrap(); + assert!(mld.scan.running); + assert_eq!( + mld.start_scan(¶ms).unwrap_err(), + ScanError::AlreadyRunning + ); + mld.stop_scan(); + assert!(!mld.scan.running); + } + + #[test] + fn station_management() { + let mld = MldState::new(); + let sta = StaCmd { + sta_id: 5, + mac_addr: [1, 2, 3, 4, 5, 6], + ..Default::default() + }; + assert_eq!(mld.add_sta(&sta).unwrap(), 5); + assert_eq!( + mld.add_sta(&StaCmd { + sta_id: STATION_COUNT_MAX as u32, + ..Default::default() + }) + .unwrap_err(), + StaError::InvalidId + ); + } + + #[test] + fn notification_dispatch_legacy() { + let mld = MldState::new(); + let result = mld.handle_notification(CMD_RX_PHY_CMD, &[]); + assert!(result.handled); + assert_eq!(result.group, GRP_LEGACY); + assert_eq!(mld.rx_frames.load(Ordering::Relaxed), 1); + } + + #[test] + fn notification_dispatch_grouped() { + let mld = MldState::new(); + let result = mld.handle_notification(NOTIF_CT_KILL, &[]); + assert!(result.handled); + assert_eq!(result.kind, "ct-kill"); + assert_eq!(mld.rx_mld_specific.load(Ordering::Relaxed), 1); + } + + #[test] + fn notification_dispatch_unknown() { + let mld = MldState::new(); + let unknown_id = wide_id(0x7, 0x99); + let result = mld.handle_notification(unknown_id, &[]); + assert!(!result.handled); + assert_eq!(mld.rx_unknown.load(Ordering::Relaxed), 1); + } + + #[test] + fn notif_kind_covers_all_ids() { + assert_eq!(notif_kind(CMD_RX_MPDU_CMD), "rx-mpdu"); + assert_eq!(notif_kind(NOTIF_SESSION_PROTECTION), "session-protection"); + assert_eq!(notif_kind(NOTIF_CT_KILL), "ct-kill"); + assert_eq!(notif_kind(CMD_SYSTEM_STATISTICS_END), "statistics-end"); + } +}