示例#1
0
/* 
 * 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 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;
	if (verbose || sim_mode) {
		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;
		}
		if (verbose || sim_mode)
			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);
	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
		if (verbose)
			cam_error_print(cam_dev, ccb, CAM_ESF_ALL,
			    CAM_EPF_ALL, stderr);
		goto bailout;
	}
	cam_freeccb(ccb);
	return (0);
bailout:
	cam_freeccb(ccb);
	return (1);
}
示例#2
0
/* 
 * 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)
{
	struct scsi_write_buffer cdb;
	progress_t progress;
	int size;
	union ccb *ccb;
	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;

	if ((ccb = cam_getccb(cam_dev)) == NULL) {
		warnx("Could not allocate CCB");
		return (1);
	}
	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));

		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);
		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) {
		max_pkt_size = UNKNOWN_MAX_PKT_SIZE;
	}
	pkt_size = vp->max_pkt_size;
	progress_init(&progress, imgname, size = img_size);
	/* Download single fw packets. */
	do {
		if (img_size <= max_pkt_size) {
			last_pkt = 1;
			pkt_size = img_size;
		}
		progress_update(&progress, size - img_size);
		progress_draw(&progress);
		bzero(&cdb, sizeof(cdb));
		if (strcmp(type, "scsi") == 0) {
			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	*/
			}
		} 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));
			}
		}
		if (!sim_mode) {
			/* Execute the command. */
			if (cam_send_ccb(cam_dev, ccb) < 0 ||
			    (ccb->ccb_h.status & CAM_STATUS_MASK) !=
			    CAM_REQ_CMP) {
				warnx("Error writing image to device");
				if (printerrors)
					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);
	progress_complete(&progress, size - img_size);
	cam_freeccb(ccb);
	return (0);
bailout:
	progress_complete(&progress, size - img_size);
	cam_freeccb(ccb);
	return (1);
}
示例#3
0
/* Executes SCSI command (or at least forwards it to lower layers).
 * Clears os_err field prior to active call (whose result may set it
 * again). */
int
do_scsi_pt(struct sg_pt_base * vp, int device_fd, int time_secs, int verbose)
{
    int fd = device_fd - FREEBSD_FDOFFSET;
    struct sg_pt_freebsd_scsi * ptp = &vp->impl;
    struct freebsd_dev_channel *fdchan;
    union ccb *ccb;
    int len, timout_ms;

    if (NULL == sg_warnings_strm)
        sg_warnings_strm = stderr;
    ptp->os_err = 0;
    if (ptp->in_err) {
        if (verbose)
            fprintf(sg_warnings_strm, "Replicated or unused set_scsi_pt...\n");
        return SCSI_PT_DO_BAD_PARAMS;
    }
    if (NULL == ptp->cdb) {
        if (verbose)
            fprintf(sg_warnings_strm, "No command (cdb) given\n");
        return SCSI_PT_DO_BAD_PARAMS;
    }

    if ((fd < 0) || (fd >= FREEBSD_MAXDEV)) {
        if (verbose)
            fprintf(sg_warnings_strm, "Bad file descriptor\n");
        ptp->os_err = ENODEV;
        return -ptp->os_err;
    }
    fdchan = devicetable[fd];
    if (NULL == fdchan) {
        if (verbose)
            fprintf(sg_warnings_strm, "File descriptor closed??\n");
        ptp->os_err = ENODEV;
        return -ptp->os_err;
    }
    if (NULL == fdchan->cam_dev) {
        if (verbose)
            fprintf(sg_warnings_strm, "No open CAM device\n");
        return SCSI_PT_DO_BAD_PARAMS;
    }

    if (! (ccb = cam_getccb(fdchan->cam_dev))) {
        if (verbose)
            fprintf(sg_warnings_strm, "cam_getccb: failed\n");
        ptp->os_err = ENOMEM;
        return -ptp->os_err;
    }
    ptp->ccb = ccb;

    // clear out structure, except for header that was filled in for us
    bzero(&(&ccb->ccb_h)[1],
            sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr));

    timout_ms = (time_secs > 0) ? (time_secs * 1000) : DEF_TIMEOUT;
    cam_fill_csio(&ccb->csio,
                  /* retries */ 1,
                  /* cbfcnp */ NULL,
                  /* flags */ ptp->dxfer_dir,
                  /* tagaction */ MSG_SIMPLE_Q_TAG,
                  /* dataptr */ ptp->dxferp,
                  /* datalen */ ptp->dxfer_len,
                  /* senselen */ ptp->sense_len,
                  /* cdblen */ ptp->cdb_len,
                  /* timeout (millisecs) */ timout_ms);
    memcpy(ccb->csio.cdb_io.cdb_bytes, ptp->cdb, ptp->cdb_len);

    if (cam_send_ccb(fdchan->cam_dev, ccb) < 0) {
        if (verbose) {
            warn("error sending SCSI ccb");
 #if __FreeBSD_version > 500000
            cam_error_print(fdchan->cam_dev, ccb, CAM_ESF_ALL,
                            CAM_EPF_ALL, stderr);
 #endif
        }
        cam_freeccb(ptp->ccb);
        ptp->ccb = NULL;
        ptp->os_err = EIO;
        return -ptp->os_err;
    }

    if (((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) ||
        ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_SCSI_STATUS_ERROR)) {
        ptp->scsi_status = ccb->csio.scsi_status;
        ptp->resid = ccb->csio.resid;
        ptp->sense_resid = ccb->csio.sense_resid;

        if ((SAM_STAT_CHECK_CONDITION == ptp->scsi_status) ||
            (SAM_STAT_COMMAND_TERMINATED == ptp->scsi_status)) {
            len = ptp->sense_len - ptp->sense_resid;
            if (len)
                memcpy(ptp->sense, &(ccb->csio.sense_data), len);
        }
    } else
        ptp->transport_err = 1;

    ptp->cam_dev = fdchan->cam_dev;     // for error processing
    return 0;
}
示例#4
0
int
smp_send_req(const struct smp_target_obj * tobj, struct smp_req_resp * rresp,
             int verbose)
{
    union ccb *ccb;
    struct tobj_cam_t * tcp;
    int retval, emsk;
    int flags = 0;

    if ((NULL == tobj) || (0 == tobj->opened) || (NULL == tobj->vp)) {
        if (verbose)
            fprintf(stderr, "smp_send_req: nothing open??\n");
        return -1;
    }
    if (I_CAM != tobj->interface_selector) {
        fprintf(stderr, "smp_send_req: unknown transport [%d]\n",
                tobj->interface_selector);
        return -1;
    }
    tcp = (struct tobj_cam_t *)tobj->vp;
    if (! (ccb = cam_getccb(tcp->cam_dev))) {
        fprintf(stderr, "cam_getccb: failed\n");
        return -1;
    }

    // clear out structure, except for header that was filled in for us
    bzero(&(&ccb->ccb_h)[1],
            sizeof(union ccb) - sizeof(struct ccb_hdr));

    flags |= CAM_DEV_QFRZDIS;
    /* CAM does not want request_len including CRC */
    cam_fill_smpio(&ccb->smpio,
                   /*retries*/ 2,       /* guess */
                   /*cbfcnp*/ NULL,
                   /*flags*/ flags,
                   /*smp_request*/ rresp->request,
                   /*smp_request_len*/ rresp->request_len - 4,
                   /*smp_response*/ rresp->response,
                   /*smp_response_len*/ rresp->max_response_len,
                   /*timeout*/ 5000);   /* milliseconds ? */

    ccb->smpio.flags = SMP_FLAG_NONE;

    emsk = 0;
    if (((retval = cam_send_ccb(tcp->cam_dev, ccb)) < 0) ||
        ((((emsk = (ccb->ccb_h.status & CAM_STATUS_MASK))) != CAM_REQ_CMP) &&
         (emsk != CAM_SMP_STATUS_ERROR))) {
        cam_error_print(tcp->cam_dev, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
        cam_freeccb(ccb);
        return -1;
    }
    if (((emsk == CAM_REQ_CMP) || (emsk == CAM_SMP_STATUS_ERROR)) &&
        (rresp->max_response_len > 0)) {
        if ((emsk == CAM_SMP_STATUS_ERROR) && (verbose > 3))
            cam_error_print(tcp->cam_dev, ccb, CAM_ESF_ALL, CAM_EPF_ALL,
                            stderr);
        rresp->act_response_len = -1;
        cam_freeccb(ccb);
        return 0;
    } else {
        fprintf(stderr, "smp_send_req(cam): not sure how it got here\n");
        cam_freeccb(ccb);
        return emsk ? emsk : -1;
    }
}
示例#5
0
int
scsiattrib(struct cam_device *device, int argc, char **argv, char *combinedopt,
	   int retry_count, int timeout, int verbosemode, int err_recover)
{
	union ccb *ccb = NULL;
	int attr_num = -1;
#if 0
	int num_attrs = 0;
#endif
	int start_attr = 0;
	int cached_attr = 0;
	int read_service_action = -1;
	int read_attr = 0, write_attr = 0;
	int element_address = 0;
	int element_type = ELEMENT_TYPE_ALL;
	int partition = 0;
	int logical_volume = 0;
	char *endptr;
	uint8_t *data_buf = NULL;
	uint32_t dxfer_len = UINT16_MAX - 1;
	uint32_t valid_len;
	uint32_t output_format;
	STAILQ_HEAD(, scsi_attr_desc) write_attr_list;
	int error = 0;
	int c;

	ccb = cam_getccb(device);
	if (ccb == NULL) {
		warnx("%s: error allocating CCB", __func__);
		error = 1;
		goto bailout;
	}

	bzero(&(&ccb->ccb_h)[1],
	      sizeof(union ccb) - sizeof(struct ccb_hdr));

	STAILQ_INIT(&write_attr_list);

	/*
	 * By default, when displaying attribute values, we trim out
	 * non-ASCII characters in ASCII fields.  We display all fields
	 * (description, attribute number, attribute size, and readonly
	 * status).  We default to displaying raw text.
	 *
	 * XXX KDM need to port this to stable/10 and newer FreeBSD
	 * versions that have iconv built in and can convert codesets.
	 */
	output_format = SCSI_ATTR_OUTPUT_NONASCII_TRIM |
			SCSI_ATTR_OUTPUT_FIELD_ALL | 
			SCSI_ATTR_OUTPUT_TEXT_RAW;

	data_buf = malloc(dxfer_len);
	if (data_buf == NULL) {
		warn("%s: error allocating %u bytes", __func__, dxfer_len);
		error = 1;
		goto bailout;
	}

	while ((c = getopt(argc, argv, combinedopt)) != -1) {
		switch (c) {
		case 'a':
			attr_num = strtol(optarg, &endptr, 0);
			if (*endptr != '\0') {
				warnx("%s: invalid attribute number %s",
				    __func__, optarg);
				error = 1;
				goto bailout;
			}
			start_attr = attr_num;
			break;
		case 'c':
			cached_attr = 1;
			break;
		case 'e':
			element_address = strtol(optarg, &endptr, 0);
			if (*endptr != '\0') {
				warnx("%s: invalid element address %s",
				    __func__, optarg);
				error = 1;
				goto bailout;
			}
			break;
		case 'F': {
			scsi_nv_status status;
			scsi_attrib_output_flags new_outflags;
			int entry_num = 0;
			char *tmpstr;

			if (isdigit(optarg[0])) {
				output_format = strtoul(optarg, &endptr, 0); 
				if (*endptr != '\0') {
					warnx("%s: invalid numeric output "
					    "format argument %s", __func__,
					    optarg);
					error = 1;
					goto bailout;
				}
				break;
			}
			new_outflags = SCSI_ATTR_OUTPUT_NONE;

			while ((tmpstr = strsep(&optarg, ",")) != NULL) {
				status = scsi_get_nv(output_format_map,
				    sizeof(output_format_map) /
				    sizeof(output_format_map[0]), tmpstr,
				    &entry_num, SCSI_NV_FLAG_IG_CASE);

				if (status == SCSI_NV_FOUND)
					new_outflags |=
					    output_format_map[entry_num].value;
				else {
					warnx("%s: %s format option %s",
					    __func__,
					    (status == SCSI_NV_AMBIGUOUS) ?
					    "ambiguous" : "invalid", tmpstr);
					error = 1;
					goto bailout;
				}
			}
			output_format = new_outflags;
			break;
		}
		case 'p':
			partition = strtol(optarg, &endptr, 0);
			if (*endptr != '\0') {
				warnx("%s: invalid partition number %s",
				    __func__, optarg);
				error = 1;
				goto bailout;
			}
			break;
		case 'r': {
			scsi_nv_status status;
			int entry_num = 0;

			status = scsi_get_nv(sa_map, sizeof(sa_map) /
			    sizeof(sa_map[0]), optarg, &entry_num,
			    SCSI_NV_FLAG_IG_CASE);
			if (status == SCSI_NV_FOUND)
				read_service_action = sa_map[entry_num].value;
			else {
				warnx("%s: %s %s option %s", __func__,
				    (status == SCSI_NV_AMBIGUOUS) ?
				    "ambiguous" : "invalid", "service action",
				    optarg);
				error = 1;
				goto bailout;
			}
			read_attr = 1;
			break;
		}
		case 's':
			start_attr = strtol(optarg, &endptr, 0);
			if (*endptr != '\0') {
				warnx("%s: invalid starting attr argument %s",
				    __func__, optarg);
				error = 1;
				goto bailout;
			}
			break;
		case 'T': {
			scsi_nv_status status;
			int entry_num = 0;

			status = scsi_get_nv(elem_type_map,
			    sizeof(elem_type_map) / sizeof(elem_type_map[0]),
			    optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
			if (status == SCSI_NV_FOUND)
				element_type = elem_type_map[entry_num].value;
			else {
				warnx("%s: %s %s option %s", __func__,
				    (status == SCSI_NV_AMBIGUOUS) ?
				    "ambiguous" : "invalid", "element type",
				    optarg);
				error = 1;
				goto bailout;
			}
			break;
		}
		case 'w':
			warnx("%s: writing attributes is not implemented yet",
			      __func__);
			error = 1;
			goto bailout;
			break;
		case 'V':
			logical_volume = strtol(optarg, &endptr, 0);

			if (*endptr != '\0') {
				warnx("%s: invalid logical volume argument %s",
				    __func__, optarg);
				error = 1;
				goto bailout;
			}
			break;
		default:
			break;
		}
	}

	/*
	 * Default to reading attributes 
	 */
	if (((read_attr == 0) && (write_attr == 0))
	 || ((read_attr != 0) && (write_attr != 0))) {
		warnx("%s: Must specify either -r or -w", __func__);
		error = 1;
		goto bailout;
	}

	if (read_attr != 0) {
		scsi_read_attribute(&ccb->csio,
				    /*retries*/ retry_count,
				    /*cbfcnp*/ NULL,
				    /*tag_action*/ MSG_SIMPLE_Q_TAG,
				    /*service_action*/ read_service_action,
				    /*element*/ element_address,
				    /*elem_type*/ element_type,
				    /*logical_volume*/ logical_volume,
				    /*partition*/ partition,
				    /*first_attribute*/ start_attr,
				    /*cache*/ cached_attr,
				    /*data_ptr*/ data_buf,
				    /*length*/ dxfer_len,
			            /*sense_len*/ SSD_FULL_SIZE,
				    /*timeout*/ timeout ? timeout : 60000);
#if 0
	} else {
#endif

	}

	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;

	if (err_recover != 0)
		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;

	if (cam_send_ccb(device, ccb) < 0) {
		warn("error sending %s ATTRIBUTE", (read_attr != 0) ?
		    "READ" : "WRITE");

		if (verbosemode != 0) {
			cam_error_print(device, ccb, CAM_ESF_ALL,
					CAM_EPF_ALL, stderr);
		}

		error = 1;
		goto bailout;
	}

	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
		if (verbosemode != 0) {
			cam_error_print(device, ccb, CAM_ESF_ALL,
					CAM_EPF_ALL, stderr);
		}
		error = 1;
		goto bailout;
	}

	if (read_attr == 0)
		goto bailout;

	valid_len = dxfer_len - ccb->csio.resid;

	switch (read_service_action) {
	case SRA_SA_ATTR_VALUES: {
		uint32_t len_left, hdr_len, cur_len;
		struct scsi_read_attribute_values *hdr;
		struct scsi_mam_attribute_header *cur_id;
		char error_str[512];
		uint8_t *cur_pos;
		struct sbuf *sb;

		hdr = (struct scsi_read_attribute_values *)data_buf;

		if (valid_len < sizeof(*hdr)) {
			fprintf(stdout, "No attributes returned.\n");
			error = 0;
			goto bailout;
		}

		sb = sbuf_new_auto();
		if (sb == NULL) {
			warn("%s: Unable to allocate sbuf", __func__);
			error = 1;
			goto bailout;
		}
		/*
		 * XXX KDM grab more data if it is available.
		 */
		hdr_len = scsi_4btoul(hdr->length);

		for (len_left = MIN(valid_len, hdr_len),
		     cur_pos = &hdr->attribute_0[0]; len_left > sizeof(*cur_id);
		     len_left -= cur_len, cur_pos += cur_len) {
			int cur_attr_num;
			cur_id = (struct scsi_mam_attribute_header *)cur_pos;
			cur_len = scsi_2btoul(cur_id->length) + sizeof(*cur_id);
			cur_attr_num = scsi_2btoul(cur_id->id);

			if ((attr_num != -1)
			 && (cur_attr_num != attr_num))
				continue;

			error = scsi_attrib_sbuf(sb, cur_id, len_left,
			    /*user_table*/ NULL, /*num_user_entries*/ 0,
			    /*prefer_user_table*/ 0, output_format, error_str,
			    sizeof(error_str));
			if (error != 0) {
				warnx("%s: %s", __func__, error_str);
				sbuf_delete(sb);
				error = 1;
				goto bailout;
			}
			if (attr_num != -1)
				break;
		}

		sbuf_finish(sb);
		fprintf(stdout, "%s", sbuf_data(sb));
		sbuf_delete(sb);
		break;
	}
	case SRA_SA_SUPPORTED_ATTRS:
	case SRA_SA_ATTR_LIST: {
		uint32_t len_left, hdr_len;
		struct scsi_attrib_list_header *hdr;
		struct scsi_attrib_table_entry *entry = NULL;
		const char *sa_name = "Supported Attributes";
		const char *at_name = "Available Attributes";
		int attr_id;
		uint8_t *cur_id;

		hdr = (struct scsi_attrib_list_header *)data_buf;
		if (valid_len < sizeof(*hdr)) {
			fprintf(stdout, "No %s\n",
				(read_service_action == SRA_SA_SUPPORTED_ATTRS)?
				 sa_name : at_name);
			error = 0;
			goto bailout;
		}
		fprintf(stdout, "%s:\n",
			(read_service_action == SRA_SA_SUPPORTED_ATTRS) ?
			 sa_name : at_name);
		hdr_len = scsi_4btoul(hdr->length);
		for (len_left = MIN(valid_len, hdr_len),
		     cur_id = &hdr->first_attr_0[0]; len_left > 1;
		     len_left -= sizeof(uint16_t), cur_id += sizeof(uint16_t)) {
			attr_id = scsi_2btoul(cur_id);

			if ((attr_num != -1)
			 && (attr_id != attr_num))
				continue;

			entry = scsi_get_attrib_entry(attr_id);
			fprintf(stdout, "0x%.4x", attr_id);
			if (entry == NULL)
				fprintf(stdout, "\n");
			else
				fprintf(stdout, ": %s\n", entry->desc);

			if (attr_num != -1)
				break;
		}
		break;
	}
	case SRA_SA_PART_LIST:
	case SRA_SA_LOG_VOL_LIST: {
		struct scsi_attrib_lv_list *lv_list;
		const char *partition_name = "Partition";
		const char *lv_name = "Logical Volume";

		if (valid_len < sizeof(*lv_list)) {
			fprintf(stdout, "No %s list returned\n",
				(read_service_action == SRA_SA_PART_LIST) ?
				partition_name : lv_name);
			error = 0;
			goto bailout;
		}

		lv_list = (struct scsi_attrib_lv_list *)data_buf;

		fprintf(stdout, "First %s: %d\n",
			(read_service_action == SRA_SA_PART_LIST) ?
			partition_name : lv_name,
			lv_list->first_lv_number);
		fprintf(stdout, "Number of %ss: %d\n",
			(read_service_action == SRA_SA_PART_LIST) ?
			partition_name : lv_name,
			lv_list->num_logical_volumes);
		break;
	}
	default:
		break;
	}
bailout:
	if (ccb != NULL)
		cam_freeccb(ccb);

	free(data_buf);

	return (error);
}