/* * * Copyright (c) 2003 Sandvine Incorporated. All rights reserved * * Original disclaimer below: * * fwprog.c, a small program to upgrade the firmware * of various SCSI devices using FreeBSD. * * Compile with: cc -O -Wall -o fwprog fwprog.c -lcam * * See also: http://www.sistina.com/gfs/mlists/gfs-devel/past/msg00131.html */ /* * THE BEER-WARE LICENSE * * wrote this file. As long as you retain * this and the above notice you can do whatever you want with this * stuff. If we meet some day, and you think this stuff is worth it, * you can buy me a beer in return. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * CREDITS: * * Many thanks goes to Marc Frajola from * TeraSolutions for the initial idea and his program for upgrading * the firmware of I*M DDYS drives which helped me to install a less * buggy firmware and get my servers running reliably again. * * No thanks goes to I*M and Sea*ate who don't make firmware * upgrades available publically. * * No thanks goes to all the SCSI device manufactures who think that * Win*ows is the only OS in the world. By not supporting a firmware * upgrade via an ASPI driver on a DOS boot disk, they make you feel * like an idiot when you have to find a Win*ows box to upgrade their * broken devices. * */ /* * BEWARE: * * The fact that you see your favourite vendor listed below does not * mean, that your equipment won't explode when you use this software * with it. It only means that I had at least one device successfully * updated with this program. However, I have never destroyed anything * when I experimented with the parameters. But I have warned you ... */ #define CMD_TIMEOUT 25000 #include #include #include #include #include #include #include #include #include #include typedef enum { TYPE_HP, TYPE_IBM, TYPE_PLEXTOR, TYPE_QUALSTAR, TYPE_QUANTUM, TYPE_SEAGATE, TYPE_HITACHI, TYPE_UNKNOWN } TYPE_E; typedef struct { char* pattern; TYPE_E type; } TYPE_T; TYPE_T Type[] = { { "HP", TYPE_HP }, { "IBM", TYPE_IBM }, { "PLEXTOR", TYPE_PLEXTOR }, { "QUALSTAR", TYPE_QUALSTAR }, { "QUANTUM", TYPE_QUANTUM }, { "SEAGATE", TYPE_SEAGATE }, { "HITACHI", TYPE_HITACHI }, { NULL, TYPE_UNKNOWN } }; typedef struct { cam_status code; char* descr; } CAMSTAT_T; CAMSTAT_T CamStat[] = { { CAM_REQ_INPROG, "CCB request is in progress" }, { CAM_REQ_ABORTED, "CCB request aborted by the host" }, { CAM_UA_ABORT, "Unable to abort CCB request" }, { CAM_REQ_CMP_ERR, "CCB request completed with an error" }, { CAM_BUSY, "CAM subsystem is busy" }, { CAM_REQ_INVALID, "CCB request was invalid" }, { CAM_PATH_INVALID, "Supplied Path ID is invalid" }, { CAM_DEV_NOT_THERE, "SCSI Device Not Installed/there" }, { CAM_UA_TERMIO, "Unable to terminate I/O CCB request" }, { CAM_SEL_TIMEOUT, "Target Selection Timeout" }, { CAM_CMD_TIMEOUT, "Command timeout" }, { CAM_SCSI_STATUS_ERROR, "SCSI error, look at error code in CCB" }, { CAM_MSG_REJECT_REC, "Message Reject Received" }, { CAM_SCSI_BUS_RESET, "SCSI Bus Reset Sent/Received" }, { CAM_UNCOR_PARITY, "Uncorrectable parity error occurred" }, { CAM_AUTOSENSE_FAIL, "Autosense: request sense cmd fail" }, { CAM_NO_HBA, "No HBA Detected error" }, { CAM_DATA_RUN_ERR, "Data Overrun error" }, { CAM_UNEXP_BUSFREE, "Unexpected Bus Free" }, { CAM_SEQUENCE_FAIL, "Target Bus Phase Sequence Failure" }, { CAM_CCB_LEN_ERR, "CCB length supplied is inadequate" }, { CAM_PROVIDE_FAIL, "Unable to provide requested capability" }, { CAM_BDR_SENT, "A SCSI BDR msg was sent to target" }, { CAM_REQ_TERMIO, "CCB request terminated by the host" }, { CAM_UNREC_HBA_ERROR, "Unrecoverable Host Bus Adapter Error" }, { CAM_REQ_TOO_BIG, "The request was too large for this host" }, { CAM_REQUEUE_REQ, "Request should be requeued" }, { CAM_IDE, "Initiator Detected Error" }, { CAM_RESRC_UNAVAIL, "Resource Unavailable" }, { CAM_UNACKED_EVENT, "Unacknowledged Event by Host" }, { CAM_MESSAGE_RECV, "Message Received in Host Target Mode" }, { CAM_INVALID_CDB, "Invalid CDB received in Host Target Mode" }, { CAM_LUN_INVALID, "Lun supplied is invalid" }, { CAM_TID_INVALID, "Target ID supplied is invalid" }, { CAM_FUNC_NOTAVAIL, "The requested function is not available" }, { CAM_NO_NEXUS, "Nexus is not established" }, { CAM_IID_INVALID, "The initiator ID is invalid" }, { CAM_CDB_RECVD, "The SCSI CDB has been received" }, { CAM_LUN_ALRDY_ENA, "The LUN is already enabled for target mode" }, { CAM_SCSI_BUSY, "SCSI Bus Busy" }, { CAM_STATUS_MASK + 1, "unknown" } }; void usage( void ); /**************/ /* main() */ /**************/ int main( int argc, char *argv[] ) { char device[32]; char* buff; int unit; int tmp; u_long img_s; u_char sim = 0; TYPE_E type; char product[ SID_PRODUCT_SIZE * 4 + 1 ]; char rev[ SID_REVISION_SIZE * 4 + 1 ]; /*------------------*/ /*--- initialize ---*/ /*------------------*/ while( (tmp = getopt( argc, argv, "s" )) != -1) switch( tmp ) { case 's': sim = 1; break; default: usage(); } argc -= optind; argv += optind; if( argc != 2 ) usage(); if( cam_get_device( *argv++, device, sizeof( device ), &unit ) == -1 ) errx( EX_DATAERR, "%s", cam_errbuf ); { char vendor[ SID_VENDOR_SIZE * 4 + 1 ]; struct cam_device* cam_dev; TYPE_T* tp = Type; if( (cam_dev = cam_open_spec_device( device, unit, O_RDWR, NULL )) == NULL ) err( EX_OSERR, "%s", cam_errbuf ); cam_strvis((uint8_t*)vendor, (uint8_t*)cam_dev->inq_data.vendor, SID_VENDOR_SIZE, sizeof(vendor) ); cam_strvis((uint8_t*)product, (uint8_t*)cam_dev->inq_data.product, SID_PRODUCT_SIZE, sizeof(product)); cam_strvis((uint8_t*)rev, (uint8_t*)cam_dev->inq_data.revision, SID_REVISION_SIZE, sizeof(rev)); while( tp->pattern && cam_strmatch( (uint8_t*)vendor, (uint8_t*)tp->pattern, strlen( vendor ) ) ) tp++; if( tp->type == TYPE_UNKNOWN ) errx( EX_CONFIG, "vendor %s not supported.", vendor ); type = tp->type; fprintf( stderr, "\nUpdating %s\n", cam_dev->inq_data.vendor ); cam_close_device( cam_dev ); } /*--------------------*/ /*--- read fw file ---*/ /*--------------------*/ { struct stat stbuf; u_short skip = 0; int input_char; /*--- open and stat file and allocate buffer ---*/ if( (tmp = open( *argv, O_RDONLY ) ) < 0 ) err( EX_NOINPUT, "%s", *argv ); if( fstat( tmp, &stbuf ) < 0 ) err( EX_NOINPUT, "%s", *argv ); if( (img_s = stbuf.st_size) == 0 ) errx( EX_DATAERR, "%s: zero length file", *argv ); if( (buff = malloc( img_s )) == NULL ) err( EX_OSERR, NULL ); /*--- skip headers if applicable ---*/ if( type == TYPE_SEAGATE ) { if( read( tmp, buff, 18) != 18 ) err( EX_DATAERR, "%s", *argv ); char* keepCharPos = buff + 18; char keepChar = *keepCharPos; *keepCharPos = '\0'; fprintf( stdout, "\nFirmware file %s: %s\n", *argv, buff); *keepCharPos = keepChar; if( lseek( tmp, 0, SEEK_SET ) == -1 ) err( EX_DATAERR, "%s", *argv ); if( (strncmp( buff, "SEAGATE,SEAGATE ", 16 ) == 0) || (img_s % 512 == 80) ) skip = 80; if(strncmp( buff+8, product, strlen(product)) == 0) { char* keepCharPos = buff + 8 + strlen(product); keepChar = *( keepCharPos ); *keepCharPos = '\0'; fprintf(stderr, "\nSuccessful drive(%s) and firmware(%s) match\n", buff+8, product); *keepCharPos = keepChar; } else { if(strncmp( buff+8+6, product+6, 4) == 0) { fprintf(stderr, "\nDrive(%s) and firmware(%s) are not an exact match\n", product, buff+8); fprintf(stderr, "but are part of the same family (%s)\n", buff+8+6); } else { fprintf(stderr, "\nDrive(%s) and firmware(%s) do not match!\n", buff+8, product); free( buff ); close( tmp ); return (0); } } } // end type == TYPE_SEAGATE else if ( type == TYPE_IBM || type == TYPE_HITACHI) { // do nothing right now. Sandvine now supports these drives, but // there is no header. } else { // XXX don't support other drive vendors besides Seagate yet fprintf(stderr, "\nThis Vendor type(%s) is not supported by " "Sandvine's scsi_prog!\n", (char *)type); free( buff ); close( tmp ); return (0); } if( type == TYPE_QUALSTAR && img_s % 1030 != 0 ) skip = img_s % 1030; if( skip ) { img_s -= skip; if( lseek( tmp, skip, SEEK_SET ) == -1 ) err( EX_DATAERR, "%s", *argv ); } if (isatty(fileno(stdin))) { fprintf(stdout, "\nAre you sure you want to upgrade the firmware?\n\n" ); fprintf(stdout, "Press the (y) key and to continue\n"); input_char = getchar(); } else { fprintf(stdout, "\nRunning without tty -- bypassing user confirmation\n\n"); input_char = 'y'; } if (input_char != 'y') { fprintf(stderr, "Ohkay -- maybe next time...\n"); free( buff ); close(tmp); return 1; } /*--- read file into the buffer ---*/ if( read( tmp, buff, img_s ) != (int)img_s ) err( EX_DATAERR, "%s", *argv ); close( tmp ); } /*------------------*/ /*--- dowload fw ---*/ /*------------------*/ { struct scsi_write_buffer cdb; struct cam_device* cam_dev; union ccb* ccb; char* p = buff; u_short pkt = 0; u_short pkt_s, max_pkt_s = 0; u_char last_pkt = 0; if( (cam_dev = cam_open_spec_device( device, unit, O_RDWR, NULL )) == NULL ) err( EX_OSERR, "%s", cam_errbuf ); switch( type ) { case TYPE_HP: case TYPE_IBM: case TYPE_HITACHI: case TYPE_SEAGATE: max_pkt_s = 0x8000; /* must be even */ break; case TYPE_PLEXTOR: case TYPE_QUANTUM: max_pkt_s = 0x2000; /* must be even */ break; case TYPE_QUALSTAR: max_pkt_s = 8 * 1030; /* must be even */ break; default: errx( EX_CONFIG, "Internal error: type %d not in switch statement", type ); } pkt_s = max_pkt_s; fprintf( stderr, "-------------------------------------------------\n" ); fprintf( stderr, "PktNo. PktSize BytesRemaining LastPkt\n" ); fprintf( stderr, "-------------------------------------------------\n" ); /*--- download single fw pkts ---*/ do { if( img_s <= max_pkt_s ) { last_pkt = 1; pkt_s = img_s; } fprintf( stderr, "%3u %5u (0x%05X) %7lu (0x%06lX) %d\n", pkt, pkt_s, pkt_s, img_s-pkt_s, img_s-pkt_s, last_pkt ); bzero( &cdb, sizeof(cdb) ); cdb.opcode = 0x3B; /* WRITE BUFFER */ cdb.control = 0; scsi_ulto3b( pkt_s, &cdb.length[0] ); /* Parameter list length */ switch( type ) { case TYPE_HP: cdb.byte2 = 0x07; cdb.buffer_id = 0; scsi_ulto3b( p-buff, &cdb.offset[0] ); break; case TYPE_IBM: case TYPE_HITACHI: cdb.byte2 = 0x05; cdb.buffer_id = pkt; scsi_ulto3b( 0, &cdb.offset[0] ); break; case TYPE_PLEXTOR: cdb.byte2 = last_pkt ? 0x05 : 0x04; cdb.buffer_id = 0; scsi_ulto3b( p-buff, &cdb.offset[0] ); break; case TYPE_QUALSTAR: cdb.byte2 = 0x05; cdb.buffer_id = 0; scsi_ulto3b( 0, &cdb.offset[0] ); break; case TYPE_QUANTUM: cdb.byte2 = 0x05; cdb.buffer_id = 0; scsi_ulto3b( p-buff, &cdb.offset[0] ); break; case TYPE_SEAGATE: cdb.byte2 = 0x07; cdb.buffer_id = 0; /* can apparently be set to pkt as well */ scsi_ulto3b( p-buff, &cdb.offset[0] ); break; default: errx( EX_CONFIG, "Internal error: type %d not in switch statement", type ); } /*--- get a ccb with path, target and lun ids already set ---*/ if( (ccb = cam_getccb( cam_dev )) == NULL ) err( EX_OSERR, "cam_getccb" ); /*--- zero rest of ccb union after initial ccb_hdr part ---*/ bzero( (u_char*)ccb + sizeof(struct ccb_hdr), sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr) ); /*--- copy previously constructed cdb into ccb_scsiio struct ---*/ bcopy( &cdb, &ccb->csio.cdb_io.cdb_bytes[0], sizeof(struct scsi_write_buffer) ); /*--- fill rest of ccb_scsiio struct ---*/ if( ! sim ) { cam_fill_csio( &ccb->csio, /* ccb_scsiio */ 0, /* retries */ NULL, /* cbfcnp */ CAM_DIR_OUT | CAM_DEV_QFRZDIS, /* flags */ CAM_TAG_ACTION_NONE, /* tag_action */ (u_int8_t*)p, /* data_ptr */ pkt_s, /* dxfer_len */ SSD_FULL_SIZE, /* sense_len */ sizeof(struct scsi_write_buffer), /* cdb_len */ CMD_TIMEOUT ); /* timeout */ /*--- execute the command ---*/ if( cam_send_ccb( cam_dev, ccb ) < 0 ) { err( EX_OSERR, "\n%s\n", cam_errbuf ); } else { tmp = ccb->ccb_h.status & CAM_STATUS_MASK; if (tmp != CAM_REQ_CMP) { if ((last_pkt) && tmp != CAM_SCSI_BUS_RESET) { CAMSTAT_T* cs = CamStat; while( cs->code != (u_int)tmp && cs->code != CAM_STATUS_MASK + 1 ) cs++; fprintf( stderr, "\nccb_h.status=%d(0x%X), %s.\n", tmp, tmp, cs->descr ); if( tmp == CAM_SCSI_STATUS_ERROR ) scsi_sense_print( cam_dev, &ccb->csio, stderr ); fputc( '\n', stderr ); exit( EX_OSERR ); } } } } /*--- free the ccb ---*/ free( ccb ); /*--- prepare next round ---*/ p += pkt_s; img_s -= pkt_s; pkt++; } while( ! last_pkt ); cam_close_device( cam_dev ); free( buff ); } fprintf( stdout, "\nDownload successful\n" ); return( 0 ); } /***************/ /* usage() */ /***************/ void usage( void ) { fprintf( stderr, "Usage: %s [-s] device fwimg\n", getprogname() ); fprintf( stderr, "[-s] -- simulation mode, does not actually write firmware\n"); exit( EX_USAGE ); }