Expand linux-kpi wireless and networking scaffolding
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#ifndef _LINUX_ATOMIC_H
|
||||
#define _LINUX_ATOMIC_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "types.h"
|
||||
|
||||
typedef struct {
|
||||
volatile int counter;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#define ERANGE 34
|
||||
#define ENOSYS 38
|
||||
#define ENODATA 61
|
||||
#define ENOSPC 28
|
||||
#define ENOTCONN 107
|
||||
#define ENOTSUP 95
|
||||
#define ETIMEDOUT 110
|
||||
|
||||
|
||||
@@ -7,14 +7,21 @@
|
||||
#define IEEE80211_NUM_ACS 4
|
||||
|
||||
struct ieee80211_channel {
|
||||
u32 band;
|
||||
u16 center_freq;
|
||||
u16 hw_value;
|
||||
u32 flags;
|
||||
s8 max_power;
|
||||
s8 max_reg_power;
|
||||
s8 max_antenna_gain;
|
||||
bool beacon_found;
|
||||
};
|
||||
|
||||
struct ieee80211_rate {
|
||||
u32 flags;
|
||||
u16 bitrate;
|
||||
u16 hw_value;
|
||||
u16 hw_value_short;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -14,8 +14,9 @@ extern int irqs_disabled(void);
|
||||
static inline int in_interrupt(void) { return irqs_disabled(); }
|
||||
static inline int in_irq(void) { return irqs_disabled(); }
|
||||
|
||||
#define disable_irq_nosync(irq) ((void)(irq))
|
||||
#define enable_irq(irq) ((void)(irq))
|
||||
extern void disable_irq(unsigned int irq);
|
||||
extern void disable_irq_nosync(unsigned int irq);
|
||||
extern void enable_irq(unsigned int irq);
|
||||
|
||||
#define IRQF_NO_SUSPEND 0x0000U
|
||||
#define IRQF_FORCE_RESUME 0x0000U
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#ifndef _LINUX_KERNEL_H
|
||||
#define _LINUX_KERNEL_H
|
||||
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/types.h>
|
||||
#include "compiler.h"
|
||||
#include "types.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -20,6 +20,13 @@ struct net_device {
|
||||
size_t __priv_alloc_align;
|
||||
};
|
||||
|
||||
struct napi_struct {
|
||||
int (*poll)(struct napi_struct *napi, int budget);
|
||||
struct net_device *dev;
|
||||
int state;
|
||||
int weight;
|
||||
};
|
||||
|
||||
extern struct net_device *alloc_netdev_mqs(size_t sizeof_priv,
|
||||
const char *name,
|
||||
unsigned char name_assign_type,
|
||||
@@ -32,5 +39,13 @@ extern void unregister_netdev(struct net_device *dev);
|
||||
extern void netif_carrier_on(struct net_device *dev);
|
||||
extern void netif_carrier_off(struct net_device *dev);
|
||||
extern int netif_carrier_ok(const struct net_device *dev);
|
||||
extern void netif_napi_add(struct net_device *dev, struct napi_struct *napi,
|
||||
int (*poll)(struct napi_struct *napi, int budget), int weight);
|
||||
extern void napi_schedule(struct napi_struct *napi);
|
||||
extern int napi_complete_done(struct napi_struct *napi, int work_done);
|
||||
extern void netif_tx_wake_queue(struct net_device *dev, u16 queue_idx);
|
||||
extern void netif_tx_stop_queue(struct net_device *dev, u16 queue_idx);
|
||||
extern void netif_device_attach(struct net_device *dev);
|
||||
extern void netif_device_detach(struct net_device *dev);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,12 @@ enum nl80211_iftype {
|
||||
NL80211_IFTYPE_MONITOR = 6,
|
||||
};
|
||||
|
||||
enum nl80211_band {
|
||||
NL80211_BAND_2GHZ = 0,
|
||||
NL80211_BAND_5GHZ = 1,
|
||||
NL80211_BAND_6GHZ = 2,
|
||||
};
|
||||
|
||||
enum nl80211_commands {
|
||||
NL80211_CMD_UNSPEC = 0,
|
||||
NL80211_CMD_TRIGGER_SCAN = 33,
|
||||
|
||||
@@ -23,7 +23,7 @@ struct pci_device_id {
|
||||
u32 device;
|
||||
u32 subvendor;
|
||||
u32 subdevice;
|
||||
u32 class;
|
||||
u32 class_code;
|
||||
u32 class_mask;
|
||||
unsigned long driver_data;
|
||||
};
|
||||
@@ -52,6 +52,12 @@ struct pci_driver {
|
||||
void (*shutdown)(struct pci_dev *dev);
|
||||
};
|
||||
|
||||
struct msix_entry {
|
||||
u32 vector;
|
||||
u16 entry;
|
||||
u16 _pad;
|
||||
};
|
||||
|
||||
extern int pci_enable_device(struct pci_dev *dev);
|
||||
extern void pci_disable_device(struct pci_dev *dev);
|
||||
extern void pci_set_master(struct pci_dev *dev);
|
||||
@@ -60,6 +66,8 @@ extern void pci_free_irq_vectors(struct pci_dev *dev);
|
||||
extern int pci_irq_vector(struct pci_dev *dev, unsigned int nr);
|
||||
extern int pci_enable_msi(struct pci_dev *dev);
|
||||
extern void pci_disable_msi(struct pci_dev *dev);
|
||||
extern int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries, int minvec, int maxvec);
|
||||
extern void pci_disable_msix(struct pci_dev *dev);
|
||||
|
||||
extern void *pci_iomap(struct pci_dev *dev, unsigned int bar, size_t max_len);
|
||||
extern void pci_iounmap(struct pci_dev *dev, void *addr, size_t size);
|
||||
@@ -70,6 +78,20 @@ extern int pci_write_config_dword(struct pci_dev *dev, unsigned int offset, u32
|
||||
extern u64 pci_resource_start(struct pci_dev *dev, unsigned int bar);
|
||||
extern u64 pci_resource_len(struct pci_dev *dev, unsigned int bar);
|
||||
|
||||
extern u64 pci_get_quirk_flags(struct pci_dev *dev);
|
||||
extern bool pci_has_quirk(struct pci_dev *dev, u64 flag);
|
||||
|
||||
#define PCI_QUIRK_NO_MSI (1ULL << 0)
|
||||
#define PCI_QUIRK_NO_MSIX (1ULL << 1)
|
||||
#define PCI_QUIRK_FORCE_LEGACY (1ULL << 2)
|
||||
#define PCI_QUIRK_NO_PM (1ULL << 3)
|
||||
#define PCI_QUIRK_NO_D3COLD (1ULL << 4)
|
||||
#define PCI_QUIRK_NO_ASPM (1ULL << 5)
|
||||
#define PCI_QUIRK_NEED_IOMMU (1ULL << 6)
|
||||
#define PCI_QUIRK_DMA_32BIT_ONLY (1ULL << 8)
|
||||
#define PCI_QUIRK_NEED_FIRMWARE (1ULL << 11)
|
||||
#define PCI_QUIRK_DISABLE_ACCEL (1ULL << 12)
|
||||
|
||||
extern int pci_register_driver(struct pci_driver *drv);
|
||||
extern void pci_unregister_driver(struct pci_driver *drv);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ struct sk_buff_head {
|
||||
struct sk_buff *next;
|
||||
struct sk_buff *prev;
|
||||
u32 qlen;
|
||||
unsigned char lock;
|
||||
};
|
||||
|
||||
extern struct sk_buff *alloc_skb(unsigned int size, gfp_t gfp_mask);
|
||||
@@ -32,10 +33,14 @@ extern void skb_queue_head_init(struct sk_buff_head *list);
|
||||
extern void skb_queue_tail(struct sk_buff_head *list, struct sk_buff *newsk);
|
||||
extern struct sk_buff *skb_dequeue(struct sk_buff_head *list);
|
||||
extern void skb_queue_purge(struct sk_buff_head *list);
|
||||
extern struct sk_buff *skb_peek(const struct sk_buff_head *list);
|
||||
extern u32 skb_queue_len(const struct sk_buff_head *list);
|
||||
extern int skb_queue_empty(const struct sk_buff_head *list);
|
||||
extern struct sk_buff *__netdev_alloc_skb(struct net_device *dev, u32 length, gfp_t gfp_mask);
|
||||
#define dev_alloc_skb(length) __netdev_alloc_skb(NULL, length, GFP_KERNEL)
|
||||
extern struct sk_buff *skb_copy(const struct sk_buff *src, gfp_t gfp);
|
||||
extern struct sk_buff *skb_clone(const struct sk_buff *skb, gfp_t gfp);
|
||||
extern void skb_set_network_header(struct sk_buff *skb, int offset);
|
||||
extern void skb_reset_mac_header(struct sk_buff *skb);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef _LINUX_SLAB_H
|
||||
#define _LINUX_SLAB_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "types.h"
|
||||
#include <stddef.h>
|
||||
|
||||
#define GFP_KERNEL 0U
|
||||
|
||||
@@ -19,7 +19,11 @@ static inline void init_waitqueue_head(struct wait_queue_head *wq)
|
||||
do { while (!(condition)) { __asm__ volatile("pause"); } } while(0)
|
||||
|
||||
#define wait_event_timeout(wq, condition, timeout) \
|
||||
({ (void)(wq); (condition) ? 1 : 0; })
|
||||
({ (void)(wq); unsigned long _deadline = jiffies + (timeout); \
|
||||
int _ret = 0; \
|
||||
while (!(_ret = !!(condition)) && time_before(jiffies, _deadline)) { \
|
||||
__asm__ volatile("pause"); \
|
||||
} _ret; })
|
||||
|
||||
#define wait_event_interruptible(wq, condition) \
|
||||
({ (void)(wq); (condition) ? 0 : -512; })
|
||||
|
||||
@@ -51,6 +51,17 @@ struct key_params {
|
||||
const u8 *key;
|
||||
u8 key_len;
|
||||
u32 cipher;
|
||||
u8 key_idx;
|
||||
};
|
||||
|
||||
struct cfg80211_bss {
|
||||
u8 bssid[6];
|
||||
struct ieee80211_channel *channel;
|
||||
s16 signal;
|
||||
u16 capability;
|
||||
u16 beacon_interval;
|
||||
const u8 *ies;
|
||||
size_t ies_len;
|
||||
};
|
||||
|
||||
struct cfg80211_connect_params {
|
||||
@@ -99,9 +110,11 @@ extern void cfg80211_connect_bss(struct net_device *dev,
|
||||
u16 status,
|
||||
gfp_t gfp);
|
||||
extern void cfg80211_new_sta(struct net_device *dev, const u8 *mac_addr,
|
||||
struct station_parameters *params, gfp_t gfp);
|
||||
struct station_parameters *params, gfp_t gfp);
|
||||
extern void cfg80211_rx_mgmt(struct wireless_dev *wdev, u32 freq, int sig_dbm,
|
||||
const u8 *buf, size_t len, gfp_t gfp);
|
||||
const u8 *buf, size_t len, gfp_t gfp);
|
||||
extern void cfg80211_mgmt_tx_status(struct wireless_dev *wdev, u64 cookie,
|
||||
const u8 *buf, size_t len, bool ack, gfp_t gfp);
|
||||
extern void cfg80211_sched_scan_results(struct wiphy *wiphy, u64 reqid);
|
||||
extern void cfg80211_ready_on_channel(struct wireless_dev *wdev,
|
||||
u64 cookie,
|
||||
@@ -109,5 +122,19 @@ extern void cfg80211_ready_on_channel(struct wireless_dev *wdev,
|
||||
u32 chan_type,
|
||||
u32 duration,
|
||||
gfp_t gfp);
|
||||
extern u32 ieee80211_channel_to_frequency(u32 chan, u32 band);
|
||||
extern u32 ieee80211_frequency_to_channel(u32 freq);
|
||||
extern struct cfg80211_bss *cfg80211_inform_bss(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev,
|
||||
u32 freq,
|
||||
const u8 *bssid,
|
||||
u64 tsf,
|
||||
u16 capability,
|
||||
u16 beacon_interval,
|
||||
const u8 *ie,
|
||||
size_t ielen,
|
||||
int signal,
|
||||
gfp_t gfp);
|
||||
extern void cfg80211_put_bss(struct cfg80211_bss *bss);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
struct ieee80211_hw {
|
||||
struct wiphy *wiphy;
|
||||
const struct ieee80211_ops *ops;
|
||||
void *priv;
|
||||
int registered;
|
||||
u32 extra_tx_headroom;
|
||||
@@ -32,6 +33,25 @@ struct ieee80211_bss_conf {
|
||||
bool assoc;
|
||||
u16 aid;
|
||||
u16 beacon_int;
|
||||
u8 bssid[6];
|
||||
u32 basic_rates;
|
||||
u8 bandwidth;
|
||||
struct {
|
||||
u32 center_freq;
|
||||
u16 band;
|
||||
void *channel;
|
||||
} chandef;
|
||||
};
|
||||
|
||||
struct ieee80211_rx_status {
|
||||
u16 freq;
|
||||
u32 band;
|
||||
s8 signal;
|
||||
s8 noise;
|
||||
u8 rate_idx;
|
||||
u32 flag;
|
||||
u8 antenna;
|
||||
u32 rx_flags;
|
||||
};
|
||||
|
||||
enum ieee80211_sta_state {
|
||||
@@ -62,8 +82,13 @@ struct ieee80211_ops {
|
||||
int (*set_key)(struct ieee80211_hw *hw, enum set_key_cmd cmd,
|
||||
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
|
||||
struct key_params *key);
|
||||
int (*ampdu_action)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta, u16 action, u16 tid, u16 ssn);
|
||||
void (*sw_scan_start)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac_addr);
|
||||
void (*sw_scan_complete)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
|
||||
u64 (*prepare_multicast)(struct ieee80211_hw *hw, void *mc_list);
|
||||
void (*configure_filter)(struct ieee80211_hw *hw, u32 changed_flags,
|
||||
u32 *total_flags, u64 multicast);
|
||||
int (*sched_scan_start)(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void *req);
|
||||
void (*sched_scan_stop)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
|
||||
};
|
||||
@@ -87,6 +112,11 @@ extern void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted);
|
||||
extern void ieee80211_connection_loss(struct ieee80211_vif *vif);
|
||||
extern int ieee80211_start_tx_ba_session(struct ieee80211_sta *sta, u16 tid, u16 timeout);
|
||||
extern int ieee80211_stop_tx_ba_session(struct ieee80211_sta *sta, u16 tid);
|
||||
extern int ieee80211_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta, u32 old_state, u32 new_state);
|
||||
extern struct ieee80211_sta *ieee80211_find_sta(struct ieee80211_hw *hw, const u8 *addr);
|
||||
extern void ieee80211_beacon_loss(struct ieee80211_vif *vif);
|
||||
extern void ieee80211_rx_irqsafe(struct ieee80211_hw *hw, struct sk_buff *skb);
|
||||
extern size_t ieee80211_rx_drain(struct ieee80211_hw *hw);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,5 +17,7 @@ pub use rust_impl::memory;
|
||||
pub use rust_impl::net;
|
||||
pub use rust_impl::pci;
|
||||
pub use rust_impl::sync;
|
||||
pub use rust_impl::timer;
|
||||
pub use rust_impl::wait;
|
||||
pub use rust_impl::wireless;
|
||||
pub use rust_impl::workqueue;
|
||||
|
||||
@@ -100,3 +100,56 @@ pub extern "C" fn devres_free_all(dev: *mut u8) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn devm_kzalloc_and_devm_kfree_round_trip() {
|
||||
let dev: *mut u8 = 0xDEAD_0000 as *mut u8;
|
||||
let ptr = devm_kzalloc(dev, 64, 0);
|
||||
assert!(!ptr.is_null(), "devm_kzalloc should return non-null");
|
||||
|
||||
let bytes = unsafe { std::slice::from_raw_parts(ptr, 64) };
|
||||
assert!(
|
||||
bytes.iter().all(|&b| b == 0),
|
||||
"devm_kzalloc should zero memory"
|
||||
);
|
||||
|
||||
devm_kfree(dev, ptr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devm_kfree_null_is_safe() {
|
||||
devm_kfree(0xDEAD_0001 as *mut u8, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devres_free_all_cleans_up() {
|
||||
let dev: *mut u8 = 0xBEEF_0000 as *mut u8;
|
||||
let p1 = devm_kzalloc(dev, 32, 0);
|
||||
let p2 = devm_kzalloc(dev, 64, 0);
|
||||
assert!(!p1.is_null());
|
||||
assert!(!p2.is_null());
|
||||
|
||||
devres_free_all(dev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devres_free_all_null_is_safe() {
|
||||
devres_free_all(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn devm_kzalloc_null_dev_returns_untracked() {
|
||||
let ptr = devm_kzalloc(std::ptr::null_mut(), 32, 0);
|
||||
assert!(!ptr.is_null());
|
||||
crate::rust_impl::memory::kfree(ptr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracked_layout_rejects_zero_size() {
|
||||
assert!(tracked_layout(0, 0).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::alloc::{alloc_zeroed, dealloc, Layout};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_char, c_void, CStr};
|
||||
use std::ptr;
|
||||
use std::sync::atomic::{fence, Ordering};
|
||||
@@ -10,6 +11,7 @@ lazy_static::lazy_static! {
|
||||
.ok()
|
||||
.map(|fd| fd)
|
||||
};
|
||||
static ref DMA_MASK_TRACKER: Mutex<HashMap<usize, u64>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
@@ -157,15 +159,38 @@ pub extern "C" fn dma_map_single(_dev: *mut u8, ptr: *mut u8, _size: usize, _dir
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dma_unmap_single(_dev: *mut u8, _addr: u64, _size: usize, _dir: u32) {}
|
||||
pub extern "C" fn dma_unmap_single(_dev: *mut u8, addr: u64, _size: usize, _dir: u32) {
|
||||
if addr == 0 {
|
||||
return;
|
||||
}
|
||||
log::trace!("dma_unmap_single: addr={:#x}", addr);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dma_set_mask(_dev: *mut u8, _mask: u64) -> i32 {
|
||||
pub extern "C" fn dma_set_mask(dev: *mut u8, mask: u64) -> i32 {
|
||||
if dev.is_null() {
|
||||
return -22;
|
||||
}
|
||||
if let Ok(mut tracker) = DMA_MASK_TRACKER.lock() {
|
||||
tracker.insert(dev as usize, mask);
|
||||
}
|
||||
log::debug!("dma_set_mask: dev={:#x} mask={:#x}", dev as usize, mask);
|
||||
0
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dma_set_coherent_mask(_dev: *mut u8, _mask: u64) -> i32 {
|
||||
pub extern "C" fn dma_set_coherent_mask(dev: *mut u8, mask: u64) -> i32 {
|
||||
if dev.is_null() {
|
||||
return -22;
|
||||
}
|
||||
if let Ok(mut tracker) = DMA_MASK_TRACKER.lock() {
|
||||
tracker.insert(dev as usize | 0x1, mask);
|
||||
}
|
||||
log::debug!(
|
||||
"dma_set_coherent_mask: dev={:#x} mask={:#x}",
|
||||
dev as usize,
|
||||
mask
|
||||
);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -338,7 +363,12 @@ pub extern "C" fn dma_map_page(
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dma_unmap_page(_dev: *mut u8, _addr: u64, _size: usize, _dir: u32) {}
|
||||
pub extern "C" fn dma_unmap_page(_dev: *mut u8, addr: u64, _size: usize, _dir: u32) {
|
||||
if addr == 0 {
|
||||
return;
|
||||
}
|
||||
log::trace!("dma_unmap_page: addr={:#x}", addr);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn dma_mapping_error(_dev: *mut u8, addr: u64) -> i32 {
|
||||
|
||||
@@ -45,7 +45,10 @@ where
|
||||
if guard.is_none() {
|
||||
*guard = Some(HashMap::new());
|
||||
}
|
||||
f(guard.as_mut().unwrap())
|
||||
match guard.as_mut() {
|
||||
Some(map) => f(map),
|
||||
None => f(&mut HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_handles<F, R>(f: F) -> R
|
||||
@@ -56,7 +59,10 @@ where
|
||||
if guard.is_none() {
|
||||
*guard = Some(BTreeMap::new());
|
||||
}
|
||||
f(guard.as_mut().unwrap())
|
||||
match guard.as_mut() {
|
||||
Some(map) => f(map),
|
||||
None => f(&mut BTreeMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn next_gem_handle() -> u32 {
|
||||
@@ -263,3 +269,105 @@ pub extern "C" fn drm_connector_register(_connector: *mut u8) -> i32 {
|
||||
pub extern "C" fn drm_crtc_handle_vblank(_crtc: *mut u8) -> u32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn alloc_fake_obj() -> *mut u8 {
|
||||
let layout = std::alloc::Layout::from_size_align(256, 8).unwrap();
|
||||
let ptr = unsafe { std::alloc::alloc(layout) };
|
||||
assert!(!ptr.is_null());
|
||||
ptr
|
||||
}
|
||||
|
||||
fn free_fake_obj(ptr: *mut u8) {
|
||||
let layout = std::alloc::Layout::from_size_align(256, 8).unwrap();
|
||||
unsafe { std::alloc::dealloc(ptr, layout) };
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_gem_object_init_and_release() {
|
||||
let obj = alloc_fake_obj();
|
||||
let rc = drm_gem_object_init(std::ptr::null_mut(), obj, 4096);
|
||||
assert_eq!(rc, 0);
|
||||
drm_gem_object_release(obj);
|
||||
free_fake_obj(obj);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_gem_handle_create_and_lookup() {
|
||||
let file: *mut u8 = 0x1100 as *mut u8;
|
||||
let obj = alloc_fake_obj();
|
||||
drm_gem_object_init(std::ptr::null_mut(), obj, 1024);
|
||||
|
||||
let mut handle: u32 = 0;
|
||||
let rc = drm_gem_handle_create(file, obj, &mut handle);
|
||||
assert_eq!(rc, 0, "handle_create should succeed");
|
||||
assert!(handle > 0, "handle should be nonzero");
|
||||
|
||||
let found = drm_gem_object_lookup(file, handle);
|
||||
assert_eq!(found, obj, "lookup should return the original object");
|
||||
|
||||
let not_found = drm_gem_object_lookup(file, 99999);
|
||||
assert_eq!(
|
||||
not_found,
|
||||
std::ptr::null_mut(),
|
||||
"invalid handle should return null"
|
||||
);
|
||||
|
||||
drm_gem_object_release(obj);
|
||||
free_fake_obj(obj);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_gem_handle_delete_removes_mapping() {
|
||||
let file: *mut u8 = 0x1200 as *mut u8;
|
||||
let obj = alloc_fake_obj();
|
||||
drm_gem_object_init(std::ptr::null_mut(), obj, 2048);
|
||||
|
||||
let mut handle: u32 = 0;
|
||||
drm_gem_handle_create(file, obj, &mut handle);
|
||||
assert!(handle > 0);
|
||||
|
||||
drm_gem_handle_delete(file, handle);
|
||||
let found = drm_gem_object_lookup(file, handle);
|
||||
assert_eq!(
|
||||
found,
|
||||
std::ptr::null_mut(),
|
||||
"deleted handle should return null"
|
||||
);
|
||||
|
||||
drm_gem_object_release(obj);
|
||||
free_fake_obj(obj);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_dev_register_and_unregister_are_callable() {
|
||||
let dev: *mut u8 = 0x1300 as *mut u8;
|
||||
assert_eq!(drm_dev_register(dev, 0), 0);
|
||||
drm_dev_unregister(dev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_ioctl_returns_zero() {
|
||||
assert_eq!(
|
||||
drm_ioctl(
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut()
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drm_null_pointers_are_safe() {
|
||||
drm_gem_object_release(std::ptr::null_mut());
|
||||
drm_dev_unregister(std::ptr::null_mut());
|
||||
drm_mode_config_reset(std::ptr::null_mut());
|
||||
assert_eq!(drm_connector_register(std::ptr::null_mut()), 0);
|
||||
assert_eq!(drm_crtc_handle_vblank(std::ptr::null_mut()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,3 +149,95 @@ pub extern "C" fn idr_destroy(idr: *mut Idr) {
|
||||
idr_ref.map.clear();
|
||||
idr_ref.next_id = 0;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn idr_alloc_and_find_round_trip() {
|
||||
let mut idr = std::mem::MaybeUninit::<Idr>::uninit();
|
||||
idr_init(idr.as_mut_ptr());
|
||||
|
||||
let ptr1: *mut u8 = 0x1000 as *mut u8;
|
||||
let id1 = idr_alloc(idr.as_mut_ptr(), ptr1, 1, 0, 0);
|
||||
assert!(id1 >= 1, "allocated ID should be >= start");
|
||||
|
||||
assert_eq!(idr_find(idr.as_mut_ptr(), id1 as u32), ptr1);
|
||||
assert_eq!(idr_find(idr.as_mut_ptr(), 9999), std::ptr::null_mut());
|
||||
|
||||
idr_destroy(idr.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idr_remove_frees_slot() {
|
||||
let mut idr = std::mem::MaybeUninit::<Idr>::uninit();
|
||||
idr_init(idr.as_mut_ptr());
|
||||
|
||||
let ptr1: *mut u8 = 0x2000 as *mut u8;
|
||||
let id1 = idr_alloc(idr.as_mut_ptr(), ptr1, 10, 0, 0);
|
||||
assert!(id1 >= 10);
|
||||
|
||||
idr_remove(idr.as_mut_ptr(), id1 as u32);
|
||||
assert_eq!(idr_find(idr.as_mut_ptr(), id1 as u32), std::ptr::null_mut());
|
||||
|
||||
idr_destroy(idr.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idr_alloc_with_bounded_range() {
|
||||
let mut idr = std::mem::MaybeUninit::<Idr>::uninit();
|
||||
idr_init(idr.as_mut_ptr());
|
||||
|
||||
let ptr1: *mut u8 = 0x3000 as *mut u8;
|
||||
let id1 = idr_alloc(idr.as_mut_ptr(), ptr1, 5, 8, 0);
|
||||
assert!(id1 >= 5 && id1 < 8, "ID should be in [5, 8)");
|
||||
|
||||
idr_destroy(idr.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idr_alloc_returns_enospc_when_full() {
|
||||
let mut idr = std::mem::MaybeUninit::<Idr>::uninit();
|
||||
idr_init(idr.as_mut_ptr());
|
||||
|
||||
let ptr1: *mut u8 = 0x4000 as *mut u8;
|
||||
let id1 = idr_alloc(idr.as_mut_ptr(), ptr1, 1, 2, 0);
|
||||
assert_eq!(id1, 1);
|
||||
|
||||
let ptr2: *mut u8 = 0x4001 as *mut u8;
|
||||
let id2 = idr_alloc(idr.as_mut_ptr(), ptr2, 1, 2, 0);
|
||||
assert_eq!(id2, -ENOSPC);
|
||||
|
||||
idr_destroy(idr.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idr_null_pointers_are_safe() {
|
||||
assert_eq!(
|
||||
idr_alloc(std::ptr::null_mut(), std::ptr::null_mut(), 1, 0, 0),
|
||||
-EINVAL
|
||||
);
|
||||
assert_eq!(idr_find(std::ptr::null_mut(), 1), std::ptr::null_mut());
|
||||
idr_remove(std::ptr::null_mut(), 1);
|
||||
idr_destroy(std::ptr::null_mut());
|
||||
idr_init(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn idr_alloc_reuses_removed_id() {
|
||||
let mut idr = std::mem::MaybeUninit::<Idr>::uninit();
|
||||
idr_init(idr.as_mut_ptr());
|
||||
|
||||
let ptr1: *mut u8 = 0x5000 as *mut u8;
|
||||
let id1 = idr_alloc(idr.as_mut_ptr(), ptr1, 1, 0, 0);
|
||||
|
||||
idr_remove(idr.as_mut_ptr(), id1 as u32);
|
||||
|
||||
let ptr2: *mut u8 = 0x5001 as *mut u8;
|
||||
let id2 = idr_alloc(idr.as_mut_ptr(), ptr2, 1, 0, 0);
|
||||
assert_eq!(id2, id1, "should reuse removed ID");
|
||||
|
||||
idr_destroy(idr.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub type IrqHandler = extern "C" fn(i32, *mut u8) -> u32;
|
||||
|
||||
struct IrqEntry {
|
||||
cancel: Arc<AtomicBool>,
|
||||
masked: Arc<AtomicBool>,
|
||||
fd: Option<File>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
@@ -51,7 +52,9 @@ pub extern "C" fn request_irq(
|
||||
};
|
||||
|
||||
let cancel = Arc::new(AtomicBool::new(false));
|
||||
let masked = Arc::new(AtomicBool::new(false));
|
||||
let cancel_clone = Arc::clone(&cancel);
|
||||
let masked_clone = Arc::clone(&masked);
|
||||
let send_dev_id = SendU8Ptr(dev_id);
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
@@ -69,6 +72,9 @@ pub extern "C" fn request_irq(
|
||||
if cancel_clone.load(Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
if masked_clone.load(Ordering::Acquire) {
|
||||
continue;
|
||||
}
|
||||
handler(irq as i32, send_dev_id.as_ptr());
|
||||
}
|
||||
}
|
||||
@@ -77,6 +83,7 @@ pub extern "C" fn request_irq(
|
||||
|
||||
let entry = IrqEntry {
|
||||
cancel: Arc::clone(&cancel),
|
||||
masked: Arc::clone(&masked),
|
||||
fd: Some(fd),
|
||||
handle: Some(handle),
|
||||
};
|
||||
@@ -120,7 +127,102 @@ pub extern "C" fn free_irq(irq: u32, _dev_id: *mut u8) {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn enable_irq(_irq: u32) {}
|
||||
pub extern "C" fn disable_irq_nosync(irq: u32) {
|
||||
disable_irq(irq)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn disable_irq(_irq: u32) {}
|
||||
pub extern "C" fn enable_irq(irq: u32) {
|
||||
if let Ok(table) = IRQ_TABLE.lock() {
|
||||
if let Some(entry) = table.get(&irq) {
|
||||
entry.masked.store(false, Ordering::Release);
|
||||
log::trace!("enable_irq: unmasked IRQ {}", irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn disable_irq(irq: u32) {
|
||||
if let Ok(table) = IRQ_TABLE.lock() {
|
||||
if let Some(entry) = table.get(&irq) {
|
||||
entry.masked.store(true, Ordering::Release);
|
||||
log::trace!("disable_irq: masked IRQ {}", irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn request_irq_returns_error_without_scheme() {
|
||||
let result = request_irq(99, test_handler, 0, std::ptr::null(), std::ptr::null_mut());
|
||||
assert_eq!(
|
||||
result, -22,
|
||||
"request_irq should return -EINVAL without /scheme/irq/"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn free_irq_on_unregistered_is_safe() {
|
||||
free_irq(42, std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enable_irq_on_unregistered_is_noop() {
|
||||
enable_irq(42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disable_irq_on_unregistered_is_noop() {
|
||||
disable_irq(42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disable_irq_nosync_is_equivalent_to_disable_irq() {
|
||||
// Insert a synthetic entry directly, then verify disable_irq_nosync
|
||||
// sets the same masked state as disable_irq.
|
||||
let cancel = Arc::new(AtomicBool::new(false));
|
||||
let masked = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let entry = IrqEntry {
|
||||
cancel,
|
||||
masked: Arc::clone(&masked),
|
||||
fd: None,
|
||||
handle: None,
|
||||
};
|
||||
|
||||
{
|
||||
let mut table = IRQ_TABLE.lock().unwrap();
|
||||
table.insert(200, entry);
|
||||
}
|
||||
|
||||
disable_irq_nosync(200);
|
||||
assert!(
|
||||
masked.load(Ordering::Acquire),
|
||||
"disable_irq_nosync should set masked=true"
|
||||
);
|
||||
|
||||
enable_irq(200);
|
||||
assert!(
|
||||
!masked.load(Ordering::Acquire),
|
||||
"enable_irq should set masked=false"
|
||||
);
|
||||
|
||||
disable_irq(200);
|
||||
assert!(
|
||||
masked.load(Ordering::Acquire),
|
||||
"disable_irq should set masked=true"
|
||||
);
|
||||
|
||||
{
|
||||
let mut table = IRQ_TABLE.lock().unwrap();
|
||||
table.remove(&200);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn test_handler(_irq: i32, _dev_id: *mut u8) -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ const EBUSY: i32 = 16;
|
||||
lazy_static::lazy_static! {
|
||||
static ref STA_REGISTRY: Mutex<HashMap<usize, StaRegistryEntry>> = Mutex::new(HashMap::new());
|
||||
static ref BA_SESSIONS: Mutex<HashMap<usize, Vec<u16>>> = Mutex::new(HashMap::new());
|
||||
static ref RX_QUEUE: Mutex<Vec<(usize, usize)>> = Mutex::new(Vec::new());
|
||||
static ref HW_WDEV_MAP: Mutex<HashMap<usize, usize>> = Mutex::new(HashMap::new());
|
||||
static ref RX_CALLBACKS: Mutex<HashMap<usize, usize>> = Mutex::new(HashMap::new());
|
||||
static ref TX_STATS: Mutex<HashMap<usize, TxStats>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -26,6 +30,15 @@ struct StaRegistryEntry {
|
||||
state: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct TxStats {
|
||||
pub total: u64,
|
||||
pub acked: u64,
|
||||
pub nacked: u64,
|
||||
}
|
||||
|
||||
pub type RxCallback = extern "C" fn(*mut Ieee80211Hw, *mut SkBuff);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Ieee80211Ops {
|
||||
pub tx: Option<extern "C" fn(*mut Ieee80211Hw, *mut SkBuff)>,
|
||||
@@ -177,8 +190,37 @@ pub extern "C" fn ieee80211_free_hw(hw: *mut Ieee80211Hw) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
let hw_key = hw as usize;
|
||||
|
||||
if let Ok(mut map) = HW_WDEV_MAP.lock() {
|
||||
map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut map) = RX_CALLBACKS.lock() {
|
||||
map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut stats_map) = TX_STATS.lock() {
|
||||
stats_map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut queue) = RX_QUEUE.lock() {
|
||||
let mut i = 0;
|
||||
while i < queue.len() {
|
||||
if queue[i].0 == hw_key {
|
||||
let (_, skb_key) = queue.swap_remove(i);
|
||||
let skb = skb_key as *mut SkBuff;
|
||||
if !skb.is_null() {
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut registry) = STA_REGISTRY.lock() {
|
||||
registry.retain(|_, entry| entry.hw != hw as usize);
|
||||
registry.retain(|_, entry| entry.hw != hw_key);
|
||||
}
|
||||
unsafe {
|
||||
let hw_box = Box::from_raw(hw);
|
||||
@@ -230,8 +272,93 @@ pub extern "C" fn ieee80211_queue_work(_hw: *mut Ieee80211Hw, work: *mut c_void)
|
||||
let _ = schedule_work(work.cast::<WorkStruct>());
|
||||
}
|
||||
|
||||
/// Register the WirelessDev associated with an Ieee80211Hw.
|
||||
/// Must be called by the driver after ieee80211_alloc_hw_nm and before scan/connect.
|
||||
/// Required for ieee80211_scan_completed to find the correct wdev.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_scan_completed(_hw: *mut Ieee80211Hw, _aborted: bool) {}
|
||||
pub extern "C" fn ieee80211_link_hw_wdev(
|
||||
hw: *mut Ieee80211Hw,
|
||||
wdev: *mut super::wireless::WirelessDev,
|
||||
) {
|
||||
if hw.is_null() || wdev.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut map) = HW_WDEV_MAP.lock() {
|
||||
map.insert(hw as usize, wdev as usize);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a per-hw callback that receives drained RX frames.
|
||||
/// When `ieee80211_rx_drain` processes a queued frame and a callback
|
||||
/// is registered for the hw instance, the frame is delivered to the
|
||||
/// callback instead of being logged and freed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_register_rx_handler(
|
||||
hw: *mut Ieee80211Hw,
|
||||
callback: Option<RxCallback>,
|
||||
) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut map) = RX_CALLBACKS.lock() {
|
||||
match callback {
|
||||
Some(cb) => {
|
||||
map.insert(hw as usize, cb as usize);
|
||||
}
|
||||
None => {
|
||||
map.remove(&(hw as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve accumulated TX statistics for a given hw instance.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_get_tx_stats(hw: *mut Ieee80211Hw) -> TxStats {
|
||||
if hw.is_null() {
|
||||
return TxStats::default();
|
||||
}
|
||||
if let Ok(stats_map) = TX_STATS.lock() {
|
||||
stats_map.get(&(hw as usize)).copied().unwrap_or_default()
|
||||
} else {
|
||||
TxStats::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_scan_completed(hw: *mut Ieee80211Hw, aborted: bool) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let wiphy = unsafe { (*hw).wiphy };
|
||||
if wiphy.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let wdev_ptr = match HW_WDEV_MAP.lock() {
|
||||
Ok(map) => match map.get(&(hw as usize)) {
|
||||
Some(&ptr) => ptr as *mut super::wireless::WirelessDev,
|
||||
None => {
|
||||
log::warn!(
|
||||
"ieee80211_scan_completed: no wdev registered for hw={:#x}",
|
||||
hw as usize
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let scan_info = super::wireless::Cfg80211ScanInfo { aborted };
|
||||
let mut scan_request = super::wireless::Cfg80211ScanRequest {
|
||||
wiphy,
|
||||
wdev: wdev_ptr,
|
||||
n_ssids: 0,
|
||||
n_channels: 0,
|
||||
};
|
||||
super::wireless::cfg80211_scan_done(&mut scan_request, &scan_info);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_connection_loss(vif: *mut Ieee80211Vif) {
|
||||
@@ -291,6 +418,129 @@ pub extern "C" fn ieee80211_rx_irqsafe(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
if hw.is_null() || skb.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let hw_key = hw as usize;
|
||||
let skb_key = skb as usize;
|
||||
if let Ok(mut queue) = RX_QUEUE.lock() {
|
||||
queue.push((hw_key, skb_key));
|
||||
log::trace!(
|
||||
"ieee80211_rx_irqsafe: queued frame hw={:#x} skb={:#x} queue_len={}",
|
||||
hw_key,
|
||||
skb_key,
|
||||
queue.len()
|
||||
);
|
||||
} else {
|
||||
log::warn!("ieee80211_rx_irqsafe: failed to lock RX queue, dropping frame");
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain and consume all queued RX frames for a specific hw instance.
|
||||
/// If an RX handler has been registered via ieee80211_register_rx_handler,
|
||||
/// frames are delivered to that handler. Otherwise frames are classified,
|
||||
/// logged, and freed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_rx_drain(hw: *mut Ieee80211Hw) -> usize {
|
||||
if hw.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let hw_key = hw as usize;
|
||||
|
||||
let rx_callback = if let Ok(map) = RX_CALLBACKS.lock() {
|
||||
map.get(&hw_key).copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Ok(mut queue) = RX_QUEUE.lock() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let mut drained = 0usize;
|
||||
let mut i = 0;
|
||||
while i < queue.len() {
|
||||
if queue[i].0 == hw_key {
|
||||
let (_, skb_key) = queue.swap_remove(i);
|
||||
let skb = skb_key as *mut SkBuff;
|
||||
if !skb.is_null() {
|
||||
if let Some(cb) = rx_callback {
|
||||
let callback: RxCallback = unsafe { std::mem::transmute(cb) };
|
||||
callback(hw, skb);
|
||||
} else {
|
||||
let skb_ref = unsafe { &mut *skb };
|
||||
let frame_type = extract_frame_type(skb_ref);
|
||||
let frame_len = skb_ref.len;
|
||||
|
||||
match frame_type {
|
||||
FrameType::Management(subtype) => {
|
||||
log::debug!(
|
||||
"rx_drain: mgmt subtype={} len={} hw={:#x}",
|
||||
subtype,
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
FrameType::Data => {
|
||||
log::debug!("rx_drain: data frame len={} hw={:#x}", frame_len, hw_key);
|
||||
}
|
||||
FrameType::Control(subtype) => {
|
||||
log::trace!(
|
||||
"rx_drain: ctrl subtype={} len={} hw={:#x}",
|
||||
subtype,
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
FrameType::Unknown => {
|
||||
log::trace!(
|
||||
"rx_drain: unknown frame len={} hw={:#x}",
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
drained += 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if drained > 0 {
|
||||
log::debug!(
|
||||
"ieee80211_rx_drain: hw={:#x} drained {} frames",
|
||||
hw_key,
|
||||
drained
|
||||
);
|
||||
}
|
||||
drained
|
||||
}
|
||||
|
||||
enum FrameType {
|
||||
Management(u8),
|
||||
Data,
|
||||
Control(u8),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
fn extract_frame_type(skb: &SkBuff) -> FrameType {
|
||||
let len = (skb.len as usize).min(2);
|
||||
if len < 2 {
|
||||
return FrameType::Unknown;
|
||||
}
|
||||
let data = unsafe { std::slice::from_raw_parts(skb.data, len) };
|
||||
let frame_ctl = u16::from_le_bytes([data[0], data[1]]);
|
||||
let type_val = ((frame_ctl >> 2) & 0x3) as u8;
|
||||
let subtype = ((frame_ctl >> 4) & 0xF) as u8;
|
||||
match type_val {
|
||||
0 => FrameType::Management(subtype),
|
||||
1 => FrameType::Control(subtype),
|
||||
2 => FrameType::Data,
|
||||
_ => FrameType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -298,6 +548,20 @@ pub extern "C" fn ieee80211_tx_status(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
if hw.is_null() || skb.is_null() {
|
||||
return;
|
||||
}
|
||||
let hw_key = hw as usize;
|
||||
|
||||
if let Ok(mut stats_map) = TX_STATS.lock() {
|
||||
let stats = stats_map.entry(hw_key).or_default();
|
||||
stats.total += 1;
|
||||
stats.acked += 1;
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"ieee80211_tx_status: hw={:#x} skb={:#x}",
|
||||
hw_key,
|
||||
skb as usize
|
||||
);
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -305,19 +569,53 @@ pub extern "C" fn ieee80211_get_tid(skb: *const SkBuff) -> u8 {
|
||||
if skb.is_null() {
|
||||
return 0;
|
||||
}
|
||||
unsafe {
|
||||
let s = &*skb;
|
||||
if s.data.is_null() || s.len < 26 {
|
||||
return 0;
|
||||
}
|
||||
let frame_control = u16::from_le_bytes([*s.data, *s.data.add(1)]);
|
||||
let subtype = (frame_control >> 4) & 0xF;
|
||||
if subtype != 0x8 {
|
||||
return 0;
|
||||
}
|
||||
let qos = (frame_control >> 7) & 0x1;
|
||||
if qos == 0 {
|
||||
return 0;
|
||||
}
|
||||
let qos_offset: usize = 24;
|
||||
if (s.len as usize) < qos_offset + 2 {
|
||||
return 0;
|
||||
}
|
||||
let tid = (*s.data.add(qos_offset)) & 0xF;
|
||||
tid
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
#[repr(C)]
|
||||
struct ChanDef {
|
||||
center_freq: u32,
|
||||
band: u16,
|
||||
channel: *mut c_void,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_chandef_create(
|
||||
chandef: *mut c_void,
|
||||
channel: *const super::wireless::Ieee80211Channel,
|
||||
_chan_type: u32,
|
||||
chan_type: u32,
|
||||
) {
|
||||
if chandef.is_null() || channel.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let cd = chandef.cast::<ChanDef>();
|
||||
let ch = &*channel;
|
||||
(*cd).center_freq = ch.center_freq as u32;
|
||||
(*cd).band = ch.band as u16;
|
||||
(*cd).channel = channel as *mut c_void;
|
||||
let _ = chan_type;
|
||||
}
|
||||
}
|
||||
|
||||
pub const IEEE80211_STA_NOTEXIST: u32 = 0;
|
||||
@@ -587,4 +885,74 @@ mod tests {
|
||||
fn ieee80211_get_tid_returns_zero_for_null() {
|
||||
assert_eq!(ieee80211_get_tid(ptr::null()), 0);
|
||||
}
|
||||
|
||||
static RX_RECEIVED: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
extern "C" fn test_rx_callback(_hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
RX_RECEIVED.fetch_add(1, Ordering::Release);
|
||||
super::super::net::kfree_skb(skb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rx_callback_receives_drained_frames() {
|
||||
let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null());
|
||||
assert!(!hw.is_null());
|
||||
|
||||
RX_RECEIVED.store(0, Ordering::Release);
|
||||
ieee80211_register_rx_handler(hw, Some(test_rx_callback));
|
||||
|
||||
let data: [u8; 24] = [
|
||||
0x88u8, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x06, 0x05, 0x04, 0x03,
|
||||
0x02, 0x01, 0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00, 0x08, 0x06,
|
||||
];
|
||||
let skb = super::super::net::alloc_skb(128, 0);
|
||||
assert!(!skb.is_null());
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(data.as_ptr(), (*skb).data, data.len());
|
||||
(*skb).len = data.len() as u32;
|
||||
}
|
||||
ieee80211_rx_irqsafe(hw, skb);
|
||||
assert_eq!(ieee80211_rx_drain(hw), 1);
|
||||
assert_eq!(RX_RECEIVED.load(Ordering::Acquire), 1);
|
||||
|
||||
ieee80211_register_rx_handler(hw, None);
|
||||
let skb2 = super::super::net::alloc_skb(128, 0);
|
||||
assert!(!skb2.is_null());
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(data.as_ptr(), (*skb2).data, data.len());
|
||||
(*skb2).len = data.len() as u32;
|
||||
}
|
||||
ieee80211_rx_irqsafe(hw, skb2);
|
||||
assert_eq!(ieee80211_rx_drain(hw), 1);
|
||||
assert_eq!(RX_RECEIVED.load(Ordering::Acquire), 1);
|
||||
|
||||
ieee80211_free_hw(hw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_status_tracks_statistics() {
|
||||
let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null());
|
||||
assert!(!hw.is_null());
|
||||
|
||||
let stats = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats.total, 0);
|
||||
assert_eq!(stats.acked, 0);
|
||||
|
||||
for _ in 0..3 {
|
||||
let skb = super::super::net::alloc_skb(64, 0);
|
||||
assert!(!skb.is_null());
|
||||
unsafe {
|
||||
(*skb).len = 10;
|
||||
}
|
||||
ieee80211_tx_status(hw, skb);
|
||||
}
|
||||
|
||||
let stats = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats.total, 3);
|
||||
assert_eq!(stats.acked, 3);
|
||||
|
||||
ieee80211_free_hw(hw);
|
||||
let stats_after_free = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats_after_free.total, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,46 @@ fn open_current_device(dev: *mut PciDev) -> Result<PciDevice, i32> {
|
||||
})
|
||||
}
|
||||
|
||||
fn quirk_location_from_dev(dev: &PciDev) -> PciLocation {
|
||||
PciLocation {
|
||||
segment: 0,
|
||||
bus: dev.bus,
|
||||
device: dev.dev,
|
||||
function: dev.func,
|
||||
}
|
||||
}
|
||||
|
||||
fn quirk_info_from_dev(dev: &PciDev) -> PciDeviceInfo {
|
||||
let location = quirk_location_from_dev(dev);
|
||||
let mut info = PciDeviceInfo {
|
||||
location,
|
||||
vendor_id: dev.vendor,
|
||||
device_id: dev.device,
|
||||
subsystem_vendor_id: redox_driver_sys::quirks::PCI_QUIRK_ANY_ID,
|
||||
subsystem_device_id: redox_driver_sys::quirks::PCI_QUIRK_ANY_ID,
|
||||
revision: dev.revision,
|
||||
class_code: 0,
|
||||
subclass: 0,
|
||||
prog_if: 0,
|
||||
header_type: 0,
|
||||
irq: if dev.irq != 0 && dev.irq != u32::from(u8::MAX) {
|
||||
Some(dev.irq)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
bars: Vec::new(),
|
||||
capabilities: Vec::new(),
|
||||
};
|
||||
|
||||
if let Ok(mut pci) = PciDevice::open_location(&location) {
|
||||
if let Ok(full_info) = pci.full_info() {
|
||||
info = full_info;
|
||||
}
|
||||
}
|
||||
|
||||
info
|
||||
}
|
||||
|
||||
fn clear_irq_vectors_for_ptr(dev_ptr: usize) {
|
||||
if let Ok(mut vectors) = IRQ_VECTORS.lock() {
|
||||
vectors.remove(&dev_ptr);
|
||||
@@ -300,7 +340,7 @@ pub extern "C" fn pci_iomap(dev: *mut PciDev, bar: u32, max_len: usize) -> *mut
|
||||
if len == 0 {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
log::warn!("pci_iomap: bar={} len={} — using heap fallback", bar, len);
|
||||
log::debug!("pci_iomap: bar={} len={} — mapping via ioremap", bar, len);
|
||||
super::io::ioremap(unsafe { (*dev).bars[bar as usize] }, len)
|
||||
}
|
||||
|
||||
@@ -378,7 +418,28 @@ pub extern "C" fn pci_set_master(dev: *mut PciDev) {
|
||||
if dev.is_null() {
|
||||
return;
|
||||
}
|
||||
log::info!("pci_set_master");
|
||||
|
||||
let mut cmd: u32 = 0;
|
||||
let rc = pci_read_config_dword(dev, 0x04, &mut cmd);
|
||||
if rc != 0 {
|
||||
log::warn!("pci_set_master: failed to read command register");
|
||||
return;
|
||||
}
|
||||
|
||||
if cmd & 0x04 != 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = (cmd & 0x0000_FFFF) | 0x04;
|
||||
let rc = pci_write_config_dword(dev, 0x04, cmd);
|
||||
if rc != 0 {
|
||||
log::warn!("pci_set_master: failed to write command register");
|
||||
return;
|
||||
}
|
||||
log::info!(
|
||||
"pci_set_master: enabled bus mastering (cmd={:#06x})",
|
||||
cmd & 0xFFFF
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -397,6 +458,21 @@ pub extern "C" fn pci_resource_len(dev: *const PciDev, bar: u32) -> u64 {
|
||||
unsafe { (*dev).bar_sizes[bar as usize] }
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pci_get_quirk_flags(dev: *mut PciDev) -> u64 {
|
||||
if dev.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let info = quirk_info_from_dev(unsafe { &*dev });
|
||||
redox_driver_sys::quirks::lookup_pci_quirks(&info).bits()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn pci_has_quirk(dev: *mut PciDev, flag: u64) -> bool {
|
||||
(pci_get_quirk_flags(dev) & flag) != 0
|
||||
}
|
||||
|
||||
pub type PciDriverProbe = extern "C" fn(*mut PciDev, *const PciDeviceId) -> i32;
|
||||
pub type PciDriverRemove = extern "C" fn(*mut PciDev);
|
||||
|
||||
|
||||
@@ -169,7 +169,21 @@ pub extern "C" fn mod_timer(timer: *mut TimerList, expires: u64) -> i32 {
|
||||
let data_addr = entry.data.load(Ordering::Acquire) as usize;
|
||||
let entry_for_thread = entry.clone();
|
||||
let handle = std::thread::spawn(move || {
|
||||
std::thread::sleep(Duration::from_millis(delay));
|
||||
let start = std::time::Instant::now();
|
||||
let total = Duration::from_millis(delay);
|
||||
loop {
|
||||
let elapsed = start.elapsed();
|
||||
if elapsed >= total {
|
||||
break;
|
||||
}
|
||||
if !entry_for_thread.active.load(Ordering::Acquire) {
|
||||
return;
|
||||
}
|
||||
if entry_for_thread.generation.load(Ordering::Acquire) != generation {
|
||||
return;
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
|
||||
if !entry_for_thread.active.load(Ordering::Acquire) {
|
||||
return;
|
||||
@@ -254,3 +268,157 @@ pub extern "C" fn timer_pending(timer: *const TimerList) -> i32 {
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
extern "C" fn test_timer_callback(_data: c_ulong) {}
|
||||
|
||||
#[test]
|
||||
fn setup_timer_initializes_struct() {
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), test_timer_callback, 42);
|
||||
|
||||
let timer_ref = unsafe { &*timer.as_ptr() };
|
||||
assert_eq!(timer_ref.expires.load(Ordering::Acquire), 0);
|
||||
assert!(!timer_ref.active.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_timer_null_pointer_is_safe() {
|
||||
setup_timer(std::ptr::null_mut(), test_timer_callback, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_timer_returns_was_active() {
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), test_timer_callback, 0);
|
||||
|
||||
let was_active = mod_timer(timer.as_mut_ptr(), current_jiffies() + 30000);
|
||||
assert_eq!(
|
||||
was_active, 0,
|
||||
"first mod_timer should return 0 (was inactive)"
|
||||
);
|
||||
|
||||
let was_active = mod_timer(timer.as_mut_ptr(), current_jiffies() + 30000);
|
||||
assert_eq!(
|
||||
was_active, 1,
|
||||
"second mod_timer should return 1 (was active)"
|
||||
);
|
||||
|
||||
del_timer_sync(timer.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_timer_null_pointer_returns_zero() {
|
||||
assert_eq!(mod_timer(std::ptr::null_mut(), 1000), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn del_timer_cancels_active_timer() {
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), test_timer_callback, 0);
|
||||
mod_timer(timer.as_mut_ptr(), current_jiffies() + 30000);
|
||||
|
||||
let result = del_timer_sync(timer.as_mut_ptr());
|
||||
assert_eq!(result, 1, "del_timer should return 1 for active timer");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn del_timer_null_pointer_returns_zero() {
|
||||
assert_eq!(del_timer(std::ptr::null_mut()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn del_timer_sync_cancels_long_timer() {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
extern "C" fn counting_callback(_data: c_ulong) {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), counting_callback, 0);
|
||||
|
||||
COUNTER.store(0, Ordering::Relaxed);
|
||||
mod_timer(timer.as_mut_ptr(), current_jiffies() + 30000);
|
||||
|
||||
let result = del_timer_sync(timer.as_mut_ptr());
|
||||
assert_eq!(result, 1, "del_timer_sync should return 1 for active timer");
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
assert_eq!(
|
||||
COUNTER.load(Ordering::Relaxed),
|
||||
0,
|
||||
"callback should not have fired after cancel"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn del_timer_returns_zero_for_inactive() {
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), test_timer_callback, 0);
|
||||
|
||||
assert_eq!(
|
||||
del_timer(timer.as_mut_ptr()),
|
||||
0,
|
||||
"del_timer on inactive timer should return 0"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timer_pending_null_returns_zero() {
|
||||
assert_eq!(timer_pending(std::ptr::null()), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timer_pending_reflects_state() {
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), test_timer_callback, 0);
|
||||
|
||||
assert_eq!(
|
||||
timer_pending(timer.as_ptr()),
|
||||
0,
|
||||
"new timer should not be pending"
|
||||
);
|
||||
|
||||
mod_timer(timer.as_mut_ptr(), current_jiffies() + 30000);
|
||||
assert_eq!(
|
||||
timer_pending(timer.as_ptr()),
|
||||
1,
|
||||
"armed timer should be pending"
|
||||
);
|
||||
|
||||
del_timer_sync(timer.as_mut_ptr());
|
||||
assert_eq!(
|
||||
timer_pending(timer.as_ptr()),
|
||||
0,
|
||||
"cancelled timer should not be pending"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mod_timer_fires_callback() {
|
||||
static FIRE_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
extern "C" fn fire_callback(_data: c_ulong) {
|
||||
FIRE_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let mut timer = std::mem::MaybeUninit::<TimerList>::uninit();
|
||||
setup_timer(timer.as_mut_ptr(), fire_callback, 0);
|
||||
|
||||
FIRE_COUNT.store(0, Ordering::Relaxed);
|
||||
mod_timer(timer.as_mut_ptr(), current_jiffies());
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
assert!(
|
||||
FIRE_COUNT.load(Ordering::Relaxed) >= 1,
|
||||
"callback should have fired"
|
||||
);
|
||||
|
||||
del_timer_sync(timer.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,3 +184,135 @@ pub extern "C" fn wait_event_timeout(
|
||||
) -> i32 {
|
||||
wait_event_timeout_impl(wq, || condition(), timeout_ms)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct SendWq(*mut WaitQueueHead);
|
||||
unsafe impl Send for SendWq {}
|
||||
impl SendWq {
|
||||
fn ptr(&self) -> *mut WaitQueueHead {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_waitqueue_head_initializes() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
let wq_ref = unsafe { &*wq.as_ptr() };
|
||||
let guard = wq_ref.mutex.lock().unwrap();
|
||||
assert!(!*guard);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_waitqueue_head_null_is_safe() {
|
||||
init_waitqueue_head(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wake_up_null_is_safe() {
|
||||
wake_up(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_unblocks_on_wake_up() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
let send = SendWq(wq.as_mut_ptr());
|
||||
|
||||
static WOKE: AtomicBool = AtomicBool::new(false);
|
||||
WOKE.store(false, AtomicOrdering::Relaxed);
|
||||
|
||||
extern "C" fn cond_check_woke() -> bool {
|
||||
WOKE.load(AtomicOrdering::Relaxed)
|
||||
}
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
wait_event(send.ptr(), cond_check_woke);
|
||||
});
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
assert!(!handle.is_finished());
|
||||
|
||||
WOKE.store(true, AtomicOrdering::Relaxed);
|
||||
wake_up(wq.as_mut_ptr());
|
||||
|
||||
handle.join().expect("wait_event thread should complete");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_returns_immediately_when_condition_true() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
|
||||
extern "C" fn cond_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
let send = SendWq(wq.as_mut_ptr());
|
||||
let handle = std::thread::spawn(move || {
|
||||
wait_event(send.ptr(), cond_true);
|
||||
});
|
||||
|
||||
assert!(handle.join().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_timeout_returns_one_when_condition_met() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
|
||||
extern "C" fn cond_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
assert_eq!(wait_event_timeout(wq.as_mut_ptr(), cond_true, 100), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_timeout_returns_zero_on_timeout() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
|
||||
extern "C" fn cond_false() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
assert_eq!(wait_event_timeout(wq.as_mut_ptr(), cond_false, 10), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_timeout_wakes_early() {
|
||||
let mut wq = std::mem::MaybeUninit::<WaitQueueHead>::uninit();
|
||||
init_waitqueue_head(wq.as_mut_ptr());
|
||||
let send = SendWq(wq.as_mut_ptr());
|
||||
|
||||
static WOKE: AtomicBool = AtomicBool::new(false);
|
||||
WOKE.store(false, AtomicOrdering::Relaxed);
|
||||
|
||||
extern "C" fn cond_check() -> bool {
|
||||
WOKE.load(AtomicOrdering::Relaxed)
|
||||
}
|
||||
|
||||
let handle = std::thread::spawn(move || wait_event_timeout(send.ptr(), cond_check, 5000));
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(20));
|
||||
WOKE.store(true, AtomicOrdering::Relaxed);
|
||||
wake_up(wq.as_mut_ptr());
|
||||
|
||||
let result = handle.join().expect("thread should complete");
|
||||
assert_eq!(result, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wait_event_timeout_null_returns_zero() {
|
||||
extern "C" fn cond_true() -> bool {
|
||||
true
|
||||
}
|
||||
assert_eq!(wait_event_timeout(std::ptr::null_mut(), cond_true, 100), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,31 @@ struct WirelessEventState {
|
||||
mgmt_rx_freq: u32,
|
||||
mgmt_rx_signal: i32,
|
||||
mgmt_rx_len: usize,
|
||||
mgmt_rx_data: Vec<u8>,
|
||||
mgmt_tx_cookie: u64,
|
||||
mgmt_tx_len: usize,
|
||||
mgmt_tx_ack: bool,
|
||||
mgmt_tx_data: Vec<u8>,
|
||||
sched_scan_reqid: u64,
|
||||
roc_cookie: u64,
|
||||
roc_chan_freq: u16,
|
||||
roc_band: u32,
|
||||
roc_duration: u32,
|
||||
roc_active: bool,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct WiphyBands {
|
||||
bands: [usize; 3],
|
||||
}
|
||||
|
||||
unsafe impl Send for WiphyBands {}
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref WIRELESS_EVENTS: Mutex<HashMap<usize, WirelessEventState>> = Mutex::new(HashMap::new());
|
||||
static ref BSS_REGISTRY: Mutex<Vec<Box<Cfg80211Bss>>> = Mutex::new(Vec::new());
|
||||
static ref BSS_IES: Mutex<HashMap<usize, Vec<u8>>> = Mutex::new(HashMap::new());
|
||||
static ref WIPY_BANDS_MAP: Mutex<HashMap<usize, WiphyBands>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -74,6 +91,7 @@ pub struct KeyParams {
|
||||
pub key: *const u8,
|
||||
pub key_len: u8,
|
||||
pub cipher: u32,
|
||||
pub key_idx: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -141,8 +159,23 @@ pub extern "C" fn wiphy_free(wiphy: *mut Wiphy) {
|
||||
if wiphy.is_null() {
|
||||
return;
|
||||
}
|
||||
let wiphy_key = wiphy as usize;
|
||||
if let Ok(mut events) = WIRELESS_EVENTS.lock() {
|
||||
events.remove(&(wiphy as usize));
|
||||
events.remove(&wiphy_key);
|
||||
}
|
||||
if let Ok(mut registry) = BSS_REGISTRY.lock() {
|
||||
if let Ok(mut ies_map) = BSS_IES.lock() {
|
||||
for entry in registry.iter() {
|
||||
if entry.wiphy == wiphy_key {
|
||||
let ptr = entry.as_ref() as *const Cfg80211Bss as usize;
|
||||
ies_map.remove(&ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
registry.retain(|e| e.wiphy != wiphy_key);
|
||||
}
|
||||
if let Ok(mut bands_map) = WIPY_BANDS_MAP.lock() {
|
||||
bands_map.remove(&wiphy_key);
|
||||
}
|
||||
unsafe {
|
||||
let wiphy_box = Box::from_raw(wiphy);
|
||||
@@ -304,13 +337,37 @@ pub extern "C" fn cfg80211_connect_bss(
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cfg80211_ready_on_channel(
|
||||
_wdev: *mut WirelessDev,
|
||||
_cookie: u64,
|
||||
_chan: *mut c_void,
|
||||
wdev: *mut WirelessDev,
|
||||
cookie: u64,
|
||||
chan: *mut c_void,
|
||||
_chan_type: u32,
|
||||
_duration: u32,
|
||||
duration: u32,
|
||||
_gfp: u32,
|
||||
) {
|
||||
if wdev.is_null() {
|
||||
return;
|
||||
}
|
||||
let key = wdev as usize;
|
||||
let (freq, band) = if chan.is_null() {
|
||||
(0u16, 0u32)
|
||||
} else {
|
||||
let ch = chan.cast::<Ieee80211Channel>();
|
||||
unsafe { ((*ch).center_freq, (*ch).band) }
|
||||
};
|
||||
update_event_state(key, |state| {
|
||||
state.roc_cookie = cookie;
|
||||
state.roc_chan_freq = freq;
|
||||
state.roc_band = band;
|
||||
state.roc_duration = duration;
|
||||
state.roc_active = true;
|
||||
});
|
||||
log::trace!(
|
||||
"cfg80211_ready_on_channel: wdev={:#x} cookie={} freq={} duration={}",
|
||||
key,
|
||||
cookie,
|
||||
freq,
|
||||
duration
|
||||
);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@@ -379,6 +436,14 @@ pub extern "C" fn wiphy_bands_append(
|
||||
return -22;
|
||||
}
|
||||
|
||||
let key = wiphy as usize;
|
||||
if let Ok(mut map) = WIPY_BANDS_MAP.lock() {
|
||||
let entry = map
|
||||
.entry(key)
|
||||
.or_insert_with(|| WiphyBands { bands: [0; 3] });
|
||||
entry.bands[band_idx as usize] = band as usize;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
@@ -391,8 +456,11 @@ pub struct Cfg80211Bss {
|
||||
pub beacon_interval: u16,
|
||||
pub ies: *const u8,
|
||||
pub ies_len: usize,
|
||||
wiphy: usize,
|
||||
}
|
||||
|
||||
unsafe impl Send for Cfg80211Bss {}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cfg80211_inform_bss(
|
||||
wiphy: *mut Wiphy,
|
||||
@@ -416,16 +484,65 @@ pub extern "C" fn cfg80211_inform_bss(
|
||||
ptr::copy_nonoverlapping(bssid, bssid_bytes.as_mut_ptr(), bssid_bytes.len());
|
||||
}
|
||||
|
||||
let Ok(mut registry) = BSS_REGISTRY.lock() else {
|
||||
log::warn!("cfg80211_inform_bss: registry lock failed");
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
let clamped_signal = signal.clamp(i16::MIN as i32, i16::MAX as i32) as i16;
|
||||
|
||||
let ies_owned: Vec<u8> = if !ies.is_null() && ies_len > 0 {
|
||||
unsafe { std::slice::from_raw_parts(ies, ies_len) }.to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
for entry in registry.iter_mut() {
|
||||
if entry.bssid == bssid_bytes && entry.wiphy == wiphy as usize {
|
||||
entry.signal = clamped_signal;
|
||||
entry.capability = capability;
|
||||
entry.beacon_interval = beacon_interval;
|
||||
let entry_ptr = entry.as_ref() as *const Cfg80211Bss as usize;
|
||||
if let Ok(mut ies_map) = BSS_IES.lock() {
|
||||
entry.ies_len = ies_owned.len();
|
||||
if ies_owned.is_empty() {
|
||||
entry.ies = ptr::null();
|
||||
ies_map.remove(&entry_ptr);
|
||||
} else {
|
||||
let stored = ies_map.entry(entry_ptr).or_default();
|
||||
stored.clear();
|
||||
stored.extend_from_slice(&ies_owned);
|
||||
entry.ies = stored.as_ptr();
|
||||
}
|
||||
}
|
||||
return entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss;
|
||||
}
|
||||
}
|
||||
|
||||
let bss = Box::new(Cfg80211Bss {
|
||||
bssid: bssid_bytes,
|
||||
channel: ptr::null_mut(),
|
||||
signal: signal.clamp(i16::MIN as i32, i16::MAX as i32) as i16,
|
||||
signal: clamped_signal,
|
||||
capability,
|
||||
beacon_interval,
|
||||
ies,
|
||||
ies_len,
|
||||
ies: ptr::null(),
|
||||
ies_len: 0,
|
||||
wiphy: wiphy as usize,
|
||||
});
|
||||
Box::into_raw(bss)
|
||||
registry.push(bss);
|
||||
let entry = match registry.last_mut() {
|
||||
Some(e) => e,
|
||||
None => return ptr::null_mut(),
|
||||
};
|
||||
let entry_ptr = entry.as_ref() as *const Cfg80211Bss as usize;
|
||||
if let Ok(mut ies_map) = BSS_IES.lock() {
|
||||
if !ies_owned.is_empty() {
|
||||
ies_map.insert(entry_ptr, ies_owned);
|
||||
entry.ies = ies_map[&entry_ptr].as_ptr();
|
||||
entry.ies_len = ies_map[&entry_ptr].len();
|
||||
}
|
||||
}
|
||||
entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -433,26 +550,98 @@ pub extern "C" fn cfg80211_put_bss(bss: *mut Cfg80211Bss) {
|
||||
if bss.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
drop(Box::from_raw(bss));
|
||||
let bss_addr = bss as usize;
|
||||
let Ok(mut registry) = BSS_REGISTRY.lock() else {
|
||||
log::warn!("cfg80211_put_bss: registry lock failed");
|
||||
return;
|
||||
};
|
||||
let before = registry.len();
|
||||
registry.retain(|entry| entry.as_ref() as *const Cfg80211Bss as usize != bss_addr);
|
||||
let removed = before != registry.len();
|
||||
if removed {
|
||||
if let Ok(mut ies_map) = BSS_IES.lock() {
|
||||
ies_map.remove(&bss_addr);
|
||||
}
|
||||
}
|
||||
log::trace!(
|
||||
"cfg80211_put_bss: released reference bss={:#x} (removed={})",
|
||||
bss_addr,
|
||||
removed
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn cfg80211_get_bss(
|
||||
wiphy: *mut Wiphy,
|
||||
band: u32,
|
||||
_bssid: *const u8,
|
||||
_ssid: *const u8,
|
||||
_ssid_len: usize,
|
||||
_band: u32,
|
||||
bssid: *const u8,
|
||||
ssid: *const u8,
|
||||
ssid_len: usize,
|
||||
_bss_type: u32,
|
||||
_privacy: u32,
|
||||
) -> *mut Cfg80211Bss {
|
||||
if wiphy.is_null() || band > NL80211_BAND_6GHZ {
|
||||
if wiphy.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let Ok(registry) = BSS_REGISTRY.lock() else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
let want_bssid = if bssid.is_null() {
|
||||
None
|
||||
} else {
|
||||
let mut bytes = [0u8; 6];
|
||||
unsafe { ptr::copy_nonoverlapping(bssid, bytes.as_mut_ptr(), 6) };
|
||||
Some(bytes)
|
||||
};
|
||||
|
||||
let want_ssid = if ssid.is_null() || ssid_len == 0 {
|
||||
None
|
||||
} else {
|
||||
let slice = unsafe { std::slice::from_raw_parts(ssid, ssid_len) };
|
||||
Some(slice.to_vec())
|
||||
};
|
||||
|
||||
for entry in registry.iter() {
|
||||
if entry.wiphy != wiphy as usize {
|
||||
continue;
|
||||
}
|
||||
if let Some(ref wb) = want_bssid {
|
||||
if entry.bssid != *wb {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(ref ws) = want_ssid {
|
||||
if entry.ies.is_null() || entry.ies_len < ws.len() + 2 {
|
||||
continue;
|
||||
}
|
||||
unsafe {
|
||||
let ies_slice = std::slice::from_raw_parts(entry.ies, entry.ies_len);
|
||||
let mut offset = 0;
|
||||
let mut found = false;
|
||||
while offset + 2 <= ies_slice.len() {
|
||||
let tag_id = ies_slice[offset];
|
||||
let tag_len = ies_slice[offset + 1] as usize;
|
||||
if offset + 2 + tag_len > ies_slice.len() {
|
||||
break;
|
||||
}
|
||||
if tag_id == 0 && tag_len == ws.len() {
|
||||
if &ies_slice[offset + 2..offset + 2 + tag_len] == ws.as_slice() {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset += 2 + tag_len;
|
||||
}
|
||||
if !found {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return entry.as_ref() as *const Cfg80211Bss as *mut Cfg80211Bss;
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
@@ -492,11 +681,26 @@ pub extern "C" fn cfg80211_rx_mgmt(
|
||||
return;
|
||||
}
|
||||
|
||||
let frame_data = if !buf.is_null() && len > 0 {
|
||||
unsafe { std::slice::from_raw_parts(buf, len) }.to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
update_event_state(wdev as usize, |state| {
|
||||
state.mgmt_rx_freq = freq;
|
||||
state.mgmt_rx_signal = sig_dbm;
|
||||
state.mgmt_rx_len = len;
|
||||
state.mgmt_rx_data = frame_data;
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
"cfg80211_rx_mgmt: wdev={:#x} freq={} sig={} len={}",
|
||||
wdev as usize,
|
||||
freq,
|
||||
sig_dbm,
|
||||
len
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -512,11 +716,26 @@ pub extern "C" fn cfg80211_mgmt_tx_status(
|
||||
return;
|
||||
}
|
||||
|
||||
let frame_data = if !buf.is_null() && len > 0 {
|
||||
unsafe { std::slice::from_raw_parts(buf, len) }.to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
update_event_state(wdev as usize, |state| {
|
||||
state.mgmt_tx_cookie = cookie;
|
||||
state.mgmt_tx_len = len;
|
||||
state.mgmt_tx_ack = ack;
|
||||
state.mgmt_tx_data = frame_data;
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
"cfg80211_mgmt_tx_status: wdev={:#x} cookie={} len={} ack={}",
|
||||
wdev as usize,
|
||||
cookie,
|
||||
len,
|
||||
ack
|
||||
);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -706,8 +925,10 @@ mod tests {
|
||||
.expect("wdev event state");
|
||||
assert_eq!(wdev_state.mgmt_rx_freq, 2412);
|
||||
assert_eq!(wdev_state.mgmt_rx_signal, -42);
|
||||
assert_eq!(wdev_state.mgmt_rx_data, sta.to_vec());
|
||||
assert_eq!(wdev_state.mgmt_tx_cookie, 99);
|
||||
assert!(wdev_state.mgmt_tx_ack);
|
||||
assert_eq!(wdev_state.mgmt_tx_data, sta.to_vec());
|
||||
drop(events);
|
||||
|
||||
assert_eq!(ieee80211_channel_to_frequency(1, NL80211_BAND_2GHZ), 2412);
|
||||
@@ -718,4 +939,64 @@ mod tests {
|
||||
wiphy_free(wiphy);
|
||||
free_netdev(dev);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cfg80211_put_bss_removes_from_registry() {
|
||||
let name = CString::new("wlan%d").expect("valid test CString");
|
||||
let dev = alloc_netdev_mqs(0, name.as_ptr().cast::<u8>(), 0, None, 1, 1);
|
||||
assert!(!dev.is_null());
|
||||
let wiphy = wiphy_new_nm(ptr::null(), 32, ptr::null());
|
||||
assert!(!wiphy.is_null());
|
||||
|
||||
let mut wdev = WirelessDev {
|
||||
wiphy,
|
||||
netdev: dev.cast::<c_void>(),
|
||||
iftype: 0,
|
||||
scan_in_flight: false,
|
||||
scan_aborted: false,
|
||||
connecting: false,
|
||||
connected: false,
|
||||
locally_generated: false,
|
||||
last_status: 0,
|
||||
last_reason: 0,
|
||||
has_bssid: false,
|
||||
last_bssid: [0; 6],
|
||||
};
|
||||
unsafe { (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::<c_void>() };
|
||||
|
||||
let bssid: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff];
|
||||
let ies = [1u8, 2, 3, 4];
|
||||
|
||||
let bss = cfg80211_inform_bss(
|
||||
wiphy,
|
||||
&mut wdev as *mut WirelessDev,
|
||||
2412,
|
||||
bssid.as_ptr(),
|
||||
0,
|
||||
0x0431,
|
||||
100,
|
||||
ies.as_ptr(),
|
||||
ies.len(),
|
||||
-50,
|
||||
0,
|
||||
);
|
||||
assert!(!bss.is_null());
|
||||
|
||||
let found = cfg80211_get_bss(wiphy, 0, bssid.as_ptr(), ptr::null(), 0, 0, 0);
|
||||
assert!(
|
||||
!found.is_null(),
|
||||
"BSS should be in registry after inform_bss"
|
||||
);
|
||||
|
||||
cfg80211_put_bss(bss);
|
||||
|
||||
let after_put = cfg80211_get_bss(wiphy, 0, bssid.as_ptr(), ptr::null(), 0, 0, 0);
|
||||
assert!(
|
||||
after_put.is_null(),
|
||||
"BSS should be removed from registry after put_bss"
|
||||
);
|
||||
|
||||
wiphy_free(wiphy);
|
||||
free_netdev(dev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,3 +288,85 @@ pub extern "C" fn flush_scheduled_work() {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
#[test]
|
||||
fn alloc_and_destroy_workqueue() {
|
||||
let name = b"test_wq\0";
|
||||
let wq = alloc_workqueue(name.as_ptr(), 0, 2);
|
||||
assert!(!wq.is_null());
|
||||
destroy_workqueue(wq);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn destroy_workqueue_null_is_safe() {
|
||||
destroy_workqueue(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_work_executes_function() {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
COUNTER.store(0, Ordering::Relaxed);
|
||||
|
||||
extern "C" fn work_fn(_work: *mut WorkStruct) {
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let name = b"exec_wq\0";
|
||||
let wq = alloc_workqueue(name.as_ptr(), 0, 2);
|
||||
let mut work = WorkStruct {
|
||||
func: Some(work_fn),
|
||||
__opaque: [0u8; 64],
|
||||
};
|
||||
|
||||
queue_work(wq, &mut work);
|
||||
flush_workqueue(wq);
|
||||
|
||||
assert!(
|
||||
COUNTER.load(Ordering::Relaxed) >= 1,
|
||||
"work function should have executed"
|
||||
);
|
||||
destroy_workqueue(wq);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queue_work_null_wq_returns_zero() {
|
||||
let mut work = WorkStruct {
|
||||
func: None,
|
||||
__opaque: [0u8; 64],
|
||||
};
|
||||
assert_eq!(queue_work(std::ptr::null_mut(), &mut work), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_work_uses_default_wq() {
|
||||
static SCHED_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
SCHED_COUNTER.store(0, Ordering::Relaxed);
|
||||
|
||||
extern "C" fn sched_fn(_work: *mut WorkStruct) {
|
||||
SCHED_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let mut work = WorkStruct {
|
||||
func: Some(sched_fn),
|
||||
__opaque: [0u8; 64],
|
||||
};
|
||||
|
||||
schedule_work(&mut work);
|
||||
flush_scheduled_work();
|
||||
|
||||
assert!(
|
||||
SCHED_COUNTER.load(Ordering::Relaxed) >= 1,
|
||||
"schedule_work should dispatch to default wq"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush_workqueue_null_is_safe() {
|
||||
flush_workqueue(std::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user