Skip to main content

starnix_modules_fastrpc/
fastrpc.rs

1// Copyright 2025 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::canonicalize_ioctl_request;
6use crate::dma_heap::{Alloc, dma_heap_device_register};
7use bitfield::bitfield;
8use bstr::ByteSlice;
9use fidl_fuchsia_hardware_qualcomm_fastrpc as frpc;
10use starnix_core::device::DeviceOps;
11use starnix_core::mm::memory::MemoryObject;
12use starnix_core::mm::{MemoryAccessor, MemoryAccessorExt, ProtectionFlags};
13use starnix_core::task::{CurrentTask, ThreadGroupKey};
14use starnix_core::vfs::{
15    Anon, FdFlags, FdNumber, FileObject, FileObjectState, FileOps, NamespaceNode,
16    call_fidl_and_await_close, default_ioctl,
17};
18use starnix_core::{
19    fileops_impl_dataless, fileops_impl_memory, fileops_impl_noop_sync, fileops_impl_seekless,
20};
21use starnix_logging::{log_debug, log_error, log_warn};
22use starnix_sync::{FastrpcInnerState, FileOpsCore, Locked, OrderedMutex, Unlocked};
23use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
24use starnix_types::user_buffer::UserBuffer;
25use starnix_uapi::device_type::DeviceType;
26use starnix_uapi::errors::{Errno, ErrnoCode};
27use starnix_uapi::open_flags::OpenFlags;
28use starnix_uapi::user_address::{MultiArchUserRef, UserCString, UserRef};
29use starnix_uapi::{errno, error};
30use std::collections::VecDeque;
31use std::sync::atomic::{AtomicI64, Ordering};
32use std::sync::{Arc, OnceLock};
33use zx::HandleBased;
34
35type IoctlInvokeFdPtr = MultiArchUserRef<
36    linux_uapi::fastrpc_ioctl_invoke_fd,
37    linux_uapi::arch32::fastrpc_ioctl_invoke_fd,
38>;
39
40type IoctlInvoke2Ptr =
41    MultiArchUserRef<linux_uapi::fastrpc_ioctl_invoke2, linux_uapi::arch32::fastrpc_ioctl_invoke2>;
42
43type IoctlInitPtr =
44    MultiArchUserRef<linux_uapi::fastrpc_ioctl_init, linux_uapi::arch32::fastrpc_ioctl_init>;
45
46type IoctlInvokePtr =
47    MultiArchUserRef<linux_uapi::fastrpc_ioctl_invoke, linux_uapi::arch32::fastrpc_ioctl_invoke>;
48
49type RemoteBufPtr = MultiArchUserRef<linux_uapi::remote_buf, linux_uapi::arch32::remote_buf>;
50
51const FASTRPC_MAX_DSP_ATTRIBUTES: usize = 256;
52const FASTRPC_MAX_ATTRIBUTES: usize = 260;
53
54// Performance data capability not supported.
55const PERF_CAPABILITY_SUPPORT: u32 = 0;
56
57// Newer error version.
58const KERNEL_ERROR_CODE_V1_SUPPORT: u32 = 0;
59
60// Userspace allocation supported through dma-heap.
61const USERSPACE_ALLOCATION_SUPPORT: u32 = 1;
62
63// No signaling support.
64const DSPSIGNAL_SUPPORT: u32 = 0;
65
66const KERNEL_CAPABILITIES: [u32; FASTRPC_MAX_ATTRIBUTES - FASTRPC_MAX_DSP_ATTRIBUTES] = [
67    PERF_CAPABILITY_SUPPORT,
68    KERNEL_ERROR_CODE_V1_SUPPORT,
69    USERSPACE_ALLOCATION_SUPPORT,
70    DSPSIGNAL_SUPPORT,
71];
72
73const ASYNC_FASTRPC_CAP: usize = 9;
74const DMA_HANDLE_REVERSE_RPC_CAP: usize = 129;
75
76const INVOKE2_MAX: u32 = 4;
77
78const FASTRPC_INIT_ATTACH: u32 = 0;
79const FASTRPC_INIT_CREATE_STATIC: u32 = 2;
80
81const INIT_FILELEN_MAX: u32 = 2 * 1024 * 1024;
82const INIT_MEMLEN_MAX: u32 = 8 * 1024 * 1024;
83
84// Scalars:
85// These are how we designate the number of various elements inside an rpc method.
86// It comes in a u32 with the bit format:
87//
88// aaam mmmm    iiii iiii    oooo oooo    xxxx yyyy
89//
90// a = attribute (3 bits)
91// m = method (5 bits)
92// i = inbuf (8 bits)
93// o = outbuf (8 bits)
94// x = in handle (4 bits)
95// y = out handle (4 bits)
96//
97// Currently we only support buffers and not handles in this implementation.
98bitfield! {
99    pub struct Scalar(u32);
100    impl Debug;
101
102    pub method_id, _: 28, 24;
103    pub inbuffs, _: 23, 16;
104    pub outbuffs, _: 15, 8;
105    pub inhandles, _: 7, 4;
106    pub outhandles, _: 3, 0;
107}
108
109impl Scalar {
110    fn len(&self) -> u32 {
111        self.inbuffs() as u32
112            + self.outbuffs() as u32
113            + self.inhandles() as u32
114            + self.outhandles() as u32
115    }
116}
117
118// All fidl transport errors should be considered as error, and converted to IO error.
119fn fidl_error_to_errno(info: &str, error: fidl::Error) -> starnix_uapi::errors::Errno {
120    if !error.is_closed() {
121        log_error!("{}: {:?}", info, error);
122        return errno!(EIO);
123    }
124
125    // Log at most once every 5 seconds for PEER_CLOSED errors which can spam if the driver
126    // has crashed.
127    static LAST_LOG_TIME: AtomicI64 = AtomicI64::new(0);
128    let now = zx::MonotonicInstant::get().into_nanos();
129    let last = LAST_LOG_TIME.load(Ordering::Relaxed);
130    if now - last > 5_000_000_000 {
131        LAST_LOG_TIME.store(now, Ordering::Relaxed);
132        log_error!("{}: {:?}", info, error);
133    }
134    errno!(EIO)
135}
136
137// zx.Status errors from fidl domain errors can be converted into fdio-like errnos.
138fn zx_i32_to_errno(info: &str, error: i32) -> starnix_uapi::errors::Errno {
139    starnix_uapi::from_status_like_fdio!(zx::Status::from_raw(error), info)
140}
141
142// zx.Status errors from syscalls can be converted into fdio-like errnos.
143fn zx_status_to_errno(info: &str, error: zx::Status) -> starnix_uapi::errors::Errno {
144    starnix_uapi::from_status_like_fdio!(error, info)
145}
146
147// Directly passthrough retval errors from the driver to the user.
148fn retval_i32_to_errno(info: &str, error: i32) -> starnix_uapi::errors::Errno {
149    let code = ErrnoCode::from_return_value(error as u64);
150    log_debug!("{}: {:?}", info, code);
151    Errno::with_context(code, info)
152}
153
154fn fastrpc_align(size: u64) -> Result<u64, Errno> {
155    // 128 is the memory alignment within the fastrpc framework.
156    size.checked_next_multiple_of(128).ok_or_else(|| errno!(EOVERFLOW))
157}
158
159struct DmaBufFile {
160    memory: Arc<MemoryObject>,
161}
162
163impl DmaBufFile {
164    fn new(memory: Arc<MemoryObject>) -> Box<Self> {
165        Box::new(Self { memory })
166    }
167}
168
169impl FileOps for DmaBufFile {
170    fileops_impl_memory!(self, &self.memory);
171    fileops_impl_noop_sync!();
172
173    fn ioctl(
174        &self,
175        locked: &mut Locked<Unlocked>,
176        file: &FileObject,
177        current_task: &CurrentTask,
178        request: u32,
179        arg: SyscallArg,
180    ) -> Result<SyscallResult, Errno> {
181        match canonicalize_ioctl_request(current_task, request) {
182            linux_uapi::DMA_BUF_SET_NAME_B => {
183                let name = current_task.read_c_string_to_vec(
184                    UserCString::new(current_task, arg),
185                    linux_uapi::DMA_BUF_NAME_LEN as usize,
186                )?;
187                log_debug!(
188                    "dma buf file with koid {:?} got ioctl set name: {}",
189                    self.memory.get_koid(),
190                    name
191                );
192                self.memory.set_zx_name(&name);
193                Ok(SUCCESS)
194            }
195            _ => default_ioctl(file, locked, current_task, request, arg),
196        }
197    }
198}
199
200struct SystemHeap {
201    device: Arc<frpc::SecureFastRpcSynchronousProxy>,
202}
203
204impl Alloc for SystemHeap {
205    fn alloc(
206        &self,
207        locked: &mut Locked<Unlocked>,
208        current_task: &CurrentTask,
209        size: u64,
210        fd_flags: FdFlags,
211    ) -> Result<FdNumber, Errno> {
212        let vmo = self
213            .device
214            .allocate(size, zx::MonotonicInstant::INFINITE)
215            .map_err(|e| fidl_error_to_errno("allocate call", e))?
216            .map_err(|e| zx_i32_to_errno("allocate", e))?;
217
218        log_debug!("allocated vmo with koid {:?}", vmo.koid());
219
220        let memory = Arc::new(MemoryObject::from(vmo));
221
222        let file = Anon::new_private_file(
223            locked,
224            current_task,
225            DmaBufFile::new(memory),
226            OpenFlags::RDWR,
227            "[fastrpc:buffer]",
228        );
229
230        current_task.add_file(locked, file, fd_flags)
231    }
232}
233
234#[derive(Default)]
235struct FastRPCFileState {
236    session: Option<Arc<frpc::RemoteDomainSynchronousProxy>>,
237    payload_vmos: VecDeque<frpc::SharedPayloadBuffer>,
238    cid: Option<i32>,
239    pid: Option<ThreadGroupKey>,
240}
241
242struct ParsedInvoke {
243    invoke: linux_uapi::fastrpc_ioctl_invoke,
244    scalar: Scalar,
245    fd_vmos: Option<Vec<Option<zx::Vmo>>>,
246}
247
248#[derive(PartialEq)]
249struct BufferWithMergeInfo {
250    start: u64,
251    end: u64,
252    buffer_index: usize,
253    merge_contribution: u64,
254    merge_offset: u64,
255}
256
257impl std::fmt::Debug for BufferWithMergeInfo {
258    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259        f.debug_struct("BufferWithMergeInfo")
260            .field("start", &format_args!("{:#x}", self.start))
261            .field("end", &format_args!("{:#x}", self.end))
262            .field("buffer_index", &self.buffer_index)
263            .field("merge_contribution", &format_args!("{:#x}", self.merge_contribution))
264            .field("merge_offset", &self.merge_offset)
265            .finish()
266    }
267}
268
269struct OutputArgumentInfo {
270    mapped: bool,
271    offset: u64,
272    length: u64,
273}
274
275struct PayloadInformation {
276    payload_buffer: Option<frpc::SharedPayloadBuffer>,
277    input_args: Vec<frpc::ArgumentEntry>,
278    output_args: Vec<frpc::ArgumentEntry>,
279    output_info: Vec<OutputArgumentInfo>,
280}
281
282struct FastRPCFile {
283    pid_open: ThreadGroupKey,
284    device: Arc<frpc::SecureFastRpcSynchronousProxy>,
285    cached_capabilities: Arc<OnceLock<[u32; FASTRPC_MAX_DSP_ATTRIBUTES]>>,
286    inner_state: OrderedMutex<FastRPCFileState, FastrpcInnerState>,
287}
288
289impl FastRPCFile {
290    fn new(
291        pid_open: ThreadGroupKey,
292        device: Arc<frpc::SecureFastRpcSynchronousProxy>,
293        cached_capabilities: Arc<OnceLock<[u32; FASTRPC_MAX_DSP_ATTRIBUTES]>>,
294    ) -> Self {
295        Self {
296            pid_open,
297            device,
298            cached_capabilities,
299            inner_state: OrderedMutex::new(FastRPCFileState::default()),
300        }
301    }
302
303    fn invoke(
304        &self,
305        current_task: &CurrentTask,
306        locked: &mut Locked<Unlocked>,
307        request: u32,
308        arg: SyscallArg,
309    ) -> Result<SyscallResult, Errno> {
310        let parsed_invoke = Self::parse_invoke_request(current_task, request, arg)?;
311        let ParsedInvoke { invoke: info, scalar, mut fd_vmos } = parsed_invoke;
312
313        log_debug!(
314            "FastRPC ioctl invoke, scalar {} ({}, {}, {}), handle {}",
315            info.sc,
316            scalar.method_id(),
317            scalar.inbuffs(),
318            scalar.outbuffs(),
319            info.handle
320        );
321
322        let length = scalar.len();
323        let inbufs = scalar.inbuffs() as u32;
324
325        if scalar.inhandles() != 0 || scalar.outhandles() != 0 {
326            log_error!("handles in scalar not supported.");
327            return error!(ENOSYS);
328        }
329
330        let remote_bufs = current_task.read_multi_arch_objects_to_vec(
331            RemoteBufPtr::new(current_task, info.pra),
332            length as usize,
333        )?;
334        let merged_buffers = Self::merge_buffers(&fd_vmos, &remote_bufs)?;
335        let payload = Self::get_payload_info(
336            current_task,
337            locked,
338            &self.inner_state,
339            &merged_buffers,
340            &remote_bufs,
341            &mut fd_vmos,
342            inbufs,
343        )?;
344
345        let payload_buffer_id = match &payload.payload_buffer {
346            Some(buffer) => buffer.id,
347            None => 0,
348        };
349
350        let session = self.get_session(locked)?;
351        let invoke_res = session.invoke(
352            current_task.get_tid(),
353            info.handle,
354            scalar.method_id() as u32,
355            payload_buffer_id,
356            payload.input_args,
357            payload.output_args,
358            zx::MonotonicInstant::INFINITE,
359        );
360
361        let buffer_after_invoke = |success: bool| -> Result<(), Errno> {
362            if success {
363                if let Some(buffer) = &payload.payload_buffer {
364                    self.process_out_bufs(
365                        current_task,
366                        &remote_bufs,
367                        &buffer.vmo,
368                        &payload.output_info,
369                        inbufs,
370                    )?;
371                }
372            }
373
374            if let Some(buffer) = payload.payload_buffer {
375                log_debug!("returning payload buffer {}", buffer.id);
376                self.inner_state.lock(locked).payload_vmos.push_back(buffer);
377            };
378
379            Ok(())
380        };
381
382        match invoke_res {
383            Ok(Ok(())) => {
384                buffer_after_invoke(true)?;
385                Ok(SUCCESS)
386            }
387            Ok(Err(e)) => {
388                buffer_after_invoke(false)?;
389                Err(retval_i32_to_errno("invoke", e))
390            }
391            Err(e) => {
392                buffer_after_invoke(false)?;
393                Err(fidl_error_to_errno("invoke call", e))
394            }
395        }
396    }
397
398    fn get_session(
399        &self,
400        locked: &mut Locked<Unlocked>,
401    ) -> Result<Arc<frpc::RemoteDomainSynchronousProxy>, Errno> {
402        let inner = self.inner_state.lock(locked);
403        Ok(inner.session.as_ref().ok_or_else(|| errno!(ENOENT))?.clone())
404    }
405
406    fn get_capabilities_from_device(
407        &self,
408        _domain: u32,
409    ) -> Result<[u32; FASTRPC_MAX_DSP_ATTRIBUTES], Errno> {
410        let capabilities = self
411            .device
412            .get_capabilities(zx::MonotonicInstant::INFINITE)
413            .map_err(|e| fidl_error_to_errno("get_capabilities call", e))?
414            .map_err(|e| retval_i32_to_errno("get_capabilities", e))?;
415
416        let mut res: [u32; FASTRPC_MAX_DSP_ATTRIBUTES] = [0; FASTRPC_MAX_DSP_ATTRIBUTES];
417        let attribute_buffer_length = FASTRPC_MAX_DSP_ATTRIBUTES - 1;
418        // 0th capability is not filled by the driver.
419        res[0] = 0;
420        res[1..(attribute_buffer_length + 1)]
421            .copy_from_slice(&capabilities[..attribute_buffer_length]);
422
423        log_debug!("ASYNC_FASTRPC_CAP: {}", res[ASYNC_FASTRPC_CAP]);
424        log_debug!("DMA_HANDLE_REVERSE_RPC_CAP: {}", res[DMA_HANDLE_REVERSE_RPC_CAP]);
425        Ok(res)
426    }
427
428    fn get_capabilities(&self, domain: u32, attr: usize) -> Result<u32, Errno> {
429        if attr >= FASTRPC_MAX_ATTRIBUTES {
430            return error!(EOVERFLOW);
431        }
432
433        if attr >= FASTRPC_MAX_DSP_ATTRIBUTES {
434            return Ok(KERNEL_CAPABILITIES[(attr) - FASTRPC_MAX_DSP_ATTRIBUTES]);
435        }
436
437        // OnceLock's get_or_try_init is a nightly feature so we end up with this which might call
438        // get_capabilities_from_device unnecessarily.
439        let caps = self.cached_capabilities.get();
440        match caps {
441            Some(caps) => Ok(caps[attr]),
442            None => {
443                let from_device = self.get_capabilities_from_device(domain)?;
444                let caps = self.cached_capabilities.get_or_init(|| from_device);
445                Ok(caps[attr])
446            }
447        }
448    }
449
450    fn parse_invoke_request(
451        current_task: &CurrentTask,
452        request: u32,
453        arg: SyscallArg,
454    ) -> Result<ParsedInvoke, Errno> {
455        match canonicalize_ioctl_request(current_task, request) {
456            linux_uapi::FASTRPC_IOCTL_INVOKE_FD => {
457                let info = current_task
458                    .read_multi_arch_object(IoctlInvokeFdPtr::new(current_task, arg))?;
459                log_debug!("FastRPC ioctl invoke_fd {:?}", info);
460
461                let scalar = Scalar(info.inv.sc);
462
463                let fds = current_task
464                    .read_objects_to_vec::<i32>(info.fds.into(), scalar.len() as usize)?;
465
466                // Collect the vmos for our fds, as well as a mapping to use locally to check
467                // if an entry is mapped or not.
468                let mut fd_vmos = vec![];
469                for fd in fds {
470                    // A non-postive fd signifies a non-mapped entry.
471                    if fd > 0 {
472                        let file = current_task.get_file(FdNumber::from_raw(fd))?;
473                        let dma_buf =
474                            file.downcast_file::<DmaBufFile>().ok_or_else(|| errno!(EBADF))?;
475
476                        let fd_vmo = dma_buf
477                            .memory
478                            .as_vmo()
479                            .ok_or_else(|| errno!(EBADF))?
480                            .duplicate_handle(fidl::Rights::SAME_RIGHTS)
481                            .map_err(|e| {
482                                zx_status_to_errno("parse_invoke_request duplicate_handle", e)
483                            })?;
484
485                        fd_vmos.push(Some(fd_vmo));
486                    } else {
487                        fd_vmos.push(None);
488                    }
489                }
490
491                Ok(ParsedInvoke { invoke: info.inv, scalar, fd_vmos: Some(fd_vmos) })
492            }
493            linux_uapi::FASTRPC_IOCTL_INVOKE => {
494                let info =
495                    current_task.read_multi_arch_object(IoctlInvokePtr::new(current_task, arg))?;
496                let scalar = Scalar(info.sc);
497                Ok(ParsedInvoke { invoke: info, scalar, fd_vmos: None })
498            }
499            _ => {
500                error!(ENOSYS)
501            }
502        }
503    }
504
505    fn merge_buffers(
506        fd_vmos: &Option<Vec<Option<zx::Vmo>>>,
507        remote_bufs: &[linux_uapi::remote_buf],
508    ) -> Result<Vec<BufferWithMergeInfo>, Errno> {
509        // Get the indices for the buffers since we will be shuffling them around.
510        let mut indexed_buffers = remote_bufs
511            .iter()
512            .enumerate()
513            .map(|(index, buf_ref)| (index, buf_ref))
514            .collect::<Vec<_>>();
515
516        // Sort them by start address, if equal start address we sort by reverse of end address.
517        indexed_buffers.sort_by(|(_, b1), (_, b2)| {
518            let start_comparison = b1.pv.cmp(&b2.pv);
519            let end_reverse_comparison = (b2.pv.addr + b2.len).cmp(&(b1.pv.addr + b1.len));
520            match start_comparison {
521                std::cmp::Ordering::Equal => end_reverse_comparison,
522                std::cmp::Ordering::Greater | std::cmp::Ordering::Less => start_comparison,
523            }
524        });
525
526        let mut results = Vec::new();
527
528        // This is used to track the current merge region's endpoint. We don't need to track
529        // a start as we have already sorted them using the start address.
530        let mut current_merge_end: u64 = 0;
531
532        for (original_idx, buffer) in indexed_buffers.into_iter() {
533            let start = buffer.pv.addr;
534            let end = buffer.pv.addr.checked_add(buffer.len).ok_or_else(|| errno!(EOVERFLOW))?;
535
536            // The merge_contribution signifies the unique memory that needs to be used to represent
537            // this buffer in memory.
538            let merge_contribution;
539
540            // The merge offset is used to get the actual start of a buffer given a merge point,
541            // this is a negative offset on the current_merge_end.
542            let merge_offset;
543
544            if Self::is_buffer_mapped(fd_vmos, original_idx) {
545                // Ignore buffers that are mapped in our overlap calculations.
546                merge_contribution = 0;
547                merge_offset = 0;
548            } else if start < current_merge_end && end <= current_merge_end {
549                // Buffer lives entirely in the current merged region.
550                merge_contribution = 0;
551                merge_offset = current_merge_end - start;
552            } else if start < current_merge_end {
553                // Buffer lives partially in the current merged region.
554                merge_contribution = end - current_merge_end;
555                merge_offset = current_merge_end - start;
556
557                // Extend the merge region.
558                current_merge_end = end;
559            } else {
560                // Buffer does not live anywhere in the current merged region.
561                merge_contribution = end - start;
562                merge_offset = 0;
563
564                // Start a new merged region.
565                current_merge_end = end;
566            }
567
568            results.push(BufferWithMergeInfo {
569                start,
570                end,
571                buffer_index: original_idx,
572                merge_contribution,
573                merge_offset,
574            });
575        }
576
577        Ok(results)
578    }
579
580    fn get_payload_size(
581        fd_vmos: &Option<Vec<Option<zx::Vmo>>>,
582        merged_buffers: &Vec<BufferWithMergeInfo>,
583    ) -> Result<u64, Errno> {
584        let mut size: u64 = 0;
585        for i in 0..merged_buffers.len() {
586            let buffer_index = merged_buffers[i].buffer_index;
587
588            // Include in payload if not mapped.
589            if !Self::is_buffer_mapped(fd_vmos, buffer_index) {
590                if merged_buffers[i].merge_offset == 0 {
591                    // Align each new merged region.
592                    size = fastrpc_align(size)?;
593                }
594
595                size = size
596                    .checked_add(merged_buffers[i].merge_contribution)
597                    .ok_or_else(|| errno!(EOVERFLOW))?;
598            }
599        }
600
601        Ok(size)
602    }
603
604    fn is_buffer_mapped(fd_vmos: &Option<Vec<Option<zx::Vmo>>>, idx: usize) -> bool {
605        match fd_vmos {
606            None => false,
607            Some(vmos) => vmos[idx].is_some(),
608        }
609    }
610
611    fn get_mapped_memory_and_offset(
612        current_task: &CurrentTask,
613        buf: &linux_uapi::remote_buf,
614        fd_vmos: &mut Option<Vec<Option<zx::Vmo>>>,
615        idx: usize,
616    ) -> Result<(u64, zx::Vmo), Errno> {
617        let (mm_vmo, mm_offset) = current_task
618            .mm()?
619            .get_mapping_memory(buf.pv.into(), ProtectionFlags::READ | ProtectionFlags::WRITE)?;
620
621        if let Some(fd_vmo) =
622            fd_vmos.as_deref_mut().and_then(|v| v.get_mut(idx)).and_then(|o| o.take())
623        {
624            if mm_vmo.get_koid()
625                == fd_vmo
626                    .basic_info()
627                    .map_err(|e| zx_status_to_errno("get_mapped_memory_and_offset basic_info", e))?
628                    .koid
629            {
630                log_debug!(
631                    "FastRPC ioctl invoke found allocated vmo for user address. koid: {:?}. User pointer: {:#x} offset in vmo: {}",
632                    mm_vmo.get_koid(),
633                    buf.pv.addr,
634                    mm_offset
635                );
636                return Ok((mm_offset, fd_vmo));
637            }
638        }
639
640        error!(ENOSYS)
641    }
642
643    fn get_payload_info(
644        current_task: &CurrentTask,
645        locked: &mut Locked<Unlocked>,
646        inner_state: &OrderedMutex<FastRPCFileState, FastrpcInnerState>,
647        merged_buffers: &Vec<BufferWithMergeInfo>,
648        remote_bufs: &Vec<linux_uapi::remote_buf>,
649        fd_vmos: &mut Option<Vec<Option<zx::Vmo>>>,
650        inbufs: u32,
651    ) -> Result<PayloadInformation, Errno> {
652        let payload_size = Self::get_payload_size(fd_vmos, &merged_buffers)?;
653        let payload_buffer = if payload_size == 0 {
654            None
655        } else {
656            let payload_buffer =
657                inner_state.lock(locked).payload_vmos.pop_front().ok_or_else(|| errno!(ENOBUFS))?;
658            log_debug!("selected payload buffer {}", payload_buffer.id);
659            Some(payload_buffer)
660        };
661
662        // Construct these with the usize buffer_index so we can sort them after.
663        //
664        // Output is specified twice, once for the fidl invocation, the other
665        // to be used after the invocation is done since we want to copy data back
666        // to the user.
667        let mut input_args: Vec<(usize, frpc::ArgumentEntry)> = vec![];
668        let mut output_args: Vec<(usize, frpc::ArgumentEntry)> = vec![];
669        let mut output_info: Vec<(usize, OutputArgumentInfo)> = vec![];
670        let mut curr_merge_point = 0;
671
672        for merged_buffer in merged_buffers {
673            let buf =
674                remote_bufs.get(merged_buffer.buffer_index).expect("to have index in remote bufs");
675            let is_mapped = Self::is_buffer_mapped(fd_vmos, merged_buffer.buffer_index);
676
677            let (entry, offset) = if is_mapped {
678                let (offset, vmo) = Self::get_mapped_memory_and_offset(
679                    current_task,
680                    buf,
681                    fd_vmos,
682                    merged_buffer.buffer_index,
683                )?;
684                (
685                    frpc::ArgumentEntry::VmoArgument(frpc::VmoArgument {
686                        vmo,
687                        offset,
688                        length: buf.len,
689                    }),
690                    offset,
691                )
692            } else {
693                if merged_buffer.merge_offset == 0 {
694                    curr_merge_point = fastrpc_align(curr_merge_point)?;
695                }
696
697                let offset = curr_merge_point - merged_buffer.merge_offset;
698                curr_merge_point = curr_merge_point
699                    .checked_add(merged_buffer.merge_contribution)
700                    .ok_or_else(|| errno!(EOVERFLOW))?;
701                (frpc::ArgumentEntry::Argument(frpc::Argument { offset, length: buf.len }), offset)
702            };
703
704            if merged_buffer.buffer_index < inbufs as usize {
705                // Write data and flush for non-empty, non-mapped input buffers.
706                if !is_mapped && buf.len > 0 {
707                    let buf_data = current_task.read_buffer(&UserBuffer {
708                        address: buf.pv.into(),
709                        length: buf.len as usize,
710                    })?;
711
712                    let vmo = &payload_buffer.as_ref().expect("payload buffer").vmo;
713                    vmo.write(buf_data.as_slice(), offset)
714                        .map_err(|e| zx_status_to_errno("get_payload_info write", e))?;
715                }
716
717                input_args.push((merged_buffer.buffer_index, entry));
718            } else {
719                output_args.push((merged_buffer.buffer_index, entry));
720                output_info.push((
721                    merged_buffer.buffer_index,
722                    OutputArgumentInfo { mapped: is_mapped, offset, length: buf.len },
723                ));
724            }
725        }
726
727        input_args.sort_by_key(|e| e.0);
728        output_args.sort_by_key(|e| e.0);
729        output_info.sort_by_key(|e| e.0);
730
731        let input_args = input_args.into_iter().map(|e| e.1).collect();
732        let output_args = output_args.into_iter().map(|e| e.1).collect();
733        let output_info = output_info.into_iter().map(|e| e.1).collect();
734
735        Ok(PayloadInformation { payload_buffer, input_args, output_args, output_info })
736    }
737
738    fn process_out_bufs(
739        &self,
740        current_task: &CurrentTask,
741        remote_bufs: &Vec<linux_uapi::remote_buf>,
742        payload_vmo: &zx::Vmo,
743        output_infos: &Vec<OutputArgumentInfo>,
744        inbufs: u32,
745    ) -> Result<(), Errno> {
746        let max_len = output_infos.iter().filter(|i| !i.mapped).map(|i| i.length).max();
747        let Some(max_len) = max_len else {
748            return Ok(());
749        };
750
751        let mut read_vec = vec![0; max_len as usize];
752
753        for (output_index, output_info) in output_infos.iter().enumerate() {
754            if output_info.mapped {
755                continue;
756            }
757            if output_info.length == 0 {
758                continue;
759            }
760
761            let buf = &remote_bufs[output_index + inbufs as usize];
762
763            assert_eq!(buf.len, output_info.length);
764
765            payload_vmo
766                .read(&mut read_vec[0..output_info.length as usize], output_info.offset)
767                .map_err(|e| zx_status_to_errno("process_out_bufs read", e))?;
768
769            let _ = current_task
770                .write_memory(buf.pv.into(), &read_vec[0..output_info.length as usize])?;
771        }
772        Ok(())
773    }
774}
775
776impl FileOps for FastRPCFile {
777    fileops_impl_noop_sync!();
778    fileops_impl_seekless!();
779    fileops_impl_dataless!();
780
781    fn close(
782        self: Box<Self>,
783        locked: &mut Locked<FileOpsCore>,
784        _file: &FileObjectState,
785        _current_task: &CurrentTask,
786    ) {
787        let inner = self.inner_state.lock(locked);
788        if let Some(ref session) = inner.session {
789            call_fidl_and_await_close(frpc::RemoteDomainSynchronousProxy::close, session.as_ref());
790        }
791    }
792
793    fn ioctl(
794        &self,
795        locked: &mut Locked<Unlocked>,
796        file: &FileObject,
797        current_task: &CurrentTask,
798        request: u32,
799        arg: SyscallArg,
800    ) -> Result<SyscallResult, Errno> {
801        let pid = current_task.thread_group_key.clone();
802        if pid != self.pid_open {
803            return error!(EPERM);
804        }
805
806        match canonicalize_ioctl_request(current_task, request) {
807            linux_uapi::FASTRPC_IOCTL_INVOKE | linux_uapi::FASTRPC_IOCTL_INVOKE_FD => {
808                self.invoke(current_task, locked, request, arg)
809            }
810            linux_uapi::FASTRPC_IOCTL_GETINFO => {
811                let user_info = UserRef::<u32>::from(arg);
812                let channel_id = current_task.read_object(user_info)?;
813                let device_channel_id = self
814                    .device
815                    .get_channel_id(zx::MonotonicInstant::INFINITE)
816                    .map_err(|e| fidl_error_to_errno("get_channel_id call", e))?
817                    .map_err(|e| zx_i32_to_errno("get_channel_id", e))?;
818
819                if device_channel_id != channel_id {
820                    return error!(EPERM);
821                }
822
823                let mut inner = self.inner_state.lock(locked);
824                if inner.session.is_some() {
825                    return error!(EEXIST);
826                }
827
828                inner.pid = Some(pid);
829                inner.cid = Some(channel_id as i32);
830
831                log_debug!("FastRPC ioctl getinfo for channel_id {}", channel_id);
832
833                // The reply value indicates to the user whether the smmu
834                // is enabled for this session. On Fuchsia currently we enable the smmu in a
835                // passthrough mode and hardcode a stream id. Eventually when we fully enable the
836                // smmu we will need to allocate and use specific context banks for sessions so
837                // this value will need to come from the driver.
838                current_task.write_object(user_info, &(1u32))?;
839                Ok(SUCCESS)
840            }
841            linux_uapi::FASTRPC_IOCTL_GET_DSP_INFO => {
842                // UserRef note:
843                // fastrpc_ioctl_capability is checked for check_arch_independent_layout.
844                let user_ref = UserRef::<linux_uapi::fastrpc_ioctl_capability>::new(arg.into());
845                let mut info = current_task.read_object(user_ref)?;
846                log_debug!(
847                    "FastRPC ioctl get dsp info domain {} attribute {}",
848                    info.domain,
849                    info.attribute_ID
850                );
851                info.capability = self.get_capabilities(info.domain, info.attribute_ID as usize)?;
852                current_task.write_object(user_ref, &info)?;
853                Ok(SUCCESS)
854            }
855            linux_uapi::FASTRPC_IOCTL_INVOKE2 => {
856                let info =
857                    current_task.read_multi_arch_object(IoctlInvoke2Ptr::new(current_task, arg))?;
858                if info.req > INVOKE2_MAX {
859                    log_debug!("FastRPC ioctl invoke2 out of bounds req number {}", info.req);
860                    return error!(ENOTTY);
861                }
862
863                log_debug!("FastRPC ioctl invoke2 {:?}", info);
864                error!(ENOSYS)
865            }
866            linux_uapi::FASTRPC_IOCTL_INIT => {
867                let info =
868                    current_task.read_multi_arch_object(IoctlInitPtr::new(current_task, arg))?;
869
870                if info.filelen >= INIT_FILELEN_MAX || info.memlen >= INIT_MEMLEN_MAX {
871                    return error!(EFBIG);
872                }
873
874                let mut inner = self.inner_state.lock(locked);
875                if inner.session.is_some() {
876                    return error!(EEXIST);
877                }
878
879                match info.flags {
880                    FASTRPC_INIT_ATTACH => {
881                        log_debug!("FastRPC ioctl init FASTRPC_INIT_ATTACH {:?}", info);
882
883                        let (client, server) =
884                            fidl::endpoints::create_sync_proxy::<frpc::RemoteDomainMarker>();
885
886                        self.device
887                            .attach_root_domain(server, zx::MonotonicInstant::INFINITE)
888                            .map_err(|e| fidl_error_to_errno("attach_root_domain call", e))?
889                            .map_err(|e| retval_i32_to_errno("attach_root_domain", e))?;
890
891                        inner.payload_vmos = client
892                            .get_payload_buffer_set(3, zx::MonotonicInstant::INFINITE)
893                            .map_err(|e| fidl_error_to_errno("get_payload_buffer_set call", e))?
894                            .map_err(|e| zx_i32_to_errno("get_payload_buffer_set", e))?
895                            .into();
896
897                        inner.session = Some(Arc::new(client));
898                        Ok(SUCCESS)
899                    }
900                    FASTRPC_INIT_CREATE_STATIC => {
901                        log_debug!("FastRPC ioctl init FASTRPC_INIT_CREATE_STATIC {:?}", info);
902                        let file_name = current_task.read_c_string_to_vec(
903                            UserCString::new(current_task, info.file),
904                            info.filelen as usize,
905                        )?;
906
907                        let (client, server) =
908                            fidl::endpoints::create_sync_proxy::<frpc::RemoteDomainMarker>();
909
910                        self.device
911                            .create_static_domain(
912                                file_name.to_str().map_err(|_| errno!(EINVAL))?,
913                                info.memlen,
914                                server,
915                                zx::MonotonicInstant::INFINITE,
916                            )
917                            .map_err(|e| fidl_error_to_errno("create_static_domain call", e))?
918                            .map_err(|e| retval_i32_to_errno("create_static_domain", e))?;
919
920                        inner.payload_vmos = client
921                            .get_payload_buffer_set(3, zx::MonotonicInstant::INFINITE)
922                            .map_err(|e| fidl_error_to_errno("get_payload_buffer_set call", e))?
923                            .map_err(|e| zx_i32_to_errno("get_payload_buffer_set", e))?
924                            .into();
925
926                        inner.session = Some(Arc::new(client));
927                        Ok(SUCCESS)
928                    }
929                    _ => {
930                        log_warn!("FastRPC ioctl init with unsupported flag {:?}", info);
931                        error!(ENOSYS)
932                    }
933                }
934            }
935            _ => default_ioctl(file, locked, current_task, request, arg),
936        }
937    }
938}
939
940#[derive(Clone)]
941struct FastRPCDevice {
942    device: Arc<frpc::SecureFastRpcSynchronousProxy>,
943    cached_capabilities: Arc<OnceLock<[u32; FASTRPC_MAX_DSP_ATTRIBUTES]>>,
944}
945
946impl FastRPCDevice {
947    fn new(device: Arc<frpc::SecureFastRpcSynchronousProxy>) -> Self {
948        Self { device, cached_capabilities: Arc::new(OnceLock::new()) }
949    }
950}
951
952impl DeviceOps for FastRPCDevice {
953    fn open(
954        &self,
955        _locked: &mut Locked<FileOpsCore>,
956        current_task: &CurrentTask,
957        _id: DeviceType,
958        _node: &NamespaceNode,
959        _flags: OpenFlags,
960    ) -> Result<Box<dyn FileOps>, Errno> {
961        Ok(Box::new(FastRPCFile::new(
962            current_task.thread_group_key.clone(),
963            self.device.clone(),
964            self.cached_capabilities.clone(),
965        )))
966    }
967}
968
969pub fn fastrpc_device_init(locked: &mut Locked<Unlocked>, system_task: &CurrentTask) {
970    let device = fuchsia_component::client::connect_to_protocol_sync::<frpc::SecureFastRpcMarker>()
971        .expect("Failed to connect to fuchsia.hardware.qualcomm.fastrpc.SecureFastRpc");
972
973    let device = Arc::new(device);
974
975    // This is called the "system" dma heap, but as of now the fastrpc client is its only client.
976    // Because fastrpc needs to be aware of the fds from this, we are putting the implementation
977    // in this module.
978    dma_heap_device_register(locked, system_task, "system", SystemHeap { device: device.clone() });
979
980    let device = FastRPCDevice::new(device);
981    let registry = &system_task.kernel().device_registry;
982    registry
983        .register_dyn_device(
984            locked,
985            system_task,
986            "adsprpc-smd-secure".into(),
987            registry.objects.get_or_create_class("fastrpc".into(), registry.objects.virtual_bus()),
988            device,
989        )
990        .expect("Can register heap device");
991}
992
993#[cfg(test)]
994pub mod tests {
995    use crate::fastrpc::{BufferWithMergeInfo, FastRPCFile, FastRPCFileState};
996    use fidl_fuchsia_hardware_qualcomm_fastrpc::{
997        Argument, ArgumentEntry, SharedPayloadBuffer, VmoArgument,
998    };
999    use linux_uapi::{remote_buf, uaddr};
1000    use starnix_core::mm::ProtectionFlags;
1001    use starnix_core::testing::{UserMemoryWriter, map_memory, spawn_kernel_and_run};
1002    use starnix_sync::OrderedMutex;
1003    use starnix_types::PAGE_SIZE;
1004    use starnix_uapi::user_address::UserAddress;
1005    use zx::HandleBased;
1006
1007    #[fuchsia::test]
1008    fn merge_buffers_test_empty_input() {
1009        let remote_bufs: Vec<remote_buf> = vec![];
1010        let fd_vmos = None;
1011        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1012        assert!(results.is_empty());
1013    }
1014
1015    #[fuchsia::test]
1016    fn merge_buffers_test_single_buffer() {
1017        let remote_bufs = vec![remote_buf { pv: uaddr { addr: 100 }, len: 50 }];
1018        let fd_vmos = None;
1019        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1020        assert_eq!(
1021            results,
1022            vec![BufferWithMergeInfo {
1023                start: 100,
1024                end: 150,
1025                buffer_index: 0,
1026                merge_contribution: 50,
1027                merge_offset: 0,
1028            }]
1029        );
1030    }
1031
1032    #[fuchsia::test]
1033    fn merge_buffers_test_disjoint_buffers_sorted_input() {
1034        let remote_bufs = vec![
1035            remote_buf { pv: uaddr { addr: 100 }, len: 50 },
1036            remote_buf { pv: uaddr { addr: 200 }, len: 50 },
1037        ];
1038        let fd_vmos = None;
1039        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1040        assert_eq!(
1041            results,
1042            vec![
1043                BufferWithMergeInfo {
1044                    start: 100,
1045                    end: 150,
1046                    buffer_index: 0,
1047                    merge_contribution: 50,
1048                    merge_offset: 0
1049                },
1050                BufferWithMergeInfo {
1051                    start: 200,
1052                    end: 250,
1053                    buffer_index: 1,
1054                    merge_contribution: 50,
1055                    merge_offset: 0
1056                },
1057            ]
1058        );
1059    }
1060
1061    #[fuchsia::test]
1062    fn merge_buffers_test_disjoint_buffers_unsorted_input() {
1063        let remote_bufs = vec![
1064            remote_buf { pv: uaddr { addr: 200 }, len: 50 }, // index 0
1065            remote_buf { pv: uaddr { addr: 100 }, len: 50 }, // index 1
1066        ];
1067        let fd_vmos = None;
1068        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1069        assert_eq!(
1070            results,
1071            vec![
1072                BufferWithMergeInfo {
1073                    start: 100,
1074                    end: 150,
1075                    buffer_index: 1,
1076                    merge_contribution: 50,
1077                    merge_offset: 0
1078                },
1079                BufferWithMergeInfo {
1080                    start: 200,
1081                    end: 250,
1082                    buffer_index: 0,
1083                    merge_contribution: 50,
1084                    merge_offset: 0
1085                },
1086            ]
1087        );
1088    }
1089
1090    #[fuchsia::test]
1091    fn merge_buffers_test_touching_buffers() {
1092        let remote_bufs = vec![
1093            remote_buf { pv: uaddr { addr: 100 }, len: 50 },
1094            remote_buf { pv: uaddr { addr: 150 }, len: 50 },
1095        ];
1096        let fd_vmos = None;
1097        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1098        assert_eq!(
1099            results,
1100            vec![
1101                BufferWithMergeInfo {
1102                    start: 100,
1103                    end: 150,
1104                    buffer_index: 0,
1105                    merge_contribution: 50,
1106                    merge_offset: 0
1107                },
1108                BufferWithMergeInfo {
1109                    start: 150,
1110                    end: 200,
1111                    buffer_index: 1,
1112                    merge_contribution: 50,
1113                    merge_offset: 0
1114                },
1115            ]
1116        );
1117    }
1118
1119    #[fuchsia::test]
1120    fn merge_buffers_test_touching_buffers_one_mapped() {
1121        let remote_bufs = vec![
1122            remote_buf { pv: uaddr { addr: 100 }, len: 50 },
1123            remote_buf { pv: uaddr { addr: 150 }, len: 50 },
1124        ];
1125        let fd_vmos = Some(vec![None, Some(zx::Vmo::create(1).expect("vmo"))]);
1126        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1127        assert_eq!(
1128            results,
1129            vec![
1130                BufferWithMergeInfo {
1131                    start: 100,
1132                    end: 150,
1133                    buffer_index: 0,
1134                    merge_contribution: 50,
1135                    merge_offset: 0
1136                },
1137                BufferWithMergeInfo {
1138                    start: 150,
1139                    end: 200,
1140                    buffer_index: 1,
1141                    merge_contribution: 00,
1142                    merge_offset: 0
1143                },
1144            ]
1145        );
1146    }
1147
1148    #[fuchsia::test]
1149    fn merge_buffers_test_partial_overlap() {
1150        let remote_bufs = vec![
1151            remote_buf { pv: uaddr { addr: 100 }, len: 100 },
1152            remote_buf { pv: uaddr { addr: 150 }, len: 100 },
1153        ];
1154        let fd_vmos = None;
1155        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1156        assert_eq!(
1157            results,
1158            vec![
1159                BufferWithMergeInfo {
1160                    start: 100,
1161                    end: 200,
1162                    buffer_index: 0,
1163                    merge_contribution: 100,
1164                    merge_offset: 0
1165                },
1166                BufferWithMergeInfo {
1167                    start: 150,
1168                    end: 250,
1169                    buffer_index: 1,
1170                    merge_contribution: 50,
1171                    merge_offset: 50
1172                },
1173            ]
1174        );
1175    }
1176
1177    #[fuchsia::test]
1178    fn merge_buffers_test_full_containment() {
1179        let remote_bufs = vec![
1180            remote_buf { pv: uaddr { addr: 100 }, len: 100 },
1181            remote_buf { pv: uaddr { addr: 120 }, len: 50 },
1182        ];
1183        let fd_vmos = None;
1184        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1185        assert_eq!(
1186            results,
1187            vec![
1188                BufferWithMergeInfo {
1189                    start: 100,
1190                    end: 200,
1191                    buffer_index: 0,
1192                    merge_contribution: 100,
1193                    merge_offset: 0
1194                },
1195                BufferWithMergeInfo {
1196                    start: 120,
1197                    end: 170,
1198                    buffer_index: 1,
1199                    merge_contribution: 0,
1200                    merge_offset: 80
1201                },
1202            ]
1203        );
1204    }
1205
1206    #[fuchsia::test]
1207    fn merge_buffers_test_same_start_address() {
1208        let remote_bufs = vec![
1209            remote_buf { pv: uaddr { addr: 100 }, len: 50 },
1210            remote_buf { pv: uaddr { addr: 100 }, len: 100 },
1211        ];
1212        let fd_vmos = None;
1213        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1214        assert_eq!(
1215            results,
1216            vec![
1217                BufferWithMergeInfo {
1218                    start: 100,
1219                    end: 200,
1220                    buffer_index: 1,
1221                    merge_contribution: 100,
1222                    merge_offset: 0
1223                },
1224                BufferWithMergeInfo {
1225                    start: 100,
1226                    end: 150,
1227                    buffer_index: 0,
1228                    merge_contribution: 0,
1229                    merge_offset: 100
1230                },
1231            ]
1232        );
1233    }
1234
1235    #[fuchsia::test]
1236    fn merge_buffers_test_zero_length_buffers() {
1237        let remote_bufs = vec![
1238            remote_buf { pv: uaddr { addr: 100 }, len: 50 },
1239            remote_buf { pv: uaddr { addr: 120 }, len: 0 },
1240            remote_buf { pv: uaddr { addr: 200 }, len: 0 },
1241        ];
1242        let fd_vmos = None;
1243        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1244        assert_eq!(
1245            results,
1246            vec![
1247                BufferWithMergeInfo {
1248                    start: 100,
1249                    end: 150,
1250                    buffer_index: 0,
1251                    merge_contribution: 50,
1252                    merge_offset: 0
1253                },
1254                BufferWithMergeInfo {
1255                    start: 120,
1256                    end: 120,
1257                    buffer_index: 1,
1258                    merge_contribution: 0,
1259                    merge_offset: 30
1260                },
1261                BufferWithMergeInfo {
1262                    start: 200,
1263                    end: 200,
1264                    buffer_index: 2,
1265                    merge_contribution: 0,
1266                    merge_offset: 0
1267                },
1268            ]
1269        );
1270    }
1271
1272    #[fuchsia::test]
1273    fn merge_buffers_test_complex() {
1274        let remote_bufs = vec![
1275            remote_buf { pv: uaddr { addr: 500 }, len: 100 }, // 500-600, index 0
1276            remote_buf { pv: uaddr { addr: 100 }, len: 100 }, // 100-200, index 1
1277            remote_buf { pv: uaddr { addr: 150 }, len: 100 }, // 150-250, index 2
1278            remote_buf { pv: uaddr { addr: 400 }, len: 50 },  // 400-450, index 3
1279            remote_buf { pv: uaddr { addr: 180 }, len: 20 },  // 180-200, index 4 (contained)
1280        ];
1281        let fd_vmos = None;
1282        let results = FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1283        let expected = vec![
1284            // First merge region (100 -> 200 -> 250)
1285            BufferWithMergeInfo {
1286                start: 100,
1287                end: 200,
1288                buffer_index: 1,
1289                merge_contribution: 100,
1290                merge_offset: 0,
1291            },
1292            BufferWithMergeInfo {
1293                start: 150,
1294                end: 250,
1295                buffer_index: 2,
1296                merge_contribution: 50,
1297                merge_offset: 50,
1298            },
1299            BufferWithMergeInfo {
1300                start: 180,
1301                end: 200,
1302                buffer_index: 4,
1303                merge_contribution: 0,
1304                merge_offset: 70,
1305            },
1306            // Second merge region (400 -> 450)
1307            BufferWithMergeInfo {
1308                start: 400,
1309                end: 450,
1310                buffer_index: 3,
1311                merge_contribution: 50,
1312                merge_offset: 0,
1313            },
1314            // Third merge region (500 -> 600)
1315            BufferWithMergeInfo {
1316                start: 500,
1317                end: 600,
1318                buffer_index: 0,
1319                merge_contribution: 100,
1320                merge_offset: 0,
1321            },
1322        ];
1323
1324        assert_eq!(results, expected);
1325    }
1326
1327    #[fuchsia::test]
1328    async fn get_payload_info_test_complex_range_values() {
1329        spawn_kernel_and_run(async |locked, current_task| {
1330            let addr = map_memory(locked, &current_task, UserAddress::from_ptr(100 as usize), 500);
1331
1332            // Use the same buffers as merge_buffers_test_complex but just offset them in the
1333            // memory we got mapped above.
1334            let remote_bufs = vec![
1335                remote_buf { pv: uaddr { addr: (addr + 500u64).expect("add").into() }, len: 100 },
1336                remote_buf { pv: uaddr { addr: (addr + 100u64).expect("add").into() }, len: 100 },
1337                remote_buf { pv: uaddr { addr: (addr + 150u64).expect("add").into() }, len: 100 },
1338                remote_buf { pv: uaddr { addr: (addr + 400u64).expect("add").into() }, len: 50 },
1339                remote_buf { pv: uaddr { addr: (addr + 180u64).expect("add").into() }, len: 20 },
1340            ];
1341
1342            // This variant of the test puts range based values into the user memory.
1343            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[0].pv.into());
1344            let data = (0..remote_bufs[0].len as u8).collect::<Vec<_>>();
1345            writer.write(&data);
1346
1347            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[1].pv.into());
1348            let data = (0..remote_bufs[1].len as u8).collect::<Vec<_>>();
1349            writer.write(&data);
1350
1351            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[2].pv.into());
1352            let data = (0..remote_bufs[2].len as u8).collect::<Vec<_>>();
1353            writer.write(&data);
1354
1355            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[3].pv.into());
1356            let data = (0..remote_bufs[3].len as u8).collect::<Vec<_>>();
1357            writer.write(&data);
1358
1359            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[4].pv.into());
1360            let data = (0..remote_bufs[4].len as u8).collect::<Vec<_>>();
1361            writer.write(&data);
1362
1363            let vmo = zx::Vmo::create(*PAGE_SIZE).expect("vmo create");
1364            let vmo_dup = vmo.duplicate_handle(fidl::Rights::SAME_RIGHTS).expect("dup");
1365
1366            let state = OrderedMutex::new(FastRPCFileState {
1367                session: None,
1368                payload_vmos: vec![SharedPayloadBuffer { id: 1, vmo: vmo }].into(),
1369                cid: None,
1370                pid: None,
1371            });
1372            let mut fd_vmos = None;
1373
1374            let merged_buffers =
1375                FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1376            let payload_info = FastRPCFile::get_payload_info(
1377                &current_task,
1378                locked,
1379                &state,
1380                &merged_buffers,
1381                &remote_bufs,
1382                &mut fd_vmos,
1383                3,
1384            )
1385            .expect("get_payload_info");
1386
1387            assert_eq!(
1388                payload_info.input_args,
1389                vec![
1390                    ArgumentEntry::Argument(Argument { offset: 384, length: 100 }),
1391                    ArgumentEntry::Argument(Argument { offset: 0, length: 100 }),
1392                    ArgumentEntry::Argument(Argument { offset: 50, length: 100 })
1393                ]
1394            );
1395
1396            assert_eq!(
1397                payload_info.output_args,
1398                vec![
1399                    ArgumentEntry::Argument(Argument { offset: 256, length: 50 }),
1400                    ArgumentEntry::Argument(Argument { offset: 80, length: 20 }),
1401                ]
1402            );
1403
1404            // Tests that the input buffers have been correctly setup in the payload.
1405            //
1406            // Since the buffer at 256 is part of the output, it will not be copied into the vmo as
1407            // part of the setup. But because 80-100 is already included as part of the input buffer
1408            // from 0-100 and 50-150 the data appears in here just as a side effect.
1409            let data = vmo_dup.read_to_vec::<u8>(0, 484).expect("read");
1410            let expected_vmo = vec![
1411                0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
1412                23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
1413                44, 45, 46, 47, 48, 49, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
1414                17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
1415                10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
1416                61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
1417                82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 0, 0, 0, 0,
1418                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1419                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1420                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1421                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1422                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1423                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1424                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1425                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1426                0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
1427                19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
1428                40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
1429                61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
1430                82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
1431            ];
1432
1433            assert_eq!(expected_vmo, data);
1434        })
1435        .await;
1436    }
1437
1438    #[fuchsia::test]
1439    async fn get_payload_info_test_complex_single_values() {
1440        spawn_kernel_and_run(async |locked, current_task| {
1441            let addr = map_memory(locked, &current_task, UserAddress::from_ptr(100 as usize), 500);
1442
1443            // Use the same buffers as merge_buffers_test_complex but just offset them in the
1444            // memory we got mapped above.
1445            let remote_bufs = vec![
1446                remote_buf { pv: uaddr { addr: (addr + 500u64).expect("add").into() }, len: 100 },
1447                remote_buf { pv: uaddr { addr: (addr + 100u64).expect("add").into() }, len: 100 },
1448                remote_buf { pv: uaddr { addr: (addr + 150u64).expect("add").into() }, len: 100 },
1449                remote_buf { pv: uaddr { addr: (addr + 400u64).expect("add").into() }, len: 50 },
1450                remote_buf { pv: uaddr { addr: (addr + 180u64).expect("add").into() }, len: 20 },
1451            ];
1452
1453            // This variant of the test puts single values based on the buffer index
1454            // into the user memory.
1455            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[0].pv.into());
1456            let data = vec![10; remote_bufs[0].len as usize];
1457            writer.write(&data);
1458
1459            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[1].pv.into());
1460            let data = vec![11; remote_bufs[1].len as usize];
1461            writer.write(&data);
1462
1463            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[2].pv.into());
1464            let data = vec![12; remote_bufs[2].len as usize];
1465            writer.write(&data);
1466
1467            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[3].pv.into());
1468            let data = vec![13; remote_bufs[3].len as usize];
1469            writer.write(&data);
1470
1471            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[4].pv.into());
1472            let data = vec![14; remote_bufs[4].len as usize];
1473            writer.write(&data);
1474
1475            let vmo = zx::Vmo::create(*PAGE_SIZE).expect("vmo create");
1476            let vmo_dup = vmo.duplicate_handle(fidl::Rights::SAME_RIGHTS).expect("dup");
1477
1478            let state = OrderedMutex::new(FastRPCFileState {
1479                session: None,
1480                payload_vmos: vec![SharedPayloadBuffer { id: 1, vmo: vmo }].into(),
1481                cid: None,
1482                pid: None,
1483            });
1484            let mut fd_vmos = None;
1485
1486            let merged_buffers =
1487                FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1488            let payload_info = FastRPCFile::get_payload_info(
1489                &current_task,
1490                locked,
1491                &state,
1492                &merged_buffers,
1493                &remote_bufs,
1494                &mut fd_vmos,
1495                3,
1496            )
1497            .expect("get_payload_info");
1498
1499            assert_eq!(
1500                payload_info.input_args,
1501                vec![
1502                    ArgumentEntry::Argument(Argument { offset: 384, length: 100 }),
1503                    ArgumentEntry::Argument(Argument { offset: 0, length: 100 }),
1504                    ArgumentEntry::Argument(Argument { offset: 50, length: 100 })
1505                ]
1506            );
1507
1508            assert_eq!(
1509                payload_info.output_args,
1510                vec![
1511                    ArgumentEntry::Argument(Argument { offset: 256, length: 50 }),
1512                    ArgumentEntry::Argument(Argument { offset: 80, length: 20 }),
1513                ]
1514            );
1515
1516            // Tests that the input buffers have been correctly setup in the payload.
1517            //
1518            // Since the buffer at 256 is part of the output, it will not be copied into the vmo as
1519            // part of the setup. But because 80-100 is already included as part of the input buffer
1520            // from 0-100 and 50-150 the data appears in here just as a side effect.
1521            let data = vmo_dup.read_to_vec::<u8>(0, 484).expect("read");
1522            let expected_vmo = vec![
1523                11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
1524                11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
1525                11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1526                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14,
1527                14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 12, 12, 12, 12, 12,
1528                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1529                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1530                12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1531                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1532                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1533                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1534                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1535                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1536                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1537                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1538                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
1539                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
1540                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
1541                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
1542                10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
1543                10, 10, 10, 10, 10, 10,
1544            ];
1545
1546            assert_eq!(expected_vmo, data);
1547        })
1548        .await;
1549    }
1550
1551    #[fuchsia::test]
1552    async fn get_payload_info_test_complex_single_values_with_one_mapped() {
1553        spawn_kernel_and_run(async |locked, current_task| {
1554            let addr = map_memory(locked, &current_task, UserAddress::from_ptr(100 as usize), 400);
1555
1556            let mapped_addr = starnix_core::testing::map_memory_anywhere(locked, current_task, 100);
1557            let (mm_vmo, _mm_offset) = current_task
1558                .mm()
1559                .unwrap()
1560                .get_mapping_memory(mapped_addr, ProtectionFlags::READ | ProtectionFlags::WRITE)
1561                .expect("mem");
1562
1563            // Use the same buffers as merge_buffers_test_complex but just offset them in the
1564            // memory we got mapped above.
1565            let remote_bufs = vec![
1566                remote_buf { pv: uaddr { addr: mapped_addr.into() }, len: 100 },
1567                remote_buf { pv: uaddr { addr: (addr + 100u64).expect("add").into() }, len: 100 },
1568                remote_buf { pv: uaddr { addr: (addr + 150u64).expect("add").into() }, len: 100 },
1569                remote_buf { pv: uaddr { addr: (addr + 400u64).expect("add").into() }, len: 50 },
1570                remote_buf { pv: uaddr { addr: (addr + 180u64).expect("add").into() }, len: 20 },
1571            ];
1572
1573            // This variant of the test puts single values based on the buffer index
1574            // into the user memory.
1575            let mut writer = UserMemoryWriter::new(&current_task, mapped_addr.into());
1576            let data = vec![10; remote_bufs[0].len as usize];
1577            writer.write(&data);
1578
1579            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[1].pv.into());
1580            let data = vec![11; remote_bufs[1].len as usize];
1581            writer.write(&data);
1582
1583            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[2].pv.into());
1584            let data = vec![12; remote_bufs[2].len as usize];
1585            writer.write(&data);
1586
1587            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[3].pv.into());
1588            let data = vec![13; remote_bufs[3].len as usize];
1589            writer.write(&data);
1590
1591            let mut writer = UserMemoryWriter::new(&current_task, remote_bufs[4].pv.into());
1592            let data = vec![14; remote_bufs[4].len as usize];
1593            writer.write(&data);
1594
1595            let vmo = zx::Vmo::create(*PAGE_SIZE).expect("vmo create");
1596            let vmo_dup = vmo.duplicate_handle(fidl::Rights::SAME_RIGHTS).expect("dup");
1597
1598            let state = OrderedMutex::new(FastRPCFileState {
1599                session: None,
1600                payload_vmos: vec![SharedPayloadBuffer { id: 1, vmo: vmo }].into(),
1601                cid: None,
1602                pid: None,
1603            });
1604            let mut fd_vmos = Some(vec![
1605                Some(
1606                    mm_vmo
1607                        .as_vmo()
1608                        .unwrap()
1609                        .duplicate_handle(fidl::Rights::SAME_RIGHTS)
1610                        .expect("dup"),
1611                ),
1612                None,
1613                None,
1614                None,
1615                None,
1616            ]);
1617
1618            let merged_buffers =
1619                FastRPCFile::merge_buffers(&fd_vmos, &remote_bufs).expect("merge to succeed");
1620            let payload_info = FastRPCFile::get_payload_info(
1621                &current_task,
1622                locked,
1623                &state,
1624                &merged_buffers,
1625                &remote_bufs,
1626                &mut fd_vmos,
1627                3,
1628            )
1629            .expect("get_payload_info");
1630
1631            let ArgumentEntry::VmoArgument(VmoArgument { vmo: _vmo, offset: _offset, length }) =
1632                &payload_info.input_args[0]
1633            else {
1634                panic!("wrong type")
1635            };
1636
1637            assert_eq!(length, &100u64);
1638
1639            assert_eq!(
1640                payload_info.input_args[1..3],
1641                vec![
1642                    ArgumentEntry::Argument(Argument { offset: 0, length: 100 }),
1643                    ArgumentEntry::Argument(Argument { offset: 50, length: 100 })
1644                ]
1645            );
1646
1647            assert_eq!(
1648                payload_info.output_args,
1649                vec![
1650                    ArgumentEntry::Argument(Argument { offset: 256, length: 50 }),
1651                    ArgumentEntry::Argument(Argument { offset: 80, length: 20 }),
1652                ]
1653            );
1654
1655            // Tests that the input buffers have been correctly setup in the payload.
1656            //
1657            // The buffers at 500 is mapped so it should not appear here.
1658            //
1659            // Since the buffer at 256 is part of the output, it will not be copied into the vmo as
1660            // part of the setup. But because 80-100 is already included as part of the input buffer
1661            // from 0-100 and 50-150 the data appears in here just as a side effect.
1662            let data = vmo_dup.read_to_vec::<u8>(0, 484).expect("read");
1663            let expected_vmo = vec![
1664                11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
1665                11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
1666                11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1667                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 14, 14, 14, 14,
1668                14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 12, 12, 12, 12, 12,
1669                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1670                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
1671                12, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1672                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1673                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1674                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1675                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1676                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1677                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1678                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1679                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1680                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1681                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1682                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
1683                0, 0,
1684            ];
1685
1686            assert_eq!(expected_vmo, data);
1687        })
1688        .await;
1689    }
1690}