Expand linux-kpi wireless and networking scaffolding
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -17,6 +17,10 @@ const EBUSY: i32 = 16;
|
||||
lazy_static::lazy_static! {
|
||||
static ref STA_REGISTRY: Mutex<HashMap<usize, StaRegistryEntry>> = Mutex::new(HashMap::new());
|
||||
static ref BA_SESSIONS: Mutex<HashMap<usize, Vec<u16>>> = Mutex::new(HashMap::new());
|
||||
static ref RX_QUEUE: Mutex<Vec<(usize, usize)>> = Mutex::new(Vec::new());
|
||||
static ref HW_WDEV_MAP: Mutex<HashMap<usize, usize>> = Mutex::new(HashMap::new());
|
||||
static ref RX_CALLBACKS: Mutex<HashMap<usize, usize>> = Mutex::new(HashMap::new());
|
||||
static ref TX_STATS: Mutex<HashMap<usize, TxStats>> = Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -26,6 +30,15 @@ struct StaRegistryEntry {
|
||||
state: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct TxStats {
|
||||
pub total: u64,
|
||||
pub acked: u64,
|
||||
pub nacked: u64,
|
||||
}
|
||||
|
||||
pub type RxCallback = extern "C" fn(*mut Ieee80211Hw, *mut SkBuff);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Ieee80211Ops {
|
||||
pub tx: Option<extern "C" fn(*mut Ieee80211Hw, *mut SkBuff)>,
|
||||
@@ -177,8 +190,37 @@ pub extern "C" fn ieee80211_free_hw(hw: *mut Ieee80211Hw) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
let hw_key = hw as usize;
|
||||
|
||||
if let Ok(mut map) = HW_WDEV_MAP.lock() {
|
||||
map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut map) = RX_CALLBACKS.lock() {
|
||||
map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut stats_map) = TX_STATS.lock() {
|
||||
stats_map.remove(&hw_key);
|
||||
}
|
||||
|
||||
if let Ok(mut queue) = RX_QUEUE.lock() {
|
||||
let mut i = 0;
|
||||
while i < queue.len() {
|
||||
if queue[i].0 == hw_key {
|
||||
let (_, skb_key) = queue.swap_remove(i);
|
||||
let skb = skb_key as *mut SkBuff;
|
||||
if !skb.is_null() {
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut registry) = STA_REGISTRY.lock() {
|
||||
registry.retain(|_, entry| entry.hw != hw as usize);
|
||||
registry.retain(|_, entry| entry.hw != hw_key);
|
||||
}
|
||||
unsafe {
|
||||
let hw_box = Box::from_raw(hw);
|
||||
@@ -230,8 +272,93 @@ pub extern "C" fn ieee80211_queue_work(_hw: *mut Ieee80211Hw, work: *mut c_void)
|
||||
let _ = schedule_work(work.cast::<WorkStruct>());
|
||||
}
|
||||
|
||||
/// Register the WirelessDev associated with an Ieee80211Hw.
|
||||
/// Must be called by the driver after ieee80211_alloc_hw_nm and before scan/connect.
|
||||
/// Required for ieee80211_scan_completed to find the correct wdev.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_scan_completed(_hw: *mut Ieee80211Hw, _aborted: bool) {}
|
||||
pub extern "C" fn ieee80211_link_hw_wdev(
|
||||
hw: *mut Ieee80211Hw,
|
||||
wdev: *mut super::wireless::WirelessDev,
|
||||
) {
|
||||
if hw.is_null() || wdev.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut map) = HW_WDEV_MAP.lock() {
|
||||
map.insert(hw as usize, wdev as usize);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a per-hw callback that receives drained RX frames.
|
||||
/// When `ieee80211_rx_drain` processes a queued frame and a callback
|
||||
/// is registered for the hw instance, the frame is delivered to the
|
||||
/// callback instead of being logged and freed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_register_rx_handler(
|
||||
hw: *mut Ieee80211Hw,
|
||||
callback: Option<RxCallback>,
|
||||
) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
if let Ok(mut map) = RX_CALLBACKS.lock() {
|
||||
match callback {
|
||||
Some(cb) => {
|
||||
map.insert(hw as usize, cb as usize);
|
||||
}
|
||||
None => {
|
||||
map.remove(&(hw as usize));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve accumulated TX statistics for a given hw instance.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_get_tx_stats(hw: *mut Ieee80211Hw) -> TxStats {
|
||||
if hw.is_null() {
|
||||
return TxStats::default();
|
||||
}
|
||||
if let Ok(stats_map) = TX_STATS.lock() {
|
||||
stats_map.get(&(hw as usize)).copied().unwrap_or_default()
|
||||
} else {
|
||||
TxStats::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_scan_completed(hw: *mut Ieee80211Hw, aborted: bool) {
|
||||
if hw.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let wiphy = unsafe { (*hw).wiphy };
|
||||
if wiphy.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let wdev_ptr = match HW_WDEV_MAP.lock() {
|
||||
Ok(map) => match map.get(&(hw as usize)) {
|
||||
Some(&ptr) => ptr as *mut super::wireless::WirelessDev,
|
||||
None => {
|
||||
log::warn!(
|
||||
"ieee80211_scan_completed: no wdev registered for hw={:#x}",
|
||||
hw as usize
|
||||
);
|
||||
return;
|
||||
}
|
||||
},
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let scan_info = super::wireless::Cfg80211ScanInfo { aborted };
|
||||
let mut scan_request = super::wireless::Cfg80211ScanRequest {
|
||||
wiphy,
|
||||
wdev: wdev_ptr,
|
||||
n_ssids: 0,
|
||||
n_channels: 0,
|
||||
};
|
||||
super::wireless::cfg80211_scan_done(&mut scan_request, &scan_info);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_connection_loss(vif: *mut Ieee80211Vif) {
|
||||
@@ -291,6 +418,129 @@ pub extern "C" fn ieee80211_rx_irqsafe(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
if hw.is_null() || skb.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let hw_key = hw as usize;
|
||||
let skb_key = skb as usize;
|
||||
if let Ok(mut queue) = RX_QUEUE.lock() {
|
||||
queue.push((hw_key, skb_key));
|
||||
log::trace!(
|
||||
"ieee80211_rx_irqsafe: queued frame hw={:#x} skb={:#x} queue_len={}",
|
||||
hw_key,
|
||||
skb_key,
|
||||
queue.len()
|
||||
);
|
||||
} else {
|
||||
log::warn!("ieee80211_rx_irqsafe: failed to lock RX queue, dropping frame");
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain and consume all queued RX frames for a specific hw instance.
|
||||
/// If an RX handler has been registered via ieee80211_register_rx_handler,
|
||||
/// frames are delivered to that handler. Otherwise frames are classified,
|
||||
/// logged, and freed.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_rx_drain(hw: *mut Ieee80211Hw) -> usize {
|
||||
if hw.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let hw_key = hw as usize;
|
||||
|
||||
let rx_callback = if let Ok(map) = RX_CALLBACKS.lock() {
|
||||
map.get(&hw_key).copied()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Ok(mut queue) = RX_QUEUE.lock() else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let mut drained = 0usize;
|
||||
let mut i = 0;
|
||||
while i < queue.len() {
|
||||
if queue[i].0 == hw_key {
|
||||
let (_, skb_key) = queue.swap_remove(i);
|
||||
let skb = skb_key as *mut SkBuff;
|
||||
if !skb.is_null() {
|
||||
if let Some(cb) = rx_callback {
|
||||
let callback: RxCallback = unsafe { std::mem::transmute(cb) };
|
||||
callback(hw, skb);
|
||||
} else {
|
||||
let skb_ref = unsafe { &mut *skb };
|
||||
let frame_type = extract_frame_type(skb_ref);
|
||||
let frame_len = skb_ref.len;
|
||||
|
||||
match frame_type {
|
||||
FrameType::Management(subtype) => {
|
||||
log::debug!(
|
||||
"rx_drain: mgmt subtype={} len={} hw={:#x}",
|
||||
subtype,
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
FrameType::Data => {
|
||||
log::debug!("rx_drain: data frame len={} hw={:#x}", frame_len, hw_key);
|
||||
}
|
||||
FrameType::Control(subtype) => {
|
||||
log::trace!(
|
||||
"rx_drain: ctrl subtype={} len={} hw={:#x}",
|
||||
subtype,
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
FrameType::Unknown => {
|
||||
log::trace!(
|
||||
"rx_drain: unknown frame len={} hw={:#x}",
|
||||
frame_len,
|
||||
hw_key
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
}
|
||||
drained += 1;
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
if drained > 0 {
|
||||
log::debug!(
|
||||
"ieee80211_rx_drain: hw={:#x} drained {} frames",
|
||||
hw_key,
|
||||
drained
|
||||
);
|
||||
}
|
||||
drained
|
||||
}
|
||||
|
||||
enum FrameType {
|
||||
Management(u8),
|
||||
Data,
|
||||
Control(u8),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
fn extract_frame_type(skb: &SkBuff) -> FrameType {
|
||||
let len = (skb.len as usize).min(2);
|
||||
if len < 2 {
|
||||
return FrameType::Unknown;
|
||||
}
|
||||
let data = unsafe { std::slice::from_raw_parts(skb.data, len) };
|
||||
let frame_ctl = u16::from_le_bytes([data[0], data[1]]);
|
||||
let type_val = ((frame_ctl >> 2) & 0x3) as u8;
|
||||
let subtype = ((frame_ctl >> 4) & 0xF) as u8;
|
||||
match type_val {
|
||||
0 => FrameType::Management(subtype),
|
||||
1 => FrameType::Control(subtype),
|
||||
2 => FrameType::Data,
|
||||
_ => FrameType::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -298,6 +548,20 @@ pub extern "C" fn ieee80211_tx_status(hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
if hw.is_null() || skb.is_null() {
|
||||
return;
|
||||
}
|
||||
let hw_key = hw as usize;
|
||||
|
||||
if let Ok(mut stats_map) = TX_STATS.lock() {
|
||||
let stats = stats_map.entry(hw_key).or_default();
|
||||
stats.total += 1;
|
||||
stats.acked += 1;
|
||||
}
|
||||
|
||||
log::trace!(
|
||||
"ieee80211_tx_status: hw={:#x} skb={:#x}",
|
||||
hw_key,
|
||||
skb as usize
|
||||
);
|
||||
super::net::kfree_skb(skb);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -305,19 +569,53 @@ pub extern "C" fn ieee80211_get_tid(skb: *const SkBuff) -> u8 {
|
||||
if skb.is_null() {
|
||||
return 0;
|
||||
}
|
||||
unsafe {
|
||||
let s = &*skb;
|
||||
if s.data.is_null() || s.len < 26 {
|
||||
return 0;
|
||||
}
|
||||
let frame_control = u16::from_le_bytes([*s.data, *s.data.add(1)]);
|
||||
let subtype = (frame_control >> 4) & 0xF;
|
||||
if subtype != 0x8 {
|
||||
return 0;
|
||||
}
|
||||
let qos = (frame_control >> 7) & 0x1;
|
||||
if qos == 0 {
|
||||
return 0;
|
||||
}
|
||||
let qos_offset: usize = 24;
|
||||
if (s.len as usize) < qos_offset + 2 {
|
||||
return 0;
|
||||
}
|
||||
let tid = (*s.data.add(qos_offset)) & 0xF;
|
||||
tid
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
#[repr(C)]
|
||||
struct ChanDef {
|
||||
center_freq: u32,
|
||||
band: u16,
|
||||
channel: *mut c_void,
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ieee80211_chandef_create(
|
||||
chandef: *mut c_void,
|
||||
channel: *const super::wireless::Ieee80211Channel,
|
||||
_chan_type: u32,
|
||||
chan_type: u32,
|
||||
) {
|
||||
if chandef.is_null() || channel.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let cd = chandef.cast::<ChanDef>();
|
||||
let ch = &*channel;
|
||||
(*cd).center_freq = ch.center_freq as u32;
|
||||
(*cd).band = ch.band as u16;
|
||||
(*cd).channel = channel as *mut c_void;
|
||||
let _ = chan_type;
|
||||
}
|
||||
}
|
||||
|
||||
pub const IEEE80211_STA_NOTEXIST: u32 = 0;
|
||||
@@ -587,4 +885,74 @@ mod tests {
|
||||
fn ieee80211_get_tid_returns_zero_for_null() {
|
||||
assert_eq!(ieee80211_get_tid(ptr::null()), 0);
|
||||
}
|
||||
|
||||
static RX_RECEIVED: AtomicI32 = AtomicI32::new(0);
|
||||
|
||||
extern "C" fn test_rx_callback(_hw: *mut Ieee80211Hw, skb: *mut SkBuff) {
|
||||
RX_RECEIVED.fetch_add(1, Ordering::Release);
|
||||
super::super::net::kfree_skb(skb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rx_callback_receives_drained_frames() {
|
||||
let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null());
|
||||
assert!(!hw.is_null());
|
||||
|
||||
RX_RECEIVED.store(0, Ordering::Release);
|
||||
ieee80211_register_rx_handler(hw, Some(test_rx_callback));
|
||||
|
||||
let data: [u8; 24] = [
|
||||
0x88u8, 0x01, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x06, 0x05, 0x04, 0x03,
|
||||
0x02, 0x01, 0xAA, 0xAA, 0x03, 0x00, 0x00, 0x00, 0x08, 0x06,
|
||||
];
|
||||
let skb = super::super::net::alloc_skb(128, 0);
|
||||
assert!(!skb.is_null());
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(data.as_ptr(), (*skb).data, data.len());
|
||||
(*skb).len = data.len() as u32;
|
||||
}
|
||||
ieee80211_rx_irqsafe(hw, skb);
|
||||
assert_eq!(ieee80211_rx_drain(hw), 1);
|
||||
assert_eq!(RX_RECEIVED.load(Ordering::Acquire), 1);
|
||||
|
||||
ieee80211_register_rx_handler(hw, None);
|
||||
let skb2 = super::super::net::alloc_skb(128, 0);
|
||||
assert!(!skb2.is_null());
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(data.as_ptr(), (*skb2).data, data.len());
|
||||
(*skb2).len = data.len() as u32;
|
||||
}
|
||||
ieee80211_rx_irqsafe(hw, skb2);
|
||||
assert_eq!(ieee80211_rx_drain(hw), 1);
|
||||
assert_eq!(RX_RECEIVED.load(Ordering::Acquire), 1);
|
||||
|
||||
ieee80211_free_hw(hw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tx_status_tracks_statistics() {
|
||||
let hw = ieee80211_alloc_hw_nm(0, ptr::null(), ptr::null());
|
||||
assert!(!hw.is_null());
|
||||
|
||||
let stats = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats.total, 0);
|
||||
assert_eq!(stats.acked, 0);
|
||||
|
||||
for _ in 0..3 {
|
||||
let skb = super::super::net::alloc_skb(64, 0);
|
||||
assert!(!skb.is_null());
|
||||
unsafe {
|
||||
(*skb).len = 10;
|
||||
}
|
||||
ieee80211_tx_status(hw, skb);
|
||||
}
|
||||
|
||||
let stats = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats.total, 3);
|
||||
assert_eq!(stats.acked, 3);
|
||||
|
||||
ieee80211_free_hw(hw);
|
||||
let stats_after_free = ieee80211_get_tx_stats(hw);
|
||||
assert_eq!(stats_after_free.total, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user