USB mass storage device і libopencm3

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)) {
/* Помилка */
}
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)) {
/* Помилка */
}
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)) {
/* Помилка */
}

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 */
}
}
}



* Більш детально ознайомитися зі SCSI командами можна знову-таки тут.

Після всіх цих хірургічних втручань бібліотеку потрібно перезібрати, попередньо виконавши команду make clean.

Незабаром я планую зробити pull request в репозиторій з libopencm3, однак залишається тільки припускати, як скоро власники внесуть ці зміни в бібліотеку, а працювати тим часом нам всім потрібно тут і зараз.

Дуже сподіваюся, що хоч кого-небудь ця стаття позбавить від зайвого головного болю і буде корисною.

P. S.: Тут лежить наш корпоративний fork бібліотеки із вже внесеними змінами, якщо ліниво копатися самому.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.