Skip to content

Commit fddb8ef

Browse files
committed
uefi: Add safe protocol bindings for EFI_EXT_SCSI_PASS_THRU_PROTOCOL
1 parent f453c6b commit fddb8ef

File tree

5 files changed

+605
-1
lines changed

5 files changed

+605
-1
lines changed

uefi-raw/src/protocol/scsi.rs

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

+1
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

+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

+362
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::time::Duration;
10+
use core::ptr;
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+
}

0 commit comments

Comments
 (0)