USB大容量記憶装置とlibopencm3





画像
















私の仕事は、マイクロコントローラ、特にSTM32のプログラミングに関連しています。 長い間、私はSTM32 Standard Peripheral Libraryを使用して周辺機器を操作しました。これは、製造元から提供されているため、最も完全なものです。 ただし、それを使用することは非常に不便です。初期化構造は多くの場合冗長です。一般に、機能に足を踏み入れてしまいます。



長い検索の後、オープンソースのlibopencm3ライブラリが見つかり、すべての要件を満たしました。 彼女についてのレビューは肯定的であり、彼女と一緒に仕事をすることは可能な限り快適でした。



職場での最後のタスクの1つは、USB MSDを上げることでした。 この問題を解決するために、STM32F4-discoveryデバッグボードとこの例を使用しました。 例は開始されませんでした。 次の2つの問題がありました。

1.ディスクに移動してそこにあるファイルを読み取ることは不可能でした。

2.デバイスをディスクとして認識するのに2分以上かかりました。



これはすべて、 usb_msc.cファイルにいくつかのバグがあるためです。 したがって、この記事では、これらのエラーを修正し、libopencm3ライブラリを引き続き使用する方法についてお話します。





問題#1の解決策:

エラーの本質は、デバイスが書き込み要求を受信すると、それを正しく処理しますが、CSW要求処理のステータス(Command Status Wrapper)を返送しないことです。 したがって、USBホスト(この場合、これはPCです)は、要求に対する応答を無限に待機し、デバイスが切断されるまですべてがハング、グリッチ、停止します)



* Mass Storage Bulk-OnlyまたはCBI Transport Specificationの詳細については、 こちらをご覧ください



したがって、 usb_msc.cファイルでmsc_data_rx_cb関数を見つけて、次の形式にします。

追加されたコードはスラッシュの間にあります
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の解決策:

この問題の本質は、2つのSCSIコマンドが同じusb_msc.cファイルに実装されていないことです。 これは、非常に便利なusblyserプログラムを使用して明らかになりました。これにより、usbデバイスとusbホスト間の小包の交換を簡単に表示できます。



そのため、最初に、ホストはREAD_FORMAT_CAPACITIESコマンドに対する応答を受信しません。 したがって、scsi_read_format_capacities関数をusb_msc.cファイルに追加し、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コマンドを実行してライブラリを再構築する必要があります。



すぐにlibopencm3を使用してリポジトリにプルリクエストを行う予定ですが、所有者がライブラリにこれらの変更を加えるのはいつか、そして今のところ私たち全員がここで作業する必要があります。



少なくともこの記事が不必要な頭痛を和らげ、役に立つことを願っています。



PS: ここに、あなたが怠yourselfに自分自身を掘り下げた場合の、既に行われた変更を含む私たちの企業フォークライブラリがあります。



All Articles