Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ff4607d

Browse files
committedMar 24, 2025··
uefi: Add safe protocol bindings for EFI_EXT_SCSI_PASS_THRU_PROTOCOL
1 parent cbd28fe commit ff4607d

File tree

9 files changed

+729
-2
lines changed

9 files changed

+729
-2
lines changed
 

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub struct ScsiIoScsiRequestPacket {
8484
///
8585
/// A `timeout` value of 0 indicates that the function will wait indefinitely for
8686
/// the execution to complete. If the execution time exceeds the specified `timeout`
87-
/// (greater than 0), the function will return `EFI_TIMEOUT`.
87+
/// (greater than 0), the function will return [`Status::TIMEOUT`].
8888
pub timeout: u64,
8989

9090
/// A pointer to the data buffer for reading from the device in read and bidirectional commands.

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

Lines changed: 2 additions & 0 deletions
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",
Lines changed: 7 additions & 0 deletions
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+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
assert_eq!(scsi_ctrl_handles.len(), 2);
15+
16+
let mut found_drive = false;
17+
18+
for handle in scsi_ctrl_handles {
19+
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
20+
for device in scsi_pt.iter_devices() {
21+
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
22+
// 3.6 INQUIRY command
23+
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
24+
.with_timeout(core::time::Duration::from_millis(500))
25+
.with_command_data(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00])
26+
.unwrap()
27+
.with_read_buffer(255)
28+
.unwrap()
29+
.build();
30+
let Ok(response) = device.execute_command(request) else {
31+
continue; // no device
32+
};
33+
let bfr = response.read_buffer().unwrap();
34+
// more no device checks
35+
if bfr.len() < 32 {
36+
continue;
37+
}
38+
if bfr[0] & 0b00011111 == 0x1F {
39+
continue;
40+
}
41+
42+
// found device
43+
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
44+
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
45+
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
46+
info!(
47+
"Found Testdisk at: {:?} | {}",
48+
device.target(),
49+
device.lun()
50+
);
51+
found_drive = true;
52+
}
53+
}
54+
}
55+
56+
assert!(found_drive);
57+
}
58+
59+
fn test_reusing_buffer_api() {
60+
let scsi_ctrl_handles = uefi::boot::find_handles::<ExtScsiPassThru>().unwrap();
61+
assert_eq!(scsi_ctrl_handles.len(), 2);
62+
63+
let mut found_drive = false;
64+
65+
for handle in scsi_ctrl_handles {
66+
let scsi_pt = uefi::boot::open_protocol_exclusive::<ExtScsiPassThru>(handle).unwrap();
67+
let mut cmd_bfr = scsi_pt.alloc_io_buffer(6).unwrap();
68+
cmd_bfr.copy_from(&[0x12, 0x00, 0x00, 0x00, 0xFF, 0x00]);
69+
let mut read_bfr = scsi_pt.alloc_io_buffer(255).unwrap();
70+
71+
for device in scsi_pt.iter_devices() {
72+
// see: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf
73+
// 3.6 INQUIRY command
74+
let request = ScsiRequestBuilder::read(scsi_pt.io_align())
75+
.with_timeout(core::time::Duration::from_millis(500))
76+
.use_command_buffer(&mut cmd_bfr)
77+
.unwrap()
78+
.use_read_buffer(&mut read_bfr)
79+
.unwrap()
80+
.build();
81+
let Ok(response) = device.execute_command(request) else {
82+
continue; // no device
83+
};
84+
let bfr = response.read_buffer().unwrap();
85+
// more no device checks
86+
if bfr.len() < 32 {
87+
continue;
88+
}
89+
if bfr[0] & 0b00011111 == 0x1F {
90+
continue;
91+
}
92+
93+
// found device
94+
let vendor_id = core::str::from_utf8(&bfr[8..16]).unwrap().trim();
95+
let product_id = core::str::from_utf8(&bfr[16..32]).unwrap().trim();
96+
if vendor_id == "uefi-rs" && product_id == "ExtScsiPassThru" {
97+
info!(
98+
"Found Testdisk at: {:?} | {}",
99+
device.target(),
100+
device.lun()
101+
);
102+
found_drive = true;
103+
}
104+
}
105+
}
106+
107+
assert!(found_drive);
108+
}

‎uefi/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- Added `boot::signal_event`.
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.
7+
- Added `proto::scsi::pass_thru::ExtScsiPassThru`.
78

89
## Changed
910
- **Breaking:** Removed `BootPolicyError` as `BootPolicy` construction is no

‎uefi/src/proto/mod.rs

Lines changed: 2 additions & 0 deletions
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

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

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

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

‎xtask/src/qemu.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
456456
add_pflash_args(&mut cmd, &ovmf_paths.code, PflashMode::ReadOnly);
457457
add_pflash_args(&mut cmd, &ovmf_vars, PflashMode::ReadWrite);
458458

459+
// Configure SCSI Controller
460+
cmd.arg("-device");
461+
cmd.arg("virtio-scsi-pci,id=scsi");
462+
459463
// Mount a local directory as a FAT partition.
460464
cmd.arg("-drive");
461465
let mut drive_arg = OsString::from("format=raw,file=fat:rw:");
@@ -471,9 +475,11 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> {
471475
create_mbr_test_disk(&test_disk)?;
472476

473477
cmd.arg("-drive");
474-
let mut drive_arg = OsString::from("format=raw,file=");
478+
let mut drive_arg = OsString::from("if=none,id=disk1,format=raw,file=");
475479
drive_arg.push(test_disk.clone());
476480
cmd.arg(drive_arg);
481+
cmd.arg("-device"); // attach disk to SCSI controller
482+
cmd.arg("scsi-hd,drive=disk1,vendor=uefi-rs,product=ExtScsiPassThru");
477483

478484
let qemu_monitor_pipe = Pipe::new(tmp_dir, "qemu-monitor")?;
479485
let serial_pipe = Pipe::new(tmp_dir, "serial")?;

0 commit comments

Comments
 (0)
Please sign in to comment.