wlan_ffi_transport/
transport.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::completers::Completer;
6use fdf::{fdf_arena_t, Arena, ArenaStaticBox};
7use log::error;
8use std::ffi::c_void;
9use std::marker::{PhantomData, PhantomPinned};
10use std::pin::Pin;
11use std::ptr::NonNull;
12use std::{mem, slice};
13use wlan_fidl_ext::{TryUnpack, WithName};
14use {fidl_fuchsia_wlan_softmac as fidl_softmac, fuchsia_trace as trace, wlan_trace as wtrace};
15
16// Defined as an opaque type as suggested by
17// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
18#[repr(C)]
19pub struct FfiEthernetRxCtx {
20    _data: [u8; 0],
21    _marker: PhantomData<(*mut u8, PhantomPinned)>,
22}
23
24#[repr(C)]
25pub struct FfiEthernetRx {
26    ctx: *mut FfiEthernetRxCtx,
27    /// Sends an Ethernet frame to the C++ portion of wlansoftmac.
28    ///
29    /// # Safety
30    ///
31    /// Behavior is undefined unless `payload` contains a persisted `EthernetRx.Transfer` request
32    /// and `payload_len` is the length of the persisted byte array.
33    transfer: unsafe extern "C" fn(
34        ctx: *mut FfiEthernetRxCtx,
35        payload: *const u8,
36        payload_len: usize,
37    ) -> zx::sys::zx_status_t,
38}
39
40// Safety: The FFI provided by FfiEthernetRx is thread-safe. In particular, the wlansoftmac
41// driver synchronizes all of its ddk::EthernetIfcProtocolClient calls.
42unsafe impl Send for FfiEthernetRx {}
43
44pub struct EthernetRx {
45    ffi: FfiEthernetRx,
46}
47
48impl EthernetRx {
49    pub fn new(ffi: FfiEthernetRx) -> Self {
50        Self { ffi }
51    }
52
53    pub fn transfer(
54        &mut self,
55        request: &fidl_softmac::EthernetRxTransferRequest,
56    ) -> Result<(), zx::Status> {
57        wtrace::duration!(c"EthernetRx transfer");
58        let payload = fidl::persist(request);
59        match payload {
60            Err(e) => {
61                error!("Failed to persist EthernetRx.Transfer request: {}", e);
62                Err(zx::Status::INTERNAL)
63            }
64            Ok(payload) => {
65                let payload = payload.as_slice();
66                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
67                // `EthernetRx.Transfer` request.
68                zx::Status::from_raw(unsafe {
69                    (self.ffi.transfer)(self.ffi.ctx, payload.as_ptr(), payload.len())
70                })
71                .into()
72            }
73        }
74    }
75}
76
77// Defined as an opaque type as suggested by
78// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
79#[repr(C)]
80pub struct FfiWlanTxCtx {
81    _data: [u8; 0],
82    _marker: PhantomData<(*mut u8, PhantomPinned)>,
83}
84
85#[repr(C)]
86pub struct FfiWlanTx {
87    ctx: *mut FfiWlanTxCtx,
88    /// Sends a WLAN MAC frame to the C++ portion of wlansoftmac.
89    ///
90    /// # Safety
91    ///
92    /// Behavior is undefined unless `payload` contains a persisted `WlanTx.Transfer` request
93    /// and `payload_len` is the length of the persisted byte array.
94    transfer: unsafe extern "C" fn(
95        ctx: *mut FfiWlanTxCtx,
96        payload: *const u8,
97        payload_len: usize,
98    ) -> zx::sys::zx_status_t,
99}
100
101// Safety: The FFI provided by FfiWlanTx is thread-safe. In particular, the wlansoftmac
102// driver synchronizes all of its fdf::SharedClient<fuchsia_wlan_softmac::WlanSoftmac>
103// calls.
104unsafe impl Send for FfiWlanTx {}
105
106pub struct WlanTx {
107    ffi: FfiWlanTx,
108}
109
110impl WlanTx {
111    pub fn new(ffi: FfiWlanTx) -> Self {
112        Self { ffi }
113    }
114
115    pub fn transfer(
116        &mut self,
117        request: &fidl_softmac::WlanTxTransferRequest,
118    ) -> Result<(), zx::Status> {
119        wtrace::duration!(c"WlanTx transfer");
120        let payload = fidl::persist(request);
121        match payload {
122            Err(e) => {
123                error!("Failed to persist WlanTx.Transfer request: {}", e);
124                Err(zx::Status::INTERNAL)
125            }
126            Ok(payload) => {
127                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
128                // `EthernetRx.Transfer` request.
129                zx::Status::from_raw(unsafe {
130                    (self.ffi.transfer)(self.ffi.ctx, payload.as_slice().as_ptr(), payload.len())
131                })
132                .into()
133            }
134        }
135    }
136}
137
138pub trait EthernetTxEventSender {
139    fn unbounded_send(&self, event: EthernetTxEvent) -> Result<(), (String, EthernetTxEvent)>;
140}
141
142#[repr(C)]
143pub struct FfiEthernetTxCtx {
144    sender: Box<dyn EthernetTxEventSender>,
145    pin: PhantomPinned,
146}
147
148#[repr(C)]
149pub struct FfiEthernetTx {
150    ctx: *const FfiEthernetTxCtx,
151    transfer: unsafe extern "C" fn(
152        ctx: *const FfiEthernetTxCtx,
153        request: *const u8,
154        request_size: usize,
155    ) -> zx::sys::zx_status_t,
156}
157
158pub struct EthernetTx {
159    ctx: Pin<Box<FfiEthernetTxCtx>>,
160}
161
162// TODO(https://fxbug.dev/42119762): We need to keep stats for these events and respond to StatsQueryRequest.
163pub struct EthernetTxEvent {
164    pub bytes: NonNull<[u8]>,
165    pub async_id: trace::Id,
166    pub borrowed_operation: Completer<Box<dyn FnOnce(zx::sys::zx_status_t)>>,
167}
168
169impl EthernetTx {
170    /// Return a pinned `EthernetTx`.
171    ///
172    /// Pinning the returned value is imperative to ensure future `to_c_binding()` calls will return
173    /// pointers that are valid for the lifetime of the returned value.
174    pub fn new(sender: Box<dyn EthernetTxEventSender>) -> Self {
175        Self { ctx: Box::pin(FfiEthernetTxCtx { sender, pin: PhantomPinned }) }
176    }
177
178    /// Returns a `FfiEthernetTx` containing functions to queue `EthernetTxEvent` values into the
179    /// corresponding `EthernetTx`.
180    ///
181    /// Note that the pointers in the returned `FfiEthernetTx` are all to static and pinned values
182    /// so it's safe to move this `EthernetTx` after calling this function.
183    ///
184    /// # Safety
185    ///
186    /// This method unsafe because we cannot guarantee the returned `FfiEthernetTxCtx`
187    /// will have a lifetime that is shorther than this `EthernetTx`.
188    ///
189    /// By using this method, the caller promises the lifetime of this `EthernetTx` will exceed the
190    /// `ctx` pointer used across the FFI boundary.
191    pub unsafe fn to_ffi(&self) -> FfiEthernetTx {
192        FfiEthernetTx {
193            ctx: &*self.ctx.as_ref() as *const FfiEthernetTxCtx,
194            transfer: Self::ethernet_tx_transfer,
195        }
196    }
197
198    /// Queues an Ethernet frame into the `EthernetTx` for processing.
199    ///
200    /// The caller should either end the async
201    /// trace event corresponding to |async_id| if an error occurs or deferred ending the trace to a later call
202    /// into the C++ portion of wlansoftmac.
203    ///
204    /// Assuming no errors occur, the Rust portion of wlansoftmac will eventually
205    /// rust_device_interface_t.queue_tx() with the same |async_id|. At that point, the C++ portion of
206    /// wlansoftmac will assume responsibility for ending the async trace event.
207    ///
208    /// # Errors
209    ///
210    /// This function will return ZX_ERR_BAD_STATE if and only if it did not claim ownership
211    /// of the eth::BorrowedOperation before returning.
212    ///
213    /// # Safety
214    ///
215    /// Behavior is undefined unless `payload` points to a persisted
216    /// `fuchsia.wlan.softmac/EthernetTx.Transfer` request of length `payload_len` that is properly
217    /// aligned.
218    #[no_mangle]
219    unsafe extern "C" fn ethernet_tx_transfer(
220        ctx: *const FfiEthernetTxCtx,
221        payload: *const u8,
222        payload_len: usize,
223    ) -> zx::sys::zx_status_t {
224        wtrace::duration!(c"EthernetTx transfer");
225
226        // Safety: This call is safe because the caller promises `payload` points to a persisted
227        // `fuchsia.wlan.softmac/EthernetTx.Transfer` request of length `payload_len` that is properly
228        // aligned.
229        let payload = unsafe { slice::from_raw_parts(payload, payload_len) };
230        let payload = match fidl::unpersist::<fidl_softmac::EthernetTxTransferRequest>(payload) {
231            Ok(payload) => payload,
232            Err(e) => {
233                error!("Unable to unpersist EthernetTx.Transfer request: {}", e);
234                return zx::Status::BAD_STATE.into_raw();
235            }
236        };
237
238        let borrowed_operation =
239            match payload.borrowed_operation.with_name("borrowed_operation").try_unpack() {
240                Ok(x) => x as *mut c_void,
241                Err(e) => {
242                    let e = e.context("Missing required field in EthernetTxTransferRequest.");
243                    error!("{}", e);
244                    return zx::Status::BAD_STATE.into_raw();
245                }
246            };
247
248        let complete_borrowed_operation: unsafe extern "C" fn(
249            borrowed_operation: *mut c_void,
250            status: zx::sys::zx_status_t,
251        ) = match payload
252            .complete_borrowed_operation
253            .with_name("complete_borrowed_operation")
254            .try_unpack()
255        {
256            // Safety: Per the safety documentation of this FFI, the sender promises
257            // this field has the type unsafe extern "C" fn(*mut c_void, zx::sys::zx_status_t).
258            Ok(x) => unsafe { mem::transmute(x) },
259            Err(e) => {
260                let e = e.context("Missing required field in EthernetTxTransferRequest.");
261                error!("{}", e);
262                return zx::Status::BAD_STATE.into_raw();
263            }
264        };
265
266        // Box the closure so that EthernetTxEventSender can be object-safe.
267        let borrowed_operation: Completer<Box<dyn FnOnce(zx::sys::zx_status_t)>> = {
268            // Safety: This call of `complete_borrowed_operation` uses the value
269            // of the received `borrowed_operation` field as its first argument
270            // and will only be called once.
271            let completer = Box::new(move |status| unsafe {
272                complete_borrowed_operation(borrowed_operation, status);
273            });
274            // Safety: The borrowed_operation pointer and complete_borrowed_operation
275            // function are both thread-safe.
276            unsafe { Completer::new_unchecked(completer) }
277        };
278
279        let async_id = match payload.async_id.with_name("async_id").try_unpack() {
280            Ok(x) => x,
281            Err(e) => {
282                let e = e.context("Missing required field in EthernetTxTransferRequest.");
283                error!("{}", e);
284                return zx::Status::INVALID_ARGS.into_raw();
285            }
286        };
287
288        let (packet_address, packet_size) = match (
289            payload.packet_address.with_name("packet_address"),
290            payload.packet_size.with_name("packet_size"),
291        )
292            .try_unpack()
293        {
294            Ok(x) => x,
295            Err(e) => {
296                let e = e.context("Missing required field(s) in EthernetTxTransferRequest.");
297                error!("{}", e);
298                return zx::Status::INVALID_ARGS.into_raw();
299            }
300        };
301
302        let packet_ptr = packet_address as *mut u8;
303        if packet_ptr.is_null() {
304            error!("EthernetTx.Transfer request contained NULL packet_address");
305            return zx::Status::INVALID_ARGS.into_raw();
306        }
307
308        // Safety: This call is safe because a `EthernetTx` request is defined such that a slice
309        // such as this one can be constructed from the `packet_address` and `packet_size` fields.
310        let bytes = unsafe {
311            NonNull::new_unchecked(slice::from_raw_parts_mut(packet_ptr, packet_size as usize))
312        };
313
314        // Safety: This dereference is safe because the lifetime of this pointer was promised to
315        // live as long as function could be called when `EthernetTx::to_ffi` was called.
316        match unsafe {
317            (*ctx).sender.unbounded_send(EthernetTxEvent {
318                bytes,
319                async_id: async_id.into(),
320                borrowed_operation,
321            })
322        } {
323            Err((error, _event)) => {
324                error!("Failed to queue EthernetTx.Transfer request: {}", error);
325                zx::Status::INTERNAL.into_raw()
326            }
327            Ok(()) => zx::Status::OK.into_raw(),
328        }
329    }
330}
331
332pub trait WlanRxEventSender {
333    fn unbounded_send(&self, event: WlanRxEvent) -> Result<(), (String, WlanRxEvent)>;
334}
335
336#[repr(C)]
337pub struct FfiWlanRxCtx {
338    sender: Box<dyn WlanRxEventSender>,
339    pin: PhantomPinned,
340}
341
342#[repr(C)]
343pub struct FfiWlanRx {
344    ctx: *const FfiWlanRxCtx,
345    transfer:
346        unsafe extern "C" fn(ctx: *const FfiWlanRxCtx, request: *const u8, request_size: usize),
347}
348
349pub struct WlanRx {
350    ctx: Pin<Box<FfiWlanRxCtx>>,
351}
352
353/// Indicates receipt of a MAC frame.
354// TODO(https://fxbug.dev/42119762): We need to keep stats for these events and respond to StatsQueryRequest.
355pub struct WlanRxEvent {
356    pub bytes: ArenaStaticBox<[u8]>,
357    pub rx_info: fidl_softmac::WlanRxInfo,
358    pub async_id: trace::Id,
359}
360
361impl WlanRx {
362    /// Return a pinned `WlanRx`.
363    ///
364    /// Pinning the returned value is imperative to ensure future `to_c_binding()` calls will return
365    /// pointers that are valid for the lifetime of the returned value.
366    pub fn new(sender: Box<dyn WlanRxEventSender>) -> Self {
367        Self { ctx: Box::pin(FfiWlanRxCtx { sender, pin: PhantomPinned }) }
368    }
369
370    /// Returns a `FfiWlanRx` containing functions to queue `WlanRxEvent` values into the
371    /// corresponding `WlanRx`.
372    ///
373    /// Note that the pointers in the returned `FfiWlanRx` are all to static and pinned values
374    /// so it's safe to move this `WlanRx` after calling this function.
375    ///
376    /// # Safety
377    ///
378    /// This method unsafe because we cannot guarantee the returned `FfiWlanRxCtx`
379    /// will have a lifetime that is shorther than this `WlanRx`.
380    ///
381    /// By using this method, the caller promises the lifetime of this `WlanRx` will exceed the
382    /// `ctx` pointer used across the FFI boundary.
383    pub unsafe fn to_ffi(&self) -> FfiWlanRx {
384        FfiWlanRx {
385            ctx: &*self.ctx.as_ref() as *const FfiWlanRxCtx,
386            transfer: Self::wlan_rx_transfer,
387        }
388    }
389
390    /// Queues a WLAN MAC frame into the `WlanRx` for processing.
391    ///
392    /// # Safety
393    ///
394    /// Behavior is undefined unless `payload` points to a persisted
395    /// `fuchsia.wlan.softmac/WlanRx.Transfer` request of length `payload_len` that is properly
396    /// aligned.
397    #[no_mangle]
398    unsafe extern "C" fn wlan_rx_transfer(
399        ctx: *const FfiWlanRxCtx,
400        payload: *const u8,
401        payload_len: usize,
402    ) {
403        wtrace::duration!(c"WlanRx transfer");
404
405        // Safety: This call is safe because the caller promises `payload` points to a persisted
406        // `fuchsia.wlan.softmac/WlanRx.Transfer` request of length `payload_len` that is properly
407        // aligned.
408        let payload = unsafe { slice::from_raw_parts(payload, payload_len) };
409        let payload = match fidl::unpersist::<fidl_softmac::WlanRxTransferRequest>(payload) {
410            Ok(payload) => payload,
411            Err(e) => {
412                error!("Unable to unpersist WlanRx.Transfer request: {}", e);
413                return;
414            }
415        };
416
417        let async_id = match payload.async_id.with_name("async_id").try_unpack() {
418            Ok(x) => x,
419            Err(e) => {
420                let e = e.context("Missing required field in WlanRxTransferRequest.");
421                error!("{}", e);
422                return;
423            }
424        };
425
426        let arena = match payload.arena.with_name("arena").try_unpack() {
427            Ok(x) => {
428                if x == 0 {
429                    error!("Received arena is null");
430                    return;
431                }
432                // Safety: The received arena is assumed to be valid if it's not null.
433                unsafe { Arena::from_raw(NonNull::new_unchecked(x as *mut fdf_arena_t)) }
434            }
435            Err(e) => {
436                let e = e.context("Missing required field in WlanRxTransferRequest.");
437                error!("{}", e);
438                return;
439            }
440        };
441
442        let (packet_address, packet_size, packet_info) = match (
443            payload.packet_address.with_name("packet_address"),
444            payload.packet_size.with_name("packet_size"),
445            payload.packet_info.with_name("packet_info"),
446        )
447            .try_unpack()
448        {
449            Ok(x) => x,
450            Err(e) => {
451                let e = e.context("Missing required field(s) in WlanRxTransferRequest.");
452                error!("{}", e);
453                wtrace::async_end_wlansoftmac_rx(async_id.into(), &e.to_string());
454                return;
455            }
456        };
457
458        let packet_ptr = packet_address as *mut u8;
459        if packet_ptr.is_null() {
460            let e = "WlanRx.Transfer request contained NULL packet_address";
461            error!("{}", e);
462            wtrace::async_end_wlansoftmac_rx(async_id.into(), e);
463            return;
464        }
465
466        // Safety: This call is safe because a `WlanRx` request is defined such that a slice
467        // such as this one can be constructed from the `packet_address` and `packet_size` fields.
468        // Also, the slice is allocated in `arena`.
469        let bytes = unsafe {
470            arena.assume_unchecked(NonNull::new_unchecked(slice::from_raw_parts_mut(
471                packet_ptr,
472                packet_size as usize,
473            )))
474        };
475        let bytes = arena.make_static(bytes);
476
477        // Safety: This dereference is safe because the lifetime of this pointer was promised to
478        // live as long as function could be called when `WlanRx::to_ffi` was called.
479        let _: Result<(), ()> = unsafe {
480            (*ctx).sender.unbounded_send(WlanRxEvent {
481                bytes,
482                rx_info: packet_info,
483                async_id: async_id.into(),
484            })
485        }
486        .map_err(|(error, _event)| {
487            let e = format!("Failed to queue WlanRx.Transfer request: {}", error);
488            error!("{}", error);
489            wtrace::async_end_wlansoftmac_rx(async_id.into(), &e);
490        });
491    }
492}