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
--- 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 <arg>          number of seconds before respective state.\n");
+"-t <arg>          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 <an...@albsmeier.net>
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cam/scsi/scsi_all.h>
+#include <cam/scsi/scsi_message.h>
+#include <camlib.h>
+
+#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
_______________________________________________
freebsd-current@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-current
To unsubscribe, send any mail to "freebsd-current-unsubscr...@freebsd.org"

Reply via email to