| // Portions Copyright 2017 The Chromium OS Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE-BSD-3-Clause file. | 
 | // | 
 | // Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. | 
 | // | 
 | // Copyright © 2019 Intel Corporation | 
 | // | 
 | // Copyright (C) 2020-2021 Alibaba Cloud. All rights reserved. | 
 | // | 
 | // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause | 
 |  | 
 | use std::fmt::{self, Debug}; | 
 | use std::mem::size_of; | 
 | use std::ops::Deref; | 
 |  | 
 | use vm_memory::bitmap::{BitmapSlice, WithBitmapSlice}; | 
 | use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryRegion}; | 
 |  | 
 | use crate::{Descriptor, Error, Reader, Writer}; | 
 | use virtio_bindings::bindings::virtio_ring::VRING_DESC_ALIGN_SIZE; | 
 |  | 
 | /// A virtio descriptor chain. | 
 | #[derive(Clone, Debug)] | 
 | pub struct DescriptorChain<M> { | 
 |     mem: M, | 
 |     desc_table: GuestAddress, | 
 |     queue_size: u16, | 
 |     head_index: u16, | 
 |     next_index: u16, | 
 |     ttl: u16, | 
 |     yielded_bytes: u32, | 
 |     is_indirect: bool, | 
 | } | 
 |  | 
 | impl<M> DescriptorChain<M> | 
 | where | 
 |     M: Deref, | 
 |     M::Target: GuestMemory, | 
 | { | 
 |     fn with_ttl( | 
 |         mem: M, | 
 |         desc_table: GuestAddress, | 
 |         queue_size: u16, | 
 |         ttl: u16, | 
 |         head_index: u16, | 
 |     ) -> Self { | 
 |         DescriptorChain { | 
 |             mem, | 
 |             desc_table, | 
 |             queue_size, | 
 |             head_index, | 
 |             next_index: head_index, | 
 |             ttl, | 
 |             is_indirect: false, | 
 |             yielded_bytes: 0, | 
 |         } | 
 |     } | 
 |  | 
 |     /// Create a new `DescriptorChain` instance. | 
 |     /// | 
 |     /// # Arguments | 
 |     /// * `mem` - the `GuestMemory` object that can be used to access the buffers pointed to by the | 
 |     ///           descriptor chain. | 
 |     /// * `desc_table` - the address of the descriptor table. | 
 |     /// * `queue_size` - the size of the queue, which is also the maximum size of a descriptor | 
 |     ///                  chain. | 
 |     /// * `head_index` - the descriptor index of the chain head. | 
 |     pub(crate) fn new(mem: M, desc_table: GuestAddress, queue_size: u16, head_index: u16) -> Self { | 
 |         Self::with_ttl(mem, desc_table, queue_size, queue_size, head_index) | 
 |     } | 
 |  | 
 |     /// Get the descriptor index of the chain head. | 
 |     pub fn head_index(&self) -> u16 { | 
 |         self.head_index | 
 |     } | 
 |  | 
 |     /// Return a `GuestMemory` object that can be used to access the buffers pointed to by the | 
 |     /// descriptor chain. | 
 |     pub fn memory(&self) -> &M::Target { | 
 |         self.mem.deref() | 
 |     } | 
 |  | 
 |     /// Return an iterator that only yields the readable descriptors in the chain. | 
 |     pub fn readable(self) -> DescriptorChainRwIter<M> { | 
 |         DescriptorChainRwIter { | 
 |             chain: self, | 
 |             writable: false, | 
 |         } | 
 |     } | 
 |  | 
 |     /// Return a new instance of Writer | 
 |     pub fn writer<'a, B: BitmapSlice>(self, mem: &'a M::Target) -> Result<Writer<'a, B>, Error> | 
 |     where | 
 |         M::Target: Sized, | 
 |         <<M::Target as GuestMemory>::R as GuestMemoryRegion>::B: WithBitmapSlice<'a, S = B>, | 
 |     { | 
 |         Writer::new(mem, self).map_err(|_| Error::InvalidChain) | 
 |     } | 
 |  | 
 |     /// Return a new instance of Reader | 
 |     pub fn reader<'a, B: BitmapSlice>(self, mem: &'a M::Target) -> Result<Reader<'a, B>, Error> | 
 |     where | 
 |         M::Target: Sized, | 
 |         <<M::Target as GuestMemory>::R as GuestMemoryRegion>::B: WithBitmapSlice<'a, S = B>, | 
 |     { | 
 |         Reader::new(mem, self).map_err(|_| Error::InvalidChain) | 
 |     } | 
 |  | 
 |     /// Return an iterator that only yields the writable descriptors in the chain. | 
 |     pub fn writable(self) -> DescriptorChainRwIter<M> { | 
 |         DescriptorChainRwIter { | 
 |             chain: self, | 
 |             writable: true, | 
 |         } | 
 |     } | 
 |  | 
 |     // Alters the internal state of the `DescriptorChain` to switch iterating over an | 
 |     // indirect descriptor table defined by `desc`. | 
 |     fn switch_to_indirect_table(&mut self, desc: Descriptor) -> Result<(), Error> { | 
 |         // Check the VIRTQ_DESC_F_INDIRECT flag (i.e., is_indirect) is not set inside | 
 |         // an indirect descriptor. | 
 |         // (see VIRTIO Spec, Section 2.6.5.3.1 Driver Requirements: Indirect Descriptors) | 
 |         if self.is_indirect { | 
 |             return Err(Error::InvalidIndirectDescriptor); | 
 |         } | 
 |  | 
 |         // Alignment requirements for vring elements start from virtio 1.0, | 
 |         // but this is not necessary for address of indirect descriptor. | 
 |         if desc.len() & (VRING_DESC_ALIGN_SIZE - 1) != 0 { | 
 |             return Err(Error::InvalidIndirectDescriptorTable); | 
 |         } | 
 |  | 
 |         // It is safe to do a plain division since we checked above that desc.len() is a multiple of | 
 |         // VRING_DESC_ALIGN_SIZE, and VRING_DESC_ALIGN_SIZE is != 0. | 
 |         let table_len = desc.len() / VRING_DESC_ALIGN_SIZE; | 
 |         if table_len > u32::from(u16::MAX) { | 
 |             return Err(Error::InvalidIndirectDescriptorTable); | 
 |         } | 
 |  | 
 |         self.desc_table = desc.addr(); | 
 |         // try_from cannot fail as we've checked table_len above | 
 |         self.queue_size = u16::try_from(table_len).expect("invalid table_len"); | 
 |         self.next_index = 0; | 
 |         self.ttl = self.queue_size; | 
 |         self.is_indirect = true; | 
 |  | 
 |         Ok(()) | 
 |     } | 
 | } | 
 |  | 
 | impl<M> Iterator for DescriptorChain<M> | 
 | where | 
 |     M: Deref, | 
 |     M::Target: GuestMemory, | 
 | { | 
 |     type Item = Descriptor; | 
 |  | 
 |     /// Return the next descriptor in this descriptor chain, if there is one. | 
 |     /// | 
 |     /// Note that this is distinct from the next descriptor chain returned by | 
 |     /// [`AvailIter`](struct.AvailIter.html), which is the head of the next | 
 |     /// _available_ descriptor chain. | 
 |     fn next(&mut self) -> Option<Self::Item> { | 
 |         if self.ttl == 0 || self.next_index >= self.queue_size { | 
 |             return None; | 
 |         } | 
 |  | 
 |         let desc_addr = self | 
 |             .desc_table | 
 |             // The multiplication can not overflow an u64 since we are multiplying an u16 with a | 
 |             // small number. | 
 |             .checked_add(self.next_index as u64 * size_of::<Descriptor>() as u64)?; | 
 |  | 
 |         // The guest device driver should not touch the descriptor once submitted, so it's safe | 
 |         // to use read_obj() here. | 
 |         let desc = self.mem.read_obj::<Descriptor>(desc_addr).ok()?; | 
 |  | 
 |         if desc.refers_to_indirect_table() { | 
 |             self.switch_to_indirect_table(desc).ok()?; | 
 |             return self.next(); | 
 |         } | 
 |  | 
 |         // constructing a chain that is longer than 2^32 bytes is illegal, | 
 |         // let's terminate the iteration if something violated this. | 
 |         // (VIRTIO v1.2, 2.7.5.2: "Drivers MUST NOT add a descriptor chain | 
 |         // longer than 2^32 bytes in total;") | 
 |         match self.yielded_bytes.checked_add(desc.len()) { | 
 |             Some(yielded_bytes) => self.yielded_bytes = yielded_bytes, | 
 |             None => return None, | 
 |         }; | 
 |  | 
 |         if desc.has_next() { | 
 |             self.next_index = desc.next(); | 
 |             // It's ok to decrement `self.ttl` here because we check at the start of the method | 
 |             // that it's greater than 0. | 
 |             self.ttl -= 1; | 
 |         } else { | 
 |             self.ttl = 0; | 
 |         } | 
 |  | 
 |         Some(desc) | 
 |     } | 
 | } | 
 |  | 
 | /// An iterator for readable or writable descriptors. | 
 | #[derive(Clone)] | 
 | pub struct DescriptorChainRwIter<M> { | 
 |     chain: DescriptorChain<M>, | 
 |     writable: bool, | 
 | } | 
 |  | 
 | impl<M> Iterator for DescriptorChainRwIter<M> | 
 | where | 
 |     M: Deref, | 
 |     M::Target: GuestMemory, | 
 | { | 
 |     type Item = Descriptor; | 
 |  | 
 |     /// Return the next readable/writeable descriptor (depending on the `writable` value) in this | 
 |     /// descriptor chain, if there is one. | 
 |     /// | 
 |     /// Note that this is distinct from the next descriptor chain returned by | 
 |     /// [`AvailIter`](struct.AvailIter.html), which is the head of the next | 
 |     /// _available_ descriptor chain. | 
 |     fn next(&mut self) -> Option<Self::Item> { | 
 |         loop { | 
 |             match self.chain.next() { | 
 |                 Some(v) => { | 
 |                     if v.is_write_only() == self.writable { | 
 |                         return Some(v); | 
 |                     } | 
 |                 } | 
 |                 None => return None, | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | // We can't derive Debug, because rustc doesn't generate the `M::T: Debug` constraint | 
 | impl<M> Debug for DescriptorChainRwIter<M> | 
 | where | 
 |     M: Debug, | 
 | { | 
 |     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
 |         f.debug_struct("DescriptorChainRwIter") | 
 |             .field("chain", &self.chain) | 
 |             .field("writable", &self.writable) | 
 |             .finish() | 
 |     } | 
 | } | 
 |  | 
 | #[cfg(test)] | 
 | mod tests { | 
 |     use super::*; | 
 |     use crate::mock::{DescriptorTable, MockSplitQueue}; | 
 |     use virtio_bindings::bindings::virtio_ring::{VRING_DESC_F_INDIRECT, VRING_DESC_F_NEXT}; | 
 |     use vm_memory::GuestMemoryMmap; | 
 |  | 
 |     #[test] | 
 |     fn test_checked_new_descriptor_chain() { | 
 |         let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |         let vq = MockSplitQueue::new(m, 16); | 
 |  | 
 |         assert!(vq.end().0 < 0x1000); | 
 |  | 
 |         // index >= queue_size | 
 |         assert!( | 
 |             DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 16) | 
 |                 .next() | 
 |                 .is_none() | 
 |         ); | 
 |  | 
 |         // desc_table address is way off | 
 |         assert!( | 
 |             DescriptorChain::<&GuestMemoryMmap>::new(m, GuestAddress(0x00ff_ffff_ffff), 16, 0) | 
 |                 .next() | 
 |                 .is_none() | 
 |         ); | 
 |  | 
 |         { | 
 |             // the first desc has a normal len, and the next_descriptor flag is set | 
 |             // but the the index of the next descriptor is too large | 
 |             let desc = Descriptor::new(0x1000, 0x1000, VRING_DESC_F_NEXT as u16, 16); | 
 |             vq.desc_table().store(0, desc).unwrap(); | 
 |  | 
 |             let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 0); | 
 |             c.next().unwrap(); | 
 |             assert!(c.next().is_none()); | 
 |         } | 
 |  | 
 |         // finally, let's test an ok chain | 
 |         { | 
 |             let desc = Descriptor::new(0x1000, 0x1000, VRING_DESC_F_NEXT as u16, 1); | 
 |             vq.desc_table().store(0, desc).unwrap(); | 
 |  | 
 |             let desc = Descriptor::new(0x2000, 0x1000, 0, 0); | 
 |             vq.desc_table().store(1, desc).unwrap(); | 
 |  | 
 |             let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), 16, 0); | 
 |  | 
 |             assert_eq!( | 
 |                 c.memory() as *const GuestMemoryMmap, | 
 |                 m as *const GuestMemoryMmap | 
 |             ); | 
 |  | 
 |             assert_eq!(c.desc_table, vq.start()); | 
 |             assert_eq!(c.queue_size, 16); | 
 |             assert_eq!(c.ttl, c.queue_size); | 
 |  | 
 |             let desc = c.next().unwrap(); | 
 |             assert_eq!(desc.addr(), GuestAddress(0x1000)); | 
 |             assert_eq!(desc.len(), 0x1000); | 
 |             assert_eq!(desc.flags(), VRING_DESC_F_NEXT as u16); | 
 |             assert_eq!(desc.next(), 1); | 
 |             assert_eq!(c.ttl, c.queue_size - 1); | 
 |  | 
 |             assert!(c.next().is_some()); | 
 |             // The descriptor above was the last from the chain, so `ttl` should be 0 now. | 
 |             assert_eq!(c.ttl, 0); | 
 |             assert!(c.next().is_none()); | 
 |             assert_eq!(c.ttl, 0); | 
 |         } | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_ttl_wrap_around() { | 
 |         const QUEUE_SIZE: u16 = 16; | 
 |  | 
 |         let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x100000)]).unwrap(); | 
 |         let vq = MockSplitQueue::new(m, QUEUE_SIZE); | 
 |  | 
 |         // Populate the entire descriptor table with entries. Only the last one should not have the | 
 |         // VIRTQ_DESC_F_NEXT set. | 
 |         for i in 0..QUEUE_SIZE - 1 { | 
 |             let desc = Descriptor::new( | 
 |                 0x1000 * (i + 1) as u64, | 
 |                 0x1000, | 
 |                 VRING_DESC_F_NEXT as u16, | 
 |                 i + 1, | 
 |             ); | 
 |             vq.desc_table().store(i, desc).unwrap(); | 
 |         } | 
 |         let desc = Descriptor::new((0x1000 * 16) as u64, 0x1000, 0, 0); | 
 |         vq.desc_table().store(QUEUE_SIZE - 1, desc).unwrap(); | 
 |  | 
 |         let mut c = DescriptorChain::<&GuestMemoryMmap>::new(m, vq.start(), QUEUE_SIZE, 0); | 
 |         assert_eq!(c.ttl, c.queue_size); | 
 |  | 
 |         // Validate that `ttl` wraps around even when the entire descriptor table is populated. | 
 |         for i in 0..QUEUE_SIZE { | 
 |             let _desc = c.next().unwrap(); | 
 |             assert_eq!(c.ttl, c.queue_size - i - 1); | 
 |         } | 
 |         assert!(c.next().is_none()); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_new_from_indirect_descriptor() { | 
 |         // This is testing that chaining an indirect table works as expected. It is also a negative | 
 |         // test for the following requirement from the spec: | 
 |         // `A driver MUST NOT set both VIRTQ_DESC_F_INDIRECT and VIRTQ_DESC_F_NEXT in flags.`. In | 
 |         // case the driver is setting both of these flags, we check that the device doesn't panic. | 
 |         let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |         let vq = MockSplitQueue::new(m, 16); | 
 |         let dtable = vq.desc_table(); | 
 |  | 
 |         // Create a chain with one normal descriptor and one pointing to an indirect table. | 
 |         let desc = Descriptor::new(0x6000, 0x1000, VRING_DESC_F_NEXT as u16, 1); | 
 |         dtable.store(0, desc).unwrap(); | 
 |         // The spec forbids setting both VIRTQ_DESC_F_INDIRECT and VIRTQ_DESC_F_NEXT in flags. We do | 
 |         // not currently enforce this rule, we just ignore the VIRTQ_DESC_F_NEXT flag. | 
 |         let desc = Descriptor::new( | 
 |             0x7000, | 
 |             0x1000, | 
 |             (VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT) as u16, | 
 |             2, | 
 |         ); | 
 |         dtable.store(1, desc).unwrap(); | 
 |         let desc = Descriptor::new(0x8000, 0x1000, 0, 0); | 
 |         dtable.store(2, desc).unwrap(); | 
 |  | 
 |         let mut c: DescriptorChain<&GuestMemoryMmap> = DescriptorChain::new(m, vq.start(), 16, 0); | 
 |  | 
 |         // create an indirect table with 4 chained descriptors | 
 |         let idtable = DescriptorTable::new(m, GuestAddress(0x7000), 4); | 
 |         for i in 0..4u16 { | 
 |             let desc: Descriptor = if i < 3 { | 
 |                 Descriptor::new(0x1000 * i as u64, 0x1000, VRING_DESC_F_NEXT as u16, i + 1) | 
 |             } else { | 
 |                 Descriptor::new(0x1000 * i as u64, 0x1000, 0, 0) | 
 |             }; | 
 |             idtable.store(i, desc).unwrap(); | 
 |         } | 
 |  | 
 |         assert_eq!(c.head_index(), 0); | 
 |         // Consume the first descriptor. | 
 |         c.next().unwrap(); | 
 |  | 
 |         // The chain logic hasn't parsed the indirect descriptor yet. | 
 |         assert!(!c.is_indirect); | 
 |  | 
 |         // Try to iterate through the indirect descriptor chain. | 
 |         for i in 0..4 { | 
 |             let desc = c.next().unwrap(); | 
 |             assert!(c.is_indirect); | 
 |             if i < 3 { | 
 |                 assert_eq!(desc.flags(), VRING_DESC_F_NEXT as u16); | 
 |                 assert_eq!(desc.next(), i + 1); | 
 |             } | 
 |         } | 
 |         // Even though we added a new descriptor after the one that is pointing to the indirect | 
 |         // table, this descriptor won't be available when parsing the chain. | 
 |         assert!(c.next().is_none()); | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_indirect_descriptor_address_noaligned() { | 
 |         // Alignment requirements for vring elements start from virtio 1.0, | 
 |         // but this is not necessary for address of indirect descriptor. | 
 |         let m = &GuestMemoryMmap::<()>::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |         let vq = MockSplitQueue::new(m, 16); | 
 |         let dtable = vq.desc_table(); | 
 |  | 
 |         // Create a chain with a descriptor pointing to an indirect table with unaligned address. | 
 |         let desc = Descriptor::new( | 
 |             0x7001, | 
 |             0x1000, | 
 |             (VRING_DESC_F_INDIRECT | VRING_DESC_F_NEXT) as u16, | 
 |             2, | 
 |         ); | 
 |         dtable.store(0, desc).unwrap(); | 
 |  | 
 |         let mut c: DescriptorChain<&GuestMemoryMmap> = DescriptorChain::new(m, vq.start(), 16, 0); | 
 |  | 
 |         // Create an indirect table with 4 chained descriptors. | 
 |         let idtable = DescriptorTable::new(m, GuestAddress(0x7001), 4); | 
 |         for i in 0..4u16 { | 
 |             let desc: Descriptor = if i < 3 { | 
 |                 Descriptor::new(0x1000 * i as u64, 0x1000, VRING_DESC_F_NEXT as u16, i + 1) | 
 |             } else { | 
 |                 Descriptor::new(0x1000 * i as u64, 0x1000, 0, 0) | 
 |             }; | 
 |             idtable.store(i, desc).unwrap(); | 
 |         } | 
 |  | 
 |         // Try to iterate through the indirect descriptor chain. | 
 |         for i in 0..4 { | 
 |             let desc = c.next().unwrap(); | 
 |             assert!(c.is_indirect); | 
 |             if i < 3 { | 
 |                 assert_eq!(desc.flags(), VRING_DESC_F_NEXT as u16); | 
 |                 assert_eq!(desc.next(), i + 1); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     #[test] | 
 |     fn test_indirect_descriptor_err() { | 
 |         // We are testing here different misconfigurations of the indirect table. For these error | 
 |         // case scenarios, the iterator over the descriptor chain won't return a new descriptor. | 
 |         { | 
 |             let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |             let vq = MockSplitQueue::new(m, 16); | 
 |  | 
 |             // Create a chain with a descriptor pointing to an invalid indirect table: len not a | 
 |             // multiple of descriptor size. | 
 |             let desc = Descriptor::new(0x1000, 0x1001, VRING_DESC_F_INDIRECT as u16, 0); | 
 |             vq.desc_table().store(0, desc).unwrap(); | 
 |  | 
 |             let mut c: DescriptorChain<&GuestMemoryMmap> = | 
 |                 DescriptorChain::new(m, vq.start(), 16, 0); | 
 |  | 
 |             assert!(c.next().is_none()); | 
 |         } | 
 |  | 
 |         { | 
 |             let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |             let vq = MockSplitQueue::new(m, 16); | 
 |  | 
 |             // Create a chain with a descriptor pointing to an invalid indirect table: table len > | 
 |             // u16::MAX. | 
 |             let desc = Descriptor::new( | 
 |                 0x1000, | 
 |                 (u16::MAX as u32 + 1) * VRING_DESC_ALIGN_SIZE, | 
 |                 VRING_DESC_F_INDIRECT as u16, | 
 |                 0, | 
 |             ); | 
 |             vq.desc_table().store(0, desc).unwrap(); | 
 |  | 
 |             let mut c: DescriptorChain<&GuestMemoryMmap> = | 
 |                 DescriptorChain::new(m, vq.start(), 16, 0); | 
 |  | 
 |             assert!(c.next().is_none()); | 
 |         } | 
 |  | 
 |         { | 
 |             let m = &GuestMemoryMmap::from_ranges(&[(GuestAddress(0), 0x10000)]).unwrap(); | 
 |             let vq = MockSplitQueue::new(m, 16); | 
 |  | 
 |             // Create a chain with a descriptor pointing to an indirect table. | 
 |             let desc = Descriptor::new(0x1000, 0x1000, VRING_DESC_F_INDIRECT as u16, 0); | 
 |             vq.desc_table().store(0, desc).unwrap(); | 
 |             // It's ok for an indirect descriptor to have flags = 0. | 
 |             let desc = Descriptor::new(0x3000, 0x1000, 0, 0); | 
 |             m.write_obj(desc, GuestAddress(0x1000)).unwrap(); | 
 |  | 
 |             let mut c: DescriptorChain<&GuestMemoryMmap> = | 
 |                 DescriptorChain::new(m, vq.start(), 16, 0); | 
 |             assert!(c.next().is_some()); | 
 |  | 
 |             // But it's not allowed to have an indirect descriptor that points to another indirect | 
 |             // table. | 
 |             let desc = Descriptor::new(0x3000, 0x1000, VRING_DESC_F_INDIRECT as u16, 0); | 
 |             m.write_obj(desc, GuestAddress(0x1000)).unwrap(); | 
 |  | 
 |             let mut c: DescriptorChain<&GuestMemoryMmap> = | 
 |                 DescriptorChain::new(m, vq.start(), 16, 0); | 
 |  | 
 |             assert!(c.next().is_none()); | 
 |         } | 
 |     } | 
 | } |