📄 massstorage.dir
字号:
USBD_Write(0, 0, 0, 0, 0);
}
else {
USBD_Stall(0);
}
break;
\endcode
!!State Machine
...
!Rationale
A state machine is necessary for #non-blocking# operation of the driver. As
previously stated, there are three steps when processing a command:
- Reception of the CBW
- Processing of the command (with data transfers if required)
- Emission of the CSW
Without a state machine, the program execution would be stopped at each step
to wait for transfers completion or command processing. For example, reception
of a CBW does not always happen immediately (the host does not have to issue
commands regularly) and can block the system for a long time.
Developing an asynchronous design based on a state machine is made easier when
using Atmel "AT91 USB device framework", as most methods are asynchronous. For
example, a write operation (using the USBD_Write function) returns
immediately; a callback function can then be invoked when the transfer
actually completes.
!States
Apart from the three states corresponding to the command processing flow (CBW,
command processing and CSW), two more can be identified. The
reception/emission of CBW/CSW will be broken down into two different states:
the first state is used to issue the read/write operation, while the second
one waits for the transfer to finish. This can be done by monitoring a
"transfer complete" flag which is set using a callback function.
In addition, some commands can be quite complicated to process: they may
require several consecutive data transfers mixed with media access. Each
command thus has its own second-tier state machine. During execution of a
command, the main state machine remains in the "processing" state, and
proceeds to the next one (CSW emission) only when the command is complete.
Here is the states list:
- MSDDriver_STATE_READ_CBW: Start of CBW reception
(initial state after reset)
- MSDDriver_STATE_WAIT_CBW: Waiting for CBW reception
- MSDDriver_STATE_PROCESS_CBW: Command processing
- MSDDriver_STATE_SEND_CSW: Start of CSW emission
- MSDDriver_STATE_WAIT_CSW: Waiting for CSW emission
A single function, named MSDDriver_StateMachine, is provided by the driver. It
must be called regularly during the program execution. The following
subsections describe the actions that must be performed during each state.
\image MSDDriverStates.png "MSD Driver State Machine"
#MSDDriver_STATE_READ_CBW#
As said previously, this state is used to start the reception of a new Command
Block Wrapper. This is done using the USB_Read method of the USB framework.
The result code of the function is checked for any error; the
USB_STATUS_SUCCESS code indicates that the transfer has been successfully
started.
\code
//----------------------
case MSDDriver_STATE_READ_CBW:
//----------------------
// Start the CBW read operation
transfer->semaphore = 0;
status = USBD_Read(MSDDriverDescriptors_BULKOUT,
cbw,
MSD_CBW_SIZE,
(TransferCallback) MSDDriver_Callback,
(void *) transfer);
// Check operation result code
if (status == USBD_STATUS_SUCCESS) {
// If the command was successful, wait for transfer
msdDriver.state = MSDDriver_STATE_WAIT_CBW;
}
break;
\endcode
A callback function to invoke when the transfer is complete is provided to the
USBD_Read method, to update a MSDTransfer structure. This structure
indicates the transfer completion, the returned result code and the number of
transferred and remaining bytes.
\code
typedef struct {
unsigned int transferred; //!< Number of bytes transferred
unsigned int remaining; //!< Number of bytes not transferred
unsigned char semaphore; //!< Semaphore to indicate transfer completion
unsigned char status; //!< Operation result code
} MSDTransfer;
\endcode
The callback function is trivial and thus not listed here.
#MSDDriver_STATE_WAIT_CBW#
The first step here is to monitor the }semaphore} field of the MSDTransfer
structure (see above); this will enable detection of the transfer end. Please
note that this field must be declared as volatile in C, or accesses to it
might get optimized by the compiler; this can result in endless loops.
If the transfer is complete, then the result code must be checked to see if
there was an error. If the operation is successful, the state machine can
proceed to command processing. Otherwise, it returns to the READ_CBW state.
\code
//----------------------
case MSDDriver_STATE_WAIT_CBW:
//----------------------
// Check transfer semaphore
if (transfer->semaphore > 0) {
// Take semaphore and terminate transfer
transfer->semaphore--;
// Check if transfer was successful
if (transfer->status == USBD_STATUS_SUCCESS) {
// Process received command
msdDriver.state = MSDDriver_STATE_PROCESS_CBW;
}
else if (transfer->status == USBD_STATUS_RESET) {
msdDriver.state = MSDDriver_STATE_READ_CBW;
}
else {
msdDriver.state = MSDDriver_STATE_READ_CBW;
}
}
break;
\endcode
#MSDDriver_STATE_PROCESS_CBW#
Once the CBW has been received, its validity must be checked. A CBW is not
valid if:
- it has not been received right after a CSW was sent or a reset occured or
- it is not exactly 31 bytes long or
- its signature field is not equal to 43425355h
The state machine prevents the first case from happening, so only the two
other cases have to be verified.
The number of bytes transferred during a USBD_Read operation is passed as an
argument to the callback function, if one has been specified. As stated
previously, such a function is used to fill a MSDTransfer structure.
Therefore, it is trivial to check that the CBW is indeed 31 bytes by verifying
that the number of bytes transferred is 31, and that there are no remaining
bytes. The following table illustrates the three cases which may happen:
||Number of bytes transferred||Number of bytes remaining||Meaning
|transferred<31|remaining==0|CBW is too short
|transferred==31|remaining>0|CBW is too long
|transferred==31|remaining==0|CBW length is correct
Checking the signature is simply done by comparing the dCBWSignature field
with the expected value (43425355h).
If the CBW is not valid, then the device must immediately halt both Bulk
endpoints, to STALL further traffic from the host. In addition, it should stay
in this state until a Reset Recovery is performed by the host. This is done by
setting the waitResetRecovery flag in the MSDDriver structure. Finally, the
CSW status is set to report an error, and the state machine is returned to
MSDDriver_STATE_READ_CBW.
Otherwise, if the CBW is correct, then the command can be processed. Remember
the CBW tag must be copied regardless of the validity of the CBW.
Note that these steps are only necessary for a new command (remember commands
are asynchronous and are carried out in several calls, so a check can be
performed to avoid useless processing. A value of zero for the internal
command state indicates a new command.
\code
//-------------------------
case MSDDriver_STATE_PROCESS_CBW:
//-------------------------
// Check if this is a new command
if (commandState->state == 0) {
// Copy the CBW tag
csw->dCSWTag = cbw->dCBWTag;
// Check that the CBW is 31 bytes long
if ((transfer->transferred != MSD_CBW_SIZE) ||
(transfer->remaining != 0)) {
// Wait for a reset recovery
msdDriver.waitResetRecovery = 1;
// Halt the Bulk-IN and Bulk-OUT pipes
USBD_Halt(MSDDriverDescriptors_BULKOUT);
USBD_Halt(MSDDriverDescriptors_BULKIN);
csw->bCSWStatus = MSD_CSW_COMMAND_FAILED;
msdDriver.state = MSDDriver_STATE_READ_CBW;
}
// Check the CBW Signature
else if (cbw->dCBWSignature != MSD_CBW_SIGNATURE) {
// Wait for a reset recovery
msdDriver.waitResetRecovery = 1;
// Halt the Bulk-IN and Bulk-OUT pipes
USBD_Halt(MSDDriverDescriptors_BULKOUT);
USBD_Halt(MSDDriverDescriptors_BULKIN);
csw->bCSWStatus = MSD_CSW_COMMAND_FAILED;
msdDriver.state = MSDDriver_STATE_READ_CBW;
}
else {
// Pre-process command
MSDDriver_PreProcessCommand();
}
}
// Process command
if (csw->bCSWStatus == MSDDriver_STATUS_SUCCESS) {
if (MSDDriver_ProcessCommand()) {
// Post-process command if it is finished
MSDDriver_PostProcessCommand();
msdDriver.state = MSDDriver_STATE_SEND_CSW;
}
}
break;
\endcode
#MSDDriver_STATE_SEND_CSW#
This state is similar to MSDDriver_STATE_READ_CBW, except that a write
operation is performed instead of a read and the CSW is sent, not the CBW. The
same callback function is used to fill the transfer structure, which is
checked in the next state:
\code
//----------------------
case MSDDriver_STATE_SEND_CSW:
//----------------------
// Set signature
csw->dCSWSignature = MSD_CSW_SIGNATURE;
// Start the CSW write operation
status = USBD_Write(MSDDriverDescriptors_BULKIN,
csw,
MSD_CSW_SIZE,
(TransferCallback) MSDDriver_Callback,
(void *) transfer);
// Check operation result code
if (status == USBD_STATUS_SUCCESS) {
// Wait for end of transfer
msdDriver.state = MSDDriver_STATE_WAIT_CSW;
}
break;
\endcode
#MSDDriver_STATE_WAIT_CSW#
Again, this state is very similar to MSDDriver_STATE_WAIT_CBW. The only
difference is that the state machine is set to MSDDriver_STATE_READ_CBW
regardless of the operation result code:
\code
//----------------------
case MSDDriver_STATE_WAIT_CSW:
//----------------------
// Check transfer semaphore
if (transfer->semaphore > 0) {
// Take semaphore and terminate transfer
transfer->semaphore--;
// Read new CBW
msdDriver.state = MSDDriver_STATE_READ_CBW;
}
break;
\endcode
!!Media
USB MSD Media access is three-level abstraction.
\image MSDMediaArch.png "Media Architecture"
The bottom level is the specific driver for each media type (See memories).
In the middle, a structure Media is used to hide which specific driver a media
instance is using. This enables transparent use of any media driver once it
has been initialized (See _Media).
Finally, a LUN abstraction is made over the media structure to allow multiple
partitions over one media. This also makes it possible to place the LUN at any
address and use any block size. When performing a write or read operation on a
LUN, it forwards the operation to the underlying media while translating it to
the correct address and length.
!Media Drivers
A media driver must provide several functions for:
- Reading data from the media
- Writing data on the media
- Handling interrupts on the media
The last function may be empty if the media does not require interrupts for
asynchronous operation, or if synchronous operation produces an acceptable
delay.
In addition, it should also define a function for initializing a Media
structure with the correct values, as well as perform the necessary step for
the media to be useable.
For the drivers see:
- MEDSdram.h: }Internal Flash Driver}
- MEDFlash.h: }SDRAM disk driver}
!!SCSI Commands
The example software described in this application note uses SCSI commands
with the MSD class, since this is the most appropriate setting for a Flash
device. This section details how SCSI commands are processed.
!Documents
There are several documents covering SCSI commands. In this application note,
the reference document used is SCSI Block Commands - 3 (SBC-3). However, it
makes many references to another SCSI document, SCSI Primary Commands - 4
(SPC-4). Both are needed for full details on required commands.
!Endianness
SCSI commands use the big-endian format for storing word- and double word-
sized data. This means the Most Significant Bit (MSB) is stored at the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -