Skip to content

Commit 3787821

Browse files
committed
uefi: Add safe protocol bindings for EFI_EXT_SCSI_PASS_THRU_PROTOCOL
1 parent f0527d1 commit 3787821

File tree

4 files changed

+603
-0
lines changed

4 files changed

+603
-0
lines changed

Diff for: 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

Diff for: 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;

Diff for: uefi/src/proto/scsi/mod.rs

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

0 commit comments

Comments
 (0)