summaryrefslogtreecommitdiffstats
path: root/sbin/camcontrol/fwdownload.c
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/camcontrol/fwdownload.c')
-rw-r--r--sbin/camcontrol/fwdownload.c886
1 files changed, 726 insertions, 160 deletions
diff --git a/sbin/camcontrol/fwdownload.c b/sbin/camcontrol/fwdownload.c
index 181c234..2c2d12a 100644
--- a/sbin/camcontrol/fwdownload.c
+++ b/sbin/camcontrol/fwdownload.c
@@ -68,9 +68,10 @@ __FBSDID("$FreeBSD$");
#include "camcontrol.h"
-#define CMD_TIMEOUT 50000 /* 50 seconds */
+#define WB_TIMEOUT 50000 /* 50 seconds */
typedef enum {
+ VENDOR_HGST,
VENDOR_HITACHI,
VENDOR_HP,
VENDOR_IBM,
@@ -79,32 +80,163 @@ typedef enum {
VENDOR_QUANTUM,
VENDOR_SAMSUNG,
VENDOR_SEAGATE,
+ VENDOR_SMART,
+ VENDOR_ATA,
VENDOR_UNKNOWN
} fw_vendor_t;
+/*
+ * FW_TUR_READY: The drive must return good status for a test unit ready.
+ *
+ * FW_TUR_NOT_READY: The drive must return not ready status for a test unit
+ * ready. You may want this in a removable media drive.
+ *
+ * FW_TUR_NA: It doesn't matter whether the drive is ready or not.
+ * This may be the case for a removable media drive.
+ */
+typedef enum {
+ FW_TUR_NONE,
+ FW_TUR_READY,
+ FW_TUR_NOT_READY,
+ FW_TUR_NA
+} fw_tur_status;
+
+/*
+ * FW_TIMEOUT_DEFAULT: Attempt to probe for a WRITE BUFFER timeout
+ * value from the drive. If we get an answer,
+ * use the Recommended timeout. Otherwise,
+ * use the default value from the table.
+ *
+ * FW_TIMEOUT_DEV_REPORTED: The timeout value was probed directly from
+ * the device.
+ *
+ * FW_TIMEOUT_NO_PROBE: Do not ask the device for a WRITE BUFFER
+ * timeout value. Use the device-specific
+ * value.
+ *
+ * FW_TIMEOUT_USER_SPEC: The user specified a timeout on the command
+ * line with the -t option. This overrides any
+ * probe or default timeout.
+ */
+typedef enum {
+ FW_TIMEOUT_DEFAULT,
+ FW_TIMEOUT_DEV_REPORTED,
+ FW_TIMEOUT_NO_PROBE,
+ FW_TIMEOUT_USER_SPEC
+} fw_timeout_type;
+
+/*
+ * type: Enumeration for the particular vendor.
+ *
+ * pattern: Pattern to match for the Vendor ID from the SCSI
+ * Inquiry data.
+ *
+ * dev_type: SCSI device type to match, or T_ANY to match any
+ * device from the given vendor. Note that if there
+ * is a specific device type listed for a particular
+ * vendor, it must be listed before a T_ANY entry.
+ *
+ * max_pkt_size: Maximum packet size when talking to a device. Note
+ * that although large data sizes may be supported by
+ * the target device, they may not be supported by the
+ * OS or the controller.
+ *
+ * cdb_byte2: This specifies byte 2 (byte 1 when counting from 0)
+ * of the CDB. This is generally the WRITE BUFFER mode.
+ *
+ * cdb_byte2_last: This specifies byte 2 for the last chunk of the
+ * download.
+ *
+ * inc_cdb_buffer_id: Increment the buffer ID by 1 for each chunk sent
+ * down to the drive.
+ *
+ * inc_cdb_offset: Increment the offset field in the CDB with the byte
+ * offset into the firmware file.
+ *
+ * tur_status: Pay attention to whether the device is ready before
+ * upgrading the firmware, or not. See above for the
+ * values.
+ */
struct fw_vendor {
fw_vendor_t type;
const char *pattern;
+ int dev_type;
int max_pkt_size;
u_int8_t cdb_byte2;
u_int8_t cdb_byte2_last;
int inc_cdb_buffer_id;
int inc_cdb_offset;
+ fw_tur_status tur_status;
+ int timeout_ms;
+ fw_timeout_type timeout_type;
+};
+
+/*
+ * Vendor notes:
+ *
+ * HGST: The packets need to be sent in multiples of 4K.
+ *
+ * IBM: For LTO and TS drives, the buffer ID is ignored in mode 7 (and
+ * some other modes). It treats the request as a firmware download.
+ * The offset (and therefore the length of each chunk sent) needs
+ * to be a multiple of the offset boundary specified for firmware
+ * (buffer ID 4) in the read buffer command. At least for LTO-6,
+ * that seems to be 0, but using a 32K chunk size should satisfy
+ * most any alignment requirement.
+ *
+ * SmrtStor: Mode 5 is also supported, but since the firmware is 400KB or
+ * so, we can't fit it in a single request in most cases.
+ */
+static struct fw_vendor vendors_list[] = {
+ {VENDOR_HGST, "HGST", T_DIRECT,
+ 0x1000, 0x07, 0x07, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_HITACHI, "HITACHI", T_ANY,
+ 0x8000, 0x05, 0x05, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_HP, "HP", T_ANY,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_IBM, "IBM", T_SEQUENTIAL,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_NA, 300 * 1000, FW_TIMEOUT_DEFAULT},
+ {VENDOR_IBM, "IBM", T_ANY,
+ 0x8000, 0x05, 0x05, 1, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_PLEXTOR, "PLEXTOR", T_ANY,
+ 0x2000, 0x04, 0x05, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_QUALSTAR, "QUALSTAR", T_ANY,
+ 0x2030, 0x05, 0x05, 0, 0, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_QUANTUM, "QUANTUM", T_ANY,
+ 0x2000, 0x04, 0x05, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_SAMSUNG, "SAMSUNG", T_ANY,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_SEAGATE, "SEAGATE", T_ANY,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+ {VENDOR_SMART, "SmrtStor", T_DIRECT,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT, FW_TIMEOUT_DEFAULT},
+
+ /*
+ * We match any ATA device. This is really just a placeholder,
+ * since we won't actually send a WRITE BUFFER with any of the
+ * listed parameters. If a SATA device is behind a SAS controller,
+ * the SCSI to ATA translation code (at least for LSI) doesn't
+ * generaly translate a SCSI WRITE BUFFER into an ATA DOWNLOAD
+ * MICROCODE command. So, we use the SCSI ATA PASS_THROUGH command
+ * to send the ATA DOWNLOAD MICROCODE command instead.
+ */
+ {VENDOR_ATA, "ATA", T_ANY,
+ 0x8000, 0x07, 0x07, 0, 1, FW_TUR_READY, WB_TIMEOUT,
+ FW_TIMEOUT_NO_PROBE},
+ {VENDOR_UNKNOWN, NULL, T_ANY,
+ 0x0000, 0x00, 0x00, 0, 0, FW_TUR_NONE, WB_TIMEOUT, FW_TIMEOUT_DEFAULT}
+};
+
+struct fw_timeout_desc {
+ fw_timeout_type timeout_type;
+ const char *timeout_desc;
};
-static const 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_SAMSUNG, "SAMSUNG", 0x8000, 0x07, 0x07, 0, 1},
- {VENDOR_SEAGATE, "SEAGATE", 0x8000, 0x07, 0x07, 0, 1},
- /* the next 2 are SATA disks going through SAS HBA */
- {VENDOR_SEAGATE, "ATA ST", 0x8000, 0x07, 0x07, 0, 1},
- {VENDOR_HITACHI, "ATA HDS", 0x8000, 0x05, 0x05, 1, 0},
- {VENDOR_UNKNOWN, NULL, 0x0000, 0x00, 0x00, 0, 0}
+static const struct fw_timeout_desc fw_timeout_desc_table[] = {
+ { FW_TIMEOUT_DEFAULT, "the default" },
+ { FW_TIMEOUT_DEV_REPORTED, "recommended by this particular device" },
+ { FW_TIMEOUT_NO_PROBE, "the default" },
+ { FW_TIMEOUT_USER_SPEC, "what was specified on the command line" }
};
#ifndef ATA_DOWNLOAD_MICROCODE
@@ -128,43 +260,308 @@ static const struct fw_vendor vendors_list[] = {
#define UNKNOWN_MAX_PKT_SIZE 0x8000
#endif
-static const struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev);
-static char *fw_read_img(const char *fw_img_path,
- const struct fw_vendor *vp, int *num_bytes);
-static int fw_download_img(struct cam_device *cam_dev,
- const struct fw_vendor *vp, char *buf, int img_size,
- int sim_mode, int printerrors, int retry_count, int timeout,
- const char */*name*/, const char */*type*/);
+static struct fw_vendor *fw_get_vendor(struct cam_device *cam_dev,
+ struct ata_params *ident_buf);
+static int fw_get_timeout(struct cam_device *cam_dev, struct fw_vendor *vp,
+ int retry_count, int timeout);
+static int fw_validate_ibm(struct cam_device *dev, int retry_count,
+ int timeout, int fd, char *buf,
+ const char *fw_img_path, int quiet);
+static char *fw_read_img(struct cam_device *dev, int retry_count,
+ int timeout, int quiet, const char *fw_img_path,
+ struct fw_vendor *vp, int *num_bytes);
+static int fw_check_device_ready(struct cam_device *dev,
+ camcontrol_devtype devtype,
+ struct fw_vendor *vp, int printerrors,
+ int timeout);
+static int fw_download_img(struct cam_device *cam_dev,
+ struct fw_vendor *vp, char *buf, int img_size,
+ int sim_mode, int printerrors, int quiet,
+ int retry_count, int timeout, const char */*name*/,
+ camcontrol_devtype devtype);
/*
* Find entry in vendors list that belongs to
* the vendor of given cam device.
*/
-static const struct fw_vendor *
-fw_get_vendor(struct cam_device *cam_dev)
+static struct fw_vendor *
+fw_get_vendor(struct cam_device *cam_dev, struct ata_params *ident_buf)
{
- char vendor[SID_VENDOR_SIZE + 1];
- const struct fw_vendor *vp;
+ char vendor[42];
+ 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));
+
+ if (ident_buf != NULL) {
+ cam_strvis((u_char *)vendor, ident_buf->model,
+ sizeof(ident_buf->model), sizeof(vendor));
+ for (vp = vendors_list; vp->pattern != NULL; vp++) {
+ if (vp->type == VENDOR_ATA)
+ return (vp);
+ }
+ } else {
+ 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;
+ (const u_char *)vp->pattern, strlen(vendor))) {
+ if ((vp->dev_type == T_ANY)
+ || (vp->dev_type == SID_TYPE(&cam_dev->inq_data)))
+ break;
+ }
}
return (vp);
}
+static int
+fw_get_timeout(struct cam_device *cam_dev, struct fw_vendor *vp,
+ int retry_count, int timeout)
+{
+ struct scsi_report_supported_opcodes_one *one;
+ struct scsi_report_supported_opcodes_timeout *td;
+ uint8_t *buf = NULL;
+ uint32_t fill_len = 0, cdb_len = 0, rec_timeout = 0;
+ int retval = 0;
+
+ /*
+ * If the user has specified a timeout on the command line, we let
+ * him override any default or probed value.
+ */
+ if (timeout != 0) {
+ vp->timeout_type = FW_TIMEOUT_USER_SPEC;
+ vp->timeout_ms = timeout;
+ goto bailout;
+ }
+
+ /*
+ * Check to see whether we should probe for a timeout for this
+ * device.
+ */
+ if (vp->timeout_type == FW_TIMEOUT_NO_PROBE)
+ goto bailout;
+
+ retval = scsigetopcodes(/*device*/ cam_dev,
+ /*opcode_set*/ 1,
+ /*opcode*/ WRITE_BUFFER,
+ /*show_sa_errors*/ 1,
+ /*sa_set*/ 0,
+ /*service_action*/ 0,
+ /*timeout_desc*/ 1,
+ /*retry_count*/ retry_count,
+ /*timeout*/ 10000,
+ /*verbose*/ 0,
+ /*fill_len*/ &fill_len,
+ /*data_ptr*/ &buf);
+ /*
+ * It isn't an error if we can't get a timeout descriptor. We just
+ * continue on with the default timeout.
+ */
+ if (retval != 0) {
+ retval = 0;
+ goto bailout;
+ }
+
+ /*
+ * Even if the drive didn't return a SCSI error, if we don't have
+ * enough data to contain the one opcode descriptor, the CDB
+ * structure and a timeout descriptor, we don't have the timeout
+ * value we're looking for. So we'll just fall back to the
+ * default value.
+ */
+ if (fill_len < (sizeof(*one) + sizeof(struct scsi_write_buffer) +
+ sizeof(*td)))
+ goto bailout;
+
+ one = (struct scsi_report_supported_opcodes_one *)buf;
+
+ /*
+ * If the drive claims to not support the WRITE BUFFER command...
+ * fall back to the default timeout value and let things fail on
+ * the actual firmware download.
+ */
+ if ((one->support & RSO_ONE_SUP_MASK) == RSO_ONE_SUP_NOT_SUP)
+ goto bailout;
+
+ cdb_len = scsi_2btoul(one->cdb_length);
+ td = (struct scsi_report_supported_opcodes_timeout *)
+ &buf[sizeof(*one) + cdb_len];
+
+ rec_timeout = scsi_4btoul(td->recommended_time);
+ /*
+ * If the recommended timeout is 0, then the device has probably
+ * returned a bogus value.
+ */
+ if (rec_timeout == 0)
+ goto bailout;
+
+ /* CAM timeouts are in ms */
+ rec_timeout *= 1000;
+
+ vp->timeout_ms = rec_timeout;
+ vp->timeout_type = FW_TIMEOUT_DEV_REPORTED;
+
+bailout:
+ return (retval);
+}
+
+#define SVPD_IBM_FW_DESIGNATION 0x03
+
+/*
+ * IBM LTO and TS tape drives have an INQUIRY VPD page 0x3 with the following
+ * format:
+ */
+struct fw_ibm_tape_fw_designation {
+ uint8_t device;
+ uint8_t page_code;
+ uint8_t reserved;
+ uint8_t length;
+ uint8_t ascii_length;
+ uint8_t reserved2[3];
+ uint8_t load_id[4];
+ uint8_t fw_rev[4];
+ uint8_t ptf_number[4];
+ uint8_t patch_number[4];
+ uint8_t ru_name[8];
+ uint8_t lib_seq_num[5];
+};
+
+/*
+ * The firmware for IBM tape drives has the following header format. The
+ * load_id and ru_name in the header file should match what is returned in
+ * VPD page 0x3.
+ */
+struct fw_ibm_tape_fw_header {
+ uint8_t unspec[4];
+ uint8_t length[4]; /* Firmware and header! */
+ uint8_t load_id[4];
+ uint8_t fw_rev[4];
+ uint8_t reserved[8];
+ uint8_t ru_name[8];
+};
+
+static int
+fw_validate_ibm(struct cam_device *dev, int retry_count, int timeout, int fd,
+ char *buf, const char *fw_img_path, int quiet)
+{
+ union ccb *ccb;
+ struct fw_ibm_tape_fw_designation vpd_page;
+ struct fw_ibm_tape_fw_header *header;
+ char drive_rev[sizeof(vpd_page.fw_rev) + 1];
+ char file_rev[sizeof(vpd_page.fw_rev) + 1];
+ int retval = 1;
+
+ ccb = cam_getccb(dev);
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ goto bailout;
+ }
+
+ /* cam_getccb cleans up the header, caller has to zero the payload */
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
+
+ bzero(&vpd_page, sizeof(vpd_page));
+
+ scsi_inquiry(&ccb->csio,
+ /*retries*/ retry_count,
+ /*cbfcnp*/ NULL,
+ /* tag_action */ MSG_SIMPLE_Q_TAG,
+ /* inq_buf */ (u_int8_t *)&vpd_page,
+ /* inq_len */ sizeof(vpd_page),
+ /* evpd */ 1,
+ /* page_code */ SVPD_IBM_FW_DESIGNATION,
+ /* sense_len */ SSD_FULL_SIZE,
+ /* timeout */ timeout ? timeout : 5000);
+
+ /* Disable freezing the device queue */
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ if (retry_count != 0)
+ ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+ if (cam_send_ccb(dev, ccb) < 0) {
+ warn("error getting firmware designation page");
+
+ cam_error_print(dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+
+ cam_freeccb(ccb);
+ goto bailout;
+ }
+
+ if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+ cam_error_print(dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ goto bailout;
+ }
+
+ /*
+ * Read the firmware header only.
+ */
+ if (read(fd, buf, sizeof(*header)) != sizeof(*header)) {
+ warn("unable to read %zu bytes from %s", sizeof(*header),
+ fw_img_path);
+ goto bailout;
+ }
+
+ /* Rewind the file back to 0 for the full file read. */
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ warn("Unable to lseek");
+ goto bailout;
+ }
+
+ header = (struct fw_ibm_tape_fw_header *)buf;
+
+ bzero(drive_rev, sizeof(drive_rev));
+ bcopy(vpd_page.fw_rev, drive_rev, sizeof(vpd_page.fw_rev));
+ bzero(file_rev, sizeof(file_rev));
+ bcopy(header->fw_rev, file_rev, sizeof(header->fw_rev));
+
+ if (quiet == 0) {
+ fprintf(stdout, "Current Drive Firmware version: %s\n",
+ drive_rev);
+ fprintf(stdout, "Firmware File version: %s\n", file_rev);
+ }
+
+ /*
+ * For IBM tape drives the load ID and RU name reported by the
+ * drive should match what is in the firmware file.
+ */
+ if (bcmp(vpd_page.load_id, header->load_id,
+ MIN(sizeof(vpd_page.load_id), sizeof(header->load_id))) != 0) {
+ warnx("Drive Firmware load ID 0x%x does not match firmware "
+ "file load ID 0x%x", scsi_4btoul(vpd_page.load_id),
+ scsi_4btoul(header->load_id));
+ goto bailout;
+ }
+
+ if (bcmp(vpd_page.ru_name, header->ru_name,
+ MIN(sizeof(vpd_page.ru_name), sizeof(header->ru_name))) != 0) {
+ warnx("Drive Firmware RU name 0x%jx does not match firmware "
+ "file RU name 0x%jx",
+ (uintmax_t)scsi_8btou64(vpd_page.ru_name),
+ (uintmax_t)scsi_8btou64(header->ru_name));
+ goto bailout;
+ }
+ if (quiet == 0)
+ fprintf(stdout, "Firmware file is valid for this drive.\n");
+ retval = 0;
+bailout:
+ cam_freeccb(ccb);
+
+ return (retval);
+}
+
/*
* 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(const char *fw_img_path, const struct fw_vendor *vp, int *num_bytes)
+fw_read_img(struct cam_device *dev, int retry_count, int timeout, int quiet,
+ const char *fw_img_path, struct fw_vendor *vp, int *num_bytes)
{
int fd;
struct stat stbuf;
@@ -207,6 +604,14 @@ fw_read_img(const char *fw_img_path, const struct fw_vendor *vp, int *num_bytes)
case VENDOR_QUALSTAR:
skip_bytes = img_size % 1030;
break;
+ case VENDOR_IBM: {
+ if (vp->dev_type != T_SEQUENTIAL)
+ break;
+ if (fw_validate_ibm(dev, retry_count, timeout, fd, buf,
+ fw_img_path, quiet) != 0)
+ goto bailout;
+ break;
+ }
default:
break;
}
@@ -234,85 +639,162 @@ bailout1:
return (NULL);
}
+/*
+ * Returns 0 for "success", where success means that the device has met the
+ * requirement in the vendor structure for being ready or not ready when
+ * firmware is downloaded.
+ *
+ * Returns 1 for a failure to be ready to accept a firmware download.
+ * (e.g., a drive needs to be ready, but returns not ready)
+ *
+ * Returns -1 for any other failure.
+ */
+static int
+fw_check_device_ready(struct cam_device *dev, camcontrol_devtype devtype,
+ struct fw_vendor *vp, int printerrors, int timeout)
+{
+ union ccb *ccb;
+ int retval = 0;
+ int16_t *ptr = NULL;
+ size_t dxfer_len = 0;
+
+ if ((ccb = cam_getccb(dev)) == NULL) {
+ warnx("Could not allocate CCB");
+ retval = -1;
+ goto bailout;
+ }
+
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
+
+ if (devtype != CC_DT_SCSI) {
+ dxfer_len = sizeof(struct ata_params);
+
+ ptr = (uint16_t *)malloc(dxfer_len);
+ if (ptr == NULL) {
+ warnx("can't malloc memory for identify");
+ retval = -1;
+ goto bailout;
+ }
+ bzero(ptr, dxfer_len);
+ }
+
+ switch (devtype) {
+ case CC_DT_SCSI:
+ scsi_test_unit_ready(&ccb->csio,
+ /*retries*/ 0,
+ /*cbfcnp*/ NULL,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ 5000);
+ break;
+ case CC_DT_ATA_BEHIND_SCSI:
+ case CC_DT_ATA: {
+ build_ata_cmd(ccb,
+ /*retries*/ 1,
+ /*flags*/ CAM_DIR_IN,
+ /*tag_action*/ MSG_SIMPLE_Q_TAG,
+ /*protocol*/ AP_PROTO_PIO_IN,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_FROM_DEV,
+ /*features*/ 0,
+ /*sector_count*/ (uint8_t) dxfer_len,
+ /*lba*/ 0,
+ /*command*/ ATA_ATA_IDENTIFY,
+ /*data_ptr*/ (uint8_t *)ptr,
+ /*dxfer_len*/ dxfer_len,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : 30 * 1000,
+ /*is48bit*/ 0,
+ /*devtype*/ devtype);
+ break;
+ }
+ default:
+ warnx("Unknown disk type %d", devtype);
+ retval = -1;
+ goto bailout;
+ break; /*NOTREACHED*/
+ }
+
+ ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+ retval = cam_send_ccb(dev, ccb);
+ if (retval != 0) {
+ warn("error sending %s CCB", (devtype == CC_DT_SCSI) ?
+ "Test Unit Ready" : "Identify");
+ retval = -1;
+ goto bailout;
+ }
+
+ if (((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)
+ && (vp->tur_status == FW_TUR_READY)) {
+ warnx("Device is not ready");
+ if (printerrors)
+ cam_error_print(dev, ccb, CAM_ESF_ALL,
+ CAM_EPF_ALL, stderr);
+ retval = 1;
+ goto bailout;
+ } else if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)
+ && (vp->tur_status == FW_TUR_NOT_READY)) {
+ warnx("Device cannot have media loaded when firmware is "
+ "downloaded");
+ retval = 1;
+ goto bailout;
+ }
+bailout:
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+
+ return (retval);
+}
+
/*
* 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, const struct fw_vendor *vp,
- char *buf, int img_size, int sim_mode, int printerrors, int retry_count,
- int timeout, const char *imgname, const char *type)
+fw_download_img(struct cam_device *cam_dev, struct fw_vendor *vp,
+ char *buf, int img_size, int sim_mode, int printerrors, int quiet,
+ int retry_count, int timeout, const char *imgname,
+ camcontrol_devtype devtype)
{
struct scsi_write_buffer cdb;
progress_t progress;
- int size;
- union ccb *ccb;
+ int size = 0;
+ union ccb *ccb = NULL;
int pkt_count = 0;
int max_pkt_size;
u_int32_t pkt_size = 0;
char *pkt_ptr = buf;
u_int32_t offset;
int last_pkt = 0;
- int16_t *ptr;
+ int retval = 0;
+
+ /*
+ * Check to see whether the device is ready to accept a firmware
+ * download.
+ */
+ retval = fw_check_device_ready(cam_dev, devtype, vp, printerrors,
+ timeout);
+ if (retval != 0)
+ goto bailout;
if ((ccb = cam_getccb(cam_dev)) == NULL) {
warnx("Could not allocate CCB");
- return (1);
+ retval = 1;
+ goto bailout;
}
- if (strcmp(type, "scsi") == 0) {
- scsi_test_unit_ready(&ccb->csio, 0, NULL, MSG_SIMPLE_Q_TAG,
- SSD_FULL_SIZE, 5000);
- } else if (strcmp(type, "ata") == 0) {
- /* cam_getccb cleans up the header, caller has to zero the payload */
- bzero(&(&ccb->ccb_h)[1],
- sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
- ptr = (uint16_t *)malloc(sizeof(struct ata_params));
+ bzero(&(&ccb->ccb_h)[1],
+ sizeof(union ccb) - sizeof(struct ccb_hdr));
- if (ptr == NULL) {
- cam_freeccb(ccb);
- warnx("can't malloc memory for identify\n");
- return(1);
- }
- bzero(ptr, sizeof(struct ata_params));
- cam_fill_ataio(&ccb->ataio,
- 1,
- NULL,
- /*flags*/CAM_DIR_IN,
- MSG_SIMPLE_Q_TAG,
- /*data_ptr*/(uint8_t *)ptr,
- /*dxfer_len*/sizeof(struct ata_params),
- timeout ? timeout : 30 * 1000);
- ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0);
- } else {
- warnx("weird disk type '%s'", type);
- cam_freeccb(ccb);
- return 1;
- }
- /* Disable freezing the device queue. */
- ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
- if (cam_send_ccb(cam_dev, ccb) < 0) {
- warnx("Error sending identify/test unit ready");
- if (printerrors)
- 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 (printerrors)
- cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
- CAM_EPF_ALL, stderr);
- cam_freeccb(ccb);
- return (1);
- }
max_pkt_size = vp->max_pkt_size;
- if (vp->max_pkt_size == 0 && strcmp(type, "ata") == 0) {
+ if (max_pkt_size == 0)
max_pkt_size = UNKNOWN_MAX_PKT_SIZE;
- }
- pkt_size = vp->max_pkt_size;
+
+ pkt_size = max_pkt_size;
progress_init(&progress, imgname, size = img_size);
/* Download single fw packets. */
do {
@@ -321,56 +803,73 @@ fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp,
pkt_size = img_size;
}
progress_update(&progress, size - img_size);
- progress_draw(&progress);
+ if (((sim_mode == 0) && (quiet == 0))
+ || ((sim_mode != 0) && (printerrors == 0)))
+ progress_draw(&progress);
bzero(&cdb, sizeof(cdb));
- if (strcmp(type, "scsi") == 0) {
+ switch (devtype) {
+ case CC_DT_SCSI:
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.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),
+ bzero(&(&ccb->ccb_h)[1],
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));
- /* Copy previously constructed cdb into ccb_scsiio struct. */
+ /*
+ * 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 */
- }
- } else if (strcmp(type, "ata") == 0) {
- bzero(&(&ccb->ccb_h)[1],
- sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
- if (!sim_mode) {
- uint32_t off;
-
- cam_fill_ataio(&ccb->ataio,
- (last_pkt) ? 256 : retry_count,
- NULL,
- /*flags*/CAM_DIR_OUT | CAM_DEV_QFRZDIS,
- CAM_TAG_ACTION_NONE,
- /*data_ptr*/(uint8_t *)pkt_ptr,
- /*dxfer_len*/pkt_size,
- timeout ? timeout : 30 * 1000);
- off = (uint32_t)(pkt_ptr - buf);
- ata_28bit_cmd(&ccb->ataio, ATA_DOWNLOAD_MICROCODE,
- USE_OFFSETS_FEATURE,
- ATA_MAKE_LBA(off, pkt_size),
- ATA_MAKE_SECTORS(pkt_size));
- }
+ 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 : WB_TIMEOUT); /* timeout*/
+ break;
+ case CC_DT_ATA:
+ case CC_DT_ATA_BEHIND_SCSI: {
+ uint32_t off;
+
+ off = (uint32_t)(pkt_ptr - buf);
+
+ build_ata_cmd(ccb,
+ /*retry_count*/ retry_count,
+ /*flags*/ CAM_DIR_OUT | CAM_DEV_QFRZDIS,
+ /*tag_action*/ CAM_TAG_ACTION_NONE,
+ /*protocol*/ AP_PROTO_PIO_OUT,
+ /*ata_flags*/ AP_FLAG_BYT_BLOK_BYTES |
+ AP_FLAG_TLEN_SECT_CNT |
+ AP_FLAG_TDIR_TO_DEV,
+ /*features*/ USE_OFFSETS_FEATURE,
+ /*sector_count*/ ATA_MAKE_SECTORS(pkt_size),
+ /*lba*/ ATA_MAKE_LBA(off, pkt_size),
+ /*command*/ ATA_DOWNLOAD_MICROCODE,
+ /*data_ptr*/ (uint8_t *)pkt_ptr,
+ /*dxfer_len*/ pkt_size,
+ /*sense_len*/ SSD_FULL_SIZE,
+ /*timeout*/ timeout ? timeout : WB_TIMEOUT,
+ /*is48bit*/ 0,
+ /*devtype*/ devtype);
+ break;
+ }
+ default:
+ warnx("Unknown device type %d", devtype);
+ retval = 1;
+ goto bailout;
+ break; /*NOTREACHED*/
}
if (!sim_mode) {
/* Execute the command. */
@@ -379,47 +878,56 @@ fw_download_img(struct cam_device *cam_dev, const struct fw_vendor *vp,
CAM_REQ_CMP) {
warnx("Error writing image to device");
if (printerrors)
- cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
- CAM_EPF_ALL, stderr);
+ cam_error_print(cam_dev, ccb,
+ CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+ retval = 1;
goto bailout;
}
+ } else if (printerrors) {
+ cam_error_print(cam_dev, ccb, CAM_ESF_COMMAND, 0,
+ stdout);
}
+
/* Prepare next round. */
pkt_count++;
pkt_ptr += pkt_size;
img_size -= pkt_size;
} while(!last_pkt);
- progress_complete(&progress, size - img_size);
- cam_freeccb(ccb);
- return (0);
bailout:
- progress_complete(&progress, size - img_size);
- cam_freeccb(ccb);
- return (1);
+ if (quiet == 0)
+ progress_complete(&progress, size - img_size);
+ if (ccb != NULL)
+ cam_freeccb(ccb);
+ return (retval);
}
int
fwdownload(struct cam_device *device, int argc, char **argv,
- char *combinedopt, int printerrors, int retry_count, int timeout,
- const char *type)
+ char *combinedopt, int printerrors, int retry_count, int timeout)
{
- const struct fw_vendor *vp;
+ struct fw_vendor *vp;
char *fw_img_path = NULL;
- char *buf;
+ struct ata_params *ident_buf = NULL;
+ camcontrol_devtype devtype;
+ char *buf = NULL;
int img_size;
int c;
int sim_mode = 0;
int confirmed = 0;
+ int quiet = 0;
+ int retval = 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 'q':
+ quiet = 1;
+ break;
+ case 's':
+ sim_mode = 1;
+ break;
case 'y':
confirmed = 1;
break;
@@ -429,42 +937,100 @@ fwdownload(struct cam_device *device, int argc, char **argv,
}
if (fw_img_path == NULL)
- errx(1, "you must specify a firmware image file using -f option");
+ errx(1, "you must specify a firmware image file using -f "
+ "option");
+
+ retval = get_device_type(device, retry_count, timeout, printerrors,
+ &devtype);
+ if (retval != 0)
+ errx(1, "Unable to determine device type");
+
+ if ((devtype == CC_DT_ATA)
+ || (devtype == CC_DT_ATA_BEHIND_SCSI)) {
+ union ccb *ccb;
+
+ ccb = cam_getccb(device);
+ if (ccb == NULL) {
+ warnx("couldn't allocate CCB");
+ retval = 1;
+ goto bailout;
+ }
+
+ if (ata_do_identify(device, retry_count, timeout, ccb,
+ &ident_buf) != 0) {
+ cam_freeccb(ccb);
+ retval = 1;
+ goto bailout;
+ }
+ } else if (devtype != CC_DT_SCSI)
+ errx(1, "Unsupported device type %d", devtype);
- vp = fw_get_vendor(device);
- if (vp == NULL)
- errx(1, "NULL vendor");
- if (vp->type == VENDOR_UNKNOWN)
- warnx("Unsupported device - flashing through an HBA?");
+ vp = fw_get_vendor(device, ident_buf);
+ /*
+ * Bail out if we have an unknown vendor and this isn't an ATA
+ * disk. For a SCSI disk, we have no chance of working properly
+ * with the default values in the VENDOR_UNKNOWN case. For an ATA
+ * disk connected via an ATA transport, we may work for drives that
+ * support the ATA_DOWNLOAD_MICROCODE command.
+ */
+ if (((vp == NULL)
+ || (vp->type == VENDOR_UNKNOWN))
+ && (devtype == CC_DT_SCSI))
+ errx(1, "Unsupported device");
- buf = fw_read_img(fw_img_path, vp, &img_size);
- if (buf == NULL)
- goto fail;
+ retval = fw_get_timeout(device, vp, retry_count, timeout);
+ if (retval != 0) {
+ warnx("Unable to get a firmware download timeout value");
+ goto bailout;
+ }
+
+ buf = fw_read_img(device, retry_count, timeout, quiet, fw_img_path,
+ vp, &img_size);
+ if (buf == NULL) {
+ retval = 1;
+ goto bailout;
+ }
if (!confirmed) {
fprintf(stdout, "You are about to download firmware image (%s)"
" into the following device:\n",
fw_img_path);
+ if (devtype == CC_DT_SCSI) {
+ if (scsidoinquiry(device, argc, argv, combinedopt, 0,
+ 5000) != 0) {
+ warnx("Error sending inquiry");
+ retval = 1;
+ goto bailout;
+ }
+ } else {
+ printf("%s%d: ", device->device_name,
+ device->dev_unit_num);
+ ata_print_ident(ident_buf);
+ camxferrate(device);
+ free(ident_buf);
+ }
+ fprintf(stdout, "Using a timeout of %u ms, which is %s.\n",
+ vp->timeout_ms,
+ fw_timeout_desc_table[vp->timeout_type].timeout_desc);
fprintf(stdout, "\nIt may damage your drive. ");
- if (!get_confirmation())
- goto fail;
+ if (!get_confirmation()) {
+ retval = 1;
+ goto bailout;
+ }
}
- if (sim_mode)
+ if ((sim_mode != 0) && (quiet == 0))
fprintf(stdout, "Running in simulation mode\n");
if (fw_download_img(device, vp, buf, img_size, sim_mode, printerrors,
- retry_count, timeout, fw_img_path, type) != 0) {
+ quiet, retry_count, vp->timeout_ms, fw_img_path, devtype) != 0) {
fprintf(stderr, "Firmware download failed\n");
- goto fail;
- }
- else
+ retval = 1;
+ goto bailout;
+ } else if (quiet == 0)
fprintf(stdout, "Firmware download successful\n");
+bailout:
free(buf);
- return (0);
-fail:
- if (buf != NULL)
- free(buf);
- return (1);
+ return (retval);
}
OpenPOWER on IntegriCloud