Skip to main content

virtio_driver/common/queue/
buffer.rs

1// Copyright 2026 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
5//! Models buffers used in virtqueues.
6
7// TODO(https://fxbug.dev/504722357): Remove this in favor of more granular
8// attributes when the Rust port is completed.
9#![allow(dead_code)]
10
11use std::num::NonZero;
12
13/// Describes a buffer that can be submitted to a virtio queue.
14///
15/// A buffer describes the memory used by a virtio device to read the input and
16/// write the output for a task. The description is a list of
17/// [`VirtioMemoryRange`]. Each memory range must be contiguous in the PCI
18/// device's addressable space.
19///
20/// The driver submits a buffer to ask the virtio device to perform a task.
21/// Submitting a buffer transfers the driver's ownership of the buffer to the
22/// device. The device returns the buffer when it completes the task. Returning
23/// the buffer transfers ownership back to the driver.
24///
25/// For convenience, we say that a buffer is submitted when it is owned by the
26/// device.  We also say that a memory range is submitted when the buffer that
27/// it belongs to is submitted. The device may only access the memory submitted
28/// buffers, which are the buffers that it owns.
29///
30/// The list of memory regions must first contain the regions storing the task's
31/// input (using [`VirtioMemoryRangeAccess::Input`]), followed by the regions
32/// storing the task's output (using [`VirtioMemoryRangeAccess::Output`]).
33// @cite(virtio): sec="2.7" title="Split Virtqueues"
34// @cite(virtio): sec="2.7.4" title="Message Framing"
35// @cite(virtio): sec="2.7.4.2" title="Driver Requirements: Message Framing"
36// @cite(virtio): sec="2.7.5" title="The Virtqueue Descriptor Table"
37// @cite(virtio): sec="2.7.13.1" title="Placing Buffers Into The Descriptor Table"
38// @cite(virtio): sec="2.8" title="Packed Virtqueues"
39pub struct VirtioBufferRef<'a> {
40    range_list: &'a [VirtioMemoryRange],
41}
42
43impl<'a> AsRef<[VirtioMemoryRange]> for VirtioBufferRef<'a> {
44    fn as_ref(&self) -> &[VirtioMemoryRange] {
45        self.range_list
46    }
47}
48
49impl<'a> VirtioBufferRef<'a> {
50    /// The maximum number of contiguous memory regions in a buffer.
51    ///
52    /// The upper bound is based on the fact that a buffer's memory regions must
53    /// fit in a virtqueue's descriptor table, so the number of memory regions
54    /// is capped by the virtqueue descriptor table size.
55    // @cite(virtio): sec="2.7" title="Split Virtqueues"
56    // @cite(virtio): sec="2.8" title="Packed Virtqueues"
57    pub const MAX_LENGTH: usize = 32768;
58
59    /// Panics if the list of ranges is empty, or if a range with
60    /// [`VirtioMemoryRangeAccess::Input`] access follows a range with
61    /// [`VirtioMemoryRangeAccess::Output`] access.
62    pub fn new(range_list: &'a [VirtioMemoryRange]) -> VirtioBufferRef<'a> {
63        assert!(!range_list.is_empty(), "virtio buffers must have at least one memory range");
64        assert!(
65            range_list.len() <= Self::MAX_LENGTH,
66            "virtio buffer has too many memory ranges: {}",
67            range_list.len()
68        );
69
70        // Check the memory access constraint.
71        let mut last_device_access = VirtioMemoryRangeAccess::Input;
72        for range in range_list {
73            if range.device_access == last_device_access {
74                continue;
75            }
76            assert!(
77                range.device_access == VirtioMemoryRangeAccess::Output,
78                "Range with {:?} access following range with {:?} access",
79                range.device_access,
80                last_device_access,
81            );
82            last_device_access = range.device_access;
83        }
84
85        VirtioBufferRef { range_list }
86    }
87
88    /// Returns the number of memory ranges in the buffer.
89    pub fn len(&self) -> NonZero<u16> {
90        // `as` does not truncate the length because the constructor ensures the
91        // range list has at most [`Self::MAX_LENGTH`] ranges.
92        // [`Option::unwrap()`] will not panic because the constructor ensures
93        // that the range list is not empty.
94        NonZero::<u16>::new(self.range_list.len() as u16).unwrap()
95    }
96}
97
98/// Identifies a buffer submitted to a virtqueue.
99///
100/// The identifier uniquely identifies a buffer from the moment it is submitted
101/// to the device via the queue until the moment it is returned to the driver
102/// via the queue. Once the buffer is returned to the driver, a future queue
103/// submission may reuse the identifier. The identifier is unique within one
104/// virtqueue.
105///
106/// It follows that higher-level code may use the buffer ID (potentially
107/// qualified by the virtqueue index) to track other buffer-related resources,
108/// as long as the tracking stops right after a buffer is returned.
109#[derive(Clone, Copy, Debug, PartialEq, Eq)]
110pub struct VirtioSubmittedBufferId(pub u16);
111
112/// Describes a buffer returned by a virtqueue.
113pub struct VirtioReturnedBufferInfo {
114    /// Indicates which previously submitted buffer was returned.
115    ///
116    /// [`VirtioSubmittedBufferId`] documents the validity window.
117    pub submitted_buffer_id: VirtioSubmittedBufferId,
118
119    /// The number of bytes written by the device to the buffer.
120    ///
121    /// The device writes to the buffer regions that allow it by having their
122    /// [`VirtioMemoryRangeFlags::written_by_hardware`] set to true. The device
123    /// uses the writable regions in the order in which they appear in the
124    /// submitted buffer region list.
125    // @cite(virtio): sec="2.7.8.2" title="Device Requirements: The Virtqueue Used Ring"
126    pub written_bytes: u32,
127}
128
129/// Describes a memory region accessible by a virtio device.
130///
131/// The memory region is contiguous in the PCI device's physical address space.
132/// If no IOMMU is involved, the PCI device's physical address space is the same
133/// as the CPU physical address space visible to the guest (virtualized) OS.
134///
135/// Each instance describes a memory region that is submitted (owned by the
136/// device) or is about to be submitted (owned by the device). Device ownership
137/// is conceptually equivalent to a Rust mutable reference covering the entire
138/// memory range. Here are two immediate implications:
139///
140/// 1. No Rust references may point into a range described by an instance.
141/// 2. The memory ranges described by any two instances must not overlap.
142// @cite(virtio): sec="2.7.5" title="The Virtqueue Descriptor Table"
143// @cite(virtio): sec="2.7.13.1" title="Placing Buffers Into The Descriptor Table"
144#[derive(Debug)]
145pub struct VirtioMemoryRange {
146    start_physical_address: u64,
147    size: NonZero<u32>,
148    device_access: VirtioMemoryRangeAccess,
149}
150
151impl VirtioMemoryRange {
152    /// Describes a memory range that will be submitted to a virtio device.
153    ///
154    /// # Safety
155    ///
156    /// `start_physical_address` and `size` must identify a valid range
157    /// of memory that is contiguous in the PCI virtio device's physical address
158    /// space. The newly created instance must be treated like a mutable Rust
159    /// reference covering the entire memory range.
160    pub unsafe fn new(
161        start_physical_address: u64,
162        size: NonZero<u32>,
163        device_access: VirtioMemoryRangeAccess,
164    ) -> VirtioMemoryRange {
165        VirtioMemoryRange { start_physical_address, size, device_access }
166    }
167
168    /// Points to the first byte in the memory region.
169    ///
170    /// Uses the PCI device's physical addressable space.
171    pub fn start_physical_address(&self) -> u64 {
172        self.start_physical_address
173    }
174
175    /// The number of bytes that make up the contiguous region.
176    pub fn size(&self) -> NonZero<u32> {
177        self.size
178    }
179
180    /// Constrains a device's accesses to the memory range.
181    pub fn device_access(&self) -> VirtioMemoryRangeAccess {
182        self.device_access
183    }
184}
185
186/// Describes the way a device may access a [`VirtioMemoryRange`].
187///
188/// The virtio device may only access a range's memory while the range belongs
189/// to a buffer that is submitted (owned by the device).
190// @cite(virtio): sec="2.7.5" title="The Virtqueue Descriptor Table"
191// @alias(virtio): theirs="VIRTQ_DESC_F_WRITE"
192#[derive(Clone, Copy, Debug, PartialEq, Eq)]
193pub enum VirtioMemoryRangeAccess {
194    /// The device may read from the memory range while owning its buffer.
195    ///
196    /// This access mode implies that the memory range stores input parameters
197    /// for the task assigned to the device.
198    ///
199    /// The driver is expected to initialize (write) all the range's memory
200    /// before submitting the buffer to the device.
201    ///
202    /// The Rust memory model dictates that the driver may hold immutable
203    /// references to the buffer's memory while the buffer is submitted.
204    /// However, for simplicity, we use the stronger constraint in [`Output`]
205    /// for all ranges owned by the device.
206    // @cite(virtio): sec="2.7.5" title="The Virtqueue Descriptor Table"
207    // @alias(virtio): theirs="device-readable"
208    Input,
209
210    /// The device may write to the memory range while owning its buffer.
211    ///
212    /// This access mode implies that the memory range stores the output (also
213    /// called "results" and "returned values") of the task assigned to the
214    /// device.
215    ///
216    /// The driver is expected to read the output from the range after the
217    /// device returns it. The Rust memory model dictates that the driver must
218    /// not have any reference to the range's memory while the range's buffer is
219    /// owned by the device.
220    // @cite(virtio): sec="2.7.5" title="The Virtqueue Descriptor Table"
221    // @alias(virtio): theirs="device-writable"
222    Output,
223}