From owner-freebsd-current@FreeBSD.ORG Fri Oct 28 20:57:56 2011 Return-Path: Delivered-To: freebsd-current@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id 8EBC0106566B for ; Fri, 28 Oct 2011 20:57:56 +0000 (UTC) (envelope-from nmisaghian@sandvine.com) Received: from mail1.sandvine.com (Mail1.sandvine.com [64.7.137.134]) by mx1.freebsd.org (Postfix) with ESMTP id 2A3458FC13 for ; Fri, 28 Oct 2011 20:57:56 +0000 (UTC) Received: from labgw2.phaedrus.sandvine.com (192.168.222.22) by WTL-EXCH-1.sandvine.com (192.168.196.31) with Microsoft SMTP Server id 14.0.694.0; Fri, 28 Oct 2011 16:47:06 -0400 Received: by labgw2.phaedrus.sandvine.com (Postfix, from userid 10332) id B60E633C02; Fri, 28 Oct 2011 16:47:06 -0400 (EDT) Date: Fri, 28 Oct 2011 16:47:06 -0400 From: Nima Misaghian To: Message-ID: <20111028204706.GA57454@sandvine.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="Q68bSM7Ycu6FN28Q" Content-Disposition: inline User-Agent: Mutt/1.4.2.1i Subject: Adding disk firmware programming capability to camcontrol X-BeenThere: freebsd-current@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Discussions about the use of FreeBSD-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 28 Oct 2011 20:57:56 -0000 --Q68bSM7Ycu6FN28Q Content-Type: text/plain; charset="us-ascii" Content-Disposition: inline Hi, I have got code developed by Andre Albsmeier that is capable of programming firmware of hard drives from several vendors and turned it into a camcontrol command. The posted patch (against RELENG_8_2) basically adds the following new command to camcontrol: camcontrol fwdownload [device id] [generic args] <-f fw_image> [-s] I would appreciate it if FreeBSD experts in this area of code would take the time to review this patch. Thanks. Nima Misaghian Sandvine Incorporated --Q68bSM7Ycu6FN28Q Content-Type: text/plain; charset="us-ascii" Content-Disposition: attachment; filename="fwdownload.diff" --- old/camcontrol.h 2011-10-28 14:25:56.000000000 -0400 +++ camcontrol.h 2011-10-28 14:26:02.000000000 -0400 @@ -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); --- old/camcontrol.c 2011-10-28 14:25:56.000000000 -0400 +++ camcontrol.c 2011-10-28 14:26:02.000000000 -0400 @@ -77,7 +77,8 @@ CAM_CMD_IDENTIFY = 0x00000013, CAM_CMD_IDLE = 0x00000014, CAM_CMD_STANDBY = 0x00000015, - CAM_CMD_SLEEP = 0x00000016 + CAM_CMD_SLEEP = 0x00000016, + CAM_CMD_DOWNLOAD_FW = 0x00000017 } cam_cmdmask; typedef enum { @@ -160,6 +161,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:s"}, #endif /* MINIMALISTIC */ {"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, {"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL}, @@ -4565,6 +4567,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> [-s]\n" #endif /* MINIMALISTIC */ " camcontrol help\n"); if (!verbose) @@ -4595,6 +4598,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" @@ -4664,7 +4668,10 @@ "-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" +"-s run in simulation mode\n"); #endif /* MINIMALISTIC */ } @@ -4959,6 +4966,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 2011-10-28 15:00:37.000000000 -0400 +++ fwdownload.c 2011-10-28 14:26:02.000000000 -0400 @@ -0,0 +1,349 @@ +/*- + * 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_QUALSTAR, + 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_QUALSTAR, "QUALSTAR", 0x2030, 0x05, 0x05, 0, 0}, + {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 = NULL; + + 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; + case VENDOR_QUALSTAR: + skip_bytes = img_size % 1030; + 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; + 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; + } + 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); + 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; + + while ((c = getopt(argc, argv, combinedopt)) != -1) { + switch (c) { + case 's': + sim_mode = 1; + break; + case 'f': + fw_img_path = optarg; + 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 (fw_download_img(device, vp, buf, img_size, sim_mode, verbose, + retry_count, timeout) != 0) { + fprintf(stderr, "Firmware download failed"); + 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-10-28 14:25:56.000000000 -0400 +++ camcontrol.8 2011-10-28 14:26:02.000000000 -0400 @@ -183,6 +183,12 @@ .Op device id .Op generic args .Nm +.Ic fwdownload +.Op device id +.Op generic args +.Aq Fl f Ar fw_image +.Op Fl s +.Nm .Ic help .Sh DESCRIPTION The @@ -853,6 +859,42 @@ .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 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 +QUALSTAR +.It +QUANTUM +.It +SEAGATE +.El +.Pp +.Em 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 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. +.El .It Ic help Print out verbose usage information. .El --Q68bSM7Ycu6FN28Q--