Skip to content

Commit 4c4f1d3

Browse files
committed
uefi: Add safe protocol implementation for NVM_EXPRESS_PASS_THRU_PROTOCOL
1 parent 9010a6a commit 4c4f1d3

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

uefi/CHANGELOG.md

+1
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/nvme/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ use uefi_raw::protocol::nvme::{
1111
NvmExpressCommand, NvmExpressCommandCdwValidity, NvmExpressPassThruCommandPacket,
1212
};
1313

14+
pub mod pass_thru;
15+
1416
/// Represents the completion status of an NVMe command.
1517
///
1618
/// This structure contains various fields related to the status and results

uefi/src/proto/nvme/pass_thru.rs

+214
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+
}

0 commit comments

Comments
 (0)