USB Mass Storage BOT чиним multi LUN у STM32

Попалась задача в проекте реализовать, чтобы по USB микроконтроллер прикидывался несколькими дисковыми устройствами для MicroSD, встроенной EEPROM и нескольких страничек оперативной памяти. Решил, что вполне логично, пойти по пути наименьшего сопротивления, попробовав запустить из коробки, то что ST реализовали в своей библиотеке. Работа c USB разделена у них на уровни абстракции: драйвер + MiddleWare:

USB библиотека STM32USB библиотека STM32

Ошибки у нас закрались MiddleWare и благодаря тому, что MiddleWare максимально абстрагирован от железа, этом метод позволит исправить реализацию и для остальных семейств с минимальными правками кода. Подробное описание MiddleWare расписано у ST в UM1717, UM0424, UM1021, UM1720.

Запускаем из коробки (в чистом виде, что ST предоставил)

Создаём в кубе проект, застраиваем USB и выбираем в MiddleWare Class For FS IP: Mass Storage Class и правим при желании дескрипторы. Затем в файле USB_DEVICE→App→usbd_storage_if.c прописываем логику работы нашего устройства и описание LUN. Привожу изменённую часть файла:

/* USER CODE BEGIN PRIVATE_TYPES */

typedef enum LUN_NUM{
	LUN_NUM_MICROSD	 =	0,
	LUN_NUM_EEPROM,
	LUN_NUM_SYSTEM,
	LUN_NUM_TEST
} lun_num_t;

/* USER CODE END PRIVATE_TYPES */

/* USER CODE BEGIN PRIVATE_DEFINES */

#define STORAGE_LUNS	0x8

#define EEPROM_BLOCK_SIZE 512
#define EEPROM_BLOCKS 	  64

/* USER CODE END PRIVATE_DEFINES */

/** USB Mass storage Standard Inquiry Data. */
const int8_t STORAGE_Inquirydata_FS[] = {/* 36 */

  /* LUN 0 */
  0x00,
  0x00, // Disable MSB bit - for multipartishion recognize in windows, removeble device = 0x80
  0x02,
  0x02,
  (STANDARD_INQUIRY_DATA_LEN - 5),
  0x00,
  0x00,
  0x00,
  'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
  'M', 'i', 'c', 'r', 'o', 'S', 'D', ' ', /* Product      : 16 Bytes */
  'D', 'e', 'v', 'i', 'c', 'e', ' ', ' ',
  '0', '.', '0' ,'1',                      /* Version      : 4 Bytes */

  /* LUN 1 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // 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 */
  'E', 'E', 'P', 'R', 'O', 'M', ' ', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */

  /* LUN 2 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // 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 */
  'S', 'y', 's', 't', 'e', 'm', ' ', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */

   /* LUN 3 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // 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 */
  'T', 'e', 's', 't', ' ', ' ', ' ', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */


  /* LUN 4 */
  0x00,
  0x00, // Disable MSB bit - for multipartishion recognize in windows, removeble device = 0x80
  0x02,
  0x02,
  (STANDARD_INQUIRY_DATA_LEN - 5),
  0x00,
  0x00,
  0x00,
  'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
  'M', 'i', 'c', 'r', 'o', 'S', 'D', '2', /* Product      : 16 Bytes */
  'D', 'e', 'v', 'i', 'c', 'e', ' ', ' ',
  '0', '.', '0' ,'1',                      /* Version      : 4 Bytes */

  /* LUN 5 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // 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 */
  'E', 'E', 'P', 'R', 'O', 'M', '2', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */

  /* LUN 6 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // bit6:0 - reseForrved, 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 */
  'S', 'y', 's', 't', 'e', 'm', '2', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */

   /* LUN 7 */
  0x00, // bit4:0 - peripheral device type, bit7:5 - reserved
  0x00, // 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 */
  'T', 'e', 's', 't', '2', ' ', ' ', ' ', /* Product : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0', '1', /* Version : 4 Bytes */

 };
/* USER CODE END INQUIRY_DATA_FS */

int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
  switch(lun){
  	  case LUN_NUM_MICROSD:
  		  *block_num  = sd_card_glb->data.size_kb << 1;
  		  *block_size = sd_card_glb->data.block_size;
  		  break;
  	  case LUN_NUM_EEPROM:
  	  	  *block_num  = EEPROM_BLOCKS;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case LUN_NUM_SYSTEM:
  	  	  *block_num  = EEPROM_BLOCKS << 2;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case LUN_NUM_TEST:
  	  	  *block_num  = EEPROM_BLOCKS << 4;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case 0x04:
  	  	  *block_num  = EEPROM_BLOCKS << 5;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case 0x05:
  	  	  *block_num  = EEPROM_BLOCKS << 6;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case 0x06:
  	  	  *block_num  = EEPROM_BLOCKS << 7;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  	  case 0x07:
  	  	  *block_num  = EEPROM_BLOCKS << 8;
  	  	  *block_size = EEPROM_BLOCK_SIZE;
  	  	  break;
  }


  return (USBD_OK);
  /* USER CODE END 3 */
}

int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */

  switch(lun){
  	  case LUN_NUM_MICROSD:
  		  sd_card_read_single_block(sd_card_glb, blk_addr, buf);
		/*	for (int i =0 ; i<512; i++){
						  buf[i] = blk_addr;
			}*/
		  break;
	  case LUN_NUM_EEPROM:
		  for (int i =0 ; i<512; i++){
			  buf[i] = i;
		  }
		  break;
	  case LUN_NUM_SYSTEM:
  	  	  for (int i =0 ; i<512; i++){
			  buf[i] = blk_addr;
		  }
  	  	  break;
  	  case LUN_NUM_TEST:
  	  	  for (int i =0 ; i<512; i++){
			  buf[i] = lun;
		  }
  	  	  break;
  }


  return (USBD_OK);
  /* USER CODE END 6 */
}

Собираем проект, запускаем и видим, что у нас вместо тестовых 8 LUN определилось только 2, да и нормально работают, если в STORAGE_GetCapacity_FS выдать одинаковый размер для обоих LUN, да ещё устройство переодически сбрасывается…

В kernel log:

usb 1-1: new full-speed USB device number 10 using xhci_hcd
usb 1-1: New USB device found, idVendor=0483, idProduct=572a, bcdDevice= 2.00
usb 1-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-1: Product: Test MASS Storage
usb 1-1: Manufacturer: Developer
usb 1-1: SerialNumber: 2062369C5950
usb-storage 1-1:1.0: USB Mass Storage device detected
scsi host4: usb-storage 1-1:1.0
scsi 4:0:0:0: Direct-Access     STM      MicroSD Device   0.01 PQ: 0 ANSI: 2
scsi 4:0:0:1: Direct-Access     STM      EEPROM           0.01 PQ: 0 ANSI: 2
scsi 4:0:0:0: Attached scsi generic sg1 type 0
scsi 4:0:0:1: Attached scsi generic sg2 type 0
sd 4:0:0:0: [sdb] 1030144 512-byte logical blocks: (527 MB/503 MiB)
sd 4:0:0:1: [sdc] 64 512-byte logical blocks: (32.8 kB/32.0 KiB)
sd 4:0:0:1: [sdc] Write Protect is off
sd 4:0:0:1: [sdc] Mode Sense: 22 0 0 0
sd 4:0:0:1: [sdc] Write cache: disabled, read cache: enabled, doesn't support DPO or FUA
 sdb: sdb1 sdb2 sdb3
usb 1-1: reset full-speed USB device number 10 using xhci_hcd
sd 4:0:0:0: [sdb] Attached SCSI disk
usb 1-1: reset full-speed USB device number 10 using xhci_hcd
usb 1-1: reset full-speed USB device number 10 using xhci_hcd
sd 4:0:0:0: [sdb] tag#0 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE cmd_age=0s
sd 4:0:0:0: [sdb] tag#0 Sense Key : Illegal Request [current]
sd 4:0:0:0: [sdb] tag#0 Add. Sense: Logical block address out of range
sd 4:0:0:0: [sdb] tag#0 CDB: Read(10) 28 0 0 0f b7 f8 0 0 1 0
blk_update_request: critical target error, dev sdb, sector 1030136 op 0x0:(READ) flags 0x80700 phys_seg 1 prio class 0
sd 4:0:0:1: [sdc] Attached SCSI disk
sd 4:0:0:0: [sdb] tag#0 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE cmd_age=0s
sd 4:0:0:0: [sdb] tag#0 Sense Key : Illegal Request [current]
sd 4:0:0:0: [sdb] tag#0 Add. Sense: Logical block address out of range
sd 4:0:0:0: [sdb] tag#0 CDB: Read(10) 28 0 0 0f b7 f8 0 0 1 0
blk_update_request: critical target error, dev sdb, sector 1030136 op 0x0:(READ) flags 0x0 phys_seg 1 prio class 0
buffer_io_error: 1 callbacks suppressed
Buffer I/O error on dev sdb, logical block 128767 async page read

В логе USB, захваченным Wireshark:

запрос USBMS 64 GET MAX LUN — отдаёт 7 (LUN нумеруются с 0 и отдаётся максимально возможный номер), а LUN 2 и остальные — игнорируются

ответы на запросы SCSI: Data In LUN: (Mode Sense (6) — говорят что получены неправильные данные от устройства, а это данные о параметров каждого LUN, в частности, делающие их readonly

не знает команду USBMS 95 SCSI Command: 0xa1 LUN:0×00 — команда ATA Command Pass-Through, после которой идёт переинициализация устройства по шине c продолжением работы:

386 4.538519  1.10.1  host  USBMS 77  SCSI: Response LUN: 0x00 (Read Capacity(10)) (Good)
387 4.538572  host  1.10.1  USBMS 95  SCSI Command: 0xa1 LUN:0x00
388 4.538641  1.10.1  host  USB 64  URB_BULK out
389 4.538657  host  1.10.1  USB 64  URB_BULK in
390 4.538711  1.10.1  host  USB 64  URB_BULK in
391 4.538726  host  1.10.0  USB 64  CLEAR FEATURE Request
392 4.538810  1.10.0  host  USB 64  CLEAR FEATURE Response
393 4.538824  host  1.10.1  USB 64  URB_BULK in
394 4.538925  1.10.1  host  USB 64  URB_BULK in
395 4.538937  host  1.10.0  USB 64  CLEAR FEATURE Request
396 4.539014  1.10.0  host  USB 64  CLEAR FEATURE Response
397 4.539067  host  1.10.1  USB 64  URB_BULK in
398 4.539125  1.10.1  host  USB 64  URB_BULK in
399 4.539169  host  1.10.0  USB 64  CLEAR FEATURE Request
400 4.539254  1.10.0  host  USB 64  CLEAR FEATURE Response
415 4.815078  host  1.10.0  USB 64  GET DESCRIPTOR Request DEVICE
416 4.815289  1.10.0  host  USB 82  GET DESCRIPTOR Response DEVICE
417 4.815371  host  1.10.0  USB 64  GET DESCRIPTOR Request BOS
418 4.815507  1.10.0  host  USB 69  GET DESCRIPTOR Response BOS
419 4.815584  host  1.10.0  USB 64  GET DESCRIPTOR Request BOS
420 4.815802  1.10.0  host  USB 76  GET DESCRIPTOR Response BOS
421 4.815898  host  1.10.0  USB 64  GET DESCRIPTOR Request CONFIGURATION
422 4.816161  1.10.0  host  USB 96  GET DESCRIPTOR Response CONFIGURATION
423 4.816257  host  1.10.0  USB 64  GET DESCRIPTOR Request CONFIGURATION
424 4.816527  1.10.0  host  USB 96  GET DESCRIPTOR Response CONFIGURATION
425 4.816616  host  1.10.0  USB 64  GET DESCRIPTOR Request CONFIGURATION
426 4.816874  1.10.0  host  USB 96  GET DESCRIPTOR Response CONFIGURATION
427 4.816959  host  1.10.0  USB 64  GET DESCRIPTOR Request CONFIGURATION
428 4.817224  1.10.0  host  USB 96  GET DESCRIPTOR Response CONFIGURATION
429 4.817309  host  1.10.0  USB 64  GET DESCRIPTOR Request STRING
430 4.817470  1.10.0  host  USB 90  GET DESCRIPTOR Response STRING
431 4.818177  host  1.10.0  USB 64  SET CONFIGURATION Request
432 4.818338  1.10.0  host  USB 64  SET CONFIGURATION Response
433 4.818507  host  1.10.1  USBMS 95  SCSI: Read(10) LUN: 0x03 (LBA: 0x00000008, Len: 8)

В итоге, библиотека не работает с несколькими LUN, разве что при одном условии у нас только 2 LUN и их размеры равны. Обнаруженные проблемы:

1) Независимо от указанного нами количество LUN в ответе на call back функцию int8_t STORAGE_GetMaxLun_FS (void) — отдаёт правильное количесво LUN, но LUN больше 1 — игнорируются

2) Если LUNы разного размера — возможно корректно работать только с меньшим из них, иначе, судя по логам — недопустимый для чтения номер блока

3) Постоянный сброс устройства по шине из-за отсутствия ответа на команду SCSI 0xa1 ATA Command Pass-Through

Для лучшего понимания, что мы будем дальше делать, кратко расмотрим работу Mass Storage с интерфейсом BOT

Описание работы USB Mass Storage BOT

Протокол BOT расшифровывается как Bulk Only Transfer, который ещё также называют BBB, так как все 3 фазы обмена (команд/данных/статуса) используют конечные точки. Существует ещё протокол CBI — Control Buck Interrupt, использующийся, в основном, для full-speed floppy дисководов.

На рисунке ниже изображена передача команд, данных и статуса по протоколу BOT. Контейнер командного блока CBW представляет из себя короткий пакет, длиной 31 байт. CBW и все последующие данные с контейнером статуса команды CSW представлют из себя пакет. Ещё одна особенность, это кодировка CBW — little-endian с LSB (0 байт идёт первым).

Передача команд/данных/статуса через BOT интерфейсПередача команд/данных/статуса через BOT интерфейс

В интерфейс BOT завернут трнспортный протокол SCSI (Small Computer System Interface), разработанный ещё в 1978 году настолько удачным, что используется до сих пор для взаимодействия с блочными носителями данных. Робота по SCSI выглядит следующим образом:

  1. Устройству хост отправляет команду с параметрами: SCSI: [команда + параметры]

  2. Устройство отдаёт хосту данные: SCSI Payload (Inquiry Response Data)

  3. Устройство отдаёт хосту статус: SCSI: Response (Inquiry) [статус]

    Чтение данныхЧтение данных

Запись данныхЗапись данных

Рассмотрим минимальных набор команд для определения нашего устройство системой. Команды взаимодействия с нашим устройством по USB выглядят следующим образом:

Пртокол

Направление

Команда

Данные

USB

Запрос дескрипторов устройства

Тип дескрипторов

USB

<-

DESCRIPTOR Request

Дескрипторы

USBMS

Запрос максимального номера LUN

USBMS

<-

GET MAX LUN Response

Максимальный LUN

SCSI

Запрос структуры LUN

номер LUN

SCSI

<-

SCSI: Data In LUN: 0xyy (Inquiry Response Data)

Структура LUN

SCSI

Юнит тест LUN

Номер LUN

SCSI

<-

SCSI: Response LUN: 0xyy (Test Unit Ready)

статус LUN

SCSI

Запрос ёмкости LUN: размер и количество блоков

номер LUN

SCSI

<-

SCSI: Data In LUN: 0×01 (Read Capacity (10) Response Data)

размер и количество блоков LUN

SCSI

Запрос режимов

номер LUN

SCSI

<-

SCSI: Data In LUN: 0×01 (Mode Sense (6) Response Data)

Режимы LUN, в том числе read-only

SCSI

Чтение блока

номер LUN, номер блоков, количество блоков

SCSI

<-

SCSI: Read (10) LUN: 0×03 (LBA: 0×00000000, Len: 8)

Прочитанные данные

Таким образом, после запроса дискрипторов, запроса готовности LUN и запроса ёмкости, драйвером операционной системы производится чтение блоков: вычитывается таблица разделов (MBR или GPT), затем сами разделы для определения их параметров и файловой системы, а затем последние блоки из заявленной ёмкости.

Перейдём теперь к структуре библиотек ST:

Файл

Назначение

USB_DEVICE→App→usb_device.c

Функции инициализации USB, здесь можно описать композитные устройства или переклбчение устройств

USB_DEVICE→App→usbd_desk.c

Дескриптор устройства DEVICE DESCRIPTOR (VID/PID), информация о производителе. Структура USBD_FS_DeviceDescUSBD_FS_DeviceDesc

Middlewares→ST→STM32_USB_Device_Library→Class→MSC→Src→usbd_msc.c

Дескрипторы конфигурации, интерфейса и конечных точек MassStorage и функции инициализакии USBD_MSC_CfgFSDesc

Middlewares→ST→STM32_USB_Device_Library→Class→MSC→Src→usbd_msc_bot.c

Функции интерфейса BOT

Middlewares→ST→STM32_USB_Device_Library→Class→MSC→Src→usbd_msc_data.c

Структуры ответов на некоторые команды SCSI, в частности на MODE_SENSE6 и MSC_Mode_Sense10

Middlewares→ST→STM32_USB_Device_Library→Class→MSC→Src→usbd_msc_scsi.c

Функции отправки и обработки SCSI команд интерфейса

USB_DEVICE→App→usbd_storage_if.с

Здесь мы описываем структуру LUN и функции взаимодействия с ними и с источниками данных

После того, как разобрались как всё работает и с чем будем иметь дело приступаем к починке.

1. Включаем больше 2 LUN

Исходя из специфики интерфейса BOT, максимальное количество поддерживаемых LUN равно 15 или 0×0f, видимо, больше бит жалко было, да и задач себе не представляю для микроконтроллера с 15-ю дисками, хотя… если очень хочется, можно сделать устройство композитным и навесить ещё 1 интерфейс со своими функциями обработки.

Драйверами операционных систем что Linux, что Windows, увидеть больше 8 LUN не получалось. Судя по анадизу трафика USB запрос GET MAX LUN больше 7 съедает, в логи драйвер не ругается, но 8-ое и более старшее устройство опросить даже не пытается…

Но библиотеки ST не идеальны и видимо не проходят должного тестирования функционала. Такое ощущение, что сидят студенты, проверят базовые функции и на этом заканчивают, убедившись, что раз работает — значит сойдет. Так и в нашем случае, протестировали с 1 LUN и успокоились на этом, при этом с 2-мя работает очень криво при равных размерах, а большем — вообще никак. Ну что же делать, остаётся нам править имеющееся, чтобы можно было пользоваться.

Тк у нас отсутствует отбработка SCSI команд для LUN, начиная с 2 и более, ищем функцию

static void  MSC_BOT_CBW_Decode(USBD_HandleTypeDef *pdev)

формирования SCSI команд Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_bot.c и видим ограничение номера LUN константой 1U! Правим, на максимальное, заданное нами количество LUN hmsc→max_lun:

@@ -255,7 +255,7 @@ static void  MSC_BOT_CBW_Decode(USBD_HandleTypeDef *pdev)
  
if ((USBD_LL_GetRxDataSize(pdev, MSC_EPOUT_ADDR) != USBD_BOT_CBW_LENGTH) ||
       (hmsc->cbw.dSignature != USBD_BOT_CBW_SIGNATURE) ||
-      (hmsc->cbw.bLUN > 1U) || (hmsc->cbw.bCBLength < 1U) ||
+      (hmsc->cbw.bLUN > hmsc->max_lun) || (hmsc->cbw.bCBLength < 1U) ||
       (hmsc->cbw.bCBLength > 16U))
   {
  SCSI_SenseCode(pdev, hmsc->cbw.bLUN, ILLEGAL_REQUEST, INVALID_CDB);

Но интерфейсом у нас ограничено максимальное количество 0×0f, поэтому ограничем максимально задаваемое количество LUN этим числом, добавив константу в файл Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc/usbd_msc.h:

@@ -55,6 +55,7 @@ extern "C" {
 #define BOT_RESET                    0xFF
 #define USB_MSC_CONFIG_DESC_SIZ      32
 
+#define MSC_BOT_MAX_LUN              0x0F
 
 #define MSC_EPIN_ADDR                0x81U
 #define MSC_EPOUT_ADDR               0x01U
@@ -82,25 +83,31 @@ typedef struct _USBD_STORAGE

Ограничивать будем hmsc→max_lun, получаемое из CALLBACK

int8_t STORAGE_GetMaxLun_FS(void)
{
  /* USER CODE BEGIN 8 */
  return (STORAGE_LUNS - 1);
  /* USER CODE END 8 */
}

файла USB_DEVICE→App→usbd_storage_if.c

Ограничиваем результат вызова CALLBACK в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc.c

@@ -390,6 +390,7 @@ uint8_t USBD_MSC_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req)
               ((req->bmRequest & 0x80U) == 0x80U))
           {
             hmsc->max_lun = (uint32_t)((USBD_StorageTypeDef *)pdev->pUserData)->GetMaxLun();
+            hmsc->max_lun = (hmsc->max_lun > MSC_BOT_MAX_LUN) ? MSC_BOT_MAX_LUN : hmsc->max_lun;
             (void)USBD_CtlSendData(pdev, (uint8_t *)&hmsc->max_lun, 1U);
           }
           else

После этого на хост стали приходить данне от всех 8 LUN, но радовться ещё рано, так как если размер LUN разный — блок больший минимального не вычитывается при перекрёстном опросе LUN.

2. Чиним LUN несколько LUN c разными размерами блока и количеством

Ошибка о вылете из адресного пространства номера блока исходит из функции проверки диапазона адресов

static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
                                     uint32_t blk_offset, uint32_t blk_nbr)
{
  USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;

  if (hmsc == NULL)
  {
    return -1;
  }

  if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr)
  {
    SCSI_SenseCode(pdev, lun, ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
    return -1;
  }

  return 0;
}

файла Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c. Внимательно присмотревшись, видим, что номер текущего блока сравнивается со считанным до этого количеством блоков hmsc→scsi_blk_nbr. Учитывая специфику протокола SCSI, хост не обязан перед каждой операцией запрашивать размер и количетво блоков читаемого/записываемого LUN и, соответственно, там могут остаться параметры другого LUN, что мы и видим. Вот как считывается количество и размер блоков LUN на примере SCSI_ReadCapacity16, для SCSI_ReadCapacity10 аналогично:

static int8_t SCSI_ReadCapacity16(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
{
  UNUSED(params);
  uint8_t idx;
  int8_t ret;
  USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;

  if (hmsc == NULL)
  {
    return -1;
  }

  ret = ((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &hmsc->scsi_blk_nbr, &hmsc->scsi_blk_size);

  if ((ret != 0) || (hmsc->scsi_medium_state == SCSI_MEDIUM_EJECTED))
  {
    SCSI_SenseCode(pdev, lun, NOT_READY, MEDIUM_NOT_PRESENT);
    return -1;
  }

  hmsc->bot_data_length = ((uint32_t)params[10] << 24) |
                          ((uint32_t)params[11] << 16) |
                          ((uint32_t)params[12] <<  8) |
                          (uint32_t)params[13];

  for (idx = 0U; idx < hmsc->bot_data_length; idx++)
  {
    hmsc->bot_data[idx] = 0U;
  }

  hmsc->bot_data[4] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 24);
  hmsc->bot_data[5] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 16);
  hmsc->bot_data[6] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >>  8);
  hmsc->bot_data[7] = (uint8_t)(hmsc->scsi_blk_nbr - 1U);

  hmsc->bot_data[8] = (uint8_t)(hmsc->scsi_blk_size >>  24);
  hmsc->bot_data[9] = (uint8_t)(hmsc->scsi_blk_size >>  16);
  hmsc->bot_data[10] = (uint8_t)(hmsc->scsi_blk_size >>  8);
  hmsc->bot_data[11] = (uint8_t)(hmsc->scsi_blk_size);

  hmsc->bot_data_length = ((uint32_t)params[10] << 24) |
                          ((uint32_t)params[11] << 16) |
                          ((uint32_t)params[12] <<  8) |
                          (uint32_t)params[13];

  return 0;
}

Те видим, что проверка адресов будет проходить только по последнему результату! Как не трудно догадаться, для исправления нам необходимо, чтобы для каждого LUN все параметры сохранялись индивидуально, те массив. PS: Оптимальнее всего было-бы вообще выкинуть CALLBACK STORAGE_GetMaxLun_FS и сразу заполнить структуру параметрами всех LUN, но учитывая, неоптимальность всего остального, это капля в море, тк оптимизировать там надо многое…

Для быстрого испавления в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc/usbd_msc.h создадим вспомогательную струтуру USBD_LUN_BLK_HandleTypeDef и исправим существующую USBD_LUN_BLK_HandleTypeDef:

typedef struct
{
  uint32_t                 max_lun;
  uint32_t                 interface;
  uint8_t                  bot_state;
  uint8_t                  bot_status;
  uint32_t                 bot_data_length;
  uint8_t                  bot_data[MSC_MEDIA_PACKET];
  USBD_MSC_BOT_CBWTypeDef  cbw;
  USBD_MSC_BOT_CSWTypeDef  csw;

  USBD_SCSI_SenseTypeDef   scsi_sense [SENSE_LIST_DEEPTH];
  uint8_t                  scsi_sense_head;
  uint8_t                  scsi_sense_tail;
  uint8_t                  scsi_medium_state;

  uint16_t                 scsi_blk_size;
  uint32_t                 scsi_blk_nbr;

  uint32_t                 scsi_blk_addr;
  uint32_t                 scsi_blk_len;
}
USBD_MSC_BOT_HandleTypeDef;

Следующим образом:

typedef struct
{
	uint16_t	size;
	uint32_t	nbr;
	uint32_t	addr;
	uint32_t	len;
}
USBD_LUN_BLK_HandleTypeDef;

typedef struct
{
  uint32_t                    max_lun;
  uint32_t                    interface;
  uint8_t                     bot_state;
  uint8_t                     bot_status;
  uint32_t                    bot_data_length;
  uint8_t                     bot_data[MSC_MEDIA_PACKET];
  USBD_MSC_BOT_CBWTypeDef     cbw;
  USBD_MSC_BOT_CSWTypeDef     csw;

  USBD_SCSI_SenseTypeDef      scsi_sense [SENSE_LIST_DEEPTH];
  uint8_t                     scsi_sense_head;
  uint8_t                     scsi_sense_tail;
  uint8_t                     scsi_medium_state;

  USBD_LUN_BLK_HandleTypeDef  scsi_blk[MSC_BOT_MAX_LUN];

}
USBD_MSC_BOT_HandleTypeDef;

Затем, нам необходимо исправить обраработчики команд SCSI в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c:

@@ -317,13 +317,14 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t
   UNUSED(params);
   int8_t ret;
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
 
   if (hmsc == NULL)
   {
     return -1;
   }
 
-  ret = ((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &hmsc->scsi_blk_nbr, &hmsc->scsi_blk_size);
+  ret = ((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &hlun->nbr, &hlun->size);
 
   if ((ret != 0) || (hmsc->scsi_medium_state == SCSI_MEDIUM_EJECTED))
   {
@@ -331,15 +332,15 @@ static int8_t SCSI_ReadCapacity10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t
     return -1;
   }
 
-  hmsc->bot_data[0] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 24);
-  hmsc->bot_data[1] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 16);
-  hmsc->bot_data[2] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >>  8);
-  hmsc->bot_data[3] = (uint8_t)(hmsc->scsi_blk_nbr - 1U);
+  hmsc->bot_data[0] = (uint8_t)((hlun->nbr - 1U) >> 24);
+  hmsc->bot_data[1] = (uint8_t)((hlun->nbr - 1U) >> 16);
+  hmsc->bot_data[2] = (uint8_t)((hlun->nbr - 1U) >>  8);
+  hmsc->bot_data[3] = (uint8_t)(hlun->nbr - 1U);
 
-  hmsc->bot_data[4] = (uint8_t)(hmsc->scsi_blk_size >>  24);
-  hmsc->bot_data[5] = (uint8_t)(hmsc->scsi_blk_size >>  16);
-  hmsc->bot_data[6] = (uint8_t)(hmsc->scsi_blk_size >>  8);
-  hmsc->bot_data[7] = (uint8_t)(hmsc->scsi_blk_size);
+  hmsc->bot_data[4] = (uint8_t)(hlun->size >>  24);
+  hmsc->bot_data[5] = (uint8_t)(hlun->size >>  16);
+  hmsc->bot_data[6] = (uint8_t)(hlun->size >>  8);
+  hmsc->bot_data[7] = (uint8_t)(hlun->size);
 
   hmsc->bot_data_length = 8U;
 
@@ -361,13 +362,14 @@ static int8_t SCSI_ReadCapacity16(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t
   uint8_t idx;
   int8_t ret;
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
 
   if (hmsc == NULL)
   {
     return -1;
   }
 
-  ret = ((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &hmsc->scsi_blk_nbr, &hmsc->scsi_blk_size);
+  ret = ((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &hlun->nbr, &hlun->size);
 
   if ((ret != 0) || (hmsc->scsi_medium_state == SCSI_MEDIUM_EJECTED))
   {
@@ -385,15 +387,15 @@ static int8_t SCSI_ReadCapacity16(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t
     hmsc->bot_data[idx] = 0U;
   }
 
-  hmsc->bot_data[4] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 24);
-  hmsc->bot_data[5] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >> 16);
-  hmsc->bot_data[6] = (uint8_t)((hmsc->scsi_blk_nbr - 1U) >>  8);
-  hmsc->bot_data[7] = (uint8_t)(hmsc->scsi_blk_nbr - 1U);
+  hmsc->bot_data[4] = (uint8_t)((hlun->nbr - 1U) >> 24);
+  hmsc->bot_data[5] = (uint8_t)((hlun->nbr - 1U) >> 16);
+  hmsc->bot_data[6] = (uint8_t)((hlun->nbr - 1U) >>  8);
+  hmsc->bot_data[7] = (uint8_t)(hlun->nbr - 1U);
 
-  hmsc->bot_data[8] = (uint8_t)(hmsc->scsi_blk_size >>  24);
-  hmsc->bot_data[9] = (uint8_t)(hmsc->scsi_blk_size >>  16);
-  hmsc->bot_data[10] = (uint8_t)(hmsc->scsi_blk_size >>  8);
-  hmsc->bot_data[11] = (uint8_t)(hmsc->scsi_blk_size);
+  hmsc->bot_data[8] = (uint8_t)(hlun->size >>  24);
+  hmsc->bot_data[9] = (uint8_t)(hlun->size >>  16);
+  hmsc->bot_data[10] = (uint8_t)(hlun->size >>  8);
+  hmsc->bot_data[11] = (uint8_t)(hlun->size);
 
   hmsc->bot_data_length = ((uint32_t)params[10] << 24) |
                           ((uint32_t)params[11] << 16) |
@@ -472,6 +474,12 @@ static int8_t SCSI_ModeSense6(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *pa
   {
     return -1;
   }
+  
+  /* Check If media is write-protected */
+  if (((USBD_StorageTypeDef *)pdev->pUserData)->IsWriteProtected(lun) == 1)
+  {
+    MSC_Mode_Sense6_data[2] |= 0x80;
+  }
 
   if (params[4] <= len)
   {
@@ -502,6 +510,12 @@ static int8_t SCSI_ModeSense10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *p
     return -1;
   }
 
+  /* Check If media is write-protected */
+  if (((USBD_StorageTypeDef *)pdev->pUserData)->IsWriteProtected(lun) == 1)
+  {
+    MSC_Mode_Sense6_data[2] |= 0x80;
+  }
+
   if (params[8] <= len)
   {
     len = params[8];
@@ -688,6 +702,7 @@ static int8_t SCSI_AllowPreventRemovable(USBD_HandleTypeDef *pdev, uint8_t lun,
 static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
 
   if (hmsc == NULL)
   {
@@ -716,21 +731,20 @@ static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params
       return -1;
     }
 
-    hmsc->scsi_blk_addr = ((uint32_t)params[2] << 24) |
-                          ((uint32_t)params[3] << 16) |
-                          ((uint32_t)params[4] <<  8) |
-                          (uint32_t)params[5];
+    hlun->addr = ((uint32_t)params[2] << 24) |
+                 ((uint32_t)params[3] << 16) |
+                 ((uint32_t)params[4] <<  8) |
+                  (uint32_t)params[5];
 
-    hmsc->scsi_blk_len = ((uint32_t)params[7] <<  8) | (uint32_t)params[8];
+    hlun->len = ((uint32_t)params[7] <<  8) | (uint32_t)params[8];
 
-    if (SCSI_CheckAddressRange(pdev, lun, hmsc->scsi_blk_addr,
-                               hmsc->scsi_blk_len) < 0)
+    if (SCSI_CheckAddressRange(pdev, lun, hlun->addr, hlun->len) < 0)
     {
       return -1; /* error */
     }
 
     /* cases 4,5 : Hi <> Dn */
-    if (hmsc->cbw.dDataLength != (hmsc->scsi_blk_len * hmsc->scsi_blk_size))
+    if (hmsc->cbw.dDataLength != (hlun->len * hlun->size))
     {
       SCSI_SenseCode(pdev, hmsc->cbw.bLUN, ILLEGAL_REQUEST, INVALID_CDB);
       return -1;
@@ -754,6 +768,7 @@ static int8_t SCSI_Read10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params
 static int8_t SCSI_Read12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
 
   if (hmsc == NULL)
   {
@@ -781,24 +796,23 @@ static int8_t SCSI_Read12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params
       return -1;
     }
 
-    hmsc->scsi_blk_addr = ((uint32_t)params[2] << 24) |
-                          ((uint32_t)params[3] << 16) |
-                          ((uint32_t)params[4] <<  8) |
-                          (uint32_t)params[5];
+    hlun->addr = ((uint32_t)params[2] << 24) |
+                 ((uint32_t)params[3] << 16) |
+                 ((uint32_t)params[4] <<  8) |
+                  (uint32_t)params[5];
 
-    hmsc->scsi_blk_len = ((uint32_t)params[6] << 24) |
-                         ((uint32_t)params[7] << 16) |
-                         ((uint32_t)params[8] << 8) |
-                         (uint32_t)params[9];
+    hlun->len = ((uint32_t)params[6] << 24) |
+                ((uint32_t)params[7] << 16) |
+                ((uint32_t)params[8] << 8) |
+                 (uint32_t)params[9];
 
-    if (SCSI_CheckAddressRange(pdev, lun, hmsc->scsi_blk_addr,
-                               hmsc->scsi_blk_len) < 0)
+    if (SCSI_CheckAddressRange(pdev, lun, hlun->addr, hlun->len) < 0)
     {
       return -1; /* error */
     }
 
     /* cases 4,5 : Hi <> Dn */
-    if (hmsc->cbw.dDataLength != (hmsc->scsi_blk_len * hmsc->scsi_blk_size))
+    if (hmsc->cbw.dDataLength != (hlun->len * hlun->size))
     {
       SCSI_SenseCode(pdev, hmsc->cbw.bLUN, ILLEGAL_REQUEST, INVALID_CDB);
       return -1;
@@ -822,6 +836,7 @@ static int8_t SCSI_Read12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params
 static int8_t SCSI_Write10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
   uint32_t len;
 
   if (hmsc == NULL)
@@ -858,22 +873,21 @@ static int8_t SCSI_Write10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *param
       return -1;
     }
 
-    hmsc->scsi_blk_addr = ((uint32_t)params[2] << 24) |
-                          ((uint32_t)params[3] << 16) |
-                          ((uint32_t)params[4] << 8) |
-                          (uint32_t)params[5];
+    hlun->addr = ((uint32_t)params[2] << 24) |
+                 ((uint32_t)params[3] << 16) |
+                 ((uint32_t)params[4] << 8) |
+                  (uint32_t)params[5];
 
-    hmsc->scsi_blk_len = ((uint32_t)params[7] << 8) |
-                         (uint32_t)params[8];
+    hlun->len = ((uint32_t)params[7] << 8) |
+                 (uint32_t)params[8];
 
     /* check if LBA address is in the right range */
-    if (SCSI_CheckAddressRange(pdev, lun, hmsc->scsi_blk_addr,
-                               hmsc->scsi_blk_len) < 0)
+    if (SCSI_CheckAddressRange(pdev, lun, hlun->addr, hlun->len) < 0)
     {
       return -1; /* error */
     }
 
-    len = hmsc->scsi_blk_len * hmsc->scsi_blk_size;
+    len = hlun->len * hlun->size;
 
     /* cases 3,11,13 : Hn,Ho <> D0 */
     if (hmsc->cbw.dDataLength != len)
@@ -907,6 +921,7 @@ static int8_t SCSI_Write10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *param
 static int8_t SCSI_Write12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
   uint32_t len;
 
   if (hmsc == NULL)
@@ -945,24 +960,23 @@ static int8_t SCSI_Write12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *param
       return -1;
     }
 
-    hmsc->scsi_blk_addr = ((uint32_t)params[2] << 24) |
-                          ((uint32_t)params[3] << 16) |
-                          ((uint32_t)params[4] << 8) |
-                          (uint32_t)params[5];
+    hlun->addr = ((uint32_t)params[2] << 24) |
+                 ((uint32_t)params[3] << 16) |
+                 ((uint32_t)params[4] << 8) |
+                  (uint32_t)params[5];
 
-    hmsc->scsi_blk_len = ((uint32_t)params[6] << 24) |
-                         ((uint32_t)params[7] << 16) |
-                         ((uint32_t)params[8] << 8) |
-                         (uint32_t)params[9];
+    hlun->len = ((uint32_t)params[6] << 24) |
+                ((uint32_t)params[7] << 16) |
+                ((uint32_t)params[8] << 8) |
+                 (uint32_t)params[9];
 
     /* check if LBA address is in the right range */
-    if (SCSI_CheckAddressRange(pdev, lun, hmsc->scsi_blk_addr,
-                               hmsc->scsi_blk_len) < 0)
+    if (SCSI_CheckAddressRange(pdev, lun, hlun->addr, hlun->len) < 0)
     {
       return -1; /* error */
     }
 
-    len = hmsc->scsi_blk_len * hmsc->scsi_blk_size;
+    len = hlun->len * hlun->size;
 
     /* cases 3,11,13 : Hn,Ho <> D0 */
     if (hmsc->cbw.dDataLength != len)
@@ -996,6 +1010,7 @@ static int8_t SCSI_Write12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *param
 static int8_t SCSI_Verify10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
 
   if (hmsc == NULL)
   {
@@ -1008,7 +1023,7 @@ static int8_t SCSI_Verify10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *para
     return -1; /* Error, Verify Mode Not supported*/
   }
 
-  if (SCSI_CheckAddressRange(pdev, lun, hmsc->scsi_blk_addr, hmsc->scsi_blk_len) < 0)
+  if (SCSI_CheckAddressRange(pdev, lun, hlun->addr, hlun->len) < 0)
   {
     return -1; /* error */
   }
@@ -1026,22 +1041,23 @@ static int8_t SCSI_Verify10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *para
   * @param  blk_nbr: number of block to be processed
   * @retval status
   */
-static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
+__attribute__((optimize(0))) static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
                                      uint32_t blk_offset, uint32_t blk_nbr)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
-
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];

   if (hmsc == NULL)
   {
     return -1;
   }
 
-  if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr)
+  if ((blk_offset + blk_nbr) > hlun->nbr)
   {
     SCSI_SenseCode(pdev, lun, ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
     return -1;
   }


   return 0;
 }
 
@@ -1054,7 +1070,8 @@ static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
 static int8_t SCSI_ProcessRead(USBD_HandleTypeDef *pdev, uint8_t lun)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
-  uint32_t len = hmsc->scsi_blk_len * hmsc->scsi_blk_size;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
+  uint32_t len = hlun->len * hlun->size;
 
   if (hmsc == NULL)
   {
@@ -1064,8 +1081,8 @@ static int8_t SCSI_ProcessRead(USBD_HandleTypeDef *pdev, uint8_t lun)
   len = MIN(len, MSC_MEDIA_PACKET);
 
   if (((USBD_StorageTypeDef *)pdev->pUserData)->Read(lun, hmsc->bot_data,
-                                                     hmsc->scsi_blk_addr,
-                                                     (len / hmsc->scsi_blk_size)) < 0)
+		                                             hlun->addr,
+                                                     (len / hlun->size)) < 0)
   {
     SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, UNRECOVERED_READ_ERROR);
     return -1;
@@ -1073,13 +1090,13 @@ static int8_t SCSI_ProcessRead(USBD_HandleTypeDef *pdev, uint8_t lun)
 
   (void)USBD_LL_Transmit(pdev, MSC_EPIN_ADDR, hmsc->bot_data, len);
 
-  hmsc->scsi_blk_addr += (len / hmsc->scsi_blk_size);
-  hmsc->scsi_blk_len -= (len / hmsc->scsi_blk_size);
+  hlun->addr += (len / hlun->size);
+  hlun->len -= (len / hlun->size);
 
   /* case 6 : Hi = Di */
   hmsc->csw.dDataResidue -= len;
 
-  if (hmsc->scsi_blk_len == 0U)
+  if (hlun->len == 0U)
   {
     hmsc->bot_state = USBD_BOT_LAST_DATA_IN;
   }
@@ -1096,7 +1113,8 @@ static int8_t SCSI_ProcessRead(USBD_HandleTypeDef *pdev, uint8_t lun)
 static int8_t SCSI_ProcessWrite(USBD_HandleTypeDef *pdev, uint8_t lun)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
-  uint32_t len = hmsc->scsi_blk_len * hmsc->scsi_blk_size;
+  USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
+  uint32_t len = hlun->len * hlun->size;
 
   if (hmsc == NULL)
   {
@@ -1106,26 +1124,26 @@ static int8_t SCSI_ProcessWrite(USBD_HandleTypeDef *pdev, uint8_t lun)
   len = MIN(len, MSC_MEDIA_PACKET);
 
   if (((USBD_StorageTypeDef *)pdev->pUserData)->Write(lun, hmsc->bot_data,
-                                                      hmsc->scsi_blk_addr,
-                                                      (len / hmsc->scsi_blk_size)) < 0)
+		                                              hlun->addr,
+                                                      (len / hlun->size)) < 0)
   {
     SCSI_SenseCode(pdev, lun, HARDWARE_ERROR, WRITE_FAULT);
     return -1;
   }
 
-  hmsc->scsi_blk_addr += (len / hmsc->scsi_blk_size);
-  hmsc->scsi_blk_len -= (len / hmsc->scsi_blk_size);
+  hlun->addr += (len / hlun->size);
+  hlun->len -= (len / hlun->size);
 
   /* case 12 : Ho = Do */
   hmsc->csw.dDataResidue -= len;
 
-  if (hmsc->scsi_blk_len == 0U)
+  if (hlun->len == 0U)
   {
     MSC_BOT_SendCSW(pdev, USBD_CSW_CMD_PASSED);
   }
   else
   {
-    len = MIN((hmsc->scsi_blk_len * hmsc->scsi_blk_size), MSC_MEDIA_PACKET);
+    len = MIN((hlun->len * hlun->size), MSC_MEDIA_PACKET);
 
     /* Prepare EP to Receive next packet */
     (void)USBD_LL_PrepareReceive(pdev, MSC_EPOUT_ADDR, hmsc->bot_data, len);

3. Чиним SCSI Mode Sense и добавляем режим Read Only (Write Protect)

При первом знакомстве с ST библиотекой меня очень обрадовало наличие в USB_DEVICE→App→usbd_storage_if.с CALLBACK

int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);

Но по изучению кода оказалось, что он разве что пригоден для временного отключения записи возвратом USBD_FAIL, а запись включена USBD_OK, тк реализован проверкой результата на момент вызова SCSI Write. Хотелось бы видеть реализацию постоянного запрета на запись при инициализации устройства, о чём сообщает ядро следующим образом (при разрешённой записи):

sd 4:0:0:2: [sdd] Write Protect is off
sd 4:0:0:2: [sdd] Mode Sense: 22 0 0 0

За включение Write Protect отвечает SCSI запрос Mode Sense, возвращающий 4 байта в ответе по спещификации команд SCSI. Но присавшие библиотеку в ST явно чем-то мягким напрыгали на аж 23 байта, ибо 0×22 это дилина пакета и это 34! На это пакет WireShark орёт благим матом, только и в лог ядра попадает часть этого безобразия. Ну что же, приводим это недоразумение в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_data.c в соответствии со спецификацией:

/* USB Mass storage sense 6 Data */
uint8_t MSC_Mode_Sense6_data[MODE_SENSE6_LEN] =
{
  0x03,     /* MODE DATA LENGTH. The number of bytes that follow. */
  0x00,     /* MEDIUM TYPE. 00h for SBC devices. */
  0x00,     /* DEVICE-SPECIFIC PARAMETER. For SBC devices: 
             *   bit 7: WP. Set to 1 if the media is write-protected.
             *   bits 6..4: reserved
             *   bit 4: DPOFUA. Set to 1 if the device supports the DPO and FUA bits (used in caching)
             *   bits 3..0: reserved*/
  0x00      /* Put Product Serial number */
};


/* USB Mass storage sense 10  Data */
uint8_t MSC_Mode_Sense10_data[MODE_SENSE10_LEN] =
{
  0x07,     /* MODE DATA LENGTH. The number of bytes that follow. */
  0x00,     /* MEDIUM TYPE. 00h for SBC devices. */
  0x00,     /* DEVICE-SPECIFIC PARAMETER. For SBC devices: 
             *   bit 7: WP. Set to 1 if the media is write-protected.
             *   bits 6..4: reserved
             *   bit 4: DPOFUA. Set to 1 if the device supports the DPO and FUA bits (used in caching)
             *   bits 3..0: reserved*/
  0x00,     /* Reserved */
  0x00,     /* Reserved */
  0x00,     /* Reserved */
  0x00,     /* Reserved */
  0x00      /* BLOCK DESCRIPTOR LENGTH. The length in bytes of all block descriptors in the
             *   mode parameter list. */
};

Не забывая при этом исправить длину ответа в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc/usbd_msc_data.h

@@ -40,8 +40,8 @@ extern "C" {
 /** @defgroup USB_INFO_Exported_Defines
   * @{
   */
-#define MODE_SENSE6_LEN                    0x17U
-#define MODE_SENSE10_LEN                   0x1BU
+#define MODE_SENSE6_LEN                    0x04U
+#define MODE_SENSE10_LEN                   0x08U
 #define LENGTH_INQUIRY_PAGE00              0x06U
 #define LENGTH_INQUIRY_PAGE80              0x08U

Этим мы успокоили WireShak, но мф же хотим сделать ReadOnly. Самый простой вариант и для всех дисков — задать по умочанию прямо в структуре, но он будет сразу для всех LUN. Для выбора задействуем рассмотренный выше CALLBACK STORAGE_IsWriteProtected_FS(uint8_t lun);, расширив его функциональность. Пусть теперь при возврате USBD_BUSY он делает диск WriteProtect на этапе инициализации. Для этого в файле Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c правим функции обработки SCSI команды SCSI_ModeSense 6 и 10:

@@ -472,6 +472,12 @@ static int8_t SCSI_ModeSense6(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *pa
   {
     return -1;
   }
+  
+  /* Check If media is write-protected */
+  if (((USBD_StorageTypeDef *)pdev->pUserData)->IsWriteProtected(lun) == 1)
+  {
+    MSC_Mode_Sense6_data[2] |= 0x80;
+  }
 
   if (params[4] <= len)
   {
@@ -502,6 +508,12 @@ static int8_t SCSI_ModeSense10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *p
     return -1;
   }
 
+  /* Check If media is write-protected */
+  if (((USBD_StorageTypeDef *)pdev->pUserData)->IsWriteProtected(lun) == 1)
+  {
+    MSC_Mode_Sense6_data[2] |= 0x80;
+  }
+
   if (params[8] <= len)
   {
     len = params[8];

Теперь, при возрате CALLBACK

int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
  /* USER CODE BEGIN 5 */
  switch(lun){
  	  case LUN_NUM_MICROSD:
  		return (USBD_OK);
	  case LUN_NUM_EEPROM:
		return (USBD_BUSY);
		break;
	  case LUN_NUM_SYSTEM:
  	  	return (USBD_BUSY);
  	  	break;
  	  case LUN_NUM_TEST:
  	  	return (USBD_BUSY);
  	  	break;
  	  default:
		return (USBD_FAIL);
  }  
  /* USER CODE END 5 */
}

Поллучаем для:

LUN_NUM_MICROSD — Запись разрешена

LUN_NUM_EEPROM, LUN_NUM_SYSTEM, LUN_NUM_TEST — Запрещена полностью при инициализации диска системой

Для остальных — Запрещена проверкой в команде SCSI Write

В логе linux выглядит это следующим образом:

sd 4:0:0:0: [sdb] Write Protect is off
sd 4:0:0:0: [sdb] Mode Sense: 03 00 00 00

sd 4:0:0:1: [sdc] Write Protect is on
sd 4:0:0:1: [sdc] Mode Sense: 03 00 80 00

sd 4:0:0:2: [sdd] Write Protect is on
sd 4:0:0:2: [sdd] Mode Sense: 03 00 80 00

sd 4:0:0:3: [sde] Write Protect is on
sd 4:0:0:3: [sde] Mode Sense: 03 00 80 00

sd 4:0:0:4: [sdf] Write Protect is off
sd 4:0:0:4: [sdf] Mode Sense: 03 00 00 00

sd 4:0:0:5: [sdg] Write Protect is off
sd 4:0:0:5: [sdg] Mode Sense: 03 00 00 00

sd 4:0:0:7: [sdi] Write Protect is off
sd 4:0:0:7: [sdi] Mode Sense: 03 00 00 00

sd 4:0:0:6: [sdh] Write Protect is off
sd 4:0:0:6: [sdh] Mode Sense: 03 00 00 00

4. Чиним сброс устройство, добавлением недостающих SCSI команд

После всех предыдущих исправлений осталось единственное. Устройство вроде работает, но в логе сообщений ядра linux проскакивает строчка о сбросе устройства:

usb 1-1: reset full-speed USB device number 10 using xhci_hcd

А в WireShark виден сброс с перезапросом дескрипторов. Этому всегда предшествует некорректный ответ на неизвестные устройству команды. Для исправления этого добавим пустые, синтаксически правильные ответы на следующие команды: SCSI_REPORT_LUNS12, SCSI_LOG_SENSE10, SCSI_RECEIVE_DIAGNOSTIC8, SCSI_ATA_PASSTHROUGH12

Для SCSI_REPORT_LUNS12 добавим номер команды в файл Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc/usbd_msc_scsi.h:

@ -74,6 +74,8 @@ extern "C" {
 #define SCSI_SEND_DIAGNOSTIC                        0x1DU
 #define SCSI_READ_FORMAT_CAPACITIES                 0x23U
 
+#define SCSI_REPORT_LUNS12                          0xA0U
+

И её реализацию в файл Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c:

@@ -100,6 +100,9 @@ static int8_t SCSI_ProcessWrite(USBD_HandleTypeDef *pdev, uint8_t lun);
 
 static int8_t SCSI_UpdateBotData(USBD_MSC_BOT_HandleTypeDef *hmsc,
                                  uint8_t *pBuff, uint16_t length);
+
+static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef *pdev, uint8_t lun,
+                                 uint8_t *params);
 /**
   * @}
   */
@@ -190,6 +193,10 @@ int8_t SCSI_ProcessCmd(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *cmd)
       ret = SCSI_Verify10(pdev, lun, cmd);
       break;
 
+    case SCSI_REPORT_LUNS12:
+      ret = SCSI_ReportLuns12(pdev, lun, cmd);
+      break;
+
     default:
       SCSI_SenseCode(pdev, lun, ILLEGAL_REQUEST, INVALID_CDB);
       hmsc->bot_status = USBD_BOT_STATUS_ERROR;
@@ -1041,12 +1048,12 @@ static int8_t SCSI_Verify10(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *para
   * @param  blk_nbr: number of block to be processed
   * @retval status
   */
-__attribute__((optimize(0))) static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
+static int8_t SCSI_CheckAddressRange(USBD_HandleTypeDef *pdev, uint8_t lun,
                                      uint32_t blk_offset, uint32_t blk_nbr)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
   USBD_LUN_BLK_HandleTypeDef *hlun = &hmsc->scsi_blk[lun];
-/*
+
   if (hmsc == NULL)
   {
     return -1;
@@ -1057,7 +1064,7 @@ __attribute__((optimize(0))) static int8_t SCSI_CheckAddressRange(USBD_HandleTyp
     SCSI_SenseCode(pdev, lun, ILLEGAL_REQUEST, ADDRESS_OUT_OF_RANGE);
     return -1;
   }
-*/
+
   return 0;
 }
 
@@ -1181,6 +1188,41 @@ static int8_t SCSI_UpdateBotData(USBD_MSC_BOT_HandleTypeDef *hmsc,
 
   return 0;
 }
+
+
+/**
+  * @brief  SCSI_Write12
+  *         Process Write12 command
+  * @param  lun: Logical unit number
+  * @param  params: Command parameters
+  * @retval status
+  */
+__attribute__((optimize(0))) static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
+{
+  USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
+
+  uint16_t lun_i;
+
+  static uint64_t report_luns[MSC_BOT_MAX_LUN + 1];
+
+  if (hmsc == NULL)
+  {
+    return -1;
+  }
+
+
+  report_luns[0] = 0;
+  ((uint8_t *)report_luns)[3] = sizeof(uint64_t)*(hmsc->max_lun + 1);
+
+  for (lun_i = 0; lun_i <= hmsc->max_lun; lun_i++){
+  	report_luns[lun_i + 1] = lun_i << 8;
+  }
+
+  (void)SCSI_UpdateBotData(hmsc, (uint8_t *)report_luns, sizeof(uint64_t)*(hmsc->max_lun + 2) );
+
+  return 0;
+}
+
 /**@@ -75,6 +75,7 @@ extern "C" {
 #define SCSI_READ_FORMAT_CAPACITIES                 0x23U
 
 #define SCSI_REPORT_LUNS12                          0xA0U
+#define SCSI_LOG_SENSE10                            0x4DU

Для SCSI_LOG_SENSE10 добавим номер команды в файл Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Inc/usbd_msc_scsi.h:

@@ -75,6 +75,7 @@ extern "C" {
 #define SCSI_READ_FORMAT_CAPACITIES                 0x23U
 
 #define SCSI_REPORT_LUNS12                          0xA0U
+#define SCSI_LOG_SENSE10                            0x4DU

И её реализацию в файл Middlewares/ST/STM32_USB_Device_Library/Class/MSC/Src/usbd_msc_scsi.c:

@@ -103,6 +103,8 @@ static int8_t SCSI_UpdateBotData(USBD_MSC_BOT_HandleTypeDef *hmsc,
 
 static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef *pdev, uint8_t lun,
                                  uint8_t *params);
+static int8_t SCSI_Log_Sense10(USBD_HandleTypeDef *pdev, uint8_t lun,
+								uint8_t *params);
 /**
   * @}
   */
@@ -197,6 +199,10 @@ int8_t SCSI_ProcessCmd(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *cmd)
       ret = SCSI_ReportLuns12(pdev, lun, cmd);
       break;
 
+    case SCSI_LOG_SENSE10:
+      ret = SCSI_Log_Sense10(pdev, lun, cmd);
SCSI_RECEIVE_DIAGNOSTIC8+      break;
+
     default:
       SCSI_SenseCode(pdev, lun, ILLEGAL_REQUEST, INVALID_CDB);
       hmsc->bot_status = USBD_BOT_STATUS_ERROR;
@@ -1197,7 +1203,7 @@ static int8_t SCSI_UpdateBotData(USBD_MSC_BOT_HandleTypeDef *hmsc,
   * @param  params: Command parameters
   * @retval status
   */
-__attribute__((optimize(0))) static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
+static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef *pdev, uint8_t lun, uint8_t *params)
 {
   USBD_MSC_BOT_HandleTypeDef *hmsc = (USBD_MSC_BOT_HandleTypeDef *)pdev->pClassData;
 
@@ -1223,6 +1229,31 @@ __attribute__((optimize(0))) static int8_t SCSI_ReportLuns12(USBD_HandleTypeDef
   return 0;
 }
 
+

Для SCSI_RECEIVE_DIAGNOSTIC8, SCSI_ATA_PASSTHROUGH12 добавим

© Habrahabr.ru