/* MSC Teensy36 USB Host Mass Storage library * Copyright (c) 2017-2019 Warren Watson. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ //MassStorageDriver.cpp #include #include "USBHost_t36.h" // Read this header first for key info #define print USBHost::print_ #define println USBHost::println_ // Uncomment this to display function usage and sequencing. //#define DBGprint 1 // Big Endian/Little Endian #define swap32(x) ((x >> 24) & 0xff) | \ ((x << 8) & 0xff0000) | \ ((x >> 8) & 0xff00) | \ ((x << 24) & 0xff000000) void msController::init() { contribute_Pipes(mypipes, sizeof(mypipes)/sizeof(Pipe_t)); contribute_Transfers(mytransfers, sizeof(mytransfers)/sizeof(Transfer_t)); contribute_String_Buffers(mystring_bufs, sizeof(mystring_bufs)/sizeof(strbuf_t)); driver_ready_for_device(this); } bool msController::claim(Device_t *dev, int type, const uint8_t *descriptors, uint32_t len) { println("msController claim this=", (uint32_t)this, HEX); // only claim at interface level if (type != 1) return false; if (len < 9+7+7) return false; // Interface descriptor + 2 endpoint decriptors print_hexbytes(descriptors, len); uint32_t numendpoint = descriptors[4]; if (numendpoint < 1) return false; if (descriptors[5] != 8) return false; // bInterfaceClass, 8 = MASS Storage class if (descriptors[6] != 6) return false; // bInterfaceSubClass, 6 = SCSI transparent command set (SCSI Standards) if (descriptors[7] != 80) return false; // bInterfaceProtocol, 80 = BULK-ONLY TRANSPORT uint8_t desc_index = 9; uint8_t in_index = 0xff, out_index = 0xff; println("numendpoint=", numendpoint, HEX); while (numendpoint--) { if ((descriptors[desc_index] != 7) || (descriptors[desc_index+1] != 5)) return false; // not an end point if (descriptors[desc_index+3] == 2) { // Bulk end point if (descriptors[desc_index+2] & 0x80) in_index = desc_index; else out_index = desc_index; } desc_index += 7; // point to next one... } if ((in_index == 0xff) || (out_index == 0xff)) return false; // did not find end point endpointIn = descriptors[in_index+2]; // bulk-in descriptor 1 81h endpointOut = descriptors[out_index+2]; // bulk-out descriptor 2 02h println("endpointIn=", endpointIn, HEX); println("endpointOut=", endpointOut, HEX); uint32_t sizeIn = descriptors[in_index+4] | (descriptors[in_index+5] << 8); println("packet size in (msController) = ", sizeIn); uint32_t sizeOut = descriptors[out_index+4] | (descriptors[out_index+5] << 8); println("packet size out (msController) = ", sizeOut); packetSizeIn = sizeIn; packetSizeOut = sizeOut; uint32_t intervalIn = descriptors[in_index+6]; uint32_t intervalOut = descriptors[out_index+6]; println("polling intervalIn = ", intervalIn); println("polling intervalOut = ", intervalOut); datapipeIn = new_Pipe(dev, 2, endpointIn, 1, packetSizeIn, intervalIn); datapipeOut = new_Pipe(dev, 2, endpointOut, 0, packetSizeOut, intervalOut); datapipeIn->callback_function = callbackIn; datapipeOut->callback_function = callbackOut; idVendor = dev->idVendor; idProduct = dev->idProduct; hubNumber = dev->hub_address; deviceAddress = dev->address; hubPort = dev->hub_port; // Used for device ID with multiple drives. msOutCompleted = false; msInCompleted = false; msControlCompleted = false; deviceAvailable = true; msDriveInfo.initialized = false; msDriveInfo.connected = true; #ifdef DBGprint Serial.printf(" connected %d\n",msDriveInfo.connected); Serial.printf(" initialized %d\n",msDriveInfo.initialized); #endif return true; } void msController::disconnect() { deviceAvailable = false; println("Device Disconnected..."); msDriveInfo.connected = false; msDriveInfo.initialized = false; memset(&msDriveInfo, 0, sizeof(msDriveInfo_t)); #ifdef DBGprint Serial.printf(" connected %d\n",msDriveInfo.connected); Serial.printf(" initialized %d\n",msDriveInfo.initialized); #endif } void msController::control(const Transfer_t *transfer) { println("control CallbackIn (msController)"); print_hexbytes(report, 8); msControlCompleted = true; } void msController::callbackIn(const Transfer_t *transfer) { println("msController CallbackIn (static)"); if (transfer->driver) { print("transfer->qtd.token = "); println(transfer->qtd.token & 255); ((msController *)(transfer->driver))->new_dataIn(transfer); } } void msController::callbackOut(const Transfer_t *transfer) { println("msController CallbackOut (static)"); if (transfer->driver) { print("transfer->qtd.token = "); println(transfer->qtd.token & 255); ((msController *)(transfer->driver))->new_dataOut(transfer); } } void msController::new_dataOut(const Transfer_t *transfer) { uint32_t len = transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF); println("msController dataOut (static)", len, DEC); print_hexbytes((uint8_t*)transfer->buffer, (len < 32)? len : 32 ); msOutCompleted = true; // Last out transaction is completed. } void msController::new_dataIn(const Transfer_t *transfer) { uint32_t len = transfer->length - ((transfer->qtd.token >> 16) & 0x7FFF); println("msController dataIn (static): ", len, DEC); print_hexbytes((uint8_t*)transfer->buffer, (len < 32)? len : 32 ); msInCompleted = true; // Last in transaction is completed. } // Initialize Mass Storage Device uint8_t msController::mscInit(void) { #ifdef DBGprint Serial.printf("mscIint()\n"); #endif uint8_t msResult = MS_CBW_PASS; CBWTag = 0; uint32_t start = millis(); // Check if device is connected. do { if((millis() - start) >= MSC_CONNECT_TIMEOUT) { return MS_NO_MEDIA_ERR; // Not connected Error. } yield(); } while(!available()); msReset(); delay(500); maxLUN = msGetMaxLun(); // msResult = msReportLUNs(&maxLUN); //Serial.printf("maxLUN = %d\n",maxLUN); // delay(150); //------------------------------------------------------- // msResult = msStartStopUnit(1); msResult = WaitMediaReady(); if(msResult) return msResult; // Retrieve drive information. msDriveInfo.initialized = true; msDriveInfo.hubNumber = getHubNumber(); // Which HUB. msDriveInfo.hubPort = getHubPort(); // Which HUB port. msDriveInfo.deviceAddress = getDeviceAddress(); // Device addreess. msDriveInfo.idVendor = getIDVendor(); // USB Vendor ID. msDriveInfo.idProduct = getIDProduct(); // USB Product ID. msResult = msDeviceInquiry(&msInquiry); // Config Info. if(msResult) return msResult; msResult = msReadDeviceCapacity(&msCapacity); // Size Info. if(msResult) return msResult; memcpy(&msDriveInfo.inquiry, &msInquiry, sizeof(msInquiryResponse_t)); memcpy(&msDriveInfo.capacity, &msCapacity, sizeof(msSCSICapacity_t)); return msResult; } //--------------------------------------------------------------------------- // Perform Mass Storage Reset void msController::msReset() { #ifdef DBGprint Serial.printf("msReset()\n"); #endif mk_setup(setup, 0x21, 0xff, 0, 0, 0); queue_Control_Transfer(device, &setup, NULL, this); while (!msControlCompleted) yield(); msControlCompleted = false; } //--------------------------------------------------------------------------- // Get MAX LUN uint8_t msController::msGetMaxLun() { #ifdef DBGprint Serial.printf("msGetMaxLun()\n"); #endif report[0] = 0; mk_setup(setup, 0xa1, 0xfe, 0, 0, 1); queue_Control_Transfer(device, &setup, report, this); while (!msControlCompleted) yield(); msControlCompleted = false; maxLUN = report[0]; return maxLUN; } uint8_t msController::WaitMediaReady() { uint8_t msResult; uint32_t start = millis(); #ifdef DBGprint Serial.printf("WaitMediaReady()\n"); #endif do { if((millis() - start) >= MEDIA_READY_TIMEOUT) { return MS_UNIT_NOT_READY; // Not Ready Error. } msResult = msTestReady(); yield(); } while(msResult == 1); return msResult; } // Check if drive is connected and Initialized. uint8_t msController::checkConnectedInitialized(void) { uint8_t msResult = MS_CBW_PASS; #ifdef DBGprint Serial.printf("checkConnectedInitialized()\n"); #endif if(!msDriveInfo.connected) { return MS_NO_MEDIA_ERR; } if(!msDriveInfo.initialized) { msResult = mscInit(); if(msResult != MS_CBW_PASS) return MS_UNIT_NOT_READY; // Not Initialized } return MS_CBW_PASS; } //--------------------------------------------------------------------------- // Send SCSI Command // Do a complete 3 stage transfer. uint8_t msController::msDoCommand(msCommandBlockWrapper_t *CBW, void *buffer) { uint8_t CSWResult = 0; mscTransferComplete = false; #ifdef DBGprint Serial.printf("msDoCommand():\n"); #endif if(CBWTag == 0xFFFFFFFF) CBWTag = 1; queue_Data_Transfer(datapipeOut, CBW, sizeof(msCommandBlockWrapper_t), this); // Command stage. while(!msOutCompleted) yield(); msOutCompleted = false; if((CBW->Flags == CMD_DIR_DATA_IN)) { // Data stage from device. queue_Data_Transfer(datapipeIn, buffer, CBW->TransferLength, this); while(!msInCompleted) yield(); msInCompleted = false; } else { // Data stage to device. queue_Data_Transfer(datapipeOut, buffer, CBW->TransferLength, this); while(!msOutCompleted) yield(); msOutCompleted = false; } CSWResult = msGetCSW(); // Status stage. // All stages of this transfer have completed. //Check for special cases. //If test for unit ready command is given then // return the CSW status byte. //Bit 0 == 1 == not ready else //Bit 0 == 0 == ready. //And the Start/Stop Unit command as well. if((CBW->CommandData[0] == CMD_TEST_UNIT_READY) || (CBW->CommandData[0] == CMD_START_STOP_UNIT)) return CSWResult; else // Process possible SCSI errors. return msProcessError(CSWResult); } //--------------------------------------------------------------------------- // Get Command Status Wrapper uint8_t msController::msGetCSW(void) { #ifdef DBGprint Serial.printf("msGetCSW()\n"); #endif msCommandStatusWrapper_t StatusBlockWrapper = (msCommandStatusWrapper_t) { .Signature = CSW_SIGNATURE, .Tag = 0, .DataResidue = 0, // TODO: Proccess this if received. .Status = 0 }; queue_Data_Transfer(datapipeIn, &StatusBlockWrapper, sizeof(StatusBlockWrapper), this); while(!msInCompleted) yield(); msInCompleted = false; mscTransferComplete = true; if(StatusBlockWrapper.Signature != CSW_SIGNATURE) return msProcessError(MS_CSW_SIG_ERROR); // Signature error if(StatusBlockWrapper.Tag != CBWTag) return msProcessError(MS_CSW_TAG_ERROR); // Tag mismatch error return StatusBlockWrapper.Status; } //--------------------------------------------------------------------------- // Test Unit Ready uint8_t msController::msTestReady() { #ifdef DBGprint Serial.printf("msTestReady()\n"); #endif msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = 0, .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 6, .CommandData = {CMD_TEST_UNIT_READY, 0x00, 0x00, 0x00, 0x00, 0x00} }; queue_Data_Transfer(datapipeOut, &CommandBlockWrapper, sizeof(CommandBlockWrapper), this); while(!msOutCompleted) yield(); msOutCompleted = false; return msGetCSW(); } //--------------------------------------------------------------------------- // Start/Stop unit uint8_t msController::msStartStopUnit(uint8_t mode) { #ifdef DBGprint Serial.printf("msStartStopUnit()\n"); #endif msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = 0, .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 6, .CommandData = {CMD_START_STOP_UNIT, 0x01, 0x00, 0x00, mode, 0x00} }; queue_Data_Transfer(datapipeOut, &CommandBlockWrapper, sizeof(CommandBlockWrapper), this); while(!msOutCompleted) yield(); msOutCompleted = false; return msGetCSW(); } //--------------------------------------------------------------------------- // Read Mass Storage Device Capacity (Number of Blocks and Block Size) uint8_t msController::msReadDeviceCapacity(msSCSICapacity_t * const Capacity) { #ifdef DBGprint Serial.printf("msReadDeviceCapacity()\n"); #endif uint8_t result = 0; msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = sizeof(msSCSICapacity_t), .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 10, .CommandData = {CMD_RD_CAPACITY_10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} }; result = msDoCommand(&CommandBlockWrapper, Capacity); Capacity->Blocks = swap32(Capacity->Blocks); Capacity->BlockSize = swap32(Capacity->BlockSize); return result; } //--------------------------------------------------------------------------- // Do Mass Storage Device Inquiry uint8_t msController::msDeviceInquiry(msInquiryResponse_t * const Inquiry) { #ifdef DBGprint Serial.printf("msDeviceInquiry()\n"); #endif msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = sizeof(msInquiryResponse_t), .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 6, .CommandData = {CMD_INQUIRY,0x00,0x00,0x00,sizeof(msInquiryResponse_t),0x00} }; return msDoCommand(&CommandBlockWrapper, Inquiry); } //--------------------------------------------------------------------------- // Request Sense Data uint8_t msController::msRequestSense(msRequestSenseResponse_t * const Sense) { #ifdef DBGprint Serial.printf("msRequestSense()\n"); #endif msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = sizeof(msRequestSenseResponse_t), .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 6, .CommandData = {CMD_REQUEST_SENSE, 0x00, 0x00, 0x00, sizeof(msRequestSenseResponse_t), 0x00} }; return msDoCommand(&CommandBlockWrapper, Sense); } //--------------------------------------------------------------------------- // Report LUNs uint8_t msController::msReportLUNs(uint8_t *Buffer) { #ifdef DBGprint Serial.printf("msReportLuns()\n"); #endif msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = MAXLUNS, .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 12, .CommandData = {CMD_REPORT_LUNS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, MAXLUNS, 0x00, 0x00} }; return msDoCommand(&CommandBlockWrapper, Buffer); } //--------------------------------------------------------------------------- // Read Sectors (Multi Sector Capable) uint8_t msController::msReadBlocks( const uint32_t BlockAddress, const uint16_t Blocks, const uint16_t BlockSize, void * sectorBuffer) { #ifdef DBGprint Serial.printf("msReadBlocks()\n"); #endif uint8_t BlockHi = (Blocks >> 8) & 0xFF; uint8_t BlockLo = Blocks & 0xFF; msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = (uint32_t)(Blocks * BlockSize), .Flags = CMD_DIR_DATA_IN, .LUN = currentLUN, .CommandLength = 10, .CommandData = {CMD_RD_10, 0x00, (uint8_t)(BlockAddress >> 24), (uint8_t)(BlockAddress >> 16), (uint8_t)(BlockAddress >> 8), (uint8_t)(BlockAddress & 0xFF), 0x00, BlockHi, BlockLo, 0x00} }; return msDoCommand(&CommandBlockWrapper, sectorBuffer); } //--------------------------------------------------------------------------- // Write Sectors (Multi Sector Capable) uint8_t msController::msWriteBlocks( const uint32_t BlockAddress, const uint16_t Blocks, const uint16_t BlockSize, const void * sectorBuffer) { #ifdef DBGprint Serial.printf("msWriteBlocks()\n"); #endif uint8_t BlockHi = (Blocks >> 8) & 0xFF; uint8_t BlockLo = Blocks & 0xFF; msCommandBlockWrapper_t CommandBlockWrapper = (msCommandBlockWrapper_t) { .Signature = CBW_SIGNATURE, .Tag = ++CBWTag, .TransferLength = (uint32_t)(Blocks * BlockSize), .Flags = CMD_DIR_DATA_OUT, .LUN = currentLUN, .CommandLength = 10, .CommandData = {CMD_WR_10, 0x00, (uint8_t)(BlockAddress >> 24), (uint8_t)(BlockAddress >> 16), (uint8_t)(BlockAddress >> 8), (uint8_t)(BlockAddress & 0xFF), 0x00, BlockHi, BlockLo, 0x00} }; return msDoCommand(&CommandBlockWrapper, (void *)sectorBuffer); } // Proccess Possible SCSI errors uint8_t msController::msProcessError(uint8_t msStatus) { #ifdef DBGprint Serial.printf("msProcessError()\n"); #endif uint8_t msResult = 0; switch(msStatus) { case MS_CBW_PASS: return MS_CBW_PASS; break; case MS_CBW_PHASE_ERROR: Serial.printf("SCSI Phase Error: %d\n",msStatus); return MS_SCSI_ERROR; break; case MS_CSW_TAG_ERROR: Serial.printf("CSW Tag Error: %d\n",MS_CSW_TAG_ERROR); return MS_CSW_TAG_ERROR; break; case MS_CSW_SIG_ERROR: Serial.printf("CSW Signature Error: %d\n",MS_CSW_SIG_ERROR); return MS_CSW_SIG_ERROR; break; case MS_CBW_FAIL: msResult = msRequestSense(&msSense); if(msResult) return msResult; switch(msSense.SenseKey) { case MS_UNIT_ATTENTION: switch(msSense.AdditionalSenseCode) { case MS_MEDIA_CHANGED: return MS_MEDIA_CHANGED_ERR; break; default: msStatus = MS_UNIT_NOT_READY; } case MS_NOT_READY: switch(msSense.AdditionalSenseCode) { case MS_MEDIUM_NOT_PRESENT: msStatus = MS_NO_MEDIA_ERR; break; default: msStatus = MS_UNIT_NOT_READY; } case MS_ILLEGAL_REQUEST: switch(msSense.AdditionalSenseCode) { case MS_LBA_OUT_OF_RANGE: msStatus = MS_BAD_LBA_ERR; break; default: msStatus = MS_CMD_ERR; } default: msStatus = MS_SCSI_ERROR; } default: Serial.printf("SCSI Error: %d\n",msStatus); return msStatus; } }