Skip to main content

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::{Arena, ArenaStaticBox, fdf_arena_t};
7use fidl_fuchsia_wlan_softmac as fidl_softmac;
8use fuchsia_trace as trace;
9use log::error;
10use std::ffi::c_void;
11use std::marker::{PhantomData, PhantomPinned};
12use std::pin::Pin;
13use std::ptr::NonNull;
14use std::{mem, slice};
15use wlan_fidl_ext::{TryUnpack, WithName};
16use wlan_trace as wtrace;
17
18// Defined as an opaque type as suggested by
19// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
20#[repr(C)]
21pub struct FfiEthernetRxCtx {
22    _data: [u8; 0],
23    _marker: PhantomData<(*mut u8, PhantomPinned)>,
24}
25
26#[repr(C)]
27pub struct FfiEthernetRx {
28    ctx: *mut FfiEthernetRxCtx,
29    /// Sends an Ethernet frame to the C++ portion of wlansoftmac.
30    ///
31    /// # Safety
32    ///
33    /// Behavior is undefined unless `payload` contains a persisted `EthernetRx.Transfer` request
34    /// and `payload_len` is the length of the persisted byte array.
35    transfer: unsafe extern "C" fn(
36        ctx: *mut FfiEthernetRxCtx,
37        payload: *const u8,
38        payload_len: usize,
39    ) -> zx::sys::zx_status_t,
40}
41
42// Safety: The FFI provided by FfiEthernetRx is thread-safe. In particular, the wlansoftmac
43// driver synchronizes all of its ddk::EthernetIfcProtocolClient calls.
44unsafe impl Send for FfiEthernetRx {}
45
46pub struct EthernetRx {
47    ffi: FfiEthernetRx,
48}
49
50impl EthernetRx {
51    pub fn new(ffi: FfiEthernetRx) -> Self {
52        Self { ffi }
53    }
54
55    pub fn transfer(
56        &mut self,
57        request: &fidl_softmac::EthernetRxTransferRequest,
58    ) -> Result<(), zx::Status> {
59        wtrace::duration!("EthernetRx transfer");
60        let payload = fidl::persist(request);
61        match payload {
62            Err(e) => {
63                error!("Failed to persist EthernetRx.Transfer request: {}", e);
64                Err(zx::Status::INTERNAL)
65            }
66            Ok(payload) => {
67                let payload = payload.as_slice();
68                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
69                // `EthernetRx.Transfer` request.
70                zx::Status::ok(unsafe {
71                    (self.ffi.transfer)(self.ffi.ctx, payload.as_ptr(), payload.len())
72                })
73            }
74        }
75    }
76}
77
78// Defined as an opaque type as suggested by
79// https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs.
80#[repr(C)]
81pub struct FfiWlanTxCtx {
82    _data: [u8; 0],
83    _marker: PhantomData<(*mut u8, PhantomPinned)>,
84}
85
86#[repr(C)]
87pub struct FfiWlanTx {
88    ctx: *mut FfiWlanTxCtx,
89    /// Sends a WLAN MAC frame to the C++ portion of wlansoftmac.
90    ///
91    /// # Safety
92    ///
93    /// Behavior is undefined unless `payload` contains a persisted `WlanTx.Transfer` request
94    /// and `payload_len` is the length of the persisted byte array.
95    transfer: unsafe extern "C" fn(
96        ctx: *mut FfiWlanTxCtx,
97        payload: *const u8,
98        payload_len: usize,
99    ) -> zx::sys::zx_status_t,
100}
101
102// Safety: The FFI provided by FfiWlanTx is thread-safe. In particular, the wlansoftmac
103// driver synchronizes all of its fdf::SharedClient<fuchsia_wlan_softmac::WlanSoftmac>
104// calls.
105unsafe impl Send for FfiWlanTx {}
106
107pub struct WlanTx {
108    ffi: FfiWlanTx,
109}
110
111impl WlanTx {
112    pub fn new(ffi: FfiWlanTx) -> Self {
113        Self { ffi }
114    }
115
116    pub fn transfer(
117        &mut self,
118        request: &fidl_softmac::WlanTxTransferRequest,
119    ) -> Result<(), zx::Status> {
120        wtrace::duration!("WlanTx transfer");
121        let payload = fidl::persist(request);
122        match payload {
123            Err(e) => {
124                error!("Failed to persist WlanTx.Transfer request: {}", e);
125                Err(zx::Status::INTERNAL)
126            }
127            Ok(payload) => {
128                // Safety: The `self.ffi.transfer` call is safe because the payload is a persisted
129                // `EthernetRx.Transfer` request.
130                zx::Status::ok(unsafe {
131                    (self.ffi.transfer)(self.ffi.ctx, payload.as_slice().as_ptr(), payload.len())
132                })
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    #[unsafe(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!("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::sys::ZX_OK,
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    #[unsafe(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!("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}