Skip to main content

virtio/
common.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::queue::QueueCompletion;
5use crate::queue::QueueCoreCompleteWork;
6use crate::queue::QueueCoreGetWork;
7use crate::queue::QueueError;
8use crate::queue::QueueParams;
9use crate::queue::QueueState;
10use crate::queue::VirtioQueuePayload;
11use crate::queue::new_queue;
12use crate::spec::VirtioDeviceFeatures;
13use crate::spec::VirtioDeviceType;
14use futures::FutureExt;
15use futures::Stream;
16use guestmem::DoorbellRegistration;
17use guestmem::GuestMemory;
18use guestmem::GuestMemoryError;
19use inspect::Inspect;
20use pal_async::wait::PolledWait;
21use pal_event::Event;
22use std::io::Error;
23use std::pin::Pin;
24use std::sync::Arc;
25use std::task::Context;
26use std::task::Poll;
27use std::task::ready;
28use thiserror::Error;
29use vmcore::interrupt::Interrupt;
30
31/// Read all readable payload buffers into `target`. Returns the number of bytes read.
32fn read_from_payload(
33    payload: &[VirtioQueuePayload],
34    mem: &GuestMemory,
35    target: &mut [u8],
36) -> Result<usize, GuestMemoryError> {
37    let mut remaining = target;
38    let mut read_bytes: usize = 0;
39    for payload in payload {
40        if payload.writeable {
41            continue;
42        }
43        let size = std::cmp::min(payload.length as usize, remaining.len());
44        let (current, next) = remaining.split_at_mut(size);
45        mem.read_at(payload.address, current)?;
46        read_bytes += size;
47        if next.is_empty() {
48            break;
49        }
50        remaining = next;
51    }
52    Ok(read_bytes)
53}
54
55/// Total length of all readable (non-writeable) payload buffers.
56fn readable_payload_length(payload: &[VirtioQueuePayload]) -> u64 {
57    payload
58        .iter()
59        .filter(|p| !p.writeable)
60        .fold(0, |acc, p| acc + p.length as u64)
61}
62
63/// Read readable payload buffers into `target`, skipping the first `offset`
64/// bytes of readable data. Returns the number of bytes read.
65fn read_from_payload_at_offset(
66    payload: &[VirtioQueuePayload],
67    offset: u64,
68    mem: &GuestMemory,
69    target: &mut [u8],
70) -> Result<usize, GuestMemoryError> {
71    let mut skip = offset;
72    let mut remaining = target;
73    let mut read_bytes: usize = 0;
74    for payload in payload {
75        if payload.writeable {
76            continue;
77        }
78        let payload_len = payload.length as u64;
79        if skip >= payload_len {
80            skip -= payload_len;
81            continue;
82        }
83        let usable = (payload_len - skip) as usize;
84        let size = std::cmp::min(usable, remaining.len());
85        let (current, next) = remaining.split_at_mut(size);
86        // Use saturating add so that an overflowing guest-provided address
87        // is guaranteed to land out of range rather than wrapping to a low
88        // GPA.
89        mem.read_at(payload.address.saturating_add(skip), current)?;
90        read_bytes += size;
91        skip = 0;
92        if next.is_empty() {
93            break;
94        }
95        remaining = next;
96    }
97    Ok(read_bytes)
98}
99
100/// A descriptor chain popped from a [`VirtioQueue`].
101///
102/// The device must call [`VirtioQueue::complete`] exactly once to post a
103/// completion to the guest's used ring. Dropping without completing is a bug
104/// and will not automatically post a completion.
105#[must_use]
106pub struct VirtioQueueCallbackWork {
107    completion: QueueCompletion,
108    pub payload: Vec<VirtioQueuePayload>,
109}
110
111impl VirtioQueueCallbackWork {
112    pub(crate) fn from_parts(
113        completion: QueueCompletion,
114        payload: Vec<VirtioQueuePayload>,
115    ) -> Self {
116        Self {
117            completion,
118            payload,
119        }
120    }
121
122    /// Borrows the completion token, used internally to advance the available
123    /// index for a peeked descriptor.
124    pub(crate) fn completion(&self) -> &QueueCompletion {
125        &self.completion
126    }
127
128    pub fn descriptor_index(&self) -> u16 {
129        self.completion.descriptor_index()
130    }
131
132    /// Discards the payload buffer descriptors and returns the lightweight
133    /// [`QueueCompletion`] token, which carries only the state
134    /// [`VirtioQueue::complete_prepared`] needs to publish the used ring entry.
135    ///
136    /// Use this to buffer a completion (e.g. for in-order publication) without
137    /// retaining the full payload.
138    pub fn into_completion(self) -> QueueCompletion {
139        self.completion
140    }
141
142    // Determine the total size of all readable or all writeable payload buffers.
143    pub fn get_payload_length(&self, writeable: bool) -> u64 {
144        self.payload
145            .iter()
146            .filter(|x| x.writeable == writeable)
147            .fold(0, |acc, x| acc + x.length as u64)
148    }
149
150    // Read all payload into a buffer.
151    pub fn read(&self, mem: &GuestMemory, target: &mut [u8]) -> Result<usize, GuestMemoryError> {
152        read_from_payload(&self.payload, mem, target)
153    }
154
155    /// Read readable payload into `target`, skipping the first `offset`
156    /// bytes of readable data.
157    pub fn read_at_offset(
158        &self,
159        offset: u64,
160        mem: &GuestMemory,
161        target: &mut [u8],
162    ) -> Result<usize, GuestMemoryError> {
163        read_from_payload_at_offset(&self.payload, offset, mem, target)
164    }
165
166    // Write the specified buffer to the payload buffers.
167    pub fn write_at_offset(
168        &self,
169        offset: u64,
170        mem: &GuestMemory,
171        source: &[u8],
172    ) -> Result<(), VirtioWriteError> {
173        let mut skip_bytes = offset;
174        let mut remaining = source;
175        for payload in &self.payload {
176            if !payload.writeable {
177                continue;
178            }
179
180            let payload_length = payload.length as u64;
181            if skip_bytes >= payload_length {
182                skip_bytes -= payload_length;
183                continue;
184            }
185
186            let size = std::cmp::min(
187                payload_length as usize - skip_bytes as usize,
188                remaining.len(),
189            );
190            let (current, next) = remaining.split_at(size);
191            // Saturating add so an overflowing guest address lands out of range
192            // rather than wrapping to a low GPA (mirrors the read path).
193            mem.write_at(payload.address.saturating_add(skip_bytes), current)?;
194            remaining = next;
195            if remaining.is_empty() {
196                break;
197            }
198            skip_bytes = 0;
199        }
200
201        if !remaining.is_empty() {
202            return Err(VirtioWriteError::NotAllWritten(source.len()));
203        }
204
205        Ok(())
206    }
207
208    pub fn write(&self, mem: &GuestMemory, source: &[u8]) -> Result<(), VirtioWriteError> {
209        self.write_at_offset(0, mem, source)
210    }
211}
212
213#[derive(Debug, Error)]
214pub enum VirtioWriteError {
215    #[error(transparent)]
216    Memory(#[from] GuestMemoryError),
217    #[error("{0:#x} bytes not written")]
218    NotAllWritten(usize),
219}
220
221/// A descriptor that has been peeked from a [`VirtioQueue`] without advancing
222/// the available index.
223///
224/// The descriptor remains in the available ring until [`consume`](Self::consume)
225/// is called, which advances the index and returns a normal
226/// [`VirtioQueueCallbackWork`] for completion.
227///
228/// Dropping a `PeekedWork` without consuming is a no-op — the descriptor stays
229/// available for the next peek/next call.
230pub struct PeekedWork<'a> {
231    queue: &'a mut VirtioQueue,
232    work: VirtioQueueCallbackWork,
233}
234
235impl<'a> PeekedWork<'a> {
236    fn new(queue: &'a mut VirtioQueue, work: VirtioQueueCallbackWork) -> Self {
237        Self { queue, work }
238    }
239
240    /// Returns the payload descriptors.
241    pub fn payload(&self) -> &[VirtioQueuePayload] {
242        &self.work.payload
243    }
244
245    /// Total length of all readable (guest-written) payload buffers.
246    pub fn readable_length(&self) -> u64 {
247        readable_payload_length(&self.work.payload)
248    }
249
250    /// Read all readable payload into `target`.
251    pub fn read(&self, mem: &GuestMemory, target: &mut [u8]) -> Result<usize, GuestMemoryError> {
252        read_from_payload(&self.work.payload, mem, target)
253    }
254
255    /// Read readable payload into `target`, skipping the first `offset`
256    /// bytes of readable data.
257    pub fn read_at_offset(
258        &self,
259        offset: u64,
260        mem: &GuestMemory,
261        target: &mut [u8],
262    ) -> Result<usize, GuestMemoryError> {
263        read_from_payload_at_offset(&self.work.payload, offset, mem, target)
264    }
265
266    /// Consume this peeked work, advancing the queue's available index.
267    ///
268    /// Returns a [`VirtioQueueCallbackWork`] that must be explicitly
269    /// completed via [`VirtioQueue::complete`].
270    pub fn consume(self) -> VirtioQueueCallbackWork {
271        self.queue.core.advance(self.work.completion());
272        self.work
273    }
274}
275
276#[derive(Debug, Inspect)]
277pub struct VirtioQueue {
278    #[inspect(flatten)]
279    core: QueueCoreGetWork,
280    #[inspect(flatten)]
281    complete: QueueCoreCompleteWork,
282    #[inspect(skip)]
283    notify_guest: Interrupt,
284    #[inspect(skip)]
285    queue_event: PolledWait<Event>,
286}
287
288impl VirtioQueue {
289    pub fn new(
290        features: VirtioDeviceFeatures,
291        params: QueueParams,
292        mem: GuestMemory,
293        notify: Interrupt,
294        queue_event: PolledWait<Event>,
295        initial_state: Option<QueueState>,
296    ) -> Result<Self, QueueError> {
297        let (get_work, complete_work) = new_queue(features, mem, params, initial_state)?;
298        Ok(Self {
299            core: get_work,
300            complete: complete_work,
301            notify_guest: notify,
302            queue_event,
303        })
304    }
305
306    /// Returns the current queue progress state.
307    pub fn queue_state(&self) -> QueueState {
308        QueueState {
309            avail_index: self.core.avail_index(),
310            used_index: self.complete.used_index(),
311        }
312    }
313
314    /// Polls until the queue is kicked by the guest, indicating new work may be
315    /// available.
316    ///
317    /// Before sleeping, this arms kick notification and rechecks the queue. If
318    /// new data arrived during arming, it returns immediately without sleeping.
319    /// On wakeup, kicks are suppressed to avoid unnecessary doorbells while
320    /// the caller drains the queue.
321    ///
322    /// Returns `Poll::Pending` forever once the queue has failed, since no
323    /// further work can ever be fetched. This keeps callers that loop on
324    /// "kick, then retry" from spinning.
325    pub fn poll_kick(&mut self, cx: &mut Context<'_>) -> Poll<()> {
326        if self.core.failed() {
327            return Poll::Pending;
328        }
329        if self.core.arm_for_kick() {
330            ready!(self.queue_event.wait().poll_unpin(cx)).expect("waits on Event cannot fail");
331        }
332        Poll::Ready(())
333    }
334
335    /// Try to get the next work item from the queue. Returns `Ok(None)` if no
336    /// work is currently available, or an error if there was an issue accessing
337    /// the queue.
338    ///
339    /// This is a lightweight check that does not arm kick notification. When
340    /// used in a poll loop with [`poll_kick`](Self::poll_kick), the kick will
341    /// be armed automatically before sleeping.
342    pub fn try_next(&mut self) -> Result<Option<VirtioQueueCallbackWork>, Error> {
343        self.core.try_next_work().map_err(Error::other)
344    }
345
346    /// Peek at the next available descriptor without advancing the available
347    /// index. Returns a [`PeekedWork`] that holds the descriptor payload and
348    /// a mutable reference to this queue.
349    ///
350    /// The descriptor stays in the available ring. Call
351    /// [`PeekedWork::consume`] to advance the index and get a normal
352    /// [`VirtioQueueCallbackWork`] for completion.
353    ///
354    /// Dropping the [`PeekedWork`] without consuming is a no-op — the
355    /// descriptor remains available.
356    ///
357    /// Calling `try_peek` again without consuming returns the **same**
358    /// descriptor (the descriptor metadata is captured at peek time), but
359    /// note that the guest may have modified the underlying buffer contents
360    /// in the meantime.
361    pub fn try_peek(&mut self) -> Result<Option<PeekedWork<'_>>, Error> {
362        let work = self.core.try_peek_work().map_err(Error::other)?;
363        Ok(work.map(|w| PeekedWork::new(self, w)))
364    }
365
366    /// Waits until a descriptor is available for peeking, without advancing
367    /// the available index. See [`try_peek`](Self::try_peek).
368    ///
369    /// Note that descriptor metadata is captured at peek time, but the guest
370    /// may modify the underlying buffer contents between a peek and a
371    /// subsequent consume or re-peek, so callers must not assume the buffer
372    /// data is stable.
373    pub async fn peek(&mut self) -> Result<PeekedWork<'_>, Error> {
374        let work = loop {
375            if let Some(work) = self.core.try_peek_work().map_err(Error::other)? {
376                break work;
377            }
378            std::future::poll_fn(|cx| self.poll_kick(cx)).await;
379        };
380        Ok(PeekedWork::new(self, work))
381    }
382
383    /// Complete a descriptor previously obtained from this queue.
384    ///
385    /// Writes `bytes_written` to the used ring and delivers an interrupt
386    /// to the guest (unless interrupt suppression is active).
387    ///
388    /// Takes ownership of the work item, ensuring it can only be completed
389    /// once.
390    pub fn complete(&mut self, work: VirtioQueueCallbackWork, bytes_written: u32) {
391        self.complete_prepared(work.into_completion(), bytes_written);
392    }
393
394    /// Completes a descriptor from a lightweight [`QueueCompletion`] token
395    /// previously obtained via [`VirtioQueueCallbackWork::into_completion`].
396    ///
397    /// Equivalent to [`complete`](Self::complete) but does not require holding
398    /// the payload, so callers that buffer completions can store only the
399    /// token.
400    pub fn complete_prepared(&mut self, completion: QueueCompletion, bytes_written: u32) {
401        // The completion token is consumed even if publishing it to the used ring
402        // fails, so release its in-flight capacity before attempting the write.
403        self.core.work_completed(&completion);
404        match self
405            .complete
406            .complete_descriptor(&completion, bytes_written)
407        {
408            Ok(true) => {
409                self.notify_guest.deliver();
410            }
411            Ok(false) => {}
412            Err(err) => {
413                tracelimit::error_ratelimited!(
414                    error = &err as &dyn std::error::Error,
415                    "failed to complete descriptor"
416                );
417            }
418        }
419    }
420
421    fn poll_next_buffer(
422        &mut self,
423        cx: &mut Context<'_>,
424    ) -> Poll<Result<VirtioQueueCallbackWork, Error>> {
425        loop {
426            if let Some(work) = self.try_next()? {
427                return Poll::Ready(Ok(work));
428            }
429            ready!(self.poll_kick(cx));
430        }
431    }
432}
433
434/// Yields each descriptor chain the guest makes available.
435///
436/// After yielding a [`QueueError`] the queue is retired, and the stream parks
437/// forever rather than ending: returning `None` would be ready synchronously on
438/// every poll, so a caller looping over the stream would spin on it. Parking
439/// makes it impossible for any caller to busy-loop, and the worker stays
440/// cancellable.
441impl Stream for VirtioQueue {
442    type Item = Result<VirtioQueueCallbackWork, Error>;
443
444    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
445        Some(ready!(self.get_mut().poll_next_buffer(cx))).into()
446    }
447}
448
449pub(crate) struct VirtioDoorbells {
450    registration: Option<Arc<dyn DoorbellRegistration>>,
451    doorbells: Vec<Box<dyn Send + Sync>>,
452}
453
454impl VirtioDoorbells {
455    pub fn new(registration: Option<Arc<dyn DoorbellRegistration>>) -> Self {
456        Self {
457            registration,
458            doorbells: Vec::new(),
459        }
460    }
461
462    pub fn add(&mut self, address: u64, value: Option<u64>, length: Option<u32>, event: &Event) {
463        if let Some(registration) = &mut self.registration {
464            let doorbell = registration.register_doorbell(address, value, length, event);
465            if let Ok(doorbell) = doorbell {
466                self.doorbells.push(doorbell);
467            }
468        }
469    }
470
471    pub fn clear(&mut self) {
472        self.doorbells.clear();
473    }
474}
475
476#[derive(Copy, Clone, Debug, Default)]
477pub struct DeviceTraitsSharedMemory {
478    pub id: u8,
479    pub size: u64,
480}
481
482#[derive(Clone, Debug)]
483pub struct DeviceTraits {
484    pub device_id: VirtioDeviceType,
485    pub device_features: VirtioDeviceFeatures,
486    pub max_queues: u16,
487    pub device_register_length: u32,
488    pub shared_memory: DeviceTraitsSharedMemory,
489}
490
491impl Default for DeviceTraits {
492    fn default() -> Self {
493        Self {
494            device_id: VirtioDeviceType(0),
495            device_features: Default::default(),
496            max_queues: 0,
497            device_register_length: 0,
498            shared_memory: Default::default(),
499        }
500    }
501}
502
503pub struct QueueResources {
504    pub params: QueueParams,
505    pub notify: Interrupt,
506    pub event: Event,
507    pub guest_memory: GuestMemory,
508}