--- old/camcontrol.h 2011-11-17 11:51:30.000000000 -0500 +++ new/camcontrol.h 2011-11-17 11:51:30.000000000 -0500 @@ -40,6 +40,8 @@ int got; }; +int fwdownload(struct cam_device *device, int argc, char **argv, + char *combinedopt, int verbose, int retry_count, int timeout); void mode_sense(struct cam_device *device, int mode_page, int page_control, int dbd, int retry_count, int timeout, u_int8_t *data, int datalen); @@ -49,8 +51,11 @@ int edit, int binary, int retry_count, int timeout); void mode_list(struct cam_device *device, int page_control, int dbd, int retry_count, int timeout); +int scsidoinquiry(struct cam_device *device, int argc, char **argv, + char *combinedopt, int retry_count, int timeout); char *cget(void *hook, char *name); int iget(void *hook, char *name); void arg_put(void *hook, int letter, void *arg, int count, char *name); +int get_confirmation(void); void usage(int verbose); #endif /* _CAMCONTROL_H */ --- old/camcontrol.c 2011-11-17 11:51:30.000000000 -0500 +++ new/camcontrol.c 2011-11-17 11:51:30.000000000 -0500 @@ -86,7 +86,8 @@ CAM_CMD_SMP_RG = 0x00000018, CAM_CMD_SMP_PC = 0x00000019, CAM_CMD_SMP_PHYLIST = 0x0000001a, - CAM_CMD_SMP_MANINFO = 0x0000001b + CAM_CMD_SMP_MANINFO = 0x0000001b, + CAM_CMD_DOWNLOAD_FW = 0x0000001c } cam_cmdmask; typedef enum { @@ -180,6 +181,7 @@ {"idle", CAM_CMD_IDLE, CAM_ARG_NONE, "t:"}, {"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:"}, {"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""}, + {"fwdownload", CAM_CMD_DOWNLOAD_FW, CAM_ARG_NONE, "f:ys"}, #endif /* MINIMALISTIC */ {"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, @@ -683,7 +685,7 @@ return(error); } -static int +int scsidoinquiry(struct cam_device *device, int argc, char **argv, char *combinedopt, int retry_count, int timeout) { @@ -3571,7 +3573,7 @@ union ccb *ccb; int c; int ycount = 0, quiet = 0; - int error = 0, response = 0, retval = 0; + int error = 0, retval = 0; int use_timeout = 10800 * 1000; int immediate = 1; struct format_defect_list_header fh; @@ -3625,27 +3627,7 @@ } if (ycount == 0) { - - do { - char str[1024]; - - fprintf(stdout, "Are you SURE you want to do " - "this? (yes/no) "); - - if (fgets(str, sizeof(str), stdin) != NULL) { - - if (strncasecmp(str, "yes", 3) == 0) - response = 1; - else if (strncasecmp(str, "no", 2) == 0) - response = -1; - else { - fprintf(stdout, "Please answer" - " \"yes\" or \"no\"\n"); - } - } - } while (response == 0); - - if (response == -1) { + if (!get_confirmation()) { error = 1; goto scsiformat_bailout; } @@ -5693,6 +5675,7 @@ " camcontrol idle [dev_id][generic args][-t time]\n" " camcontrol standby [dev_id][generic args][-t time]\n" " camcontrol sleep [dev_id][generic args]\n" +" camcontrol fwdownload [dev_id][generic args] <-f fw_image> [-y][-s]\n" #endif /* MINIMALISTIC */ " camcontrol help\n"); if (!verbose) @@ -5728,6 +5711,7 @@ "idle send the ATA IDLE command to the named device\n" "standby send the ATA STANDBY command to the named device\n" "sleep send the ATA SLEEP command to the named device\n" +"fwdownload program firmware of the named device with the given image" "help this message\n" "Device Identifiers:\n" "bus:target specify the bus and target, lun defaults to 0\n" @@ -5819,7 +5803,12 @@ "-w don't send immediate format command\n" "-y don't ask any questions\n" "idle/standby arguments:\n" -"-t number of seconds before respective state.\n"); +"-t number of seconds before respective state.\n" +"fwdownload arguments:\n" +"-f fw_image path to firmware image file\n" +"-y don't ask any questions\n" +"-s run in simulation mode\n" +"-v print info for every firmware segment sent to device\n"); #endif /* MINIMALISTIC */ } @@ -6135,6 +6124,10 @@ combinedopt, retry_count, timeout); break; + case CAM_CMD_DOWNLOAD_FW: + error = fwdownload(cam_dev, argc, argv, combinedopt, + arglist & CAM_ARG_VERBOSE, retry_count, timeout); + break; #endif /* MINIMALISTIC */ case CAM_CMD_USAGE: usage(1); --- old/fwdownload.c 1969-12-31 19:00:00.000000000 -0500 +++ new/fwdownload.c 2011-11-17 11:51:30.000000000 -0500 @@ -0,0 +1,378 @@ +/*- + * Copyright (c) 2011 Sandvine Incorporated. All rights reserved. + * Copyright (c) 2002-2011 Andre Albsmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification, immediately at the beginning of the file. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ + +/* + * BEWARE: + * + * The fact that you see your favorite vendor listed below does not + * imply that your equipment won't break when you use this software + * with it. It only means that the firmware of at least one device type + * of each vendor listed has been programmed successfully using this code. + * + * The -s option simulates a download but does nothing apart from that. + * It can be used to check what chunk sizes would have been used with the + * specified device. + */ + + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "camcontrol.h" + +#define CMD_TIMEOUT 50000 /* 50 seconds */ + +typedef enum { + VENDOR_HITACHI, + VENDOR_HP, + VENDOR_IBM, + VENDOR_PLEXTOR, + VENDOR_QUANTUM, + VENDOR_SEAGATE, + VENDOR_UNKNOWN +} fw_vendor_t; + +struct fw_vendor { + fw_vendor_t type; + const char *pattern; + int max_pkt_size; + u_int8_t cdb_byte2; + u_int8_t cdb_byte2_last; + int inc_cdb_buffer_id; + int inc_cdb_offset; +}; + +struct fw_vendor vendors_list[] = { + {VENDOR_HITACHI, "HITACHI", 0x8000, 0x05, 0x05, 1, 0}, + {VENDOR_HP, "HP", 0x8000, 0x07, 0x07, 0, 1}, + {VENDOR_IBM, "IBM", 0x8000, 0x05, 0x05, 1, 0}, + {VENDOR_PLEXTOR, "PLEXTOR", 0x2000, 0x04, 0x05, 0, 1}, + {VENDOR_QUANTUM, "QUANTUM", 0x2000, 0x04, 0x05, 0, 1}, + {VENDOR_SEAGATE, "SEAGATE", 0x8000, 0x07, 0x07, 0, 1}, + {VENDOR_UNKNOWN, NULL, 0x0000, 0x00, 0x00, 0, 0} +}; + +static struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev); +static char *fw_read_img(char *fw_img_path, struct fw_vendor *vp, + int *num_bytes); +static int fw_download_img(struct cam_device *cam_dev, + struct fw_vendor *vp, char *buf, int img_size, + int sim_mode, int verbose, int retry_count, int timeout); + +/* + * Find entry in vendors list that belongs to + * the vendor of given cam device. + */ +static struct fw_vendor * +fw_get_vendor(struct cam_device *cam_dev) +{ + char vendor[SID_VENDOR_SIZE + 1]; + struct fw_vendor *vp; + + if (cam_dev == NULL) + return (NULL); + cam_strvis((u_char *)vendor, (u_char *)cam_dev->inq_data.vendor, + sizeof(cam_dev->inq_data.vendor), sizeof(vendor)); + for (vp = vendors_list; vp->pattern != NULL; vp++) { + if (!cam_strmatch((const u_char *)vendor, + (const u_char *)vp->pattern, strlen(vendor))) + break; + } + return (vp); +} + +/* + * Allocate a buffer and read fw image file into it + * from given path. Number of bytes read is stored + * in num_bytes. + */ +static char * +fw_read_img(char *fw_img_path, struct fw_vendor *vp, int *num_bytes) +{ + int fd; + struct stat stbuf; + char *buf; + off_t img_size; + int skip_bytes = 0; + + if ((fd = open(fw_img_path, O_RDONLY)) < 0) { + warn("Could not open image file %s", fw_img_path); + return (NULL); + } + if (fstat(fd, &stbuf) < 0) { + warn("Could not stat image file %s", fw_img_path); + goto bailout1; + } + if ((img_size = stbuf.st_size) == 0) { + warnx("Zero length image file %s", fw_img_path); + goto bailout1; + } + if ((buf = malloc(img_size)) == NULL) { + warnx("Could not allocate buffer to read image file %s", + fw_img_path); + goto bailout1; + } + /* Skip headers if applicable. */ + switch (vp->type) { + case VENDOR_SEAGATE: + if (read(fd, buf, 16) != 16) { + warn("Could not read image file %s", fw_img_path); + goto bailout; + } + if (lseek(fd, 0, SEEK_SET) == -1) { + warn("Unable to lseek"); + goto bailout; + } + if ((strncmp(buf, "SEAGATE,SEAGATE ", 16) == 0) || + (img_size % 512 == 80)) + skip_bytes = 80; + break; + default: + break; + } + if (skip_bytes != 0) { + fprintf(stdout, "Skipping %d byte header.\n", skip_bytes); + if (lseek(fd, skip_bytes, SEEK_SET) == -1) { + warn("Could not lseek"); + goto bailout; + } + img_size -= skip_bytes; + } + /* Read image into a buffer. */ + if (read(fd, buf, img_size) != img_size) { + warn("Could not read image file %s", fw_img_path); + goto bailout; + } + *num_bytes = img_size; + return (buf); +bailout: + free(buf); +bailout1: + close(fd); + *num_bytes = 0; + return (NULL); +} + +/* + * Download firmware stored in buf to cam_dev. If simulation mode + * is enabled, only show what packet sizes would be sent to the + * device but do not sent any actual packets + */ +static int +fw_download_img(struct cam_device *cam_dev, struct fw_vendor *vp, + char *buf, int img_size, int sim_mode, int verbose, int retry_count, + int timeout) +{ + struct scsi_write_buffer cdb; + union ccb *ccb; + int pkt_count = 0; + u_int32_t pkt_size = 0; + char *pkt_ptr = buf; + u_int32_t offset; + int last_pkt = 0; + + if ((ccb = cam_getccb(cam_dev)) == NULL) { + warnx("Could not allocate CCB"); + return (1); + } + scsi_test_unit_ready(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG, + SSD_FULL_SIZE, 5000); + /* Disable freezing the device queue. */ + ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; + if (cam_send_ccb(cam_dev, ccb) < 0) { + warnx("Error sending test unit ready"); + if (verbose) + cam_error_print(cam_dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + cam_freeccb(ccb); + return(1); + } + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + warnx("Device is not ready"); + if (verbose) + cam_error_print(cam_dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + cam_freeccb(ccb); + return (1); + } + pkt_size = vp->max_pkt_size; + if (verbose || sim_mode) { + fprintf(stdout, + "--------------------------------------------------\n"); + fprintf(stdout, + "PktNo. PktSize BytesRemaining LastPkt\n"); + fprintf(stdout, + "--------------------------------------------------\n"); + } + /* Download single fw packets. */ + do { + if (img_size <= vp->max_pkt_size) { + last_pkt = 1; + pkt_size = img_size; + } + if (verbose || sim_mode) + fprintf(stdout, "%3u %5u (0x%05X) %7u (0x%06X) " + "%d\n", pkt_count, pkt_size, pkt_size, + img_size - pkt_size, img_size - pkt_size, + last_pkt); + bzero(&cdb, sizeof(cdb)); + cdb.opcode = WRITE_BUFFER; + cdb.control = 0; + /* Parameter list length. */ + scsi_ulto3b(pkt_size, &cdb.length[0]); + offset = vp->inc_cdb_offset ? (pkt_ptr - buf) : 0; + scsi_ulto3b(offset, &cdb.offset[0]); + cdb.byte2 = last_pkt ? vp->cdb_byte2_last : vp->cdb_byte2; + cdb.buffer_id = vp->inc_cdb_buffer_id ? pkt_count : 0; + /* Zero out payload of ccb union after ccb header. */ + 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_mode) { + cam_fill_csio(&ccb->csio, /* ccb_scsiio */ + retry_count, /* retries */ + NULL, /* cbfcnp */ + CAM_DIR_OUT | CAM_DEV_QFRZDIS, /* flags */ + CAM_TAG_ACTION_NONE, /* tag_action */ + (u_char *)pkt_ptr, /* data_ptr */ + pkt_size, /* dxfer_len */ + SSD_FULL_SIZE, /* sense_len */ + sizeof(struct scsi_write_buffer), /* cdb_len */ + timeout ? timeout : CMD_TIMEOUT); /* timeout */ + /* Execute the command. */ + if (cam_send_ccb(cam_dev, ccb) < 0) { + warnx("Error writing image to device"); + if (verbose) + cam_error_print(cam_dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + goto bailout; + } + } + /* Prepare next round. */ + pkt_count++; + pkt_ptr += pkt_size; + img_size -= pkt_size; + } while(!last_pkt); + if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { + if (verbose) + cam_error_print(cam_dev, ccb, CAM_ESF_ALL, + CAM_EPF_ALL, stderr); + goto bailout; + } + cam_freeccb(ccb); + return (0); +bailout: + cam_freeccb(ccb); + return (1); +} + +int +fwdownload(struct cam_device *device, int argc, char **argv, + char *combinedopt, int verbose, int retry_count, int timeout) +{ + struct fw_vendor *vp; + char *fw_img_path = NULL; + char *buf; + int img_size; + int c; + int sim_mode = 0; + int confirmed = 0; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 's': + sim_mode = 1; + confirmed = 1; + break; + case 'f': + fw_img_path = optarg; + break; + case 'y': + confirmed = 1; + break; + default: + break; + } + } + + if (fw_img_path == NULL) + errx(1, + "you must specify a firmware image file using -f option"); + + vp = fw_get_vendor(device); + if (vp == NULL || vp->type == VENDOR_UNKNOWN) + errx(1, "Unsupported device"); + + buf = fw_read_img(fw_img_path, vp, &img_size); + if (buf == NULL) + goto fail; + + if (!confirmed) { + fprintf(stdout, "You are about to download firmware image (%s)" + " into the following device:\n", + fw_img_path); + if (scsidoinquiry(device, argc, argv, combinedopt, 0, + 5000) != 0) { + warnx("Error sending inquiry"); + goto fail; + } + fprintf(stdout, "\nIt may damage your drive. "); + if (!get_confirmation()) + goto fail; + } + if (sim_mode) + fprintf(stdout, "Running in simulation mode\n"); + + if (fw_download_img(device, vp, buf, img_size, sim_mode, verbose, + retry_count, timeout) != 0) { + fprintf(stderr, "Firmware download failed\n"); + goto fail; + } else + fprintf(stdout, "Firmware download successful\n"); + + free(buf); + return (0); +fail: + if (buf != NULL) + free(buf); + return (1); +} + --- old/camcontrol.8 2011-11-17 11:51:30.000000000 -0500 +++ new/camcontrol.8 2011-11-17 11:51:30.000000000 -0500 @@ -27,7 +27,7 @@ .\" .\" $FreeBSD$ .\" -.Dd November 30, 2010 +.Dd November 17, 2011 .Dt CAMCONTROL 8 .Os .Sh NAME @@ -220,6 +220,13 @@ .Op device id .Op generic args .Nm +.Ic fwdownload +.Op device id +.Op generic args +.Aq Fl f Ar fw_image +.Op Fl y +.Op Fl s +.Nm .Ic help .Sh DESCRIPTION The @@ -1060,6 +1067,49 @@ .It Ic sleep Put ATA device into SLEEP state. Note that the only way get device out of this state may be reset. +.It Ic fwdownload +Program firmware of the named SCSI device using the image file provided. +.Pp +Current list of supported vendors: +.Bl -bullet -offset indent -compact +.It +HITACHI +.It +HP +.It +IBM +.It +PLEXTOR +.It +QUANTUM +.It +SEAGATE +.El +.Pp +.Em WARNING! WARNING! WARNING! +.Pp +Very little testing has been done to make sure that different device models +of each vendor work correctly with fwdownload command. Showing up a vendor +name in the supported list basically means firmware of at least one device +type from that vendor has successfully been programmed with fwdownload +command. Extra caution should be taken when using this command since there is +no guarantee it would not break a device from listed vendors. +.Bl -tag -width 11n +.It Fl f Ar fw_image +Path to the firmware image file to be downloaded to the specified device. +.It Fl y +Do not ask the user for confirmation. +.It Fl s +Run in simulation mode where packet sizes that are going to be sent are shown, +but no actual packet is sent to the device. No confimation question is asked +from user in simulation mode. +.It Fl v +Besides showing sense information in case of a failure, the verbose option causes +.Nm +to output a line for every firmware segment that is sent to the device by the +fwdownload command +-- the same as the ones shown in simulation mode. +.El .It Ic help Print out verbose usage information. .El --- old/Makefile 2011-11-17 11:51:30.000000000 -0500 +++ new/Makefile 2011-11-17 11:51:30.000000000 -0500 @@ -1,7 +1,7 @@ # $FreeBSD$ PROG= camcontrol -SRCS= camcontrol.c util.c +SRCS= camcontrol.c fwdownload.c util.c .if !defined(RELEASE_CRUNCH) SRCS+= modeedit.c .else --- old/util.c 2011-11-17 11:51:30.000000000 -0500 +++ new/util.c 2011-11-17 11:51:30.000000000 -0500 @@ -154,3 +154,31 @@ if (verbose) putchar('\n'); } + +/* + * Get confirmation from user + * Return values: + * 1: confirmed + * 0: unconfirmed + */ +int +get_confirmation() +{ + char str[1024]; + int response = -1; + + do { + fprintf(stdout, "Are you SURE you want to do this? (yes/no) "); + if (fgets(str, sizeof(str), stdin) != NULL) { + if (strncasecmp(str, "yes", 3) == 0) + response = 1; + else if (strncasecmp(str, "no", 2) == 0) + response = 0; + else + fprintf(stdout, + "Please answer \"yes\" or \"no\"\n"); + } else + response = 0; + } while (response == -1); + return (response); +}