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:
@@ -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(¶ms) {
|
||||
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 @@
|
||||
//! R1–R10 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user