Beispiel #1
0
static rtems_status_code
rtems_bsd_scsi_read_capacity(union ccb *ccb, uint32_t *block_count, uint32_t *block_size)
{
	rtems_status_code sc = RTEMS_SUCCESSFUL;
	struct scsi_read_capacity_data rdcap;

	memset(&rdcap, 0, sizeof(rdcap));

	scsi_read_capacity(
		&ccb->csio,
		BSD_SCSI_RETRIES,
		rtems_bsd_ccb_callback,
		BSD_SCSI_TAG,
		&rdcap,
		SSD_FULL_SIZE,
		BSD_SCSI_TIMEOUT
	);

	sc = rtems_bsd_ccb_action(ccb);
	if (sc != RTEMS_SUCCESSFUL) {
		return RTEMS_IO_ERROR;
	}

	*block_size = scsi_4btoul(rdcap.length);
	*block_count = scsi_4btoul(rdcap.addr) + 1;

	return RTEMS_SUCCESSFUL;
}
Beispiel #2
0
/*
 * Convert the SCSI command in ccb to an ata_xfer command in xa
 * for ATA_PORT_T_DISK operations.  Set the completion function
 * to convert the response back, then dispatch to the OpenBSD AHCI
 * layer.
 *
 * AHCI DISK commands only support a limited command set, and we
 * fake additional commands to make it play nice with the CAM subsystem.
 */
static
void
ahci_xpt_scsi_disk_io(struct ahci_port *ap, struct ata_port *atx,
		      union ccb *ccb)
{
	struct ccb_hdr *ccbh;
	struct ccb_scsiio *csio;
	struct ata_xfer *xa;
	struct ata_port	*at;
	struct ata_fis_h2d *fis;
	struct ata_pass_12 *atp12;
	struct ata_pass_16 *atp16;
	scsi_cdb_t cdb;
	union scsi_data *rdata;
	int rdata_len;
	u_int64_t capacity;
	u_int64_t lba;
	u_int32_t count;

	ccbh = &ccb->csio.ccb_h;
	csio = &ccb->csio;
	at = atx ? atx : ap->ap_ata[0];

	/*
	 * XXX not passing NULL at for direct attach!
	 */
	xa = ahci_ata_get_xfer(ap, atx);
	rdata = (void *)csio->data_ptr;
	rdata_len = csio->dxfer_len;

	/*
	 * Build the FIS or process the csio to completion.
	 */
	cdb = (void *)((ccbh->flags & CAM_CDB_POINTER) ?
			csio->cdb_io.cdb_ptr : csio->cdb_io.cdb_bytes);

	switch(cdb->generic.opcode) {
	case REQUEST_SENSE:
		/*
		 * Auto-sense everything, so explicit sense requests
		 * return no-sense.
		 */
		ccbh->status = CAM_SCSI_STATUS_ERROR;
		break;
	case INQUIRY:
		/*
		 * Inquiry supported features
		 *
		 * [opcode, byte2, page_code, length, control]
		 */
		if (cdb->inquiry.byte2 & SI_EVPD) {
			ahci_xpt_page_inquiry(ap, at, ccb);
		} else {
			bzero(rdata, rdata_len);
			if (rdata_len < SHORT_INQUIRY_LENGTH) {
				ccbh->status = CAM_CCB_LEN_ERR;
				break;
			}
			if (rdata_len > sizeof(rdata->inquiry_data))
				rdata_len = sizeof(rdata->inquiry_data);
			rdata->inquiry_data.device = T_DIRECT;
			rdata->inquiry_data.version = SCSI_REV_SPC2;
			rdata->inquiry_data.response_format = 2;
			rdata->inquiry_data.additional_length = 32;
			bcopy("SATA    ", rdata->inquiry_data.vendor, 8);
			bcopy(at->at_identify.model,
			      rdata->inquiry_data.product,
			      sizeof(rdata->inquiry_data.product));
			bcopy(at->at_identify.firmware,
			      rdata->inquiry_data.revision,
			      sizeof(rdata->inquiry_data.revision));
			ccbh->status = CAM_REQ_CMP;
		}
		
		/*
		 * Use the vendor specific area to set the TRIM status
		 * for scsi_da
		 */
		if (at->at_identify.support_dsm) {
			rdata->inquiry_data.vendor_specific1[0] =
			    at->at_identify.support_dsm &ATA_SUPPORT_DSM_TRIM;
			rdata->inquiry_data.vendor_specific1[1] = 
			    at->at_identify.max_dsm_blocks;
		}
		break;
	case READ_CAPACITY_16:
		if (cdb->read_capacity_16.service_action != SRC16_SERVICE_ACTION) {
			ccbh->status = CAM_REQ_INVALID;
			break;
		}
		if (rdata_len < sizeof(rdata->read_capacity_data_16)) {
			ccbh->status = CAM_CCB_LEN_ERR;
			break;
		}
		/* fall through */
	case READ_CAPACITY:
		if (rdata_len < sizeof(rdata->read_capacity_data)) {
			ccbh->status = CAM_CCB_LEN_ERR;
			break;
		}

		capacity = at->at_capacity;

		bzero(rdata, rdata_len);
		if (cdb->generic.opcode == READ_CAPACITY) {
			rdata_len = sizeof(rdata->read_capacity_data);
			if (capacity > 0xFFFFFFFFU)
				capacity = 0xFFFFFFFFU;
			bzero(&rdata->read_capacity_data, rdata_len);
			scsi_ulto4b((u_int32_t)capacity - 1,
				    rdata->read_capacity_data.addr);
			scsi_ulto4b(512, rdata->read_capacity_data.length);
		} else {
			rdata_len = sizeof(rdata->read_capacity_data_16);
			bzero(&rdata->read_capacity_data_16, rdata_len);
			scsi_u64to8b(capacity - 1,
				     rdata->read_capacity_data_16.addr);
			scsi_ulto4b(512, rdata->read_capacity_data_16.length);
		}
		ccbh->status = CAM_REQ_CMP;
		break;
	case SYNCHRONIZE_CACHE:
		/*
		 * Synchronize cache.  Specification says this can take
		 * greater then 30 seconds so give it at least 45.
		 */
		fis = xa->fis;
		fis->flags = ATA_H2D_FLAGS_CMD;
		fis->command = ATA_C_FLUSH_CACHE;
		fis->device = 0;
		if (xa->timeout < 45000)
			xa->timeout = 45000;
		xa->datalen = 0;
		xa->flags = 0;
		xa->complete = ahci_ata_complete_disk_synchronize_cache;
		break;
	case TRIM:
		fis = xa->fis;
		fis->command = ATA_C_DATA_SET_MANAGEMENT;
		fis->features = (u_int8_t)ATA_SF_DSM_TRIM;
		fis->features_exp = (u_int8_t)(ATA_SF_DSM_TRIM>> 8);

		xa->flags = ATA_F_WRITE;
		fis->flags = ATA_H2D_FLAGS_CMD;

		xa->data = csio->data_ptr;
		xa->datalen = csio->dxfer_len;
		xa->timeout = ccbh->timeout*50;	/* milliseconds */

		fis->sector_count =(u_int8_t)(xa->datalen/512);
		fis->sector_count_exp =(u_int8_t)((xa->datalen/512)>>8);

		lba = 0;
		fis->lba_low = (u_int8_t)lba;
		fis->lba_mid = (u_int8_t)(lba >> 8);
		fis->lba_high = (u_int8_t)(lba >> 16);
		fis->lba_low_exp = (u_int8_t)(lba >> 24);
		fis->lba_mid_exp = (u_int8_t)(lba >> 32);
		fis->lba_high_exp = (u_int8_t)(lba >> 40);
		   
		fis->device = ATA_H2D_DEVICE_LBA;
		xa->data = csio->data_ptr;

		xa->complete = ahci_ata_complete_disk_rw;
		ccbh->status = CAM_REQ_INPROG;
		break;
	case TEST_UNIT_READY:
	case START_STOP_UNIT:
	case PREVENT_ALLOW:
		/*
		 * Just silently return success
		 */
		ccbh->status = CAM_REQ_CMP;
		rdata_len = 0;
		break;
	case ATA_PASS_12:
		atp12 = &cdb->ata_pass_12;
		fis = xa->fis;
		/*
		 * Figure out the flags to be used, depending on the direction of the
		 * CAM request.
		 */
		switch (ccbh->flags & CAM_DIR_MASK) {
		case CAM_DIR_IN:
			xa->flags = ATA_F_READ;
			break;
		case CAM_DIR_OUT:
			xa->flags = ATA_F_WRITE;
			break;
		default:
			xa->flags = 0;
		}
		xa->flags |= ATA_F_POLL | ATA_F_EXCLUSIVE;
		xa->data = csio->data_ptr;
		xa->datalen = csio->dxfer_len;
		xa->complete = ahci_ata_complete_disk_rw;
		xa->timeout = ccbh->timeout;

		/*
		 * Populate the fis from the information we received through CAM
		 * ATA passthrough.
		 */
		fis->flags = ATA_H2D_FLAGS_CMD;	/* maybe also atp12->flags ? */
		fis->features = atp12->features;
		fis->sector_count = atp12->sector_count;
		fis->lba_low = atp12->lba_low;
		fis->lba_mid = atp12->lba_mid;
		fis->lba_high = atp12->lba_high;
		fis->device = atp12->device;	/* maybe always 0? */
		fis->command = atp12->command;
		fis->control = atp12->control;

		/*
		 * Mark as in progress so it is sent to the device.
		 */
		ccbh->status = CAM_REQ_INPROG;
		break;
	case ATA_PASS_16:
		atp16 = &cdb->ata_pass_16;
		fis = xa->fis;
		/*
		 * Figure out the flags to be used, depending on the direction of the
		 * CAM request.
		 */
		switch (ccbh->flags & CAM_DIR_MASK) {
		case CAM_DIR_IN:
			xa->flags = ATA_F_READ;
			break;
		case CAM_DIR_OUT:
			xa->flags = ATA_F_WRITE;
			break;
		default:
			xa->flags = 0;
		}
		xa->flags |= ATA_F_POLL | ATA_F_EXCLUSIVE;
		xa->data = csio->data_ptr;
		xa->datalen = csio->dxfer_len;
		xa->complete = ahci_ata_complete_disk_rw;
		xa->timeout = ccbh->timeout;

		/*
		 * Populate the fis from the information we received through CAM
		 * ATA passthrough.
		 */
		fis->flags = ATA_H2D_FLAGS_CMD;	/* maybe also atp16->flags ? */
		fis->features = atp16->features;
		fis->features_exp = atp16->features_ext;
		fis->sector_count = atp16->sector_count;
		fis->sector_count_exp = atp16->sector_count_ext;
		fis->lba_low = atp16->lba_low;
		fis->lba_low_exp = atp16->lba_low_ext;
		fis->lba_mid = atp16->lba_mid;
		fis->lba_mid_exp = atp16->lba_mid_ext;
		fis->lba_high = atp16->lba_high;
		fis->lba_mid_exp = atp16->lba_mid_ext;
		fis->device = atp16->device;	/* maybe always 0? */
		fis->command = atp16->command;

		/*
		 * Mark as in progress so it is sent to the device.
		 */
		ccbh->status = CAM_REQ_INPROG;
		break;
	default:
		switch(cdb->generic.opcode) {
		case READ_6:
			lba = scsi_3btoul(cdb->rw_6.addr) & 0x1FFFFF;
			count = cdb->rw_6.length ? cdb->rw_6.length : 0x100;
			xa->flags = ATA_F_READ;
			break;
		case READ_10:
			lba = scsi_4btoul(cdb->rw_10.addr);
			count = scsi_2btoul(cdb->rw_10.length);
			xa->flags = ATA_F_READ;
			break;
		case READ_12:
			lba = scsi_4btoul(cdb->rw_12.addr);
			count = scsi_4btoul(cdb->rw_12.length);
			xa->flags = ATA_F_READ;
			break;
		case READ_16:
			lba = scsi_8btou64(cdb->rw_16.addr);
			count = scsi_4btoul(cdb->rw_16.length);
			xa->flags = ATA_F_READ;
			break;
		case WRITE_6:
			lba = scsi_3btoul(cdb->rw_6.addr) & 0x1FFFFF;
			count = cdb->rw_6.length ? cdb->rw_6.length : 0x100;
			xa->flags = ATA_F_WRITE;
			break;
		case WRITE_10:
			lba = scsi_4btoul(cdb->rw_10.addr);
			count = scsi_2btoul(cdb->rw_10.length);
			xa->flags = ATA_F_WRITE;
			break;
		case WRITE_12:
			lba = scsi_4btoul(cdb->rw_12.addr);
			count = scsi_4btoul(cdb->rw_12.length);
			xa->flags = ATA_F_WRITE;
			break;
		case WRITE_16:
			lba = scsi_8btou64(cdb->rw_16.addr);
			count = scsi_4btoul(cdb->rw_16.length);
			xa->flags = ATA_F_WRITE;
			break;
		default:
			ccbh->status = CAM_REQ_INVALID;
			break;
		}
		if (ccbh->status != CAM_REQ_INPROG)
			break;

		fis = xa->fis;
		fis->flags = ATA_H2D_FLAGS_CMD;
		fis->lba_low = (u_int8_t)lba;
		fis->lba_mid = (u_int8_t)(lba >> 8);
		fis->lba_high = (u_int8_t)(lba >> 16);
		fis->device = ATA_H2D_DEVICE_LBA;

		/*
		 * NCQ only for direct-attached disks, do not currently
		 * try to use NCQ with port multipliers.
		 */
		if (at->at_ncqdepth > 1 &&
		    ap->ap_type == ATA_PORT_T_DISK &&
		    (ap->ap_sc->sc_cap & AHCI_REG_CAP_SNCQ) &&
		    (ccbh->flags & CAM_POLLED) == 0) {
			/*
			 * Use NCQ - always uses 48 bit addressing
			 */
			xa->flags |= ATA_F_NCQ;
			fis->command = (xa->flags & ATA_F_WRITE) ?
					ATA_C_WRITE_FPDMA : ATA_C_READ_FPDMA;
			fis->lba_low_exp = (u_int8_t)(lba >> 24);
			fis->lba_mid_exp = (u_int8_t)(lba >> 32);
			fis->lba_high_exp = (u_int8_t)(lba >> 40);
			fis->sector_count = xa->tag << 3;
			fis->features = (u_int8_t)count;
			fis->features_exp = (u_int8_t)(count >> 8);
		} else if (count > 0x100 || lba > 0x0FFFFFFFU) {
Beispiel #3
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);
}