diff --git a/uefi-raw/src/protocol/nvme.rs b/uefi-raw/src/protocol/nvme.rs index 07055469f..856cfbd66 100644 --- a/uefi-raw/src/protocol/nvme.rs +++ b/uefi-raw/src/protocol/nvme.rs @@ -5,16 +5,63 @@ use crate::Status; use core::ffi::c_void; use uguid::{guid, Guid}; -#[derive(Debug)] +bitflags::bitflags! { + /// In an NVMe command, the `flags` field specifies which cdw (command specific word) + /// contains a value. + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct NvmExpressCommandCdwValidity: u8 { + const CDW_2 = 0x01; + const CDW_3 = 0x02; + const CDW_10 = 0x04; + const CDW_11 = 0x08; + const CDW_12 = 0x10; + const CDW_13 = 0x20; + const CDW_14 = 0x40; + const CDW_15 = 0x80; + } + + /// Represents the `EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_*` defines from the UEFI specification. + /// + /// # UEFI Specification Description + /// Tells if the interface is for physical NVM Express controllers or logical NVM Express controllers. + /// + /// Drivers for non-RAID NVM Express controllers will set both the `PHYSICAL` and the `LOGICAL` bit. + /// + /// Drivers for RAID controllers that allow access to the underlying physical controllers will produces + /// two protocol instances. One where the `LOGICAL` bit is set (representing the logical RAID volume), + /// and one where the `PHYSICAL` bit is set, which can be used to access the underlying NVMe controllers. + /// + /// Drivers for RAID controllers that do not allow access of the underlying NVMe controllers will only + /// produce one protocol instance for the logical RAID volume with the `LOGICAL` bit set. + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct NvmExpressPassThruAttributes: u32 { + /// If this bit is set, the interface is for directly addressable namespaces. + const PHYSICAL = 0x0001; + + /// If this bit is set, the interface is for a single logical namespace comprising multiple namespaces. + const LOGICAL = 0x0002; + + /// If this bit is set, the interface supports both blocking and non-blocking I/O. + /// - All interfaces must support blocking I/O, but this bit indicates that non-blocking I/O is also supported. + const NONBLOCKIO = 0x0004; + + /// If this bit is set, the interface supports the NVM Express command set. + const CMD_SET_NVM = 0x0008; + } +} + +#[derive(Clone, Debug)] #[repr(C)] pub struct NvmExpressPassThruMode { - pub attributes: u32, + pub attributes: NvmExpressPassThruAttributes, pub io_align: u32, pub nvme_version: u32, } /// This structure maps to the NVM Express specification Submission Queue Entry -#[derive(Debug)] +#[derive(Debug, Default)] #[repr(C)] pub struct NvmExpressCommand { pub cdw0: u32, @@ -30,8 +77,20 @@ pub struct NvmExpressCommand { pub cdw15: u32, } +newtype_enum! { + /// Type of queues an NVMe command can be placed into + /// (Which queue a command should be placed into depends on the command) + #[derive(Default)] + pub enum NvmExpressQueueType: u8 => { + /// Admin Submission Queue + ADMIN = 0, + /// 1) I/O Submission Queue + IO = 1, + } +} + /// This structure maps to the NVM Express specification Completion Queue Entry -#[derive(Debug)] +#[derive(Debug, Default)] #[repr(C)] pub struct NvmExpressCompletion { pub dw0: u32, @@ -48,7 +107,7 @@ pub struct NvmExpressPassThruCommandPacket { pub transfer_length: u32, pub meta_data_buffer: *mut c_void, pub meta_data_length: u32, - pub queue_type: u8, + pub queue_type: NvmExpressQueueType, pub nvme_cmd: *const NvmExpressCommand, pub nvme_completion: *mut NvmExpressCompletion, } @@ -58,7 +117,7 @@ pub struct NvmExpressPassThruCommandPacket { pub struct NvmExpressPassThruProtocol { pub mode: *const NvmExpressPassThruMode, pub pass_thru: unsafe extern "efiapi" fn( - this: *const Self, + this: *mut Self, namespace_id: u32, packet: *mut NvmExpressPassThruCommandPacket, event: *mut c_void, @@ -68,7 +127,7 @@ pub struct NvmExpressPassThruProtocol { pub build_device_path: unsafe extern "efiapi" fn( this: *const Self, namespace_id: u32, - device_path: *mut *mut DevicePathProtocol, + device_path: *mut *const DevicePathProtocol, ) -> Status, pub get_namespace: unsafe extern "efiapi" fn( this: *const Self, diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index 2c33e22bd..e666dc93a 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -26,6 +26,7 @@ pub fn test() { string::test(); misc::test(); scsi::test(); + nvme::test(); #[cfg(any( target_arch = "x86", @@ -72,6 +73,7 @@ mod loaded_image; mod media; mod misc; mod network; +mod nvme; mod pi; mod rng; mod scsi; diff --git a/uefi-test-runner/src/proto/nvme/mod.rs b/uefi-test-runner/src/proto/nvme/mod.rs new file mode 100644 index 000000000..1c28fa962 --- /dev/null +++ b/uefi-test-runner/src/proto/nvme/mod.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +mod pass_thru; + +pub fn test() { + pass_thru::test(); +} diff --git a/uefi-test-runner/src/proto/nvme/pass_thru.rs b/uefi-test-runner/src/proto/nvme/pass_thru.rs new file mode 100644 index 000000000..5597e63b9 --- /dev/null +++ b/uefi-test-runner/src/proto/nvme/pass_thru.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use core::time::Duration; +use uefi::boot; +use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly}; +use uefi::proto::device_path::DevicePath; +use uefi::proto::media::block::BlockIO; +use uefi::proto::nvme::pass_thru::NvmePassThru; +use uefi::proto::nvme::{NvmeQueueType, NvmeRequestBuilder}; + +pub fn test() { + info!("Running NVMe PassThru tests"); + + assert!(has_nvme_drive()); +} + +fn has_nvme_drive() -> bool { + let block_io_handles = boot::find_handles::().unwrap(); + for handle in block_io_handles { + let Ok(device_path) = boot::open_protocol_exclusive::(handle) else { + continue; + }; + let mut device_path = &*device_path; + + let Ok(nvme_pt_handle) = boot::locate_device_path::(&mut device_path) else { + continue; + }; + let nvme_pt = boot::open_protocol_exclusive::(nvme_pt_handle).unwrap(); + let device_path_str = device_path + .to_string(DisplayOnly(true), AllowShortcuts(false)) + .unwrap(); + info!("- Successfully opened NVMe: {}", device_path_str); + let mut nvme_ctrl = nvme_pt.controller(); + + let request = NvmeRequestBuilder::new(nvme_pt.io_align(), 0x06, NvmeQueueType::ADMIN) + .with_timeout(Duration::from_millis(500)) + .with_cdw10(1) // we want info about controller + .with_transfer_buffer(4096) + .unwrap() + .build(); + let result = nvme_ctrl.execute_command(request); + if let Ok(result) = result { + let bfr = result.transfer_buffer().unwrap(); + let serial = core::str::from_utf8(&bfr[4..24]).unwrap().trim(); + info!("Found NVMe with serial: '{}'", serial); + if serial == "uefi-rsNvmePassThru" { + return true; + } + } + } + + false +} diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index b7ed4ddf4..7a4600a47 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -9,6 +9,7 @@ - Added `proto::device_path::DevicePath::append_path()`. - Added `proto::device_path::DevicePath::append_node()`. - Added `proto::scsi::pass_thru::ExtScsiPassThru`. +- Added `proto::nvme::pass_thru::NvmePassThru`. ## Changed - **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index b9ce30081..2bcc58ffe 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -18,6 +18,8 @@ pub mod loaded_image; pub mod media; pub mod misc; pub mod network; +#[cfg(feature = "alloc")] +pub mod nvme; pub mod pi; pub mod rng; #[cfg(feature = "alloc")] diff --git a/uefi/src/proto/nvme/mod.rs b/uefi/src/proto/nvme/mod.rs new file mode 100644 index 000000000..f84773129 --- /dev/null +++ b/uefi/src/proto/nvme/mod.rs @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! NVM Express Protocols. + +use crate::mem::{AlignedBuffer, AlignmentError}; +use core::alloc::LayoutError; +use core::marker::PhantomData; +use core::ptr; +use core::time::Duration; +use uefi_raw::protocol::nvme::{ + NvmExpressCommand, NvmExpressCommandCdwValidity, NvmExpressPassThruCommandPacket, +}; + +pub mod pass_thru; + +/// Represents the completion status of an NVMe command. +/// +/// This structure contains various fields related to the status and results +/// of an executed command, including fields for error codes, specific command IDs, +/// and general state of the NVMe device. +pub type NvmeCompletion = uefi_raw::protocol::nvme::NvmExpressCompletion; + +/// Type of queues an NVMe command can be placed into +/// (Which queue a command should be placed into depends on the command) +pub type NvmeQueueType = uefi_raw::protocol::nvme::NvmExpressQueueType; + +/// Represents a request for executing an NVMe command. +/// +/// This structure encapsulates the command to be sent to the NVMe device, along with +/// optional data transfer and metadata buffers. It ensures proper alignment and safety +/// during interactions with the NVMe protocol. +/// +/// # Lifetime +/// `'buffers`: Makes sure the io-buffers bound to the built request +/// stay alive until the response was interpreted. +#[derive(Debug)] +pub struct NvmeRequest<'buffers> { + io_align: u32, + cmd: NvmExpressCommand, + packet: NvmExpressPassThruCommandPacket, + transfer_buffer: Option, + meta_data_buffer: Option, + _phantom: PhantomData<&'buffers u8>, +} + +// NVMe commands consist of a bunch of CDWs (command data words) and a flags bitmask, where +// one bit per cdw is set when it should be read. Our request builder has one setter method +// with_cdwX() for every cdw, which also automatically sets the corresponding flag-bit. +// This macro generates one such setter method. +macro_rules! define_nvme_command_builder_with_cdw { + ($fnname:ident: $fieldname:ident => $flagmask:expr) => { + /// Set the $fieldname parameter on the constructed nvme command. + /// This also automatically flags the parameter as valid in the command's `flags` field. + /// + /// # About NVMe commands + /// NVMe commands are constructed of a bunch of numbered CDWs (command data words) and a `flags` field. + /// The `flags´ field tells the NVMe controller which CDWs was set and whether it should respect + /// the corresponding CDWs value. + /// CDWs have no fixed interpretation - the interpretation depends on the command to execute. + /// Which CDWs have to be supplied (and enabled in the `flags` field) depends on the command that + /// should be sent to and executed by the controller. + /// See: + #[must_use] + pub const fn $fnname(mut self, $fieldname: u32) -> Self { + self.req.cmd.$fieldname = $fieldname; + self.req.cmd.flags |= $flagmask.bits(); + self + } + }; +} + +/// Builder for constructing an NVMe request. +/// +/// This structure provides convenient methods for configuring NVMe commands, +/// including parameters like command-specific data words (CDWs) +/// and optional buffers for transfer and metadata operations. +/// +/// It ensures safe and ergonomic setup of NVMe requests. +/// +/// # Lifetime +/// `'buffers`: Makes sure the io-buffers bound to the built request +/// stay alive until the response was interpreted. +#[derive(Debug)] +pub struct NvmeRequestBuilder<'buffers> { + req: NvmeRequest<'buffers>, +} +impl<'buffers> NvmeRequestBuilder<'buffers> { + /// Creates a new builder for configuring an NVMe request. + /// + /// # Parameters + /// - `io_align`: Memory alignment requirements for buffers. + /// - `opcode`: The opcode for the NVMe command. + /// - `queue_type`: Specifies the type of queue the command should be placed into. + /// + /// # Returns + /// An instance of [`NvmeRequestBuilder`] for further configuration. + #[must_use] + pub fn new(io_align: u32, opcode: u8, queue_type: NvmeQueueType) -> Self { + Self { + req: NvmeRequest { + io_align, + cmd: NvmExpressCommand { + cdw0: opcode as u32, + ..Default::default() + }, + packet: NvmExpressPassThruCommandPacket { + command_timeout: 0, + transfer_buffer: ptr::null_mut(), + transfer_length: 0, + meta_data_buffer: ptr::null_mut(), + meta_data_length: 0, + queue_type, + nvme_cmd: ptr::null(), // filled during execution + nvme_completion: ptr::null_mut(), // filled during execution + }, + transfer_buffer: None, + meta_data_buffer: None, + _phantom: PhantomData, + }, + } + } + + /// Configure the given timeout for this request. + #[must_use] + pub const fn with_timeout(mut self, timeout: Duration) -> Self { + self.req.packet.command_timeout = (timeout.as_nanos() / 100) as u64; + self + } + + // define the with_cdwX() builder methods + define_nvme_command_builder_with_cdw!(with_cdw2: cdw2 => NvmExpressCommandCdwValidity::CDW_2); + define_nvme_command_builder_with_cdw!(with_cdw3: cdw3 => NvmExpressCommandCdwValidity::CDW_3); + define_nvme_command_builder_with_cdw!(with_cdw10: cdw10 => NvmExpressCommandCdwValidity::CDW_10); + define_nvme_command_builder_with_cdw!(with_cdw11: cdw11 => NvmExpressCommandCdwValidity::CDW_11); + define_nvme_command_builder_with_cdw!(with_cdw12: cdw12 => NvmExpressCommandCdwValidity::CDW_12); + define_nvme_command_builder_with_cdw!(with_cdw13: cdw13 => NvmExpressCommandCdwValidity::CDW_13); + define_nvme_command_builder_with_cdw!(with_cdw14: cdw14 => NvmExpressCommandCdwValidity::CDW_14); + define_nvme_command_builder_with_cdw!(with_cdw15: cdw15 => NvmExpressCommandCdwValidity::CDW_15); + + // # TRANSFER BUFFER + // ######################################################################################## + + /// Uses a user-supplied buffer for reading data from the device. + /// + /// # Parameters + /// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store data read from the device. + /// + /// # Returns + /// `Result` indicating success or an alignment issue with the provided buffer. + /// + /// # Description + /// This method checks the alignment of the buffer against the protocol's requirements and assigns it to + /// the `transfer_buffer` of the underlying [`NvmeRequest`]. + pub fn use_transfer_buffer( + mut self, + bfr: &'buffers mut AlignedBuffer, + ) -> Result { + // check alignment of externally supplied buffer + bfr.check_alignment(self.req.io_align as usize)?; + self.req.transfer_buffer = None; + self.req.packet.transfer_buffer = bfr.ptr_mut().cast(); + self.req.packet.transfer_length = bfr.size() as u32; + Ok(self) + } + + /// Adds a newly allocated transfer buffer to the built NVMe request. + /// + /// # Parameters + /// - `len`: The size of the buffer (in bytes) to allocate for receiving data. + /// + /// # Returns + /// `Result` indicating success or a memory allocation error. + pub fn with_transfer_buffer(mut self, len: usize) -> Result { + let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?; + self.req.packet.transfer_buffer = bfr.ptr_mut().cast(); + self.req.packet.transfer_length = bfr.size() as u32; + self.req.transfer_buffer = Some(bfr); + Ok(self) + } + + // # METADATA BUFFER + // ######################################################################################## + + /// Uses a user-supplied metadata buffer. + /// + /// # Parameters + /// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store metadata. + /// + /// # Returns + /// `Result` indicating success or an alignment issue with the provided buffer. + /// + /// # Description + /// This method checks the alignment of the buffer against the protocol's requirements and assigns it to + /// the `meta_data_buffer` of the underlying [`NvmeRequest`]. + pub fn use_metadata_buffer( + mut self, + bfr: &'buffers mut AlignedBuffer, + ) -> Result { + // check alignment of externally supplied buffer + bfr.check_alignment(self.req.io_align as usize)?; + self.req.meta_data_buffer = None; + self.req.packet.meta_data_buffer = bfr.ptr_mut().cast(); + self.req.packet.meta_data_length = bfr.size() as u32; + Ok(self) + } + + /// Adds a newly allocated metadata buffer to the built NVMe request. + /// + /// # Parameters + /// - `len`: The size of the buffer (in bytes) to allocate for storing metadata. + /// + /// # Returns + /// `Result` indicating success or a memory allocation error. + pub fn with_metadata_buffer(mut self, len: usize) -> Result { + let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?; + self.req.packet.meta_data_buffer = bfr.ptr_mut().cast(); + self.req.packet.meta_data_length = bfr.size() as u32; + self.req.meta_data_buffer = Some(bfr); + Ok(self) + } + + /// Build the final [`NvmeRequest`]. + /// + /// # Returns + /// A fully-configured [`NvmeRequest`] ready for execution. + #[must_use] + pub fn build(self) -> NvmeRequest<'buffers> { + self.req + } +} + +/// Represents the response from executing an NVMe command. +/// +/// This structure encapsulates the original request, as well as the command's completion status. +/// +/// # Lifetime +/// `'buffers`: Makes sure the io-buffers bound to the built request +/// stay alive until the response was interpreted. +#[derive(Debug)] +pub struct NvmeResponse<'buffers> { + req: NvmeRequest<'buffers>, + completion: NvmeCompletion, +} +impl<'buffers> NvmeResponse<'buffers> { + /// Returns the buffer containing transferred data from the device (if any). + /// + /// # Returns + /// `Option<&[u8]>`: A slice of the transfer buffer, or `None` if the request was started without. + #[must_use] + pub fn transfer_buffer(&self) -> Option<&'buffers [u8]> { + if self.req.packet.transfer_buffer.is_null() { + return None; + } + unsafe { + Some(core::slice::from_raw_parts( + self.req.packet.transfer_buffer.cast(), + self.req.packet.transfer_length as usize, + )) + } + } + + /// Returns the buffer containing metadata data from the device (if any). + /// + /// # Returns + /// `Option<&[u8]>`: A slice of the metadata buffer, or `None` if the request was started without. + #[must_use] + pub fn metadata_buffer(&self) -> Option<&'buffers [u8]> { + if self.req.packet.meta_data_buffer.is_null() { + return None; + } + unsafe { + Some(core::slice::from_raw_parts( + self.req.packet.meta_data_buffer.cast(), + self.req.packet.meta_data_length as usize, + )) + } + } + + /// Provides access to the completion structure of the NVMe command. + /// + /// # Returns + /// A reference to the [`NvmeCompletion`] structure containing the status and results of the command. + #[must_use] + pub const fn completion(&self) -> &NvmeCompletion { + &self.completion + } +} diff --git a/uefi/src/proto/nvme/pass_thru.rs b/uefi/src/proto/nvme/pass_thru.rs new file mode 100644 index 000000000..668b70734 --- /dev/null +++ b/uefi/src/proto/nvme/pass_thru.rs @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! NVM Express Pass Thru Protocol. + +use super::{NvmeRequest, NvmeResponse}; +use crate::mem::{AlignedBuffer, PoolAllocation}; +use crate::proto::device_path::PoolDevicePathNode; +use crate::StatusExt; +use core::alloc::LayoutError; +use core::ptr::{self, NonNull}; +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::device_path::DevicePathProtocol; +use uefi_raw::protocol::nvme::{NvmExpressCompletion, NvmExpressPassThruProtocol}; +use uefi_raw::Status; + +/// Nvme Pass Thru Protocol Mode structure. +/// +/// This contains information regarding the specific capabilities and requirements +/// of the NVMe controller, such as buffer alignment constraints. +pub type NvmePassThruMode = uefi_raw::protocol::nvme::NvmExpressPassThruMode; + +/// Identifier for an NVMe namespace. +/// +/// Namespace IDs are used to target specific namespaces on an NVMe device for commands. +pub type NvmeNamespaceId = u32; + +/// NVMe Pass Thru Protocol. +/// +/// One protocol instance corresponds to one NVMe controller +/// (which, most of the time, corresponds to one SSD). +/// +/// This API offers a safe and convenient, yet still low-level interface to NVMe devices. +/// It is designed as a foundational layer, leaving higher-level abstractions responsible for implementing +/// richer storage semantics, device-specific commands, and advanced use cases. +/// +/// # UEFI Spec Description +/// The `EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL` provides essential functionality for interacting +/// with NVMe controllers and namespaces. It allows sending NVMe commands to either the +/// controller itself or specific namespaces within the controller. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(NvmExpressPassThruProtocol::GUID)] +pub struct NvmePassThru(NvmExpressPassThruProtocol); + +impl NvmePassThru { + /// Retrieves the mode of the NVMe Pass Thru protocol. + /// + /// # Returns + /// An instance of [`NvmePassThruMode`] describing the NVMe controller's capabilities. + #[must_use] + pub fn mode(&self) -> NvmePassThruMode { + unsafe { (*self.0.mode).clone() } + } + + /// Retrieves the alignment requirements for I/O buffers. + /// + /// # Returns + /// An alignment value (in bytes) that all I/O buffers must adhere to for successful operation. + #[must_use] + pub fn io_align(&self) -> u32 { + self.mode().io_align + } + + /// Allocates an I/O buffer with the necessary alignment for this NVMe Controller. + /// + /// You can alternatively do this yourself using the [`AlignedBuffer`] helper directly. + /// The `nvme` api will validate that your buffers have the correct alignment and error + /// if they don't. + /// + /// # Parameters + /// - `len`: The size (in bytes) of the buffer to allocate. + /// + /// # Returns + /// [`AlignedBuffer`] containing the allocated memory. + /// + /// # Errors + /// This method can fail due to alignment or memory allocation issues. + pub fn alloc_io_buffer(&self, len: usize) -> Result { + AlignedBuffer::from_size_align(len, self.io_align() as usize) + } + + /// Iterate over all valid namespaces on this NVMe controller. + /// + /// This ignores the 0-namespaces, which corresponds to the controller itself. + /// The iterator yields [`NvmeNamespace`] instances representing individual namespaces. + /// + /// # Returns + /// A [`NvmeNamespaceIterator`] for iterating through the namespaces. + #[must_use] + pub const fn iter_namespaces(&self) -> NvmeNamespaceIterator<'_> { + NvmeNamespaceIterator { + proto: &self.0, + prev: 0xFFFFFFFF, + } + } + + /// Get the controller namespace (id = 0). + /// This can be used to send ADMIN commands. + /// + /// # Returns + /// A [`NvmeNamespaceIterator`] for iterating through the namespaces. + #[must_use] + pub const fn controller(&self) -> NvmeNamespace<'_> { + NvmeNamespace { + proto: &self.0, + namespace_id: 0, + } + } +} + +/// Represents one namespace on an NVMe controller. +/// +/// A namespace is a shard of storage that the controller can be partitioned into. +/// Typically, consumer devices only have a single namespace where all the data resides (id 1). +#[derive(Debug)] +pub struct NvmeNamespace<'a> { + proto: &'a NvmExpressPassThruProtocol, + namespace_id: NvmeNamespaceId, +} + +impl NvmeNamespace<'_> { + fn proto_mut(&mut self) -> *mut NvmExpressPassThruProtocol { + ptr::from_ref(self.proto).cast_mut() + } + + /// Retrieves the namespace identifier (NSID) associated with this NVMe namespace. + #[must_use] + pub const fn namespace_id(&self) -> NvmeNamespaceId { + self.namespace_id + } + + /// Get the final device path node for this namespace. + /// + /// For a full [`crate::proto::device_path::DevicePath`] pointing to this namespace on the + /// corresponding NVMe controller. + pub fn path_node(&self) -> crate::Result { + unsafe { + let mut path_ptr: *const DevicePathProtocol = ptr::null(); + (self.proto.build_device_path)(self.proto, self.namespace_id, &mut path_ptr) + .to_result()?; + NonNull::new(path_ptr.cast_mut()) + .map(|p| PoolDevicePathNode(PoolAllocation::new(p.cast()))) + .ok_or(Status::OUT_OF_RESOURCES.into()) + } + } + + /// Sends an NVM Express command to this namespace (Namespace ID ≥ 1). + /// + /// # Parameters + /// - `req`: The [`NvmeRequest`] containing the command and associated data to send to the namespace. + /// + /// # Returns + /// - [`NvmeResponse`] containing the results of the operation, such as data and status. + /// + /// # Errors + /// - [`Status::BAD_BUFFER_SIZE`] The NVM Express Command Packet was not executed. The number + /// of bytes that could be transferred is returned in `TransferLength`. + /// - [`Status::NOT_READY`] The NVM Express Command Packet could not be sent because the controller + /// is not ready. The caller may retry later. + /// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to send the NVM Express + /// Command Packet. Additional status information is available in `NvmeCompletion`. + /// - [`Status::INVALID_PARAMETER`] The Namespace ID or the contents of the Command Packet are invalid. + /// The NVM Express Command Packet was not sent, and no additional status information is available. + /// - [`Status::UNSUPPORTED`] The command described by the NVM Express Command Packet is not supported + /// by the NVM Express controller. The Command Packet was not sent, and no additional status + /// information is available. + /// - [`Status::TIMEOUT`] A timeout occurred while executing the NVM Express Command Packet. + /// Additional status information is available in `NvmeCompletion`. + pub fn execute_command<'req>( + &mut self, + mut req: NvmeRequest<'req>, + ) -> crate::Result> { + let mut completion = NvmExpressCompletion::default(); + // prepare cmd packet + req.cmd.nsid = self.namespace_id; + req.packet.nvme_cmd = &req.cmd; + req.packet.nvme_completion = &mut completion; + unsafe { + (self.proto.pass_thru)( + self.proto_mut(), + self.namespace_id, + &mut req.packet, + ptr::null_mut(), + ) + .to_result_with_val(|| NvmeResponse { req, completion }) + } + } +} + +/// An iterator over the namespaces of an NVMe controller. +/// +/// The iterator yields [`NvmeNamespace`] instances, each representing one namespace +/// on the NVMe controller. +#[derive(Debug)] +pub struct NvmeNamespaceIterator<'a> { + proto: &'a NvmExpressPassThruProtocol, + prev: NvmeNamespaceId, +} + +impl<'a> Iterator for NvmeNamespaceIterator<'a> { + type Item = NvmeNamespace<'a>; + + fn next(&mut self) -> Option { + let result = unsafe { (self.proto.get_next_namespace)(self.proto, &mut self.prev) }; + match result { + Status::SUCCESS => Some(NvmeNamespace { + proto: self.proto, + namespace_id: self.prev, + }), + Status::NOT_FOUND => None, + _ => panic!("Must not happen according to spec!"), + } + } +} diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index d233dd35b..292e8df06 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -489,6 +489,16 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { cmd.arg("-device"); // attach disk to SCSI controller cmd.arg("scsi-hd,drive=scsidisk0,vendor=uefi-rs,product=ExtScsiPassThru"); + // Fourth (NVMe) disk for NvmePassThru tests + let nvme_test_disk = tmp_dir.join("test_disk3.empty.img"); + std::fs::File::create(&nvme_test_disk)?.set_len(1024 * 1024 * 10)?; + cmd.arg("-drive"); + let mut drive_arg = OsString::from("if=none,id=nvmedisk0,format=raw,file="); + drive_arg.push(nvme_test_disk.clone()); + cmd.arg(drive_arg); + cmd.arg("-device"); + cmd.arg("nvme,drive=nvmedisk0,serial=uefi-rsNvmePassThru"); + let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?; let serial_pipe = Pipe::new(tmp_dir, "serial")?;