Adding disk firmware programming capability to camcontrol

Nima Misaghian nimamisa1 at gmail.com
Fri Oct 28 20:13:16 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