Back to Home

USB mass storage device and libopencm3

libopencm3 · stm32 · microcontrollers

USB mass storage device and libopencm3

    image




    My work is related to the programming of microcontrollers, in particular STM32. For a long time I used the STM32 Standard Peripheral Library to work with peripherals, as it is provided by the manufacturer and, accordingly, is the most complete. However, working with it is extremely inconvenient: initializing structures are often redundant, they will break a leg in functions, in general, an irresistible desire to get down from this library and go to something more neat, correctly designed and written with “clean code” very soon appears.

    After a long search, the open source libopencm3 library was found, which met all the requirements. Reviews about her were positive and working with her was as pleasant as possible.

    One of the last tasks at work was to raise the USB MSD. To solve the problem, we used the STM32F4-discovery debug board and this example . An example did not start. There were two problems:
    1. It was impossible to go to disk and read the file there.
    2. Recognizing the device as a disk took more than 2 minutes.

    All this was due to the presence of several bugs in the usb_msc.c file . Thus, in this article I will talk about how to fix these errors and continue to use the libopencm3 library with pleasure.


    Solution to Problem # 1:
    The essence of the error is that when the device receives a write request, it processes it correctly, but does not send back the status of the CSW request processing (Command Status Wrapper). Thus, the usb host (in our case, this is our PC) goes into endless waiting for a response to the request, everything hangs, glitches, dies until you disconnect the device =)

    * Read more about Mass Storage Bulk-Only or CBI Transport Specification is available here .

    Therefore, we find the function msc_data_rx_cb in the usb_msc.c file and bring it to the following form:
    The added piece of code is between slashes
    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;
    		}
    	}
    }
    



    Hooray, now we can go to disk to read the file!

    Solution to problem # 2:
    The essence of this problem is that two SCSI commands are not implemented in the same usb_msc.c file. This was revealed using a very useful usblyser program , which allows you to conveniently view the exchange of parcels between a usb device and a usb host.

    So, firstly, the host does not receive a response to the READ_FORMAT_CAPACITIES command. Therefore, we add the scsi_read_format_capacities function to the usb_msc.c file and bring the scsi_command function to the following form:
    View code
    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;
    	}
    }
    



    Secondly, the host does not receive a response to the INQUIRY (SERIAL NUMBER) command. To fix this error, you need to create the _spc3_inquiry_sn_response array and set the scsi_inquiry function to the following form:
    View code
    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 */
    		}
    	}
    }
    



    * More details about SCSI commands can be found here again .

    After all these surgical interventions, the library needs to be rebuilt by first executing the make clean command.

    In the near future I plan to make a pull request to the repository with libopencm3, however, I can only assume how soon the owners will make these changes to the library, and in the meantime we all need to work here and now.

    I really hope that at least someone this article will relieve unnecessary headaches and will be useful.

    PS: Here lies our corporate fork library with the changes already made, if you lazily dig yourself.

    Read Next