More MSC work

This commit is contained in:
chemicstry 2023-02-17 01:30:45 +02:00
parent ab7e26a777
commit 9be45c3be9
14 changed files with 831 additions and 86 deletions

View File

@ -1,5 +1,7 @@
use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_hal_common::{into_ref, PeripheralRef};
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use embedded_storage::nor_flash::{
ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash,
};
pub use crate::pac::{ERASE_SIZE, ERASE_VALUE, FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; pub use crate::pac::{ERASE_SIZE, ERASE_VALUE, FLASH_BASE, FLASH_SIZE, WRITE_SIZE};
use crate::peripherals::FLASH; use crate::peripherals::FLASH;
@ -136,6 +138,8 @@ impl<'d> NorFlash for Flash<'d> {
} }
} }
impl<'d> MultiwriteNorFlash for Flash<'d> {}
/* /*
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(feature = "nightly")] if #[cfg(feature = "nightly")]

View File

@ -1127,6 +1127,7 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> {
impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> {
trace!("write ep={:?} data={:?}", self.info.addr, buf); trace!("write ep={:?} data={:?}", self.info.addr, buf);
// info!("w:{}", self.info.addr.index());
if buf.len() > self.info.max_packet_size as usize { if buf.len() > self.info.max_packet_size as usize {
return Err(EndpointError::BufferOverflow); return Err(EndpointError::BufferOverflow);
@ -1152,6 +1153,8 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
}) })
.await?; .await?;
info!("ww");
if buf.len() > 0 { if buf.len() > 0 {
poll_fn(|cx| { poll_fn(|cx| {
state.ep_in_wakers[index].register(cx.waker()); state.ep_in_wakers[index].register(cx.waker());
@ -1206,6 +1209,7 @@ impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> {
unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) }; unsafe { r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))) };
} }
info!("wd");
trace!("write done ep={:?}", self.info.addr); trace!("write done ep={:?}", self.info.addr);
Ok(()) Ok(())

View File

@ -31,8 +31,8 @@ pub trait BlockDevice {
/// to read/write functions. /// to read/write functions.
fn block_size(&self) -> Result<usize, BlockDeviceError>; fn block_size(&self) -> Result<usize, BlockDeviceError>;
/// Number of blocks in device (max LBA index) /// Number of blocks in device
fn max_lba(&self) -> Result<u32, BlockDeviceError>; fn num_blocks(&self) -> Result<u32, BlockDeviceError>;
/// Read the block indicated by `lba` into the provided buffer /// Read the block indicated by `lba` into the provided buffer
async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError>; async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError>;

View File

@ -21,7 +21,16 @@ use crate::class::msc::subclass::scsi::enums::{
PeripheralDeviceType, PeripheralQualifier, ResponseDataFormat, SpcVersion, TargetPortGroupSupport, PeripheralDeviceType, PeripheralQualifier, ResponseDataFormat, SpcVersion, TargetPortGroupSupport,
}; };
use crate::packed::BE; use crate::packed::BE;
use crate::packed_struct; use crate::{packed_enum, packed_struct};
packed_enum! {
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum VitalProductDataPage<u8> {
SupportedVitalProductDataPages = 0x00,
UnitSerialNumber = 0x80,
}
}
packed_struct! { packed_struct! {
pub struct InquiryCommand<6> { pub struct InquiryCommand<6> {
@ -30,7 +39,7 @@ packed_struct! {
#[offset = 1*8, size = 1] #[offset = 1*8, size = 1]
enable_vital_product_data: bool, enable_vital_product_data: bool,
#[offset = 2*8, size = 8] #[offset = 2*8, size = 8]
page_code: u8, page_code: VitalProductDataPage,
#[offset = 3*8, size = 16] #[offset = 3*8, size = 16]
allocation_length: BE<u16>, allocation_length: BE<u16>,
#[offset = 5*8, size = 8] #[offset = 5*8, size = 8]
@ -121,3 +130,29 @@ packed_struct! {
product_revision_level: [u8; 4], product_revision_level: [u8; 4],
} }
} }
packed_struct! {
pub struct SupportedVitalProductDataPages<4> {
#[offset = 0*8+0, size = 5]
peripheral_device_type: PeripheralDeviceType,
#[offset = 0*8+5, size = 3]
peripheral_qualifier: PeripheralQualifier,
#[offset = 1*8, size = 8]
page_code: VitalProductDataPage,
#[offset = 3*8, size = 8]
page_length: u8,
}
}
packed_struct! {
pub struct UnitSerialNumberPage<4> {
#[offset = 0*8+0, size = 5]
peripheral_device_type: PeripheralDeviceType,
#[offset = 0*8+5, size = 3]
peripheral_qualifier: PeripheralQualifier,
#[offset = 1*8, size = 8]
page_code: VitalProductDataPage,
#[offset = 3*8, size = 8]
page_length: u8,
}
}

View File

@ -1,4 +1,5 @@
use crate::class::msc::subclass::scsi::enums::MediumType; use crate::class::msc::subclass::scsi::enums::{MediumType, Mrie};
use crate::packed::BE;
use crate::{packed_enum, packed_struct}; use crate::{packed_enum, packed_struct};
packed_struct! { packed_struct! {
@ -35,19 +36,264 @@ packed_enum! {
#[derive(Clone, Copy, Eq, PartialEq, Debug)] #[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PageCode<u8> { pub enum PageCode<u8> {
CachingModePage = 0x08, Caching = 0x08,
InformationalExceptionsControl = 0x1C,
AllPages = 0x3F,
} }
} }
packed_struct! { packed_struct! {
pub struct CachingModePage<3> { pub struct ModePageHeader<2> {
#[offset = 0, size = 6] #[offset = 0, size = 6]
page_code: PageCode, page_code: PageCode,
#[offset = 1*8+0, size = 8] #[offset = 1*8+0, size = 8]
page_length: u8, page_length: u8,
#[offset = 2*8+0, size = 1] }
}
packed_struct! {
pub struct CachingModePage<18> {
/// RCD (READ Cache Disable) bit:
/// - `false` - SCSI READ commands may access the cache or the media.
/// - `true` - SCSI READ commands must access the media. Data cannot come from the cache
#[offset = 0*8+0, size = 1]
read_cache_disable: bool, read_cache_disable: bool,
#[offset = 2*8+2, size = 1] /// MF (Multiplication Factor) bit:
/// - `false` - The Minimum PREFETCH and Maximum PREFETCH fields are interpreted as a number of logical blocks.
/// - `true` - Specifies that the target shall interpret the minimum and maximum PREFETCH fields to be specified in terms of a scalar number which,
/// when multiplied by the number of logical blocks to be transferred for the current command, yields the number of logical blocks for each
/// of the respective types of PREFETCH.
#[offset = 0*8+1, size = 1]
multiplication_factor: bool,
/// WCE (Write Cache Enable) bit:
/// - `false` - SCSI WRITE commands may not return status and completion message bytes until all data has been written to the media.
/// - `true` - SCSI WRITE commands may return status and completion message bytes as soon as all data has been received from the host.
#[offset = 0*8+2, size = 1]
write_cache_enable: bool, write_cache_enable: bool,
/// SIZE (Size Enable) bit:
/// - `false` - Initiator requests that the Number of Cache Segments is to be used to control caching segmentation.
/// - `true` - Indicates that the Cache Segment Size is to be used to control caching segmentation.
#[offset = 0*8+3, size = 1]
size_enable: bool,
/// DISC (Discontinuity) bit:
/// - `false` - When set to zero, the DISC requests that prefetches be truncated at time discontinuities.
/// - `true` - The Discontinuity (DISC) bit, when set to one, requests that the SCSI device continue the PREFETCH across time discontinuities, such as
/// across cylinders or tracks up to the limits of the buffer, or segment, space available for PREFETCH.
#[offset = 0*8+4, size = 1]
discontinuity: bool,
/// CAP (Caching Analysis Permitted) bit:
/// - `false` - A zero indicates caching analysis is disabled. Caching analysis results are placed in the SCSI logging information table.
/// See individual drives Product Manual, Volume 1, SCSI Bus Conditions and Miscellaneous Features Supported table.
/// - `true` - The Caching Analysis Permitted (CAP) bit, when set to one, enables caching analysis.
#[offset = 0*8+5, size = 1]
caching_analysis_permitted: bool,
/// ABPF (Abort Prefetch) bit:
/// - `false` - When set to zero, with the DRA bit equal to zero, the termination of any active PREFETCH is dependent upon Caching Page bytes 4
/// through 11 and is operation and/or vendor-specific.
/// - `true` - The ABORT PREFETCH (ABPF) bit, when set to one, with the DRA bit equal to zero, requests that the SCSI device abort the PREFETCH
/// upon selection. The ABPF set to one takes precedence over the Minimum PREFETCH bytes.
#[offset = 0*8+6, size = 1]
abort_prefetch: bool,
/// IC (Initiator Control) bit:
/// - `false` - When IC is set to ZERO, ARLA is enabled. Since Seagate drives covered by this manual do not organize the cache according to size of segment, but rather by number of segments, this bit is used to enable or disable ARLA (in non-Seagate equipment, this might be used to
/// designate cache size).
/// - `true` - When the Initiator Control (IC) enable bit is set to one, adaptive read look-ahead (ARLA) is disabled.
#[offset = 0*8+7, size = 1]
initiator_control: bool,
/// WRITE RETENTION PRIORITY. The cache replacement algorithm does distinguish between retention in the cache of host-requested data and
/// PREFETCH data. Therefore, this half byte is always 0.
#[offset = 1*8+0, size = 4]
write_retention_priority: u8,
/// DEMAND READ RETENTION PRIORITY. The cache replacement algorithm does not distinguish between retention in the cache of
/// host-requested data and PREFETCH data. Therefore, this half byte is always 0.
#[offset = 1*8+4, size = 4]
demand_read_retention_priority: u8,
/// DISABLE PREFETCH TRANSFER LENGTH. PREFETCH is disabled for any SCSI READ command whose requested transfer length exceeds this value.
#[offset = 2*8+0, size = 16]
disable_prefetch_transfer_length: BE<u16>,
/// The MINIMUM PREFETCH specifies the minimum number sectors to prefetch, regardless of the delay it may cause to other commands.
#[offset = 4*8+0, size = 16]
minimum_prefetch: BE<u16>,
/// The MAXIMUM PREFETCH specifies the maximum number of logical blocks that may be prefetched. The PREFETCH operation may be aborted
/// before the MAXIMUM PREFETCH value is reached, but only if the MINIMUM PREFETCH value has been satisfied.
#[offset = 6*8+0, size = 16]
maximum_prefetch: BE<u16>,
/// The MAXIMUM PREFETCH Ceiling specifies an upper limit on the number of logical blocks computed as the maximum prefetch.
/// If the MAXIMUM PREFETCH value is greater than the MAXIMUM PREFETCH CEILING, the value is truncated to the MAXIMUM PREFETCH CEILING value.
#[offset = 8*8+0, size = 16]
maximum_prefetch_ceiling: BE<u16>,
/// NV_DIS bit:
/// - `false` - An NV_DIS bit set to zero specifies that the device server may use a non-volatile cache and indicates that a non-volatile cache may be
/// present and enabled.
/// - `true` - An NV_DIS bit set to one specifies that the device server shall disable a non-volatile cache and indicates that a non-volatile cache is supported but disabled.
#[offset = 10*8+0, size = 1]
non_volatile_cache_disable: bool,
/// The synchronize cache progress indication support (SYNC_PROG) field specifies device server progress indication reporting for the
/// SYNCHRONIZE CACHE commands as defined in 3.52.
#[offset = 10*8+1, size = 2]
sync_prog: u8,
/// DRA (Disable READ-Ahead) bit:
/// - `false` - When the DRA bit equals zero, the target may continue to read logical blocks into the buffer beyond the addressed logical block(s).
/// - `true` - The Disable READ-Ahead (DRA) bit, when set to one, requests that the target not read into the buffer any logical blocks beyond the
/// addressed logical block(s).
#[offset = 10*8+5, size = 1]
disable_read_ahead: bool,
/// LBCSS bit:
/// - `false` - An LBCSS bit set to zero specifies that the CACHE SEGMENT SIZE field units shall be interpreted as bytes. The LBCSS shall not impact the
/// units of other fields.
/// - `true` - A logical block cache segment size (LBCSS) bit set to one specifies that the CACHE SEGMENT SIZE field units shall be interpreted as logical blocks.
#[offset = 10*8+6, size = 1]
logical_block_cache_segment_size: bool,
/// FSW (FORCE SEQUENTIAL WRITE) bit:
/// - `false` - When the FSW bit equals zero, the target is allowed to reorder the sequence of writing addressed logical blocks in order to achieve a
/// faster command completion.
/// - `true` - The Force Sequential Write (FSW) bit, when set to one, indicates that multiple block writes are to be transferred over the SCSI bus and
/// written to the media in an ascending, sequential, logical block order.
#[offset = 10*8+7, size = 1]
fsw: bool,
/// The NUMBER OF CACHE SEGMENTS byte gives the number of segments into which the host requests the drive divide the cache.
#[offset = 11*8+0, size = 8]
num_cache_segments: u8,
/// The CACHE SEGMENT SIZE field indicates the requested segment size in bytes. This manual assumes that the Cache Segment Size field is valid
/// only when the Size bit is one.
#[offset = 12*8+0, size = 16]
cache_segment_size: BE<u16>,
}
}
packed_struct! {
/// The Informational Exceptions Control mode page (see table 390) defines the methods used by the device server to control the
/// reporting and the operations of specific informational exception conditions. This page shall only apply to informational
/// exceptions that report an additional sense code of FAILURE PREDICTION THRESHOLD EXCEEDED or an additional sense code of
/// WARNING to the application client. The mode page policy (see 5.4.14) for this mode page shall be shared, or per I_T nexus.
///
/// Informational exception conditions occur as the result of vendor specific events within a logical unit. An informational exception
/// condition may occur asynchronous to any commands issued by an application client.
pub struct InformationalExceptionsControlModePage<10> {
/// LOGERR (Log Error) bit:
/// - `false` - If the log errors (LOGERR) bit is set to zero, the logging of informational exception conditions by a device server is vendor specific.
/// - `true` - If the LOGERR bit is set to one, the device server shall log informational exception conditions.
#[offset = 0*8+0, size = 1]
log_error: bool,
/// EBACKERR (enable background error) bit:
/// - `false` - An enable background error (EBACKERR) bit set to zero indicates the target shall disable reporting of background self-test errors (SPC-5)
/// and background scan errors (see SBC-4).
/// - `true` - An EBACKERR bit set to one indicates reporting of background self-test errors and background scan errors shall be enabled. The method
/// for reporting background self-test errors and background scan errors is determined by contents of the mrie field. Background self-test
/// errors and background scan errors shall be reported as soon as the method specified in the mrie field occurs (i.e., the interval timer field
/// and report count field do not apply for background self-test errors and background scan errors).
#[offset = 0*8+1, size = 1]
enable_background_error: bool,
/// TEST bit:
/// - `false` - A TEST bit set to zero shall instruct the device server not to generate any test device failure notifications.
/// - `true` - If DEXCPT bit is set to zero and the TEST bit set to one, then the device server shall create a test device failure as specified by the MRIE
/// field, INTERVAL TIMER field, and REPORT COUNT field (see table 390). The test device failure shall be reported with the additional sense
/// code set to FAILURE PREDICTION THRESHOLD EXCEEDED (FALSE). If both the TEST bit and the DEXCPT bit are set to one, then the MODE
/// SELECT command shall be terminated with CHECK CONDITION status, with the sense key set to ILLEGAL REQUEST, and the additional
/// sense code set to INVALID FIELD IN PARAMETER LIST.
#[offset = 0*8+2, size = 1]
test: bool,
/// DEXCPT (Disable Exception Control) bit:
/// - `false` - A disable exception control (DEXCPT) bit set to zero indicates the failure prediction threshold exceeded reporting shall be enabled. The
/// method for reporting the failure prediction threshold exceeded when the DEXCPT bit is set to zero is determined from the MRIE field.
/// - `true` - A DEXCPT bit set to one indicates the device server shall disable reporting of the failure prediction threshold exceeded. The MRIE field is
/// ignored when DEXCPT is set to one and EWASC is set to zero.
#[offset = 0*8+3, size = 1]
disable_exception_control: bool,
/// EWASC (Enable Warning) bit:
/// - `false` - If the enable warning (EWASC) bit is set to zero, the device server shall disable reporting of the warning. The MRIE field is ignored when
/// DEXCPT is set to one and EWASC is set to zero.
/// - `true` - If the EWASC bit is set to one, warning reporting shall be enabled. The method for reporting the warning when the EWASC bit is set to
/// one is determined from the MRIE field.
#[offset = 0*8+4, size = 1]
enable_warning: bool,
/// EBF (Enable Background Function) bit:
/// - `false` - If the EBF bit is set to zero, the device server shall disable the functions. Background functions with separate enable control bits (e.g.,
/// background medium scan defined in 4.3.7 are not controlled by this bit.
/// - `true` - If background functions are supported and the Enable Background Function (EBF) bit is set to one, then the device server shall enable
/// background functions.
///
/// For the purposes of the EBF bit, background functions are defined as idle time functions that may impact performance that are performed by a
/// device server operating without errors but do not impact the reliability of the logical unit (e.g., read scan).
#[offset = 0*8+5, size = 1]
enable_background_function: bool,
/// PERF (Performance) bit:
/// - `false` - If the performance (PERF) bit is set to zero, informational exception operations that are the cause of delays are acceptable.
/// - `true` - If the PERF bit is set to one, the device server shall not cause delays while doing informational exception operations. A PERF bit set to
/// one may cause the device server to disable some or all of the informational exceptions operations, thereby limiting the reporting of
/// informational exception conditions.
#[offset = 0*8+7, size = 1]
performance: bool,
/// The value in the method of reporting informational exceptions (MRIE) field (see table 390) defines the method that shall be used
/// by the device server to report informational exception conditions. The priority of reporting multiple information exceptions is
/// vendor specific.
#[offset = 1*8+0, size = 4]
mire: Mrie,
/// The INTERVAL TIMER field specifies the period in 100 millisecond increments that the device server shall use for reporting that an informational
/// exception condition has occurred (see table 392). After an informational exception condition has been reported, the interval timer shall be
/// started. An INTERVAL TIMER field set to zero or FFFF_FFFFh specifies that the period for reporting an informational exception condition is
/// vendor specific.
#[offset = 2*8+0, size = 32]
interval_timer: BE<u32>,
/// The REPORT COUNT field specifies the maximum number of times the device server may report an informational exception condition to the
/// application client. A REPORT COUNT field set to zero specifies that there is no limit on the number of times the device server may report an
/// informational exception condition.
#[offset = 6*8+0, size = 32]
report_count: BE<u32>,
}
}
pub enum ModeParameterWriterError {
BufferTooSmall,
}
pub struct ModeParameter6Writer<'d> {
buf: &'d mut [u8],
offset: usize,
}
impl<'d> ModeParameter6Writer<'d> {
pub fn new(buf: &'d mut [u8]) -> Result<Self, ModeParameterWriterError> {
if buf.len() < ModeParameterHeader6::SIZE {
Err(ModeParameterWriterError::BufferTooSmall)
} else {
Ok(Self {
buf,
offset: ModeParameterHeader6::SIZE,
})
}
}
pub fn write_page<'a>(
&'a mut self,
page_code: PageCode,
size: usize,
) -> Result<&'a mut [u8], ModeParameterWriterError> {
if self.buf.len() < self.offset + 2 + size {
Err(ModeParameterWriterError::BufferTooSmall)
} else {
let mut page_header = ModePageHeader::from_bytes(&mut self.buf[self.offset..self.offset + 2]).unwrap();
page_header.data.fill(0x00);
page_header.set_page_code(page_code);
page_header.set_page_length(size as u8);
self.offset += 2;
let page_data = &mut self.buf[self.offset..self.offset + size];
self.offset += size;
page_data.fill(0x00);
Ok(page_data)
}
}
pub fn page_size(&self) -> usize {
self.offset - ModeParameterHeader6::SIZE
}
pub fn finalize(self) -> &'d [u8] {
let mut header = ModeParameterHeader6::from_bytes(&mut self.buf[..ModeParameterHeader6::SIZE]).unwrap();
header.data.fill(0x00);
header.set_mode_data_length(self.offset as u8 - 1);
&self.buf[..self.offset]
} }
} }

View File

@ -1,4 +1,5 @@
use super::control::Control; use super::control::Control;
use super::PageCode;
use crate::class::msc::subclass::scsi::enums::PageControl; use crate::class::msc::subclass::scsi::enums::PageControl;
use crate::packed_struct; use crate::packed_struct;
@ -13,7 +14,7 @@ packed_struct! {
disable_block_descriptors: bool, disable_block_descriptors: bool,
/// The PAGE CODE and SUBPAGE CODE fields specify which mode pages and subpages to return /// The PAGE CODE and SUBPAGE CODE fields specify which mode pages and subpages to return
#[offset = 2*8+0, size = 6] #[offset = 2*8+0, size = 6]
page_code: u8, page_code: PageCode,
#[offset = 2*8+6, size = 2] #[offset = 2*8+6, size = 2]
page_control: PageControl, page_control: PageControl,
#[offset = 3*8+0, size = 8] #[offset = 3*8+0, size = 8]

View File

@ -27,9 +27,9 @@ packed_struct! {
#[offset = 3*8, size = 8] #[offset = 3*8, size = 8]
capacity_list_length: u8, capacity_list_length: u8,
#[offset = 4*8, size = 32] #[offset = 4*8, size = 32]
max_lba: BE<u32>, num_blocks: BE<u32>,
#[offset = 8*8, size = 2] #[offset = 8*8, size = 2]
descriptor_type: BE<u32>, descriptor_type: u8,
// TODO should be 24 bits // TODO should be 24 bits
#[offset = 8*8, size = 32] #[offset = 8*8, size = 32]
block_size: BE<u32>, block_size: BE<u32>,

View File

@ -246,3 +246,56 @@ impl AdditionalSenseCode {
} }
} }
} }
packed_enum! {
/// Method Of Reporting Informational Exceptions
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Mrie<u8> {
/// The device server shall not report information exception conditions.
NoReportingOfInformationalExceptionCondition = 0x0,
/// The device server shall report informational exception conditions by establishing a unit attention
/// condition (see SAM-5) for the initiator port associated with every I_T nexus, with the additional sense code set to indicate the
/// cause of the informational exception condition.
///
/// As defined in SAM-5, the command that has the CHECK CONDITION status with the sense key set to UNIT ATTENTION is not
/// processed before the informational exception condition is reported.
GenerateUnitAttention = 0x2,
/// The device server shall report informational exception conditions, if the reporting of
/// recovered errors is allowed, [a] by returning a CHECK CONDITION status. If the TEST bit is set to zero, the status may be returned
/// after the informational exception condition occurs on any command for which GOOD status or INTERMEDIATE status would have
/// been returned. If the TEST bit is set to one, the status shall be returned on the next command received on any I_T nexus that is
/// normally capable of returning an informational exception condition when the test bit is set to zero. The sense key shall be set to
/// RECOVERED ERROR and the additional sense code shall indicate the cause of the informational exception condition.
///
/// The command that returns the CHECK CONDITION for the informational exception shall complete without error before any
/// informational exception condition may be reported.
ConditionallyGenerateRecoveredError = 0x3,
/// The device server shall report informational exception conditions, regardless of
/// whether the reporting of recovered errors is allowed, [a] by returning a CHECK CONDITION status. If the TEST bit is set to zero, the
/// status may be returned after the informational exception condition occurs on any command for which GOOD status or
/// INTERMEDIATE status would have been returned. If the TEST bit is set to one, the status shall be returned on the next command
/// received on any I_T nexus that is normally capable of returning an informational exception condition when the TEST bit is set to
/// zero. The sense key shall be set to RECOVERED ERROR and the additional sense code shall indicate the cause of the informational
/// exception condition.
///
/// The command that returns the CHECK CONDITION for the informational exception shall complete without error before any
/// informational exception condition may be reported.
UnconditionallyGenerateRecoveredError = 0x4,
/// The device server shall report informational exception conditions by returning a CHECK CONDITION status. If
/// the TEST bit is set to zero, the status may be returned after the informational exception condition occurs on any command for
/// which GOOD status or INTERMEDIATE status would have been returned. If the TEST bit is set to one, the status shall be returned on
/// the next command received on any I_T nexus that is normally capable of returning an informational exception condition when
/// the TEST bit is set to zero. The sense key shall be set to NO SENSE and the additional sense code shall indicate the cause of the
/// informational exception condition.
///
/// The command that returns the CHECK CONDITION for the informational exception shall complete without error before any
/// informational exception condition may be reported.
GenerateNoSense = 0x5,
/// The device server shall preserve the informational exception(s)
/// information. To find out about information exception conditions the application client polls the device server by issuing a
/// REQUEST SENSE command. In the REQUEST SENSE parameter data that contains the sense data, the sense key shall be set to NO
/// SENSE and the additional sense code shall indicate the cause of the informational exception condition.
OnlyReportInformationalExceptionConditionOnRequest = 0x6,
}
}

View File

@ -8,9 +8,11 @@ use core::mem::MaybeUninit;
use self::block_device::{BlockDevice, BlockDeviceError}; use self::block_device::{BlockDevice, BlockDeviceError};
use self::enums::AdditionalSenseCode; use self::enums::AdditionalSenseCode;
use crate::class::msc::subclass::scsi::commands::{ use crate::class::msc::subclass::scsi::commands::{
InquiryCommand, InquiryResponse, ModeParameterHeader6, ModeSense6Command, PreventAllowMediumRemoval, Read10Command, CachingModePage, InformationalExceptionsControlModePage, InquiryCommand, InquiryResponse, ModeParameter6Writer,
ReadCapacity10Command, ReadCapacity10Response, ReadFormatCapacitiesCommand, ReadFormatCapacitiesResponse, ModeParameterHeader6, ModeSense6Command, PageCode, PreventAllowMediumRemoval, Read10Command, ReadCapacity10Command,
RequestSenseCommand, RequestSenseResponse, TestUnitReadyCommand, Write10Command, ReadCapacity10Response, ReadFormatCapacitiesCommand, ReadFormatCapacitiesResponse, RequestSenseCommand,
RequestSenseResponse, SupportedVitalProductDataPages, TestUnitReadyCommand, UnitSerialNumberPage,
VitalProductDataPage, Write10Command,
}; };
use crate::class::msc::subclass::scsi::enums::{ use crate::class::msc::subclass::scsi::enums::{
PeripheralDeviceType, PeripheralQualifier, ResponseCode, ResponseDataFormat, SenseKey, SpcVersion, PeripheralDeviceType, PeripheralQualifier, ResponseCode, ResponseDataFormat, SenseKey, SpcVersion,
@ -31,17 +33,18 @@ pub struct SenseData {
asc: AdditionalSenseCode, asc: AdditionalSenseCode,
} }
pub struct Scsi<B: BlockDevice> { pub struct Scsi<'d, B: BlockDevice> {
/// Backing storage block device /// Backing storage block device
device: B, device: B,
buffer: &'d mut [u8],
/// Last operation sense data /// Last operation sense data
sense: Option<SenseData>, sense: Option<SenseData>,
vendor_id: [u8; 8], vendor_id: [u8; 8],
product_id: [u8; 16], product_id: [u8; 16],
} }
impl<B: BlockDevice> Scsi<B> { impl<'d, B: BlockDevice> Scsi<'d, B> {
pub fn new(device: B, vendor: &str, product: &str) -> Self { pub fn new(device: B, buffer: &'d mut [u8], vendor: &str, product: &str) -> Self {
let mut vendor_id = [b' '; 8]; let mut vendor_id = [b' '; 8];
fill_from_slice(&mut vendor_id, vendor.as_bytes()); fill_from_slice(&mut vendor_id, vendor.as_bytes());
@ -50,6 +53,7 @@ impl<B: BlockDevice> Scsi<B> {
Self { Self {
device, device,
buffer,
sense: None, sense: None,
vendor_id, vendor_id,
product_id, product_id,
@ -82,27 +86,32 @@ impl<B: BlockDevice> Scsi<B> {
// If the device server does not support the medium changer prevent state, it shall terminate the // If the device server does not support the medium changer prevent state, it shall terminate the
// PREVENT ALLOW MEDIUM REMOVAL command with CHECK CONDITION status with the sense // PREVENT ALLOW MEDIUM REMOVAL command with CHECK CONDITION status with the sense
// key set to ILLEGAL REQUEST and the additional sense code set to INVALID FIELD IN CDB. // key set to ILLEGAL REQUEST and the additional sense code set to INVALID FIELD IN CDB.
Err(InternalError::CustomSenseData(SenseData { if req.prevent() {
key: SenseKey::IllegalRequest, Err(ERR_INVALID_FIELD_IN_CBD)
asc: AdditionalSenseCode::InvalidFieldInCdb, } else {
})) Ok(())
}
} }
Write10Command::OPCODE => { Write10Command::OPCODE => {
let req = Write10Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?; let req = Write10Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?;
debug!("{:?}", req); debug!("{:?}", req);
let mut data = MaybeUninit::<[u8; 512]>::uninit();
let start_lba = req.lba(); let start_lba = req.lba();
let transfer_length = req.transfer_length() as u32; let transfer_length = req.transfer_length() as u32;
if start_lba + transfer_length > self.device.max_lba()? + 1 { if start_lba + transfer_length > self.device.num_blocks()? {
return Err(InternalError::LbaOutOfRange); return Err(InternalError::LbaOutOfRange);
} }
let block_size = self.device.block_size()?;
assert!(
block_size <= self.buffer.len(),
"SCSI buffer smaller than device block size"
);
for lba in start_lba..start_lba + transfer_length { for lba in start_lba..start_lba + transfer_length {
pipe.read(unsafe { data.assume_init_mut() }).await?; pipe.read(&mut self.buffer[..block_size]).await?;
self.device.write_block(lba, unsafe { data.assume_init_ref() }).await?; self.device.write_block(lba, &self.buffer[..block_size]).await?;
} }
Ok(()) Ok(())
@ -128,6 +137,40 @@ impl<B: BlockDevice> Scsi<B> {
let req = InquiryCommand::from_bytes(cmd).ok_or(InternalError::CommandParseError)?; let req = InquiryCommand::from_bytes(cmd).ok_or(InternalError::CommandParseError)?;
debug!("{:#?}", req); debug!("{:#?}", req);
if req.enable_vital_product_data() {
match req.page_code() {
Ok(VitalProductDataPage::SupportedVitalProductDataPages) => {
const SUPPORTED_PAGES: [VitalProductDataPage; 2] = [
VitalProductDataPage::SupportedVitalProductDataPages,
VitalProductDataPage::UnitSerialNumber,
];
let mut buf = [0u8; SupportedVitalProductDataPages::SIZE + SUPPORTED_PAGES.len()];
let mut svpdp = SupportedVitalProductDataPages::from_bytes(&mut buf).unwrap();
svpdp.set_page_code(VitalProductDataPage::SupportedVitalProductDataPages);
svpdp.set_page_length(SUPPORTED_PAGES.len() as u8);
for (i, page) in SUPPORTED_PAGES.iter().enumerate() {
buf[SupportedVitalProductDataPages::SIZE + i] = (*page).into();
}
pipe.write(&buf).await?;
Ok(())
}
Ok(VitalProductDataPage::UnitSerialNumber) => {
const SERIAL_NUMBER: &[u8; 8] = b"01020304";
let mut buf = [0u8; UnitSerialNumberPage::SIZE + SERIAL_NUMBER.len()];
let mut usnp = UnitSerialNumberPage::from_bytes(&mut buf).unwrap();
usnp.set_page_code(VitalProductDataPage::UnitSerialNumber);
usnp.set_page_length(SERIAL_NUMBER.len() as u8);
buf[UnitSerialNumberPage::SIZE..].copy_from_slice(SERIAL_NUMBER);
pipe.write(&buf).await?;
Ok(())
}
_ => Err(ERR_INVALID_FIELD_IN_CBD),
}
} else {
let mut resp = InquiryResponse::new(); let mut resp = InquiryResponse::new();
resp.set_peripheral_device_type(PeripheralDeviceType::DirectAccessBlock); resp.set_peripheral_device_type(PeripheralDeviceType::DirectAccessBlock);
resp.set_peripheral_qualifier(PeripheralQualifier::Connected); resp.set_peripheral_qualifier(PeripheralQualifier::Connected);
@ -146,21 +189,65 @@ impl<B: BlockDevice> Scsi<B> {
resp.set_enclosure_services(false); resp.set_enclosure_services(false);
resp.set_vendor_identification(&self.vendor_id); resp.set_vendor_identification(&self.vendor_id);
resp.set_product_identification(&self.product_id); resp.set_product_identification(&self.product_id);
resp.set_product_revision_level(&[b' '; 4]); resp.set_product_revision_level(b"1.00");
pipe.write(&resp.data).await?; pipe.write(&resp.data).await?;
Ok(()) Ok(())
} }
}
ModeSense6Command::OPCODE => { ModeSense6Command::OPCODE => {
let req = ModeSense6Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?; let req = ModeSense6Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?;
debug!("{:?}", req); debug!("{:?} {:?}", req, cmd);
let mut header = ModeParameterHeader6::new(); // let mut buf = [0u8; ModeParameterHeader6::SIZE + CachingModePage::SIZE];
header.set_mode_data_length(ModeParameterHeader6::SIZE as u8 - 1); // let mut header = ModeParameterHeader6::from_bytes(&mut buf[..ModeParameterHeader6::SIZE]).unwrap();
pipe.write(&header.data).await?; // 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 - 2);
// caching_mode_page.set_read_cache_disable(true);
// caching_mode_page.set_write_cache_enable(false);
// pipe.write(&buf).await?;
let mut writer = ModeParameter6Writer::new(self.buffer).map_err(|_| ERR_INVALID_FIELD_IN_CBD)?;
let all_pages = matches!(req.page_code(), Ok(PageCode::AllPages));
if all_pages || matches!(req.page_code(), Ok(PageCode::Caching)) {
let mut caching_mode_page = CachingModePage::from_bytes(
writer
.write_page(PageCode::Caching, CachingModePage::SIZE)
.map_err(|_| ERR_INVALID_FIELD_IN_CBD)?,
)
.unwrap();
caching_mode_page.set_read_cache_disable(true);
caching_mode_page.set_write_cache_enable(false);
}
if all_pages || matches!(req.page_code(), Ok(PageCode::InformationalExceptionsControl)) {
let mut _iec = InformationalExceptionsControlModePage::from_bytes(
writer
.write_page(
PageCode::InformationalExceptionsControl,
InformationalExceptionsControlModePage::SIZE,
)
.map_err(|_| ERR_INVALID_FIELD_IN_CBD)?,
)
.unwrap();
}
// Should never return zero pages
if writer.page_size() == 0 {
Err(ERR_INVALID_FIELD_IN_CBD)
} else {
pipe.write(writer.finalize()).await?;
Ok(()) Ok(())
} }
}
RequestSenseCommand::OPCODE => { RequestSenseCommand::OPCODE => {
let req = RequestSenseCommand::from_bytes(cmd).ok_or(InternalError::CommandParseError)?; let req = RequestSenseCommand::from_bytes(cmd).ok_or(InternalError::CommandParseError)?;
debug!("{:?}", req); debug!("{:?}", req);
@ -190,7 +277,7 @@ impl<B: BlockDevice> Scsi<B> {
debug!("{:?}", req); debug!("{:?}", req);
let mut resp = ReadCapacity10Response::new(); let mut resp = ReadCapacity10Response::new();
resp.set_max_lba(self.device.max_lba()?); resp.set_max_lba(self.device.num_blocks()? - 1);
resp.set_block_size(self.device.block_size()? as u32); resp.set_block_size(self.device.block_size()? as u32);
pipe.write(&resp.data).await?; pipe.write(&resp.data).await?;
@ -202,8 +289,9 @@ impl<B: BlockDevice> Scsi<B> {
let mut resp = ReadFormatCapacitiesResponse::new(); let mut resp = ReadFormatCapacitiesResponse::new();
resp.set_capacity_list_length(8); resp.set_capacity_list_length(8);
resp.set_max_lba(self.device.max_lba()?); resp.set_num_blocks(self.device.num_blocks()?);
resp.set_block_size(self.device.block_size()? as u32); resp.set_block_size(self.device.block_size()? as u32);
resp.set_descriptor_type(0x03);
pipe.write(&resp.data).await?; pipe.write(&resp.data).await?;
return Ok(()); return Ok(());
@ -212,19 +300,23 @@ impl<B: BlockDevice> Scsi<B> {
let req = Read10Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?; let req = Read10Command::from_bytes(cmd).ok_or(InternalError::CommandParseError)?;
debug!("{:?}", req); debug!("{:?}", req);
let mut data = MaybeUninit::<[u8; 512]>::uninit();
let start_lba = req.lba(); let start_lba = req.lba();
let transfer_length = req.transfer_length() as u32; let transfer_length = req.transfer_length() as u32;
if start_lba + transfer_length > self.device.max_lba()? + 1 { if start_lba + transfer_length > self.device.num_blocks()? {
return Err(InternalError::LbaOutOfRange); return Err(InternalError::LbaOutOfRange);
} }
for lba in start_lba..start_lba + transfer_length { let block_size = self.device.block_size()?;
self.device.read_block(lba, unsafe { data.assume_init_mut() }).await?; assert!(
block_size <= self.buffer.len(),
"SCSI buffer smaller than device block size"
);
pipe.write(unsafe { data.assume_init_ref() }).await?; for lba in start_lba..start_lba + transfer_length {
self.device.read_block(lba, &mut self.buffer[..block_size]).await?;
pipe.write(&self.buffer[..block_size]).await?;
} }
Ok(()) Ok(())
@ -234,7 +326,7 @@ impl<B: BlockDevice> Scsi<B> {
} }
} }
impl<B: BlockDevice> CommandSetHandler for Scsi<B> { impl<'d, B: BlockDevice> CommandSetHandler for Scsi<'d, B> {
const MSC_SUBCLASS: MscSubclass = MscSubclass::ScsiTransparentCommandSet; const MSC_SUBCLASS: MscSubclass = MscSubclass::ScsiTransparentCommandSet;
const MAX_LUN: u8 = 0; const MAX_LUN: u8 = 0;
@ -302,6 +394,12 @@ enum InternalError {
CustomSenseData(SenseData), CustomSenseData(SenseData),
} }
/// Commonly used error (IllegalRequest, InvalidFieldInCdb)
const ERR_INVALID_FIELD_IN_CBD: InternalError = InternalError::CustomSenseData(SenseData {
key: SenseKey::IllegalRequest,
asc: AdditionalSenseCode::InvalidFieldInCdb,
});
impl InternalError { impl InternalError {
fn into_sense_data(self) -> SenseData { fn into_sense_data(self) -> SenseData {
match self { match self {

View File

@ -83,23 +83,17 @@ impl<'d, D: Driver<'d>, C: CommandSetHandler> BulkOnlyTransport<'d, D, C> {
} }
async fn receive_control_block_wrapper(&mut self) -> CommandBlockWrapper { async fn receive_control_block_wrapper(&mut self) -> CommandBlockWrapper {
let mut cbw_buf = [0u8; size_of::<CommandBlockWrapper>()]; let mut cbw_buf = [0u8; 64];
loop { loop {
// CBW is always sent at a packet boundary and is a short packet of 31 bytes // CBW is always sent at a packet boundary and is a short packet of 31 bytes
match self.read_ep.read(&mut cbw_buf).await { match self.read_ep.read(&mut cbw_buf).await {
Ok(len) => { Ok(_) => match CommandBlockWrapper::from_bytes(&cbw_buf) {
if len != cbw_buf.len() {
error!("Invalid CBW length");
}
match CommandBlockWrapper::from_bytes(&cbw_buf) {
Ok(cbw) => return cbw, Ok(cbw) => return cbw,
Err(e) => { Err(e) => {
error!("Invalid CBW: {:?}", e); error!("Invalid CBW: {:?}", e);
} }
} },
}
Err(e) => match e { Err(e) => match e {
EndpointError::BufferOverflow => { EndpointError::BufferOverflow => {
error!("Host sent too long CBW"); error!("Host sent too long CBW");

View File

@ -82,7 +82,7 @@ impl PackedField for u8 {
fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> { fn get<'a, const OFFSET: usize, const SIZE: usize>(data: &'a [u8]) -> Self::Get<'a> {
let byte = OFFSET / 8; let byte = OFFSET / 8;
let bit = OFFSET % 8; let bit = OFFSET % 8;
let mask = (0xFF >> SIZE) << bit; let mask = (0xFF >> (8 - SIZE)) << bit;
(data[byte] & mask) >> bit (data[byte] & mask) >> bit
} }
@ -90,7 +90,7 @@ impl PackedField for u8 {
fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) { fn set<const OFFSET: usize, const SIZE: usize>(data: &mut [u8], val: Self::Set<'_>) {
let byte = OFFSET / 8; let byte = OFFSET / 8;
let bit = OFFSET % 8; let bit = OFFSET % 8;
let mask = (0xFF >> SIZE) << bit; let mask = (0xFF >> (8 - SIZE)) << bit;
data[byte] = (val << bit) | (data[byte] & !mask); data[byte] = (val << bit) | (data[byte] & !mask);
} }
} }

View File

@ -0,0 +1,144 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(async_fn_in_trait)]
use core::cell::RefCell;
use core::ops::Range;
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::flash::{self, Flash};
use embassy_stm32::time::mhz;
use embassy_stm32::usb_otg::Driver;
use embassy_stm32::{interrupt, Config};
use embassy_usb::class::msc::subclass::scsi::block_device::{BlockDevice, BlockDeviceError};
use embassy_usb::class::msc::subclass::scsi::Scsi;
use embassy_usb::class::msc::transport::bulk_only::BulkOnlyTransport;
use embassy_usb::Builder;
use embedded_storage::nor_flash::RmwMultiwriteNorFlashStorage;
use embedded_storage::{ReadStorage, Storage};
use futures::future::join;
use {defmt_rtt as _, panic_probe as _};
// Ideally we would use 128K block size, which is the flash sector size of STM32,
// however, most operating systems only support 512 or 4096 byte blocks.
//
// To work around this limitation we must use RmwMultiwriteNorFlashStorage, which performs
// read-modify(-erase)-write operations on flash storage and optimises the number of erase
// operations.
//
// WARNING: this example is way too slow to
const BLOCK_SIZE: usize = 512;
struct FlashBlockDevice<'d> {
flash: RefCell<RmwMultiwriteNorFlashStorage<'d, Flash<'d>>>,
range: Range<usize>,
}
impl<'d> BlockDevice for FlashBlockDevice<'d> {
fn status(&self) -> Result<(), BlockDeviceError> {
Ok(())
}
fn block_size(&self) -> Result<usize, BlockDeviceError> {
Ok(BLOCK_SIZE)
}
fn num_blocks(&self) -> Result<u32, BlockDeviceError> {
Ok((self.range.len() / BLOCK_SIZE) as u32)
}
async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError> {
self.flash
.borrow_mut()
.read(self.range.start as u32 + (lba * BLOCK_SIZE as u32), block)
.map_err(|_| BlockDeviceError::ReadError)?;
Ok(())
}
async fn write_block(&mut self, lba: u32, block: &[u8]) -> Result<(), BlockDeviceError> {
let mut flash = self.flash.borrow_mut();
flash
.write(self.range.start as u32 + (lba * BLOCK_SIZE as u32), block)
.map_err(|_| BlockDeviceError::WriteError)?;
Ok(())
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
info!("Hello World!");
let mut config = Config::default();
config.rcc.pll48 = true;
config.rcc.sys_ck = Some(mhz(48));
let p = embassy_stm32::init(config);
// Create the driver, from the HAL.
let irq = interrupt::take!(OTG_FS);
let mut ep_out_buffer = [0u8; 256];
let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
// Create embassy-usb Config
let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("MSC example");
config.serial_number = Some("12345678");
// Required for windows compatiblity.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 64];
let mut state = Default::default();
let mut builder = Builder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
None,
);
let mut flash_buffer = [0u8; flash::ERASE_SIZE];
let flash = RefCell::new(RmwMultiwriteNorFlashStorage::new(
Flash::new(p.FLASH),
&mut flash_buffer,
));
// Use upper 1MB of the 2MB flash
let range = (1024 * 1024)..(2048 * 1024);
let mut scsi_buffer = [0u8; BLOCK_SIZE];
// Create SCSI target for our block device
let scsi = Scsi::new(FlashBlockDevice { flash, range }, &mut scsi_buffer, "Embassy", "MSC");
// Use bulk-only transport for our SCSI target
let mut msc_transport = BulkOnlyTransport::new(&mut builder, &mut state, 64, scsi);
// Build the builder.
let mut usb = builder.build();
// Run the USB device.
let usb_fut = usb.run();
// Run mass storage transport
let msc_fut = msc_transport.run();
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, msc_fut).await;
}

View File

@ -15,8 +15,11 @@ use embassy_usb::Builder;
use futures::future::join; use futures::future::join;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
// 512 is a standard block size supported by most systems
const BLOCK_SIZE: usize = 512;
struct RamBlockDevice { struct RamBlockDevice {
data: [u8; 512 * 128], data: [u8; BLOCK_SIZE * 128],
} }
impl BlockDevice for RamBlockDevice { impl BlockDevice for RamBlockDevice {
@ -25,20 +28,20 @@ impl BlockDevice for RamBlockDevice {
} }
fn block_size(&self) -> Result<usize, BlockDeviceError> { fn block_size(&self) -> Result<usize, BlockDeviceError> {
Ok(512) Ok(BLOCK_SIZE)
} }
fn max_lba(&self) -> Result<u32, BlockDeviceError> { fn num_blocks(&self) -> Result<u32, BlockDeviceError> {
Ok((self.data.len() / self.block_size().unwrap()) as u32 - 1) Ok((self.data.len() / self.block_size().unwrap()) as u32)
} }
async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError> { async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError> {
block.copy_from_slice(&self.data[lba as usize * 512..(lba as usize + 1) * 512]); block.copy_from_slice(&self.data[lba as usize * BLOCK_SIZE..(lba as usize + 1) * BLOCK_SIZE]);
Ok(()) Ok(())
} }
async fn write_block(&mut self, lba: u32, block: &[u8]) -> Result<(), BlockDeviceError> { async fn write_block(&mut self, lba: u32, block: &[u8]) -> Result<(), BlockDeviceError> {
self.data[lba as usize * 512..(lba as usize + 1) * 512].copy_from_slice(block); self.data[lba as usize * BLOCK_SIZE..(lba as usize + 1) * BLOCK_SIZE].copy_from_slice(block);
Ok(()) Ok(())
} }
} }
@ -91,7 +94,15 @@ async fn main(_spawner: Spawner) {
); );
// Create SCSI target for our block device // Create SCSI target for our block device
let scsi = Scsi::new(RamBlockDevice { data: [0u8; 512 * 128] }, "Embassy", "MSC"); let mut scsi_buffer = [0u8; BLOCK_SIZE];
let scsi = Scsi::new(
RamBlockDevice {
data: [0u8; BLOCK_SIZE * 128],
},
&mut scsi_buffer,
"Embassy",
"MSC",
);
// Use bulk-only transport for our SCSI target // Use bulk-only transport for our SCSI target
let mut msc_transport = BulkOnlyTransport::new(&mut builder, &mut state, 64, scsi); let mut msc_transport = BulkOnlyTransport::new(&mut builder, &mut state, 64, scsi);

View File

@ -0,0 +1,155 @@
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
#![feature(async_fn_in_trait)]
use core::cell::RefCell;
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::peripherals::{DMA2_CH6, SDIO};
use embassy_stm32::sdmmc::{self, Sdmmc};
use embassy_stm32::time::{mhz, Hertz};
use embassy_stm32::usb_otg::Driver;
use embassy_stm32::{interrupt, Config};
use embassy_usb::class::msc::subclass::scsi::block_device::{BlockDevice, BlockDeviceError};
use embassy_usb::class::msc::subclass::scsi::Scsi;
use embassy_usb::class::msc::transport::bulk_only::BulkOnlyTransport;
use embassy_usb::Builder;
use futures::future::join;
use {defmt_rtt as _, panic_probe as _};
// SDMMC driver only supports 512 byte blocks for now
const BLOCK_SIZE: usize = 512;
type MySdmmc<'d> = Sdmmc<'d, SDIO, DMA2_CH6>;
const SDIO_FREQ: Hertz = Hertz(12_000_000);
struct SdmmcBlockDevice<'d> {
sdmmc: RefCell<MySdmmc<'d>>,
}
impl<'d> BlockDevice for SdmmcBlockDevice<'d> {
fn status(&self) -> Result<(), BlockDeviceError> {
Ok(())
}
fn block_size(&self) -> Result<usize, BlockDeviceError> {
Ok(BLOCK_SIZE)
}
fn num_blocks(&self) -> Result<u32, BlockDeviceError> {
// Ok(128)
Ok((self.sdmmc.borrow().card().unwrap().csd.card_size() / BLOCK_SIZE as u64) as u32)
}
async fn read_block(&self, lba: u32, block: &mut [u8]) -> Result<(), BlockDeviceError> {
self.sdmmc.borrow_mut().read_block(lba, block).await.map_err(|e| {
error!("SDMMC read error: {:?}", e);
BlockDeviceError::ReadError
})
}
async fn write_block(&mut self, lba: u32, block: &[u8]) -> Result<(), BlockDeviceError> {
self.sdmmc.borrow_mut().write_block(lba, block).await.map_err(|e| {
error!("SDMMC write error: {:?}", e);
BlockDeviceError::WriteError
})
}
}
#[repr(align(4))]
struct AlignedBuffer([u8; BLOCK_SIZE]);
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
info!("Hello World!");
let mut config = Config::default();
config.rcc.pll48 = true;
config.rcc.sys_ck = Some(mhz(48));
let p = embassy_stm32::init(config);
let mut config = sdmmc::Config::default();
// config.data_transfer_timeout = 20_000_000;
let mut sdmmc = Sdmmc::new_4bit(
p.SDIO,
interrupt::take!(SDIO),
p.DMA2_CH6,
p.PC12,
p.PD2,
p.PC8,
p.PC9,
p.PC10,
p.PC11,
config,
);
sdmmc.init_card(SDIO_FREQ).await.expect("SD card init failed");
info!("Initialized SD card: {:#?}", Debug2Format(sdmmc.card().unwrap()));
// Create the driver, from the HAL.
let irq = interrupt::take!(OTG_FS);
let mut ep_out_buffer = [0u8; 256];
let driver = Driver::new_fs(p.USB_OTG_FS, irq, p.PA12, p.PA11, &mut ep_out_buffer);
// Create embassy-usb Config
let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("MSC example");
config.serial_number = Some("12345678");
// Required for windows compatiblity.
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
config.device_class = 0xEF;
config.device_sub_class = 0x02;
config.device_protocol = 0x01;
config.composite_with_iads = true;
// Create embassy-usb DeviceBuilder using the driver and config.
// It needs some buffers for building the descriptors.
let mut device_descriptor = [0; 256];
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 64];
let mut state = Default::default();
let mut builder = Builder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
None,
);
// Create SCSI target for our block device
let mut scsi_buffer = AlignedBuffer([0u8; BLOCK_SIZE]);
let scsi = Scsi::new(
SdmmcBlockDevice {
sdmmc: RefCell::new(sdmmc),
},
&mut scsi_buffer.0,
"Embassy",
"MSC",
);
// Use bulk-only transport for our SCSI target
let mut msc_transport = BulkOnlyTransport::new(&mut builder, &mut state, 64, scsi);
// Build the builder.
let mut usb = builder.build();
// Run the USB device.
let usb_fut = usb.run();
// Run mass storage transport
let msc_fut = msc_transport.run();
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join(usb_fut, msc_fut).await;
}