USB Device MSC Class

This section describes the mass storage device class (MSC) supported by Silicon Labs USB Device. MSC is a protocol that enables the transfer of information between a USB device and a host. The information being transferred is anything that can be stored electronically, such as executable programs, source code, documents, images, configuration data, or other text or numeric data. The USB device appears as an external storage medium to the host, enabling the transfer of files via drag and drop.

A file system defines how the files are organized in the storage media. The USB mass storage class specification does not require any particular file system to be used on conforming devices. Instead, it provides a simple interface to read and write sectors of data using the Small Computer System Interface (SCSI) transparent command set. As such, operating systems may treat the USB drive like a hard drive, and can format it with any file system they like.

The USB mass storage device class supports two transport protocols, as follows:

The mass storage device class implements the SCSI transparent command set using the BOT protocol only, which signifies that only bulk endpoints will be used to transmit data and status information. The MSC implementation supports multiple logical units.

The MSC implementation is in compliance with the following specifications:

USB Device MSC Class Overview

Protocol

In this section, we will discuss the Bulk-Only Transport (BOT) protocol of the Mass Storage Class. The Bulk-Only Transport protocol has three stages:

Mass storage commands are sent by the host through a structure called the Command Block Wrapper (CBW). For commands requiring a data transport stage, the host will attempt to send or receive the exact number of bytes from the device as specified by the length and flag fields of the CBW. After the data transport stage, the host attempts to receive a Command Status Wrapper (CSW) from the device that details the status of the command as well as any data residue (if any). For commands that do not include a data transport stage, the host attempts to receive the CSW directly after CBW is sent. The protocol is detailed in Figure - MSC Protocol.

Figure - MSC Protocol

Figure 12 MSC Protocol

Endpoints

On the device side, in compliance with the BOT specification, the MSC is composed of the following endpoints:

The table below indicates the different usages of the endpoints.

Table - MSC Endpoint Usage
EndpointDirectionUsage
Control INDevice to HostEnumeration and MSC class-specific requests
Control OUTHost to DeviceEnumeration and MSC class-specific requests
Bulk INDevice to HostSend CSW and data
Bulk OUTHost to DeviceReceive CBW and data

Class Requests

There are two defined control requests for the MSC BOT protocol. These requests and their descriptions are detailed in the table below.

Table - Mass Storage Class Requests
Class RequestsDescription
Bulk-Only Mass Storage ResetThis request is used to reset the mass storage device and its associated interface. This request readies the device to receive the next command block.
Get Max LUNThis request is used to return the highest logical unit number (LUN) supported by the device. For example, a device with LUN 0 and LUN 1 will return a value of 1. A device with a single logical unit will return 0 or stall the request. The maximum value that can be returned is 15.

Small Computer System Interface (SCSI)

At the programming interface level, the MSC device implements one of the standard storage-media communication protocols, like SCSI and SFF-8020i (ATAPI). The "Programming Interface" specifies which protocol is implemented, and helps the host operating system to load the suitable device driver for communicating with the USB storage device. SCSI is the most common protocol used with USB MSC storage devices. We provide an implementation for MSC SCSI subclass that our GSDK users can use out of the box.

SCSI is a set of standards for handling communication between computers and peripheral devices. These standards include commands, protocols, electrical interfaces and optical interfaces. Storage devices that use other hardware interfaces, such as USB, use SCSI commands for obtaining device/host information and controlling the device’s operation and transferring blocks of data in the storage media.

SCSI commands cover a vast range of device types and functions and as such, devices need a subset of these commands. In general, the following commands are necessary for basic communication:

USB Device MSC Class Resource Needs from Core

Each time you add an MSC class instance to a USB configuration via the function sl_usbd_msc_add_to_configuration(), the following resources will be allocated from the core.

ResourceQuantity
Interfaces1
Alternate interfaces1
Endpoints2
Interface groups0

Note that those numbers are per configuration. When setting up your SL_USBD_INTERFACE_QUANTITY, SL_USBD_ALT_INTERFACE_QUANTITY, SL_USBD_INTERFACE_GROUP_QUANTITY and SL_USBD_DESCRIPTOR_QUANTITY configuration values, don't forget to take into account on how many configurations the class will be added. For the SL_USBD_OPEN_ENDPOINTS_QUANTITY configuration value, since endpoints are opened only when a configuration is set by the host, you just need to take into account the number of needed endpoints for a class instance.

USB Device MSC Class Configuration

Two groups of configuration parameters are used to configure the MSC class:

USB Device MSC Class Application-Specific Configurations

Class Compile-Time Configurations

Silicon Labs USB Device MSC class and SCSI subclass are configurable at compile time via #defines located in the sl_usbd_core_config.h file.

Table - Generic Configuration Constants
Configuration NameDescriptionDefault Value
SL_USBD_MSC_CLASS_INSTANCE_QUANTITYNumber of class instances you will allocate via a call to the function sl_usbd_msc_scsi_create_instance().2
SL_USBD_MSC_CONFIGURATION_QUANTITYNumber of configuration to which a class instance can be added via a call to the function sl_usbd_msc_scsi_add_to_configuration().1
SL_USBD_MSC_LUN_QUANTITYNumber of logical units per class instance that you will add via a call to the function sl_usbd_msc_scsi_lun_add().2
SL_USBD_MSC_SCSI_64_BIT_LBA_ENEnables or disables support for Logical Block Address (LBA) of 64 bits.0
SL_USBD_MSC_DATA_BUFFER_SIZESize of data buffer per class instance in bytes512

Class Instance Creation

Creating a USB Device MSC SCSI class instance is done by calling the sl_usbd_msc_scsi_create_instance() function. This function takes one configuration argument that is described below.

p_scsi_callbacks

p_scsi_callbacks is a pointer to a configuration structure of type sl_usbd_msc_scsi_callbacks_t. In addition to the common usb device class callbacks connect/disconnect, it provides the MSC class with a set of optional callback functions that are called when an event occurs on the logical unit. A null pointer (NULL) can be passed to this argument if no callbacks are needed.

The table below describes each configuration field available in this configuration structure.

Table - sl_usbd_msc_scsi_callbacks_t Configuration Structure
Fields Description Function signature
.enable Called when the USB class instance is enabled successfully. void app_usbd_msc_scsi_enable(uint8_t class_nbr);
.disable Called when the USB class instance is disabled. void app_usbd_msc_scsi_disable(uint8_t class_nbr);
.host_eject Function called when a logical unit is ejected from the host. void app_usbd_msc_scsi_host_eject(uint8_t class_nbr, uint8_t lu_nbr);

USB Device MSC Class Logical Unit Configuration

Adding a logical unit to an MSC class instance is done by calling the function sl_usbd_msc_lun_add(). This function takes one configuration argument that is described below.

p_lu_info

p_lu_info is a pointer to a structure of type sl_usbd_msc_scsi_lun_info_t. Its purpose is to provide the information on the logical unit to the MSC class.

The table below describes each configuration field available in this configuration structure.

Table - sl_usbd_msc_scsi_lun_info_t Configuration Structure
FieldDescription
.scsi_lun_api_ptrPointer to the media driver API that will handle this logical unit. See USB Device MSC Class Storage Drivers for more information on storage drivers.
.vendor_id_ptrPointer to a string that contains the vendor identification of the logical unit. The maximum length of the string is 8 characters.
.product_id_ptrPointer to a string that contains the product identification of the logical unit. The maximum length of the string is 16 characters.
.product_revision_levelProduct revision level.
.is_read_onlyFlag that indicates if the logical unit should be seen as read only from the point of view of the host (true) or not (false).

USB Device MSC Class Programming Guide

This section explains how to use the MSC class.

Initializing the USB Device MSC Class

To add MSC SCSI class functionality to your device, first initialize the MSC base class and the SCSI subclass by calling the function sl_usbd_msc_init() and sl_usbd_msc_scsi_init().

The example below shows how to call sl_usbd_msc_init() and sl_usbd_msc_scsi_init().

Example - Calling sl_usbd_msc_init() and sl_usbd_msc_scsi_init()
sl_status_t  status;

status = sl_usbd_msc_init();
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

status = sl_usbd_msc_scsi_init();
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

Adding a USB Device MSC SCSI Class Instance to Your Device

To add MSC SCSI class functionality to your device, first create an instance, then add it to your device's configuration(s). You must add at least one logical unit to your instance.

Creating an MSC SCSI Class Instance

Create a MSC SCSI class instance by calling the function sl_usbd_msc_scsi_create_instance().

The example below shows how to call sl_usbd_msc_scsi_create_instance() using default arguments. For more information on the configuration arguments to pass to sl_usbd_msc_scsi_create_instance(), see USB Device MSC Class Application Specific Configurations .

Example - Calling sl_usbd_msc_scsi_create_instance()
uint8_t        class_nbr;
sl_status_t    status;

sl_usbd_msc_scsi_callbacks_t app_usbd_msc_scsi_callbacks = {
  .enable = NULL,
  .disable = NULL,
  .host_eject = NULL
};

status = sl_usbd_msc_scsi_create_instance(&app_usbd_msc_scsi_callbacks,0
                                          &class_nbr);
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

Adding the MSC Class Instance to Your Device's Configuration(s)

After you have created an MSC class instance, you can add it to a configuration by calling the function sl_usbd_msc_add_to_configuration().

The example below show how to call sl_usbd_msc_scsi_add_to_configuration() using default arguments.

Example - Calling sl_usbd_msc_scsi_add_to_configuration()
sl_status_t  status;

status = sl_usbd_msc_scsi_add_to_configuration(class_nbr,                             (1)
                                               config_nbr_fs);                        (2)
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

(1) Class number to add to the configuration returned by sl_usbd_msc_scsi_create_instance().

(32) Configuration number (here adding it to a Full-Speed configuration).

USB Device MSC Class Logical Unit Handling

Adding a Logical Unit

When adding a logical unit to your MSC SCSI class instance, it must be bound to a storage medium (RAMDisk, SD card, flash memory, etc). The MSC class uses a storage driver to communicate with storage media. This driver will need to be supply when adding the logical unit.

The example below shows how to add a logical unit via sl_usbd_msc_scsi_lun_add().

Example - Adding a Logical Unit via sl_usbd_msc_scsi_lun_add()
sl_usbd_msc_scsi_lun_t       *lu_object_ptr = NULL;
sl_usbd_msc_scsi_lun_info_t  lu_info;
sl_status_t                  status;

lu_info.sl_usbd_msc_scsi_lun_api_t = &app_usbd_scsi_storage_block_device_api;
lu_info.vendor_id_ptr              = "Silicon Labs";
lu_info.product_id_ptr             = "block device example";
lu_info.product_revision_level     =  0x1000u;
lu_info.is_read_only               =  false;

status = sl_usbd_msc_scsi_lun_add(class_nbr,
                                  &lu_info,
                                  &lu_object_ptr);
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

Attaching/Detaching a Storage Medium

After the logical unit has been added, a storage medium must be attached to be available from the host side.

The MSC class offers two functions to control the storage media association to the logical unit: sl_usbd_msc_scsi_lun_attach() and sl_usbd_msc_scsi_lun_detach(). These functions allow you to emulate the removal of a storage device in order to re-gain access from the embedded application if necessary.

The example below shows how to use the function sl_usbd_msc_scsi_lun_attach() and sl_usbd_msc_scsi_lun_detach().

Example - Media Attach/Detach
sl_status_t  status;

status = sl_usbd_msc_scsi_lun_attach(lu_object_ptr);
if (status != SL_STATUS_OK) {
  /* An error occurred. Error handling should be added here. */
}

...                                                                      (1)

status = sl_usbd_msc_scsi_lun_detach(lu_object_ptr);
if (status != SL_STATUS_OK) {
    /* An error occurred. Error handling should be added here. */
}

...                                                                      (2)

status = sl_usbd_msc_scsi_lun_attach(lu_object_ptr)
if (status != SL_STATUS_OK) {
    /* An error occurred. Error handling should be added here. */
}

...                                                                      (3)

(1) From this moment, if the MSC device is connected to a host, the storage media is accessible.

(2) If the MSC device is connected to a host, the media will now appear as unavailable. At this moment, operations can be performed on the media from the embedded application.

(3) Again, if the MSC device is connected to the host, the storage media will appear as connected.

USB Device MSC Class Storage Drivers

The USB Device MSC Class needs a storage driver to communicate with a storage medium. For the moment, Silicon Labs doesn't offer drivers.

The driver API is defined by typedef sl_usbd_msc_scsi_lun_api_t. Your sl_usbd_msc_scsi_lun_api_t variable must be included to your sl_usbd_msc_scsi_lun_info_t variable, passed as argument when you add a logical unit with sl_usbd_msc_scsi_lun_add(). See section USB Device MSC SCSI API for more details on the structures.

The storage driver implementation can be as simple as an array of sectors in RAM. Typical sector size (i.e., block size) is 512 for mass storage devices, and 2048 for CD-ROMs.