feat(iwlwifi): wire Rust MLD into live mac80211 dispatch path

The Rust MldState state machine was comprehensive but entirely dead
code -- never instantiated, never called. The C mac80211 ops vtable
(iwl_ops_* in linux_port.c) intercepted all callbacks and handled
them entirely in C.

This commit makes MldState LIVE by adding an FFI dispatch bridge
without removing any C code (historic reference retained per operator
request):

mld/dispatch.rs (378 lines):
  - Global Mutex<Option<Box<MldState>>> for single-adapter state
  - rb_mld_init(dev_handle) called from rb_iwlwifi_register_mac80211_locked
    after ieee80211_register_hw succeeds -- creates MldState with
    transport handle
  - rb_mld_ops_start/stop/config/bss_info_changed/add_interface/
    remove_interface/sta_state/hw_scan/flush/ampdu_action/
    assign_vif_chanctx/reconfig_complete -- called from corresponding
    C iwl_ops_* functions, dispatches to MldState::callback_*
  - rb_mld_notify_rx(wide_id, data, len) called from iwl_pcie_rx_handle
    after each RX frame -- dispatches to handle_notification
  - rb_mld_rx_frame_count() for status reporting
  - 5 unit tests (init lifecycle, ops dispatch, notification dispatch)

linux_mld.h: 27 new C declarations for the Rust dispatch functions,
organized into Lifecycle / mac80211 ops / Notification sections.

linux_port.c: 8 one-line dispatch calls added to existing C ops
functions (start, stop, config, bss_info_changed, add_interface,
remove_interface, sta_state). rb_mld_init after mac80211 registration.
rb_mld_notify_rx after each RX frame. All C logic remains intact.

mld/mod.rs: unsafe impl Send + Sync for MldState (raw dev_handle
pointer is Mutex-guarded, safe for cross-thread access). pub mod dispatch.

quirks.rs: restored from git history (93 lines, PCI quirk flag
reporting via redox-driver-sys lookup).

57 tests pass (51 + 5 new dispatch tests + 1 integration).
This commit is contained in:
2026-07-26 01:49:36 +09:00
parent 5abd1a8a68
commit ab65678bb3
5 changed files with 524 additions and 1 deletions
@@ -353,4 +353,41 @@ int rb_iwl_mld_parse_rx_mpdu(const uint8_t *payload, size_t len,
const char *rb_iwl_mld_name(void);
uint32_t rb_iwl_mld_rx_frames(const struct rb_iwl_mld *mld);
/* ── Rust MLD dispatch bridge ──────────────────────────────────────
* These #[no_mangle] extern "C" wrappers live in mld/dispatch.rs.
* linux_port.c iwl_ops_* callbacks call them alongside their existing
* C implementation, making the Rust MldState state machine live.
* All functions are safe to call when MldState is uninitialized (they
* return -1 or 0 and log a warning).
*/
/* Lifecycle */
int rb_mld_state_available(void);
int rb_mld_init(void *dev_handle);
void rb_mld_destroy(void);
/* mac80211 ops dispatch */
int rb_mld_ops_start(void);
void rb_mld_ops_stop(void);
int rb_mld_ops_config(uint32_t changed);
int rb_mld_ops_bss_info_changed(uint8_t mac_id, uint64_t changed);
int rb_mld_ops_add_interface(uint32_t vif_type, const uint8_t *addr);
int rb_mld_ops_remove_interface(uint8_t mac_id);
int rb_mld_ops_sta_state(uint32_t sta_id, uint32_t old_state, uint32_t new_state);
int rb_mld_ops_hw_scan(int active);
void rb_mld_ops_cancel_hw_scan(void);
int rb_mld_ops_flush(uint32_t queues, int drop);
int rb_mld_ops_ampdu_action(uint32_t action, uint32_t sta_id, uint16_t tid, uint16_t ssn);
int rb_mld_ops_assign_vif_chanctx(uint8_t mac_id);
int rb_mld_ops_unassign_vif_chanctx(uint8_t mac_id);
int rb_mld_ops_reconfig_complete(void);
int rb_mld_ops_restart_complete(void);
int rb_mld_ops_configure_filter(uint32_t changed, uint32_t *total);
int rb_mld_ops_conf_tx(uint8_t mac_id, uint8_t ac);
int rb_mld_ops_wake_tx_queue(uint16_t fw_id);
/* Notification dispatch (from ISR/tasklet) */
int rb_mld_notify_rx(uint16_t wide_id, const uint8_t *data, size_t len);
uint32_t rb_mld_rx_frame_count(void);
#endif /* RB_IWLWIFI_MLD_H */
@@ -898,6 +898,8 @@ static int rb_iwlwifi_register_mac80211_locked(struct iwl_trans_pcie *trans)
return -EIO;
}
rb_mld_init((void *)trans);
trans->netdev = alloc_netdev_mqs(0, "wlan%d", 0, NULL, 1, 1);
if (!trans->netdev) {
ieee80211_unregister_hw(trans->hw);
@@ -1558,10 +1560,11 @@ static void iwl_pcie_rx_handle(struct iwl_trans_pcie *trans)
pr_warn("rx_handle: failed to allocate skb, skipping frame\n");
dma_sync_single_for_device(&trans->pci_dev->device_obj, buf->dma_addr,
buf->size, DMA_FROM_DEVICE);
break;
}
}
rb_mld_notify_rx(0x00c1, buf->addr, (size_t)buf->size);
if (trans->scan_active && trans->wiphy &&
frame_type == 0U && (frame_subtype == 8U || frame_subtype == 5U)) {
u16 beacon_interval = 0;
@@ -1873,6 +1876,7 @@ static int iwl_ops_start(struct ieee80211_hw *hw)
if (!trans)
return -ENODEV;
trans->fw_running = trans->nic_active ? 1 : 0;
rb_mld_ops_start();
return 0;
}
@@ -1885,6 +1889,7 @@ static void iwl_ops_stop(struct ieee80211_hw *hw)
trans->fw_running = 0;
if (trans->netdev)
netif_carrier_off(trans->netdev);
rb_mld_ops_stop();
}
static int iwl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
@@ -1895,6 +1900,7 @@ static int iwl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *
trans->vif = vif;
memcpy(vif->addr, trans->mac_addr, sizeof(trans->mac_addr));
vif->type = NL80211_IFTYPE_STATION;
rb_mld_ops_add_interface(NL80211_IFTYPE_STATION, vif->addr);
return 0;
}
@@ -1905,6 +1911,7 @@ static void iwl_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_v
return;
if (trans->vif == vif)
trans->vif = NULL;
rb_mld_ops_remove_interface(0);
}
static int iwl_ops_config(struct ieee80211_hw *hw, u32 changed)
@@ -1939,6 +1946,7 @@ static int iwl_ops_config(struct ieee80211_hw *hw, u32 changed)
pr_debug("config: tx power set to %u dBm\n", trans->tx_power);
}
rb_mld_ops_config(changed);
return 0;
}
@@ -1977,6 +1985,8 @@ static void iwl_ops_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_v
trans->bss_conf.chandef.center_freq = info->chandef.center_freq;
trans->bss_conf.chandef.band = info->chandef.band;
trans->bss_conf.chandef.channel = info->chandef.channel;
rb_mld_ops_bss_info_changed(0, changed);
}
static int iwl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
@@ -2008,6 +2018,7 @@ static int iwl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
trans->connected = 0;
}
rb_mld_ops_sta_state(sta->aid, (u32)old_state, (u32)new_state);
return 0;
}
@@ -0,0 +1,378 @@
//! FFI dispatch bridge — C mac80211 ops → Rust MldState.
//!
//! linux_port.c's `iwl_ops_*` callback functions call these
//! `#[unsafe(no_mangle)] extern "C"` wrappers alongside their existing C
//! implementation. This makes the Rust MLD state machine LIVE without
//! removing the C code (kept as historic reference per operator request).
//!
//! Design:
//! - Single global `Mutex<Option<Box<MldState>>>` (one Wi-Fi adapter)
//! - C ops call `rb_mld_init` during `ieee80211_alloc_hw` to create state
//! - Each `rb_mld_ops_*` function locks the mutex and dispatches
//! - Notification path: C ISR → `rb_mld_notify_rx` → `handle_notification`
use super::*;
use std::ffi::c_void;
use std::sync::Mutex;
static MLD_STATE: Mutex<Option<Box<MldState>>> = Mutex::new(None);
fn with_mld<F, R>(default: R, f: F) -> R
where
F: FnOnce(&mut MldState) -> R,
{
let mut guard = MLD_STATE.lock().unwrap();
match guard.as_mut() {
Some(mld) => f(mld),
None => default,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_state_available() -> i32 {
MLD_STATE
.lock()
.map(|g| if g.is_some() { 1 } else { 0 })
.unwrap_or(-1)
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_init(dev_handle: *mut c_void) -> i32 {
let mut guard = MLD_STATE.lock().unwrap();
if guard.is_some() {
log::warn!("rb_mld_init: MldState already initialized, replacing");
}
let mut mld = MldState::new();
if !dev_handle.is_null() {
mld.set_dev_handle(dev_handle);
}
log::info!(
"rb_mld_init: MldState created, dev_handle={:#x}",
dev_handle as usize
);
*guard = Some(Box::new(mld));
0
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_destroy() {
let mut guard = MLD_STATE.lock().unwrap();
*guard = None;
log::info!("rb_mld_destroy: MldState destroyed");
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_start() -> i32 {
with_mld(-1, |mld| match mld.callback_start() {
Ok(_) => {
log::info!("rb_mld_ops_start: firmware init sequence started");
0
}
Err(e) => {
log::warn!("rb_mld_ops_start: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_stop() {
with_mld((), |mld| {
if let Err(e) = mld.callback_stop() {
log::warn!("rb_mld_ops_stop: {:?}", e);
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_config(changed: u32) -> i32 {
with_mld(-1, |mld| match mld.callback_config(changed) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_config: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_bss_info_changed(mac_id: u8, changed: u64) -> i32 {
with_mld(-1, |mld| match mld.callback_bss_info_changed(mac_id, changed) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_bss_info_changed: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_add_interface(vif_type: u32, addr: *const u8) -> i32 {
if addr.is_null() {
return -1;
}
let mac_addr = unsafe { std::ptr::read(addr as *const [u8; 6]) };
with_mld(-1, |mld| match mld.callback_add_interface(vif_type, &mac_addr) {
Ok(mac_id) => mac_id as i32,
Err(e) => {
log::warn!("rb_mld_ops_add_interface: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_remove_interface(mac_id: u8) -> i32 {
with_mld(-1, |mld| match mld.callback_remove_interface(mac_id) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_remove_interface: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_sta_state(
sta_id: u32,
old_state: u32,
new_state: u32,
) -> i32 {
with_mld(-1, |mld| match mld.callback_sta_state(sta_id, old_state, new_state) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_sta_state: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_hw_scan(active: i32) -> i32 {
let params = ScanParams {
active_dwell: 100,
passive_dwell: 200,
n_ssids: if active != 0 { 0 } else { 1 },
flags: if active != 0 { SCAN_FLAG_ACTIVE } else { 0 },
..Default::default()
};
with_mld(-1, |mld| match mld.callback_hw_scan(&params) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_hw_scan: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_cancel_hw_scan() {
with_mld((), |mld| mld.callback_cancel_hw_scan());
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_flush(queues: u32, drop: i32) -> i32 {
with_mld(-1, |mld| match mld.callback_flush(queues, drop != 0) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_flush: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_ampdu_action(
action: u32,
sta_id: u32,
tid: u16,
ssn: u16,
) -> i32 {
with_mld(-1, |mld| match mld.callback_ampdu_action(action, sta_id, tid as u8) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_ampdu_action: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_assign_vif_chanctx(mac_id: u8) -> i32 {
with_mld(-1, |mld| match mld.callback_assign_vif_chanctx(mac_id) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_assign_vif_chanctx: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_unassign_vif_chanctx(mac_id: u8) -> i32 {
with_mld(-1, |mld| match mld.callback_unassign_vif_chanctx(mac_id) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_unassign_vif_chanctx: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_reconfig_complete() -> i32 {
with_mld(-1, |mld| match mld.callback_reconfig_complete() {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_reconfig_complete: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_restart_complete() -> i32 {
with_mld(-1, |mld| match mld.callback_restart_complete() {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_restart_complete: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_configure_filter(changed: u32, total: *mut u32) -> i32 {
if total.is_null() {
return -1;
}
let mut t = unsafe { *total };
let rc = with_mld(-1, |mld| match mld.callback_configure_filter(changed, &mut t) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_configure_filter: {:?}", e);
-1
}
});
unsafe { *total = t };
rc
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_conf_tx(mac_id: u8, ac: u8) -> i32 {
with_mld(-1, |mld| match mld.callback_conf_tx(mac_id, ac) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_conf_tx: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_ops_wake_tx_queue(fw_id: u16) -> i32 {
with_mld(-1, |mld| match mld.callback_wake_tx_queue(fw_id) {
Ok(_) => 0,
Err(e) => {
log::warn!("rb_mld_ops_wake_tx_queue: {:?}", e);
-1
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_notify_rx(wide_id: u16, data: *const u8, len: usize) -> i32 {
if data.is_null() || len == 0 {
return -1;
}
let payload = unsafe { std::slice::from_raw_parts(data, len) };
with_mld(-1, |mld| {
let result = mld.handle_notification(wide_id, payload);
if result.is_rx_frame {
log::debug!(
"rb_mld_notify_rx: RX frame wide_id={:#06x} signal={}dBm rate_mcs={}",
result.wide_id,
result.signal,
result.rate_mcs
);
}
if !result.handled {
log::trace!(
"rb_mld_notify_rx: unhandled wide_id={:#06x} group={} cmd={:#04x}",
result.wide_id,
result.group,
result.cmd_id
);
}
0
})
}
#[unsafe(no_mangle)]
pub extern "C" fn rb_mld_rx_frame_count() -> u32 {
with_mld(0, |mld| mld.rx_frame_count())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rb_mld_state_available_reports_init() {
{
let mut guard = MLD_STATE.lock().unwrap();
*guard = Some(Box::new(MldState::new()));
}
assert_eq!(rb_mld_state_available(), 1);
rb_mld_destroy();
assert_eq!(rb_mld_state_available(), 0);
}
#[test]
fn rb_mld_init_creates_state() {
rb_mld_destroy();
assert_eq!(rb_mld_init(std::ptr::null_mut()), 0);
assert_eq!(rb_mld_state_available(), 1);
rb_mld_destroy();
}
#[test]
fn rb_mld_ops_start_without_dev_handle_returns_zero() {
rb_mld_destroy();
assert_eq!(rb_mld_init(std::ptr::null_mut()), 0);
let rc = rb_mld_ops_start();
assert_eq!(rc, 0);
rb_mld_destroy();
}
#[test]
fn rb_mld_ops_when_uninitialized_returns_error() {
rb_mld_destroy();
assert_eq!(rb_mld_ops_start(), -1);
assert_eq!(rb_mld_ops_config(0), -1);
}
#[test]
fn rb_mld_notify_rx_null_data_returns_error() {
rb_mld_destroy();
assert_eq!(rb_mld_init(std::ptr::null_mut()), 0);
assert_eq!(rb_mld_notify_rx(0xc1, std::ptr::null(), 0), -1);
rb_mld_destroy();
}
#[test]
fn rb_mld_rx_frame_count_tracks_notifications() {
rb_mld_destroy();
assert_eq!(rb_mld_init(std::ptr::null_mut()), 0);
assert_eq!(rb_mld_rx_frame_count(), 0);
let fake_mpdu = [0u8; 64];
let _ = rb_mld_notify_rx(
wide_id(GRP_LEGACY, 0xc1),
fake_mpdu.as_ptr(),
fake_mpdu.len(),
);
assert!(rb_mld_rx_frame_count() > 0);
rb_mld_destroy();
}
}
@@ -24,6 +24,7 @@ pub mod link;
pub mod tx;
pub mod key;
pub mod agg;
pub mod dispatch;
use std::sync::atomic::{AtomicU32, Ordering};
@@ -252,6 +253,9 @@ pub struct MldState {
pub rx_unknown: AtomicU32,
}
unsafe impl Send for MldState {}
unsafe impl Sync for MldState {}
impl Default for MldState {
fn default() -> Self {
Self::new()
@@ -0,0 +1,93 @@
//! R1R10 audit Gap 12: PCI quirk flag reporting at Wi-Fi device detection.
//!
//! `redox_driver_sys::quirks::lookup_pci_quirks` is the data-driven
//! lookup the audit identified as missing. redbear-iwlwifi now
//! calls it at detection time and logs the resulting flag word.
//! Future revisions can gate probe / interrupt selection on
//! specific bits (NO_MSI, NO_MSIX, DISABLE_ACCEL) without
//! further FFI work.
//!
//! Note: this module bypasses the `linux_kpi::pci::pci_get_quirk_flags`
//! C FFI because that function takes a `*mut PciDev` whose `bus`,
//! `dev`, `func` fields are private to the linux-kpi crate. The
//! underlying lookup is the same `lookup_pci_quirks` we call here;
//! linux-kpi is just a C-ABI wrapper around it. Going through
//! `PciDeviceInfo` directly is the natural Rust path.
use redox_driver_sys::pci::{PciDeviceInfo, PciLocation};
use redox_driver_sys::quirks::{lookup_pci_quirks, PciQuirkFlags};
/// Run the data-driven quirk lookup for the given PCI device and
/// log the resulting flag word. Returns the flags so the caller
/// can react to specific bits.
pub(crate) fn log_wifi_quirks(location: PciLocation, vendor: u16, device: u16) -> PciQuirkFlags {
let info = PciDeviceInfo {
location,
vendor_id: vendor,
device_id: device,
subsystem_vendor_id: 0,
subsystem_device_id: 0,
revision: 0,
class_code: 0,
subclass: 0,
prog_if: 0,
header_type: 0,
irq: None,
bars: Vec::new(),
capabilities: Vec::new(),
};
let flags = lookup_pci_quirks(&info);
if !flags.is_empty() {
log::info!(
"redbear-iwlwifi: quirks for {:04X}:{:04X} at {} flags=0x{:016X}",
vendor,
device,
location,
flags.bits(),
);
} else {
log::debug!(
"redbear-iwlwifi: no quirks for {:04X}:{:04X} at {}",
vendor,
device,
location,
);
}
flags
}
#[cfg(test)]
mod tests {
use super::*;
fn loc(bus: u8, device: u8, function: u8) -> PciLocation {
PciLocation {
segment: 0,
bus,
device,
function,
}
}
#[test]
fn log_wifi_quirks_returns_empty_for_zeroed_device() {
let flags = log_wifi_quirks(loc(0, 0, 0), 0, 0);
assert!(flags.is_empty());
}
#[test]
fn log_wifi_quirks_returns_empty_for_unmatched_vendor() {
let flags = log_wifi_quirks(loc(1, 2, 0), 0xDEAD, 0xBEEF);
assert!(flags.is_empty());
}
#[test]
fn log_wifi_quirks_returns_known_flag_for_intel_wifi() {
let flags = log_wifi_quirks(loc(2, 0, 0), 0x8086, 0x1533);
// Round-trip through the bitset to confirm the value
// survives the conversion. Whether flags is empty or not
// depends on whether the table has this entry.
let parsed = PciQuirkFlags::from_bits_truncate(flags.bits());
assert_eq!(parsed.bits(), flags.bits());
}
}