diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index 15a8d793f..7dd2014d3 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -1,5 +1,9 @@ # uefi-raw - [Unreleased] +## Added + +- Added `ScsiIoProtocol`. + ## Changed - `maximum_capsule_size` of `query_capsule_capabilities` now takes a *mut u64 instead of a *mut usize. diff --git a/uefi-raw/src/protocol/mod.rs b/uefi-raw/src/protocol/mod.rs index 16616ca0a..d62300ed1 100644 --- a/uefi-raw/src/protocol/mod.rs +++ b/uefi-raw/src/protocol/mod.rs @@ -16,4 +16,5 @@ pub mod memory_protection; pub mod misc; pub mod network; pub mod rng; +pub mod scsi; pub mod shell_params; diff --git a/uefi-raw/src/protocol/scsi.rs b/uefi-raw/src/protocol/scsi.rs new file mode 100644 index 000000000..c90ba6b3c --- /dev/null +++ b/uefi-raw/src/protocol/scsi.rs @@ -0,0 +1,160 @@ +use core::ffi::c_void; + +use crate::{guid, Event, Guid, Status}; + +/// TODO: use #define TARGET_MAX_BYTES 0x10, the limit of target. +#[allow(unused)] +const TARGET_MAX_BYTES: u32 = 0x10; + +newtype_enum! { + /// DataDirection + #[derive(Default)] + pub enum DataDirection: u8 => { + READ = 0, + WRITE = 1, + BIDIRECTIONAL = 2, + } +} + +newtype_enum! { + /// HostAdapterStatus + #[derive(Default)] + pub enum HostAdapterStatus: u8 => { + + /// EFI_SCSI_IO_STATUS_HOST_ADAPTER_OK + OK = 0x00, + TIMEOUT_COMMAND = 0x09, + TIMEOUT = 0x0b, + MESSAGE_REJECT = 0x0d, + BUS_RESET = 0x0e, + PARITY_ERROR = 0x0f, + REQUEST_SENSE_FAILED = 0x10, + SELECTION_TIMEOUT = 0x11, + DATA_OVERRUN_UNDERRUN = 0x12, + BUS_FREE = 0x13, + PHASE_ERROR = 0x14, + OTHER = 0x7f, + } +} + +newtype_enum! { + /// TargetStatus + #[derive(Default)] + pub enum TargetStatus: u8 => { + /// EFI_SCSI_IO_STATUS_TARGET_GOOD + GOOD = 0x00, + CHECK_CONDITION = 0x02, + CONDITION_MET = 0x04, + BUSY = 0x08, + INTERMEDIATE = 0x10, + INTERMEDIATE_CONDITION_MET = 0x14, + RESERVATION_CONFLICT = 0x18, + COMMAND_TERMINATED = 0x22, + QUEUE_FULL = 0x28, + } +} + +/// EFI_SCSI_IO_SCSI_REQUEST_PACKET +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct ScsiIoScsiRequestPacket { + /// Timeout: The timeout, in 100 ns units, to use for the execution of this SCSI Request Packet. + /// A Timeout value of 0 means that this function will wait indefinitely for the SCSI Request Packet to execute. + /// If Timeout is greater than zero, then this function will return EFI_TIMEOUT if the time required to execute the SCSI Request Packet is greater than Timeout . + pub timeout: u64, + + /// []DataBuffer: A pointer to the data buffer to transfer from or to the SCSI device. + /// InDataBuffer: A pointer to the data buffer to transfer between the SCSI controller and the SCSI device for SCSI READ command. + /// For all SCSI WRITE Commands this must point to NULL . + pub in_data_buffer: *mut c_void, + /// OutDataBuffer: A pointer to the data buffer to transfer between the SCSI controller and the SCSI device for SCSI WRITE command. + /// For all SCSI READ commands this field must point to NULL . + pub out_data_buffer: *mut c_void, + /// SenseData: A pointer to the sense data that was generated by the execution of the SCSI Request Packet. + pub sense_data: *mut c_void, + /// Cdb: A pointer to buffer that contains the Command Data Block to send to the SCSI device. + pub cdb: *mut c_void, + + /// InTransferLength: On Input, the size, in bytes, of InDataBuffer . + /// On output, the number of bytes transferred between the SCSI controller and the SCSI device. + /// If InTransferLength is larger than the SCSI controller can handle, no data will be transferred, + /// InTransferLength will be updated to contain the number of bytes that the SCSI controller is able to transfer, and EFI_BAD_BUFFER_SIZE will be returned. + pub in_transfer_length: u32, + /// OutTransferLength: On Input, the size, in bytes of OutDataBuffer . + /// On Output, the Number of bytes transferred between SCSI Controller and the SCSI device. + /// If OutTransferLength is larger than the SCSI controller can handle, no data will be transferred, + /// OutTransferLength will be updated to contain the number of bytes that the SCSI controller is able to transfer, and EFI_BAD_BUFFER_SIZE will be returned. + pub out_transfer_length: u32, + + /// CdbLength: The length, in bytes, of the buffer Cdb . + /// The standard values are 6, 10, 12, and 16, but other values are possible if a variable length CDB is used. + pub cdb_length: u8, + /// DataDirection: The direction of the data transfer. 0 for reads, 1 for writes. + /// A value of 2 is Reserved for Bi-Directional SCSI commands. For example XDREADWRITE. + /// All other values are reserved, and must not be used. + pub data_direction: DataDirection, + /// HostAdapterStatus: The status of the SCSI Host Controller that produces the SCSI bus + /// where the SCSI device attached when the SCSI Request Packet was executed on the SCSI Controller. + pub host_adapter_status: HostAdapterStatus, + /// TargetStatus: The status returned by the SCSI device when the SCSI Request Packet was executed. + pub target_status: TargetStatus, + /// SenseDataLength: On input, the length in bytes of the SenseData buffer. + /// On output, the number of bytes written to the SenseData buffer. + pub sense_data_length: u8, +} + +newtype_enum! { + /// DeviceType + /// Defined in the SCSI Primary Commands standard (e.g., SPC-4) + #[derive(Default)] + pub enum DeviceType: u8 => { + DISK = 0x00, // Disk device + TAPE = 0x01, // Tape device + PRINTER = 0x02,// Printer + PROCESSOR = 0x03,// Processor + WORM = 0x04,// Write-once read-multiple + CDROM = 0x05,// CD or DVD device + SCANNER = 0x06,// Scanner device + OPTICAL = 0x07,// Optical memory device + MEDIUMCHANGER = 0x08,// Medium Changer device + COMMUNICATION = 0x09,// Communications device + + + MFI_A = 0x0A, // Obsolete + MFI_B = 0x0B, // Obsolete + MFI_RAID = 0x0C, // Storage array controller + MFI_SES = 0x0D, // Enclosure services device + MFI_RBC = 0x0E, // Simplified direct-access + MFI_OCRW = 0x0F, // Optical card reader/ + MFI_BRIDGE = 0x10, // Bridge Controller + MFI_OSD = 0x11, // Object-based Storage + + RESERVED_LOW = 0x12, // Reserved (low) + RESERVED_HIGH = 0x1E, // Reserved (high) + UNKNOWN = 0x1F, // Unknown no device type + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct ScsiIoProtocol { + //TODO: return deviceType + pub get_device_type: + unsafe extern "efiapi" fn(this: *const Self, device_type: *mut DeviceType) -> Status, + //TODO: raw pointer need to fixed, see uefi-rs service code like pointer *u8 + pub get_device_location: + unsafe extern "efiapi" fn(this: *const Self, target: *mut *mut u8, lun: *mut u64) -> Status, + pub reset_bus: unsafe extern "efiapi" fn(this: *mut Self) -> Status, + pub reset_device: unsafe extern "efiapi" fn(this: *mut Self) -> Status, + pub execute_scsi_command: unsafe extern "efiapi" fn( + this: *const Self, + packet: *mut ScsiIoScsiRequestPacket, + event: Event, + ) -> Status, + pub io_align: u32, +} + +/// 15.4. EFI SCSI I/O Protocol +impl ScsiIoProtocol { + pub const GUID: Guid = guid!("932f47e6-2362-4002-803e-3cd54b138f85"); +} diff --git a/uefi-test-runner/examples/scsi.rs b/uefi-test-runner/examples/scsi.rs new file mode 100644 index 000000000..2840bd8f5 --- /dev/null +++ b/uefi-test-runner/examples/scsi.rs @@ -0,0 +1,132 @@ +// ANCHOR: all +// ANCHOR: features +#![no_main] +#![no_std] +// ANCHOR_END: features + +extern crate alloc; + +use alloc::vec; +use core::mem; + +use log::info; + +// ANCHOR: use +use uefi::prelude::*; +use uefi::proto::scsi::{ScsiIo, ScsiRequestPacket}; + +// ANCHOR_END: use + +// ANCHOR: entry +#[entry] +fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { + // ANCHOR_END: entry + // ANCHOR: services + uefi::helpers::init(&mut system_table).unwrap(); + let boot_services = system_table.boot_services(); + // ANCHOR_END: services + + // ANCHOR: params, all api OK, but memory panic when return, maybe the vec. + test_scsi_io(boot_services); + // ANCHOR_END: params, panic at uefi/src/allocator.rs#L130 (*boot_services()).free_pool(ptr).unwrap(); + + + // ANCHOR: stall + boot_services.stall(10_000_000); + // ANCHOR_END: stall + + // ANCHOR: return + Status::SUCCESS +} +// ANCHOR_END: return + +pub fn test_scsi_io(bt: &BootServices) { + info!("Running loaded Scsi protocol test"); + + let handle = bt + .get_handle_for_protocol::() + .expect("Failed to get handles for `ScsiIo` protocol"); + + let mut scsi_protocol = bt + .open_protocol_exclusive::(handle) + .expect("Founded ScsiIo Protocol but open failed"); + + // value efi_reset_fn is the type of ResetSystemFn, a function pointer + + let result = scsi_protocol.get_device_type(); + info!("SCSI_IO Protocol get device_type: {:?}", result); + + let result = scsi_protocol.io_align(); + info!("SCSI_IO Protocol's io_align: {:?}", result); + + let result = scsi_protocol.get_device_location(); + info!("SCSI_IO Protocol get dev location: {:?}", result); + + let result = scsi_protocol.reset_bus(); + info!("SCSI_IO Protocol reset bus test: {:?}", result); + + let result = scsi_protocol.reset_device(); + info!("SCSI_IO Protocol reset dev test: {:?}", result); + + bt.stall(10_000); + + let mut packet_tur: ScsiRequestPacket = ScsiRequestPacket::default(); + packet_tur.is_a_write_packet = false; + packet_tur.cdb = vec![0x00, 0, 0, 0, 0, 0x00]; + packet_tur.timeout = 0; + info!("packet_tur: {:?}", packet_tur); + let result = scsi_protocol.execute_scsi_command(&mut packet_tur, None); + info!("=================SCSI_IO Protocol exec scsi command [TestUnitReady] test: {:?}", result); + + let mut packet_icmd: ScsiRequestPacket = ScsiRequestPacket::default(); + packet_icmd.is_a_write_packet = false; + packet_icmd.cdb = vec![0x12, 0x01, 0x00, 0, 0, 0x00]; + packet_icmd.data_buffer = vec![0; 96]; + packet_icmd.sense_data = vec![0; 18]; + packet_icmd.timeout = 0; + let result = scsi_protocol.execute_scsi_command(&mut packet_icmd, None); + info!("=================SCSI_IO Protocol exec scsi command [InquiryCommand] test: {:?}", result); + + // drop(packet) + // now send Req is ok. but it seems couldn't receive Resp. +} + +/* +// The InquiryCommand + + +// sense_data with UINT8*18 +/// +/// Error codes 70h and 71h sense data format +/// +typedef struct { +UINT8 Error_Code : 7; +UINT8 Valid : 1; +UINT8 Segment_Number; +UINT8 Sense_Key : 4; +UINT8 Reserved_21 : 1; +UINT8 Ili : 1; +UINT8 Reserved_22 : 2; +UINT8 Information_3_6[4]; +UINT8 Addnl_Sense_Length; ///< Additional sense length (n-7) +UINT8 Vendor_Specific_8_11[4]; +UINT8 Addnl_Sense_Code; ///< Additional sense code +UINT8 Addnl_Sense_Code_Qualifier; ///< Additional sense code qualifier +UINT8 Field_Replaceable_Unit_Code; ///< Field replaceable unit code +UINT8 Reserved_15_17[3]; +} EFI_SCSI_SENSE_DATA; + + + +// in_data_buf with UINT8 * 96 +typedef struct { + UINT8 Peripheral_Type : 5; + UINT8 Peripheral_Qualifier : 3; + UINT8 DeviceType_Modifier : 7; + UINT8 Rmb : 1; + UINT8 Version; + UINT8 Response_Data_Format; + UINT8 Addnl_Length; + UINT8 Reserved_5_95[95 - 5 + 1]; +} EFI_SCSI_INQUIRY_DATA; +*/ diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 8e7e3930e..4298bedf1 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -1,5 +1,9 @@ # uefi - [Unreleased] +## Added + +- Added `ScsiIo` Protocol. + ## Added - Added `RuntimeServices::update_capsule`. diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 5919b7afc..dbb750c9f 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -9,9 +9,12 @@ //! //! [`BootServices`]: crate::table::boot::BootServices#accessing-protocols -use crate::Identify; use core::ffi::c_void; +pub use uefi_macros::unsafe_protocol; + +use crate::Identify; + /// Common trait implemented by all standard UEFI protocols. /// /// You can derive the `Protocol` trait and specify the protocol's GUID using @@ -64,8 +67,6 @@ where } } -pub use uefi_macros::unsafe_protocol; - pub mod console; pub mod debug; pub mod device_path; @@ -76,6 +77,7 @@ pub mod misc; pub mod network; pub mod pi; pub mod rng; +pub mod scsi; pub mod security; pub mod shell_params; pub mod shim; diff --git a/uefi/src/proto/scsi.rs b/uefi/src/proto/scsi.rs new file mode 100644 index 000000000..c53d3f440 --- /dev/null +++ b/uefi/src/proto/scsi.rs @@ -0,0 +1,281 @@ +//! EFI SCSI I/O protocols. + +use alloc::vec::Vec; +use core::ffi::c_void; +use core::ptr; +use core::ptr::null_mut; + +use log::info; + +use uefi_raw::protocol::scsi; +use uefi_raw::protocol::scsi::{ + DataDirection, HostAdapterStatus, ScsiIoProtocol, ScsiIoScsiRequestPacket, TargetStatus, +}; + +use crate::{Event, Result, StatusExt}; +use crate::proto::unsafe_protocol; + +/// Protocol for who running in the EFI boot services environment such as code, typically drivers, able to access SCSI devices. +/// see example at `uefi-test-runner/examples/scsi.rs` +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(ScsiIoProtocol::GUID)] +pub struct ScsiIo(ScsiIoProtocol); + +/// Represents a scsi device location which {target, lun}. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct ScsiDeviceLocation { + /// Target ID + pub target: *mut u8, + /// Logical Unit Number + pub lun: u64, +} + +impl ScsiDeviceLocation { + /// constructor for ScsiDeviceLocation {target, lun} + pub fn new(target: *mut u8, lun: u64) -> Self { + ScsiDeviceLocation { target, lun } + } +} + +impl Default for ScsiDeviceLocation { + fn default() -> Self { + ScsiDeviceLocation { + target: null_mut(), + lun: 0, + } + } +} +impl ScsiIo { + /// Retrieves the device type information of the SCSI Device. + pub fn get_device_type(&self) -> Result { + let mut device_type = scsi::DeviceType::default(); + unsafe { (self.0.get_device_type)(&self.0, &mut device_type) } + .to_result_with_val(|| device_type) + } + + /// Retrieves the SCSI device location in the SCSI channel. + pub fn get_device_location(&self) -> Result { + let mut location = ScsiDeviceLocation::default(); + unsafe { (self.0.get_device_location)(&self.0, &mut location.target, &mut location.lun) } + .to_result_with_val(|| location) + } + /// Resets the SCSI Bus that the SCSI Device is attached to. + pub fn reset_bus(&mut self) -> Result { + unsafe { (self.0.reset_bus)(&mut self.0) }.to_result() + } + /// Resets the SCSI Device that is specified by the device handle that the SCSI I/O Protocol is attached. + pub fn reset_device(&mut self) -> Result { + unsafe { (self.0.reset_device)(&mut self.0) }.to_result() + } + + /// Sends a SCSI Request Packet to the SCSI Device for execution. + ///TODO: ScsiIoScsiRequestPacket must to refactor + pub fn execute_scsi_command( + &self, + packet: &mut ScsiRequestPacket, + event: Option, + ) -> Result { + info!("before: ffi_packet = {:?}", packet); + let in_packet = &mut (packet.convert_auto_request_packet()); + info!("before: raw_packet = {:?}", in_packet); + + let event_arg = match event { + Some(event) => event.as_ptr(), + None => ptr::null_mut(), + }; + + let status = unsafe { (self.0.execute_scsi_command)(&self.0, in_packet, event_arg) }; + info!("after: raw_packet = {:?}", in_packet); + // TODO: print log with raw dat/len about `ScsiIoScsiRequestPacket` + + status.to_result_with_val(|| packet.sync_from_request_packet(in_packet)) + } + + /// the value of ioAlign + pub fn io_align(&self) -> Result { + Ok(self.0.io_align) + } +} + +/// the rust FFI for `EFI_SCSI_IO_SCSI_REQUEST_PACKET` +#[derive(Debug, Default, Clone)] +pub struct ScsiRequestPacket { + /// whether the request is written scsi + pub is_a_write_packet: bool, + /// timeout + pub timeout: u64, + /// data_buffer is `in_data_buffer` or `out_data_buffer` + pub data_buffer: Vec, + /// SCSI's cdb, refer to T10 SPC + pub cdb: Vec, + /// SCSI's sense data, refer to T10 SPC, scsi resp return it + pub sense_data: Vec, + /// uefi_raw::protocol::scsi::DataDirection + pub data_direction: DataDirection, + /// uefi_raw::protocol::scsi::HostAdapterStatus, scsi resp status + pub host_adapter_status: HostAdapterStatus, + /// uefi_raw::protocol::scsi::TargetStatus, scsi resp status + pub target_status: TargetStatus, +} + +impl ScsiRequestPacket { + /// convert FFI `ScsiRequestPacket` to raw UEFI SCSI request packet `EFI_SCSI_IO_SCSI_REQUEST_PACKET` + pub fn convert_zero_request_packet(&mut self) -> ScsiIoScsiRequestPacket { + let packet: ScsiIoScsiRequestPacket = ScsiIoScsiRequestPacket { + timeout: self.timeout, + + in_data_buffer: null_mut(), + out_data_buffer: null_mut(), + sense_data: null_mut(), + cdb: self.cdb.as_mut_ptr() as *mut c_void, + + in_transfer_length: 0, + out_transfer_length: 0, + sense_data_length: 0, + cdb_length: self.cdb.len() as u8, + + data_direction: self.data_direction, + host_adapter_status: self.host_adapter_status, + target_status: self.target_status, + }; + packet + } + // auto convert + /// convert auto + pub fn convert_auto_request_packet(&mut self) -> ScsiIoScsiRequestPacket { + ScsiIoScsiRequestPacket { + timeout: 0, + + in_data_buffer: if (self.data_buffer.len() != 0) && !self.is_a_write_packet { + self.data_buffer.as_mut_ptr().cast::() + } else { + null_mut() + }, + out_data_buffer: if self.data_buffer.len() != 0 && self.is_a_write_packet { + self.data_buffer.as_mut_ptr().cast::() + } else { + null_mut() + }, + + sense_data: if self.sense_data.len() != 0 { + self.sense_data.as_mut_ptr().cast::() + } else { + null_mut() + }, + cdb: if self.cdb.len() != 0 { + self.cdb.as_mut_ptr().cast::() + } else { + null_mut() + }, + + in_transfer_length: if !self.is_a_write_packet { + self.data_buffer.len() as u32 + } else { + 0 + }, + out_transfer_length: if self.is_a_write_packet { + self.data_buffer.len() as u32 + } else { + 0 + }, + cdb_length: self.cdb.len() as u8, + sense_data_length: self.sense_data.len() as u8, + + data_direction: Default::default(), + host_adapter_status: Default::default(), + target_status: Default::default(), + } + } + + /// convert FFI `ScsiRequestPacket` to raw UEFI SCSI request packet `EFI_SCSI_IO_SCSI_REQUEST_PACKET` + pub fn convert_to_request_packet(&mut self) -> ScsiIoScsiRequestPacket { + if self.is_a_write_packet { + self._convert_to_write_request_packet() + } else { + self._convert_to_read_request_packet() + } + } + + /// `ScsiRequestPacket` FFI sync from raw_packet `ScsiIoScsiRequestPacket` by ptr. + pub fn sync_from_request_packet( + &mut self, + raw_packet: &mut ScsiIoScsiRequestPacket, + ) -> ScsiRequestPacket { + unsafe { + self.timeout = raw_packet.timeout; + // c (void* data, int len) => rust Vec + + self.cdb = Vec::from_raw_parts( + raw_packet.cdb as *mut u8, + raw_packet.cdb_length as usize, + isize::MAX as usize, + ); + self.sense_data = Vec::from_raw_parts( + raw_packet.sense_data as *mut u8, + raw_packet.sense_data_length as usize, + isize::MAX as usize, + ); + self.data_buffer = if self.is_a_write_packet { + Vec::from_raw_parts( + raw_packet.in_data_buffer as *mut u8, + raw_packet.in_transfer_length as usize, + isize::MAX as usize, + ) + } else { + Vec::from_raw_parts( + raw_packet.out_data_buffer as *mut u8, + raw_packet.out_transfer_length as usize, + isize::MAX as usize, + ) + }; + + self.data_direction = raw_packet.data_direction; + self.host_adapter_status = raw_packet.host_adapter_status; + self.target_status = raw_packet.target_status; + } + self.clone() + } + + fn _convert_to_read_request_packet(&mut self) -> ScsiIoScsiRequestPacket { + let packet: ScsiIoScsiRequestPacket = ScsiIoScsiRequestPacket { + timeout: self.timeout, + + in_data_buffer: self.data_buffer.as_mut_ptr() as *mut c_void, + out_data_buffer: null_mut(), + sense_data: self.sense_data.as_mut_ptr() as *mut c_void, + cdb: self.cdb.as_mut_ptr() as *mut c_void, + + in_transfer_length: self.data_buffer.len() as u32, + out_transfer_length: 0, + sense_data_length: self.sense_data.len() as u8, + cdb_length: self.cdb.len() as u8, + + data_direction: self.data_direction, + host_adapter_status: self.host_adapter_status, + target_status: self.target_status, + }; + packet + } + + fn _convert_to_write_request_packet(&mut self) -> ScsiIoScsiRequestPacket { + let packet: ScsiIoScsiRequestPacket = ScsiIoScsiRequestPacket { + timeout: self.timeout, + + in_data_buffer: null_mut(), + out_data_buffer: self.data_buffer.as_mut_ptr() as *mut c_void, + sense_data: self.sense_data.as_mut_ptr() as *mut c_void, + cdb: self.cdb.as_mut_ptr() as *mut c_void, + + in_transfer_length: 0, + out_transfer_length: self.data_buffer.len() as u32, + sense_data_length: self.sense_data.len() as u8, + cdb_length: self.cdb.len() as u8, + + data_direction: self.data_direction, + host_adapter_status: self.host_adapter_status, + target_status: self.target_status, + }; + packet + } +}