В общем случае обмен по протоколу SCSI происходит так: хост посылает командный блок (CBW), далее, исходя из команды, хост может посылать блок данные, хост может принимать данные, или устройство возвращает состояние (CSW).
Рисунок 1 – Процесс обмена по протоколу SCSI
Структура командного блока (CBW):
typedef struct { uint16_t dCBWSignatureL; uint16_t dCBWSignatureH; uint16_t dCBWTagL; uint16_t dCBWTagH; uint16_t dCBWDataTransferLengthL; uint16_t dCBWDataTransferLengthH; uint8_t bmCBWFlags; uint8_t bCBWLUN; uint8_t bCBWCBLength; uint8_t CBWCB[16]; } scsi_cbw_t;
32-битные поля структуры разбиты по 16 бит, для того, чтобы эту структуру можно было «натянуть» на приемный буфер конечной точки. А там, как я уже говорил, 32-битный доступ запрещен.
Размер, бит | Поле | Описание |
32 | dCBWSignature | Число 0x43425355 ("CBSU"), служащее опознавательным признаком CBW. Младший байт (0x55) передаётся первым ("USBC") |
32 | dCBWTag | Число, которое должно совпасть со значением поля "dCSWTag" в ответном контейнере состояния команды (CSW) |
32 | dCBWDataTransferLength | Объём информации, передаваемой на этапе пересылки данных, в байтах |
8 | bmCBWFlags | Направление передачи на этапе пересылки данных. Бит 7 = 0 для направления OUT (от хоста к устройству). Бит 7 = 1 для направления IN (от устройства к хосту). Если этап передачи данных отсутствует, данный бит игнорируется. Все остальные биты должны быть равны нулю |
8 | bCBWLUN | Старшие 4 бита зарезервированы и должны быть равны нулю. Младшие биты задают номер логического накопителя (LUN) (для устройств, поддерживающих несколько логических накопителей) или равны нулю |
8 | bCBWCBLength | Старшие три бита зарезервированы и равны нулю. Младшие 5 бит задают длину команды (CDB) внутри поля "CBWCB" в байтах. Допустимы значения в диапазоне 1..16. Все определённые к настоящему моменту командные блоки имеют длину не менее шести байт |
8*16 | CBWCB[16] | Командный блок |
После приёма командного блока (CBW) устройство должно приготовиться, в зависимости от команды, к приёму данных в оконечную точку, работающую в режиме OUT, или передаче данных или контейнера состояния (CSW) из точки в режиме IN.
Структура контейнера состояния (CSW):
typedef struct { uint32_t dCSWSignature; uint32_t dCSWTag; uint32_t dCSWDataResidue; uint8_t bCSWStatus; } scsi_csw_t;
Размер, бит | Поле | Описание |
32 | dCSWSignature | Число 0x53425355 ("SBSU"), служащее опознавательным признаком CBW. Младший байт (0x55) передаётся первым |
32 | dCSWTag | Число из поля "dCBWTag" принятого командного блока (CBW) |
32 | dCSWDataResidue | Разница между dCBWDataTransferLength и реально обработанными данными |
8 | bCSWStatus | 0x00 = успешное выполнение. 0x01 = ошибка исполнения. 0x02 = ошибка протокольной последовательности. Хост должен провести процедуру сброса и восстановления |
//сразу инициализируем scsi_csw_t CSW = { 0x53425355, 0, 0, 0 };
Команды SCSI
Команды передаются внутри командного блока CBW. Реализуем следующий набор команд:
- INQUIRY
- REQUEST SENSE
- READ CAPACITY(10)
- MODE SENSE(6)
- READ(10)
- WRITE(10)
- TEST UNIT READY
- PREVENT ALLOW MEDIUM REMOVAL
INQUIRY
Эта команда запрашивает структуру с информацией об устройстве.
Бит EVPD – если равен 0, устройство возвращает стандартный ответ на INQUIRY; если 1 – то хост запрашивает специфическую информацию, которую можно определить по полю PAGE CODE.
Мы будем отвечать только на запрос вида: 12 00 00 00 24 00 (EVPD и CMDDT равны 0), иначе будем отвечать ошибкой в CSW.
Стандартный ответ на INQUIRY имеет следующий вид:
Байт | Значение | Описание |
0 | 00 | Блочное устройство прямого доступа |
1 | 80 | Съемный носитель |
2 | 04 | Версия стандарта SPC-2 |
3 | 02 | Формат ответа, должен быть 02 |
4 | 1F | Объём дополнительных данных ответа в байтах. Равен длине ответа минус 4. Для длины 36 следует устанавливать в "0x20" (На самом деле, ещё на единицу меньше - для 36 байт, т.е. без блока дополнительных параметров, длина данных равна "0x1F") |
5 | 00 | |
6 | 00 | |
7 | 00 | |
8-15 | Обозначение производителя. Выдаётся старшим байтом вперёд | |
16-31 | Обозначение изделия. Выдаётся старшим байтом вперёд | |
32-35 | Версия изделия. Выдаётся старшим байтом вперёд |
uint8_t inquiry[36] = { 0x00, //Block device 0x80, //Removable media 0x04, //SPC-2 0x02, //Response data format = 0x02 0x1F, //Additional_length = length - 5 0x00, 0x00, 0x00, 'S', 'O', 'B', ' ', 'i', 'n', 'c', '.', 'M', 'a', 's', 's', ' ', 'S', 't', 'o', 'r', 'a', 'g', 'e', ' ', ' ', ' ', ' ', '0', '0', '0', '1' };
«Скелет» функции обработки команд SCSI, с обработкой команды INQUIRY:
void SCSI_Execute(uint8_t ep_number){ uint32_t i, n; uint32_t status; uint8_t j; //Натягиваем scsi_cbw_t на приемный буфер scsi_cbw_t *cbw = (scsi_cbw_t *)endpoints[ep_number].rx_buf; //Если пакет успешно принят if (endpoints[ep_number].rx_flag){ //Сразу копируем значение dCBWTag в CSW.dCSWTag CSW.dCSWTag = (cbw -> dCBWTagH << 16) | cbw -> dCBWTagL; //Определяем пришедшую команду switch (cbw -> CBWCB[0]){ //Если INQUIRY case INQUIRY: //Проверка битов EVPD и CMDDT if (cbw -> CBWCB[1] == 0){ //Передаем стандартный ответ на INQUIRY EP_Write(ep_number, inquiry, cbw -> CBWCB[4]); //Заполняем поля CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; //Команда выполнилась успешно CSW.bCSWStatus = 0x00; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); } else { //Заполняем поля CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL); //Сообщаем об ошибке выполнения команды CSW.bCSWStatus = 0x01; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); //Подтверждаем CSW.bCSWStatus = 0x00; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); } break; //Неизвестная команда default: //Заполняем поля CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL); //Сообщаем об ошибке выполнения команды CSW.bCSWStatus = 0x01; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); //Подтверждаем CSW.bCSWStatus = 0x00; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); break; } status = USB -> EPnR[ep_number]; status = SET_VALID_RX(status); status = SET_NAK_TX(status); status = KEEP_DTOG_TX(status); status = KEEP_DTOG_RX(status); USB -> EPnR[ep_number] = status; endpoints[ep_number].rx_flag = 0; } }
REQUEST_SENSE
Если хост принял CSW с полем bCSWStatus = 1, он может послать команду REQUEST_SENSE, чтобы запросить пояснительные данные (SENSE DATA).
Вот пояснительные данные, говорящие о неизвестной команде:
uint8_t sense_data[18] = { 0x70, //VALID = 1, RESRONSE_CODE = 0x70 0x00, 0x05, //S_ILLEGAL_REQUEST 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
Добавляем обработку команды REQUEST SENSE:
case REQUEST_SENSE: //Отправляем пояснительные данные EP_Write(ep_number, sense_data, 18); //Заполняем поля CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; //Команда выполнилась успешно CSW.bCSWStatus = 0x00; //Посылаем контейнер состояния EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
READ CAPACITY 10
Используется, для того чтобы определить объем памяти устройства. На этапе пересылки данных устройство возвращает структуру, содержащую логический адрес (LBA) последнего блока на носителе и размер блока в байтах. Отметим, что команда запрашивает логический адрес (LBA) последнего блока, а не количество блоков на носителе. Логический адрес первого блока равен нулю, таким образом, логический адрес последнего блока на единицу меньше количества блоков.
Возвращаемая структура:
uint8_t capacity[8] = { 0x00, 0x00, 0x0F, 0xFF, //Addr last blocks = 2M/512 - 1 0x00, 0x00, 0x02, 0x00 //Size blocks = 512 bytes };
Обработка:
case READ_CAPACITY_10: //Передаем структуру EP_Write(ep_number, capacity, 8); //Заполняем и передаем CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
MODE SENSE 6
Ответ на нее всегда одинаковый:
uint8_t mode_sense_6[4] = { 0x03, 0x00, 0x00, 0x00, };
case MODE_SENSE_6: EP_Write(ep_number, mode_sense_6, 4); CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
READ 10
Здесь нас интересуют биты 2-5 – начальный адрес читаемого блока данных, и биты 7-8 – количество читаемых блоков.
case READ_10: //записываем в I начальный адрес читаемого блока i = ((cbw -> CBWCB[2] << 24) | (cbw -> CBWCB[3] << 16) | (cbw -> CBWCB[4] << 8) | (cbw -> CBWCB[5])); //записываем в n адрес последнего читаемого блока n = i + ((cbw -> CBWCB[7] << 8) | cbw -> CBWCB[8]); //выполняем чтение и передачу блоков for ( ; i < n; i++){ //Читаем блок из FLASH, помещаем в массив uint8_t buf[512] AT45DB161_Read_Data(i, 0, 512, buf); //Так как размер конечной точки 64 байта, передаем 512 байт за 8 раз for (j = 0; j < 8; j++){ //Передаем часть буфера EP_Write(ep_number, (uint8_t *)&buf[64*j], 64); } } //Заполняем и посылаем CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
WRITE 10
Здесь нас интересуют биты 2-5 – начальный адрес записываемого блока данных, и биты 7-8 – количество записываемых блоков.
case WRITE_10: //записываем в I начальный адрес записываемого блока i = ((cbw -> CBWCB[2] << 24) | (cbw -> CBWCB[3] << 16) | (cbw -> CBWCB[4] << 8) | (cbw -> CBWCB[5])); //записываем в n адрес последнего записываемого блока n = i + ((cbw -> CBWCB[7] << 8) | cbw -> CBWCB[8]); //выполняем чтение и запись блоков for ( ; i < n; i++){ //Так как размер конечной точки 64 байта, читаем 512 байт за 8 раз for (j = 0; j < 8; j++){ EP_Read(ep_number, (uint8_t *)&buf[64*j]); } //Записываем прочитанный блок во FLASH AT45DB161_PageProgram(i, buf, 512); } //Заполняем и посылаем CSW CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL) - cbw -> CBWCB[4]; CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
TEST UNIT READY
Команда "TEST UNIT READY" используется хостом для выяснения степени готовности устройства к работе. Команда не предусматривает этапа пересылки данных. Если устройство готово, то оно возвращает контейнер состояния CSW со значением поля "bCSWStatus" равным "0x00". Если носитель не готов, устройство обновляет подробные данные о состоянии и возвращает контейнер состояния (CSW) со значением поля "bCSWStatus" равным "0x01" (ошибка исполнения). Хост может запросить подробную информацию о состоянии командой "REQUEST SENSE". Все блоковые устройства (SBC) должны поддерживать эту команду.
case TEST_UNIT_READY: CSW.dCSWDataResidue = ((cbw -> dCBWDataTransferLengthH << 16) | cbw -> dCBWDataTransferLengthL); CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
PREVENT ALLOW MEDIUM REMOVAL
Команда "PREVENT ALLOW MEDIUM REMOVAL" разрешает или запрещает извлечение носителя из устройства. 2-х битовое поле "PREVENT" команды устанавливается в состояние "00b" для разрешения или в состояние "01b" для запрета извлечения. Данная команда не подразумевает этап пересылки данных.
Так как у нас нет съемных носителей, то отвечаем успешным пакетом CSW.
case PREVENT_ALLOW_MEDIUM_REMOVAL: CSW.dCSWDataResidue = 0; CSW.bCSWStatus = 0x00; EP_Write(ep_number, (uint8_t *)&CSW, 13); break;
Заключение
Теперь если прошить микроконтроллер, то должно появиться запоминающее устройство. Диск может не открыться, так как на флешке наверняка нет файловой системы, поэтому отформатируйте диск и пользуйтесь. Надеюсь эти статьи помогли разобраться с USB. Как-нибудь напишу про HID, там все проще.
Прикрепленные файлы:
- stm32f0_usb_msd.rar (119 Кб)
Комментарии (5) | Я собрал (0) | Подписаться
Для добавления Вашей сборки необходима регистрация
Есть проблема с пониманием
В SCSI_Exec Вы читаете и записываете в одну точку? Когда хост что-то присылает, срабатывает прерывание по первой точке (bulk out), все, что получено пишу в массив и отправляю в функцию Scsi_Exec, а в ней опять
EP_Read(ep_number, (uint8_t *)&buf[64*j]);
[Автор]
Я пытаюсь использовать внутреннюю память контроллера как флешку (использую часть памяти sam4sd32b, размером 1Мб страница размером 512 байт. Нужно будет записать файл размером 300Кб). Надеюсь провести хотя бы форматирование. Хост присылает запрос WRITE_10 i=0 n=1 - происходит запись в нулевую страницу памяти, после заполняется структура csw. Но при передаче csw хосту программа зависает, так и не дожидаясь уведомлении о прочтении UDP_CSR_TXCOMP. В чем может быть проблема?
[Автор]
В scsi происходит запрос TEST_UNIT_READY и дальше ни чего не происходит.