Pull to refresh

USB mass storage device и libopencm3

Reading time7 min
Views23K

image




Моя работа связана с программированием микроконтроллеров, в частности STM32. Долгое время для работы с периферией я использовала STM32 Standard Peripheral Library, так как она предоставляется производителем и, соответственно, является наиболее полной. Однако работать с ней крайне неудобно: инициализирующие структуры зачастую избыточны, в функциях черт ногу сломит, в общем, очень скоро появляется непреодолимое желание слезть с этой библиотеки и перейти на что-нибудь более аккуратное, грамотно спроектированное и написанное «чистым кодом».

После долгих поисков была обнаружена open source библиотека libopencm3, которая отвечала всем требованиям. Отзывы о ней были положительные и работать с ней оказалось максимально приятно.

Одной из последних задач на работе было поднять USB MSD. Для решения задачи использовалась отладочная плата STM32F4-discovery и вот этот пример. Пример не завелся. Проблем было две:
1. Было невозможно зайти на диск и прочитать находящийся там файл.
2. Распознавание устройства как дискового занимало более 2-х минут.

Все это было связано с наличием нескольких багов в файле usb_msc.c. Таким образом, в данной статье я расскажу о том, как исправить эти ошибки и продолжать с удовольствием пользоваться библиотекой libopencm3.


Решение проблемы №1:
Суть ошибки в том, что когда устройство получает запрос на запись, оно правильно его обрабатывает, однако не посылает обратно статус обработки запроса CSW (Command Status Wrapper). Таким образом, usb хост (в нашем случае, это наш ПК) уходит в бесконечное ожидание ответа на запрос, все виснет, глючит, умирает до тех пор, пока не отсоединишь устройство=)

* Более подробно ознакомиться с Mass Storage Bulk-Only or CBI Transport Specification можно здесь.

Поэтому находим функцию msc_data_rx_cb в файле usb_msc.c и приводим ее к следующему виду:
Добавленный кусок кода находится между слешами
static void msc_data_rx_cb(usbd_device *usbd_dev, uint8_t ep)
{
	usbd_mass_storage *ms;
	struct usb_msc_trans *trans;
	int len, max_len, left;
	void *p;

	ms = &_mass_storage;
	trans = &ms->trans;

	/* RX only */
	left = sizeof(struct usb_msc_cbw) - trans->cbw_cnt;
	if (0 < left) {
		max_len = MIN(ms->ep_out_size, left);
		p = &trans->cbw.buf[0x1ff & trans->cbw_cnt];
		len = usbd_ep_read_packet(usbd_dev, ep, p, max_len);
		trans->cbw_cnt += len;

		if (sizeof(struct usb_msc_cbw) == trans->cbw_cnt) {
			scsi_command(ms, trans, EVENT_CBW_VALID);
			if (trans->byte_count < trans->bytes_to_read) {
				/* We must wait until there is something to
				 * read again. */
				return;
			}
		}
	}

	if (trans->byte_count < trans->bytes_to_read) {
		if (0 < trans->block_count) {
			if ((0 == trans->byte_count) && (NULL != ms->lock)) {
				(*ms->lock)();
			}
		}

		left = trans->bytes_to_read - trans->byte_count;
		max_len = MIN(ms->ep_out_size, left);
		p = &trans->msd_buf[0x1ff & trans->byte_count];
		len = usbd_ep_read_packet(usbd_dev, ep, p, max_len);
		trans->byte_count += len;

		if (0 < trans->block_count) {
			if (0 == (0x1ff & trans->byte_count)) {
				uint32_t lba;

				lba = trans->lba_start + trans->current_block;
				if (0 != (*ms->write_block)(lba,
							    trans->msd_buf)) {
					/* Error */
				}
				trans->current_block++;
			}
		}
/////////////////////ADD THIS//////////////////////////////////////////////////////////////////////////////////
		if (false == trans->csw_valid) {
			scsi_command(ms, trans, EVENT_NEED_STATUS);
			trans->csw_valid = true;
		}

		left = sizeof(struct usb_msc_csw) - trans->csw_sent;
		if (0 < left) {
			max_len = MIN(ms->ep_out_size, left);
			p = &trans->csw.buf[trans->csw_sent];
			len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p,
					max_len);
			trans->csw_sent += len;
		}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	} else if (trans->byte_count < trans->bytes_to_write) {
		if (0 < trans->block_count) {
			if ((0 == trans->byte_count) && (NULL != ms->lock)) {
				(*ms->lock)();
			}

			if (0 == (0x1ff & trans->byte_count)) {
				uint32_t lba;

				lba = trans->lba_start + trans->current_block;
				if (0 != (*ms->read_block)(lba,
							   trans->msd_buf)) {
					/* Error */
				}
				trans->current_block++;
			}
		}

		left = trans->bytes_to_write - trans->byte_count;
		max_len = MIN(ms->ep_out_size, left);
		p = &trans->msd_buf[0x1ff & trans->byte_count];
		len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p, max_len);
		trans->byte_count += len;
	} else {
		if (0 < trans->block_count) {
			if (trans->current_block == trans->block_count) {
				uint32_t lba;

				lba = trans->lba_start + trans->current_block;
				if (0 != (*ms->write_block)(lba,
							    trans->msd_buf)) {
					/* Error */
				}

				trans->current_block = 0;
				if (NULL != ms->unlock) {
					(*ms->unlock)();
				}
			}
		}
		if (false == trans->csw_valid) {
			scsi_command(ms, trans, EVENT_NEED_STATUS);
			trans->csw_valid = true;
		}

		left = sizeof(struct usb_msc_csw) - trans->csw_sent;
		if (0 < left) {
			max_len = MIN(ms->ep_out_size, left);
			p = &trans->csw.buf[trans->csw_sent];
			len = usbd_ep_write_packet(usbd_dev, ms->ep_in, p,
						   max_len);
			trans->csw_sent += len;
		}
	}
}



Ура, теперь мы можем зайти на диск прочитать файл!

Решение проблемы №2:
Суть этой проблемы в том, что во все том же файле usb_msc.с не реализованы две SCSI команды. Это было выявлено с помощью очень полезной программы usblyser, которая позволяет в удобном виде просматривать обмен посылками между usb устройством и usb хостом.

Итак, во-первых, хост не получает ответа на команду READ_FORMAT_CAPACITIES. Следовательно, добавляем в файл usb_msc.с функцию scsi_read_format_capacities и приводим функцию scsi_command к следующему виду:
Посмотреть код
static void scsi_read_format_capacities(usbd_mass_storage *ms, struct usb_msc_trans *trans, enum trans_event event)
{
	if (EVENT_CBW_VALID == event) {

		trans->msd_buf[3] = 0x08;
		trans->msd_buf[4] = ms->block_count >> 24;
		trans->msd_buf[5] = 0xff & (ms->block_count >> 16);
		trans->msd_buf[6] = 0xff & (ms->block_count >> 8);
		trans->msd_buf[7] = 0xff & ms->block_count;

		trans->msd_buf[8] =  0x02;
		trans->msd_buf[9] = 0;
		trans->msd_buf[10] = 0x02;
		trans->msd_buf[11] = 0;
		trans->bytes_to_write = 9;
		set_sbc_status_good(ms);
	}
}

static void scsi_command(usbd_mass_storage *ms, struct usb_msc_trans *trans, enum trans_event event)
{
	if (EVENT_CBW_VALID == event) {
		/* Setup the default success */
		trans->csw_sent = 0;
		trans->csw.csw.dCSWSignature = CSW_SIGNATURE;
		trans->csw.csw.dCSWTag = trans->cbw.cbw.dCBWTag;
		trans->csw.csw.dCSWDataResidue = 0;
		trans->csw.csw.bCSWStatus = CSW_STATUS_SUCCESS;

		trans->bytes_to_write = 0;
		trans->bytes_to_read = 0;
		trans->byte_count = 0;
	}

	switch (trans->cbw.cbw.CBWCB[0]) {
	case SCSI_TEST_UNIT_READY:
	case SCSI_SEND_DIAGNOSTIC:
		/* Do nothing, just send the success. */
		set_sbc_status_good(ms);
		break;
	case SCSI_FORMAT_UNIT:
		scsi_format_unit(ms, trans, event);
		break;
	case SCSI_REQUEST_SENSE:
		scsi_request_sense(ms, trans, event);
		break;
	case SCSI_MODE_SENSE_6:
		scsi_mode_sense_6(ms, trans, event);
		break;
	case SCSI_READ_6:
		scsi_read_6(ms, trans, event);
		break;
	case SCSI_INQUIRY:
		scsi_inquiry(ms, trans, event);
		break;
	case SCSI_READ_CAPACITY:
		scsi_read_capacity(ms, trans, event);
		break;
	case SCSI_READ_10:
		scsi_read_10(ms, trans, event);
		break;
	case SCSI_WRITE_6:
		scsi_write_6(ms, trans, event);
		break;
	case SCSI_WRITE_10:
		scsi_write_10(ms, trans, event);
		break;
//////////////////ADD THIS///////////////////////////////////////////////////////////////////////////////////////////////////
	case SCSI_READ_FORMAT_CAPACITIES:
		scsi_read_format_capacities(ms, trans, event);
	 	break;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	default:
		set_sbc_status(ms, SBC_SENSE_KEY_ILLEGAL_REQUEST,
					SBC_ASC_INVALID_COMMAND_OPERATION_CODE,
					SBC_ASCQ_NA);

		trans->bytes_to_write = 0;
		trans->bytes_to_read = 0;
		trans->csw.csw.bCSWStatus = CSW_STATUS_FAILED;
		break;
	}
}




Во-вторых, хост не получает ответа на команду INQUIRY (SERIAL NUMBER). Для исправления данной ошибки необходимо создать массив _spc3_inquiry_sn_response и привести функцию scsi_inquiry к следующему виду:
Посмотреть код
static const uint8_t _spc3_inquiry_sn_response[20] = {
		0x00,
 		0x80,
 		0x00,
 		0x10, //количество символов в серийном номере. в нашем случае их 16 (third pin 123456)
 		't','h','i','r','d',' ','p','i','n',' ','1','2','3','4','5','6'
 };

static void scsi_inquiry(usbd_mass_storage *ms, struct usb_msc_trans *trans, enum trans_event event)
{
	if (EVENT_CBW_VALID == event) {
		uint8_t evpd;
		uint8_t *buf;

		buf = get_cbw_buf(trans);
		evpd = 1 & buf[1];

		if (evpd == 0) {
			size_t len;
			trans->bytes_to_write = sizeof(_spc3_inquiry_response);
			memcpy(trans->msd_buf, _spc3_inquiry_response,
			       sizeof(_spc3_inquiry_response));

			len = strlen(ms->vendor_id);
			len = MIN(len, 8);
			memcpy(&trans->msd_buf[8], ms->vendor_id, len);

			len = strlen(ms->product_id);
			len = MIN(len, 16);
			memcpy(&trans->msd_buf[16], ms->product_id, len);

			len = strlen(ms->product_revision_level);
			len = MIN(len, 4);
			memcpy(&trans->msd_buf[32], ms->product_revision_level,
			       len);

			trans->csw.csw.dCSWDataResidue =
				sizeof(_spc3_inquiry_response);

			set_sbc_status_good(ms);
		}
/////////////////////////////////ADD THIS/////////////////////////////////////////////////////////////////////////////
		else if (evpd == 1) {
			trans->bytes_to_write = sizeof(_spc3_inquiry_sn_response);
			memcpy(trans->msd_buf, _spc3_inquiry_sn_response,
					sizeof(_spc3_inquiry_sn_response));
			trans->csw.csw.dCSWDataResidue =
					sizeof(_spc3_inquiry_sn_response);

			set_sbc_status_good(ms);
		}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		else {
			/* TODO: Add VPD 0x83 support */
			/* TODO: Add VPD 0x00 support */
		}
	}
}



* Более подробно ознакомиться сo SCSI командами можно опять-таки здесь.

После всех этих хирургических вмешательств библиотеку нужно пересобрать, предварительно выполнив команду make clean.

В скором времени я планирую сделать pull request в репозиторий с libopencm3, однако остается только предполагать, как скоро владельцы внесут эти изменения в библиотеку, а работать тем временем нам всем нужно здесь и сейчас.

Очень надеюсь, что хоть кого-нибудь эта статья избавит от лишней головной боли и будет полезной.

P.S.: Здесь лежит наш корпоративный fork библиотеки с уже внесенными изменениями, если лениво копаться самому.
Tags:
Hubs:
+26
Comments21

Articles