From f44940a562de7afa09c4134f7cf028a81594d33b Mon Sep 17 00:00:00 2001 From: Vasilito Date: Thu, 16 Apr 2026 12:43:33 +0100 Subject: [PATCH] Add linux-kpi wireless compat layer Red Bear OS Team --- .../source/src/c_headers/linux/ieee80211.h | 20 + .../source/src/c_headers/linux/netdevice.h | 36 ++ .../source/src/c_headers/linux/nl80211.h | 25 ++ .../source/src/c_headers/linux/skbuff.h | 24 ++ .../source/src/c_headers/net/cfg80211.h | 108 +++++ .../source/src/c_headers/net/mac80211.h | 47 +++ .../source/src/rust_impl/mac80211.rs | 200 +++++++++ .../linux-kpi/source/src/rust_impl/net.rs | 387 ++++++++++++++++++ .../source/src/rust_impl/wireless.rs | 369 +++++++++++++++++ .../source/src/test_host_redox_shims.rs | 29 ++ 10 files changed, 1245 insertions(+) create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h create mode 100644 local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs create mode 100644 local/recipes/drivers/linux-kpi/source/src/rust_impl/net.rs create mode 100644 local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs create mode 100644 local/recipes/drivers/linux-kpi/source/src/test_host_redox_shims.rs 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 new file mode 100644 index 00000000..d4fa3e19 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/ieee80211.h @@ -0,0 +1,20 @@ +#ifndef _LINUX_IEEE80211_H +#define _LINUX_IEEE80211_H + +#include "types.h" + +#define IEEE80211_MAX_SSID_LEN 32 +#define IEEE80211_NUM_ACS 4 + +struct ieee80211_channel { + u16 center_freq; + u16 hw_value; + u32 flags; +}; + +struct ieee80211_rate { + u16 bitrate; + u16 hw_value; +}; + +#endif 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 new file mode 100644 index 00000000..dd89d764 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/netdevice.h @@ -0,0 +1,36 @@ +#ifndef _LINUX_NETDEVICE_H +#define _LINUX_NETDEVICE_H + +#include "device.h" +#include "types.h" +#include + +struct net_device { + char name[16]; + unsigned char dev_addr[6]; + unsigned char addr_len; + unsigned int mtu; + unsigned int flags; + int carrier; + void *ml_priv; + void *ieee80211_ptr; + void *priv_data; + int registered; + size_t __priv_alloc_size; + size_t __priv_alloc_align; +}; + +extern struct net_device *alloc_netdev_mqs(size_t sizeof_priv, + const char *name, + unsigned char name_assign_type, + void (*setup)(struct net_device *dev), + unsigned int txqs, + unsigned int rxqs); +extern void free_netdev(struct net_device *dev); +extern int register_netdev(struct net_device *dev); +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); + +#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 new file mode 100644 index 00000000..b718c2ab --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/nl80211.h @@ -0,0 +1,25 @@ +#ifndef _LINUX_NL80211_H +#define _LINUX_NL80211_H + +enum nl80211_iftype { + NL80211_IFTYPE_UNSPECIFIED = 0, + NL80211_IFTYPE_ADHOC = 1, + NL80211_IFTYPE_STATION = 2, + NL80211_IFTYPE_AP = 3, + NL80211_IFTYPE_MONITOR = 6, +}; + +enum nl80211_commands { + NL80211_CMD_UNSPEC = 0, + NL80211_CMD_TRIGGER_SCAN = 33, + NL80211_CMD_NEW_SCAN_RESULTS = 34, + NL80211_CMD_AUTHENTICATE = 37, + NL80211_CMD_ASSOCIATE = 38, + NL80211_CMD_CONNECT = 46, + NL80211_CMD_DISCONNECT = 48, + NL80211_CMD_SET_KEY = 80, +}; + +#define NL80211_MAX_NR_CIPHER_SUITES 5 + +#endif 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 new file mode 100644 index 00000000..f0c906aa --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/linux/skbuff.h @@ -0,0 +1,24 @@ +#ifndef _LINUX_SKBUFF_H +#define _LINUX_SKBUFF_H + +#include "types.h" + +struct sk_buff { + void *head; + void *data; + unsigned int len; + unsigned int tail; + unsigned int end; +}; + +extern struct sk_buff *alloc_skb(unsigned int size, gfp_t gfp_mask); +extern void kfree_skb(struct sk_buff *skb); +extern void skb_reserve(struct sk_buff *skb, unsigned int len); +extern void *skb_put(struct sk_buff *skb, unsigned int len); +extern void *skb_push(struct sk_buff *skb, unsigned int len); +extern void *skb_pull(struct sk_buff *skb, unsigned int len); +extern unsigned int skb_headroom(const struct sk_buff *skb); +extern unsigned int skb_tailroom(const struct sk_buff *skb); +extern void skb_trim(struct sk_buff *skb, unsigned int len); + +#endif 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 new file mode 100644 index 00000000..7ce50c44 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/cfg80211.h @@ -0,0 +1,108 @@ +#ifndef _NET_CFG80211_H +#define _NET_CFG80211_H + +#include "../linux/device.h" +#include "../linux/ieee80211.h" +#include "../linux/netdevice.h" +#include "../linux/types.h" +#include +#include + +struct wiphy { + void *privid; + int registered; + u32 interface_modes; + int max_scan_ssids; + int max_scan_ie_len; +}; + +struct wireless_dev { + struct wiphy *wiphy; + struct net_device *netdev; + u32 iftype; + bool scan_in_flight; + bool scan_aborted; + bool connecting; + bool connected; + bool locally_generated; + u16 last_status; + u16 last_reason; + bool has_bssid; + u8 last_bssid[6]; +}; + +struct cfg80211_scan_info { + bool aborted; +}; + +struct cfg80211_scan_request { + struct wiphy *wiphy; + struct wireless_dev *wdev; + u32 n_ssids; + u32 n_channels; +}; + +struct cfg80211_ssid { + u8 ssid[IEEE80211_MAX_SSID_LEN]; + u8 ssid_len; +}; + +struct key_params { + const u8 *key; + u8 key_len; + u32 cipher; +}; + +struct cfg80211_connect_params { + const u8 *ssid; + size_t ssid_len; + const u8 *bssid; + const u8 *ie; + size_t ie_len; + struct key_params key; +}; + +struct station_parameters { + const u8 *supported_rates; + size_t supported_rates_len; + u32 sta_flags_mask; + u32 sta_flags_set; +}; + +extern struct wiphy *wiphy_new_nm(const void *ops, size_t sizeof_priv, const char *requested_name); +extern void wiphy_free(struct wiphy *wiphy); +extern int wiphy_register(struct wiphy *wiphy); +extern void wiphy_unregister(struct wiphy *wiphy); + +extern void cfg80211_scan_done(struct cfg80211_scan_request *request, + const struct cfg80211_scan_info *info); +extern void cfg80211_connect_result(struct net_device *dev, + const u8 *bssid, + const u8 *req_ie, + size_t req_ie_len, + const u8 *resp_ie, + size_t resp_ie_len, + u16 status, + gfp_t gfp); +extern void cfg80211_disconnected(struct net_device *dev, + u16 reason, + const u8 *ie, + size_t ie_len, + bool locally_generated, + gfp_t gfp); +extern void cfg80211_connect_bss(struct net_device *dev, + const u8 *bssid, + const u8 *req_ie, + size_t req_ie_len, + const u8 *resp_ie, + size_t resp_ie_len, + u16 status, + gfp_t gfp); +extern void cfg80211_ready_on_channel(struct wireless_dev *wdev, + u64 cookie, + struct ieee80211_channel *chan, + u32 chan_type, + u32 duration, + gfp_t gfp); + +#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 new file mode 100644 index 00000000..9456aeb0 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/c_headers/net/mac80211.h @@ -0,0 +1,47 @@ +#ifndef _NET_MAC80211_H +#define _NET_MAC80211_H + +#include "../linux/device.h" +#include "../linux/netdevice.h" +#include "../linux/skbuff.h" +#include "../linux/types.h" +#include "cfg80211.h" + +struct ieee80211_hw { + struct wiphy *wiphy; + void *priv; + int registered; + u32 extra_tx_headroom; + u16 queues; +}; + +struct ieee80211_vif { + u8 addr[6]; + void *drv_priv; + u32 type; + bool cfg_assoc; +}; + +struct ieee80211_sta { + u8 addr[6]; + void *drv_priv; + u16 aid; +}; + +struct ieee80211_bss_conf { + bool assoc; + u16 aid; + u16 beacon_int; +}; + +extern struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len, + const void *ops, + const char *requested_name); +extern void ieee80211_free_hw(struct ieee80211_hw *hw); +extern int ieee80211_register_hw(struct ieee80211_hw *hw); +extern void ieee80211_unregister_hw(struct ieee80211_hw *hw); +extern void ieee80211_queue_work(struct ieee80211_hw *hw, void *work); +extern void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted); +extern void ieee80211_connection_loss(struct ieee80211_vif *vif); + +#endif 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 new file mode 100644 index 00000000..bd636497 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/mac80211.rs @@ -0,0 +1,200 @@ +use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::ffi::c_void; +use std::ptr; +use std::sync::atomic::{AtomicI32, Ordering}; + +use super::wireless::{wiphy_free, wiphy_new_nm, wiphy_register, wiphy_unregister, Wiphy}; +use super::workqueue::{schedule_work, WorkStruct}; + +#[repr(C)] +pub struct Ieee80211Hw { + pub wiphy: *mut Wiphy, + pub priv_data: *mut c_void, + pub registered: AtomicI32, + pub extra_tx_headroom: u32, + pub queues: u16, + priv_alloc_size: usize, + priv_alloc_align: usize, +} + +#[repr(C)] +pub struct Ieee80211Vif { + pub addr: [u8; 6], + pub drv_priv: *mut c_void, + pub type_: u32, + pub cfg_assoc: bool, +} + +#[repr(C)] +pub struct Ieee80211Sta { + pub addr: [u8; 6], + pub drv_priv: *mut c_void, + pub aid: u16, +} + +#[repr(C)] +pub struct Ieee80211BssConf { + pub assoc: bool, + pub aid: u16, + pub beacon_int: u16, +} + +#[no_mangle] +pub extern "C" fn ieee80211_alloc_hw_nm( + priv_data_len: usize, + ops: *const c_void, + requested_name: *const u8, +) -> *mut Ieee80211Hw { + let wiphy = wiphy_new_nm(ops, 0, requested_name); + if wiphy.is_null() { + return ptr::null_mut(); + } + + let mut hw = Box::new(Ieee80211Hw { + wiphy, + priv_data: ptr::null_mut(), + registered: AtomicI32::new(0), + extra_tx_headroom: 0, + queues: 1, + priv_alloc_size: 0, + priv_alloc_align: 0, + }); + + if priv_data_len != 0 { + let layout = match Layout::from_size_align(priv_data_len, 16) { + Ok(layout) => layout, + Err(_) => { + wiphy_free(wiphy); + return ptr::null_mut(); + } + }; + let ptr = unsafe { alloc_zeroed(layout) } as *mut c_void; + if ptr.is_null() { + wiphy_free(wiphy); + return ptr::null_mut(); + } + hw.priv_data = ptr; + hw.priv_alloc_size = priv_data_len; + hw.priv_alloc_align = 16; + } + + Box::into_raw(hw) +} + +#[no_mangle] +pub extern "C" fn ieee80211_free_hw(hw: *mut Ieee80211Hw) { + if hw.is_null() { + return; + } + unsafe { + let hw_box = Box::from_raw(hw); + if !hw_box.priv_data.is_null() { + if let Ok(layout) = Layout::from_size_align( + hw_box.priv_alloc_size.max(1), + hw_box.priv_alloc_align.max(1), + ) { + dealloc(hw_box.priv_data.cast::(), layout); + } + } + wiphy_free(hw_box.wiphy); + } +} + +#[no_mangle] +pub extern "C" fn ieee80211_register_hw(hw: *mut Ieee80211Hw) -> i32 { + if hw.is_null() { + return -22; + } + if unsafe { &*hw }.registered.load(Ordering::Acquire) != 0 { + return -16; + } + let rc = wiphy_register(unsafe { (*hw).wiphy }); + if rc != 0 { + return rc; + } + unsafe { &*hw }.registered.store(1, Ordering::Release); + 0 +} + +#[no_mangle] +pub extern "C" fn ieee80211_unregister_hw(hw: *mut Ieee80211Hw) { + if hw.is_null() { + return; + } + if unsafe { &*hw }.registered.load(Ordering::Acquire) == 0 { + return; + } + wiphy_unregister(unsafe { (*hw).wiphy }); + unsafe { &*hw }.registered.store(0, Ordering::Release); +} + +#[no_mangle] +pub extern "C" fn ieee80211_queue_work(_hw: *mut Ieee80211Hw, work: *mut c_void) { + if work.is_null() { + return; + } + let _ = schedule_work(work.cast::()); +} + +#[no_mangle] +pub extern "C" fn ieee80211_scan_completed(_hw: *mut Ieee80211Hw, _aborted: bool) {} + +#[no_mangle] +pub extern "C" fn ieee80211_connection_loss(vif: *mut Ieee80211Vif) { + if vif.is_null() { + return; + } + unsafe { (*vif).cfg_assoc = false }; +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rust_impl::workqueue::{flush_scheduled_work, WorkStruct}; + use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering}; + + static WORK_RAN: AtomicBool = AtomicBool::new(false); + + extern "C" fn test_work(_work: *mut WorkStruct) { + WORK_RAN.store(true, AtomicOrdering::Release); + } + + #[test] + fn ieee80211_hw_registration_round_trip_works() { + let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null()); + assert!(!hw.is_null()); + assert_eq!(ieee80211_register_hw(hw), 0); + assert_eq!(ieee80211_register_hw(hw), -16); + assert_eq!(unsafe { (*hw).registered.load(Ordering::Acquire) }, 1); + ieee80211_unregister_hw(hw); + assert_eq!(unsafe { (*hw).registered.load(Ordering::Acquire) }, 0); + ieee80211_free_hw(hw); + } + + #[test] + fn ieee80211_queue_work_dispatches_work() { + let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null()); + assert!(!hw.is_null()); + let mut work = WorkStruct { + func: Some(test_work), + __opaque: [0; 64], + }; + WORK_RAN.store(false, AtomicOrdering::Release); + ieee80211_queue_work(hw, (&mut work as *mut WorkStruct).cast::()); + flush_scheduled_work(); + assert!(WORK_RAN.load(AtomicOrdering::Acquire)); + ieee80211_free_hw(hw); + } + + #[test] + fn connection_loss_clears_assoc_state() { + let mut vif = Ieee80211Vif { + addr: [0; 6], + drv_priv: ptr::null_mut(), + type_: 0, + cfg_assoc: true, + }; + ieee80211_connection_loss(&mut vif); + assert!(!vif.cfg_assoc); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/rust_impl/net.rs b/local/recipes/drivers/linux-kpi/source/src/rust_impl/net.rs new file mode 100644 index 00000000..5779c053 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/net.rs @@ -0,0 +1,387 @@ +use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::ffi::c_void; +use std::ptr; +use std::sync::atomic::{AtomicI32, Ordering}; + +#[repr(C)] +pub struct SkBuff { + pub head: *mut u8, + pub data: *mut u8, + pub len: u32, + pub tail: u32, + pub end: u32, +} + +#[repr(C)] +pub struct NetDevice { + pub name: [u8; 16], + pub dev_addr: [u8; 6], + pub addr_len: u8, + pub mtu: u32, + pub flags: u32, + pub carrier: AtomicI32, + pub ml_priv: *mut c_void, + pub ieee80211_ptr: *mut c_void, + pub priv_data: *mut c_void, + pub registered: AtomicI32, + priv_alloc_size: usize, + priv_alloc_align: usize, +} + +unsafe fn free_skb_buffer(skb: *mut SkBuff) { + if skb.is_null() { + return; + } + let head = (*skb).head; + let end = (*skb).end as usize; + if !head.is_null() && end != 0 { + if let Ok(layout) = Layout::from_size_align(end, 16) { + dealloc(head, layout); + } + } +} + +#[no_mangle] +pub extern "C" fn alloc_skb(size: u32, _gfp_mask: u32) -> *mut SkBuff { + let capacity = size as usize; + let layout = match Layout::from_size_align(capacity.max(1), 16) { + Ok(layout) => layout, + Err(_) => return ptr::null_mut(), + }; + let head = unsafe { alloc_zeroed(layout) }; + if head.is_null() { + return ptr::null_mut(); + } + + Box::into_raw(Box::new(SkBuff { + head, + data: head, + len: 0, + tail: 0, + end: capacity as u32, + })) +} + +#[no_mangle] +pub extern "C" fn kfree_skb(skb: *mut SkBuff) { + if skb.is_null() { + return; + } + unsafe { + free_skb_buffer(skb); + drop(Box::from_raw(skb)); + } +} + +#[no_mangle] +pub extern "C" fn skb_reserve(skb: *mut SkBuff, len: u32) { + if skb.is_null() { + return; + } + + let skb_ref = unsafe { &mut *skb }; + let headroom = unsafe { skb_ref.data.offset_from(skb_ref.head) }; + let Ok(headroom) = u32::try_from(headroom) else { + return; + }; + let new_headroom = headroom.saturating_add(len); + if new_headroom > skb_ref.end || skb_ref.tail != 0 || skb_ref.len != 0 { + return; + } + + skb_ref.data = unsafe { skb_ref.head.add(new_headroom as usize) }; +} + +#[no_mangle] +pub extern "C" fn skb_put(skb: *mut SkBuff, len: u32) -> *mut u8 { + if skb.is_null() { + return ptr::null_mut(); + } + + let skb_ref = unsafe { &mut *skb }; + let new_tail = skb_ref.tail.saturating_add(len); + if new_tail > skb_ref.end { + return ptr::null_mut(); + } + + let ptr = unsafe { skb_ref.data.add(skb_ref.tail as usize) }; + skb_ref.tail = new_tail; + skb_ref.len = skb_ref.len.saturating_add(len); + ptr +} + +#[no_mangle] +pub extern "C" fn skb_push(skb: *mut SkBuff, len: u32) -> *mut u8 { + if skb.is_null() { + return ptr::null_mut(); + } + + let skb_ref = unsafe { &mut *skb }; + let headroom = unsafe { skb_ref.data.offset_from(skb_ref.head) }; + if headroom < len as isize { + return ptr::null_mut(); + } + + skb_ref.data = unsafe { skb_ref.data.sub(len as usize) }; + skb_ref.tail = skb_ref.tail.saturating_add(len); + skb_ref.len = skb_ref.len.saturating_add(len); + skb_ref.data +} + +#[no_mangle] +pub extern "C" fn skb_pull(skb: *mut SkBuff, len: u32) -> *mut u8 { + if skb.is_null() { + return ptr::null_mut(); + } + + let skb_ref = unsafe { &mut *skb }; + if len > skb_ref.len { + return ptr::null_mut(); + } + + skb_ref.data = unsafe { skb_ref.data.add(len as usize) }; + skb_ref.tail -= len; + skb_ref.len -= len; + skb_ref.data +} + +#[no_mangle] +pub extern "C" fn skb_headroom(skb: *const SkBuff) -> u32 { + if skb.is_null() { + return 0; + } + + let skb_ref = unsafe { &*skb }; + let headroom = unsafe { skb_ref.data.offset_from(skb_ref.head) }; + u32::try_from(headroom).unwrap_or_default() +} + +#[no_mangle] +pub extern "C" fn skb_tailroom(skb: *const SkBuff) -> u32 { + if skb.is_null() { + return 0; + } + + let skb_ref = unsafe { &*skb }; + let headroom = skb_headroom(skb); + skb_ref + .end + .saturating_sub(headroom.saturating_add(skb_ref.tail)) +} + +#[no_mangle] +pub extern "C" fn skb_trim(skb: *mut SkBuff, len: u32) { + if skb.is_null() { + return; + } + let skb_ref = unsafe { &mut *skb }; + let new_len = len.min(skb_ref.len); + skb_ref.len = new_len; + skb_ref.tail = new_len; +} + +#[no_mangle] +pub extern "C" fn alloc_netdev_mqs( + sizeof_priv: usize, + name: *const u8, + _name_assign_type: u8, + _setup: Option, + _txqs: u32, + _rxqs: u32, +) -> *mut NetDevice { + let mut dev = Box::new(NetDevice { + name: [0; 16], + dev_addr: [0; 6], + addr_len: 6, + mtu: 1500, + flags: 0, + carrier: AtomicI32::new(0), + ml_priv: ptr::null_mut(), + ieee80211_ptr: ptr::null_mut(), + priv_data: ptr::null_mut(), + registered: AtomicI32::new(0), + priv_alloc_size: 0, + priv_alloc_align: 0, + }); + + if !name.is_null() { + for (idx, byte) in dev.name.iter_mut().enumerate() { + let value = unsafe { *name.add(idx) }; + *byte = value; + if value == 0 { + break; + } + } + } + + if sizeof_priv != 0 { + let layout = match Layout::from_size_align(sizeof_priv, 16) { + Ok(layout) => layout, + Err(_) => return ptr::null_mut(), + }; + let priv_ptr = unsafe { alloc_zeroed(layout) } as *mut c_void; + if priv_ptr.is_null() { + return ptr::null_mut(); + } + dev.priv_data = priv_ptr; + dev.priv_alloc_size = sizeof_priv; + dev.priv_alloc_align = 16; + } + + if let Some(setup) = _setup { + setup(dev.as_mut()); + } + + Box::into_raw(dev) +} + +#[no_mangle] +pub extern "C" fn free_netdev(dev: *mut NetDevice) { + if dev.is_null() { + return; + } + unsafe { + let dev_box = Box::from_raw(dev); + if !dev_box.priv_data.is_null() { + let layout = Layout::from_size_align( + dev_box.priv_alloc_size.max(1), + dev_box.priv_alloc_align.max(1), + ) + .ok(); + if let Some(layout) = layout { + dealloc(dev_box.priv_data.cast::(), layout); + } + } + } +} + +#[no_mangle] +pub extern "C" fn register_netdev(dev: *mut NetDevice) -> i32 { + if dev.is_null() { + return -22; + } + if unsafe { &*dev }.registered.load(Ordering::Acquire) != 0 { + return -16; + } + unsafe { &*dev }.registered.store(1, Ordering::Release); + 0 +} + +#[no_mangle] +pub extern "C" fn unregister_netdev(dev: *mut NetDevice) { + if dev.is_null() { + return; + } + unsafe { &*dev }.registered.store(0, Ordering::Release); + netif_carrier_off(dev); +} + +#[no_mangle] +pub extern "C" fn netif_carrier_on(dev: *mut NetDevice) { + if dev.is_null() { + return; + } + unsafe { &*dev }.carrier.store(1, Ordering::Release); +} + +#[no_mangle] +pub extern "C" fn netif_carrier_off(dev: *mut NetDevice) { + if dev.is_null() { + return; + } + unsafe { &*dev }.carrier.store(0, Ordering::Release); +} + +#[no_mangle] +pub extern "C" fn netif_carrier_ok(dev: *const NetDevice) -> i32 { + if dev.is_null() { + return 0; + } + if unsafe { &*dev }.carrier.load(Ordering::Acquire) != 0 { + 1 + } else { + 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + use std::sync::atomic::AtomicUsize; + + static SETUP_CALLS: AtomicUsize = AtomicUsize::new(0); + + extern "C" fn test_setup(_dev: *mut NetDevice) { + SETUP_CALLS.fetch_add(1, Ordering::AcqRel); + } + + #[test] + fn skb_allocation_and_growth_work() { + let skb = alloc_skb(64, 0); + assert!(!skb.is_null()); + let tail_ptr = skb_put(skb, 8); + assert!(!tail_ptr.is_null()); + assert_eq!(unsafe { (*skb).len }, 8); + skb_trim(skb, 4); + assert_eq!(unsafe { (*skb).len }, 4); + kfree_skb(skb); + } + + #[test] + fn skb_headroom_and_push_pull_work() { + let skb = alloc_skb(64, 0); + assert!(!skb.is_null()); + skb_reserve(skb, 8); + assert_eq!(skb_headroom(skb), 8); + assert_eq!(skb_tailroom(skb), 56); + assert!(!skb_put(skb, 8).is_null()); + assert!(!skb_push(skb, 4).is_null()); + assert_eq!(unsafe { (*skb).len }, 12); + assert!(!skb_pull(skb, 6).is_null()); + assert_eq!(unsafe { (*skb).len }, 6); + kfree_skb(skb); + } + + #[test] + fn net_device_carrier_tracking_works() { + let name = CString::new("wlan%d").unwrap(); + let dev = alloc_netdev_mqs( + 0usize, + name.as_ptr().cast::(), + 0u8, + None::, + 1u32, + 1u32, + ); + assert!(!dev.is_null()); + assert_eq!(netif_carrier_ok(dev), 0); + netif_carrier_on(dev); + assert_eq!(netif_carrier_ok(dev), 1); + netif_carrier_off(dev); + assert_eq!(netif_carrier_ok(dev), 0); + free_netdev(dev); + } + + #[test] + fn net_device_setup_and_registration_work() { + SETUP_CALLS.store(0, Ordering::Release); + let name = CString::new("wlan%d").unwrap(); + let dev = alloc_netdev_mqs( + 32usize, + name.as_ptr().cast::(), + 0u8, + Some(test_setup), + 1u32, + 1u32, + ); + assert!(!dev.is_null()); + assert_eq!(SETUP_CALLS.load(Ordering::Acquire), 1); + assert_eq!(register_netdev(dev), 0); + assert_eq!(unsafe { (*dev).registered.load(Ordering::Acquire) }, 1); + assert_eq!(register_netdev(dev), -16); + unregister_netdev(dev); + assert_eq!(unsafe { (*dev).registered.load(Ordering::Acquire) }, 0); + free_netdev(dev); + } +} 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 new file mode 100644 index 00000000..7099a8f3 --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/rust_impl/wireless.rs @@ -0,0 +1,369 @@ +use std::alloc::{alloc_zeroed, dealloc, Layout}; +use std::ffi::c_void; +use std::ptr; +use std::sync::atomic::{AtomicI32, Ordering}; + +use super::net::{netif_carrier_off, netif_carrier_on, NetDevice}; + +#[repr(C)] +pub struct Wiphy { + pub priv_data: *mut c_void, + pub registered: AtomicI32, + pub interface_modes: u32, + pub max_scan_ssids: i32, + pub max_scan_ie_len: i32, + priv_alloc_size: usize, + priv_alloc_align: usize, +} + +#[repr(C)] +pub struct WirelessDev { + pub wiphy: *mut Wiphy, + pub netdev: *mut c_void, + pub iftype: u32, + pub scan_in_flight: bool, + pub scan_aborted: bool, + pub connecting: bool, + pub connected: bool, + pub locally_generated: bool, + pub last_status: u16, + pub last_reason: u16, + pub has_bssid: bool, + pub last_bssid: [u8; 6], +} + +#[repr(C)] +pub struct Cfg80211ScanInfo { + pub aborted: bool, +} + +#[repr(C)] +pub struct Cfg80211ScanRequest { + pub wiphy: *mut Wiphy, + pub wdev: *mut WirelessDev, + pub n_ssids: u32, + pub n_channels: u32, +} + +#[repr(C)] +pub struct Cfg80211Ssid { + pub ssid: [u8; 32], + pub ssid_len: u8, +} + +#[repr(C)] +pub struct KeyParams { + pub key: *const u8, + pub key_len: u8, + pub cipher: u32, +} + +#[repr(C)] +pub struct Cfg80211ConnectParams { + pub ssid: *const u8, + pub ssid_len: usize, + pub bssid: *const u8, + pub ie: *const u8, + pub ie_len: usize, + pub key: KeyParams, +} + +#[repr(C)] +pub struct StationParameters { + pub supported_rates: *const u8, + pub supported_rates_len: usize, + pub sta_flags_mask: u32, + pub sta_flags_set: u32, +} + +#[no_mangle] +pub extern "C" fn wiphy_new_nm( + _ops: *const c_void, + sizeof_priv: usize, + _requested_name: *const u8, +) -> *mut Wiphy { + let mut wiphy = Box::new(Wiphy { + priv_data: ptr::null_mut(), + registered: AtomicI32::new(0), + interface_modes: 0, + max_scan_ssids: 4, + max_scan_ie_len: 512, + priv_alloc_size: 0, + priv_alloc_align: 0, + }); + + if sizeof_priv != 0 { + let layout = match Layout::from_size_align(sizeof_priv, 16) { + Ok(layout) => layout, + Err(_) => return ptr::null_mut(), + }; + let ptr = unsafe { alloc_zeroed(layout) } as *mut c_void; + if ptr.is_null() { + return ptr::null_mut(); + } + wiphy.priv_data = ptr; + wiphy.priv_alloc_size = sizeof_priv; + wiphy.priv_alloc_align = 16; + } + + Box::into_raw(wiphy) +} + +#[no_mangle] +pub extern "C" fn wiphy_free(wiphy: *mut Wiphy) { + if wiphy.is_null() { + return; + } + unsafe { + let wiphy_box = Box::from_raw(wiphy); + if !wiphy_box.priv_data.is_null() { + if let Ok(layout) = Layout::from_size_align( + wiphy_box.priv_alloc_size.max(1), + wiphy_box.priv_alloc_align.max(1), + ) { + dealloc(wiphy_box.priv_data.cast::(), layout); + } + } + } +} + +#[no_mangle] +pub extern "C" fn wiphy_register(wiphy: *mut Wiphy) -> i32 { + if wiphy.is_null() { + return -22; + } + if unsafe { &*wiphy }.registered.load(Ordering::Acquire) != 0 { + return -16; + } + unsafe { &*wiphy }.registered.store(1, Ordering::Release); + 0 +} + +#[no_mangle] +pub extern "C" fn wiphy_unregister(wiphy: *mut Wiphy) { + if wiphy.is_null() { + return; + } + unsafe { &*wiphy }.registered.store(0, Ordering::Release); +} + +#[no_mangle] +pub extern "C" fn cfg80211_scan_done( + request: *mut Cfg80211ScanRequest, + info: *const Cfg80211ScanInfo, +) { + if request.is_null() { + return; + } + + let wdev = unsafe { (*request).wdev }; + if wdev.is_null() { + return; + } + + unsafe { + (*wdev).scan_in_flight = false; + (*wdev).scan_aborted = if info.is_null() { + false + } else { + (*info).aborted + }; + } +} + +fn netdev_to_wireless_dev(dev: *mut c_void) -> *mut WirelessDev { + if dev.is_null() { + return ptr::null_mut(); + } + let dev = dev.cast::(); + unsafe { (*dev).ieee80211_ptr.cast::() } +} + +fn copy_bssid(dst: &mut WirelessDev, bssid: *const u8) { + if bssid.is_null() { + dst.has_bssid = false; + dst.last_bssid = [0; 6]; + return; + } + + unsafe { + ptr::copy_nonoverlapping(bssid, dst.last_bssid.as_mut_ptr(), dst.last_bssid.len()); + } + dst.has_bssid = true; +} + +#[no_mangle] +pub extern "C" fn cfg80211_connect_result( + dev: *mut c_void, + bssid: *const u8, + _req_ie: *const u8, + _req_ie_len: usize, + _resp_ie: *const u8, + _resp_ie_len: usize, + status: u16, + _gfp: u32, +) { + let wdev = netdev_to_wireless_dev(dev); + if wdev.is_null() { + return; + } + + unsafe { + let wdev_ref = &mut *wdev; + wdev_ref.connecting = false; + wdev_ref.connected = status == 0; + wdev_ref.last_status = status; + wdev_ref.locally_generated = false; + copy_bssid(wdev_ref, bssid); + } + + if status == 0 { + netif_carrier_on(dev.cast::()); + } else { + netif_carrier_off(dev.cast::()); + } +} + +#[no_mangle] +pub extern "C" fn cfg80211_disconnected( + dev: *mut c_void, + reason: u16, + _ie: *const u8, + _ie_len: usize, + locally_generated: bool, + _gfp: u32, +) { + let wdev = netdev_to_wireless_dev(dev); + if !wdev.is_null() { + unsafe { + let wdev_ref = &mut *wdev; + wdev_ref.connecting = false; + wdev_ref.connected = false; + wdev_ref.last_reason = reason; + wdev_ref.locally_generated = locally_generated; + wdev_ref.has_bssid = false; + wdev_ref.last_bssid = [0; 6]; + } + } + + netif_carrier_off(dev.cast::()); +} + +#[no_mangle] +pub extern "C" fn cfg80211_connect_bss( + dev: *mut c_void, + bssid: *const u8, + req_ie: *const u8, + req_ie_len: usize, + resp_ie: *const u8, + resp_ie_len: usize, + status: u16, + gfp: u32, +) { + cfg80211_connect_result( + dev, + bssid, + req_ie, + req_ie_len, + resp_ie, + resp_ie_len, + status, + gfp, + ) +} + +#[no_mangle] +pub extern "C" fn cfg80211_ready_on_channel( + _wdev: *mut WirelessDev, + _cookie: u64, + _chan: *mut c_void, + _chan_type: u32, + _duration: u32, + _gfp: u32, +) { +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rust_impl::net::{alloc_netdev_mqs, free_netdev, netif_carrier_ok}; + use std::ffi::CString; + + #[test] + fn wiphy_registration_round_trip_works() { + let wiphy = wiphy_new_nm(ptr::null(), 0, ptr::null()); + assert!(!wiphy.is_null()); + assert_eq!(wiphy_register(wiphy), 0); + assert_eq!(wiphy_register(wiphy), -16); + assert_eq!(unsafe { (*wiphy).registered.load(Ordering::Acquire) }, 1); + wiphy_unregister(wiphy); + assert_eq!(unsafe { (*wiphy).registered.load(Ordering::Acquire) }, 0); + wiphy_free(wiphy); + } + + #[test] + fn scan_and_connect_lifecycle_updates_wireless_state() { + let name = CString::new("wlan%d").unwrap(); + 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: true, + scan_aborted: false, + connecting: true, + connected: false, + locally_generated: false, + last_status: u16::MAX, + last_reason: 0, + has_bssid: false, + last_bssid: [0; 6], + }; + + unsafe { + (*dev).ieee80211_ptr = (&mut wdev as *mut WirelessDev).cast::(); + } + + let mut request = Cfg80211ScanRequest { + wiphy, + wdev: &mut wdev, + n_ssids: 1, + n_channels: 1, + }; + let info = Cfg80211ScanInfo { aborted: true }; + cfg80211_scan_done(&mut request, &info); + assert!(!wdev.scan_in_flight); + assert!(wdev.scan_aborted); + + let bssid = [1, 2, 3, 4, 5, 6]; + cfg80211_connect_result( + dev.cast::(), + bssid.as_ptr(), + ptr::null(), + 0, + ptr::null(), + 0, + 0, + 0, + ); + assert!(wdev.connected); + assert_eq!(wdev.last_status, 0); + assert!(wdev.has_bssid); + assert_eq!(wdev.last_bssid, bssid); + assert_eq!(netif_carrier_ok(dev), 1); + + cfg80211_disconnected(dev.cast::(), 7, ptr::null(), 0, true, 0); + assert!(!wdev.connected); + assert_eq!(wdev.last_reason, 7); + assert!(wdev.locally_generated); + assert_eq!(netif_carrier_ok(dev), 0); + + wiphy_free(wiphy); + free_netdev(dev); + } +} diff --git a/local/recipes/drivers/linux-kpi/source/src/test_host_redox_shims.rs b/local/recipes/drivers/linux-kpi/source/src/test_host_redox_shims.rs new file mode 100644 index 00000000..3ec03c0e --- /dev/null +++ b/local/recipes/drivers/linux-kpi/source/src/test_host_redox_shims.rs @@ -0,0 +1,29 @@ +#[cfg(all(test, not(target_os = "redox")))] +#[no_mangle] +pub extern "C" fn redox_open_v1( + _path_base: *const u8, + _path_len: usize, + _flags: u32, + _mode: u16, +) -> usize { + usize::MAX +} + +#[cfg(all(test, not(target_os = "redox")))] +#[no_mangle] +pub extern "C" fn redox_close_v1(_fd: usize) -> usize { + 0 +} + +#[cfg(all(test, not(target_os = "redox")))] +#[no_mangle] +pub extern "C" fn redox_sys_call_v0( + _fd: usize, + _payload: *mut u8, + _payload_len: usize, + _flags: usize, + _metadata: *const u64, + _metadata_len: usize, +) -> usize { + usize::MAX +}