cancel
Showing results for 
Search instead for 
Did you mean: 

usbd_storage.c (MSC class) supports multiple LUN?

xxc2rxx
Associate II
Posted on December 16, 2015 at 03:10

Hi All,

Does anyone know if the STM32 USB device library MSC class support more than 1 LUN? I can get 1 LUN to work fine using either external flash or a uSD card. But when I tried to set STORAGE_LUN_NBR to 2 and handle the different LUN appropriately in each call-back functions (defined in usbd_storage.c), the PC would detect 2 external drives but the 2nd drive did not work properly (meaning Windows showed the drive but no size information and couldn't access it). When I swap the code back to 1 LUN, either using external flash memory or uSD, everything works fine again. 

With that said, I'm not concern about the uSD or external flash memory driver, but it's probably the STM32 USB device library that might not properly support more than 1 LUN. Please let me know if you know anything about this issue. Thanks in advance!
4 REPLIES 4
Posted on December 16, 2015 at 05:03

When I've done MSC devices in the past, the support for multiple LUN really wasn't that complicated.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
xxc2rxx
Associate II
Posted on December 16, 2015 at 05:19

Hi clive1,

Thanks for the reply. Here's my usbd_storage.c file. Please have a look and let me know if you spot anything unusual:

/**
******************************************************************************
* @file USB_Device/MSC_Standalone/Src/usbd_storage.c
* @author MCD Application Team
* @version V1.3.1
* @date 09-October-2015
* @brief Memory management layer
******************************************************************************
* @attention
*
* <
h2
><
center
>© COPYRIGHT(c) 2015 STMicroelectronics</
center
></
h2
>
*
* Licensed under MCD-ST Liberty SW License Agreement V2, (the ''License'');
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.st.com/software_license_agreement_liberty_v2
*
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an ''AS IS'' BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include ''System_Public.h''
#include ''usbd_storage.h''
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define STORAGE_LUN_NBR 1 // if I set this to 1, everything works fine. when I set it to 2, both drives will
// be detected but only the first drive works. the 2nd drive cannot be accessed.
// 
#define W25XXX_LUN_NUM 1
#define W25XXX_SectorSize ((uint32_t)4096) // size in byte per writable sector
#define W25XXX_SectorOffset ((uint32_t)20) // starting sector number of file system
#define MICROSD_LUN_NUM 0
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* USB Mass storage Standard Inquiry Data */
int8_t STORAGE_Inquirydata[] = { /* 36 */
/* LUN 0 */
0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
0x80, // bit6:0 - reserved, bit7 - removable media bit (set to 1 to indicate removable media)
0x02, // bit2:0 - ANSI version, bit5:3 - ECMA version, bit7:6 - ISO version
0x02, // bit3:0 - Response data format, bit7:4 - reserved
(STANDARD_INQUIRY_DATA_LEN - 5), // additional length - specify the length in bytes of the parameters.
0x00, // reserved
0x00, // reserved
0x00, // reserved
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer: 8 bytes */
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '.', '0','1', /* Version : 4 Bytes */
}; 
/* Private function prototypes -----------------------------------------------*/
int8_t STORAGE_Init(uint8_t lun);
int8_t STORAGE_GetCapacity(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
int8_t STORAGE_IsReady(uint8_t lun);
int8_t STORAGE_IsWriteProtected(uint8_t lun);
int8_t STORAGE_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
int8_t STORAGE_GetMaxLun(void);
USBD_StorageTypeDef USBD_DISK_fops = {
STORAGE_Init,
STORAGE_GetCapacity,
STORAGE_IsReady,
STORAGE_IsWriteProtected,
STORAGE_Read,
STORAGE_Write,
STORAGE_GetMaxLun,
STORAGE_Inquirydata, 
STORAGE_Inquirydata,
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Initailizes the storage unit (medium) 
* @param lun: Logical unit number
* @retval Status (0 : Ok / -1 : Error)
*/
int8_t STORAGE_Init(uint8_t lun)
{
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
if(W25XXX_IsReady())
retVal = 0;
else
retVal = -1;
break;
case MICROSD_LUN_NUM:
if(uSDSPIDrv_IsCardReady())
retVal = 0; // card has been initialized and ready to be accessed
else
retVal = -1;
break;
}
return retVal;
}
/**
* @brief Returns the medium capacity. 
* @param lun: Logical unit number
* @param block_num: Number of total block number
* @param block_size: Block size
* @retval Status (0: Ok / -1: Error)
*/
int8_t STORAGE_GetCapacity(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
if(W25XXX_IsReady())
{
*block_num = (W25XXX_GetCapacity()/W25XXX_SectorSize) - W25XXX_SectorOffset;
*block_size = W25XXX_SectorSize;
retVal = 0;
}
else
{
retVal = -1;
}
break;
case MICROSD_LUN_NUM:
if(uSDSPIDrv_IsCardReady())
{
*block_num = (uint32_t)(uSDSPIDrv_CardInfo.Capacity/uSDSPIDrv_CardInfo.BlockSize);
*block_size = uSDSPIDrv_CardInfo.BlockSize;
retVal = 0;
}
else
{
retVal = -1;
}
break;
}
return retVal;
}
/**
* @brief Checks whether the medium is ready. 
* @param lun: Logical unit number
* @retval Status (0: Ok / -1: Error)
*/
int8_t STORAGE_IsReady(uint8_t lun)
{ 
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
if(W25XXX_IsReady())
retVal = 0;
else
retVal = -1;
break;
case MICROSD_LUN_NUM:
if(uSDSPIDrv_IsCardReady())
retVal = 0;
else
retVal = -1;
break;
}
return retVal;
}
/**
* @brief Checks whether the medium is write protected.
* @param lun: Logical unit number
* @retval Status (0: write enabled / -1: otherwise)
*/
int8_t STORAGE_IsWriteProtected(uint8_t lun)
{
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
retVal = 0;
break;
case MICROSD_LUN_NUM:
retVal = 0;
break;
}
return retVal;
}
/**
* @brief Reads data from the medium.
* @param lun: Logical unit number
* @param blk_addr: Logical block address
* @param blk_len: Blocks number
* @retval Status (0: Ok / -1: Error)
*/
int8_t STORAGE_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
if(W25XXX_IsReady())
{
// perform read operation
// NOTE: W25XXX_Read() accepts absolute address in byte, not sector/block number, so the sector
// number needs to be converted to absolute address before passing into the function. Also,
// the file system might not start at sector 0. Therefore, the sector offset needs to be taken
// into account as well when calculating the absolute address.
xSemaphoreTake(W25XXX_Semaphore, portMAX_DELAY);
retVal = W25XXX_Read(((uint32_t)blk_addr + W25XXX_SectorOffset) * W25XXX_SectorSize, buf, blk_len * W25XXX_SectorSize);
xSemaphoreGive(W25XXX_Semaphore);
// check read operation result
if(retVal == TRUE)
retVal = 0;
else
retVal = -1;
}
else
{
retVal = -1;
}
break;
case MICROSD_LUN_NUM:
if(uSDSPIDrv_IsCardReady())
{
// perform read operation
xSemaphoreTake(uSDSPIDrv_Semaphore, portMAX_DELAY);
if(blk_len == 1)
{
retVal = uSDSPIDrv_ReadSingleBlock( blk_addr, buf );
}
else
{
retVal = uSDSPIDrv_ReadMultiBlock( blk_addr, buf, blk_len );
}
xSemaphoreGive(uSDSPIDrv_Semaphore);
// check read operation result
if(retVal == TRUE)
retVal = 0;
else
retVal = -1;
}
else
{
retVal = -1;
}
break;
}
return retVal;
}
/**
* @brief Writes data into the medium.
* @param lun: Logical unit number
* @param blk_addr: Logical block address
* @param blk_len: Blocks number
* @retval Status (0 : Ok / -1 : Error)
*/
int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
int8_t retVal = -1;
switch(lun)
{
case W25XXX_LUN_NUM:
// perform write operation
// NOTE: W25XXX_Write() accepts absolute address in byte, not sector/block number, so the sector
// number needs to be converted to absolute address before passing into the function. Also,
// the file system might not start at sector 0. Therefore, the sector offset needs to be taken
// into account as well when calculating the absolute address.
if(W25XXX_IsReady())
{
xSemaphoreTake(W25XXX_Semaphore, portMAX_DELAY);
retVal = W25XXX_Write(((uint32_t)blk_addr + W25XXX_SectorOffset) * W25XXX_SectorSize, (uint8_t *)(&buf[0]), blk_len * W25XXX_SectorSize);
xSemaphoreGive(W25XXX_Semaphore);
// check write operation result
if(retVal == TRUE)
retVal = 0;
else
retVal = -1;
}
else
{
retVal = -1;
}
break;
case MICROSD_LUN_NUM:
if(uSDSPIDrv_IsCardReady())
{
// perform write operation
xSemaphoreTake(uSDSPIDrv_Semaphore, portMAX_DELAY);
if(blk_len == 1)
{
retVal = uSDSPIDrv_WriteSingleBlock( blk_addr, buf );
}
else
{
retVal = uSDSPIDrv_WriteMultiBlock( blk_addr, buf, blk_len );
}
xSemaphoreGive(uSDSPIDrv_Semaphore);
// check write operation result
if(retVal == TRUE)
retVal = 0;
else
retVal = -1;
}
else
{
retVal = -1;
}
break;
}
return retVal;
}
/**
* @brief Returns the Max Supported LUNs. 
* @param None
* @retval Lun(s) number
*/
int8_t STORAGE_GetMaxLun(void)
{
return(STORAGE_LUN_NBR - 1);
}
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

I just want to add that I've tested the uSD with MSC in a single LUN configuration and that worked perfectly fine. I also did test the external flash with MSC in a single LUN configuration and that worked too. What didn't work was when I tried multiple LUN, both the external flash and uSD together. Both drives would be detected by the PC but only the first LUN would work. The 2nd attached drive did not show any size information nor can it be accessed.
tsuneo
Senior
Posted on December 16, 2015 at 17:23

Hi young.paul.004,

The INQUIRY response for the second LUN should be placed just after the first one in STORAGE_Inquirydata[] array. Duplicate the first block for the second one.

int8_t STORAGE_Inquirydata[] = { /* 36 + 36 */
/* LUN 0 */
0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
0x80, // bit6:0 - reserved, bit7 - removable media bit (set to 1 to indicate removable media)
0x02, // bit2:0 - ANSI version, bit5:3 - ECMA version, bit7:6 - ISO version
0x02, // bit3:0 - Response data format, bit7:4 - reserved
(STANDARD_INQUIRY_DATA_LEN - 5), // additional length - specify the length in bytes of the parameters.
0x00, // reserved
0x00, // reserved
0x00, // reserved
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer: 8 bytes */
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '.', '0','1', /* Version : 4 Bytes */
/* LUN 1 */
0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
0x80, // bit6:0 - reserved, bit7 - removable media bit (set to 1 to indicate removable media)
0x02, // bit2:0 - ANSI version, bit5:3 - ECMA version, bit7:6 - ISO version
0x02, // bit3:0 - Response data format, bit7:4 - reserved
(STANDARD_INQUIRY_DATA_LEN - 5), // additional length - specify the length in bytes of the parameters.
0x00, // reserved
0x00, // reserved
0x00, // reserved
'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer: 8 bytes */
'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product : 16 Bytes */
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '.', '0','1', /* Version : 4 Bytes */
};

In the USBD_DISK_fops structure, you have the extra line for the second INQUIRY response, but it's wrong.

USBD_StorageTypeDef USBD_DISK_fops = {
STORAGE_Init,
STORAGE_GetCapacity,
STORAGE_IsReady,
STORAGE_IsWriteProtected,
STORAGE_Read,
STORAGE_Write,
STORAGE_GetMaxLun,
STORAGE_Inquirydata,
// STORAGE_Inquirydata, <---- delete this line
};

Tsuneo
xxc2rxx
Associate II
Posted on December 16, 2015 at 21:54

Hi chinzei.tsuneo,

After adding the inquiry data for LUN 1, it works! Thank you so much!!