Skip to content

Commit 3488540

Browse files
committed
uefi: Add nvme request builder infrastructure
1 parent 3db4620 commit 3488540

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

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

0 commit comments

Comments
 (0)