uefi/proto/nvme/mod.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! NVM Express Protocols.
4
5use crate::mem::{AlignedBuffer, AlignmentError};
6use core::alloc::LayoutError;
7use core::marker::PhantomData;
8use core::ptr;
9use core::time::Duration;
10use uefi_raw::protocol::nvme::{
11 NvmExpressCommand, NvmExpressCommandCdwValidity, NvmExpressPassThruCommandPacket,
12};
13
14pub 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.
21pub 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)
25pub 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)]
37pub 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.
50macro_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)]
84pub struct NvmeRequestBuilder<'buffers> {
85 req: NvmeRequest<'buffers>,
86}
87impl<'buffers> NvmeRequestBuilder<'buffers> {
88 /// Creates a new builder for configuring an NVMe request.
89 ///
90 /// # Arguments
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 /// # Arguments
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 /// # Arguments
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 /// # Arguments
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 /// # Arguments
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)]
240pub struct NvmeResponse<'buffers> {
241 req: NvmeRequest<'buffers>,
242 completion: NvmeCompletion,
243}
244impl<'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 const 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 const 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}