/* Copyright (C) 2015-2016 Andrew J. Kroll and Copyright (C) 2011 Circuits At Home, LTD. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact information ------------------- Circuits At Home, LTD Web : https://www.circuitsathome.com e-mail : support@circuitsathome.com */ #if defined(LOAD_UHS_BULK_STORAGE) && defined(__UHS_BULK_STORAGE_H__) && !defined(UHS_BULK_STORAGE_LOADED) #define UHS_BULK_STORAGE_LOADED // uncomment to get 'printf' console debugging. NOT FOR UNO! //#define DEBUG_PRINTF_EXTRA_HUGE_UHS_BULK_STORAGE #if DEBUG_PRINTF_EXTRA_HUGE #ifdef DEBUG_PRINTF_EXTRA_HUGE_UHS_BULK_STORAGE #define BS_HOST_DEBUG(...) printf(__VA_ARGS__) #else #define BS_HOST_DEBUG(...) VOID0 #endif #else #define BS_HOST_DEBUG(...) VOID0 #endif //////////////////////////////////////////////////////////////////////////////// // Interface code //////////////////////////////////////////////////////////////////////////////// /** * Get the capacity of the media * * @param lun Logical Unit Number * @return media capacity */ uint32_t UHS_NI UHS_Bulk_Storage::GetCapacity(uint8_t lun) { uint32_t v = 0LU; pUsb->DisablePoll(); if(LUNOk[lun]) v = CurrentCapacity[lun]; pUsb->EnablePoll(); return v; } /** * Get the sector (block) size used on the media * * @param lun Logical Unit Number * @return media sector size */ uint16_t UHS_NI UHS_Bulk_Storage::GetSectorSize(uint8_t lun) { uint16_t v = 0U; pUsb->DisablePoll(); if(LUNOk[lun]) v = CurrentSectorSize[lun]; pUsb->EnablePoll(); return v; } /** * Test if LUN is ready for use * * @param lun Logical Unit Number * @return true if LUN is ready for use */ bool UHS_NI UHS_Bulk_Storage::LUNIsGood(uint8_t lun) { bool v; pUsb->DisablePoll(); v = LUNOk[lun]; pUsb->EnablePoll(); return v; } /** * Test if LUN is write protected * * @param lun Logical Unit Number * @return cached status of write protect switch */ bool UHS_NI UHS_Bulk_Storage::WriteProtected(uint8_t lun) { bool v; pUsb->DisablePoll(); v = WriteOk[lun]; pUsb->EnablePoll(); return v; } /** * Wrap and execute a SCSI CDB with length of 6 * * @param cdb CDB to execute * @param buf_size Size of expected transaction * @param buf Buffer * @param dir MASS_CMD_DIR_IN | MASS_CMD_DIR_OUT * @return */ uint8_t UHS_NI UHS_Bulk_Storage::SCSITransaction6(SCSI_CDB6_t *cdb, uint16_t buf_size, void *buf, uint8_t dir) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; pUsb->DisablePoll(); // promote buf_size to 32bits. UHS_BULK_CommandBlockWrapper cbw = UHS_BULK_CommandBlockWrapper(++dCBWTag, (uint32_t)buf_size, cdb, dir); #if 0 // Lets check the CBW here: printf("\r\n"); printf("\r\n"); uint8_t *dump = (uint8_t*)(&cbw); for(int i=0; i<(sizeof (UHS_BULK_CommandBlockWrapper)); i++) { printf("%02.2x ", *dump); dump++; } printf("\r\n"); printf("\r\n"); #endif uint8_t v = (HandleSCSIError(Transaction(&cbw, buf_size, buf))); pUsb->EnablePoll(); return v; } /** * Wrap and execute a SCSI CDB with length of 10 * * @param cdb CDB to execute * @param buf_size Size of expected transaction * @param buf Buffer * @param dir MASS_CMD_DIR_IN | MASS_CMD_DIR_OUT * @return */ uint8_t UHS_NI UHS_Bulk_Storage::SCSITransaction10(SCSI_CDB10_t *cdb, uint16_t buf_size, void *buf, uint8_t dir) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; pUsb->DisablePoll(); // promote buf_size to 32bits. UHS_BULK_CommandBlockWrapper cbw = UHS_BULK_CommandBlockWrapper(++dCBWTag, (uint32_t)buf_size, cdb, dir); //SetCurLUN(cdb->LUN); uint8_t v = (HandleSCSIError(Transaction(&cbw, buf_size, buf))); pUsb->EnablePoll(); return v; } /** * Lock or Unlock the tray or door on device. * Caution: Some devices with buggy firmware will lock up. * * @param lun Logical Unit Number * @param lock 1 to lock, 0 to unlock * @return */ uint8_t UHS_NI UHS_Bulk_Storage::LockMedia(uint8_t lun, uint8_t lock) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; pUsb->DisablePoll(); Notify(PSTR("\r\nLockMedia\r\n"), 0x80); Notify(PSTR("---------\r\n"), 0x80); SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_PREVENT_REMOVAL, lun, (uint8_t)0, lock); uint8_t v = SCSITransaction6(&cdb, (uint16_t)0, NULL, (uint8_t)UHS_BULK_CMD_DIR_IN); pUsb->EnablePoll(); return v; } /** * Media control, for spindle motor and media tray or door. * This includes CDROM, TAPE and anything with a media loader. * * @param lun Logical Unit Number * @param ctl 0x00 Stop Motor, 0x01 Start Motor, 0x02 Eject Media, 0x03 Load Media * @return 0 on success */ uint8_t UHS_NI UHS_Bulk_Storage::MediaCTL(uint8_t lun, uint8_t ctl) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; pUsb->DisablePoll(); Notify(PSTR("\r\nMediaCTL\r\n"), 0x80); Notify(PSTR("-----------------\r\n"), 0x80); uint8_t rcode = UHS_BULK_ERR_UNIT_NOT_READY; if(bAddress) { SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_START_STOP_UNIT, lun, ctl & 0x03, 0); rcode = SCSITransaction6(&cdb, (uint16_t)0, NULL, (uint8_t)UHS_BULK_CMD_DIR_OUT); } else { SetCurLUN(lun); } pUsb->EnablePoll(); return rcode; } /** * Read data from media * * @param lun Logical Unit Number * @param addr LBA address on media to read * @param bsize size of a block (we should probably use the cached size) * @param blocks how many blocks to read * @param buf memory that is able to hold the requested data * @return 0 on success */ uint8_t UHS_NI UHS_Bulk_Storage::Read(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, uint8_t *buf) { if(!bAddress) return UHS_BULK_ERR_NO_MEDIA; uint8_t er = UHS_BULK_ERR_NO_MEDIA; pUsb->DisablePoll(); if(LUNOk[lun]) { Notify(PSTR("\r\nRead LUN:\t"), 0x80); D_PrintHex (lun, 0x90); Notify(PSTR("\r\nLBA:\t\t"), 0x90); D_PrintHex (addr, 0x90); Notify(PSTR("\r\nblocks:\t\t"), 0x90); D_PrintHex (blocks, 0x90); Notify(PSTR("\r\nblock size:\t"), 0x90); D_PrintHex (bsize, 0x90); Notify(PSTR("\r\n---------\r\n"), 0x80); SCSI_CDB10_t cdb = SCSI_CDB10_t(SCSI_CMD_READ_10, lun, blocks, addr); again: er = SCSITransaction10(&cdb, ((uint16_t)bsize * blocks), buf, (uint8_t)UHS_BULK_CMD_DIR_IN); if(er == UHS_BULK_ERR_STALL) { MediaCTL(lun, 1); if(UHS_SLEEP_MS(150)) { if(!TestUnitReady(lun)) goto again; } } } qNextPollTime = millis() + 100; pUsb->EnablePoll(); return er; } /** * Write data to media * * @param lun Logical Unit Number * @param addr LBA address on media to write * @param bsize size of a block (we should probably use the cached size) * @param blocks how many blocks to write * @param buf memory that contains the data to write * @return 0 on success */ uint8_t UHS_NI UHS_Bulk_Storage::Write(uint8_t lun, uint32_t addr, uint16_t bsize, uint8_t blocks, const uint8_t * buf) { if(!bAddress) return UHS_BULK_ERR_NO_MEDIA; uint8_t er = UHS_BULK_ERR_NO_MEDIA; pUsb->DisablePoll(); if(LUNOk[lun]) { if(!WriteOk[lun]) { er = UHS_BULK_ERR_WRITE_PROTECTED; } else { Notify(PSTR("\r\nWrite LUN:\t"), 0x80); D_PrintHex (lun, 0x90); Notify(PSTR("\r\nLBA:\t\t"), 0x90); D_PrintHex (addr, 0x90); Notify(PSTR("\r\nblocks:\t\t"), 0x90); D_PrintHex (blocks, 0x90); Notify(PSTR("\r\nblock size:\t"), 0x90); D_PrintHex (bsize, 0x90); Notify(PSTR("\r\n---------\r\n"), 0x80); SCSI_CDB10_t cdb = SCSI_CDB10_t(SCSI_CMD_WRITE_10, lun, blocks, addr); again: er = SCSITransaction10(&cdb, ((uint16_t)bsize * blocks), (void*)buf, (uint8_t)UHS_BULK_CMD_DIR_OUT); if(er == UHS_BULK_ERR_WRITE_STALL) { MediaCTL(lun, 1); if(UHS_SLEEP_MS(150)) { if(!TestUnitReady(lun)) goto again; } } } } qNextPollTime = millis() + 100; pUsb->EnablePoll(); return er; } // End of user functions, the remaining code below is driver internals. // Only developer serviceable parts below! //////////////////////////////////////////////////////////////////////////////// // Main driver code //////////////////////////////////////////////////////////////////////////////// UHS_NI UHS_Bulk_Storage::UHS_Bulk_Storage(UHS_USB_HOST_BASE *p) { pUsb = p; dCBWTag = 0; if(pUsb) { DriverDefaults(); pUsb->RegisterDeviceClass(this); // Serial.print("Bulk Register to USB Host @ 0x"); // Serial.println((uint32_t)pUsb, HEX); // Serial.print("Bulk Register to USB Host Address Pool @ 0x"); // Serial.println((uint32_t)pUsb->GetAddressPool(), HEX); } } /** * @param ei Enumeration information * @return true if this interface driver can handle this interface description */ bool UHS_NI UHS_Bulk_Storage::OKtoEnumerate(ENUMERATION_INFO *ei) { BS_HOST_DEBUG("BulkOnly: checking numep %i, klass %2.2x, subklass %2.2x\r\n", ei->interface.numep, ei->klass, ei->subklass); BS_HOST_DEBUG("BulkOnly: checking protocol %2.2x, interface.klass %2.2x, interface.subklass %2.2x\r\n", ei->protocol, ei->interface.klass, ei->interface.subklass); BS_HOST_DEBUG("BulkOnly: checking interface.protocol %2.2x\r\n", ei->interface.protocol); // // TO-DO? // Check that we have 2 bulk endpoints, and one in each direction?? // e.g. (ei->interface.numep > 1) && // two or more endpoints AND check types // This will work with proper hardware though. // return ( ((ei->klass == UHS_USB_CLASS_MASS_STORAGE) || (ei->interface.klass == UHS_USB_CLASS_MASS_STORAGE)) && // mass storage class AND ((ei->subklass == UHS_BULK_SUBCLASS_SCSI) || (ei->interface.subklass == UHS_BULK_SUBCLASS_SCSI)) && // SCSI command set AND ((ei->protocol == UHS_STOR_PROTO_BBB) || (ei->interface.protocol == UHS_STOR_PROTO_BBB)) // Bulk Only transport ); } /** * @param ei Enumeration information * @return 0 always */ uint8_t UHS_NI UHS_Bulk_Storage::SetInterface(ENUMERATION_INFO *ei) { uint8_t index; bAddress = ei->address; BS_HOST_DEBUG("BS SetInterface\r\n"); // Fill in the endpoint info structure for(uint8_t ep = 0; ep < ei->interface.numep; ep++) { BS_HOST_DEBUG("ep: 0x%2.2x bmAttributes: 0x%2.2x ", ep, ei->interface.epInfo[ep].bmAttributes); if(ei->interface.epInfo[ep].bmAttributes == USB_TRANSFER_TYPE_BULK) { index = ((ei->interface.epInfo[ep].bEndpointAddress & USB_TRANSFER_DIRECTION_IN) == USB_TRANSFER_DIRECTION_IN) ? epDataInIndex : epDataOutIndex; epInfo[index].epAddr = (ei->interface.epInfo[ep].bEndpointAddress & 0x0F); epInfo[index].maxPktSize = ei->interface.epInfo[ep].wMaxPacketSize; epInfo[index].epAttribs = 0; epInfo[index].bmNakPower = UHS_USB_NAK_MAX_POWER; epInfo[index].bmSndToggle = 0; epInfo[index].bmRcvToggle = 0; epInfo[index].bIface=ei->interface.bInterfaceNumber; BS_HOST_DEBUG("index: %i\r\n", index); } BS_HOST_DEBUG("\r\n"); } bNumEP = 3; epInfo[0].epAddr = 0; epInfo[0].maxPktSize = ei->bMaxPacketSize0; epInfo[0].bmNakPower = UHS_USB_NAK_MAX_POWER; bIface = ei->interface.bInterfaceNumber; return 0; }; /** * @return 0 for success */ uint8_t UHS_NI UHS_Bulk_Storage::Start() { uint8_t rcode; // Serial.print("Bulk Start from USB Host @ 0x"); // Serial.println((uint32_t)pUsb, HEX); // Serial.print("Bulk Start USB Host Address Pool @ 0x"); // Serial.println((uint32_t)pUsb->GetAddressPool(), HEX); BS_HOST_DEBUG("BS Start, speed: %i\r\n", pUsb->GetAddressPool()->GetUsbDevicePtr(bAddress)->speed); BS_HOST_DEBUG("BS Start\r\n"); rcode = pUsb->setEpInfoEntry(bAddress, bIface, 3, epInfo); // Serial.println(rcode,HEX); if(rcode) goto FailOnInit; // Do a 1 second delay before LUN query if(!UHS_SLEEP_MS(1000)) goto FailUnPlug; rcode = GetMaxLUN(&bMaxLUN); BS_HOST_DEBUG("GetMaxLUN 0x%2.2x\r\n", rcode); if(rcode) { goto FailGetMaxLUN; } if(bMaxLUN >= MASS_MAX_SUPPORTED_LUN) bMaxLUN = MASS_MAX_SUPPORTED_LUN - 1; BS_HOST_DEBUG("MaxLUN %u\r\n", bMaxLUN); //ErrorMessage (PSTR("MaxLUN"), bMaxLUN); if(!UHS_SLEEP_MS(150)) goto FailUnPlug; // Delay a bit for slow firmware. (again) for(uint8_t lun = 0; lun <= bMaxLUN; lun++) { if(!UHS_SLEEP_MS(3)) goto FailUnPlug; SCSI_Inquiry_Response response; rcode = Inquiry(lun, sizeof (SCSI_Inquiry_Response), (uint8_t*) & response); BS_HOST_DEBUG("Inquiry 0x%2.2x 0x%2.2x\r\n", sizeof (SCSI_Inquiry_Response), rcode); if(rcode) { goto FailInquiry; #if 0 } else { BS_HOST_DEBUG("LUN %i `", lun); uint8_t *buf = response.VendorID; for(int i = 0; i < 28; i++) BS_HOST_DEBUG("%c", buf[i]); BS_HOST_DEBUG("'\r\nQualifier %1.1X ", response.PeripheralQualifier); BS_HOST_DEBUG("Device type %2.2X ", response.DeviceType); BS_HOST_DEBUG("RMB %1.1X ", response.Removable); BS_HOST_DEBUG("SSCS %1.1X ", response.SCCS); uint8_t sv = response.Version; BS_HOST_DEBUG("SCSI version %2.2X\r\nDevice conforms to ", sv); switch(sv) { case 0: BS_HOST_DEBUG("No specific"); break; case 1: BS_HOST_DEBUG("ANSI X3.131-1986 (ANSI 1)"); break; case 2: BS_HOST_DEBUG("ANSI X3.131-1994 (ANSI 2)"); break; case 3: BS_HOST_DEBUG("ANSI INCITS 301-1997 (SPC)"); break; case 4: BS_HOST_DEBUG("ANSI INCITS 351-2001 (SPC-2)"); break; case 5: BS_HOST_DEBUG("ANSI INCITS 408-2005 (SPC-4)"); break; case 6: BS_HOST_DEBUG("T10/1731-D (SPC-4)"); break; default: BS_HOST_DEBUG("unknown"); } BS_HOST_DEBUG(" standards.\r\n"); #endif } } for(uint8_t lun = 0; lun <= bMaxLUN; lun++) { if(!UHS_SLEEP_MS(3)) goto FailUnPlug; #ifndef USB_NO_TEST_UNIT_READY uint8_t tries = 0xF0; while((rcode = TestUnitReady(lun))) { BS_HOST_DEBUG("\r\nTry %2.2x TestUnitReady %2.2x\r\n", tries - 0xF0, rcode); if(rcode == 0x08) break; // break on no media, this is OK to do. if(rcode == UHS_BULK_ERR_DEVICE_DISCONNECTED) goto FailUnPlug; if(rcode == UHS_BULK_ERR_INVALID_CSW) goto Fail; if(rcode != UHS_BULK_ERR_MEDIA_CHANGED) goto Fail; if(!UHS_SLEEP_MS(2 * (tries + 1))) goto FailUnPlug; tries++; if(!tries) break; } #else // Don't wait for the LUN to become ready, as this will // trigger Marlin's watchdog timer rcode = -1; #endif if(!UHS_SLEEP_MS(3)) goto FailUnPlug; LockMedia(lun, 1); if(rcode == 0x08) { if(!UHS_SLEEP_MS(3)) goto FailUnPlug; if(MediaCTL(lun, 1) == UHS_BULK_ERR_DEVICE_DISCONNECTED) goto FailUnPlug; // I actually have a USB stick that needs this! } BS_HOST_DEBUG("\r\nTry %2.2x TestUnitReady %2.2x\r\n", tries - 0xF0, rcode); if(!rcode) { if(!UHS_SLEEP_MS(3)) goto FailUnPlug; BS_HOST_DEBUG("CheckLUN...\r\n"); BS_HOST_DEBUG("%lu\r\n", millis()/1000); // Stalls on ***some*** devices, ***WHY***?! Device SAID it is READY!! LUNOk[lun] = CheckLUN(lun); BS_HOST_DEBUG("%lu\r\n", millis()/1000); if(!LUNOk[lun]) LUNOk[lun] = CheckLUN(lun); if(!UHS_SLEEP_MS(1)) goto FailUnPlug; BS_HOST_DEBUG("Checked LUN...\r\n"); } else { LUNOk[lun] = false; } } rcode = OnStart(); if(rcode) goto FailOnInit; #ifdef DEBUG_USB_HOST USBTRACE("BS configured\r\n\r\n"); #endif qNextPollTime = millis() + 100; bPollEnable = true; return 0; FailUnPlug: rcode = UHS_BULK_ERR_DEVICE_DISCONNECTED; goto Fail; FailOnInit: #ifdef DEBUG_USB_HOST USBTRACE("OnStart:"); goto Fail; #endif FailGetMaxLUN: #ifdef DEBUG_USB_HOST USBTRACE("GetMaxLUN:"); goto Fail; #endif FailInquiry: #ifdef DEBUG_USB_HOST USBTRACE("Inquiry:"); #endif Fail: #ifdef DEBUG_USB_HOST NotifyFail(rcode); #endif Release(); return rcode; } // Base class definition of Release() used. See UHS_USBInterface class definition for details /** * For driver use only. * * @return */ //void UHS_NI UHS_Bulk_Storage::Release() { // pUsb->DisablePoll(); // OnRelease(); // DriverDefaults(); // pUsb->EnablePoll(); // return; //} /** * For driver use only. * * @param lun Logical Unit Number * @return true if LUN is ready for use. */ bool UHS_NI UHS_Bulk_Storage::CheckLUN(uint8_t lun) { uint8_t rcode; SCSI_Capacity capacity; for(uint8_t i = 0; i < 8; i++) capacity.data[i] = 0; rcode = ReadCapacity10(lun, (uint8_t*)capacity.data); if(rcode) { BS_HOST_DEBUG(">>>>>>>>>>>>>>>>ReadCapacity returned %i\r\n", rcode); return false; } #ifdef DEBUG_USB_HOST ErrorMessage (PSTR(">>>>>>>>>>>>>>>>CAPACITY OK ON LUN"), lun); for(uint8_t i = 0; i < 8 /*sizeof (Capacity)*/; i++) D_PrintHex (capacity.data[i], 0x80); Notify(PSTR("\r\n\r\n"), 0x80); #endif // Only 512/1024/2048/4096 are valid values! uint32_t c = UHS_BYTES_TO_UINT32(capacity.data[4], capacity.data[5], capacity.data[6], capacity.data[7]); if(c != 0x0200LU && c != 0x0400LU && c != 0x0800LU && c != 0x1000LU) { return false; } // Store capacity information. CurrentSectorSize[lun] = (uint16_t)(c); // & 0xFFFF); CurrentCapacity[lun] = UHS_BYTES_TO_UINT32(capacity.data[0], capacity.data[1], capacity.data[2], capacity.data[3]) + 1; if(CurrentCapacity[lun] == /*0xffffffffLU */ 0x01LU || CurrentCapacity[lun] == 0x00LU) { // Buggy firmware will report 0xFFFFFFFF or 0 for no media #ifdef DEBUG_USB_HOST if(CurrentCapacity[lun]) ErrorMessage (PSTR(">>>>>>>>>>>>>>>>BUGGY FIRMWARE. CAPACITY FAIL ON LUN"), lun); #endif return false; } if(!UHS_SLEEP_MS(20)) return false; #ifndef SKIP_PAGE3F Page3F(lun); #endif if(!TestUnitReady(lun)) return true; return false; } /** * For driver use only. * * Scan for media change on all LUNs */ void UHS_NI UHS_Bulk_Storage::CheckMedia() { if(!bAddress) return; for(uint8_t lun = 0; lun <= bMaxLUN; lun++) { if(TestUnitReady(lun)) { LUNOk[lun] = false; continue; } if(!LUNOk[lun]) LUNOk[lun] = CheckLUN(lun); } #if 0 BS_HOST_DEBUG("}}}}}}}}}}}}}}}}STATUS "); for(uint8_t lun = 0; lun <= bMaxLUN; lun++) { if(LUNOk[lun]) BS_HOST_DEBUG("#"); else BS_HOST_DEBUG("."); } BS_HOST_DEBUG("\r\n"); #endif OnPoll(); qNextPollTime = millis() + 100; } /** * For driver use only. */ void UHS_NI UHS_Bulk_Storage::Poll() { if((long)(millis() - qNextPollTime) >= 0L) { CheckMedia(); } return; } //////////////////////////////////////////////////////////////////////////////// // SCSI code //////////////////////////////////////////////////////////////////////////////// /** * For driver use only. * * @param plun * @return */ uint8_t UHS_NI UHS_Bulk_Storage::GetMaxLUN(uint8_t *plun) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint8_t ret = pUsb->ctrlReq(bAddress, mkSETUP_PKT16(UHS_BULK_bmREQ_IN, UHS_BULK_REQ_GET_MAX_LUN, 0x0000U, bIface, 1), 1, plun); if(ret == UHS_HOST_ERROR_STALL) { *plun = 0; Notify(PSTR("\r\nGetMaxLUN Stalled\r\n"), 0x80); } return 0; } /** * For driver use only. Used during Driver Start * * @param lun Logical Unit Number * @param bsize * @param buf * @return */ uint8_t UHS_NI UHS_Bulk_Storage::Inquiry(uint8_t lun, uint16_t bsize, uint8_t *buf) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; Notify(PSTR("\r\nInquiry\r\n"), 0x80); Notify(PSTR("---------\r\n"), 0x80); SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_INQUIRY, lun, 0LU, (uint8_t)bsize, 0); uint8_t rc = SCSITransaction6(&cdb, bsize, buf, (uint8_t)UHS_BULK_CMD_DIR_IN); return rc; } /** * For driver use only. * * @param lun Logical Unit Number * @return */ uint8_t UHS_NI UHS_Bulk_Storage::TestUnitReady(uint8_t lun) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; //SetCurLUN(lun); if(!bAddress) return UHS_BULK_ERR_UNIT_NOT_READY; Notify(PSTR("\r\nTestUnitReady\r\n"), 0x80); Notify(PSTR("-----------------\r\n"), 0x80); SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_TEST_UNIT_READY, lun, (uint8_t)0, 0); return SCSITransaction6(&cdb, 0, NULL, (uint8_t)UHS_BULK_CMD_DIR_IN); } /** * For driver use only. * * @param lun Logical Unit Number * @param pc * @param page * @param subpage * @param len * @param pbuf * @return */ uint8_t UHS_NI UHS_Bulk_Storage::ModeSense6(uint8_t lun, uint8_t pc, uint8_t page, uint8_t subpage, uint8_t len, uint8_t * pbuf) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; Notify(PSTR("\r\rModeSense\r\n"), 0x80); Notify(PSTR("------------\r\n"), 0x80); SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_MODE_SENSE_6, lun, (uint32_t)((((pc << 6) | page) << 8) | subpage), len, 0); return SCSITransaction6(&cdb, len, pbuf, (uint8_t)UHS_BULK_CMD_DIR_IN); } /** * For driver use only. * * @param lun Logical Unit Number * @param bsize * @param buf * @return */ uint8_t UHS_NI UHS_Bulk_Storage::ReadCapacity10(uint8_t lun, uint8_t *buf) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; Notify(PSTR("\r\nReadCapacity\r\n"), 0x80); Notify(PSTR("---------------\r\n"), 0x80); SCSI_CDB10_t cdb = SCSI_CDB10_t(SCSI_CMD_READ_CAPACITY_10, lun); return SCSITransaction10(&cdb, 8, buf, (uint8_t)UHS_BULK_CMD_DIR_IN); } /** * For driver use only. * * Page 3F contains write protect status. * * @param lun Logical Unit Number to test. * @return Write protect switch status. */ uint8_t UHS_NI UHS_Bulk_Storage::Page3F(uint8_t lun) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint8_t buf[192]; for(int i = 0; i < 192; i++) { buf[i] = 0x00; } WriteOk[lun] = true; uint8_t rc = ModeSense6(lun, 0, 0x3F, 0, 192, buf); if(!rc) { WriteOk[lun] = ((buf[2] & 0x80) == 0); #ifdef DEBUG_USB_HOST Notify(PSTR("Mode Sense: "), 0x80); for(int i = 0; i < 4; i++) { D_PrintHex (buf[i], 0x80); Notify(PSTR(" "), 0x80); } Notify(PSTR("\r\n"), 0x80); #endif } return rc; } /** * For driver use only. * * @param lun Logical Unit Number * @param size * @param buf * @return */ uint8_t UHS_NI UHS_Bulk_Storage::RequestSense(uint8_t lun, uint16_t size, uint8_t *buf) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; pUsb->DisablePoll(); Notify(PSTR("\r\nRequestSense\r\n"), 0x80); Notify(PSTR("----------------\r\n"), 0x80); SCSI_CDB6_t cdb = SCSI_CDB6_t(SCSI_CMD_REQUEST_SENSE, lun, 0LU, (uint8_t)size, 0); UHS_BULK_CommandBlockWrapper cbw = UHS_BULK_CommandBlockWrapper(++dCBWTag, (uint32_t)size, &cdb, (uint8_t)UHS_BULK_CMD_DIR_IN); uint8_t v = Transaction(&cbw, size, buf); pUsb->EnablePoll(); return v; } //////////////////////////////////////////////////////////////////////////////// // USB code //////////////////////////////////////////////////////////////////////////////// /** * For driver use only. * * @param index * @return */ uint8_t UHS_NI UHS_Bulk_Storage::ClearEpHalt(uint8_t index) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint8_t ret = 0; if(index != 0) { uint8_t ep = (index == epDataInIndex) ? (0x80 | epInfo[index].epAddr) : epInfo[index].epAddr; do { ret = pUsb->EPClearHalt(bAddress, ep); if(!UHS_SLEEP_MS(6)) break; } while(ret == 0x01); if(ret) { ErrorMessage (PSTR("ClearEpHalt"), ret); ErrorMessage (PSTR("EP"), ep); epInfo[index].bmSndToggle = 0; epInfo[index].bmRcvToggle = 0; return ret; } else { epInfo[index].bmSndToggle = 0; epInfo[index].bmRcvToggle = 0; } } return ret; } /** * For driver use only. */ void UHS_NI UHS_Bulk_Storage::Reset() { if(!bAddress) return; while(pUsb->ctrlReq(bAddress, mkSETUP_PKT16(UHS_BULK_bmREQ_OUT, UHS_BULK_REQ_BOMSR, 0x0000U, bIface, 0), 0, NULL) == 0x01) { if(!UHS_SLEEP_MS(6)) break; } if(!bAddress) return; UHS_SLEEP_MS(2500); } /** * For driver use only. * * @return 0 if successful */ uint8_t UHS_NI UHS_Bulk_Storage::ResetRecovery() { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; Notify(PSTR("\r\nResetRecovery\r\n"), 0x80); Notify(PSTR("-----------------\r\n"), 0x80); qNextPollTime = millis() + 90000; uint8_t bLastUsbError = UHS_HOST_ERROR_UNPLUGGED; if(UHS_SLEEP_MS(6)) { Reset(); if(UHS_SLEEP_MS(6)) { bLastUsbError = ClearEpHalt(epDataInIndex); if(UHS_SLEEP_MS(6)) { bLastUsbError = ClearEpHalt(epDataOutIndex); UHS_SLEEP_MS(6); } } } return bLastUsbError; } /** * For driver use only. * * Clear all EP data and clear all LUN status */ void UHS_NI UHS_Bulk_Storage::DriverDefaults() { pUsb->DeviceDefaults(MASS_MAX_ENDPOINTS, this); for(uint8_t i = 0; i < MASS_MAX_SUPPORTED_LUN; i++) { LUNOk[i] = false; WriteOk[i] = false; CurrentCapacity[i] = 0lu; CurrentSectorSize[i] = 0; } dCBWTag = 0; bMaxLUN = 0; bTheLUN = 0; } /** * For driver use only. * * @param pcsw * @param pcbw * @return */ bool UHS_NI UHS_Bulk_Storage::IsValidCSW(UHS_BULK_CommandStatusWrapper *pcsw, UHS_BULK_CommandBlockWrapperBase *pcbw) { if(!bAddress) return false; if(pcsw->dCSWSignature != UHS_BULK_CSW_SIGNATURE) { Notify(PSTR("CSW:Sig error\r\n"), 0x80); return false; } if(pcsw->dCSWTag != pcbw->dCBWTag) { Notify(PSTR("CSW:Wrong tag\r\n"), 0x80); ErrorMessage (PSTR("dCSWTag"), pcsw->dCSWTag); ErrorMessage (PSTR("dCBWTag"), pcbw->dCBWTag); return false; } return true; } /** * For driver use only. * * @param error * @param index * @return */ uint8_t UHS_NI UHS_Bulk_Storage::HandleUsbError(uint8_t error, uint8_t index) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint8_t count = 3; while(error && count) { if(error != UHS_HOST_ERROR_NONE) { ErrorMessage (PSTR("USB Error"), error); ErrorMessage (PSTR("Index"), index); } switch(error) { // case UHS_HOST_ERROR_WRONGPID: case UHS_HOST_ERROR_NONE: return UHS_BULK_ERR_SUCCESS; case UHS_HOST_ERROR_BUSY: // SIE is busy, just hang out and try again. return UHS_BULK_ERR_UNIT_BUSY; case UHS_HOST_ERROR_NAK: return UHS_BULK_ERR_UNIT_BUSY; case UHS_HOST_ERROR_UNPLUGGED: case UHS_HOST_ERROR_TIMEOUT: case UHS_HOST_ERROR_JERR: return UHS_BULK_ERR_DEVICE_DISCONNECTED; case UHS_HOST_ERROR_STALL: if(index == 0) return UHS_BULK_ERR_STALL; ClearEpHalt(index); if(index != epDataInIndex) return UHS_BULK_ERR_WRITE_STALL; return UHS_BULK_ERR_STALL; case UHS_HOST_ERROR_TOGERR: // Handle a very super rare corner case, where toggles become de-synched. // I have only ran into one device that has this firmware bug, and this is // the only clean way to get back into sync with the buggy device firmware. // --AJK if(bAddress && bConfNum) { error = pUsb->setConf(bAddress, bConfNum); if(error) break; } return UHS_BULK_ERR_SUCCESS; default: ErrorMessage (PSTR("\r\nUSB"), error); return UHS_BULK_ERR_GENERAL_USB_ERROR; } count--; } // while return ((error && !count) ? UHS_BULK_ERR_GENERAL_USB_ERROR : UHS_BULK_ERR_SUCCESS); } /** * For driver use only. * * @param pcbw * @param buf_size * @param buf * @param flags * @return */ uint8_t UHS_NI UHS_Bulk_Storage::Transaction(UHS_BULK_CommandBlockWrapper *pcbw, uint16_t buf_size, void *buf) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint16_t bytes = buf_size; bool write = (pcbw->bmCBWFlags & UHS_BULK_CMD_DIR_IN) != UHS_BULK_CMD_DIR_IN; uint8_t ret = 0; uint8_t usberr; UHS_BULK_CommandStatusWrapper csw; // up here, we allocate ahead to save cpu cycles. SetCurLUN(pcbw->bmCBWLUN); ErrorMessage (PSTR("CBW.dCBWTag"), pcbw->dCBWTag); while((usberr = pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, sizeof (UHS_BULK_CommandBlockWrapper), (uint8_t*)pcbw)) == UHS_HOST_ERROR_BUSY) { if(!UHS_SLEEP_MS(1)) return UHS_BULK_ERR_DEVICE_DISCONNECTED; } ret = HandleUsbError(usberr, epDataOutIndex); if(ret) { ErrorMessage (PSTR("============================ CBW"), ret); } else { if(bytes) { if(!write) { while((usberr = pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, &bytes, (uint8_t*)buf)) == UHS_HOST_ERROR_BUSY) { if(!UHS_SLEEP_MS(1)) return UHS_BULK_ERR_DEVICE_DISCONNECTED; } ret = HandleUsbError(usberr, epDataInIndex); } else { while((usberr = pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, bytes, (uint8_t*)buf)) == UHS_HOST_ERROR_BUSY) { if(!UHS_SLEEP_MS(1)) return UHS_BULK_ERR_DEVICE_DISCONNECTED; } ret = HandleUsbError(usberr, epDataOutIndex); } if(ret) { ErrorMessage (PSTR("============================ DAT"), ret); } } } { bytes = sizeof (UHS_BULK_CommandStatusWrapper); int tries = 2; while(tries--) { while((usberr = pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, &bytes, (uint8_t*) & csw)) == UHS_HOST_ERROR_BUSY) { if(!UHS_SLEEP_MS(1)) return UHS_BULK_ERR_DEVICE_DISCONNECTED; } if(!usberr) break; if(tries) { if(usberr == UHS_HOST_ERROR_STALL) { ResetRecovery(); } else { ClearEpHalt(epDataInIndex); } } } if(!ret) { Notify(PSTR("CBW:\t\tOK\r\n"), 0x80); Notify(PSTR("Data Stage:\tOK\r\n"), 0x80); } else { // Throw away csw, IT IS NOT OF ANY USE. ResetRecovery(); return ret; } ret = HandleUsbError(usberr, epDataInIndex); if(ret) { ErrorMessage (PSTR("============================ CSW"), ret); } if(usberr == UHS_HOST_ERROR_NONE) { if(IsValidCSW(&csw, pcbw)) { //ErrorMessage (PSTR("CSW.dCBWTag"), csw.dCSWTag); //ErrorMessage (PSTR("bCSWStatus"), csw.bCSWStatus); //ErrorMessage (PSTR("dCSWDataResidue"), csw.dCSWDataResidue); Notify(PSTR("CSW:\t\tOK\r\n\r\n"), 0x80); return csw.bCSWStatus; } else { // NOTE! Sometimes this is caused by the reported residue being wrong. // Get a different device. It isn't compliant, and should have never passed Q&A. // I own one... 05e3:0701 Genesys Logic, Inc. USB 2.0 IDE Adapter. // Other devices that exhibit this behavior exist in the wild too. // Be sure to check quirks in the Linux source code before reporting a bug. --xxxajk Notify(PSTR("Invalid CSW\r\n"), 0x80); Reset(); ResetRecovery(); return UHS_BULK_ERR_INVALID_CSW; } } } return ret; } /** * For driver use only. * * @param lun Logical Unit Number * @return */ uint8_t UHS_NI UHS_Bulk_Storage::SetCurLUN(uint8_t lun) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; if(lun > bMaxLUN) return UHS_BULK_ERR_INVALID_LUN; bTheLUN = lun; return UHS_BULK_ERR_SUCCESS; }; /** * For driver use only. * * @param status * @return */ uint8_t UHS_NI UHS_Bulk_Storage::HandleSCSIError(uint8_t status) { if(!bAddress) return UHS_BULK_ERR_DEVICE_DISCONNECTED; uint8_t ret = 0; switch(status) { case 0: return UHS_BULK_ERR_SUCCESS; case 2: ErrorMessage (PSTR("Phase Error"), status); ErrorMessage (PSTR("LUN"), bTheLUN); ResetRecovery(); return UHS_BULK_ERR_GENERAL_SCSI_ERROR; case 1: ErrorMessage (PSTR("SCSI Error"), status); ErrorMessage (PSTR("LUN"), bTheLUN); SCSI_Request_Sense_Response rsp; ret = RequestSense(bTheLUN, sizeof (SCSI_Request_Sense_Response), (uint8_t*) & rsp); if(ret) { if(ret == UHS_BULK_ERR_DEVICE_DISCONNECTED) return UHS_BULK_ERR_DEVICE_DISCONNECTED; return UHS_BULK_ERR_GENERAL_SCSI_ERROR; } #if ENABLE_UHS_DEBUGGING ErrorMessage (PSTR("Response Code"), rsp.bResponseCode); if(rsp.bResponseCode & 0x80) { Notify(PSTR("Information field: "), 0x80); for(int i = 0; i < 4; i++) { D_PrintHex (rsp.CmdSpecificInformation[i], 0x80); Notify(PSTR(" "), 0x80); } Notify(PSTR("\r\n"), 0x80); } ErrorMessage (PSTR("Sense Key"), rsp.bmSenseKey); ErrorMessage (PSTR("Add Sense Code"), rsp.bAdditionalSenseCode); ErrorMessage (PSTR("Add Sense Qual"), rsp.bAdditionalSenseQualifier); #endif // warning, this is not testing ASQ, only SK and ASC. switch(rsp.bmSenseKey) { case SCSI_S_UNIT_ATTENTION: switch(rsp.bAdditionalSenseCode) { case SCSI_ASC_MEDIA_CHANGED: return UHS_BULK_ERR_MEDIA_CHANGED; default: return UHS_BULK_ERR_UNIT_NOT_READY; } case SCSI_S_NOT_READY: switch(rsp.bAdditionalSenseCode) { case SCSI_ASC_MEDIUM_NOT_PRESENT: return UHS_BULK_ERR_NO_MEDIA; default: return UHS_BULK_ERR_UNIT_NOT_READY; } case SCSI_S_ILLEGAL_REQUEST: switch(rsp.bAdditionalSenseCode) { case SCSI_ASC_LBA_OUT_OF_RANGE: return UHS_BULK_ERR_BAD_LBA; default: return UHS_BULK_ERR_CMD_NOT_SUPPORTED; } default: return UHS_BULK_ERR_GENERAL_SCSI_ERROR; } // case 4: return MASS_ERR_UNIT_BUSY; // Busy means retry later. // case 0x05/0x14: we stalled out // case 0x15/0x16: we naked out. default: ErrorMessage (PSTR("Gen SCSI Err"), status); ErrorMessage (PSTR("LUN"), bTheLUN); return status; } // switch } //////////////////////////////////////////////////////////////////////////////// // Debugging code //////////////////////////////////////////////////////////////////////////////// /** * @param ep_ptr */ void UHS_NI UHS_Bulk_Storage::PrintEndpointDescriptor(const USB_FD_ENDPOINT_DESCRIPTOR * ep_ptr) { Notify(PSTR("Endpoint descriptor:"), 0x80); Notify(PSTR("\r\nLength:\t\t"), 0x80); D_PrintHex (ep_ptr->bLength, 0x80); Notify(PSTR("\r\nType:\t\t"), 0x80); D_PrintHex (ep_ptr->bDescriptorType, 0x80); Notify(PSTR("\r\nAddress:\t"), 0x80); D_PrintHex (ep_ptr->bEndpointAddress, 0x80); Notify(PSTR("\r\nAttributes:\t"), 0x80); D_PrintHex (ep_ptr->bmAttributes, 0x80); Notify(PSTR("\r\nMaxPktSize:\t"), 0x80); D_PrintHex (ep_ptr->wMaxPacketSize, 0x80); Notify(PSTR("\r\nPoll Intrv:\t"), 0x80); D_PrintHex (ep_ptr->bInterval, 0x80); Notify(PSTR("\r\n"), 0x80); } #else #error "Never include UHS_BULK_STORAGE_INLINE.h, include UHS_host.h instead" #endif