Basic MSC disk working!
This commit is contained in:
parent
b5b1802a57
commit
c34091b4e8
@ -16,9 +16,11 @@
|
||||
// pub control: Control,
|
||||
// }
|
||||
|
||||
use super::control::Control;
|
||||
use crate::class::msc::subclass::scsi::enums::{PeripheralDeviceType, PeripheralQualifier};
|
||||
use crate::packed::PackedField;
|
||||
use super::Control;
|
||||
use crate::class::msc::subclass::scsi::enums::{
|
||||
PeripheralDeviceType, PeripheralQualifier, ResponseDataFormat, SpcVersion, TargetPortGroupSupport,
|
||||
};
|
||||
use crate::packed::BE;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
@ -30,9 +32,9 @@ packed_struct! {
|
||||
#[offset = 2*8, size = 8]
|
||||
page_code: u8,
|
||||
#[offset = 3*8, size = 16]
|
||||
allocation_length: u16,
|
||||
allocation_length: BE<u16>,
|
||||
#[offset = 5*8, size = 8]
|
||||
control: Control<T>,
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,18 +42,82 @@ impl InquiryCommand<[u8; InquiryCommand::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x12;
|
||||
}
|
||||
|
||||
// impl<T: AsRef<[u8]>> defmt::Format for InquiryCommand<T> {
|
||||
// fn format(&self, fmt: defmt::Formatter) {
|
||||
// fmt.
|
||||
// }
|
||||
// }
|
||||
|
||||
packed_struct! {
|
||||
/// Inquiry response can contain many extensions. We support only the minimum required 36 bytes.
|
||||
pub struct InquiryResponse<36> {
|
||||
#[offset = 0, size = 5]
|
||||
peripheral_qualifier: PeripheralQualifier,
|
||||
#[offset = 5, size = 3]
|
||||
#[offset = 0*8+0, size = 5]
|
||||
peripheral_device_type: PeripheralDeviceType,
|
||||
#[offset = 0*8+5, size = 3]
|
||||
peripheral_qualifier: PeripheralQualifier,
|
||||
/// A removable medium (RMB) bit set to zero indicates that the medium is not removable.
|
||||
/// A RMB bit set to one indicates that the medium is removable.
|
||||
#[offset = 1*8+7, size = 1]
|
||||
removable_medium: bool,
|
||||
/// Indicates the implemented version of the SPC standard
|
||||
#[offset = 2*8+0, size = 8]
|
||||
version: SpcVersion,
|
||||
/// The RESPONSE DATA FORMAT field indicates the format of the standard INQUIRY data and shall be set as shown in table 139.
|
||||
/// A RESPONSE DATA FORMAT field set to 2h indicates that the standard INQUIRY data is in the format defined in this standard.
|
||||
/// Response data format values less than 2h are obsolete. Response data format values greater than 2h are reserved.
|
||||
#[offset = 3*8+0, size = 4]
|
||||
response_data_format: ResponseDataFormat,
|
||||
/// A hierarchical support (HISUP) bit set to zero indicates the SCSI target device does not use the hierarchical addressing model to assign LUNs to logical units.
|
||||
/// A HISUP bit set to one indicates the SCSI target device uses the hierarchical addressing model to assign LUNs to logical units.
|
||||
#[offset = 3*8+4, size = 1]
|
||||
hierarchical_support: bool,
|
||||
/// The Normal ACA Supported (NORMACA) bit set to one indicates that the device server supports a NACA bit set to one in the CDB CONTROL byte and supports the ACA task attribute (see SAM-4).
|
||||
/// A NORMACA bit set to zero indicates that the device server does not support a NACA bit set to one and does not support the ACA task attribute.
|
||||
#[offset = 3*8+5, size = 1]
|
||||
normal_aca: bool,
|
||||
/// The ADDITIONAL LENGTH field indicates the length in bytes of the remaining standard INQUIRY data.
|
||||
/// The relationship between the ADDITIONAL LENGTH field and the CDB ALLOCATION LENGTH field is defined in 4.3.5.6.
|
||||
/// Set to total length in bytes minus 4
|
||||
#[offset = 4*8+0, size = 8]
|
||||
additional_length: u8,
|
||||
/// A PROTECT bit set to zero indicates that the logical unit does not support protection information. A PROTECT bit set to one indicates that the logical unit supports:
|
||||
/// - a) type 1 protection, type 2 protection, or type 3 protection (see SBC-3); or
|
||||
/// - b) logical block protection (see SSC-4).
|
||||
///
|
||||
/// More information about the type of protection the logical unit supports is available in the SPT field (see 7.8.7).
|
||||
#[offset = 5*8+0, size = 1]
|
||||
protect: bool,
|
||||
/// A Third-Party Copy (3PC) bit set to one indicates that the SCSI target device contains a copy manager that is addressable through this logical unit.
|
||||
/// A 3 PC bit set to zero indicates that no copy manager is addressable through this logical unit.
|
||||
#[offset = 5*8+3, size = 1]
|
||||
third_party_copy: bool,
|
||||
/// The contents of the target port group support ( TPGS ) field (see table 143) indicate the support for asymmetric logical unit access (see 5.11).
|
||||
#[offset = 5*8+4, size = 2]
|
||||
target_port_group_support: TargetPortGroupSupport,
|
||||
/// An Access Controls Coordinator (ACC) bit set to one indicates that the SCSI target device contains an access controls coordinator (see 3.1.4) that is addressable through this logical unit.
|
||||
/// An ACC bit set to zero indicates that no access controls coordinator is addressable through this logical unit.
|
||||
/// If the SCSI target device contains an access controls coordinator that is addressable through any logical unit other than the ACCESS CONTROLS well known logical unit (see 8.3), then the ACC bit shall be set to one for LUN 0.
|
||||
#[offset = 5*8+6, size = 1]
|
||||
access_controls_coordinator: bool,
|
||||
/// An SCC Supported (SCCS) bit set to one indicates that the SCSI target device contains an embedded storage array controller component that is addressable through this logical unit.
|
||||
/// See SCC-2 for details about storage array controller devices. An SCCS bit set to zero indicates that no embedded storage array controller component is addressable through this logical unit.
|
||||
#[offset = 5*8+7, size = 1]
|
||||
scc_supported: bool,
|
||||
/// A Multi Port (MULTIP) bit set to one indicates that this is a multi-port (two or more ports) SCSI target device
|
||||
/// and conforms to the SCSI multi-port device requirements found in the applicable standards
|
||||
/// (e.g., SAM-4, a SCSI transport protocol standard and possibly provisions of a command standard).
|
||||
/// A MULTIP bit set to zero indicates that this SCSI target device has a single port and does not implement the multi-port requirements.
|
||||
#[offset = 6*8+4, size = 1]
|
||||
multi_port: bool,
|
||||
/// An Enclosure Services (ENCSERV) bit set to one indicates that the SCSI target device contains an embedded enclosure services component
|
||||
/// that is addressable through this logical unit. See SES-3 for details about enclosure services.
|
||||
/// An ENCSERV bit set to zero indicates that no embedded enclosure services component is addressable through this logical unit.
|
||||
#[offset = 6*8+6, size = 1]
|
||||
enclosure_services: bool,
|
||||
/// The T10 VENDOR IDENTIFICATION field contains eight bytes of left-aligned ASCII data (see 4.4.1) identifying the vendor of the logical unit.
|
||||
/// The T10 vendor identification shall be one assigned by INCITS.
|
||||
/// A list of assigned T10 vendor identifications is in Annex E and on the T10 web site (http://www.t10.org).
|
||||
#[offset = 8*8+0, size = 8*8]
|
||||
vendor_identification: [u8; 8],
|
||||
/// The PRODUCT IDENTIFICATION field contains sixteen bytes of left-aligned ASCII data (see 4.4.1) defined by the vendor.
|
||||
#[offset = 16*8+0, size = 16*8]
|
||||
product_identification: [u8; 16],
|
||||
/// The PRODUCT REVISION LEVEL field contains four bytes of left-aligned ASCII data defined by the vendor.
|
||||
#[offset = 32*8+0, size = 4*8]
|
||||
product_revision_level: [u8; 4],
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,35 @@
|
||||
// `bytes` in `#[bitfield(bytes = 6)]` causes a warning
|
||||
#![allow(redundant_semicolons)]
|
||||
|
||||
pub mod control;
|
||||
pub mod inquiry;
|
||||
mod control;
|
||||
pub use control::*;
|
||||
|
||||
mod inquiry;
|
||||
pub use inquiry::*;
|
||||
|
||||
mod read_capacity;
|
||||
pub use read_capacity::*;
|
||||
|
||||
mod read_format_capacities;
|
||||
pub use read_format_capacities::*;
|
||||
|
||||
mod read;
|
||||
pub use read::*;
|
||||
|
||||
mod test_unit_ready;
|
||||
pub use test_unit_ready::*;
|
||||
|
||||
mod mode_sense;
|
||||
pub use mode_sense::*;
|
||||
|
||||
mod mode_parameters;
|
||||
pub use mode_parameters::*;
|
||||
|
||||
mod prevent_allow_medium_removal;
|
||||
pub use prevent_allow_medium_removal::*;
|
||||
|
||||
mod request_sense;
|
||||
pub use request_sense::*;
|
||||
|
||||
mod write;
|
||||
pub use write::*;
|
||||
|
@ -0,0 +1,53 @@
|
||||
use crate::class::msc::subclass::scsi::enums::MediumType;
|
||||
use crate::{packed_enum, packed_struct};
|
||||
|
||||
packed_struct! {
|
||||
pub struct ModeParameterHeader6<4> {
|
||||
/// When using the MODE SENSE command, the MODE DATA LENGTH field indicates the length in bytes of the following data that is available to be
|
||||
/// transferred. The mode data length does not include the number of bytes in the MODE DATA LENGTH field.
|
||||
///
|
||||
/// When using the MODE SELECT command, this field is reserved.
|
||||
#[offset = 0, size = 8]
|
||||
mode_data_length: u8,
|
||||
#[offset = 1*8+0, size = 8]
|
||||
medium_type: MediumType,
|
||||
/// A DPOFUA bit set to zero indicates that the device server does not support the DPO and FUA bits.
|
||||
///
|
||||
/// When used with the MODE SENSE command, a DPOFUA bit set to one indicates that the device server supports the DPO and FUA bits
|
||||
#[offset = 2*8+4, size = 1]
|
||||
dpofua: bool,
|
||||
/// A WP bit set to one indicates that the medium is write-protected. The medium may be write protected when the software write protect
|
||||
/// (SWP) bit in the Control mode page (see 5.3.12) is set to one or if another vendor specific mechanism causes the medium to be write protected.
|
||||
///
|
||||
/// A WP bit set to zero indicates that the medium is not write-protected.
|
||||
#[offset = 2*8+7, size = 1]
|
||||
write_protect: bool,
|
||||
/// The BLOCK DESCRIPTOR LENGTH field contains the length in bytes of all the block descriptors. It is equal to the number of block descriptors
|
||||
/// times eight if the LONGLBA bit is set to zero or times sixteen if the LONGLBA bit is set to one, and does not include mode pages or vendor
|
||||
/// specific parameters (e.g., page code set to zero), if any, that may follow the last block descriptor. A block descriptor length of zero indicates that no
|
||||
/// block descriptors are included in the mode parameter list. This condition shall not be considered an error.
|
||||
#[offset = 3*8+0, size = 8]
|
||||
block_descriptor_length: u8,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PageCode<u8> {
|
||||
CachingModePage = 0x08,
|
||||
}
|
||||
}
|
||||
|
||||
packed_struct! {
|
||||
pub struct CachingModePage<3> {
|
||||
#[offset = 0, size = 6]
|
||||
page_code: PageCode,
|
||||
#[offset = 1*8+0, size = 8]
|
||||
page_length: u8,
|
||||
#[offset = 2*8+0, size = 1]
|
||||
read_cache_disable: bool,
|
||||
#[offset = 2*8+2, size = 1]
|
||||
write_cache_enable: bool,
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
use super::control::Control;
|
||||
use crate::class::msc::subclass::scsi::enums::PageControl;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct ModeSense6Command<6> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
/// A disable block descriptors (DBD) bit set to zero specifies that the device server may return zero or more block descriptors in the returned MODE SENSE data.
|
||||
///
|
||||
/// A DBD bit set to one specifies that the device server shall not return any block descriptors in the returned MODE SENSE data.
|
||||
#[offset = 1*8+3, size = 1]
|
||||
disable_block_descriptors: bool,
|
||||
/// The PAGE CODE and SUBPAGE CODE fields specify which mode pages and subpages to return
|
||||
#[offset = 2*8+0, size = 6]
|
||||
page_code: u8,
|
||||
#[offset = 2*8+6, size = 2]
|
||||
page_control: PageControl,
|
||||
#[offset = 3*8+0, size = 8]
|
||||
subpage_code: u8,
|
||||
#[offset = 4*8+0, size = 8]
|
||||
allocation_length: u8,
|
||||
#[offset = 5*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl ModeSense6Command<[u8; ModeSense6Command::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x1A;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
use super::control::Control;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct PreventAllowMediumRemoval<6> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
#[offset = 4*8+0, size = 1]
|
||||
prevent: bool,
|
||||
#[offset = 5*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl PreventAllowMediumRemoval<[u8; PreventAllowMediumRemoval::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x1E;
|
||||
}
|
47
embassy-usb/src/class/msc/subclass/scsi/commands/read.rs
Normal file
47
embassy-usb/src/class/msc/subclass/scsi/commands/read.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use super::control::Control;
|
||||
use crate::packed::BE;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct Read10Command<10> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
/// If rebuild assist mode is supported and not enabled, then the rebuild assist recovery control (RARC) bit is ignored.
|
||||
/// If rebuild assist mode is supported and enabled, then the RARC bit specifies that read operations are processed as defined in SBC-4.
|
||||
///
|
||||
/// If the rebuild assist mode is not supported and the RARC bit is set to one, then the device server should terminate the command with CHECK
|
||||
/// CONDITION status with the sense key set to ILLEGAL REQUEST and the additional sense code set to INVALID FIELD IN CDB.
|
||||
#[offset = 1*8+2, size = 1]
|
||||
rebuild_assist_recovery_control: bool,
|
||||
/// A Force unit Access (FUA) bit set to zero specifies that the device server may read the logical blocks from the volatile cache (if any), the
|
||||
/// specified data pattern for that LBA (e.g., the data pattern for unmapped data), the non-volatile cache, or the medium.
|
||||
///
|
||||
/// An FUA bit set to one specifies that the device server shall read the logical blocks from the specified data pattern for that LBA, the
|
||||
/// non-volatile cache (if any), or the medium. If a volatile cache contains a more recent version of a logical block, then the device server
|
||||
/// shall write that logical block to non-volatile cache or the medium before reading the logical block.
|
||||
#[offset = 1*8+3, size = 1]
|
||||
force_unit_access: bool,
|
||||
/// A Disable Page Out (DPO) bit set to zero specifies that the retention priority shall be determined by the RETENTION PRIORITY fields in the
|
||||
/// Caching mode page (see 5.3.9).
|
||||
///
|
||||
/// A DPO bit set to one specifies that the device server shall assign the logical blocks accessed by this command the lowest retention priority
|
||||
/// for being fetched into or retained by the cache. A DPO bit set to one overrides any retention priority specified in the Caching mode
|
||||
/// page. All other aspects of the algorithm implementing the cache replacement strategy are not defined by this manual.
|
||||
#[offset = 1*8+4, size = 1]
|
||||
disable_page_out: bool,
|
||||
#[offset = 1*8+5, size = 3]
|
||||
read_protect: u8,
|
||||
#[offset = 2*8+0, size = 32]
|
||||
lba: BE<u32>,
|
||||
#[offset = 6*8+0, size = 5]
|
||||
group_number: u8,
|
||||
#[offset = 7*8+0, size = 16]
|
||||
transfer_length: BE<u16>,
|
||||
#[offset = 9*8+0, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Read10Command<[u8; Read10Command::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x28;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
use super::control::Control;
|
||||
use crate::packed::BE;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct ReadCapacity10Command<10> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
#[offset = 2*8, size = 32]
|
||||
lba: BE<u32>,
|
||||
#[offset = 9*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadCapacity10Command<[u8; ReadCapacity10Command::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x25;
|
||||
}
|
||||
|
||||
packed_struct! {
|
||||
pub struct ReadCapacity10Response<8> {
|
||||
#[offset = 0*8, size = 32]
|
||||
max_lba: BE<u32>,
|
||||
#[offset = 4*8, size = 32]
|
||||
block_size: BE<u32>,
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
use super::control::Control;
|
||||
use crate::packed::BE;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct ReadFormatCapacitiesCommand<10> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
#[offset = 1*8+5, size = 3]
|
||||
lun: u8,
|
||||
#[offset = 7*8, size = 16]
|
||||
allocation_length: BE<u16>,
|
||||
#[offset = 9*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadFormatCapacitiesCommand<[u8; ReadFormatCapacitiesCommand::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x23;
|
||||
}
|
||||
|
||||
packed_struct! {
|
||||
pub struct ReadFormatCapacitiesResponse<12> {
|
||||
/// The Capacity List Length specifies the length in bytes of the Capacity Descriptors that follow. Each Capacity
|
||||
/// Descriptor is eight bytes in length, making the Capacity List Length equal to eight times the number of descriptors.
|
||||
/// Values of n * 8 are valid, where 0 < n < 32
|
||||
#[offset = 3*8, size = 8]
|
||||
capacity_list_length: u8,
|
||||
#[offset = 4*8, size = 32]
|
||||
max_lba: BE<u32>,
|
||||
#[offset = 8*8, size = 2]
|
||||
descriptor_type: BE<u32>,
|
||||
// TODO should be 24 bits
|
||||
#[offset = 8*8, size = 32]
|
||||
block_size: BE<u32>,
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use super::control::Control;
|
||||
use crate::class::msc::subclass::scsi::enums::{ResponseCode, SenseKey};
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct RequestSenseCommand<6> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
#[offset = 1*8+5, size = 3]
|
||||
lun: u8,
|
||||
#[offset = 4*8+0, size = 8]
|
||||
allocation_length: u8,
|
||||
#[offset = 5*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestSenseCommand<[u8; RequestSenseCommand::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x03;
|
||||
}
|
||||
|
||||
packed_struct! {
|
||||
pub struct RequestSenseResponse<8> {
|
||||
#[offset = 0, size = 7]
|
||||
response_code: ResponseCode,
|
||||
#[offset = 1*8+0, size = 4]
|
||||
sense_key: SenseKey,
|
||||
#[offset = 2*8+0, size = 8]
|
||||
additional_sense_code: u8,
|
||||
#[offset = 3*8+0, size = 8]
|
||||
additional_sense_code_qualifier: u8,
|
||||
#[offset = 7*8+0, size = 8]
|
||||
additional_sense_length: u8,
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
use super::control::Control;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct TestUnitReadyCommand<6> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
#[offset = 5*8, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl TestUnitReadyCommand<[u8; TestUnitReadyCommand::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x00;
|
||||
}
|
40
embassy-usb/src/class/msc/subclass/scsi/commands/write.rs
Normal file
40
embassy-usb/src/class/msc/subclass/scsi/commands/write.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use super::control::Control;
|
||||
use crate::packed::BE;
|
||||
use crate::packed_struct;
|
||||
|
||||
packed_struct! {
|
||||
pub struct Write10Command<10> {
|
||||
#[offset = 0, size = 8]
|
||||
op_code: u8,
|
||||
/// A Force unit Access (FUA) bit set to zero specifies that the device server may read the logical blocks from the volatile cache (if any), the
|
||||
/// specified data pattern for that LBA (e.g., the data pattern for unmapped data), the non-volatile cache, or the medium.
|
||||
///
|
||||
/// An FUA bit set to one specifies that the device server shall read the logical blocks from the specified data pattern for that LBA, the
|
||||
/// non-volatile cache (if any), or the medium. If a volatile cache contains a more recent version of a logical block, then the device server
|
||||
/// shall write that logical block to non-volatile cache or the medium before reading the logical block.
|
||||
#[offset = 1*8+3, size = 1]
|
||||
force_unit_access: bool,
|
||||
/// A Disable Page Out (DPO) bit set to zero specifies that the retention priority shall be determined by the RETENTION PRIORITY fields in the
|
||||
/// Caching mode page (see 5.3.9).
|
||||
///
|
||||
/// A DPO bit set to one specifies that the device server shall assign the logical blocks accessed by this command the lowest retention priority
|
||||
/// for being fetched into or retained by the cache. A DPO bit set to one overrides any retention priority specified in the Caching mode
|
||||
/// page. All other aspects of the algorithm implementing the cache replacement strategy are not defined by this manual.
|
||||
#[offset = 1*8+4, size = 1]
|
||||
disable_page_out: bool,
|
||||
#[offset = 1*8+5, size = 3]
|
||||
write_protect: u8,
|
||||
#[offset = 2*8+0, size = 32]
|
||||
lba: BE<u32>,
|
||||
#[offset = 6*8+0, size = 5]
|
||||
group_number: u8,
|
||||
#[offset = 7*8+0, size = 16]
|
||||
transfer_length: BE<u16>,
|
||||
#[offset = 9*8+0, size = 8]
|
||||
control: Control<[u8; Control::SIZE]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Write10Command<[u8; Write10Command::SIZE]> {
|
||||
pub const OPCODE: u8 = 0x2A;
|
||||
}
|
@ -2,6 +2,7 @@ use crate::packed_enum;
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PeripheralQualifier<u8> {
|
||||
/// A peripheral device having the specified peripheral device type is connected to this logical unit. If the device server is unable to determine whether or not a peripheral device is connected, it also shall use this peripheral qualifier. This peripheral qualifier does not mean that the peripheral device connected to the logical unit is ready for access.
|
||||
Connected = 0b000,
|
||||
@ -14,6 +15,7 @@ packed_enum! {
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PeripheralDeviceType<u8> {
|
||||
/// Direct access block device (e.g., magnetic disk)
|
||||
DirectAccessBlock = 0x00,
|
||||
@ -53,3 +55,182 @@ packed_enum! {
|
||||
UnknownOrNone = 0x1F,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ResponseDataFormat<u8> {
|
||||
/// A RESPONSE DATA FORMAT field set to 2h indicates that the standard INQUIRY data
|
||||
Standard = 0x2,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SpcVersion<u8> {
|
||||
/// The device does not claim conformance to any standard.
|
||||
None = 0x00,
|
||||
/// The device complies to ANSI INCITS 301-1997 (SPC)
|
||||
Spc = 0x03,
|
||||
/// The device complies to ANSI INCITS 351-2001 (SPC-2)
|
||||
Spc2 = 0x04,
|
||||
/// The device complies to ANSI INCITS 408-2005 (SPC-3)
|
||||
Spc3 = 0x05,
|
||||
/// The device complies to ANSI INCITS 513-2015 (SPC-4)
|
||||
Spc4 = 0x06,
|
||||
/// The device complies to T10/BSR INCITS 503 (SPC-5)
|
||||
Spc5 = 0x07,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum TargetPortGroupSupport<u8> {
|
||||
/// The logical unit does not support asymmetric logical unit access or supports a form of asymmetric access that is vendor specific.
|
||||
/// Neither the REPORT TARGET GROUPS nor the SET TARGET PORT GROUPS commands is supported.
|
||||
Unsupported = 0b00,
|
||||
/// The logical unit supports only implicit asymmetric logical unit access (see 5.11.2.7).
|
||||
/// The logical unit is capable of changing target port asymmetric access states without a SET TARGET PORT GROUPS command.
|
||||
/// The REPORT TARGET PORT GROUPS command is supported and the SET TARGET PORT GROUPS command is not supported.
|
||||
Implicit = 0b01,
|
||||
/// The logical unit supports only explicit asymmetric logical unit access (see 5.11.2.8).
|
||||
/// The logical unit only changes target port asymmetric access states as requested with the SET TARGET PORT GROUPS command.
|
||||
/// Both the REPORT TARGET PORT GROUPS command and the SET TARGET PORT GROUPS command are supported.
|
||||
Explicit = 0b10,
|
||||
/// The logical unit supports both explicit and implicit asymmetric logical unit access.
|
||||
/// Both the REPORT TARGET PORT GROUPS command and the SET TARGET PORT GROUPS commands are supported.
|
||||
ImplicitAndExplicit = 0b11,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PageControl<u8> {
|
||||
/// Current values
|
||||
CurrentValues = 0b00,
|
||||
/// Changeable values
|
||||
ChangeableValues = 0b01,
|
||||
/// Default values
|
||||
DefaultValues = 0b10,
|
||||
/// Saved values
|
||||
SavedValues = 0b11,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum MediumType<u8> {
|
||||
Sbc = 0x00,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ResponseCode<u8> {
|
||||
CurrentFixedSenseData = 0x70,
|
||||
DeferredFixedSenseData = 0x71,
|
||||
CurrentDescriptorSenseData = 0x72,
|
||||
DeferredDescriptorSenseData = 0x73,
|
||||
}
|
||||
}
|
||||
|
||||
packed_enum! {
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum SenseKey<u8> {
|
||||
/// Indicates that there is no specific sense key information to be reported. This may occur for a successful command or for a command that receives CHECK CONDITION status because one of the FILEMARK , EOM , or ILI bits is set to one.
|
||||
NoSense = 0x0,
|
||||
/// Indicates that the command completed successfully, with some recovery action performed by the device server. Details may be determined by examining the additional sense bytes and the INFORMATION field. When multiple recovered errors occur during one command, the choice of which error to report (e.g., first, last, most severe) is vendor specific.
|
||||
RecoveredError = 0x1,
|
||||
/// Indicates that the logical unit is not accessible. Operator intervention may be required to correct this condition.
|
||||
NotReady = 0x2,
|
||||
/// Indicates that the command terminated with a non-recovered error condition that may have been caused by a flaw in the medium or an error in the recorded data. This sense key may also be returned if the device server is unable to distinguish between a flaw in the medium and a specific hardware failure (i.e., sense key 4h).
|
||||
MediumError = 0x3,
|
||||
/// Indicates that the device server detected a non-recoverable hardware failure (e.g., controller failure, device failure, or parity error) while performing the command or during a self test.
|
||||
HardwareError = 0x4,
|
||||
/// Indicates that:
|
||||
/// a) the command was addressed to an incorrect logical unit number (see SAM-4);
|
||||
/// b) the command had an invalid task attribute (see SAM-4);
|
||||
/// c) the command was addressed to a logical unit whose current configuration prohibits
|
||||
/// processing the command;
|
||||
/// d) there was an illegal parameter in the CDB; or
|
||||
/// e) there was an illegal parameter in the additional parameters supplied as data for some
|
||||
/// commands (e.g., PERSISTENT RESERVE OUT).
|
||||
/// If the device server detects an invalid parameter in the CDB, it shall terminate the command without
|
||||
/// altering the medium. If the device server detects an invalid parameter in the additional parameters
|
||||
/// supplied as data, the device server may have already altered the medium.
|
||||
IllegalRequest = 0x5,
|
||||
/// Indicates that a unit attention condition has been established (e.g., the removable medium may have been changed, a logical unit reset occurred). See SAM-4.
|
||||
UnitAttention = 0x6,
|
||||
/// Indicates that a command that reads or writes the medium was attempted on a block that is protected. The read or write operation is not performed.
|
||||
DataProtect = 0x7,
|
||||
/// Indicates that a write-once device or a sequential-access device encountered blank medium or format-defined end-of-data indication while reading or that a write-once device encountered a non-blank medium while writing.
|
||||
BlankCheck = 0x8,
|
||||
/// This sense key is available for reporting vendor specific conditions.
|
||||
VendorSpecific = 0x9,
|
||||
/// Indicates an EXTENDED COPY command was aborted due to an error condition on the source device, the destination device, or both (see 6.3.3).
|
||||
CopyAborted = 0xA,
|
||||
/// Indicates that the device server aborted the command. The application client may be able to recover by trying the command again.
|
||||
AbortedCommand = 0xB,
|
||||
/// Indicates that a buffered SCSI device has reached the end-of-partition and data may remain in the buffer that has not been written to the medium. One or more RECOVER BUFFERED DATA command(s) may be issued to read the unwritten data from the buffer. (See SSC-2.)
|
||||
VolumeOverflow = 0xD,
|
||||
/// Indicates that the source data did not match the data read from the medium.
|
||||
Miscompare = 0xE,
|
||||
/// Indicates there is completion sense data to be reported. This may occur for a successful command.
|
||||
Completed = 0xF,
|
||||
}
|
||||
}
|
||||
|
||||
// There are many more variants (see asc-num.txt) but these are the ones the scsi code
|
||||
// currently uses
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum AdditionalSenseCode {
|
||||
/// ASC 0x20, ASCQ: 0x0 - INVALID COMMAND OPERATION CODE
|
||||
InvalidCommandOperationCode,
|
||||
/// ASC 0x64, ASCQ: 0x1 - INVALID PACKET SIZE
|
||||
InvalidPacketSize,
|
||||
/// ASC 0x24, ASCQ: 0x0 - INVALID FIELD IN CDB
|
||||
InvalidFieldInCdb,
|
||||
/// ASC 0x0, ASCQ: 0x0 - NO ADDITIONAL SENSE INFORMATION
|
||||
NoAdditionalSenseInformation,
|
||||
/// ASC 0xC, ASCQ: 0x0 - WRITE ERROR
|
||||
WriteError,
|
||||
/// ASC 0x51, ASCQ: 0x0 - ERASE FAILURE
|
||||
EraseFailure,
|
||||
/// ASC 0x21, ASCQ: 0x0 - LOGICAL BLOCK ADDRESS OUT OF RANGE
|
||||
LogicalBlockAddressOutOfRange,
|
||||
}
|
||||
|
||||
impl AdditionalSenseCode {
|
||||
/// Returns the ASC code for this variant
|
||||
pub fn asc(&self) -> u8 {
|
||||
match self {
|
||||
AdditionalSenseCode::InvalidCommandOperationCode => 32,
|
||||
AdditionalSenseCode::InvalidPacketSize => 100,
|
||||
AdditionalSenseCode::InvalidFieldInCdb => 36,
|
||||
AdditionalSenseCode::NoAdditionalSenseInformation => 0,
|
||||
AdditionalSenseCode::WriteError => 12,
|
||||
AdditionalSenseCode::EraseFailure => 81,
|
||||
AdditionalSenseCode::LogicalBlockAddressOutOfRange => 33,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the ASCQ code for this variant
|
||||
pub fn ascq(&self) -> u8 {
|
||||
match self {
|
||||
AdditionalSenseCode::InvalidCommandOperationCode => 0,
|
||||
AdditionalSenseCode::InvalidPacketSize => 1,
|
||||
AdditionalSenseCode::InvalidFieldInCdb => 0,
|
||||
AdditionalSenseCode::NoAdditionalSenseInformation => 0,
|
||||
AdditionalSenseCode::WriteError => 0,
|
||||
AdditionalSenseCode::EraseFailure => 0,
|
||||
AdditionalSenseCode::LogicalBlockAddressOutOfRange => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,14 +3,31 @@ pub mod block_device;
|
||||
pub mod commands;
|
||||
pub mod enums;
|
||||
|
||||
use core::mem::MaybeUninit;
|
||||
|
||||
use self::block_device::BlockDevice;
|
||||
use crate::class::msc::subclass::scsi::commands::inquiry::InquiryCommand;
|
||||
use crate::class::msc::subclass::scsi::commands::{
|
||||
CachingModePage, InquiryCommand, InquiryResponse, ModeParameterHeader6, ModeSense6Command, PageCode,
|
||||
PreventAllowMediumRemoval, Read10Command, ReadCapacity10Command, ReadCapacity10Response,
|
||||
ReadFormatCapacitiesCommand, ReadFormatCapacitiesResponse, RequestSenseCommand, RequestSenseResponse,
|
||||
TestUnitReadyCommand, Write10Command,
|
||||
};
|
||||
use crate::class::msc::subclass::scsi::enums::{
|
||||
PeripheralDeviceType, PeripheralQualifier, ResponseCode, ResponseDataFormat, SenseKey, SpcVersion,
|
||||
TargetPortGroupSupport,
|
||||
};
|
||||
use crate::class::msc::transport::{self, CommandSetHandler};
|
||||
|
||||
pub struct Scsi<B: BlockDevice> {
|
||||
device: B,
|
||||
}
|
||||
|
||||
impl<B: BlockDevice> Scsi<B> {
|
||||
pub fn new(device: B) -> Self {
|
||||
Self { device }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: BlockDevice> CommandSetHandler for Scsi<B> {
|
||||
async fn command_out(
|
||||
&mut self,
|
||||
@ -22,14 +39,43 @@ impl<B: BlockDevice> CommandSetHandler for Scsi<B> {
|
||||
|
||||
let op_code = cmd[0];
|
||||
match op_code {
|
||||
InquiryCommand::OPCODE => {
|
||||
let cmd = InquiryCommand::from_bytes(cmd);
|
||||
// info!("inquiry: {:#?}", cmd);
|
||||
TestUnitReadyCommand::OPCODE => {
|
||||
info!("TestUnitReadyCommand: {:#?}", TestUnitReadyCommand::from_bytes(cmd));
|
||||
return Ok(());
|
||||
}
|
||||
_ => warn!("Unknown opcode: {}", op_code),
|
||||
PreventAllowMediumRemoval::OPCODE => match PreventAllowMediumRemoval::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("PreventAllowMediumRemoval: {:?}", req);
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing PreventAllowMediumRemoval"),
|
||||
},
|
||||
Write10Command::OPCODE => match Write10Command::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("Write10Command: {:?}", req);
|
||||
|
||||
let mut data = MaybeUninit::<[u8; 512]>::uninit();
|
||||
|
||||
let start_lba = req.lba();
|
||||
let transfer_length = req.transfer_length() as u32;
|
||||
|
||||
if start_lba + transfer_length - 1 > self.device.num_blocks() {
|
||||
return Err(transport::CommandError::CommandError);
|
||||
}
|
||||
|
||||
for lba in start_lba..start_lba + transfer_length {
|
||||
pipe.read(unsafe { data.assume_init_mut() }).await?;
|
||||
self.device.write_block(lba, unsafe { data.assume_init_ref() }).unwrap();
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing Write10Command"),
|
||||
},
|
||||
_ => warn!("Unknown OUT opcode: {}", op_code),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Err(transport::CommandError::CommandError)
|
||||
}
|
||||
|
||||
async fn command_in(
|
||||
@ -40,6 +86,133 @@ impl<B: BlockDevice> CommandSetHandler for Scsi<B> {
|
||||
) -> Result<(), transport::CommandError> {
|
||||
assert!(lun == 0, "LUNs are not supported");
|
||||
|
||||
Ok(())
|
||||
let op_code = cmd[0];
|
||||
info!("op_code: {}", op_code);
|
||||
match op_code {
|
||||
InquiryCommand::OPCODE => match InquiryCommand::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("inquiry: {:#?}", req);
|
||||
|
||||
let vendor_ident = b"FAKE ";
|
||||
let product_ident = b"PRODUCT ";
|
||||
|
||||
let mut resp = InquiryResponse::new();
|
||||
resp.set_peripheral_device_type(PeripheralDeviceType::DirectAccessBlock);
|
||||
resp.set_peripheral_qualifier(PeripheralQualifier::Connected);
|
||||
resp.set_removable_medium(true);
|
||||
resp.set_version(SpcVersion::Spc3);
|
||||
resp.set_response_data_format(ResponseDataFormat::Standard);
|
||||
resp.set_hierarchical_support(false);
|
||||
resp.set_normal_aca(false);
|
||||
resp.set_additional_length((InquiryResponse::SIZE - 4) as u8);
|
||||
resp.set_protect(false);
|
||||
resp.set_third_party_copy(false);
|
||||
resp.set_target_port_group_support(TargetPortGroupSupport::Unsupported);
|
||||
resp.set_access_controls_coordinator(false);
|
||||
resp.set_scc_supported(false);
|
||||
resp.set_multi_port(false);
|
||||
resp.set_enclosure_services(false);
|
||||
resp.set_vendor_identification(vendor_ident);
|
||||
resp.set_product_identification(product_ident);
|
||||
resp.set_product_revision_level(&[b' '; 4]);
|
||||
|
||||
pipe.write(&resp.data).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing InquiryCommand"),
|
||||
},
|
||||
ModeSense6Command::OPCODE => match ModeSense6Command::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("ModeSense6Command: {:?}", req);
|
||||
|
||||
// let mut buf = [0u8; ModeParameterHeader6::SIZE + CachingModePage::SIZE];
|
||||
|
||||
// let mut header = ModeParameterHeader6::from_bytes(&mut buf[0..ModeSense6Command::SIZE]).unwrap();
|
||||
// header.set_mode_data_length((ModeParameterHeader6::SIZE + CachingModePage::SIZE - 1) as u8);
|
||||
|
||||
// let mut caching_mode_page =
|
||||
// CachingModePage::from_bytes(&mut buf[ModeParameterHeader6::SIZE..]).unwrap();
|
||||
// caching_mode_page.set_page_code(PageCode::CachingModePage);
|
||||
// caching_mode_page.set_page_length(CachingModePage::SIZE as u8);
|
||||
// caching_mode_page.set_read_cache_disable(true);
|
||||
// caching_mode_page.set_write_cache_enable(false);
|
||||
|
||||
// pipe.write(&buf).await?;
|
||||
|
||||
let mut header = ModeParameterHeader6::new();
|
||||
header.set_mode_data_length(ModeParameterHeader6::SIZE as u8 - 1);
|
||||
pipe.write(&header.data).await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing ModeSense6Command"),
|
||||
},
|
||||
RequestSenseCommand::OPCODE => match RequestSenseCommand::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("RequestSenseCommand: {:?}", req);
|
||||
|
||||
let mut resp = RequestSenseResponse::new();
|
||||
resp.set_response_code(ResponseCode::CurrentFixedSenseData);
|
||||
resp.set_sense_key(SenseKey::NoSense);
|
||||
|
||||
pipe.write(&resp.data).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing RequestSenseCommand"),
|
||||
},
|
||||
ReadCapacity10Command::OPCODE => match ReadCapacity10Command::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("ReadCapacity10Command: {:?}", req);
|
||||
|
||||
let mut resp = ReadCapacity10Response::new();
|
||||
resp.set_max_lba(self.device.num_blocks());
|
||||
resp.set_block_size(self.device.block_size() as u32);
|
||||
|
||||
pipe.write(&resp.data).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing ReadCapacity10Command"),
|
||||
},
|
||||
ReadFormatCapacitiesCommand::OPCODE => match ReadFormatCapacitiesCommand::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("ReadFormatCapacitiesCommand: {:?}", req);
|
||||
|
||||
let mut resp = ReadFormatCapacitiesResponse::new();
|
||||
resp.set_capacity_list_length(8);
|
||||
resp.set_max_lba(self.device.num_blocks());
|
||||
resp.set_block_size(self.device.block_size() as u32);
|
||||
|
||||
pipe.write(&resp.data).await?;
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing ReadFormatCapacitiesCommand"),
|
||||
},
|
||||
Read10Command::OPCODE => match Read10Command::from_bytes(cmd) {
|
||||
Some(req) => {
|
||||
info!("Read10: {:?} {:?}", req, cmd);
|
||||
|
||||
let mut data = MaybeUninit::<[u8; 512]>::uninit();
|
||||
|
||||
let start_lba = req.lba();
|
||||
let transfer_length = req.transfer_length() as u32;
|
||||
|
||||
if start_lba + transfer_length - 1 > self.device.num_blocks() {
|
||||
return Err(transport::CommandError::CommandError);
|
||||
}
|
||||
|
||||
for lba in start_lba..start_lba + transfer_length {
|
||||
self.device.read_block(lba, unsafe { data.assume_init_mut() }).unwrap();
|
||||
|
||||
pipe.write(unsafe { data.assume_init_ref() }).await?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
None => error!("Error parsing Read10Command"),
|
||||
},
|
||||
_ => warn!("Unknown IN opcode: {}", op_code),
|
||||
}
|
||||
|
||||
Err(transport::CommandError::CommandError)
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ impl<'d, D: Driver<'d>, C: CommandSetHandler> BulkOnlyTransport<'d, D, C> {
|
||||
ep: &mut self.write_ep,
|
||||
data_residue: cbw.data_transfer_length as _,
|
||||
max_packet_size: self.max_packet_size,
|
||||
last_packet_full: true,
|
||||
last_packet_size: 0,
|
||||
};
|
||||
|
||||
let status = match self.handler.command_in(cbw.lun, cbw.data(), &mut pipe_in).await {
|
||||
@ -201,12 +201,12 @@ pub struct BulkOnlyTransportDataPipeIn<'d, E: EndpointIn> {
|
||||
// requested transfer size minus already transfered bytes
|
||||
data_residue: u32,
|
||||
max_packet_size: u16,
|
||||
last_packet_full: bool,
|
||||
last_packet_size: u16,
|
||||
}
|
||||
|
||||
impl<'d, E: EndpointIn> DataPipeIn for BulkOnlyTransportDataPipeIn<'d, E> {
|
||||
async fn write(&mut self, buf: &[u8]) -> Result<(), DataPipeError> {
|
||||
if !self.last_packet_full {
|
||||
if self.last_packet_size > 0 && self.last_packet_size != self.max_packet_size {
|
||||
return Err(DataPipeError::TransferFinalized);
|
||||
}
|
||||
|
||||
@ -217,7 +217,7 @@ impl<'d, E: EndpointIn> DataPipeIn for BulkOnlyTransportDataPipeIn<'d, E> {
|
||||
|
||||
self.ep.write(chunk).await?;
|
||||
self.data_residue -= chunk.len() as u32;
|
||||
self.last_packet_full = chunk.len() == self.max_packet_size.into();
|
||||
self.last_packet_size = chunk.len() as u16;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -227,7 +227,7 @@ impl<'d, E: EndpointIn> DataPipeIn for BulkOnlyTransportDataPipeIn<'d, E> {
|
||||
impl<'d, E: EndpointIn> BulkOnlyTransportDataPipeIn<'d, E> {
|
||||
async fn finalize(&mut self) -> Result<(), DataPipeError> {
|
||||
// Send ZLP only if last packet was full and transfer size was not exhausted
|
||||
if self.last_packet_full && self.data_residue != 0 {
|
||||
if self.last_packet_size == self.max_packet_size && self.data_residue != 0 {
|
||||
self.ep.write(&[]).await?;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,12 @@ pub enum CommandError {
|
||||
CommandError,
|
||||
}
|
||||
|
||||
impl From<DataPipeError> for CommandError {
|
||||
fn from(e: DataPipeError) -> Self {
|
||||
Self::PipeError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// A pipe that allows [CommandSetHandler] to write command-specific data.
|
||||
pub trait DataPipeIn {
|
||||
/// Sends data to host.
|
||||
|
@ -12,78 +12,53 @@ macro_rules! const_assert {
|
||||
}
|
||||
|
||||
pub trait PackedField {
|
||||
type Output<'a>;
|
||||
type Get<'a>;
|
||||
type Set<'a>;
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {}
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a>;
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self);
|
||||
}
|
||||
|
||||
impl PackedField for &[u8] {
|
||||
type Output<'a> = &'a [u8];
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for u8 slices is not supported");
|
||||
}
|
||||
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
data
|
||||
}
|
||||
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
data.copy_from_slice(val)
|
||||
}
|
||||
}
|
||||
|
||||
impl PackedField for &mut [u8] {
|
||||
type Output<'a> = &'a [u8];
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for u8 slices is not supported");
|
||||
}
|
||||
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
data
|
||||
}
|
||||
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
data.copy_from_slice(val)
|
||||
}
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a>;
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>);
|
||||
}
|
||||
|
||||
impl<const N: usize> PackedField for [u8; N] {
|
||||
type Output<'a> = [u8; N];
|
||||
type Get<'a> = &'a [u8];
|
||||
type Set<'a> = &'a [u8];
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(N: usize, SIZE: usize => SIZE == N, "Incorrect array size");
|
||||
const_assert!(N: usize, SIZE: usize => SIZE == N*8, "Incorrect array size");
|
||||
const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for arrays is not supported");
|
||||
}
|
||||
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
data.try_into().unwrap()
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
let byte = OFFSET / 8;
|
||||
let size_bytes = SIZE / 8;
|
||||
&data[byte..byte + size_bytes]
|
||||
}
|
||||
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
data.copy_from_slice(&val)
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
let byte = OFFSET / 8;
|
||||
let size_bytes = SIZE / 8;
|
||||
data[byte..byte + size_bytes].copy_from_slice(&val)
|
||||
}
|
||||
}
|
||||
|
||||
impl PackedField for bool {
|
||||
type Output<'a> = bool;
|
||||
type Get<'a> = bool;
|
||||
type Set<'a> = bool;
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(SIZE: usize => SIZE == 1, "bool size must equal 1");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
let byte = OFFSET / 8;
|
||||
let bit = OFFSET % 8;
|
||||
(data[byte] & (1 << bit)) != 0
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
let byte = OFFSET / 8;
|
||||
let bit = OFFSET % 8;
|
||||
let mask = (val as u8) << bit;
|
||||
@ -92,7 +67,8 @@ impl PackedField for bool {
|
||||
}
|
||||
|
||||
impl PackedField for u8 {
|
||||
type Output<'a> = u8;
|
||||
type Get<'a> = u8;
|
||||
type Set<'a> = u8;
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(SIZE: usize => SIZE <= 8, "u8 is not large enough");
|
||||
@ -103,7 +79,7 @@ impl PackedField for u8 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
let byte = OFFSET / 8;
|
||||
let bit = OFFSET % 8;
|
||||
let mask = (0xFF >> SIZE) << bit;
|
||||
@ -111,7 +87,7 @@ impl PackedField for u8 {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
let byte = OFFSET / 8;
|
||||
let bit = OFFSET % 8;
|
||||
let mask = (0xFF >> SIZE) << bit;
|
||||
@ -119,27 +95,38 @@ impl PackedField for u8 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Big Endian
|
||||
pub struct BE<T>(T);
|
||||
|
||||
/// Little Endian
|
||||
pub struct LE<T>(T);
|
||||
|
||||
macro_rules! impl_packed_field_int {
|
||||
($ty:ty, $size:literal) => {
|
||||
impl PackedField for $ty {
|
||||
type Output<'a> = $ty;
|
||||
impl_packed_field_int!(BE<$ty>, $ty, $size, from_be_bytes, to_be_bytes);
|
||||
impl_packed_field_int!(LE<$ty>, $ty, $size, from_le_bytes, to_le_bytes);
|
||||
};
|
||||
($wrapper:ty, $ty:ty, $size:literal, $from_bytes_fn:ident, $to_bytes_fn:ident) => {
|
||||
impl PackedField for $wrapper {
|
||||
type Get<'a> = $ty;
|
||||
type Set<'a> = $ty;
|
||||
|
||||
fn assert<const OFFSET: usize, const SIZE: usize>() {
|
||||
const_assert!(SIZE: usize => SIZE <= $size, "type is not large enough");
|
||||
const_assert!(SIZE: usize => SIZE == $size, "type size mismatch");
|
||||
// most protocols only use bit packing at byte (u8) boundaries, so this is okay for now
|
||||
const_assert!(OFFSET: usize => OFFSET % 8 == 0, "bit packing for this type is not supported");
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
let byte = OFFSET / 8;
|
||||
unsafe { *(data[byte..].as_ptr() as *const $ty)}
|
||||
<$ty>::$from_bytes_fn(data[byte..byte+(SIZE/8)].try_into().unwrap())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
let byte = OFFSET / 8;
|
||||
unsafe { *(data[byte..].as_ptr() as *mut $ty) = val; }
|
||||
data[byte..byte+(SIZE/8)].copy_from_slice(&val.$to_bytes_fn())
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -160,6 +147,7 @@ macro_rules! packed_struct {
|
||||
$(#[$meta:meta])*
|
||||
$sv:vis struct $name:ident<$size:literal> {
|
||||
$(
|
||||
$(#[doc = $field_doc:literal])*
|
||||
#[offset = $offset:expr, size = $bit_size:expr]
|
||||
$field:ident: $ty:ty,
|
||||
)*
|
||||
@ -171,7 +159,8 @@ macro_rules! packed_struct {
|
||||
}
|
||||
|
||||
impl $name<[u8; $size]> {
|
||||
const SIZE: usize = $size;
|
||||
/// Packed struct size in bytes
|
||||
pub const SIZE: usize = $size;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
@ -180,7 +169,7 @@ macro_rules! packed_struct {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, T: AsRef<[u8]> + crate::packed::PackedField<Output<'d> = T> + 'd> $name<T> {
|
||||
impl<T: AsRef<[u8]>> $name<T> {
|
||||
pub const unsafe fn from_bytes_unchecked(data: T) -> Self {
|
||||
Self { data }
|
||||
}
|
||||
@ -193,9 +182,11 @@ macro_rules! packed_struct {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate getter methods for fields
|
||||
$(
|
||||
$(#[doc = $field_doc])*
|
||||
#[inline]
|
||||
pub fn $field(&self) -> <$ty as crate::packed::PackedField>::Output<'d> {
|
||||
pub fn $field(&self) -> <$ty as crate::packed::PackedField>::Get<'_> {
|
||||
const _: () = core::assert!($offset + $bit_size <= $size * 8, "Field offset is out of range");
|
||||
<$ty as crate::packed::PackedField>::assert::<{$offset}, {$bit_size}>();
|
||||
<$ty as crate::packed::PackedField>::get::<{$offset}, {$bit_size}>(self.data.as_ref())
|
||||
@ -203,11 +194,13 @@ macro_rules! packed_struct {
|
||||
)*
|
||||
}
|
||||
|
||||
impl<'d, T: AsRef<[u8]> + AsMut<[u8]> + crate::packed::PackedField<Output<'d> = T> + 'd> $name<T> {
|
||||
impl<T: AsRef<[u8]> + AsMut<[u8]>> $name<T> {
|
||||
// Generate setter methods for fields
|
||||
$(
|
||||
paste::paste! {
|
||||
$(#[doc = $field_doc])*
|
||||
#[inline]
|
||||
pub fn [<set_$field>](&mut self, val: $ty) {
|
||||
pub fn [<set_$field>](&mut self, val: <$ty as crate::packed::PackedField>::Set<'_>) {
|
||||
const _: () = core::assert!($offset + $bit_size <= $size * 8, "Field offset is out of range");
|
||||
<$ty as crate::packed::PackedField>::assert::<{$offset}, {$bit_size}>();
|
||||
<$ty as crate::packed::PackedField>::set::<{$offset}, {$bit_size}>(self.data.as_mut(), val)
|
||||
@ -216,22 +209,23 @@ macro_rules! packed_struct {
|
||||
)*
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + for<'a> crate::packed::PackedField<Output<'a> = T>> crate::packed::PackedField for $name<T> {
|
||||
type Output<'a> = Self;
|
||||
impl crate::packed::PackedField for $name<[u8; $size]> {
|
||||
type Get<'a> = $name<&'a [u8]>;
|
||||
type Set<'a> = $name<&'a [u8]>;
|
||||
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
<T as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
let val = <T as crate::packed::PackedField>::get::<OFFSET, SIZE>(data);
|
||||
unsafe { Self::from_bytes_unchecked(val) }
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
<[u8; $size] as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
let val = <[u8; $size] as crate::packed::PackedField>::get::<OFFSET, SIZE>(data);
|
||||
unsafe { Self::Get::from_bytes_unchecked(val) }
|
||||
}
|
||||
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
<T as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
<T as crate::packed::PackedField>::set::<OFFSET, SIZE>(data, val.data);
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
<[u8; $size] as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
<[u8; $size] as crate::packed::PackedField>::set::<OFFSET, SIZE>(data, &val.data);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]> + for<'a> crate::packed::PackedField<Output<'a> = T>> core::fmt::Debug for $name<T> {
|
||||
impl<T: AsRef<[u8]>> core::fmt::Debug for $name<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct(stringify!($name))
|
||||
$(
|
||||
@ -240,19 +234,19 @@ macro_rules! packed_struct {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> defmt::Format for $name<T> {
|
||||
fn format(&self, f: defmt::Formatter) {
|
||||
defmt::write!(f, "{} {{ ", stringify!($name));
|
||||
$(
|
||||
defmt::write!(f, "{}: {} ", stringify!($field), self.$field());
|
||||
)*
|
||||
defmt::write!(f, "}}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// packed_struct!(pub struct Test<8> {
|
||||
// #[offset = 8 * 6, size = 8]
|
||||
// test: u8,
|
||||
// });
|
||||
|
||||
// pub fn test() -> u8 {
|
||||
// let t = Test::new();
|
||||
// t.test()
|
||||
// }
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! packed_enum {
|
||||
(
|
||||
@ -290,27 +284,21 @@ macro_rules! packed_enum {
|
||||
}
|
||||
|
||||
impl crate::packed::PackedField for $name {
|
||||
type Output<'a> = Result<Self, $ty>;
|
||||
type Get<'a> = Result<Self, $ty>;
|
||||
type Set<'a> = Self;
|
||||
|
||||
#[inline]
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Output<'a> {
|
||||
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
|
||||
<$ty as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
let val = <$ty as crate::packed::PackedField>::get::<OFFSET, SIZE>(data);
|
||||
Self::try_from(val)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self) {
|
||||
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
|
||||
<$ty as crate::packed::PackedField>::assert::<OFFSET, SIZE>();
|
||||
<$ty as crate::packed::PackedField>::set::<OFFSET, SIZE>(data, val.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// gen_enum! {
|
||||
// pub enum Testas<u8> {
|
||||
// Hello = 0b111,
|
||||
// Test = 0b1111,
|
||||
// }
|
||||
// }
|
||||
|
@ -3,12 +3,14 @@
|
||||
#![feature(type_alias_impl_trait)]
|
||||
#![feature(async_fn_in_trait)]
|
||||
|
||||
use defmt::{panic, *};
|
||||
use defmt::{panic, todo, *};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::time::mhz;
|
||||
use embassy_stm32::usb_otg::{Driver, Instance};
|
||||
use embassy_stm32::{interrupt, Config};
|
||||
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
|
||||
use embassy_usb::class::msc::subclass::scsi::block_device::BlockDevice;
|
||||
use embassy_usb::class::msc::subclass::scsi::Scsi;
|
||||
use embassy_usb::class::msc::transport::bulk_only::BulkOnlyTransport;
|
||||
use embassy_usb::class::msc::transport::CommandSetHandler;
|
||||
use embassy_usb::class::msc::MscSubclass;
|
||||
@ -17,26 +19,58 @@ use embassy_usb::Builder;
|
||||
use futures::future::join;
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
struct CommandSet {}
|
||||
// struct CommandSet {}
|
||||
|
||||
impl CommandSetHandler for CommandSet {
|
||||
async fn command_out(
|
||||
&mut self,
|
||||
lun: u8,
|
||||
cmd: &[u8],
|
||||
pipe: &mut impl embassy_usb::class::msc::transport::DataPipeOut,
|
||||
) -> Result<(), embassy_usb::class::msc::transport::CommandError> {
|
||||
info!("CMD_OUT: {:?}", cmd);
|
||||
// impl CommandSetHandler for CommandSet {
|
||||
// async fn command_out(
|
||||
// &mut self,
|
||||
// lun: u8,
|
||||
// cmd: &[u8],
|
||||
// pipe: &mut impl embassy_usb::class::msc::transport::DataPipeOut,
|
||||
// ) -> Result<(), embassy_usb::class::msc::transport::CommandError> {
|
||||
// info!("CMD_OUT: {:?}", cmd);
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// async fn command_in(
|
||||
// &mut self,
|
||||
// lun: u8,
|
||||
// cmd: &[u8],
|
||||
// pipe: &mut impl embassy_usb::class::msc::transport::DataPipeIn,
|
||||
// ) -> Result<(), embassy_usb::class::msc::transport::CommandError> {
|
||||
// info!("CMD_IN: {:?}", cmd);
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
struct Device {
|
||||
data: [u8; 512 * 128],
|
||||
}
|
||||
|
||||
impl BlockDevice for Device {
|
||||
fn block_size(&self) -> usize {
|
||||
512
|
||||
}
|
||||
|
||||
fn num_blocks(&self) -> u32 {
|
||||
(self.data.len() / self.block_size()) as u32 - 1
|
||||
}
|
||||
|
||||
fn read_block(
|
||||
&self,
|
||||
lba: u32,
|
||||
block: &mut [u8],
|
||||
) -> Result<(), embassy_usb::class::msc::subclass::scsi::block_device::BlockDeviceError> {
|
||||
block.copy_from_slice(&self.data[lba as usize * 512..(lba as usize + 1) * 512]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn command_in(
|
||||
fn write_block(
|
||||
&mut self,
|
||||
lun: u8,
|
||||
cmd: &[u8],
|
||||
pipe: &mut impl embassy_usb::class::msc::transport::DataPipeIn,
|
||||
) -> Result<(), embassy_usb::class::msc::transport::CommandError> {
|
||||
info!("CMD_IN: {:?}", cmd);
|
||||
lba: u32,
|
||||
block: &[u8],
|
||||
) -> Result<(), embassy_usb::class::msc::subclass::scsi::block_device::BlockDeviceError> {
|
||||
self.data[lba as usize * 512..(lba as usize + 1) * 512].copy_from_slice(block);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -88,13 +122,15 @@ async fn main(_spawner: Spawner) {
|
||||
None,
|
||||
);
|
||||
|
||||
let scsi = Scsi::new(Device { data: [0u8; 512 * 128] });
|
||||
|
||||
let mut msc = BulkOnlyTransport::new(
|
||||
&mut builder,
|
||||
&mut state,
|
||||
MscSubclass::ScsiTransparentCommandSet,
|
||||
64,
|
||||
0,
|
||||
CommandSet {},
|
||||
scsi,
|
||||
);
|
||||
|
||||
// Build the builder.
|
||||
|
Loading…
Reference in New Issue
Block a user