/* ***** SCSIutil * * A utility to do some low-level operations to a SCSI disk, e.g. * * - start/stop motor * - read sectors * - read disk capacity info * - get inquiry info (manufacturers name etc) * - seek to a sector's cylinder (use to park heads) * * NOTE: this program is based on SCSI information taken from * the accompanying documentation of a NEC D3841 SCSI disk. * I don't know the extent to which SCSI standards are * supported by that disk. * * These commands work on the above disk. But a seek to * sector -1 (park on the NEC disk) fails on my Quantum 105.D * ***** Written by Gary Duncan * * Bug reports etc via e-mail to gduncan@philips.oz.au) , or mail to * * Gary Duncan * Philips PTS * 23 Lakeside Dr * Tally-Ho Technology Park * Burwood East Vic 3151 * Australia * * ***** Freely distributable for non-commercial purposes * * Complies under Lattice 5.10 * - needs AmigaDosA 2.0 #includes * ***** Thanks to Markus Illenseer for some beta-testing. * ***** Function List :- * * motor () * read_sec () * init () * seek () * inquiry() * read_capacity() * gcomp() * GetDevName() * breakcheck() * chkabort() * err_str() * usage() * sense_errs() * */ #define VERSION "1.1" #include #include #include #include #include #include #include #include #include #include #include #include "scsi_priv.h" #define BYTES_PER_LINE 16 #define SENSE_LEN 252 #define MAX_DATA_LEN 252 #define PAD 0 #define LINE_BUF (128) #define OFFS_KEY 2 #define OFFS_CODE 12 /* * we open ( if no -d option) the first *scsi*.device in the device list. */ #define SCSI_STRING "scsi" typedef struct MsgPort MSGPORT; typedef struct IOStdReq IOSTDREQ; typedef struct List LIST; typedef struct Node NODE; typedef struct SCSICmd SCSICMD; #undef FALSE #undef TRUE typedef enum { FALSE = 0, TRUE } BOOLEAN; UBYTE *ip_buf = NULL; UBYTE *scsi_data = NULL; UBYTE *scsi_sense = NULL; UBYTE *dev = ""; MSGPORT *mp_ptr = NULL; IOSTDREQ *io_ptr = NULL; SCSICMD scsi_cmd; int scsi_id = -1; int on_off = -1; UBYTE *pname; UBYTE buffer[LINE_BUF]; int secno = -1; /* * function decs */ void usage (); void motor (); void read_capacity (); void read_sec (); void read_sec_scsi (); void inquiry (); void seek (); UBYTE *sense_errs (); UBYTE *GetDevName (); BOOLEAN init (); UBYTE *err_str (); /********************************************************************* * * main * * */ main (argc, argv) int argc; char **argv; { UBYTE *p; int j = 0; /* * see if a SCSI.device specified */ if (strncmp (argv[1], "-d", 2) == 0) { j = 1; dev = argv[1] + 2; } else if ((dev = GetDevName (SCSI_STRING)) == NULL) { usage ("Error : no *scsi*.device in device list\n"); exit (1); } pname = argv[0]; if (argc < (j + 2)) { usage (""); /* help inquiry */ exit (1); } if (argc < (j + 3)) { usage ("Error : Not enough params\n"); exit (1); } if (*(p = argv[j + 2]) != '-') { usage ("Error : bad option\n"); /* help inquiry */ exit (1); } /* * pick up SCSI id ; do a rough check */ sscanf (argv[j + 1], "%d", &scsi_id); if (scsi_id < 0) { usage ("Error : Bad scsi id\n"); exit (1); } /* * now set up structures etc for SCSI xfer */ if (init () == FALSE) goto error; /* ********************** now examine the options */ switch (*++p) { /* **** read capacity */ case 'c': read_capacity (); break; /* **** inquiry */ case 'i': inquiry (); break; /* **** read sectors */ case 'R': if (argc != (j + 4)) { usage ("Error : bad param count\n"); exit (1); } /* * get sector # */ if (sscanf (argv[j + 3], "%d", &secno) != 1) { usage ("Error : Bad sec no\n"); exit (1); } read_sec (); break; case 'r': if (argc != (j + 4)) { usage ("Error : bad param count\n"); exit (1); } /* * get sector # */ if (sscanf (argv[j + 3], "%d", &secno) != 1) { usage ("Error : Bad sec no\n"); exit (1); } read_sec_scsi (); break; /* **** seek to cylinder containing secno */ case 's': if (argc != (j + 4)) { usage ("Error : bad param count\n"); exit (1); } /* * get sector # */ if (sscanf (argv[j + 3], "%d", &secno) != 1) { usage ("Error : Bad sec no\n"); exit (1); } seek (); break; /* **** fill sector with a byte */ case 'f': if (argc != (j + 5)) { usage ("Error : bad param count\n"); exit (1); } /* * get sector # */ sscanf (argv[j + 3], "%d", &secno); if (secno == -1) { usage ("Error : Bad sec no\n"); exit (1); } break; /* **** stop/start motor */ case 'm': if (argc != (j + 4)) { usage ("Error : bad param count\n"); exit (1); } sscanf (argv[j + 3], "%d", &on_off); if ((on_off == -1) || ((on_off != 0) && (on_off != 1))) { usage ("Error : motor control must be 0 or 1\n"); exit (1); } motor (); break; default: usage ("Error : bad option\n"); /* help inquiry */ exit (1); } error: if (io_ptr) { CloseDevice ((IOSTDREQ *) io_ptr); DeleteStdIO (io_ptr); } if (mp_ptr) DeletePort (mp_ptr); if (ip_buf) FreeMem (ip_buf, TD_SECTOR); if (scsi_data) FreeMem (scsi_data, MAX_DATA_LEN); if (scsi_sense) FreeMem (scsi_sense, SENSE_LEN); } /********************************************************************* * * Initialisation function * * */ BOOLEAN init () { if ((scsi_data = (UBYTE *) AllocMem (MAX_DATA_LEN, MEMF_CHIP | MEMF_CLEAR)) == NULL) { fprintf (stderr, "AllocMem(0) Fail\n"); return FALSE; } if ((scsi_sense = (UBYTE *) AllocMem (SENSE_LEN, MEMF_CHIP || MEMF_CLEAR)) == NULL) { fprintf (stderr, "AllocMem(1) Fail\n"); return FALSE; } if ((ip_buf = (UBYTE *) AllocMem (TD_SECTOR, MEMF_CHIP)) == NULL) { fprintf (stderr, "AllocMem(2) Fail\n"); return FALSE; } if ((mp_ptr = (MSGPORT *) CreatePort (NULL, 0)) == NULL) { fprintf (stderr, "CreatePort Fail\n"); return FALSE; } if ((io_ptr = (IOSTDREQ *) CreateStdIO (mp_ptr)) == NULL) { fprintf (stderr, "CreateStdIO Fail\n"); return FALSE; } if (OpenDevice (dev, scsi_id, io_ptr, 0) != 0) { fprintf (stderr, "Error %d while opening SCSI dev \"%s\", unit (%d)\n", io_ptr->io_Error, dev, scsi_id); return FALSE; } return TRUE; } /********************************************************************* * * function to read sectors from a starting sector # * - similar adjacent lines are suppressed on printout. * * - uses trackdisk.device */ void read_sec () { UBYTE *sec_click_ptr; /* click = 16 bytes */ UBYTE *pref; UBYTE *p; UWORD j; UWORD k; int err; /* * keep printing sectors until ^C , or until error */ io_ptr->io_Command = CMD_READ; io_ptr->io_Length = TD_SECTOR; io_ptr->io_Data = (APTR) ip_buf; io_ptr->io_Offset = secno * TD_SECTOR; /* will be updated... */ /* * keep reading sectors : stop on ^C on bad sector # */ for (;; ++secno) { UBYTE *ss; UWORD m_sec_offs; if (breakcheck ()) /* ^C ? */ break; io_ptr->io_Offset = secno * TD_SECTOR; /* sector offset */ DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) == 0) { printf ("\n"); /* * scan this sector ... */ for (sec_click_ptr = pref = ip_buf, m_sec_offs = 0; m_sec_offs < TD_SECTOR; m_sec_offs += BYTES_PER_LINE, sec_click_ptr += BYTES_PER_LINE) { int xxxlen = strlen (" xx"); /* byte */ if (breakcheck ()) break; /* * don't print line if same contents as previous */ if (gcomp (sec_click_ptr, pref, BYTES_PER_LINE) == TRUE) { if (m_sec_offs > 1) continue; /* same */ } (void) setmem (buffer, sizeof (buffer), ' '); /* put spaces in buffer */ sprintf (buffer, "%05X:%03X = ", secno, m_sec_offs); /* set up for loop */ k = strlen (buffer); ss = buffer + k; k += (BYTES_PER_LINE * xxxlen) + 1; for (p = sec_click_ptr, j = 0; j < BYTES_PER_LINE; ss += xxxlen, ++j, ++k) { UBYTE dd = *p++; UBYTE que = (isascii (dd) && isprint (dd)) ? dd : '.'; sprintf (ss, " %02X", dd); /* 2 hex charas */ buffer[k] = que; } buffer[strlen (buffer)] = ' '; buffer[k++] = '\n'; buffer[k++] = '\0'; printf ("%s", buffer); pref = sec_click_ptr; } } else { /* else DoIO error */ fprintf (stderr, "Error : sec = %ld dec , $%lX , [%s]\n", secno, secno, sense_errs (err)); return; } } } /********************************************************************* * * function to read sectors from a starting sector # * - similar adjacent lines are suppressed on printout. * * - uses scsi device directly */ void read_sec_scsi () { static struct CMD_XREAD { UBYTE cmd; UBYTE lba[3]; UBYTE numb_secs; UBYTE pad; } command = { SCSI_CMD_RD, 0, 0, 0, 0, PAD }; UBYTE *sec_click_ptr; /* click = 16 bytes */ UBYTE *pref; UBYTE *p; UWORD j; UWORD k; int err; /* * keep printing sectors until ^C , or until error */ io_ptr->io_Command = HD_SCSICMD; io_ptr->io_Length = sizeof (SCSICMD); io_ptr->io_Data = (APTR) & scsi_cmd; scsi_cmd.scsi_Command = (UBYTE *) & command; scsi_cmd.scsi_CmdLength = sizeof (command); scsi_cmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE; scsi_cmd.scsi_Data = (APTR) ip_buf; scsi_cmd.scsi_Length = 512; scsi_cmd.scsi_SenseData = scsi_sense; scsi_cmd.scsi_SenseLength = SENSE_LEN; /* * keep reading sectors : stop on ^C on bad sector # */ for (;; ++secno) { UBYTE *ss; UWORD m_sec_offs; command.lba[2] = secno; command.lba[1] = secno >> 8; command.lba[0] = (secno >> 8) & 0x1F; command.numb_secs = 1; if (breakcheck ()) /* ^C ? */ break; io_ptr->io_Offset = secno * TD_SECTOR; /* sector offset */ DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) == 0) { printf ("\n"); /* * scan this sector ... */ for (sec_click_ptr = pref = ip_buf, m_sec_offs = 0; m_sec_offs < TD_SECTOR; m_sec_offs += BYTES_PER_LINE, sec_click_ptr += BYTES_PER_LINE) { int xxxlen = strlen (" xx"); /* byte */ if (breakcheck ()) break; /* * don't print line if same contents as previous */ if (gcomp (sec_click_ptr, pref, BYTES_PER_LINE) == TRUE) { if (m_sec_offs > 1) continue; /* same */ } (void) setmem (buffer, sizeof (buffer), ' '); /* put spaces in buffer */ sprintf (buffer, "%05X:%03X = ", secno, m_sec_offs); /* set up for loop */ k = strlen (buffer); ss = buffer + k; k += (BYTES_PER_LINE * xxxlen) + 1; for (p = sec_click_ptr, j = 0; j < BYTES_PER_LINE; ss += xxxlen, ++j, ++k) { UBYTE dd = *p++; UBYTE que = (isascii (dd) && isprint (dd)) ? dd : '.'; sprintf (ss, " %02X", dd); /* 2 hex charas */ buffer[k] = que; } buffer[strlen (buffer)] = ' '; buffer[k++] = '\n'; buffer[k++] = '\0'; printf ("%s", buffer); pref = sec_click_ptr; } } else { /* else DoIO error */ fprintf (stderr, "Error : sec = %ld dec , $%lX , [%s]\n", secno, secno, sense_errs (err)); return; } } } /********************************************************************* * * function to stop/start motor on SCSI device * */ void motor () { static struct CMD_SSU { UBYTE cmd; UBYTE imm; UBYTE pad_a[2]; UBYTE start_stop; UBYTE pad_b; } command = { SCSI_CMD_SSU, 0, PAD, PAD, 0, PAD }; int err; command.start_stop = on_off; io_ptr->io_Command = HD_SCSICMD; io_ptr->io_Length = sizeof (SCSICMD); io_ptr->io_Data = (APTR) & scsi_cmd; scsi_cmd.scsi_Command = (UBYTE *) & command; scsi_cmd.scsi_CmdLength = sizeof (command); scsi_cmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE; scsi_cmd.scsi_SenseData = scsi_sense; scsi_cmd.scsi_SenseLength = SENSE_LEN; (void) DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) != 0) { fprintf (stderr, "Error : [%s]\n", sense_errs (err)); } } /********************************************************************* * * function to seek to a cylinder * */ void seek () { static struct CMD_SEEK { UBYTE cmd; UBYTE pad_a; ULONG lba; UBYTE pad[4]; } command = { SCSI_CMD_SKX, PAD, 0, PAD, PAD, PAD, PAD }; int err; /* * load sector # (log block addr) */ command.lba = secno; io_ptr->io_Command = HD_SCSICMD; io_ptr->io_Length = sizeof (SCSICMD); io_ptr->io_Data = (APTR) & scsi_cmd; scsi_cmd.scsi_Command = (UBYTE *) & command; scsi_cmd.scsi_CmdLength = sizeof (command); scsi_cmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE; scsi_cmd.scsi_SenseData = scsi_sense; scsi_cmd.scsi_SenseLength = SENSE_LEN; (void) DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) != 0) { fprintf (stderr, "Error : sec = %ld dec , $%lX , [%s]\n", secno, secno, sense_errs (err)); } } /********************************************************************* * * function to make an inquiry * */ void inquiry () { static struct CMD_INQUIRY { UBYTE cmd; UBYTE pad_a[3]; UBYTE len; UBYTE pad_b; } command = { SCSI_CMD_INQ, PAD, PAD, PAD, 0, PAD }; UBYTE *sec_click_ptr; /* click = 16 bytes */ UBYTE *p; UBYTE *ss; UWORD m_sec_offs; UWORD k; int err; int j; command.len = MAX_DATA_LEN; io_ptr->io_Command = HD_SCSICMD; io_ptr->io_Length = sizeof (struct SCSICmd); io_ptr->io_Data = (APTR) & scsi_cmd; scsi_cmd.scsi_Data = (UWORD *) scsi_data; scsi_cmd.scsi_Length = MAX_DATA_LEN; scsi_cmd.scsi_Command = (UBYTE *) & command; scsi_cmd.scsi_CmdLength = sizeof (command); scsi_cmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE; scsi_cmd.scsi_SenseData = scsi_sense; scsi_cmd.scsi_SenseLength = SENSE_LEN; (void) DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) == 0) { int rem = scsi_cmd.scsi_Actual; printf ("\n"); /* * now print it out */ for (sec_click_ptr = scsi_data, m_sec_offs = 0; ; m_sec_offs += BYTES_PER_LINE, sec_click_ptr += BYTES_PER_LINE) { int xxxlen = strlen (" xx"); /* byte */ (void) setmem (buffer, sizeof (buffer), ' '); /* put spaces in buffer */ sprintf (buffer, "%3X = ", m_sec_offs); /* set up for loop */ k = strlen (buffer); ss = buffer + k; k += (BYTES_PER_LINE * xxxlen) + 1; for (p = sec_click_ptr, j = 0; j < BYTES_PER_LINE; ss += xxxlen, ++j) { UBYTE dd = *p++; UBYTE que = (isascii (dd) && isprint (dd)) ? dd : '.'; sprintf (ss, " %02X", dd); /* 2 hex charas */ buffer[k++] = que; if (--rem == 0) break; } buffer[strlen (buffer)] = ' '; buffer[k++] = '\n'; buffer[k++] = '\0'; printf ("%s", buffer); if (rem <= 0) return; } } else /* error */ { fprintf (stderr, "Error : %s\n", sense_errs (err)); } } /********************************************************************* * * function to read disk capacity * */ void read_capacity () { static struct CMD_READ_CAPACITY { UBYTE cmd; UBYTE pad_a; ULONG lba; UBYTE pad_b[2]; UBYTE pmi; UBYTE pad_c; } command = { SCSI_CMD_RCP, PAD, 0, /* start from sec 0 */ PAD, PAD, 0, PAD }; int err; io_ptr->io_Command = HD_SCSICMD; io_ptr->io_Length = sizeof (struct SCSICmd); io_ptr->io_Data = (APTR) & scsi_cmd; scsi_cmd.scsi_Data = (UWORD *) scsi_data; scsi_cmd.scsi_Length = MAX_DATA_LEN; scsi_cmd.scsi_Command = (UBYTE *) & command; scsi_cmd.scsi_CmdLength = sizeof (command); scsi_cmd.scsi_Flags = SCSIF_READ | SCSIF_AUTOSENSE; scsi_cmd.scsi_SenseData = scsi_sense; scsi_cmd.scsi_SenseLength = SENSE_LEN; (void) DoIO ((IOSTDREQ *) io_ptr); if ((err = io_ptr->io_Error) == 0) { ULONG sec_no = *((ULONG *) & scsi_data[0]); ULONG sec_size = *((ULONG *) & scsi_data[4]); printf ("Max Sec = %6ld , sec size = %4ld (capacity = %6ld KB)\n", sec_no, sec_size, (sec_no * sec_size) / 1024); } else { fprintf (stderr, "Error : %s \n", sense_errs (err)); } } /********************************************************************* * * function to compare two binary strings * * returns FALSE if different */ int gcomp (p1, p2, len) char *p1; char *p2; int len; { while (len--) { if (*p1++ != *p2++) return (FALSE); } return (TRUE); } /********************************************************************* * * searches DeviceList for a device name with a given string in it. * - if found returns with a pointer to it, else NULL */ extern struct ExecBase *SysBase; UBYTE * GetDevName (grep) char *grep; { LIST *lh = (LIST *) SysBase->DeviceList.lh_Head; NODE *ln; for (ln = lh->lh_Head; ln->ln_Succ; ln = ln->ln_Succ) { UBYTE *p = ln->ln_Name; while (*p != '.') { if (strncmp (p, grep, 4) == 0) { return (ln->ln_Name); } ++p; } } return (NULL); /* not found */ } /********************************************************************* * * Break (^C) function * */ int breakcheck () { int zz = SetSignal (0L, 0L) & SIGBREAKF_CTRL_C; if (zz) { printf ("\n^C"); } return (zz); } /********************************************************************* * * tell Lattice to return */ int chkabort (void) { return (0); } /********************************************************************* * * function to return an error string * * */ UBYTE * err_str (err) int err; { static UBYTE *errors[] = { " cannot issue SCSI command to self ", " DMA error ", " illegal or unexpected SCSI phase ", " SCSI parity error ", " Select timed out ", " status and/or sense error " }; err -= 40; if ((err < 0) || (err > 5)) return ("Error out-of-range"); else return (errors[err]); } /********************************************************************* * * usage function * * */ void usage (p) char *p; { static char *zz[] = { "Inquiry : SCSIutil [-dscsi_dev] scsi_id -i\n", "Read sectors (2) : -R sec_no\n", "Read sectors : -r sec_no\n", "Read capacity : -c\n", /* "Fill sector : -f sec_no byte\n", */ "Stop/Start motor : -m {0=stop,1=start}\n", "Seek to sector (3) : -s sec_no\n", "\n", "Note 1: usually scsi_id = (BOARD * 100) + (LUN * 10) + SCSI_TARGET_ID\n", " 2: uses trackdisk.device\n", " 3: to park heads, try sec_no of -1\n", "" /* TERM */ }; int j = 0; printf ("%s", p); printf ("Usage : SCSIutil V%s [%s : %s] - written by Gary Duncan\n", VERSION, __DATE__, __TIME__, pname); while (*zz[j++]) printf ("%s", zz[j - 1]); } /********************************************************************* * * sense_errs function ; prints sense errors * * */ UBYTE * sense_errs (err) int err; { typedef struct { BYTE code; BYTE sense; UBYTE *ptr; } S_ERRS; /* * only the likely, interesting ones filled in, e.g media errors */ static S_ERRS x[] = { 0x00, 0x00, "No error", 0x01, 0x04, "?", 0x02, 0x04, "?", 0x03, 0x04, "?", 0x04, 0x02, "?", 0x06, 0x04, "?", 0x09, 0x04, "?", 0x10, 0x03, "?", 0x10, 0x04, "?", 0x11, 0x03, "?", 0x12, 0x03, "?", 0x13, 0x03, "?", 0x14, 0x03, "?", 0x15, 0x04, "Seek error ", 0x17, 0x01, "?", 0x18, 0x01, "?", 0x19, 0x03, "?", 0x1A, 0x05, "?", 0x20, 0x05, "Invalid command op code", 0x21, 0x05, "Illegal sector address", 0x24, 0x05, "?", 0x25, 0x05, "Invalid LUN", 0x26, 0x05, "Invalid field in parameter list", 0x29, 0x06, "?", 0x2A, 0x06, "?", 0x31, 0x03, "?", 0x32, 0x01, "?", 0x32, 0x03, "?", 0x40, 0x04, "?", 0x41, 0x04, "?", 0x42, 0x04, "Power-on diagnostic failure", 0x43, 0x04, "?", 0x45, 0x04, "Select / reselect failure ", 0x47, 0x04, "SCSI Interface Parity Error", 0x48, 0x0B, "?", 0x49, 0x0B, "Illegal message drive can't support", -1, -1, "ILLEGAL sense!!" }; int j = 0; UBYTE *p; char sense; char code; /* * verify that sense data looks valid */ if (((scsi_cmd.scsi_Status & 2) == 0) || (scsi_cmd.scsi_SenseActual < OFFS_KEY)) { return (""); } sense = scsi_cmd.scsi_SenseData[OFFS_KEY] & 0xF; code = scsi_cmd.scsi_SenseData[OFFS_CODE]; do { p = x[j].ptr; if ((x[j].code == code) && (x[j].sense == sense)) break; } while (x[j++].code != -1); return (p); }