1 //! VirtIO guest drivers.
2 //!
3 //! These drivers can be used by bare-metal code (such as a bootloader or OS kernel) running in a VM
4 //! to interact with VirtIO devices provided by the VMM (such as QEMU or crosvm).
5 //!
6 //! # Usage
7 //!
8 //! You must first implement the [`Hal`] trait, to allocate DMA regions and translate between
9 //! physical addresses (as seen by devices) and virtual addresses (as seen by your program). You can
10 //! then construct the appropriate transport for the VirtIO device, e.g. for an MMIO device (perhaps
11 //! discovered from the device tree):
12 //!
13 //! ```
14 //! use core::ptr::NonNull;
15 //! use virtio_drivers::transport::mmio::{MmioTransport, VirtIOHeader};
16 //!
17 //! # fn example(mmio_device_address: usize) {
18 //! let header = NonNull::new(mmio_device_address as *mut VirtIOHeader).unwrap();
19 //! let transport = unsafe { MmioTransport::new(header) }.unwrap();
20 //! # }
21 //! ```
22 //!
23 //! You can then check what kind of VirtIO device it is and construct the appropriate driver:
24 //!
25 //! ```
26 //! # use virtio_drivers::Hal;
27 //! # #[cfg(feature = "alloc")]
28 //! use virtio_drivers::{
29 //!     device::console::VirtIOConsole,
30 //!     transport::{mmio::MmioTransport, DeviceType, Transport},
31 //! };
32 
33 //!
34 //! # #[cfg(feature = "alloc")]
35 //! # fn example<HalImpl: Hal>(transport: MmioTransport) {
36 //! if transport.device_type() == DeviceType::Console {
37 //!     let mut console = VirtIOConsole::<HalImpl, _>::new(transport).unwrap();
38 //!     // Send a byte to the console.
39 //!     console.send(b'H').unwrap();
40 //! }
41 //! # }
42 //! ```
43 
44 #![cfg_attr(not(test), no_std)]
45 #![deny(unused_must_use, missing_docs)]
46 #![allow(clippy::identity_op)]
47 #![allow(dead_code)]
48 
49 #[cfg(any(feature = "alloc", test))]
50 extern crate alloc;
51 
52 pub mod device;
53 mod hal;
54 mod queue;
55 pub mod transport;
56 mod volatile;
57 
58 use core::{
59     fmt::{self, Display, Formatter},
60     ptr::{self, NonNull},
61 };
62 
63 pub use self::hal::{BufferDirection, Hal, PhysAddr};
64 
65 /// The page size in bytes supported by the library (4 KiB).
66 pub const PAGE_SIZE: usize = 0x1000;
67 
68 /// The type returned by driver methods.
69 pub type Result<T = ()> = core::result::Result<T, Error>;
70 
71 /// The error type of VirtIO drivers.
72 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
73 pub enum Error {
74     /// There are not enough descriptors available in the virtqueue, try again later.
75     QueueFull,
76     /// The device is not ready.
77     NotReady,
78     /// The device used a different descriptor chain to the one we were expecting.
79     WrongToken,
80     /// The queue is already in use.
81     AlreadyUsed,
82     /// Invalid parameter.
83     InvalidParam,
84     /// Failed to alloc DMA memory.
85     DmaError,
86     /// I/O Error
87     IoError,
88     /// The request was not supported by the device.
89     Unsupported,
90     /// The config space advertised by the device is smaller than the driver expected.
91     ConfigSpaceTooSmall,
92     /// The device doesn't have any config space, but the driver expects some.
93     ConfigSpaceMissing,
94     /// Error from the socket device.
95     SocketDeviceError(device::socket::SocketError),
96 }
97 
98 #[cfg(feature = "alloc")]
99 impl From<alloc::string::FromUtf8Error> for Error {
from(_value: alloc::string::FromUtf8Error) -> Self100     fn from(_value: alloc::string::FromUtf8Error) -> Self {
101         Self::IoError
102     }
103 }
104 
105 impl Display for Error {
fmt(&self, f: &mut Formatter) -> fmt::Result106     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
107         match self {
108             Self::QueueFull => write!(f, "Virtqueue is full"),
109             Self::NotReady => write!(f, "Device not ready"),
110             Self::WrongToken => write!(
111                 f,
112                 "Device used a different descriptor chain to the one we were expecting"
113             ),
114             Self::AlreadyUsed => write!(f, "Virtqueue is already in use"),
115             Self::InvalidParam => write!(f, "Invalid parameter"),
116             Self::DmaError => write!(f, "Failed to allocate DMA memory"),
117             Self::IoError => write!(f, "I/O Error"),
118             Self::Unsupported => write!(f, "Request not supported by device"),
119             Self::ConfigSpaceTooSmall => write!(
120                 f,
121                 "Config space advertised by the device is smaller than expected"
122             ),
123             Self::ConfigSpaceMissing => {
124                 write!(
125                     f,
126                     "The device doesn't have any config space, but the driver expects some"
127                 )
128             }
129             Self::SocketDeviceError(e) => write!(f, "Error from the socket device: {e:?}"),
130         }
131     }
132 }
133 
134 impl From<device::socket::SocketError> for Error {
from(e: device::socket::SocketError) -> Self135     fn from(e: device::socket::SocketError) -> Self {
136         Self::SocketDeviceError(e)
137     }
138 }
139 
140 /// Align `size` up to a page.
align_up(size: usize) -> usize141 fn align_up(size: usize) -> usize {
142     (size + PAGE_SIZE) & !(PAGE_SIZE - 1)
143 }
144 
145 /// The number of pages required to store `size` bytes, rounded up to a whole number of pages.
pages(size: usize) -> usize146 fn pages(size: usize) -> usize {
147     (size + PAGE_SIZE - 1) / PAGE_SIZE
148 }
149 
150 // TODO: Use NonNull::slice_from_raw_parts once it is stable.
151 /// Creates a non-null raw slice from a non-null thin pointer and length.
nonnull_slice_from_raw_parts<T>(data: NonNull<T>, len: usize) -> NonNull<[T]>152 fn nonnull_slice_from_raw_parts<T>(data: NonNull<T>, len: usize) -> NonNull<[T]> {
153     NonNull::new(ptr::slice_from_raw_parts_mut(data.as_ptr(), len)).unwrap()
154 }
155