Skip to content

Commit 6bc3c0f

Browse files
committed
uefi: Add safe bindings for EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL
1 parent aaa15b0 commit 6bc3c0f

File tree

5 files changed

+560
-7
lines changed

5 files changed

+560
-7
lines changed

uefi-raw/src/protocol/nvme.rs

+66-7
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/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Added conversions between `proto::network::IpAddress` and `core::net` types.
66
- Added conversions between `proto::network::MacAddress` and the `[u8; 6]` type that's more commonly used to represent MAC addresses.
77
- Added `proto::media::disk_info::DiskInfo`.
8+
- Added `proto::nvme::pass_thru::NvmePassThru`.
9+
810

911
## Changed
1012
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no

uefi/src/proto/mod.rs

+2
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
pub mod security;

uefi/src/proto/nvme/mod.rs

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

0 commit comments

Comments
 (0)