use crate::transport::Transport;
use crate::CLASS_MASS_STORAGE;
use core::fmt::Debug;
use num_enum::TryFromPrimitive;
use usb_device::bus::InterfaceNumber;
use usb_device::bus::UsbBus;
use usb_device::class::{ControlIn, UsbClass};
use usb_device::descriptor::DescriptorWriter;
#[cfg(feature = "bbb")]
use {
crate::fmt::debug,
crate::subclass::Command,
crate::transport::bbb::{BulkOnly, BulkOnlyError},
crate::transport::TransportError,
core::borrow::BorrowMut,
usb_device::bus::UsbBusAllocator,
usb_device::UsbError,
};
pub const SUBCLASS_SCSI: u8 = 0x06;
const TEST_UNIT_READY: u8 = 0x00;
const REQUEST_SENSE: u8 = 0x03;
const INQUIRY: u8 = 0x12;
const MODE_SENSE_6: u8 = 0x1A;
const MODE_SENSE_10: u8 = 0x5A;
const READ_10: u8 = 0x28;
#[cfg(feature = "extended_addressing")]
const READ_16: u8 = 0x88;
const READ_CAPACITY_10: u8 = 0x25;
const READ_CAPACITY_16: u8 = 0x9E;
const WRITE_10: u8 = 0x2A;
#[cfg(feature = "extended_addressing")]
const WRITE_16: u8 = 0x8A;
const READ_FORMAT_CAPACITIES: u8 = 0x23;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ScsiCommand {
Unknown {
cmd: u8,
},
Inquiry {
evpd: bool,
page_code: u8,
alloc_len: u16,
},
TestUnitReady,
RequestSense {
desc: bool,
alloc_len: u8,
},
ModeSense6 {
dbd: bool,
page_control: PageControl,
page_code: u8,
subpage_code: u8,
alloc_len: u8,
},
ModeSense10 {
dbd: bool,
page_control: PageControl,
page_code: u8,
subpage_code: u8,
alloc_len: u16,
},
ReadCapacity10,
ReadCapacity16 {
alloc_len: u32,
},
Read {
#[cfg(feature = "extended_addressing")]
lba: u64,
#[cfg(feature = "extended_addressing")]
len: u32,
#[cfg(not(feature = "extended_addressing"))]
lba: u32,
#[cfg(not(feature = "extended_addressing"))]
len: u16,
},
Write {
#[cfg(feature = "extended_addressing")]
lba: u64,
#[cfg(feature = "extended_addressing")]
len: u32,
#[cfg(not(feature = "extended_addressing"))]
lba: u32,
#[cfg(not(feature = "extended_addressing"))]
len: u16,
},
ReadFormatCapacities {
alloc_len: u16,
},
}
#[repr(u8)]
#[derive(Copy, Clone, Debug, TryFromPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PageControl {
CurrentValues = 0b00,
ChangeableValues = 0b01,
DefaultValues = 0b10,
SavedValues = 0b11,
}
#[allow(dead_code)]
fn parse_cb(cb: &[u8]) -> ScsiCommand {
match cb[0] {
TEST_UNIT_READY => ScsiCommand::TestUnitReady,
INQUIRY => ScsiCommand::Inquiry {
evpd: (cb[1] & 0b00000001) != 0,
page_code: cb[2],
alloc_len: u16::from_be_bytes([cb[3], cb[4]]),
},
REQUEST_SENSE => ScsiCommand::RequestSense {
desc: (cb[1] & 0b00000001) != 0,
alloc_len: cb[4],
},
READ_CAPACITY_10 => ScsiCommand::ReadCapacity10,
READ_CAPACITY_16 => ScsiCommand::ReadCapacity16 {
alloc_len: u32::from_be_bytes([cb[10], cb[11], cb[12], cb[13]]),
},
READ_10 => ScsiCommand::Read {
#[cfg(not(feature = "extended_addressing"))]
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]),
#[cfg(not(feature = "extended_addressing"))]
len: u16::from_be_bytes([cb[7], cb[8]]),
#[cfg(feature = "extended_addressing")]
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64,
#[cfg(feature = "extended_addressing")]
len: u16::from_be_bytes([cb[7], cb[8]]) as u32,
},
#[cfg(feature = "extended_addressing")]
READ_16 => ScsiCommand::Read {
lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()),
len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()),
},
WRITE_10 => ScsiCommand::Write {
#[cfg(not(feature = "extended_addressing"))]
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]),
#[cfg(not(feature = "extended_addressing"))]
len: u16::from_be_bytes([cb[7], cb[8]]),
#[cfg(feature = "extended_addressing")]
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64,
#[cfg(feature = "extended_addressing")]
len: u16::from_be_bytes([cb[7], cb[8]]) as u32,
},
#[cfg(feature = "extended_addressing")]
WRITE_16 => ScsiCommand::Write {
lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()),
len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()),
},
MODE_SENSE_6 => ScsiCommand::ModeSense6 {
dbd: (cb[1] & 0b00001000) != 0,
page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(),
page_code: cb[2] & 0b00111111,
subpage_code: cb[3],
alloc_len: cb[4],
},
MODE_SENSE_10 => ScsiCommand::ModeSense10 {
dbd: (cb[1] & 0b00001000) != 0,
page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(),
page_code: cb[2] & 0b00111111,
subpage_code: cb[3],
alloc_len: u16::from_be_bytes([cb[7], cb[8]]),
},
READ_FORMAT_CAPACITIES => ScsiCommand::ReadFormatCapacities {
alloc_len: u16::from_be_bytes([cb[7], cb[8]]),
},
cmd => ScsiCommand::Unknown { cmd },
}
}
pub struct Scsi<T: Transport> {
interface: InterfaceNumber,
pub(crate) transport: T,
}
#[cfg(feature = "bbb")]
impl<'alloc, Bus: UsbBus + 'alloc, Buf: BorrowMut<[u8]>> Scsi<BulkOnly<'alloc, Bus, Buf>> {
pub fn new(
alloc: &'alloc UsbBusAllocator<Bus>,
packet_size: u16,
max_lun: u8,
buf: Buf,
) -> Result<Self, BulkOnlyError> {
BulkOnly::new(alloc, packet_size, max_lun, buf).map(|transport| Self {
interface: alloc.interface(),
transport,
})
}
pub fn poll<F>(&mut self, mut callback: F) -> Result<(), UsbError>
where
F: FnMut(Command<ScsiCommand, Scsi<BulkOnly<'alloc, Bus, Buf>>>),
{
fn map_ignore<T>(res: Result<T, TransportError<BulkOnlyError>>) -> Result<(), UsbError> {
match res {
Ok(_)
| Err(TransportError::Usb(UsbError::WouldBlock))
| Err(TransportError::Error(_)) => Ok(()),
Err(TransportError::Usb(err)) => Err(err),
}
}
map_ignore(self.transport.read())?;
map_ignore(self.transport.write())?;
if let Some(raw_cb) = self.transport.get_command() {
if !self.transport.has_status() {
let lun = raw_cb.lun;
let kind = parse_cb(raw_cb.bytes);
debug!("usb: scsi: Command: {}", kind);
loop {
callback(Command {
class: self,
kind,
lun,
});
match self.transport.write() {
Err(TransportError::Error(BulkOnlyError::FullPacketExpected)) => {
continue;
}
Ok(_)
| Err(TransportError::Error(_))
| Err(TransportError::Usb(UsbError::WouldBlock)) => { }
Err(TransportError::Usb(err)) => {
return Err(err);
}
};
map_ignore(self.transport.read())?;
break;
}
}
}
Ok(())
}
}
impl<Bus, T> UsbClass<Bus> for Scsi<T>
where
Bus: UsbBus,
T: Transport<Bus = Bus>,
{
fn get_configuration_descriptors(
&self,
writer: &mut DescriptorWriter,
) -> usb_device::Result<()> {
writer.iad(
self.interface,
1,
CLASS_MASS_STORAGE,
SUBCLASS_SCSI,
T::PROTO,
None,
)?;
writer.interface(self.interface, CLASS_MASS_STORAGE, SUBCLASS_SCSI, T::PROTO)?;
self.transport.get_endpoint_descriptors(writer)?;
Ok(())
}
fn reset(&mut self) {
self.transport.reset()
}
fn control_in(&mut self, xfer: ControlIn<Bus>) {
self.transport.control_in(xfer)
}
}