devices/virtio/queue/
split_descriptor_chain.rs

1// Copyright 2023 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Split virtqueue descriptor chain iterator
6
7#![deny(missing_docs)]
8
9use anyhow::bail;
10use anyhow::Context;
11use anyhow::Result;
12use base::trace;
13use data_model::Le16;
14use data_model::Le32;
15use data_model::Le64;
16use vm_memory::GuestAddress;
17use vm_memory::GuestMemory;
18use zerocopy::FromBytes;
19use zerocopy::Immutable;
20use zerocopy::IntoBytes;
21use zerocopy::KnownLayout;
22
23use crate::virtio::descriptor_chain::Descriptor;
24use crate::virtio::descriptor_chain::DescriptorAccess;
25use crate::virtio::descriptor_chain::DescriptorChainIter;
26use crate::virtio::descriptor_chain::VIRTQ_DESC_F_NEXT;
27use crate::virtio::descriptor_chain::VIRTQ_DESC_F_WRITE;
28
29/// A single virtio split queue descriptor (`struct virtq_desc` in the spec).
30#[derive(Copy, Clone, Debug, FromBytes, Immutable, IntoBytes, KnownLayout)]
31#[repr(C)]
32pub struct Desc {
33    /// Guest address of memory described by this descriptor.
34    pub addr: Le64,
35
36    /// Length of this descriptor's memory region in bytes.
37    pub len: Le32,
38
39    /// `VIRTQ_DESC_F_*` flags for this descriptor.
40    pub flags: Le16,
41
42    /// Index of the next descriptor in the chain (only valid if `flags & VIRTQ_DESC_F_NEXT`).
43    pub next: Le16,
44}
45
46/// Iterator over the descriptors of a split virtqueue descriptor chain.
47pub struct SplitDescriptorChain<'m> {
48    /// Current descriptor index within `desc_table`, or `None` if the iterator is exhausted.
49    index: Option<u16>,
50
51    /// Number of descriptors returned by the iterator already.
52    /// If `count` reaches `queue_size`, the chain has a loop and is therefore invalid.
53    count: u16,
54
55    queue_size: u16,
56
57    mem: &'m GuestMemory,
58    desc_table: GuestAddress,
59}
60
61impl<'m> SplitDescriptorChain<'m> {
62    /// Construct a new iterator over a split virtqueue descriptor chain.
63    ///
64    /// # Arguments
65    /// * `mem` - The [`GuestMemory`] containing the descriptor chain.
66    /// * `desc_table` - Guest physical address of the descriptor table.
67    /// * `queue_size` - Total number of entries in the descriptor table.
68    /// * `index` - The index of the first descriptor in the chain.
69    pub fn new(
70        mem: &'m GuestMemory,
71        desc_table: GuestAddress,
72        queue_size: u16,
73        index: u16,
74    ) -> SplitDescriptorChain<'m> {
75        trace!("starting split descriptor chain head={index}");
76        SplitDescriptorChain {
77            index: Some(index),
78            count: 0,
79            queue_size,
80            mem,
81            desc_table,
82        }
83    }
84}
85
86impl DescriptorChainIter for SplitDescriptorChain<'_> {
87    fn next(&mut self) -> Result<Option<Descriptor>> {
88        let index = match self.index {
89            Some(index) => index,
90            None => return Ok(None),
91        };
92
93        if index >= self.queue_size {
94            bail!(
95                "out of bounds descriptor index {} for queue size {}",
96                index,
97                self.queue_size
98            );
99        }
100
101        if self.count >= self.queue_size {
102            bail!("descriptor chain loop detected");
103        }
104        self.count += 1;
105
106        let desc_addr = self
107            .desc_table
108            .checked_add((index as u64) * 16)
109            .context("integer overflow")?;
110        let desc = self
111            .mem
112            .read_obj_from_addr::<Desc>(desc_addr)
113            .with_context(|| format!("failed to read desc {:#x}", desc_addr.offset()))?;
114
115        let address: u64 = desc.addr.into();
116        let len: u32 = desc.len.into();
117        let flags: u16 = desc.flags.into();
118        let next: u16 = desc.next.into();
119
120        trace!("{index:5}: addr={address:#016x} len={len:#08x} flags={flags:#x}");
121
122        let unexpected_flags = flags & !(VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT);
123        if unexpected_flags != 0 {
124            bail!("unexpected flags in descriptor {index}: {unexpected_flags:#x}")
125        }
126
127        let access = if flags & VIRTQ_DESC_F_WRITE != 0 {
128            DescriptorAccess::DeviceWrite
129        } else {
130            DescriptorAccess::DeviceRead
131        };
132
133        self.index = if flags & VIRTQ_DESC_F_NEXT != 0 {
134            Some(next)
135        } else {
136            None
137        };
138
139        Ok(Some(Descriptor {
140            address,
141            len,
142            access,
143        }))
144    }
145
146    fn count(&self) -> u16 {
147        self.count
148    }
149
150    fn id(&self) -> Option<u16> {
151        None
152    }
153}