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