virtio_drivers/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! VirtIO guest drivers.
//!
//! These drivers can be used by bare-metal code (such as a bootloader or OS kernel) running in a VM
//! to interact with VirtIO devices provided by the VMM (such as QEMU or crosvm).
//!
//! # Usage
//!
//! You must first implement the [`Hal`] trait, to allocate DMA regions and translate between
//! physical addresses (as seen by devices) and virtual addresses (as seen by your program). You can
//! then construct the appropriate transport for the VirtIO device, e.g. for an MMIO device (perhaps
//! discovered from the device tree):
//!
//! ```
//! use core::ptr::NonNull;
//! use virtio_drivers::transport::mmio::{MmioTransport, VirtIOHeader};
//!
//! # fn example(mmio_device_address: usize) {
//! let header = NonNull::new(mmio_device_address as *mut VirtIOHeader).unwrap();
//! let transport = unsafe { MmioTransport::new(header) }.unwrap();
//! # }
//! ```
//!
//! You can then check what kind of VirtIO device it is and construct the appropriate driver:
//!
//! ```
//! # use virtio_drivers::Hal;
//! # #[cfg(feature = "alloc")]
//! use virtio_drivers::{
//!     device::console::VirtIOConsole,
//!     transport::{mmio::MmioTransport, DeviceType, Transport},
//! };

//!
//! # #[cfg(feature = "alloc")]
//! # fn example<HalImpl: Hal>(transport: MmioTransport) {
//! if transport.device_type() == DeviceType::Console {
//!     let mut console = VirtIOConsole::<HalImpl, _>::new(transport).unwrap();
//!     // Send a byte to the console.
//!     console.send(b'H').unwrap();
//! }
//! # }
//! ```

#![cfg_attr(not(test), no_std)]
#![deny(unused_must_use, missing_docs)]
#![allow(clippy::identity_op)]
#![allow(dead_code)]

#[cfg(any(feature = "alloc", test))]
extern crate alloc;

pub mod device;
mod hal;
mod queue;
pub mod transport;
mod volatile;

use core::{
    fmt::{self, Display, Formatter},
    ptr::{self, NonNull},
};

pub use self::hal::{BufferDirection, Hal, PhysAddr};

/// The page size in bytes supported by the library (4 KiB).
pub const PAGE_SIZE: usize = 0x1000;

/// The type returned by driver methods.
pub type Result<T = ()> = core::result::Result<T, Error>;

/// The error type of VirtIO drivers.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Error {
    /// There are not enough descriptors available in the virtqueue, try again later.
    QueueFull,
    /// The device is not ready.
    NotReady,
    /// The device used a different descriptor chain to the one we were expecting.
    WrongToken,
    /// The queue is already in use.
    AlreadyUsed,
    /// Invalid parameter.
    InvalidParam,
    /// Failed to alloc DMA memory.
    DmaError,
    /// I/O Error
    IoError,
    /// The request was not supported by the device.
    Unsupported,
    /// The config space advertised by the device is smaller than the driver expected.
    ConfigSpaceTooSmall,
    /// The device doesn't have any config space, but the driver expects some.
    ConfigSpaceMissing,
    /// Error from the socket device.
    SocketDeviceError(device::socket::SocketError),
}

#[cfg(feature = "alloc")]
impl From<alloc::string::FromUtf8Error> for Error {
    fn from(_value: alloc::string::FromUtf8Error) -> Self {
        Self::IoError
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::QueueFull => write!(f, "Virtqueue is full"),
            Self::NotReady => write!(f, "Device not ready"),
            Self::WrongToken => write!(
                f,
                "Device used a different descriptor chain to the one we were expecting"
            ),
            Self::AlreadyUsed => write!(f, "Virtqueue is already in use"),
            Self::InvalidParam => write!(f, "Invalid parameter"),
            Self::DmaError => write!(f, "Failed to allocate DMA memory"),
            Self::IoError => write!(f, "I/O Error"),
            Self::Unsupported => write!(f, "Request not supported by device"),
            Self::ConfigSpaceTooSmall => write!(
                f,
                "Config space advertised by the device is smaller than expected"
            ),
            Self::ConfigSpaceMissing => {
                write!(
                    f,
                    "The device doesn't have any config space, but the driver expects some"
                )
            }
            Self::SocketDeviceError(e) => write!(f, "Error from the socket device: {e:?}"),
        }
    }
}

impl From<device::socket::SocketError> for Error {
    fn from(e: device::socket::SocketError) -> Self {
        Self::SocketDeviceError(e)
    }
}

/// Align `size` up to a page.
fn align_up(size: usize) -> usize {
    (size + PAGE_SIZE) & !(PAGE_SIZE - 1)
}

/// The number of pages required to store `size` bytes, rounded up to a whole number of pages.
fn pages(size: usize) -> usize {
    (size + PAGE_SIZE - 1) / PAGE_SIZE
}

// TODO: Use NonNull::slice_from_raw_parts once it is stable.
/// Creates a non-null raw slice from a non-null thin pointer and length.
fn nonnull_slice_from_raw_parts<T>(data: NonNull<T>, len: usize) -> NonNull<[T]> {
    NonNull::new(ptr::slice_from_raw_parts_mut(data.as_ptr(), len)).unwrap()
}