#include #include #include #include #include "mac_scsi.h" #include "tip.h" //#define DEMO /* The original TIP seems to request more data than is supplied by * certain commands. While this appears to be allowed, it causes * SCSI phase errors to be reported. Setting NO_EXCESS_READS will * adjust the reads to to the max size before such errors occur. */ #define NO_EXCESS_READS /* The original TIP will always try to enable Early Recovery. This * fails on certain Jaz drives. While the original TIP will then * retry without Early Recovery, this will cause many errors to be * reported. Enable SUPRESS_ER_ERRORS to prevent this from problem * from happening as frequently */ #define SUPRESS_ER_ERRORS /* The original TIP will always try to read the defects list, but * not all drives support this, causing many errors to be shown. * Setting SUPPRESS_DEFECTS_ERROR will silence these errors. */ #define SUPPRESS_DEFECTS_ERROR #define MAKE_LITTLE_ENDIAN(a) a // Don't do anything on 68000 #define MAKE_BIG_ENDIAN(a) a // Don't do anything on 68000 // The following crashes on the 68000 due to unaligned access //#define BYTE_AT(s, a) *((char*)(s + a)) //#define WORD_AT(s, a) *((short*)(s + a)) //#define DWORD_AT(s, a) *((long*)(s + a)) // Allows unaligned memory access #define SET_BYTE_AT(s, a, v) s[a ] = v; #define SET_WORD_AT(s, a, v) s[a ] = (v & 0xFF00) >> 8; \ s[a+1] = (v & 0x00FF); #define SET_DWORD_AT(s, a, v) s[a ] = (v & 0xFF000000) >> 24; \ s[a+1] = (v & 0x00FF0000) >> 16; \ s[a+2] = (v & 0x0000FF00) >> 8; \ s[a+3] = (v & 0x000000FF); #define GET_BYTE_AT(s, a) ((unsigned char)((unsigned char)s[a ])) #define GET_WORD_AT(s, a) (((unsigned short)((unsigned char)s[a ])) << 8) | \ ((unsigned short)((unsigned char)s[a+1])) #define GET_DWORD_AT(s, a) (((unsigned long)((unsigned char)s[a ])) << 24) | \ (((unsigned long)((unsigned char)s[a+1])) << 16) | \ (((unsigned long)((unsigned char)s[a+2])) << 8) | \ ((unsigned long)((unsigned char)s[a+3])) // offsets to the various sector data images #define ZIP_100_PART 0x0000 #define ZIP_100_BOOT 0x0200 #define ZIP_250_PART 0x0400 #define ZIP_250_BOOT 0x0600 #define JAZ_1GB_PART 0x0800 #define JAZ_1GB_BOOT 0x0A00 #define JAZ_2GB_PART 0x0C00 #define JAZ_2GB_BOOT 0x0E00 struct DEFECT_LIST_HEADER { char DLH_reserved; // (00h) char DLH_BitFlags; // [000] [P] [G] [xxx - defect list format] short DLH_DefectListLength; }; #define ERROR_RECOVERY_PAGE 1 // From disassembly #define FORMAT_STATUS_PAGE 1 #define DISK_STATUS_PAGE 2 #define NEW_DISK_STATUS_OFFSET 3 // newer offset of the Disk Status Byte #define OLD_DISK_STATUS_OFFSET 1 // older offset of the Disk Status Byte #define JAZ_SPARES_COUNT_OFFSET 68 // offsets into DiskStat tbl #define NEW_ZIP_SIDE_0_SPARES_COUNT_OFFSET 13 #define NEW_ZIP_SIDE_1_SPARES_COUNT_OFFSET 17 #define OLD_ZIP_SIDE_0_SPARES_COUNT_OFFSET 11 #define OLD_ZIP_SIDE_1_SPARES_COUNT_OFFSET 15 #define JAZ_PROTECT_MODE_OFFSET 21 #define NEW_ZIP_PROTECT_MODE_OFFSET 21 #define OLD_ZIP_PROTECT_MODE_OFFSET 19 #define JAZ_LAST_LBA_OFFSET 5 #define NEW_ZIP_LAST_LBA_OFFSET 5 #define OLD_ZIP_LAST_LBA_OFFSET 3 #define DRIVE_A_SUPPORT_BIAS 32 // reduce total by 32 for DRIVE A support #define BYTES_PER_SECTOR 512 #define MAX_SECTORS_PER_TEST 128 #define BADNESS_THRESHOLD 10 #define SS_ERR 0x00000004 #define DEFECT_LIST_READ_ERROR 0x001c0003 #define LBA_TOO_LARGE 0x00210005 // accessed a non-exist LBA #define MEDIA_CHANGE_CODE 0x00280006 // media was changed #define INCOMPATIBLE_MEDIA 0x00300002 // 2Gb / 1Gb combo on "Read Defects" #define MEDIA_NOT_PRESENT 0x003a0002 #define DRIVE_COMING_READY 0x00040102 #define SCSI_CMD_TIMED_OUT 0x00FFFF00 #define BUFFER_TOO_BIG 0x00FFFFE6 #define MANUAL_INTERRUPTION 0xFFFFFFFF #define CHECK_CONDITION 0x02 TipPage CurrentPage; long CurrentDevice = -1; // the device that's been recognized long DriveCount = 0; long JazDrive = 0; // true if the current drive long CartridgeStatus = DISK_NOT_PRESENT; #ifdef SUPRESS_ER_ERRORS Boolean SupressEarlyRecovery = false; #endif #ifdef SUPPRESS_DEFECTS_ERROR Boolean SupressDefectsError = false; #endif unsigned long StartingInstant; // ----------------------------- Run Time Variables ------------------------------ long Side_0_SparesCount; // JAZ has only one count long Side_1_SparesCount; // ZIP has counts for both sides long Initial_Side_0_Spares; long Initial_Side_1_Spares; long TestingPhase = 0; // 0 = not testing, no data ... long PercentComplete; long FirstLBASector; long NumberOfLBAs; long AdapterMaxSectors; long LastLBAOnCartridge; long SecondsElapsed; long SoftErrors; long FirmErrors; long HardErrors; long ElapsedTimeOfLastEstimate; long CurrentTotalTimeEstimate; bool UserInterrupt; long LastError; long SingleTransferLBA; DriveEntry DriveArray[MAX_DRIVE_COUNT]; /******************************************************************************* * GET DRIVE ENTRY OFFSET * * Returns the offset of the chosen drive's status word *******************************************************************************/ int GetDriveEntryOffset(short Device) { for(int i = 0; i < MAX_DRIVE_COUNT; i++) if(DriveArray[i].scsi_id == Device) // did we find the right table slot? return i; return 0; } /******************************************************************************* * GET COMMAND DETAILS * * Given a SCSI command byte, this returns the command * block length in AL and the Command Flags in AH *******************************************************************************/ #define TEN_BYTE_CMDS 0x1F #define SRB_DIR_IN SCSI_READ #define SRB_DIR_OUT SCSI_WRITE void GetCommandDetails(char command, char &cmd_flags, char &cmd_length) { char CommandDetailsTable[] = { SCSI_Cmd_RequestSense, SRB_DIR_IN, // 03 IN == get from drive SCSI_Cmd_FormatUnit, 0, // 04 OUT == send to drive SCSI_Cmd_NonSenseData, SRB_DIR_IN, // 06 SCSI_Cmd_Read, SRB_DIR_IN, // 08 SCSI_Cmd_Write, SRB_DIR_OUT, // 0A SCSI_Cmd_CartProtect, SRB_DIR_OUT, // 0C SCSI_Cmd_Inquiry, SRB_DIR_IN, // 12 SCSI_Cmd_ModeSelect, SRB_DIR_OUT, // 15 SCSI_Cmd_ModeSense, SRB_DIR_IN, // 1A SCSI_Cmd_StartStopUnit, 0, // 1B SCSI_Cmd_SendDiagnostic, 0, // 1D SCSI_Cmd_PreventAllow, 0, // 1E SCSI_Cmd_TranslateLBA, SRB_DIR_IN, // 22 SCSI_Cmd_FormatTest, 0, // 24 SCSI_Cmd_ReadMany, SRB_DIR_IN, // 28 SCSI_Cmd_WriteMany, SRB_DIR_OUT, // 2A SCSI_Cmd_Verify, 0, // 2F SCSI_Cmd_ReadDefectData, SRB_DIR_IN, // 37 SCSI_Cmd_ReadLong, SRB_DIR_IN, // 3E SCSI_Cmd_WriteLong, SRB_DIR_OUT // 3F }; cmd_flags = 0; // ; if we don't locate it ... return ZERO // search the table for the command entry for(int i = 0; i < sizeof(CommandDetailsTable); i += 2) { if(CommandDetailsTable[i] == command) { // if we match we're done cmd_flags = CommandDetailsTable[i+1]; break; } } cmd_length = 6; // presume a short (6 byte) command if(command > TEN_BYTE_CMDS) // but if it's a LONG one .... cmd_length = 10; } /******************************************************************************* * SCSI COMMAND * * This executes a SCSI command through the interface. It receives a * pointer to a standard SCSI command block (SCB) and a pointer and * length to an IoBuffer for the command. It returns the complete * three-byte sense code from the command. *******************************************************************************/ long SCSICommand(short Device, char *lpCmdBlk, void *lpIoBuf, size_t IoBufLen) { char cmd_length, cmd_flags, cmd_status; GetCommandDetails(lpCmdBlk[0], cmd_flags, cmd_length); // call the SCSI interface to forward the command to the device OSErr err = scsi_cmd(Device, lpCmdBlk, cmd_length, lpIoBuf, IoBufLen, 0, cmd_flags, &cmd_status); if(err != noErr) { // else, if it's *NOT* a "Sense Data" error (SS_ERR) LastError = err | 0x00FFFF00; // [00 FF FF er] return SS_ERR; } if(cmd_status == 0) { // if the command did not generate any Sense Data, just return NULL return 0; } else if(cmd_status == 2) { // Check Condition // Request sense data scsi_sense_reply sense_data; scsi_request_sense_data(Device, &sense_data); printf("SCSI CHECK CONDITION (KEY %x, ASC %x, ASCQ %x)\n", sense_data.key, sense_data.asc, sense_data.ascq); // okay, we have an SS_ERR condition, let's check the SENSE DATA // assemble [00 ASC ASCQ SenseKey] const long res = (long(sense_data.asc) << 16) | (long(sense_data.ascq) << 8) | (long(sense_data.key) ); if(res == MEDIA_CHANGE_CODE) { printf("Media change signalled. Most recent error can be ignored\n\n"); int index = GetDriveEntryOffset(Device); DriveArray[index].flags |= MEDIA_CHANGED; return 0; } return res; } else { // else, if it's *NOT* a "Sense Data" error (SS_ERR) return cmd_status | 0x00FFFF00; // [00 FF FF er] } } /******************************************************************************* * ENUMERATE IOMEGA DEVICES *******************************************************************************/ short stricmp( const char *str1, const char *str2 ); short stricmp( const char *str1, const char *str2 ) { while (*str1 && *str2) { short cmp = tolower( *str1++ ) - tolower( *str2++ ); if(cmp != 0) return cmp; } return 0; } long EnumerateIomegaDevices(uint8_t *DrivesSkipped) { DriveCount = 0; if(DrivesSkipped) *DrivesSkipped = 0; printf("\nEnumerating Iomega Devices:\n"); // now scan the devices on the SCSI host adapter for(int Device = 0; Device < 8; Device++) { char flags = 0; //----------------------------------------------------------- #ifdef NO_EXCESS_READS scsi_inq_reply reply; if(scsi_inquiry(Device, 0, &reply) != noErr) continue; char *InqData = (char*) &reply; #else char InqData[96]; char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_Inquiry; Scsi[4] = sizeof(InqData); if(SCSICommand(Device, Scsi, InqData, sizeof(InqData))) continue; #endif //----------------------------------------------------------- InqData[14] = '\0'; InqData[19] = '\0'; const bool isIomega = !stricmp(szIomega, InqData + 8); const bool isZip = !stricmp(szZip, InqData + 16); const bool isJaz = !stricmp(szJaz, InqData + 16); //----------------------------------------------------------- if (isIomega && (isZip || isJaz)) { char flags = isJaz ? JAZ_DRIVE : 0; // check for ANSI SCSI to see whether we need to play // the Odd/Even password length game ... if(InqData[2] & 0x07 == 0) { flags |= ODD_BYTE_COMPENSATION; // turn on compensation } // On the Mac, we want to ignore drives that have media in them at // program entry, as this means the volume is mounted in Mac OS const bool driveEmpty = (GetCartridgeStatus(Device, flags) == DISK_NOT_PRESENT); if(driveEmpty) { DriveArray[DriveCount].flags = flags; DriveArray[DriveCount].scsi_id = Device; DriveCount++; } else { if(DrivesSkipped) (*DrivesSkipped)++; } printf(" %d: %s %s %s\n", Device, (flags & JAZ_DRIVE) ? "JAZ" : "ZIP", (flags & ODD_BYTE_COMPENSATION) ? "OBC" : " ", (driveEmpty) ? "EMPTY" : "MEDIA"); } } printf("\n"); return DriveCount; } /******************************************************************************* * GET MODE PAGE *******************************************************************************/ long GetModePage(short Device, short PageToGet, void *pBuffer, short BufLen) { char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_ModeSense; Scsi[2] = PageToGet; Scsi[4] = BufLen; return SCSICommand(Device, Scsi, pBuffer, BufLen); } /******************************************************************************* * SET MODE PAGE *******************************************************************************/ long SetModePage(short Device, void *pBuffer, short BufLen) { unsigned char* ebx = (unsigned char*) pBuffer; // get a pointer to the top of buffer unsigned char ecx = ebx[0] + 1; // adjust it up by one ebx[0] = 0; // now clear the two reserved bytes ebx[2] = 0; if(ecx != BufLen) { printf("Length error in SetModePage %d != %d\n\n", BufLen, (int) ecx); return 0; } char Scsi[6] = {0}; // init the SCSI parameter block Scsi[0] = SCSI_Cmd_ModeSelect; // set the command Scsi[1] = 0x10; // set the Page Format bit Scsi[4] = ecx; // set the parameter list length return SCSICommand(Device, Scsi, pBuffer, ecx); } /******************************************************************************* * SET ERROR RECOVERY *******************************************************************************/ void ModifyModePage(char *PageBuff, char ecc, char retries) { long eax = PageBuff[3]; // get the Block Descriptor Length char *ebx = PageBuff + 4; // get just past the header address // form ebx == the offset to the top of the page we've read ... ebx += eax; ebx[0] &= ~0x80; // always turn off the PS bit (parameters savable) ebx[2] = 0xC0 | ecc; // set the ECC fields ebx[3] = retries; // set the common retry count if(ebx[1] > 6) // if we have a large format page... ebx[8] = retries; // then set the write count too } long SetErrorRecovery(bool Retries, bool ECC, bool Testing) { char PageBuff[40]; #ifdef NO_EXCESS_READS // Limit reads to 20 bytes on Zip (24 bytes on Jaz) to prevent controller errors const short pageBuffLen = JazDrive ? 24 : 20; #else const short pageBuffLen = sizeof(PageBuff); #endif long eax = GetModePage(CurrentDevice, ERROR_RECOVERY_PAGE, PageBuff, pageBuffLen); if(eax) { printf("SetErrorRecovery failed\n"); return eax; } #define EARLY_RECOVERY 0x08 #define PER 0x04 #define SUPPRESS_ECC 0x01 // set the ECC fields char ecc = SUPPRESS_ECC; // presume ECC suppression if(ECC) { #ifdef SUPRESS_ER_ERRORS if(!SupressEarlyRecovery) #endif ecc = EARLY_RECOVERY; // enable ECC and Early Recovery if(Testing) { ecc = EARLY_RECOVERY | PER; // we're testing, so EER & PER } } // set the retry counts char retries = 0x16; // set retries to 22 for Zip drive if(JazDrive) retries = 0x64; // and to 100 for Jaz drive if(!Retries) // But if we have no retries ... retries = 0; ModifyModePage(PageBuff, ecc, retries); eax = SetModePage(CurrentDevice, PageBuff, pageBuffLen); // if we had an invalid field in the CDB (the EER bit was on) if (eax == 0x00260005) { GetModePage(CurrentDevice, ERROR_RECOVERY_PAGE, PageBuff, pageBuffLen); ecc &= ~EARLY_RECOVERY; // same, *BUT*NOT* Early Recovery ModifyModePage(PageBuff, ecc, retries); eax = SetModePage(CurrentDevice, PageBuff, pageBuffLen); #ifdef SUPRESS_ER_ERRORS if(!eax) { printf(" Early recovery not supported on this drive. Ignoring.\n\n"); SupressEarlyRecovery = true; } #endif } return eax; } /******************************************************************************* * GET NON-SENSE PAGE DATA * * Given Adapter, Device, DataPage, and a Buffer to receive the data, this * fills the buffer we're given and returns with the SCSI Completion Code *******************************************************************************/ long GetNonSenseData(short Device, short DataPage, void *Buffer, short BufLen) { char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_NonSenseData; // do a Non-Sense Data Read Scsi[2] = DataPage; // which page to read Scsi[4] = BufLen; // tell drive page is this long return SCSICommand(Device, Scsi, Buffer, BufLen); } /******************************************************************************* * LOCK CURRENT DRIVE *******************************************************************************/ long LockCurrentDrive() { char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_PreventAllow; Scsi[4] = 1; // set to ONE to lock the drive return SCSICommand(CurrentDevice, Scsi, NULL, 0); } /******************************************************************************* * UNLOCK CURRENT DRIVE *******************************************************************************/ long UnlockCurrentDrive() { char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_PreventAllow; return SCSICommand(CurrentDevice, Scsi, NULL, 0); } /******************************************************************************* * UNLOCK ALL MEDIA *******************************************************************************/ void UnlockAllMedia() { // make sure the media is not locked as we exit... char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_PreventAllow; for(int i = 0; i < MAX_DRIVE_COUNT; i++) { SCSICommand(DriveArray[i].scsi_id, Scsi, NULL, 0); } } /******************************************************************************* * SPIN UP IOMEGA CARTRIDGE *******************************************************************************/ long SpinUpIomegaCartridge(short Device) { char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_StartStopUnit; Scsi[1] = 1; // set the IMMED bit for offline Scsi[4] = 1; // start the disk spinning return SCSICommand(Device, Scsi, NULL, 0); } /******************************************************************************* * EJECT ALL MEDIA *******************************************************************************/ void EjectAllMedia() { // setup the SCSI command block for the operation for(int i = 0; i < MAX_DRIVE_COUNT; i++) { EjectIomegaCartridge(DriveArray[i].scsi_id); } } /******************************************************************************* * GET SPARE SECTOR COUNTS * * This returns NON-ZERO if we have trouble and posted the error message * into the RichText control, else it sets the number of spares available *******************************************************************************/ long GetSpareSectorCounts(char checkPassword) { DEFECT_LIST_HEADER DefectHeader; long eax = 0, ebx, edx; short ch, cl; ListChk: // ask for the defect list to make sure we're able to read it char Scsi[10] = {0}; Scsi[0] = SCSI_Cmd_ReadDefectData; Scsi[2] = 0x1e; // 0b00011110 defect format, G/P bits Scsi[8] = 4; // ask for only FOUR bytes #ifdef SUPPRESS_DEFECTS_ERROR if(SupressDefectsError) eax = INCOMPATIBLE_MEDIA; else #endif eax = SCSICommand(CurrentDevice, Scsi, &DefectHeader, sizeof(DefectHeader)); #ifdef SUPPRESS_DEFECTS_ERROR if(!SupressDefectsError && eax == INCOMPATIBLE_MEDIA) { printf("Defects list not supported on this drive. Ignoring.\n\n"); SupressDefectsError = true; } #endif if ((!eax) || (eax == INCOMPATIBLE_MEDIA)) { // we could read its defect list ... so show it! // -------------------------------------------------------------------------- // MLT: looks like on the Iomega Zip 100, the maximum size for DiskStat is 63 // rather than 72; it looks like this code is causing a SCSI transfer error // here... might be better to conditionally check for Jaz drive char DiskStat[72]; #ifdef NO_EXCESS_READS eax = GetNonSenseData(CurrentDevice, DISK_STATUS_PAGE, DiskStat, JazDrive ? sizeof(DiskStat) : 63); if (eax) return eax; #else eax = GetNonSenseData(CurrentDevice, DISK_STATUS_PAGE, DiskStat, sizeof(DiskStat)); if (!eax) return eax; #endif // -------------------------------------------------------------------------- ch = 0; // clear the DRIVE_A_SUPPORT if (JazDrive) { eax = GET_WORD_AT(DiskStat, JAZ_SPARES_COUNT_OFFSET); ebx = 0; cl = GET_BYTE_AT(DiskStat, JAZ_PROTECT_MODE_OFFSET); edx = GET_DWORD_AT(DiskStat, JAZ_LAST_LBA_OFFSET); } else { if (DiskStat[0] == DISK_STATUS_PAGE) { eax = GET_WORD_AT( DiskStat, NEW_ZIP_SIDE_0_SPARES_COUNT_OFFSET); ebx = GET_WORD_AT( DiskStat, NEW_ZIP_SIDE_1_SPARES_COUNT_OFFSET); cl = GET_BYTE_AT( DiskStat, NEW_ZIP_PROTECT_MODE_OFFSET); edx = GET_DWORD_AT( DiskStat, NEW_ZIP_LAST_LBA_OFFSET); ch--; // set the DRIVE_A_SUPPORT } else { eax = GET_WORD_AT( DiskStat, OLD_ZIP_SIDE_0_SPARES_COUNT_OFFSET); ebx = GET_WORD_AT( DiskStat, OLD_ZIP_SIDE_1_SPARES_COUNT_OFFSET); cl = GET_BYTE_AT( DiskStat, OLD_ZIP_PROTECT_MODE_OFFSET); edx = GET_DWORD_AT( DiskStat, OLD_ZIP_LAST_LBA_OFFSET); } if(ebx == 0) { goto NoSpares; } } //--------------------------- // bswap edx; save the last LBA in any event //--------------------------- if(ch) { edx -= DRIVE_A_SUPPORT_BIAS; } LastLBAOnCartridge = edx; MAKE_LITTLE_ENDIAN(eax); // make it little endian Side_0_SparesCount = eax; MAKE_LITTLE_ENDIAN(ebx); // make it little endian Side_1_SparesCount = ebx; // compute the number of troubles we encountered during the testing FirmErrors = Initial_Side_0_Spares - Side_0_SparesCount; FirmErrors += Initial_Side_1_Spares - Side_1_SparesCount; // check to see whether we have ANY spare sectors remaining if(!Side_0_SparesCount && !Side_1_SparesCount) { NoSpares: CartridgeStatus = DISK_TEST_FAILURE; const char *eax = szNoSpares; // if were running give them a different error message if(TestingPhase) { eax = szOutOfSpares; } SetRichEditText(eax); goto ErrorExit; } // MLT: The code for removing the ZIP protection has been omitted return 0; // return zero since no erro } else { // trouble of some sort ... so suppress controls and // show the richedit control for the trouble if (eax == DEFECT_LIST_READ_ERROR) { CartridgeStatus = DISK_Z_TRACK_FAILURE; SetRichEditText(szDefectList); ErrorExit: return -1; } else if (eax == MEDIA_NOT_PRESENT) { CartridgeStatus = MEDIA_NOT_PRESENT; } } return eax; } /******************************************************************************* * HANDLE DRIVE CHANGING * * If we're NOT in the middle of drive testing, check for any new drives * going ready, eject all others, and Select the newer drive. * If we *ARE* in the middle of drive testing, EJECT any new drive that's * attempting to go ready. *******************************************************************************/ void HandleDriveChanging() { bool Selecting = false; // true while we're changing selections uint8_t status; Rescan: for(int i = 0; i < DriveCount; i++) { const int scsi_id = DriveArray[i].scsi_id; // query the current state of the drive do { // clear media changed status DriveArray[i].flags &= ~MEDIA_CHANGED; GetCartridgeStatus(scsi_id, DriveArray[i].flags); } while(DriveArray[i].flags & MEDIA_CHANGED); // do it until NO media change! //-------------------------------------------------------------------------- status = GetCartridgeStatus(scsi_id, DriveArray[i].flags); if (status == DISK_STATUS_UNKNOWN) continue; // added by MLT // if the device we have is NOT the currently selected one if(scsi_id != CurrentDevice) { // if the disk is ANYTHING other than not present ... if(status != DISK_NOT_PRESENT) { // we have a PRESENT DISK in a non-current drive // if we're testing, reject it if (Selecting || TestingPhase >= TESTING_STARTUP) { EjectIomegaCartridge(scsi_id); // flag that we're waiting for spindown DriveArray[i].flags |= DISK_EJECTING; } // if we're not testing, and not awaiting eject // then set the current drive ... else if ((DriveArray[i].flags & DISK_EJECTING) == 0) { CurrentDevice = scsi_id; printf("Selected SCSI ID %ld\n", CurrentDevice); TestingPhase = 0; Selecting = true; //goto Rescan; break; // the PREVIOUS drive (if any) will be ejected on the next pass } } else { // the drive HAS spun down, so clear "waiting" DriveArray[i].flags &= ~DISK_EJECTING; } } else { // we're checking the current drive ... make SURE that // it is *NOT* empty! If it *IS* empty, kill current if(status == DISK_NOT_PRESENT) { CurrentDevice = -1; SetCartridgeStatusToEAX(status, DriveArray[i].flags); } // if it's not already set correctly *and* either // the cart status is one of the pre-test ones, or // the NEW status from the cart is NOT "at speed" ... if((status != CartridgeStatus) && ((CartridgeStatus <= DISK_STALLED) || (status != DISK_AT_SPEED))) { SetCartridgeStatusToEAX(status, DriveArray[i].flags); } } } // if nothing was chosen ... set us to the "Awaiting Cartridge" status if ((CurrentDevice == -1) && (status == DISK_NOT_PRESENT) && (CartridgeStatus != DISK_NOT_PRESENT)) { SetCartridgeStatusToEAX(status, 0); } } //----------------------------------------------------------------------------- // GET CARTRIDGE STATUS //----------------------------------------------------------------------------- uint8_t GetCartridgeStatus(long Device, uint8_t flags) { long eax; char DiskStat[72]; #ifdef NO_EXCESS_READS eax = GetNonSenseData(Device, DISK_STATUS_PAGE, DiskStat, 4); if (eax) return DISK_STATUS_UNKNOWN; #else eax = GetNonSenseData(Device, DISK_STATUS_PAGE, DiskStat, sizeof(DiskStat)); if (!eax) return DISK_STATUS_UNKNOWN; #endif if (DiskStat[0] == DISK_STATUS_PAGE) { return DiskStat[NEW_DISK_STATUS_OFFSET]; } else { return DiskStat[OLD_DISK_STATUS_OFFSET]; } } //----------------------------------------------------------------------------- // SetCartridgeStatusToEAX //----------------------------------------------------------------------------- void SetCartridgeStatusToEAX(long eax, uint8_t flags) { JazDrive = flags & JAZ_DRIVE; long PriorStatus = CartridgeStatus; CartridgeStatus = eax; // Set the text of the "action initiate button" const char *esi = 0; switch (CartridgeStatus) { case DISK_SPUN_DOWN: // set the button to "Start Disk Spinning" esi = szPressToSpin; EnableWindow(hTestButton, true); break; case DISK_TEST_UNDERWAY: // set the button to "Stop Testing" esi = szPressToStop; break; case DISK_NOT_PRESENT: SetRichEditText(szNotRunning); goto DisableActions; case DISK_AT_SPEED: printf("Disk at speed\n"); eax = GetSpareSectorCounts(true); // update the Cart Condition if(eax == MEDIA_NOT_PRESENT) { goto DisableActions; } //TroubleFlag = eax; // decide whether we're in trouble esi = szPressToEject; // presume trouble if(!eax) { Initial_Side_0_Spares = Side_0_SparesCount; Initial_Side_1_Spares = Side_1_SparesCount; FirmErrors = 0; // check to see if we have enough spares to start if(JazDrive) { printf("Spare Sectors: %ld/%d\n", Side_0_SparesCount, MAXIMUM_JAZ_SPARES); if(Side_0_SparesCount < MINIMUM_JAZ_SPARES) goto InsufficientSpares; } else { printf("Spare Sectors:\n"); printf(" Side 1: %ld/%d\n", Side_0_SparesCount, MAXIMUM_ZIP_SPARES); printf(" Side 2: %ld/%d\n", Side_1_SparesCount, MAXIMUM_ZIP_SPARES); if(Side_0_SparesCount < MINIMUM_ZIP_SPARES) { goto InsufficientSpares; } if(Side_1_SparesCount < MINIMUM_ZIP_SPARES) { InsufficientSpares: SetRichEditText(szFewSpares); CartridgeStatus = DISK_LOW_SPARES; esi = szPressToProceed; goto EnableTestBtn; } } // if no trouble, get ready to start testing... PrepareToBeginTesting(); esi = szPressToStart; } // The disk *IS* at speed so enable the action button! EnableTestBtn: EnableWindow(hTestButton, true); esi = szPressToStart; break; default: // set the button to "One Moment Please" DisableActions: EnableWindow(hTestButton, false); esi = szOneMoment; } // set the Window's text based upon setting of esi SetWindowText(hTestButton, esi); // based upon the TroubleFlag, show them the proper page set // SetTabErrorMode(TroubleFlag) // and if CartridgeStatus has changed, refresh the entire panel! bool ecx = false; if((PriorStatus == DISK_AT_SPEED) || (PriorStatus == DISK_SPUN_DOWN)|| (PriorStatus >= DISK_LOW_SPARES)) { ecx = !ecx; } if((CartridgeStatus == DISK_AT_SPEED) || (CartridgeStatus >= DISK_LOW_SPARES)) { ecx = !ecx; } if (ecx) { // update the entire panel's data InvalidateRect(hTestMonitor); } else { // only paint the new cartridge status GetDC(hTestMonitor); PaintCartStatus(); ReleaseDC(hTestMonitor); } } /******************************************************************************* * GET ELAPSED TIME IN SECONDS *******************************************************************************/ long GetElapsedTimeInSeconds() { return GetSystemTime() - StartingInstant; } /******************************************************************************* * PREPARE TO BEGIN TESTING *******************************************************************************/ void PrepareToBeginTesting() { // Zero all of the testing variables TestingPhase = 0; // 0 = not testing, no data ... PercentComplete = 0; FirstLBASector = 0; NumberOfLBAs = 0; SoftErrors = 0; FirmErrors = 0; HardErrors = 0; UserInterrupt = 0; LastError = 0; #ifdef SUPRESS_ER_ERRORS SupressEarlyRecovery = false; #endif #ifdef SUPPRESS_DEFECTS_ERROR SupressDefectsError = false; #endif #ifdef DEMO LastLBAOnCartridge = 99999; SoftErrors = 6; FirmErrors = 2; HardErrors = 1; UserInterrupt = 0; LastError = 0x0C8001; Side_0_SparesCount = 12; Side_1_SparesCount = 20; #endif } /******************************************************************************* * BUMP ERROR COUNTS * * See: https://en.wikipedia.org/wiki/Key_Code_Qualifier *******************************************************************************/ void BumpErrorCounts(long ErrorCode) { long eax = ErrorCode; if (eax == BUFFER_TOO_BIG) { // if we got BUFFER TOO BIG, halt! UserInterrupt = 1; } long ebx = eax & 0x00FF00FF; // mask off the middle byte if (ebx == 0x00150004) // if it was one of the many seek eax = ebx; // errors, cvrt to seek error if (eax) LastError = eax; if (eax == 0x320003 || eax == 0x328F03) CartridgeStatus = DISK_LOW_SPARES; if ((eax & 0xFF) == 1) // recovered error SoftErrors++; else HardErrors++; } /******************************************************************************* * EJECT IOMEGA CARTRIDGE *******************************************************************************/ void EjectIomegaCartridge(int Device) { // Could NOT do it through the IOCTL layer ... so eject with SCSI // make sure the media is not locked... char Scsi[6] = {0}; Scsi[0] = SCSI_Cmd_PreventAllow; SCSICommand(Device, Scsi, 0, 0); // issue an Asynchronous STOP command to induce spindown and ejection memset(Scsi, 0, sizeof(Scsi)); Scsi[0] = SCSI_Cmd_StartStopUnit; Scsi[1] = 1; // Set the IMMED bit for offline Scsi[4] = 2; // eject a Jaz disk after stopping SCSICommand(Device, Scsi, 0, 0); } /******************************************************************************* * PERFORM REGION TRANSFER *******************************************************************************/ long PerformRegionTransfer(short XferCmd, void *pBuffer) { char Scsi[10] = {0}; // clear out the SCSI CDB const long InitialHardErrors = HardErrors; long eax = SetErrorRecovery(false, false, true); // disable Retries & ECC Scsi[0] = XferCmd; SET_DWORD_AT(Scsi, 2, MAKE_BIG_ENDIAN(FirstLBASector)); // WHICH LBA's to read, BIG endian SET_WORD_AT (Scsi, 7, MAKE_BIG_ENDIAN(NumberOfLBAs)); // HOW MANY to read, BIG endian eax = SCSICommand(CurrentDevice, Scsi, pBuffer, NumberOfLBAs * BYTES_PER_SECTOR); // if we failed somewhere during our transfer ... let's zero in on it if (eax) { if ( eax == SS_ERR || // if it's a CONTROLLER ERROR, skip! eax == BUFFER_TOO_BIG || eax == LBA_TOO_LARGE) { goto Exit; } printf("Starting detailed search...\n"); //-------------------------------------------------------------------------- // Save error and current Soft + Hard Error count to see if we do FIND the glitch ... const long GlitchError = eax; // save the error which stopped us! const long GlitchCount = SoftErrors + HardErrors; char *LocalBuffer = (char*) pBuffer; ErrorSound(); SingleTransferLBA = FirstLBASector; // Perform transfer LBA block at a time for(long i = 0; i < NumberOfLBAs; ++i) { UpdateCurrentSector(); // setup for our series of transfer tests ... // disable all recovery techniques SetErrorRecovery(false, false, true); // disable Retries & ECC memset(Scsi, 0, sizeof(Scsi)); // clear out the SCSI CDB Scsi[0] = XferCmd; SET_DWORD_AT(Scsi, 2, MAKE_BIG_ENDIAN(SingleTransferLBA)); // WHICH LBA to read, BIG endian SET_WORD_AT (Scsi, 7, MAKE_BIG_ENDIAN(1)); // a single sector eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); if (eax) { // some sort of problem encountered! if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! if (eax & 0xFF == 1) goto PostTheError; // did we recover? printf(" Found error, retesting with retries\n"); SetErrorRecovery(true, false, true); // enable retries eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); if (eax) { // failed with retries if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! if (eax & 0xFF == 1) goto PostTheError; // did we recover? printf(" Found error, retesting with retries & ECC\n"); eax = SetErrorRecovery(true, true, true); // enable retries AND EEC eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); if (eax) { // failed with retries and EEC if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! if (eax & 0xFF == 1) goto PostTheError; // did we recover? } else { // succeeded with ECC eax = 0x180101; // "ECC & Retries" } } // succeeded with retries else { eax = 0x170101; // "Read with Retries" if (XferCmd == SCSI_Cmd_WriteMany) eax = 0x0C8001; // "Wrote with Retries" } PostTheError: printf(" %s (Sector %ld)\n", FindErrorString(eax), SingleTransferLBA); printf("--------------------------------------------\n"); BumpErrorCounts(eax); // given eax, count the errors GetSpareSectorCounts(false); // update the Cart's Condition UpdateRunTimeDisplay(); } LocalBuffer += BYTES_PER_SECTOR; SingleTransferLBA++; ProcessPendingMessages(); } printf("... detailed search finished\n"); // now see whether we *did* found something to complain about ... eax = SoftErrors + HardErrors; if (eax == GlitchCount) { // we missed it ... but SOMETHING happened! So let's report it ... const long SavedSoftErrors = SoftErrors; // save the existing counts const long SavedHardErrors = HardErrors; eax = GlitchError; // get the error that triggered our search long ebx = eax & 0x00FF00FF; // strip the ASCQ byte if(ebx == 0x00110003) // if we're about to say "unrecovered read" eax = 0x170101; // change it to: "Read with Retries" printf("%s\n", FindErrorString(eax)); BumpErrorCounts(eax); // given eax, count the errors HardErrors = SavedHardErrors; // restore the counts SoftErrors = SavedSoftErrors; SoftErrors++; UpdateRunTimeDisplay(); } SingleTransferLBA = 0; eax = 0; // now let's return happiness to our caller if (HardErrors != InitialHardErrors) // UNRECOVERABLE errors! eax = -1; printf("\n"); } Exit: SetErrorRecovery(true, true, false); // reenable Retries & ECC return eax; } /******************************************************************************* * TEST THE DISK *******************************************************************************/ void TestTheDisk() { // setup the initital maximum tranfer ... AdapterMaxSectors = MAX_SECTORS_PER_TEST; // limit to our max void *pDataBuffer = 0; for(;;) { pDataBuffer = malloc(AdapterMaxSectors * BYTES_PER_SECTOR * 2); if(pDataBuffer) break; AdapterMaxSectors >>= 2; // we need to make it smaller if(AdapterMaxSectors == 0) { printf("Test buffer allocation failed!\n"); return; } } printf("Allocated buffer of %ld bytes\n", AdapterMaxSectors * BYTES_PER_SECTOR); void *pPatternBuffer = pDataBuffer; void *pUserDataBuffer = (char*) pDataBuffer + AdapterMaxSectors * BYTES_PER_SECTOR; StopApplicationTimer(); PreventProgramExit(); SetRichEditText(szRunning); CartridgeStatus = DISK_TEST_UNDERWAY; TestingPhase = TESTING_STARTUP; // inhibit stopping now SetWindowText(hTestButton, szPressToStop); InvalidateRect(hTestMonitor); LockCurrentDrive(); // prevent media removal GetSpareSectorCounts(false); // update the Cart's Condition UpdateRunTimeDisplay(); // Standard Testing Operation StartingInstant = GetSystemTime(); long eax; do { ProcessPendingMessages(); NumberOfLBAs = AdapterMaxSectors; if(LastLBAOnCartridge) { if (FirstLBASector + NumberOfLBAs > LastLBAOnCartridge + 1) { NumberOfLBAs = LastLBAOnCartridge - FirstLBASector + 1; } // compute the percentage complete PercentComplete = FirstLBASector * 100 / LastLBAOnCartridge; } if(NumberOfLBAs == 0) break; // uppdate the elapsed time SecondsElapsed = GetElapsedTimeInSeconds(); // get a random pattern of data to write const long DataPattern = rand(); memset(pPatternBuffer, DataPattern, AdapterMaxSectors * BYTES_PER_SECTOR); // update the cartridge's status GetSpareSectorCounts(false); // update the Cart's Condition TestingPhase = READING_DATA; UpdateRunTimeDisplay(); eax = PerformRegionTransfer(SCSI_Cmd_ReadMany, pUserDataBuffer); if(eax == 0) { // ------------------------------- TestingPhase = WRITING_PATT; UpdateRunPhaseDisplay(); PerformRegionTransfer(SCSI_Cmd_WriteMany, pPatternBuffer); // ------------------------------- TestingPhase = READING_PATT; UpdateRunPhaseDisplay(); PerformRegionTransfer(SCSI_Cmd_Verify, pPatternBuffer); // ------------------------------- TestingPhase = WRITING_DATA; UpdateRunPhaseDisplay(); PerformRegionTransfer(SCSI_Cmd_WriteMany, pUserDataBuffer); } else if (eax == LBA_TOO_LARGE) { // if we hit the end of the disk ... exit gracefully! goto GetOut; } else if (eax == SS_ERR) { // added by MLT, exit on controller errors goto GetOut; } if (CartridgeStatus != DISK_TEST_UNDERWAY) { break; } // bump the FirstLBASector up for the next transfer FirstLBASector += NumberOfLBAs; } while(!UserInterrupt); // show that we're post-test GetOut: free(pDataBuffer); TestingPhase = UNTESTED; UnlockAllMedia(); SetErrorRecovery(true, true, false); // reenable Retries & ECC SetWindowText(hTestButton, szPressToStart); CartridgeStatus = DISK_AT_SPEED; AllowProgramExit(); // compute the number of serious troubles const char *result; long errors = FirmErrors + HardErrors; if (errors >= BADNESS_THRESHOLD) { result = szBadResult; } else if (UserInterrupt || (eax == SS_ERR)) { result = szInterrupted; } else { // it wasn't interrupted, nor seriously bad, was it perfect? errors += SoftErrors; if(errors) { result = szExplainResult; } else { result = szPerfectResult; } } SetRichEditText(result); InvalidateRect(hTestMonitor); Exit: StartApplicationTimer(); }