Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 53dd8b3

Browse files
authoredApr 12, 2025··
Merge pull request #1594 from seijikun/mr-nvmept
uefi: Add safe protocol wrapper for EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL
2 parents debe2c9 + ff49771 commit 53dd8b3

File tree

9 files changed

+642
-7
lines changed

9 files changed

+642
-7
lines changed
 

‎uefi-raw/src/protocol/nvme.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,63 @@ use crate::Status;
55
use core::ffi::c_void;
66
use uguid::{guid, Guid};
77

8-
#[derive(Debug)]
8+
bitflags::bitflags! {
9+
/// In an NVMe command, the `flags` field specifies which cdw (command specific word)
10+
/// contains a value.
11+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
12+
#[repr(transparent)]
13+
pub struct NvmExpressCommandCdwValidity: u8 {
14+
const CDW_2 = 0x01;
15+
const CDW_3 = 0x02;
16+
const CDW_10 = 0x04;
17+
const CDW_11 = 0x08;
18+
const CDW_12 = 0x10;
19+
const CDW_13 = 0x20;
20+
const CDW_14 = 0x40;
21+
const CDW_15 = 0x80;
22+
}
23+
24+
/// Represents the `EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_*` defines from the UEFI specification.
25+
///
26+
/// # UEFI Specification Description
27+
/// Tells if the interface is for physical NVM Express controllers or logical NVM Express controllers.
28+
///
29+
/// Drivers for non-RAID NVM Express controllers will set both the `PHYSICAL` and the `LOGICAL` bit.
30+
///
31+
/// Drivers for RAID controllers that allow access to the underlying physical controllers will produces
32+
/// two protocol instances. One where the `LOGICAL` bit is set (representing the logical RAID volume),
33+
/// and one where the `PHYSICAL` bit is set, which can be used to access the underlying NVMe controllers.
34+
///
35+
/// Drivers for RAID controllers that do not allow access of the underlying NVMe controllers will only
36+
/// produce one protocol instance for the logical RAID volume with the `LOGICAL` bit set.
37+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
38+
#[repr(transparent)]
39+
pub struct NvmExpressPassThruAttributes: u32 {
40+
/// If this bit is set, the interface is for directly addressable namespaces.
41+
const PHYSICAL = 0x0001;
42+
43+
/// If this bit is set, the interface is for a single logical namespace comprising multiple namespaces.
44+
const LOGICAL = 0x0002;
45+
46+
/// If this bit is set, the interface supports both blocking and non-blocking I/O.
47+
/// - All interfaces must support blocking I/O, but this bit indicates that non-blocking I/O is also supported.
48+
const NONBLOCKIO = 0x0004;
49+
50+
/// If this bit is set, the interface supports the NVM Express command set.
51+
const CMD_SET_NVM = 0x0008;
52+
}
53+
}
54+
55+
#[derive(Clone, Debug)]
956
#[repr(C)]
1057
pub struct NvmExpressPassThruMode {
11-
pub attributes: u32,
58+
pub attributes: NvmExpressPassThruAttributes,
1259
pub io_align: u32,
1360
pub nvme_version: u32,
1461
}
1562

1663
/// This structure maps to the NVM Express specification Submission Queue Entry
17-
#[derive(Debug)]
64+
#[derive(Debug, Default)]
1865
#[repr(C)]
1966
pub struct NvmExpressCommand {
2067
pub cdw0: u32,
@@ -30,8 +77,20 @@ pub struct NvmExpressCommand {
3077
pub cdw15: u32,
3178
}
3279

80+
newtype_enum! {
81+
/// Type of queues an NVMe command can be placed into
82+
/// (Which queue a command should be placed into depends on the command)
83+
#[derive(Default)]
84+
pub enum NvmExpressQueueType: u8 => {
85+
/// Admin Submission Queue
86+
ADMIN = 0,
87+
/// 1) I/O Submission Queue
88+
IO = 1,
89+
}
90+
}
91+
3392
/// This structure maps to the NVM Express specification Completion Queue Entry
34-
#[derive(Debug)]
93+
#[derive(Debug, Default)]
3594
#[repr(C)]
3695
pub struct NvmExpressCompletion {
3796
pub dw0: u32,
@@ -48,7 +107,7 @@ pub struct NvmExpressPassThruCommandPacket {
48107
pub transfer_length: u32,
49108
pub meta_data_buffer: *mut c_void,
50109
pub meta_data_length: u32,
51-
pub queue_type: u8,
110+
pub queue_type: NvmExpressQueueType,
52111
pub nvme_cmd: *const NvmExpressCommand,
53112
pub nvme_completion: *mut NvmExpressCompletion,
54113
}
@@ -58,7 +117,7 @@ pub struct NvmExpressPassThruCommandPacket {
58117
pub struct NvmExpressPassThruProtocol {
59118
pub mode: *const NvmExpressPassThruMode,
60119
pub pass_thru: unsafe extern "efiapi" fn(
61-
this: *const Self,
120+
this: *mut Self,
62121
namespace_id: u32,
63122
packet: *mut NvmExpressPassThruCommandPacket,
64123
event: *mut c_void,
@@ -68,7 +127,7 @@ pub struct NvmExpressPassThruProtocol {
68127
pub build_device_path: unsafe extern "efiapi" fn(
69128
this: *const Self,
70129
namespace_id: u32,
71-
device_path: *mut *mut DevicePathProtocol,
130+
device_path: *mut *const DevicePathProtocol,
72131
) -> Status,
73132
pub get_namespace: unsafe extern "efiapi" fn(
74133
this: *const Self,

‎uefi-test-runner/src/proto/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub fn test() {
2626
string::test();
2727
misc::test();
2828
scsi::test();
29+
nvme::test();
2930

3031
#[cfg(any(
3132
target_arch = "x86",
@@ -72,6 +73,7 @@ mod loaded_image;
7273
mod media;
7374
mod misc;
7475
mod network;
76+
mod nvme;
7577
mod pi;
7678
mod rng;
7779
mod scsi;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
mod pass_thru;
4+
5+
pub fn test() {
6+
pass_thru::test();
7+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
use core::time::Duration;
4+
use uefi::boot;
5+
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
6+
use uefi::proto::device_path::DevicePath;
7+
use uefi::proto::media::block::BlockIO;
8+
use uefi::proto::nvme::pass_thru::NvmePassThru;
9+
use uefi::proto::nvme::{NvmeQueueType, NvmeRequestBuilder};
10+
11+
pub fn test() {
12+
info!("Running NVMe PassThru tests");
13+
14+
assert!(has_nvme_drive());
15+
}
16+
17+
fn has_nvme_drive() -> bool {
18+
let block_io_handles = boot::find_handles::<BlockIO>().unwrap();
19+
for handle in block_io_handles {
20+
let Ok(device_path) = boot::open_protocol_exclusive::<DevicePath>(handle) else {
21+
continue;
22+
};
23+
let mut device_path = &*device_path;
24+
25+
let Ok(nvme_pt_handle) = boot::locate_device_path::<NvmePassThru>(&mut device_path) else {
26+
continue;
27+
};
28+
let nvme_pt = boot::open_protocol_exclusive::<NvmePassThru>(nvme_pt_handle).unwrap();
29+
let device_path_str = device_path
30+
.to_string(DisplayOnly(true), AllowShortcuts(false))
31+
.unwrap();
32+
info!("- Successfully opened NVMe: {}", device_path_str);
33+
let mut nvme_ctrl = nvme_pt.controller();
34+
35+
let request = NvmeRequestBuilder::new(nvme_pt.io_align(), 0x06, NvmeQueueType::ADMIN)
36+
.with_timeout(Duration::from_millis(500))
37+
.with_cdw10(1) // we want info about controller
38+
.with_transfer_buffer(4096)
39+
.unwrap()
40+
.build();
41+
let result = nvme_ctrl.execute_command(request);
42+
if let Ok(result) = result {
43+
let bfr = result.transfer_buffer().unwrap();
44+
let serial = core::str::from_utf8(&bfr[4..24]).unwrap().trim();
45+
info!("Found NVMe with serial: '{}'", serial);
46+
if serial == "uefi-rsNvmePassThru" {
47+
return true;
48+
}
49+
}
50+
}
51+
52+
false
53+
}

‎uefi/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added `proto::device_path::DevicePath::append_path()`.
1010
- Added `proto::device_path::DevicePath::append_node()`.
1111
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.
12+
- Added `proto::nvme::pass_thru::NvmePassThru`.
1213

1314
## Changed
1415
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no

‎uefi/src/proto/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub mod loaded_image;
1818
pub mod media;
1919
pub mod misc;
2020
pub mod network;
21+
#[cfg(feature = "alloc")]
22+
pub mod nvme;
2123
pub mod pi;
2224
pub mod rng;
2325
#[cfg(feature = "alloc")]

‎uefi/src/proto/nvme/mod.rs

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! NVM Express Protocols.
4+
5+
use crate::mem::{AlignedBuffer, AlignmentError};
6+
use core::alloc::LayoutError;
7+
use core::marker::PhantomData;
8+
use core::ptr;
9+
use core::time::Duration;
10+
use uefi_raw::protocol::nvme::{
11+
NvmExpressCommand, NvmExpressCommandCdwValidity, NvmExpressPassThruCommandPacket,
12+
};
13+
14+
pub mod pass_thru;
15+
16+
/// Represents the completion status of an NVMe command.
17+
///
18+
/// This structure contains various fields related to the status and results
19+
/// of an executed command, including fields for error codes, specific command IDs,
20+
/// and general state of the NVMe device.
21+
pub type NvmeCompletion = uefi_raw::protocol::nvme::NvmExpressCompletion;
22+
23+
/// Type of queues an NVMe command can be placed into
24+
/// (Which queue a command should be placed into depends on the command)
25+
pub type NvmeQueueType = uefi_raw::protocol::nvme::NvmExpressQueueType;
26+
27+
/// Represents a request for executing an NVMe command.
28+
///
29+
/// This structure encapsulates the command to be sent to the NVMe device, along with
30+
/// optional data transfer and metadata buffers. It ensures proper alignment and safety
31+
/// during interactions with the NVMe protocol.
32+
///
33+
/// # Lifetime
34+
/// `'buffers`: Makes sure the io-buffers bound to the built request
35+
/// stay alive until the response was interpreted.
36+
#[derive(Debug)]
37+
pub struct NvmeRequest<'buffers> {
38+
io_align: u32,
39+
cmd: NvmExpressCommand,
40+
packet: NvmExpressPassThruCommandPacket,
41+
transfer_buffer: Option<AlignedBuffer>,
42+
meta_data_buffer: Option<AlignedBuffer>,
43+
_phantom: PhantomData<&'buffers u8>,
44+
}
45+
46+
// NVMe commands consist of a bunch of CDWs (command data words) and a flags bitmask, where
47+
// one bit per cdw is set when it should be read. Our request builder has one setter method
48+
// with_cdwX() for every cdw, which also automatically sets the corresponding flag-bit.
49+
// This macro generates one such setter method.
50+
macro_rules! define_nvme_command_builder_with_cdw {
51+
($fnname:ident: $fieldname:ident => $flagmask:expr) => {
52+
/// Set the $fieldname parameter on the constructed nvme command.
53+
/// This also automatically flags the parameter as valid in the command's `flags` field.
54+
///
55+
/// # About NVMe commands
56+
/// NVMe commands are constructed of a bunch of numbered CDWs (command data words) and a `flags` field.
57+
/// The `flags´ field tells the NVMe controller which CDWs was set and whether it should respect
58+
/// the corresponding CDWs value.
59+
/// CDWs have no fixed interpretation - the interpretation depends on the command to execute.
60+
/// Which CDWs have to be supplied (and enabled in the `flags` field) depends on the command that
61+
/// should be sent to and executed by the controller.
62+
/// See: <https://nvmexpress.org/specifications/>
63+
#[must_use]
64+
pub const fn $fnname(mut self, $fieldname: u32) -> Self {
65+
self.req.cmd.$fieldname = $fieldname;
66+
self.req.cmd.flags |= $flagmask.bits();
67+
self
68+
}
69+
};
70+
}
71+
72+
/// Builder for constructing an NVMe request.
73+
///
74+
/// This structure provides convenient methods for configuring NVMe commands,
75+
/// including parameters like command-specific data words (CDWs)
76+
/// and optional buffers for transfer and metadata operations.
77+
///
78+
/// It ensures safe and ergonomic setup of NVMe requests.
79+
///
80+
/// # Lifetime
81+
/// `'buffers`: Makes sure the io-buffers bound to the built request
82+
/// stay alive until the response was interpreted.
83+
#[derive(Debug)]
84+
pub struct NvmeRequestBuilder<'buffers> {
85+
req: NvmeRequest<'buffers>,
86+
}
87+
impl<'buffers> NvmeRequestBuilder<'buffers> {
88+
/// Creates a new builder for configuring an NVMe request.
89+
///
90+
/// # Parameters
91+
/// - `io_align`: Memory alignment requirements for buffers.
92+
/// - `opcode`: The opcode for the NVMe command.
93+
/// - `queue_type`: Specifies the type of queue the command should be placed into.
94+
///
95+
/// # Returns
96+
/// An instance of [`NvmeRequestBuilder`] for further configuration.
97+
#[must_use]
98+
pub fn new(io_align: u32, opcode: u8, queue_type: NvmeQueueType) -> Self {
99+
Self {
100+
req: NvmeRequest {
101+
io_align,
102+
cmd: NvmExpressCommand {
103+
cdw0: opcode as u32,
104+
..Default::default()
105+
},
106+
packet: NvmExpressPassThruCommandPacket {
107+
command_timeout: 0,
108+
transfer_buffer: ptr::null_mut(),
109+
transfer_length: 0,
110+
meta_data_buffer: ptr::null_mut(),
111+
meta_data_length: 0,
112+
queue_type,
113+
nvme_cmd: ptr::null(), // filled during execution
114+
nvme_completion: ptr::null_mut(), // filled during execution
115+
},
116+
transfer_buffer: None,
117+
meta_data_buffer: None,
118+
_phantom: PhantomData,
119+
},
120+
}
121+
}
122+
123+
/// Configure the given timeout for this request.
124+
#[must_use]
125+
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
126+
self.req.packet.command_timeout = (timeout.as_nanos() / 100) as u64;
127+
self
128+
}
129+
130+
// define the with_cdwX() builder methods
131+
define_nvme_command_builder_with_cdw!(with_cdw2: cdw2 => NvmExpressCommandCdwValidity::CDW_2);
132+
define_nvme_command_builder_with_cdw!(with_cdw3: cdw3 => NvmExpressCommandCdwValidity::CDW_3);
133+
define_nvme_command_builder_with_cdw!(with_cdw10: cdw10 => NvmExpressCommandCdwValidity::CDW_10);
134+
define_nvme_command_builder_with_cdw!(with_cdw11: cdw11 => NvmExpressCommandCdwValidity::CDW_11);
135+
define_nvme_command_builder_with_cdw!(with_cdw12: cdw12 => NvmExpressCommandCdwValidity::CDW_12);
136+
define_nvme_command_builder_with_cdw!(with_cdw13: cdw13 => NvmExpressCommandCdwValidity::CDW_13);
137+
define_nvme_command_builder_with_cdw!(with_cdw14: cdw14 => NvmExpressCommandCdwValidity::CDW_14);
138+
define_nvme_command_builder_with_cdw!(with_cdw15: cdw15 => NvmExpressCommandCdwValidity::CDW_15);
139+
140+
// # TRANSFER BUFFER
141+
// ########################################################################################
142+
143+
/// Uses a user-supplied buffer for reading data from the device.
144+
///
145+
/// # Parameters
146+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store data read from the device.
147+
///
148+
/// # Returns
149+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
150+
///
151+
/// # Description
152+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
153+
/// the `transfer_buffer` of the underlying [`NvmeRequest`].
154+
pub fn use_transfer_buffer(
155+
mut self,
156+
bfr: &'buffers mut AlignedBuffer,
157+
) -> Result<Self, AlignmentError> {
158+
// check alignment of externally supplied buffer
159+
bfr.check_alignment(self.req.io_align as usize)?;
160+
self.req.transfer_buffer = None;
161+
self.req.packet.transfer_buffer = bfr.ptr_mut().cast();
162+
self.req.packet.transfer_length = bfr.size() as u32;
163+
Ok(self)
164+
}
165+
166+
/// Adds a newly allocated transfer buffer to the built NVMe request.
167+
///
168+
/// # Parameters
169+
/// - `len`: The size of the buffer (in bytes) to allocate for receiving data.
170+
///
171+
/// # Returns
172+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
173+
pub fn with_transfer_buffer(mut self, len: usize) -> Result<Self, LayoutError> {
174+
let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?;
175+
self.req.packet.transfer_buffer = bfr.ptr_mut().cast();
176+
self.req.packet.transfer_length = bfr.size() as u32;
177+
self.req.transfer_buffer = Some(bfr);
178+
Ok(self)
179+
}
180+
181+
// # METADATA BUFFER
182+
// ########################################################################################
183+
184+
/// Uses a user-supplied metadata buffer.
185+
///
186+
/// # Parameters
187+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store metadata.
188+
///
189+
/// # Returns
190+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
191+
///
192+
/// # Description
193+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
194+
/// the `meta_data_buffer` of the underlying [`NvmeRequest`].
195+
pub fn use_metadata_buffer(
196+
mut self,
197+
bfr: &'buffers mut AlignedBuffer,
198+
) -> Result<Self, AlignmentError> {
199+
// check alignment of externally supplied buffer
200+
bfr.check_alignment(self.req.io_align as usize)?;
201+
self.req.meta_data_buffer = None;
202+
self.req.packet.meta_data_buffer = bfr.ptr_mut().cast();
203+
self.req.packet.meta_data_length = bfr.size() as u32;
204+
Ok(self)
205+
}
206+
207+
/// Adds a newly allocated metadata buffer to the built NVMe request.
208+
///
209+
/// # Parameters
210+
/// - `len`: The size of the buffer (in bytes) to allocate for storing metadata.
211+
///
212+
/// # Returns
213+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
214+
pub fn with_metadata_buffer(mut self, len: usize) -> Result<Self, LayoutError> {
215+
let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?;
216+
self.req.packet.meta_data_buffer = bfr.ptr_mut().cast();
217+
self.req.packet.meta_data_length = bfr.size() as u32;
218+
self.req.meta_data_buffer = Some(bfr);
219+
Ok(self)
220+
}
221+
222+
/// Build the final [`NvmeRequest`].
223+
///
224+
/// # Returns
225+
/// A fully-configured [`NvmeRequest`] ready for execution.
226+
#[must_use]
227+
pub fn build(self) -> NvmeRequest<'buffers> {
228+
self.req
229+
}
230+
}
231+
232+
/// Represents the response from executing an NVMe command.
233+
///
234+
/// This structure encapsulates the original request, as well as the command's completion status.
235+
///
236+
/// # Lifetime
237+
/// `'buffers`: Makes sure the io-buffers bound to the built request
238+
/// stay alive until the response was interpreted.
239+
#[derive(Debug)]
240+
pub struct NvmeResponse<'buffers> {
241+
req: NvmeRequest<'buffers>,
242+
completion: NvmeCompletion,
243+
}
244+
impl<'buffers> NvmeResponse<'buffers> {
245+
/// Returns the buffer containing transferred data from the device (if any).
246+
///
247+
/// # Returns
248+
/// `Option<&[u8]>`: A slice of the transfer buffer, or `None` if the request was started without.
249+
#[must_use]
250+
pub fn transfer_buffer(&self) -> Option<&'buffers [u8]> {
251+
if self.req.packet.transfer_buffer.is_null() {
252+
return None;
253+
}
254+
unsafe {
255+
Some(core::slice::from_raw_parts(
256+
self.req.packet.transfer_buffer.cast(),
257+
self.req.packet.transfer_length as usize,
258+
))
259+
}
260+
}
261+
262+
/// Returns the buffer containing metadata data from the device (if any).
263+
///
264+
/// # Returns
265+
/// `Option<&[u8]>`: A slice of the metadata buffer, or `None` if the request was started without.
266+
#[must_use]
267+
pub fn metadata_buffer(&self) -> Option<&'buffers [u8]> {
268+
if self.req.packet.meta_data_buffer.is_null() {
269+
return None;
270+
}
271+
unsafe {
272+
Some(core::slice::from_raw_parts(
273+
self.req.packet.meta_data_buffer.cast(),
274+
self.req.packet.meta_data_length as usize,
275+
))
276+
}
277+
}
278+
279+
/// Provides access to the completion structure of the NVMe command.
280+
///
281+
/// # Returns
282+
/// A reference to the [`NvmeCompletion`] structure containing the status and results of the command.
283+
#[must_use]
284+
pub const fn completion(&self) -> &NvmeCompletion {
285+
&self.completion
286+
}
287+
}

‎uefi/src/proto/nvme/pass_thru.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! NVM Express Pass Thru Protocol.
4+
5+
use super::{NvmeRequest, NvmeResponse};
6+
use crate::mem::{AlignedBuffer, PoolAllocation};
7+
use crate::proto::device_path::PoolDevicePathNode;
8+
use crate::StatusExt;
9+
use core::alloc::LayoutError;
10+
use core::ptr::{self, NonNull};
11+
use uefi_macros::unsafe_protocol;
12+
use uefi_raw::protocol::device_path::DevicePathProtocol;
13+
use uefi_raw::protocol::nvme::{NvmExpressCompletion, NvmExpressPassThruProtocol};
14+
use uefi_raw::Status;
15+
16+
/// Nvme Pass Thru Protocol Mode structure.
17+
///
18+
/// This contains information regarding the specific capabilities and requirements
19+
/// of the NVMe controller, such as buffer alignment constraints.
20+
pub type NvmePassThruMode = uefi_raw::protocol::nvme::NvmExpressPassThruMode;
21+
22+
/// Identifier for an NVMe namespace.
23+
///
24+
/// Namespace IDs are used to target specific namespaces on an NVMe device for commands.
25+
pub type NvmeNamespaceId = u32;
26+
27+
/// NVMe Pass Thru Protocol.
28+
///
29+
/// One protocol instance corresponds to one NVMe controller
30+
/// (which, most of the time, corresponds to one SSD).
31+
///
32+
/// This API offers a safe and convenient, yet still low-level interface to NVMe devices.
33+
/// It is designed as a foundational layer, leaving higher-level abstractions responsible for implementing
34+
/// richer storage semantics, device-specific commands, and advanced use cases.
35+
///
36+
/// # UEFI Spec Description
37+
/// The `EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL` provides essential functionality for interacting
38+
/// with NVMe controllers and namespaces. It allows sending NVMe commands to either the
39+
/// controller itself or specific namespaces within the controller.
40+
#[derive(Debug)]
41+
#[repr(transparent)]
42+
#[unsafe_protocol(NvmExpressPassThruProtocol::GUID)]
43+
pub struct NvmePassThru(NvmExpressPassThruProtocol);
44+
45+
impl NvmePassThru {
46+
/// Retrieves the mode of the NVMe Pass Thru protocol.
47+
///
48+
/// # Returns
49+
/// An instance of [`NvmePassThruMode`] describing the NVMe controller's capabilities.
50+
#[must_use]
51+
pub fn mode(&self) -> NvmePassThruMode {
52+
unsafe { (*self.0.mode).clone() }
53+
}
54+
55+
/// Retrieves the alignment requirements for I/O buffers.
56+
///
57+
/// # Returns
58+
/// An alignment value (in bytes) that all I/O buffers must adhere to for successful operation.
59+
#[must_use]
60+
pub fn io_align(&self) -> u32 {
61+
self.mode().io_align
62+
}
63+
64+
/// Allocates an I/O buffer with the necessary alignment for this NVMe Controller.
65+
///
66+
/// You can alternatively do this yourself using the [`AlignedBuffer`] helper directly.
67+
/// The `nvme` api will validate that your buffers have the correct alignment and error
68+
/// if they don't.
69+
///
70+
/// # Parameters
71+
/// - `len`: The size (in bytes) of the buffer to allocate.
72+
///
73+
/// # Returns
74+
/// [`AlignedBuffer`] containing the allocated memory.
75+
///
76+
/// # Errors
77+
/// This method can fail due to alignment or memory allocation issues.
78+
pub fn alloc_io_buffer(&self, len: usize) -> Result<AlignedBuffer, LayoutError> {
79+
AlignedBuffer::from_size_align(len, self.io_align() as usize)
80+
}
81+
82+
/// Iterate over all valid namespaces on this NVMe controller.
83+
///
84+
/// This ignores the 0-namespaces, which corresponds to the controller itself.
85+
/// The iterator yields [`NvmeNamespace`] instances representing individual namespaces.
86+
///
87+
/// # Returns
88+
/// A [`NvmeNamespaceIterator`] for iterating through the namespaces.
89+
#[must_use]
90+
pub const fn iter_namespaces(&self) -> NvmeNamespaceIterator<'_> {
91+
NvmeNamespaceIterator {
92+
proto: &self.0,
93+
prev: 0xFFFFFFFF,
94+
}
95+
}
96+
97+
/// Get the controller namespace (id = 0).
98+
/// This can be used to send ADMIN commands.
99+
///
100+
/// # Returns
101+
/// A [`NvmeNamespaceIterator`] for iterating through the namespaces.
102+
#[must_use]
103+
pub const fn controller(&self) -> NvmeNamespace<'_> {
104+
NvmeNamespace {
105+
proto: &self.0,
106+
namespace_id: 0,
107+
}
108+
}
109+
}
110+
111+
/// Represents one namespace on an NVMe controller.
112+
///
113+
/// A namespace is a shard of storage that the controller can be partitioned into.
114+
/// Typically, consumer devices only have a single namespace where all the data resides (id 1).
115+
#[derive(Debug)]
116+
pub struct NvmeNamespace<'a> {
117+
proto: &'a NvmExpressPassThruProtocol,
118+
namespace_id: NvmeNamespaceId,
119+
}
120+
121+
impl NvmeNamespace<'_> {
122+
fn proto_mut(&mut self) -> *mut NvmExpressPassThruProtocol {
123+
ptr::from_ref(self.proto).cast_mut()
124+
}
125+
126+
/// Retrieves the namespace identifier (NSID) associated with this NVMe namespace.
127+
#[must_use]
128+
pub const fn namespace_id(&self) -> NvmeNamespaceId {
129+
self.namespace_id
130+
}
131+
132+
/// Get the final device path node for this namespace.
133+
///
134+
/// For a full [`crate::proto::device_path::DevicePath`] pointing to this namespace on the
135+
/// corresponding NVMe controller.
136+
pub fn path_node(&self) -> crate::Result<PoolDevicePathNode> {
137+
unsafe {
138+
let mut path_ptr: *const DevicePathProtocol = ptr::null();
139+
(self.proto.build_device_path)(self.proto, self.namespace_id, &mut path_ptr)
140+
.to_result()?;
141+
NonNull::new(path_ptr.cast_mut())
142+
.map(|p| PoolDevicePathNode(PoolAllocation::new(p.cast())))
143+
.ok_or(Status::OUT_OF_RESOURCES.into())
144+
}
145+
}
146+
147+
/// Sends an NVM Express command to this namespace (Namespace ID ≥ 1).
148+
///
149+
/// # Parameters
150+
/// - `req`: The [`NvmeRequest`] containing the command and associated data to send to the namespace.
151+
///
152+
/// # Returns
153+
/// - [`NvmeResponse`] containing the results of the operation, such as data and status.
154+
///
155+
/// # Errors
156+
/// - [`Status::BAD_BUFFER_SIZE`] The NVM Express Command Packet was not executed. The number
157+
/// of bytes that could be transferred is returned in `TransferLength`.
158+
/// - [`Status::NOT_READY`] The NVM Express Command Packet could not be sent because the controller
159+
/// is not ready. The caller may retry later.
160+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to send the NVM Express
161+
/// Command Packet. Additional status information is available in `NvmeCompletion`.
162+
/// - [`Status::INVALID_PARAMETER`] The Namespace ID or the contents of the Command Packet are invalid.
163+
/// The NVM Express Command Packet was not sent, and no additional status information is available.
164+
/// - [`Status::UNSUPPORTED`] The command described by the NVM Express Command Packet is not supported
165+
/// by the NVM Express controller. The Command Packet was not sent, and no additional status
166+
/// information is available.
167+
/// - [`Status::TIMEOUT`] A timeout occurred while executing the NVM Express Command Packet.
168+
/// Additional status information is available in `NvmeCompletion`.
169+
pub fn execute_command<'req>(
170+
&mut self,
171+
mut req: NvmeRequest<'req>,
172+
) -> crate::Result<NvmeResponse<'req>> {
173+
let mut completion = NvmExpressCompletion::default();
174+
// prepare cmd packet
175+
req.cmd.nsid = self.namespace_id;
176+
req.packet.nvme_cmd = &req.cmd;
177+
req.packet.nvme_completion = &mut completion;
178+
unsafe {
179+
(self.proto.pass_thru)(
180+
self.proto_mut(),
181+
self.namespace_id,
182+
&mut req.packet,
183+
ptr::null_mut(),
184+
)
185+
.to_result_with_val(|| NvmeResponse { req, completion })
186+
}
187+
}
188+
}
189+
190+
/// An iterator over the namespaces of an NVMe controller.
191+
///
192+
/// The iterator yields [`NvmeNamespace`] instances, each representing one namespace
193+
/// on the NVMe controller.
194+
#[derive(Debug)]
195+
pub struct NvmeNamespaceIterator<'a> {
196+
proto: &'a NvmExpressPassThruProtocol,
197+
prev: NvmeNamespaceId,
198+
}
199+
200+
impl<'a> Iterator for NvmeNamespaceIterator<'a> {
201+
type Item = NvmeNamespace<'a>;
202+
203+
fn next(&mut self) -> Option<Self::Item> {
204+
let result = unsafe { (self.proto.get_next_namespace)(self.proto, &mut self.prev) };
205+
match result {
206+
Status::SUCCESS => Some(NvmeNamespace {
207+
proto: self.proto,
208+
namespace_id: self.prev,
209+
}),
210+
Status::NOT_FOUND => None,
211+
_ => panic!("Must not happen according to spec!"),
212+
}
213+
}
214+
}

‎xtask/src/qemu.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,16 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
489489
cmd.arg("-device"); // attach disk to SCSI controller
490490
cmd.arg("scsi-hd,drive=scsidisk0,vendor=uefi-rs,product=ExtScsiPassThru");
491491

492+
// Fourth (NVMe) disk for NvmePassThru tests
493+
let nvme_test_disk = tmp_dir.join("test_disk3.empty.img");
494+
std::fs::File::create(&nvme_test_disk)?.set_len(1024 * 1024 * 10)?;
495+
cmd.arg("-drive");
496+
let mut drive_arg = OsString::from("if=none,id=nvmedisk0,format=raw,file=");
497+
drive_arg.push(nvme_test_disk.clone());
498+
cmd.arg(drive_arg);
499+
cmd.arg("-device");
500+
cmd.arg("nvme,drive=nvmedisk0,serial=uefi-rsNvmePassThru");
501+
492502
let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?;
493503
let serial_pipe = Pipe::new(tmp_dir, "serial")?;
494504

0 commit comments

Comments
 (0)
Please sign in to comment.