Skip to main content

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}