Skip to content

uefi: Add safe protocol wrapper for EFI_NVM_EXPRESS_PASS_THRU_PROTOCOL #1594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 66 additions & 7 deletions uefi-raw/src/protocol/nvme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,63 @@ use crate::Status;
use core::ffi::c_void;
use uguid::{guid, Guid};

#[derive(Debug)]
bitflags::bitflags! {
/// In an NVMe command, the `flags` field specifies which cdw (command specific word)
/// contains a value.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct NvmExpressCommandCdwValidity: u8 {
const CDW_2 = 0x01;
const CDW_3 = 0x02;
const CDW_10 = 0x04;
const CDW_11 = 0x08;
const CDW_12 = 0x10;
const CDW_13 = 0x20;
const CDW_14 = 0x40;
const CDW_15 = 0x80;
}

/// Represents the `EFI_NVM_EXPRESS_PASS_THRU_ATTRIBUTES_*` defines from the UEFI specification.
///
/// # UEFI Specification Description
/// Tells if the interface is for physical NVM Express controllers or logical NVM Express controllers.
///
/// Drivers for non-RAID NVM Express controllers will set both the `PHYSICAL` and the `LOGICAL` bit.
///
/// Drivers for RAID controllers that allow access to the underlying physical controllers will produces
/// two protocol instances. One where the `LOGICAL` bit is set (representing the logical RAID volume),
/// and one where the `PHYSICAL` bit is set, which can be used to access the underlying NVMe controllers.
///
/// Drivers for RAID controllers that do not allow access of the underlying NVMe controllers will only
/// produce one protocol instance for the logical RAID volume with the `LOGICAL` bit set.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct NvmExpressPassThruAttributes: u32 {
/// If this bit is set, the interface is for directly addressable namespaces.
const PHYSICAL = 0x0001;

/// If this bit is set, the interface is for a single logical namespace comprising multiple namespaces.
const LOGICAL = 0x0002;

/// If this bit is set, the interface supports both blocking and non-blocking I/O.
/// - All interfaces must support blocking I/O, but this bit indicates that non-blocking I/O is also supported.
const NONBLOCKIO = 0x0004;

/// If this bit is set, the interface supports the NVM Express command set.
const CMD_SET_NVM = 0x0008;
}
}

#[derive(Clone, Debug)]
#[repr(C)]
pub struct NvmExpressPassThruMode {
pub attributes: u32,
pub attributes: NvmExpressPassThruAttributes,
pub io_align: u32,
pub nvme_version: u32,
}

/// This structure maps to the NVM Express specification Submission Queue Entry
#[derive(Debug)]
#[derive(Debug, Default)]
#[repr(C)]
pub struct NvmExpressCommand {
pub cdw0: u32,
Expand All @@ -30,8 +77,20 @@ pub struct NvmExpressCommand {
pub cdw15: u32,
}

newtype_enum! {
/// Type of queues an NVMe command can be placed into
/// (Which queue a command should be placed into depends on the command)
#[derive(Default)]
pub enum NvmExpressQueueType: u8 => {
/// Admin Submission Queue
ADMIN = 0,
/// 1) I/O Submission Queue
IO = 1,
}
}

/// This structure maps to the NVM Express specification Completion Queue Entry
#[derive(Debug)]
#[derive(Debug, Default)]
#[repr(C)]
pub struct NvmExpressCompletion {
pub dw0: u32,
Expand All @@ -48,7 +107,7 @@ pub struct NvmExpressPassThruCommandPacket {
pub transfer_length: u32,
pub meta_data_buffer: *mut c_void,
pub meta_data_length: u32,
pub queue_type: u8,
pub queue_type: NvmExpressQueueType,
pub nvme_cmd: *const NvmExpressCommand,
pub nvme_completion: *mut NvmExpressCompletion,
}
Expand All @@ -58,7 +117,7 @@ pub struct NvmExpressPassThruCommandPacket {
pub struct NvmExpressPassThruProtocol {
pub mode: *const NvmExpressPassThruMode,
pub pass_thru: unsafe extern "efiapi" fn(
this: *const Self,
this: *mut Self,
namespace_id: u32,
packet: *mut NvmExpressPassThruCommandPacket,
event: *mut c_void,
Expand All @@ -68,7 +127,7 @@ pub struct NvmExpressPassThruProtocol {
pub build_device_path: unsafe extern "efiapi" fn(
this: *const Self,
namespace_id: u32,
device_path: *mut *mut DevicePathProtocol,
device_path: *mut *const DevicePathProtocol,
) -> Status,
pub get_namespace: unsafe extern "efiapi" fn(
this: *const Self,
Expand Down
2 changes: 2 additions & 0 deletions uefi-test-runner/src/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn test() {
string::test();
misc::test();
scsi::test();
nvme::test();

#[cfg(any(
target_arch = "x86",
Expand Down Expand Up @@ -72,6 +73,7 @@ mod loaded_image;
mod media;
mod misc;
mod network;
mod nvme;
mod pi;
mod rng;
mod scsi;
Expand Down
7 changes: 7 additions & 0 deletions uefi-test-runner/src/proto/nvme/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

mod pass_thru;

pub fn test() {
pass_thru::test();
}
53 changes: 53 additions & 0 deletions uefi-test-runner/src/proto/nvme/pass_thru.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use core::time::Duration;
use uefi::boot;
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
use uefi::proto::device_path::DevicePath;
use uefi::proto::media::block::BlockIO;
use uefi::proto::nvme::pass_thru::NvmePassThru;
use uefi::proto::nvme::{NvmeQueueType, NvmeRequestBuilder};

pub fn test() {
info!("Running NVMe PassThru tests");

assert!(has_nvme_drive());
}

fn has_nvme_drive() -> bool {
let block_io_handles = boot::find_handles::<BlockIO>().unwrap();
for handle in block_io_handles {
let Ok(device_path) = boot::open_protocol_exclusive::<DevicePath>(handle) else {
continue;
};
let mut device_path = &*device_path;

let Ok(nvme_pt_handle) = boot::locate_device_path::<NvmePassThru>(&mut device_path) else {
continue;
};
let nvme_pt = boot::open_protocol_exclusive::<NvmePassThru>(nvme_pt_handle).unwrap();
let device_path_str = device_path
.to_string(DisplayOnly(true), AllowShortcuts(false))
.unwrap();
info!("- Successfully opened NVMe: {}", device_path_str);
let mut nvme_ctrl = nvme_pt.controller();

let request = NvmeRequestBuilder::new(nvme_pt.io_align(), 0x06, NvmeQueueType::ADMIN)
.with_timeout(Duration::from_millis(500))
.with_cdw10(1) // we want info about controller
.with_transfer_buffer(4096)
.unwrap()
.build();
let result = nvme_ctrl.execute_command(request);
if let Ok(result) = result {
let bfr = result.transfer_buffer().unwrap();
let serial = core::str::from_utf8(&bfr[4..24]).unwrap().trim();
info!("Found NVMe with serial: '{}'", serial);
if serial == "uefi-rsNvmePassThru" {
return true;
}
}
}

false
}
1 change: 1 addition & 0 deletions uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added `proto::device_path::DevicePath::append_path()`.
- Added `proto::device_path::DevicePath::append_node()`.
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.
- Added `proto::nvme::pass_thru::NvmePassThru`.

## Changed
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no
Expand Down
2 changes: 2 additions & 0 deletions uefi/src/proto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ pub mod loaded_image;
pub mod media;
pub mod misc;
pub mod network;
#[cfg(feature = "alloc")]
pub mod nvme;
pub mod pi;
pub mod rng;
#[cfg(feature = "alloc")]
Expand Down
Loading