Skip to content

uefi: Add safe protocol wrapper for EFI_EXT_SCSI_PASS_THRU_PROTOCOL #1589

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 2 commits into from
Apr 10, 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
2 changes: 1 addition & 1 deletion uefi-raw/src/protocol/scsi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ pub struct ExtScsiPassThruMode {
pub struct ExtScsiPassThruProtocol {
pub passthru_mode: *const ExtScsiPassThruMode,
pub pass_thru: unsafe extern "efiapi" fn(
this: *const Self,
this: *mut Self,
target: *const u8,
lun: u64,
packet: *mut ScsiIoScsiRequestPacket,
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 @@ -25,6 +25,7 @@ pub fn test() {
shell_params::test();
string::test();
misc::test();
scsi::test();

#[cfg(any(
target_arch = "x86",
Expand Down Expand Up @@ -73,6 +74,7 @@ mod misc;
mod network;
mod pi;
mod rng;
mod scsi;
mod shell_params;
#[cfg(any(
target_arch = "x86",
Expand Down
7 changes: 7 additions & 0 deletions uefi-test-runner/src/proto/scsi/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();
}
112 changes: 112 additions & 0 deletions uefi-test-runner/src/proto/scsi/pass_thru.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use uefi::proto::scsi::pass_thru::ExtScsiPassThru;
use uefi::proto::scsi::ScsiRequestBuilder;

pub fn test() {
info!("Running extended SCSI Pass Thru tests");
test_allocating_api();
test_reusing_buffer_api();
}

fn test_allocating_api() {
let scsi_ctrl_handles = uefi::boot::find_handles::<ExtScsiPassThru>().unwrap();

// On I440FX and Q35 (both x86 machines), Qemu configures an IDE and a SATA controller
// by default respectively. We manually configure an additional SCSI controller.
// Thus, we should see two controllers with support for EXT_SCSI_PASS_THRU on this platform
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
assert_eq!(scsi_ctrl_handles.len(), 2);
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
assert_eq!(scsi_ctrl_handles.len(), 1);

let mut found_drive = false;
for handle in scsi_ctrl_handles {
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
for mut device in scsi_pt.iter_devices() {
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
// 3.6 INQUIRY command
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
.with_timeout(core::time::Duration::from_millis(500))
.with_command_data(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00])
.unwrap()
.with_read_buffer(255)
.unwrap()
.build();
let Ok(response) = device.execute_command(request) else {
continue; // no device
};
let bfr = response.read_buffer().unwrap();
// more no device checks
if bfr.len() < 32 {
continue;
}
if bfr[0] & 0b00011111 == 0x1F {
continue;
}

// found device
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
info!(
"Found Testdisk at: {:?} | {}",
device.target(),
device.lun()
);
found_drive = true;
}
}
}

assert!(found_drive);
}

fn test_reusing_buffer_api() {
let scsi_ctrl_handles = uefi::boot::find_handles::<ExtScsiPassThru>().unwrap();

let mut found_drive = false;
for handle in scsi_ctrl_handles {
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
let mut cmd_bfr = scsi_pt.alloc_io_buffer(6).unwrap();
cmd_bfr.copy_from_slice(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00]);
let mut read_bfr = scsi_pt.alloc_io_buffer(255).unwrap();

for mut device in scsi_pt.iter_devices() {
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
// 3.6 INQUIRY command
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
.with_timeout(core::time::Duration::from_millis(500))
.use_command_buffer(&mut cmd_bfr)
.unwrap()
.use_read_buffer(&mut read_bfr)
.unwrap()
.build();
let Ok(response) = device.execute_command(request) else {
continue; // no device
};
let bfr = response.read_buffer().unwrap();
// more no device checks
if bfr.len() < 32 {
continue;
}
if bfr[0] & 0b00011111 == 0x1F {
continue;
}

// found device
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
info!(
"Found Testdisk at: {:?} | {}",
device.target(),
device.lun()
);
found_drive = true;
}
}
}

assert!(found_drive);
}
1 change: 1 addition & 0 deletions uefi/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Added `mem::AlignedBuffer`.
- Added `proto::device_path::DevicePath::append_path()`.
- Added `proto::device_path::DevicePath::append_node()`.
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.

## 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 @@ -20,6 +20,8 @@ pub mod misc;
pub mod network;
pub mod pi;
pub mod rng;
#[cfg(feature = "alloc")]
pub mod scsi;
pub mod security;
pub mod shell_params;
pub mod shim;
Expand Down
Loading