Adding disk firmware programming capability to camcontrol
Nima Misaghian
nmisaghian at sandvine.com
Fri Oct 28 20:57:56 UTC 2011
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
-------------- next part --------------
--- 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 <andre at 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
More information about the freebsd-current
mailing list