Реклама ⓘ
Главная » Микроконтроллеры
Призовой фонд
на апрель 2024 г.
1. 100 руб.
От пользователей

Реклама ⓘ

USB FLASH. Часть 3 - Заключительная. Протокол SCSI

В общем случае обмен по протоколу SCSI происходит так: хост посылает командный блок (CBW), далее, исходя из команды, хост может посылать блок данные, хост может принимать данные, или устройство возвращает состояние (CSW).

Процесс обмена по протоколу SCSI
Рисунок 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. Реализуем следующий набор команд:

  1. INQUIRY
  2. REQUEST SENSE
  3. READ CAPACITY(10)
  4. MODE SENSE(6)
  5. READ(10)
  6. WRITE(10)
  7. TEST UNIT READY
  8. 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, там все проще.

Прикрепленные файлы:

Теги:

Опубликована: 0 1
Я собрал 0 3
x

Оценить статью

  • Техническая грамотность
  • Актуальность материала
  • Изложение материала
  • Полезность устройства
  • Повторяемость устройства
  • Орфография
0

Средний балл статьи: 5 Проголосовало: 3 чел.

Комментарии (5) | Я собрал (0) | Подписаться

0
daryaz #
Спасибо за статью! ПК уже опознал моё устройство (делаю на sam4s)
Есть проблема с пониманием

В SCSI_Exec Вы читаете и записываете в одну точку? Когда хост что-то присылает, срабатывает прерывание по первой точке (bulk out), все, что получено пишу в массив и отправляю в функцию Scsi_Exec, а в ней опять
EP_Read(ep_number, (uint8_t *)&buf[64*j]);
Ответить
0

[Автор]
sobs #
Да, читаю и пишу в конечную точку 1.
Ответить
0
darz #
Спасибо за ответ!
Я пытаюсь использовать внутреннюю память контроллера как флешку (использую часть памяти sam4sd32b, размером 1Мб страница размером 512 байт. Нужно будет записать файл размером 300Кб). Надеюсь провести хотя бы форматирование. Хост присылает запрос WRITE_10 i=0 n=1 - происходит запись в нулевую страницу памяти, после заполняется структура csw. Но при передаче csw хосту программа зависает, так и не дожидаясь уведомлении о прочтении UDP_CSR_TXCOMP. В чем может быть проблема?
Ответить
0

[Автор]
sobs #
Так до хоста доходит CSW? Возможно проблема с функцией отправки. Честно давно usb не использовал, уже подзабыл...
Ответить
0
Стас #
Добрый день, переделал проект под F103. В диспетчере устройств появилось запоминающее устройство, но диск не появился.
В scsi происходит запрос TEST_UNIT_READY и дальше ни чего не происходит.
Ответить
Добавить комментарий
Имя:
E-mail:
не публикуется
Текст:
Защита от спама:
В чем измеряется электрическое сопротивление?
Файлы:
 
Для выбора нескольких файлов использйте CTRL

AVR-программатор USB ASP
AVR-программатор USB ASP
Конструктор: DDS генератор сигналов Тестер ESR, полупроводников, резисторов, индуктивностей
вверх