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()
}