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