Skip to content

Commit 5b7173f

Browse files
committedApr 8, 2025··
uefi: Add safe protocol bindings for EFI_EXT_SCSI_PASS_THRU_PROTOCOL
1 parent 78a3581 commit 5b7173f

File tree

8 files changed

+747
-2
lines changed

8 files changed

+747
-2
lines changed
 

‎uefi-raw/src/protocol/scsi.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ pub struct ExtScsiPassThruMode {
186186
pub struct ExtScsiPassThruProtocol {
187187
pub passthru_mode: *const ExtScsiPassThruMode,
188188
pub pass_thru: unsafe extern "efiapi" fn(
189-
this: *const Self,
189+
this: *mut Self,
190190
target: *const u8,
191191
lun: u64,
192192
packet: *mut ScsiIoScsiRequestPacket,
@@ -208,7 +208,7 @@ pub struct ExtScsiPassThruProtocol {
208208
) -> Status,
209209
pub reset_channel: unsafe extern "efiapi" fn(this: *mut Self) -> Status,
210210
pub reset_target_lun:
211-
unsafe extern "efiapi" fn(this: *const Self, target: *const u8, lun: u64) -> Status,
211+
unsafe extern "efiapi" fn(this: *mut Self, target: *const u8, lun: u64) -> Status,
212212
pub get_next_target:
213213
unsafe extern "efiapi" fn(this: *const Self, target: *mut *mut u8) -> Status,
214214
}

‎uefi-test-runner/src/proto/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub fn test() {
2525
shell_params::test();
2626
string::test();
2727
misc::test();
28+
scsi::test();
2829

2930
#[cfg(any(
3031
target_arch = "x86",
@@ -73,6 +74,7 @@ mod misc;
7374
mod network;
7475
mod pi;
7576
mod rng;
77+
mod scsi;
7678
mod shell_params;
7779
#[cfg(any(
7880
target_arch = "x86",
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
mod pass_thru;
4+
5+
pub fn test() {
6+
pass_thru::test();
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
use uefi::proto::scsi::pass_thru::ExtScsiPassThru;
4+
use uefi::proto::scsi::ScsiRequestBuilder;
5+
6+
pub fn test() {
7+
info!("Running extended SCSI Pass Thru tests");
8+
test_allocating_api();
9+
test_reusing_buffer_api();
10+
}
11+
12+
fn test_allocating_api() {
13+
let scsi_ctrl_handles = uefi::boot::find_handles::<ExtScsiPassThru>().unwrap();
14+
15+
// On I440FX and Q35 (both x86 machines), Qemu configures an IDE and a SATA controller
16+
// by default respectively. We manually configure an additional SCSI controller.
17+
// Thus, we should see two controllers with support for EXT_SCSI_PASS_THRU on this platform
18+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
19+
assert_eq!(scsi_ctrl_handles.len(), 2);
20+
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
21+
assert_eq!(scsi_ctrl_handles.len(), 1);
22+
23+
let mut found_drive = false;
24+
for handle in scsi_ctrl_handles {
25+
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
26+
for mut device in scsi_pt.iter_devices() {
27+
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
28+
// 3.6 INQUIRY command
29+
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
30+
.with_timeout(core::time::Duration::from_millis(500))
31+
.with_command_data(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00])
32+
.unwrap()
33+
.with_read_buffer(255)
34+
.unwrap()
35+
.build();
36+
let Ok(response) = device.execute_command(request) else {
37+
continue; // no device
38+
};
39+
let bfr = response.read_buffer().unwrap();
40+
// more no device checks
41+
if bfr.len() < 32 {
42+
continue;
43+
}
44+
if bfr[0] & 0b00011111 == 0x1F {
45+
continue;
46+
}
47+
48+
// found device
49+
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
50+
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
51+
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
52+
info!(
53+
"Found Testdisk at: {:?} | {}",
54+
device.target(),
55+
device.lun()
56+
);
57+
found_drive = true;
58+
}
59+
}
60+
}
61+
62+
assert!(found_drive);
63+
}
64+
65+
fn test_reusing_buffer_api() {
66+
let scsi_ctrl_handles = uefi::boot::find_handles::<ExtScsiPassThru>().unwrap();
67+
68+
let mut found_drive = false;
69+
for handle in scsi_ctrl_handles {
70+
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
71+
let mut cmd_bfr = scsi_pt.alloc_io_buffer(6).unwrap();
72+
cmd_bfr.copy_from_slice(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00]);
73+
let mut read_bfr = scsi_pt.alloc_io_buffer(255).unwrap();
74+
75+
for mut device in scsi_pt.iter_devices() {
76+
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
77+
// 3.6 INQUIRY command
78+
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
79+
.with_timeout(core::time::Duration::from_millis(500))
80+
.use_command_buffer(&mut cmd_bfr)
81+
.unwrap()
82+
.use_read_buffer(&mut read_bfr)
83+
.unwrap()
84+
.build();
85+
let Ok(response) = device.execute_command(request) else {
86+
continue; // no device
87+
};
88+
let bfr = response.read_buffer().unwrap();
89+
// more no device checks
90+
if bfr.len() < 32 {
91+
continue;
92+
}
93+
if bfr[0] & 0b00011111 == 0x1F {
94+
continue;
95+
}
96+
97+
// found device
98+
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
99+
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
100+
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
101+
info!(
102+
"Found Testdisk at: {:?} | {}",
103+
device.target(),
104+
device.lun()
105+
);
106+
found_drive = true;
107+
}
108+
}
109+
}
110+
111+
assert!(found_drive);
112+
}

‎uefi/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Added `mem::AlignedBuffer`.
99
- Added `proto::device_path::DevicePath::append_path()`.
1010
- Added `proto::device_path::DevicePath::append_node()`.
11+
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.
1112

1213
## Changed
1314
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no

‎uefi/src/proto/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub mod misc;
2020
pub mod network;
2121
pub mod pi;
2222
pub mod rng;
23+
#[cfg(feature = "alloc")]
24+
pub mod scsi;
2325
pub mod security;
2426
pub mod shell_params;
2527
pub mod shim;

‎uefi/src/proto/scsi/mod.rs

+352
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! SCSI Bus specific protocols.
4+
5+
use crate::mem::{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::scsi::{
11+
ScsiIoDataDirection, ScsiIoHostAdapterStatus, ScsiIoScsiRequestPacket, ScsiIoTargetStatus,
12+
};
13+
14+
pub mod pass_thru;
15+
16+
/// Represents the data direction for a SCSI request.
17+
///
18+
/// Used to specify whether the request involves reading, writing, or bidirectional data transfer.
19+
pub type ScsiRequestDirection = uefi_raw::protocol::scsi::ScsiIoDataDirection;
20+
21+
/// Represents a SCSI request packet.
22+
///
23+
/// This structure encapsulates the necessary data for sending a command to a SCSI device.
24+
#[derive(Debug)]
25+
pub struct ScsiRequest<'a> {
26+
packet: ScsiIoScsiRequestPacket,
27+
io_align: u32,
28+
in_data_buffer: Option<AlignedBuffer>,
29+
out_data_buffer: Option<AlignedBuffer>,
30+
sense_data_buffer: Option<AlignedBuffer>,
31+
cdb_buffer: Option<AlignedBuffer>,
32+
_phantom: PhantomData<&'a u8>,
33+
}
34+
35+
/// A builder for constructing [`ScsiRequest`] instances.
36+
///
37+
/// Provides a safe and ergonomic interface for configuring SCSI request packets, including timeout,
38+
/// data buffers, and command descriptor blocks.
39+
#[derive(Debug)]
40+
pub struct ScsiRequestBuilder<'a> {
41+
req: ScsiRequest<'a>,
42+
}
43+
impl ScsiRequestBuilder<'_> {
44+
/// Creates a new instance with the specified data direction and alignment.
45+
///
46+
/// # Parameters
47+
/// - `direction`: Specifies the direction of data transfer (READ, WRITE, or BIDIRECTIONAL).
48+
/// - `io_align`: Specifies the required alignment for data buffers. (SCSI Controller specific!)
49+
#[must_use]
50+
pub fn new(direction: ScsiRequestDirection, io_align: u32) -> Self {
51+
Self {
52+
req: ScsiRequest {
53+
in_data_buffer: None,
54+
out_data_buffer: None,
55+
sense_data_buffer: None,
56+
cdb_buffer: None,
57+
packet: ScsiIoScsiRequestPacket {
58+
timeout: 0,
59+
in_data_buffer: ptr::null_mut(),
60+
out_data_buffer: ptr::null_mut(),
61+
sense_data: ptr::null_mut(),
62+
cdb: ptr::null_mut(),
63+
in_transfer_length: 0,
64+
out_transfer_length: 0,
65+
cdb_length: 0,
66+
data_direction: direction,
67+
host_adapter_status: ScsiIoHostAdapterStatus::default(),
68+
target_status: ScsiIoTargetStatus::default(),
69+
sense_data_length: 0,
70+
},
71+
io_align,
72+
_phantom: Default::default(),
73+
},
74+
}
75+
}
76+
77+
/// Starts a new builder preconfigured for READ operations.
78+
///
79+
/// Some examples of SCSI read commands are:
80+
/// - INQUIRY
81+
/// - READ
82+
/// - MODE_SENSE
83+
///
84+
/// # Parameters
85+
/// - `io_align`: Specifies the required alignment for data buffers.
86+
#[must_use]
87+
pub fn read(io_align: u32) -> Self {
88+
Self::new(ScsiIoDataDirection::READ, io_align)
89+
}
90+
91+
/// Starts a new builder preconfigured for WRITE operations.
92+
///
93+
/// Some examples of SCSI write commands are:
94+
/// - WRITE
95+
/// - MODE_SELECT
96+
///
97+
/// # Parameters
98+
/// - `io_align`: Specifies the required alignment for data buffers.
99+
#[must_use]
100+
pub fn write(io_align: u32) -> Self {
101+
Self::new(ScsiIoDataDirection::WRITE, io_align)
102+
}
103+
104+
/// Starts a new builder preconfigured for BIDIRECTIONAL operations.
105+
///
106+
/// Some examples of SCSI bidirectional commands are:
107+
/// - SEND DIAGNOSTIC
108+
///
109+
/// # Parameters
110+
/// - `io_align`: Specifies the required alignment for data buffers.
111+
#[must_use]
112+
pub fn bidirectional(io_align: u32) -> Self {
113+
Self::new(ScsiIoDataDirection::BIDIRECTIONAL, io_align)
114+
}
115+
}
116+
117+
impl<'a> ScsiRequestBuilder<'a> {
118+
/// Sets a timeout for the SCSI request.
119+
///
120+
/// # Parameters
121+
/// - `timeout`: A [`Duration`] representing the maximum time allowed for the request.
122+
/// The value is converted to 100-nanosecond units.
123+
///
124+
/// # Description
125+
/// By default (without calling this method, or by calling with [`Duration::ZERO`]),
126+
/// SCSI requests have no timeout.
127+
/// Setting a timeout here will cause SCSI commands to potentially fail with [`crate::Status::TIMEOUT`].
128+
#[must_use]
129+
pub const fn with_timeout(mut self, timeout: Duration) -> Self {
130+
self.req.packet.timeout = (timeout.as_nanos() / 100) as u64;
131+
self
132+
}
133+
134+
// # IN BUFFER
135+
// ########################################################################################
136+
137+
/// Uses a user-supplied buffer for reading data from the device.
138+
///
139+
/// # Parameters
140+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] that will be used to store data read from the device.
141+
///
142+
/// # Returns
143+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
144+
///
145+
/// # Description
146+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
147+
/// the `in_data_buffer` of the underlying `ScsiRequest`.
148+
pub fn use_read_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> {
149+
// check alignment of externally supplied buffer
150+
bfr.check_alignment(self.req.io_align as usize)?;
151+
self.req.in_data_buffer = None;
152+
self.req.packet.in_data_buffer = bfr.ptr_mut().cast();
153+
self.req.packet.in_transfer_length = bfr.size() as u32;
154+
Ok(self)
155+
}
156+
157+
/// Adds a newly allocated read buffer to the built SCSI request.
158+
///
159+
/// # Parameters
160+
/// - `len`: The size of the buffer (in bytes) to allocate for receiving data.
161+
///
162+
/// # Returns
163+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
164+
pub fn with_read_buffer(mut self, len: usize) -> Result<Self, LayoutError> {
165+
let mut bfr = AlignedBuffer::from_size_align(len, self.req.io_align as usize)?;
166+
self.req.packet.in_data_buffer = bfr.ptr_mut().cast();
167+
self.req.packet.in_transfer_length = bfr.size() as u32;
168+
self.req.in_data_buffer = Some(bfr);
169+
Ok(self)
170+
}
171+
172+
// # SENSE BUFFER
173+
// ########################################################################################
174+
175+
/// Adds a newly allocated sense buffer to the built SCSI request.
176+
///
177+
/// # Parameters
178+
/// - `len`: The size of the buffer (in bytes) to allocate for receiving sense data.
179+
///
180+
/// # Returns
181+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
182+
pub fn with_sense_buffer(mut self, len: u8) -> Result<Self, LayoutError> {
183+
let mut bfr = AlignedBuffer::from_size_align(len as usize, self.req.io_align as usize)?;
184+
self.req.packet.sense_data = bfr.ptr_mut().cast();
185+
self.req.packet.sense_data_length = len;
186+
self.req.sense_data_buffer = Some(bfr);
187+
Ok(self)
188+
}
189+
190+
// # WRITE BUFFER
191+
// ########################################################################################
192+
193+
/// Uses a user-supplied buffer for writing data to the device.
194+
///
195+
/// # Parameters
196+
/// - `bfr`: A mutable reference to an [`AlignedBuffer`] containing the data to be written to the device.
197+
///
198+
/// # Returns
199+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
200+
///
201+
/// # Description
202+
/// This method checks the alignment of the buffer against the protocol's requirements and assigns it to
203+
/// the `out_data_buffer` of the underlying `ScsiRequest`.
204+
pub fn use_write_buffer(mut self, bfr: &'a mut AlignedBuffer) -> Result<Self, AlignmentError> {
205+
// check alignment of externally supplied buffer
206+
bfr.check_alignment(self.req.io_align as usize)?;
207+
self.req.out_data_buffer = None;
208+
self.req.packet.out_data_buffer = bfr.ptr_mut().cast();
209+
self.req.packet.out_transfer_length = bfr.size() as u32;
210+
Ok(self)
211+
}
212+
213+
/// Adds a newly allocated write buffer to the built SCSI request that is filled from the
214+
/// given data buffer. (Done for memory alignment and lifetime purposes)
215+
///
216+
/// # Parameters
217+
/// - `data`: A slice of bytes representing the data to be written.
218+
///
219+
/// # Returns
220+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
221+
pub fn with_write_data(mut self, data: &[u8]) -> Result<Self, LayoutError> {
222+
let mut bfr = AlignedBuffer::from_size_align(data.len(), self.req.io_align as usize)?;
223+
bfr.copy_from_slice(data);
224+
self.req.packet.out_data_buffer = bfr.ptr_mut().cast();
225+
self.req.packet.out_transfer_length = bfr.size() as u32;
226+
self.req.out_data_buffer = Some(bfr);
227+
Ok(self)
228+
}
229+
230+
// # COMMAND BUFFER
231+
// ########################################################################################
232+
233+
/// Uses a user-supplied Command Data Block (CDB) buffer.
234+
///
235+
/// # Parameters
236+
/// - `data`: A mutable reference to an [`AlignedBuffer`] containing the CDB to be sent to the device.
237+
///
238+
/// # Returns
239+
/// `Result<Self, AlignmentError>` indicating success or an alignment issue with the provided buffer.
240+
///
241+
/// # Notes
242+
/// The maximum length of a CDB is 255 bytes.
243+
pub fn use_command_buffer(
244+
mut self,
245+
data: &'a mut AlignedBuffer,
246+
) -> Result<Self, AlignmentError> {
247+
assert!(data.size() <= 255);
248+
// check alignment of externally supplied buffer
249+
data.check_alignment(self.req.io_align as usize)?;
250+
self.req.cdb_buffer = None;
251+
self.req.packet.cdb = data.ptr_mut().cast();
252+
self.req.packet.cdb_length = data.size() as u8;
253+
Ok(self)
254+
}
255+
256+
/// Adds a newly allocated Command Data Block (CDB) buffer to the built SCSI request that is filled from the
257+
/// given data buffer. (Done for memory alignment and lifetime purposes)
258+
///
259+
/// # Parameters
260+
/// - `data`: A slice of bytes representing the command to be sent.
261+
///
262+
/// # Returns
263+
/// `Result<Self, LayoutError>` indicating success or a memory allocation error.
264+
///
265+
/// # Notes
266+
/// The maximum length of a CDB is 255 bytes.
267+
pub fn with_command_data(mut self, data: &[u8]) -> Result<Self, LayoutError> {
268+
assert!(data.len() <= 255);
269+
let mut bfr = AlignedBuffer::from_size_align(data.len(), self.req.io_align as usize)?;
270+
bfr.copy_from_slice(data);
271+
self.req.packet.cdb = bfr.ptr_mut().cast();
272+
self.req.packet.cdb_length = bfr.size() as u8;
273+
self.req.cdb_buffer = Some(bfr);
274+
Ok(self)
275+
}
276+
277+
/// Build the final `ScsiRequest`.
278+
///
279+
/// # Returns
280+
/// A fully-configured [`ScsiRequest`] ready for execution.
281+
#[must_use]
282+
pub fn build(self) -> ScsiRequest<'a> {
283+
self.req
284+
}
285+
}
286+
287+
/// Represents the response of a SCSI request.
288+
///
289+
/// This struct encapsulates the results of a SCSI operation, including data buffers
290+
/// for read and sense data, as well as status codes returned by the host adapter and target device.
291+
#[derive(Debug)]
292+
#[repr(transparent)]
293+
pub struct ScsiResponse<'a>(ScsiRequest<'a>);
294+
impl<'a> ScsiResponse<'a> {
295+
/// Retrieves the buffer containing data read from the device (if any).
296+
///
297+
/// # Returns
298+
/// `Option<&[u8]>`: A slice of the data read from the device, or `None` if no read buffer was assigned.
299+
///
300+
/// # Safety
301+
/// - If the buffer pointer is `NULL`, the method returns `None` and avoids dereferencing it.
302+
#[must_use]
303+
pub fn read_buffer(&self) -> Option<&'a [u8]> {
304+
if self.0.packet.in_data_buffer.is_null() {
305+
return None;
306+
}
307+
unsafe {
308+
Some(core::slice::from_raw_parts(
309+
self.0.packet.in_data_buffer.cast(),
310+
self.0.packet.in_transfer_length as usize,
311+
))
312+
}
313+
}
314+
315+
/// Retrieves the buffer containing sense data returned by the device (if any).
316+
///
317+
/// # Returns
318+
/// `Option<&[u8]>`: A slice of the sense data, or `None` if no sense data buffer was assigned.
319+
///
320+
/// # Safety
321+
/// - If the buffer pointer is `NULL`, the method returns `None` and avoids dereferencing it.
322+
#[must_use]
323+
pub fn sense_data(&self) -> Option<&'a [u8]> {
324+
if self.0.packet.sense_data.is_null() {
325+
return None;
326+
}
327+
unsafe {
328+
Some(core::slice::from_raw_parts(
329+
self.0.packet.sense_data.cast(),
330+
self.0.packet.sense_data_length as usize,
331+
))
332+
}
333+
}
334+
335+
/// Retrieves the status of the host adapter after executing the SCSI request.
336+
///
337+
/// # Returns
338+
/// [`ScsiIoHostAdapterStatus`]: The status code indicating the result of the operation from the host adapter.
339+
#[must_use]
340+
pub const fn host_adapter_status(&self) -> ScsiIoHostAdapterStatus {
341+
self.0.packet.host_adapter_status
342+
}
343+
344+
/// Retrieves the status of the target device after executing the SCSI request.
345+
///
346+
/// # Returns
347+
/// [`ScsiIoTargetStatus`]: The status code returned by the target device.
348+
#[must_use]
349+
pub const fn target_status(&self) -> ScsiIoTargetStatus {
350+
self.0.packet.target_status
351+
}
352+
}

‎uefi/src/proto/scsi/pass_thru.rs

+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
3+
//! Extended SCSI Pass Thru protocols.
4+
5+
use super::{ScsiRequest, ScsiResponse};
6+
use crate::mem::{AlignedBuffer, PoolAllocation};
7+
use crate::proto::device_path::PoolDevicePathNode;
8+
use crate::proto::unsafe_protocol;
9+
use crate::StatusExt;
10+
use core::alloc::LayoutError;
11+
use core::ptr::{self, NonNull};
12+
use uefi_raw::protocol::device_path::DevicePathProtocol;
13+
use uefi_raw::protocol::scsi::{
14+
ExtScsiPassThruMode, ExtScsiPassThruProtocol, SCSI_TARGET_MAX_BYTES,
15+
};
16+
use uefi_raw::Status;
17+
18+
/// Structure representing a SCSI target address.
19+
pub type ScsiTarget = [u8; SCSI_TARGET_MAX_BYTES];
20+
21+
/// Structure representing a fully-qualified device address, consisting of SCSI target and LUN.
22+
#[derive(Clone, Debug)]
23+
pub struct ScsiTargetLun(ScsiTarget, u64);
24+
impl Default for ScsiTargetLun {
25+
fn default() -> Self {
26+
Self([0xFF; SCSI_TARGET_MAX_BYTES], 0)
27+
}
28+
}
29+
30+
/// Enables interaction with SCSI devices using the Extended SCSI Pass Thru protocol.
31+
///
32+
/// This protocol allows communication with SCSI devices connected to the system,
33+
/// providing methods to send commands, reset devices, and enumerate SCSI targets.
34+
///
35+
/// This API offers a safe and convenient, yet still low-level interface to SCSI devices.
36+
/// It is designed as a foundational layer, leaving higher-level abstractions responsible for implementing
37+
/// richer storage semantics, device-specific commands, and advanced use cases.
38+
///
39+
/// # UEFI Spec Description
40+
/// Provides services that allow SCSI Pass Thru commands to be sent to SCSI devices attached to a SCSI channel. It also
41+
/// allows packet-based commands (ATAPI cmds) to be sent to ATAPI devices attached to a ATA controller.
42+
#[derive(Debug)]
43+
#[repr(transparent)]
44+
#[unsafe_protocol(ExtScsiPassThruProtocol::GUID)]
45+
pub struct ExtScsiPassThru(ExtScsiPassThruProtocol);
46+
47+
impl ExtScsiPassThru {
48+
/// Retrieves the mode structure for the Extended SCSI Pass Thru protocol.
49+
///
50+
/// # Returns
51+
/// The [`ExtScsiPassThruMode`] structure containing configuration details of the protocol.
52+
#[must_use]
53+
pub fn mode(&self) -> ExtScsiPassThruMode {
54+
let mut mode = unsafe { (*self.0.passthru_mode).clone() };
55+
mode.io_align = mode.io_align.max(1); // 0 and 1 is the same, says UEFI spec
56+
mode
57+
}
58+
59+
/// Retrieves the I/O buffer alignment required by this SCSI channel.
60+
///
61+
/// # Returns
62+
/// - A `u32` value representing the required I/O alignment.
63+
#[must_use]
64+
pub fn io_align(&self) -> u32 {
65+
self.mode().io_align
66+
}
67+
68+
/// Allocates an I/O buffer with the necessary alignment for this SCSI channel.
69+
///
70+
/// You can alternatively do this yourself using the [`AlignedBuffer`] helper directly.
71+
/// The Scsi api will validate that your buffers have the correct alignment and crash
72+
/// if they don't.
73+
///
74+
/// # Parameters
75+
/// - `len`: The size (in bytes) of the buffer to allocate.
76+
///
77+
/// # Returns
78+
/// [`AlignedBuffer`] containing the allocated memory.
79+
///
80+
/// # Errors
81+
/// This method can fail due to alignment or memory allocation issues.
82+
pub fn alloc_io_buffer(&self, len: usize) -> Result<AlignedBuffer, LayoutError> {
83+
AlignedBuffer::from_size_align(len, self.io_align() as usize)
84+
}
85+
86+
/// Iterate over all potential SCSI devices on this channel.
87+
///
88+
/// # Warning
89+
/// Depending on the UEFI implementation, this does not only return all actually available devices.
90+
/// Most implementations instead return a list of all possible fully-qualified device addresses.
91+
/// You have to probe for availability yourself, using [`ScsiDevice::execute_command`].
92+
///
93+
/// # Returns
94+
/// [`ScsiTargetLunIterator`] to iterate through connected SCSI devices.
95+
#[must_use]
96+
pub fn iter_devices(&self) -> ScsiTargetLunIterator<'_> {
97+
ScsiTargetLunIterator {
98+
proto: &self.0,
99+
prev: ScsiTargetLun::default(),
100+
}
101+
}
102+
103+
/// Resets the SCSI channel associated with the protocol.
104+
///
105+
/// The EFI_EXT_SCSI_PASS_THRU_PROTOCOL.ResetChannel() function resets a SCSI channel.
106+
/// This operation resets all the SCSI devices connected to the SCSI channel.
107+
///
108+
/// # Returns
109+
/// [`Result<()>`] indicating the success or failure of the operation.
110+
///
111+
/// # Errors
112+
/// - [`Status::UNSUPPORTED`] The SCSI channel does not support a channel reset operation.
113+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to reset the SCSI channel.
114+
/// - [`Status::TIMEOUT`] A timeout occurred while attempting to reset the SCSI channel.
115+
pub fn reset_channel(&mut self) -> crate::Result<()> {
116+
unsafe { (self.0.reset_channel)(&mut self.0).to_result() }
117+
}
118+
}
119+
120+
/// Structure representing a potential ScsiDevice.
121+
///
122+
/// In the UEFI Specification, this corresponds to a (SCSI target, LUN) tuple.
123+
///
124+
/// # Warning
125+
/// This does not actually have to correspond to an actual device!
126+
/// You have to probe for availability before doing anything meaningful with it.
127+
#[derive(Clone, Debug)]
128+
pub struct ScsiDevice<'a> {
129+
proto: &'a ExtScsiPassThruProtocol,
130+
target_lun: ScsiTargetLun,
131+
}
132+
impl ScsiDevice<'_> {
133+
fn proto_mut(&mut self) -> *mut ExtScsiPassThruProtocol {
134+
ptr::from_ref(self.proto).cast_mut()
135+
}
136+
137+
/// Returns the SCSI target address of the potential device.
138+
#[must_use]
139+
pub const fn target(&self) -> &ScsiTarget {
140+
&self.target_lun.0
141+
}
142+
143+
/// Returns the logical unit number (LUN) of the potential device.
144+
#[must_use]
145+
pub const fn lun(&self) -> u64 {
146+
self.target_lun.1
147+
}
148+
149+
/// Get the final device path node for this device.
150+
///
151+
/// For a full [`crate::proto::device_path::DevicePath`] pointing to this device, this needs to be appended to
152+
/// the controller's device path.
153+
pub fn path_node(&self) -> crate::Result<PoolDevicePathNode> {
154+
unsafe {
155+
let mut path_ptr: *const DevicePathProtocol = ptr::null();
156+
(self.proto.build_device_path)(
157+
self.proto,
158+
self.target().as_ptr(),
159+
self.lun(),
160+
&mut path_ptr,
161+
)
162+
.to_result()?;
163+
NonNull::new(path_ptr.cast_mut())
164+
.map(|p| PoolDevicePathNode(PoolAllocation::new(p.cast())))
165+
.ok_or(Status::OUT_OF_RESOURCES.into())
166+
}
167+
}
168+
169+
/// Resets the potential SCSI device represented by this instance.
170+
///
171+
/// The `EFI_EXT_SCSI_PASS_THRU_PROTOCOL.ResetTargetLun()` function resets the SCSI logical unit
172+
/// specified by `Target` and `Lun`. This allows for recovering a device that may be in an error state
173+
/// or requires reinitialization. The function behavior is dependent on the SCSI channel's capability
174+
/// to perform target resets.
175+
///
176+
/// # Returns
177+
/// [`Result<()>`] indicating the success or failure of the operation.
178+
///
179+
/// # Errors
180+
/// - [`Status::UNSUPPORTED`] The SCSI channel does not support a target reset operation.
181+
/// - [`Status::INVALID_PARAMETER`] The `Target` or `Lun` values are invalid.
182+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to reset the SCSI device
183+
/// specified by `Target` and `Lun`.
184+
/// - [`Status::TIMEOUT`] A timeout occurred while attempting to reset the SCSI device specified
185+
/// by `Target` and `Lun`.
186+
pub fn reset(&mut self) -> crate::Result<()> {
187+
unsafe {
188+
(self.proto.reset_target_lun)(self.proto_mut(), self.target_lun.0.as_ptr(), self.lun())
189+
.to_result()
190+
}
191+
}
192+
193+
/// Sends a SCSI command to the potential target device and retrieves the response.
194+
///
195+
/// This method sends a SCSI Request Packet to a SCSI device attached to the SCSI channel.
196+
/// It supports both blocking and nonblocking I/O. Blocking I/O is mandatory, while
197+
/// nonblocking I/O is optional and dependent on the driver's implementation.
198+
///
199+
/// # Parameters
200+
/// - `scsi_req`: The [`ScsiRequest`] containing the command and data to send to the device.
201+
///
202+
/// # Returns
203+
/// [`ScsiResponse`] containing the results of the operation, such as data and status.
204+
///
205+
/// # Errors
206+
/// - [`Status::BAD_BUFFER_SIZE`] The SCSI Request Packet was not executed because the data
207+
/// buffer size exceeded the allowed transfer size for a single command. The number of bytes
208+
/// that could be transferred is returned in `InTransferLength` or `OutTransferLength`.
209+
/// - [`Status::NOT_READY`] The SCSI Request Packet could not be sent because too many packets
210+
/// are already queued. The caller may retry later.
211+
/// - [`Status::DEVICE_ERROR`] A device error occurred while attempting to send the SCSI Request Packet.
212+
/// Additional status information is available in `HostAdapterStatus`, `TargetStatus`, `SenseDataLength`,
213+
/// and `SenseData`.
214+
/// - [`Status::INVALID_PARAMETER`] The `Target`, `Lun`, or the contents of `ScsiRequestPacket` are invalid.
215+
/// The SCSI Request Packet was not sent, and no additional status information is available.
216+
/// - [`Status::UNSUPPORTED`] The command described by the SCSI Request Packet is not supported by the
217+
/// host adapter, including unsupported bi-directional SCSI commands. The SCSI Request Packet was not
218+
/// sent, and no additional status information is available.
219+
/// - [`Status::TIMEOUT`] A timeout occurred while executing the SCSI Request Packet. Additional status
220+
/// information is available in `HostAdapterStatus`, `TargetStatus`, `SenseDataLength`, and `SenseData`.
221+
pub fn execute_command<'req>(
222+
&mut self,
223+
mut scsi_req: ScsiRequest<'req>,
224+
) -> crate::Result<ScsiResponse<'req>> {
225+
unsafe {
226+
(self.proto.pass_thru)(
227+
self.proto_mut(),
228+
self.target_lun.0.as_ptr(),
229+
self.target_lun.1,
230+
&mut scsi_req.packet,
231+
ptr::null_mut(),
232+
)
233+
.to_result_with_val(|| ScsiResponse(scsi_req))
234+
}
235+
}
236+
}
237+
238+
/// An iterator over SCSI devices available on the channel.
239+
#[derive(Debug)]
240+
pub struct ScsiTargetLunIterator<'a> {
241+
proto: &'a ExtScsiPassThruProtocol,
242+
prev: ScsiTargetLun,
243+
}
244+
impl<'a> Iterator for ScsiTargetLunIterator<'a> {
245+
type Item = ScsiDevice<'a>;
246+
247+
fn next(&mut self) -> Option<Self::Item> {
248+
// get_next_target_lun() takes the target as a double ptr, meaning that the spec allows
249+
// the implementation to return us a new buffer (most impls don't actually seem to do though)
250+
let mut target: *mut u8 = self.prev.0.as_mut_ptr();
251+
let result =
252+
unsafe { (self.proto.get_next_target_lun)(self.proto, &mut target, &mut self.prev.1) };
253+
if target != self.prev.0.as_mut_ptr() {
254+
// impl has returned us a new pointer instead of writing in our buffer, copy back
255+
unsafe {
256+
target.copy_to(self.prev.0.as_mut_ptr(), SCSI_TARGET_MAX_BYTES);
257+
}
258+
}
259+
let scsi_device = ScsiDevice {
260+
proto: self.proto,
261+
target_lun: self.prev.clone(),
262+
};
263+
match result {
264+
Status::SUCCESS => Some(scsi_device),
265+
Status::NOT_FOUND => None,
266+
_ => panic!("Must not happen according to spec!"),
267+
}
268+
}
269+
}

0 commit comments

Comments
 (0)
Please sign in to comment.