diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/atomic.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/atomic.h index 95950f0a..d600d34d 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/atomic.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/atomic.h @@ -1,7 +1,7 @@ #ifndef _LINUX_ATOMIC_H #define _LINUX_ATOMIC_H -#include +#include "types.h" typedef struct { volatile int counter; diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/errno.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/errno.h index c5067a91..43ef29d0 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/errno.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/errno.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h index d4fa3e19..d634d33e 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/interrupt.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/interrupt.h index 9cedba0e..4cd08bc3 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/interrupt.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/interrupt.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/kernel.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/kernel.h index 520a2bf6..3f540918 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/kernel.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/kernel.h @@ -1,8 +1,8 @@ #ifndef _LINUX_KERNEL_H #define _LINUX_KERNEL_H -#include -#include +#include "compiler.h" +#include "types.h" #include #include #include diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.h index dd89d764..694fcac8 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h index b718c2ab..7ff0df29 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h @@ -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, diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h index 24926510..9583abef 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/pci.h @@ -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); diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h index 5fbbf754..0cf4154c 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/slab.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/slab.h index 1b676906..b4a0dabe 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/slab.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/slab.h @@ -1,7 +1,7 @@ #ifndef _LINUX_SLAB_H #define _LINUX_SLAB_H -#include +#include "types.h" #include #define GFP_KERNEL 0U diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/wait.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/wait.h index 108a61c1..b8209e97 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/wait.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/wait.h @@ -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; }) diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h index e9e8e950..a23c69ba 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h index 7b56ff32..e8476574 100644 --- a/local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h @@ -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 diff --git a/local/recipes/drivers/linux-kpi/source/src/lib.rs b/local/recipes/drivers/linux-kpi/source/src/lib.rs index 46acd981..a0e82a92 100644 --- a/local/recipes/drivers/linux-kpi/source/src/lib.rs +++ b/local/recipes/drivers/linux-kpi/source/src/lib.rs @@ -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; diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/device.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/device.rs index 2c13f4fa..00957de9 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/device.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/device.rs @@ -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()); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/dma.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/dma.rs index 55123c3f..ae4eb549 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/dma.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/dma.rs @@ -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> = 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 { diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs index 850e8c36..df78d10b 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/drm_shim.rs @@ -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: 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); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/idr.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/idr.rs index 9af98895..e81119ac 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/idr.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/idr.rs @@ -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::::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::::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::::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::::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::::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()); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/irq.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/irq.rs index 6883d709..d25d8439 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/irq.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/irq.rs @@ -17,6 +17,7 @@ pub type IrqHandler = extern "C" fn(i32, *mut u8) -> u32; struct IrqEntry { cancel: Arc, + masked: Arc, fd: Option, handle: Option>, } @@ -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 + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs index efa28041..cad2b41c 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs @@ -17,6 +17,10 @@ const EBUSY: i32 = 16; lazy_static::lazy_static! { static ref STA_REGISTRY: Mutex> = Mutex::new(HashMap::new()); static ref BA_SESSIONS: Mutex>> = Mutex::new(HashMap::new()); + static ref RX_QUEUE: Mutex> = Mutex::new(Vec::new()); + static ref HW_WDEV_MAP: Mutex> = Mutex::new(HashMap::new()); + static ref RX_CALLBACKS: Mutex> = Mutex::new(HashMap::new()); + static ref TX_STATS: Mutex> = 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, @@ -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::()); } +/// 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, +) { + 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::(); + 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); + } } diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs index 2194fb6c..cd34ecc2 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/pci.rs @@ -128,6 +128,46 @@ fn open_current_device(dev: *mut PciDev) -> Result { }) } +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); diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/timer.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/timer.rs index e1c17f58..55f0e0a9 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/timer.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/timer.rs @@ -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::::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::::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::::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::::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::::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::::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::::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()); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/wait.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/wait.rs index 1517f404..889b738c 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/wait.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/wait.rs @@ -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::::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::::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::::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::::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::::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::::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); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs index cd96aa4e..417170b5 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs @@ -13,14 +13,31 @@ struct WirelessEventState { mgmt_rx_freq: u32, mgmt_rx_signal: i32, mgmt_rx_len: usize, + mgmt_rx_data: Vec, mgmt_tx_cookie: u64, mgmt_tx_len: usize, mgmt_tx_ack: bool, + mgmt_tx_data: Vec, 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> = Mutex::new(HashMap::new()); + static ref BSS_REGISTRY: Mutex>> = Mutex::new(Vec::new()); + static ref BSS_IES: Mutex>> = Mutex::new(HashMap::new()); + static ref WIPY_BANDS_MAP: Mutex> = 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::(); + 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 = 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::(), 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::(), + 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::() }; + + 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); + } } diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/workqueue.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/workqueue.rs index dd08e37b..4eb5c609 100644 --- a/local/recipes/drivers/linux-kpi/source/src/rust_impl/workqueue.rs +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/workqueue.rs @@ -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()); + } +}