Implement application specific behavior of a Mass Storage Class (MSC) USB Device.
More...
Implement application specific behavior of a Mass Storage Class (MSC) USB Device.
The MSC class in the USB Component is used for data storage.
Refer to:
The USB Component allows multiple instances of the MSC class. Each MSC class instance has a separate files and interface functions:
- A configuration file USBD_Config_MSC_n.h.
- An application specific user source code file which may be implemented with the user code template USBD_User_MSC_n.c.
- Functions that start with the prefix USBD_MSCn_ are available for each instance of a MSC class.
This documentation uses n as a placeholder for the instance number 0 - 3. Most applications only require one instance of a MSC class. For the first MSC class instance the instance number is 0:
- USBD_Config_MSC_0.h
- USBD_User_MSC_0.c
- The function prefix is USBD_MSC0_
Software Structure
The handling for the MSC class endpoint events is implemented in USBD_MSCn_Thread which is started by USBD_Initialize. Each instance of a MSC class runs an instance of USBD_MSCn_Thread which calls the data functions USBD_MSCn_Read and USBD_MSCn_Write.
Implementation
To create an USB Device with a MSC class:
- Set the required number for USB:Device:MSC class instances during the RTE Component Selection.
- Add the related user code template file (USBD_User_MSC_n.c) to your project.
- Implement the application specific behavior in USBD_User_MSC_n.c.
- Set the parameters in the configuration file USBD_Config_MSC_n.h.
Configuration File USBD_Config_MSC_n.h
The configuration file USBD_Config_MSC_n.h defines:
- The assignment of the MSC class to the USB Device instance.
- USB Endpoint assignments for the MSC class with parameters for Full/Low-speed and High-speed communication.
- Class Settings for the Interface Descriptor.
These settings are used to create the Interface Descriptor and Endpoint Descriptor of the related USB Device Class. It is important that the settings match the application specific behavior in the related C source file USBD_User_MSC_n.c.
Media Ownership
Sometimes, it is required to implement the ownership control over attached media and changing the ownership between USB and File System. This is required if you have a device that connects to a PC as a USB MSC device while the storage media also needs to be accessible to a user application. Using the two functions USBD_MSCn_SetMediaOwnerUSB and USBD_MSCn_SetMediaOwnerFS you can change the owner of the media to either the USB (the host PC) or the File System (the user application). The user code template USBD_MSC.c provides means to manage the ownership.
The following picture shows the connection of the device to the PC and the user application running on the device:
USB MSC Device connected to a PC with a user application accessing the attached storage medium
In the file USBD_MSC.c the variable usbd_msc0_media_own
is used to set the ownership of the media device to the application or the File System. In the file USBD_User_MSC.c the variable is used to initialize it at the beginning of the application (in the USBD_MSCn_Initialize function) and to check the ownership of the media (in the USBD_MSCn_CheckMedia function). The application then only makes use of the two functions USBD_MSCn_SetMediaOwnerUSB and USBD_MSCn_SetMediaOwnerFS as explained in this code example.
User Code Templates
There are two user code templates available that help to add support for a MSC device:
- USBD_User_MSC.c contains all the callback functions that need to be implemented by the user.
- USBD_MSC.c is a code template for the application specific functionality of a USB Device MSC instance and implements ownership control for attached media devices.
User Code Template USBD_User_MSC.c
The following source code can be used to implement the application specific behavior of a USB MSC Device.
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "rl_usb.h"
#define USE_FILE_SYSTEM 1 // 1 = File System is used, 0 = File System is not used
#ifndef USE_FILE_SYSTEM
#define USE_FILE_SYSTEM 0 // If USE_FILE_SYSTEM is not defined File System is not used
#endif
#define MEDIA_DRIVE "M0:"
#if (USE_FILE_SYSTEM == 1) // If File System is used
#include "rl_fs.h"
#define MEDIA_OWN_USB (1U ) // Media owned by USB (bit mask)
#define MEDIA_OWN_CHG (1U << 1) // Media ownership change requested (bit mask)
volatile uint8_t usbd_mscn_media_own;
static int32_t drv_id;
static bool media_ok;
#else
static uint32_t memory [8192/4];
static uint32_t block_buf[ 512/4];
extern
const uint8_t memory_disk_image[4096];
#endif
#if (USE_FILE_SYSTEM == 1) // If File System is used
uint32_t param_status;
usbd_mscn_media_own = MEDIA_OWN_USB;
media_ok = false;
if (finit (MEDIA_DRIVE) != fsOK) {
return;
}
param_status = 0U;
if (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeControlMedia, ¶m_status) != fsOK) {
return;
}
drv_id = fs_ioc_get_id (MEDIA_DRIVE);
if (drv_id < 0U) { return; }
if (fs_ioc_lock (drv_id)) {
return;
}
media_ok = true;
#else
memcpy (memory, memory_disk_image, sizeof(memory_disk_image));
#endif
}
}
#if (USE_FILE_SYSTEM == 1) // If File System is used
fsIOC_Cache cache_info;
if (fs_ioc_get_cache(drv_id, &cache_info) != fsOK) {
return false;
}
*buffer = (uint32_t)cache_info.buffer;
*size = cache_info.size;
#else
*buffer = (uint32_t)block_buf;
*size = sizeof(block_buf);
#endif
return true;
}
#if (USE_FILE_SYSTEM == 1) // If File System is used
fsMediaInfo media_info;
if (fs_ioc_read_info(drv_id, &media_info) != fsOK) {
return false;
}
*block_count = media_info.block_cnt;
*block_size = media_info.read_blen;
#else
*block_count = sizeof(memory)/512U;
*block_size = 512U;
#endif
return true;
}
#if (USE_FILE_SYSTEM == 1) // If File System is used
if (fs_ioc_read_sector (drv_id, lba, buf, cnt) != fsOK) {
return false;
}
#else
memcpy (buf, &memory[lba * (512U/4U)], cnt * 512U);
#endif
return true;
}
#if (USE_FILE_SYSTEM == 1) // If File System is used
if (fs_ioc_write_sector (drv_id, lba, buf, cnt) != fsOK) {
return false;
}
#else
memcpy (&memory[lba * (512U/4U)], buf, cnt * 512U);
#endif
return true;
}
#if (USE_FILE_SYSTEM == 1) // If File System is used
uint32_t param_status;
uint8_t media_state;
static uint8_t media_ready_ex = 0U;
uint8_t own;
media_state = 0U;
switch (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeCheckMedia, ¶m_status)) {
case fsOK:
if (param_status & FS_MEDIA_NOCHKMEDIA) {
media_state = USBD_MSC_MEDIA_READY;
break;
}
if (param_status & FS_MEDIA_INSERTED) {
media_state = USBD_MSC_MEDIA_READY;
}
if (param_status & FS_MEDIA_PROTECTED) {
media_state |= USBD_MSC_MEDIA_PROTECTED;
}
break;
default:
break;
}
own = usbd_mscn_media_own;
if (own & MEDIA_OWN_CHG) {
if (own & MEDIA_OWN_USB) {
funmount (MEDIA_DRIVE);
} else {
fs_ioc_unlock (drv_id);
}
}
if ((own & MEDIA_OWN_CHG) ||
(media_state ^ media_ready_ex)) {
if (media_state & USBD_MSC_MEDIA_READY) {
if (own & MEDIA_OWN_USB){
media_ok = false;
param_status = 0U;
if (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeControlMedia, ¶m_status) == fsOK) {
if (fs_ioc_lock (drv_id) == 0) {
media_ok = true;
}
}
} else {
if (fmount (MEDIA_DRIVE) == fsOK) {
media_ok = true;
}
}
}
if (own & MEDIA_OWN_CHG) {
usbd_mscn_media_own &= ~MEDIA_OWN_CHG;
}
media_ready_ex = media_state & USBD_MSC_MEDIA_READY;
}
if ((!media_ok) || (!(usbd_mscn_media_own & MEDIA_OWN_USB))) {
return 0U;
}
return media_state;
#else
return USBD_MSC_MEDIA_READY;
#endif
}
User Code Template USBD_MSC_n.c
#include "stdint.h"
#include "stdbool.h"
#include "cmsis_os.h"
#include "USBD_MSC_n.h"
extern volatile uint8_t usbd_mscn_media_own;
int32_t USBD_MSCn_SetMediaOwnerUSB (void) {
uint32_t timeout_cnt;
timeout_cnt = 300U;
usbd_mscn_media_own = USBD_MSCn_MEDIA_OWN_CHG | USBD_MSCn_MEDIA_OWN_USB;
while (usbd_mscn_media_own & USBD_MSCn_MEDIA_OWN_CHG) {
osDelay(10);
if ((--timeout_cnt) == 0) { return USBD_MSCn_ERROR; }
}
return USBD_MSCn_OK;
}
int32_t USBD_MSCn_SetMediaOwnerFS (void) {
uint32_t timeout_cnt;
timeout_cnt = 300U;
usbd_mscn_media_own = USBD_MSCn_MEDIA_OWN_CHG;
while (usbd_mscn_media_own & USBD_MSCn_MEDIA_OWN_CHG) {
osDelay(10);
if ((--timeout_cnt) == 0) { return USBD_MSCn_ERROR; }
}
return USBD_MSCn_OK;
}
Code Example
This code snippet shows how to use the two functions in a user application:
:
switch (DeviceState) {
case DEV_IDLE:
break;
case DEV_START_DOING_SOMETHING:
USBD_MSC0_SetMediaOwnerFS();
DeviceState = DEV_DO_IT;
break;
case DEV_STOP_DOING_SOMETHING:
USBD_MSC0_SetMediaOwnerUSB();
DeviceState = DEV_IDLE;
break;
}
:
uint32_t USBD_MSCn_CheckMedia |
( |
void |
| ) |
|
Check media presence and write protect status.
- Returns
- media presence and write protected status bit 1: write protect bit
- value 1: media is write protected
- value 0: media is not write protected bit 0: media presence bit
- value 1: media is present
- value 0: media is not present
The function USBD_MSCn_CheckMedia is called automatically upon specific USB Host requests (like Test Unit Ready) to check if media is ready for read/write operations. It needs no invocation in the user code. Modify this function to the application's needs. If media check is not available in hardware this function can be omitted.
Code Example
#include "rl_usb.h"
uint32_t USBD_MSC0_CheckMedia (void) {
uint32_t param_status;
uint8_t media_state;
static uint8_t media_ready_ex = 0U;
uint8_t own;
media_state = 0U;
switch (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeCheckMedia, ¶m_status)) {
case fsOK:
if (param_status & FS_MEDIA_NOCHKMEDIA) {
media_state = USBD_MSC_MEDIA_READY;
break;
}
if (param_status & FS_MEDIA_INSERTED) {
media_state = USBD_MSC_MEDIA_READY;
}
if (param_status & FS_MEDIA_PROTECTED) {
media_state |= USBD_MSC_MEDIA_PROTECTED;
}
break;
default:
break;
}
own = usbd_msc0_media_own;
if (own & MEDIA_OWN_CHG) {
if (own & MEDIA_OWN_USB) {
funmount (MEDIA_DRIVE);
} else {
fs_ioc_unlock (drv_id);
}
}
if ((own & MEDIA_OWN_CHG) ||
(media_state ^ media_ready_ex)) {
if (media_state & USBD_MSC_MEDIA_READY) {
if (own & MEDIA_OWN_USB){
media_ok = false;
param_status = 0U;
if (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeControlMedia, ¶m_status) == fsOK) {
if (fs_ioc_lock (drv_id) == 0) {
media_ok = true;
}
}
} else {
if (fmount (MEDIA_DRIVE) == fsOK) {
media_ok = true;
}
}
}
if (own & MEDIA_OWN_CHG) {
usbd_msc0_media_own &= ~MEDIA_OWN_CHG;
}
media_ready_ex = media_state & USBD_MSC_MEDIA_READY;
}
if ((!media_ok) || (!(usbd_msc0_media_own & MEDIA_OWN_USB))) {
return 0U;
}
return media_state;
}
bool USBD_MSCn_GetCacheInfo |
( |
uint32_t * |
buffer, |
|
|
uint32_t * |
size |
|
) |
| |
Get cache information.
- Parameters
-
[out] | buffer | cache buffer address. |
[out] | size | cache buffer size. |
- Returns
- true operation succeeded.
-
false operation failed.
The function USBD_MSCn_GetCacheInfo enables an MSC device to reuse RAM used by the File System for caching purposes as USB cache. This is required when a USB Host has control over the media so that it cannot be accessed by File System.
The argument buffer returns the cache buffer address, while the argument size returns the size of the cache buffer.
Code Example
#include "rl_usb.h"
bool USBD_MSC0_GetCacheInfo (uint32_t *buffer, uint32_t *size) {
fsIOC_Cache cache_info;
if (fs_ioc_get_cache(drv_id, &cache_info) != fsOK) {
return false;
}
*buffer = (uint32_t)cache_info.buffer;
*size = cache_info.size;
return true;
}
bool USBD_MSCn_GetMediaCapacity |
( |
uint32_t * |
block_count, |
|
|
uint32_t * |
block_size |
|
) |
| |
Get media capacity.
- Parameters
-
[out] | block_count | total number of blocks on media. |
[out] | block_size | media block size. |
- Returns
- true operation succeeded.
-
false operation failed.
The function USBD_MSCn_GetMediaCapacity can be used to determine the geometry of the attached storage media.
The argument block_count returns the total number of blocks on the media, while the argument block_size returns the block size of the media.
Code Example
#include "rl_usb.h"
bool USBD_MSC0_GetMediaCapacity (uint32_t *block_count, uint32_t *block_size) {
fsMediaInfo media_info;
if (fs_ioc_read_info(drv_id, &media_info) != fsOK) {
return false;
}
*block_count = media_info.block_cnt;
*block_size = media_info.read_blen;
return true;
}
void USBD_MSCn_Initialize |
( |
void |
| ) |
|
Called during USBD_Initialize to initialize the USB MSC class Device.
- Returns
- none.
The function USBD_MSCn_Initialize is called automatically upon initialization of a Mass Storage Class Device and needs no invocation in the user code. Modify this function to the application's needs to allocate resources and initialize additional peripherals.
Code Example
#include "rl_usb.h"
void USBD_MSC0_Initialize (void) {
uint32_t param_status;
usbd_mscn_media_own = MEDIA_OWN_USB;
media_ok = false;
if (finit (MEDIA_DRIVE) != fsOK) {
return;
}
param_status = 0U;
if (fs_ioc_device_ctrl (drv_id, fsDevCtrlCodeControlMedia, ¶m_status) != fsOK) {
return;
}
drv_id = fs_ioc_get_id (MEDIA_DRIVE);
if (drv_id < 0U) { return; }
if (fs_ioc_lock (drv_id)) {
return;
}
media_ok = true;
}
bool USBD_MSCn_Read |
( |
uint32_t |
lba, |
|
|
uint32_t |
cnt, |
|
|
uint8_t * |
buf |
|
) |
| |
Read data from media.
- Parameters
-
[in] | lba | logical address of first block to read. |
[in] | cnt | number of contiguous blocks to read from media. |
[out] | buf | data buffer for data read from media. |
- Returns
- true read succeeded.
-
false read failed.
The function USBD_MSCn_Read reads the data that should be returned to the USB Host that requested it. Modify this function to the application's needs.
The argument lba specifies the logical address of the first block that is to be read.
The argument cnt specifies the number of contiguous blocks to be read from the media.
The argument buf is pointing to the buffer where the read data should be stored.
Code Example
#include rl_usb.h
bool USBD_MSC0_Read (uint32_t lba, uint32_t cnt, uint8_t *buf) {
if (fs_ioc_read_sector (drv_id, lba, buf, cnt) != fsOK) {
return false;
}
return true;
}
void USBD_MSCn_Uninitialize |
( |
void |
| ) |
|
Called during USBD_Uninitialize to de-initialize the USB MSC class Device.
- Returns
- none.
The function USBD_MSCn_Uninitialize is called automatically upon de-initialization of a Mass Storage Class Device and needs no invocation in the user code. If USBD_MSCn_Initialize has been adapted to the application, USBD_MSCn_Uninitialize should release resources and should de-initialize peripherals.
Code Example
#include "rl_usb.h"
int main (void) {
..
...
..
}
bool USBD_MSCn_Write |
( |
uint32_t |
lba, |
|
|
uint32_t |
cnt, |
|
|
const uint8_t * |
buf |
|
) |
| |
Write data to media.
- Parameters
-
[in] | lba | logical address of first block to write. |
[in] | cnt | number of contiguous blocks to write to media. |
[out] | buf | data buffer containing data to write to media. |
- Returns
- true write succeeded.
-
false write failed.
The function USBD_MSCn_Write writes data received from the USB Host. Modify this function to the application's needs.
The argument lba specifies the logical address of the first block that is to be written.
The argument cnt specifies the number of contiguous blocks to be written to the media.
The argument buf is pointing to the buffer containing the data to be written.
Code Example
#include rl_usb.h
bool USBD_MSC0_Write (uint32_t lba, uint32_t cnt, const uint8_t *buf) {
if (fs_ioc_write_sector (drv_id, lba, buf, cnt) != fsOK) {
return false;
}
return true;
}