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