USB Mass Storage BOT чиним multi LUN у STM32
Попалась задача в проекте реализовать, чтобы по USB микроконтроллер прикидывался несколькими дисковыми устройствами для MicroSD, встроенной EEPROM и нескольких страничек оперативной памяти. Решил, что вполне логично, пойти по пути наименьшего сопротивления, попробовав запустить из коробки, то что ST реализовали в своей библиотеке. Работа c USB разделена у них на уровни абстракции: драйвер + MiddleWare:
USB библиотека 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 завернут трнспортный протокол SCSI (Small Computer System Interface), разработанный ещё в 1978 году настолько удачным, что используется до сих пор для взаимодействия с блочными носителями данных. Робота по SCSI выглядит следующим образом:
Устройству хост отправляет команду с параметрами: SCSI: [команда + параметры]
Устройство отдаёт хосту данные: SCSI Payload (Inquiry Response Data)
Устройство отдаёт хосту статус: 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 добавим