Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/ufdformat/scsi_util.c
/* | |||||
* Copyright (c) 2004 by Bruce M. Simpson. | |||||
* 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. | |||||
* 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. | |||||
* 3. The name of the author may not be used to endorse or promote products | |||||
* derived from this software without specific prior written permission. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/types.h> | |||||
#include <sys/stdint.h> | |||||
#include <sys/ioctl.h> | |||||
#include <sys/fdcio.h> /* XXX */ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include <fcntl.h> | |||||
#include <ctype.h> | |||||
#include <err.h> | |||||
#include <errno.h> | |||||
#include <paths.h> | |||||
#include <sysexits.h> | |||||
#include <cam/cam.h> | |||||
#include <cam/cam_debug.h> | |||||
#include <cam/cam_ccb.h> | |||||
#include <cam/scsi/scsi_all.h> | |||||
#include <cam/scsi/scsi_da.h> | |||||
#include <cam/scsi/scsi_pass.h> | |||||
#include <cam/scsi/scsi_message.h> | |||||
#include <camlib.h> | |||||
#include "scsi_util.h" | |||||
int camverbose = 0; | |||||
void | |||||
dump_flexible_geometry(struct flexible_disk_page *flex) | |||||
{ | |||||
u_int16_t tracks; | |||||
u_int16_t sectorsize; | |||||
tracks = scsi_2btoul(&flex->ncyl_1); | |||||
sectorsize = scsi_2btoul(&flex->bytes_s_1); | |||||
fprintf(stderr, | |||||
"Geometry: %lu cyl %u heads %u secpercyl %lu bytespersec\n", | |||||
(unsigned long)tracks, | |||||
flex->nheads, | |||||
flex->sec_per_track, | |||||
(unsigned long)sectorsize); | |||||
} | |||||
/* | |||||
* Convert UFI geometry to fdc(4) style geometry, using information | |||||
* obtained via MODE_SENSE and READ_FORMAT_CAPACITIES. | |||||
* fcl points to the chosen format_capacity_list entry (normally the | |||||
* current or maximum value) which is to be used to format the disk. | |||||
*/ | |||||
void | |||||
floppy_geom_scsi_to_fdc(struct fd_type *fdt, | |||||
const struct flexible_disk_page *flexpage, | |||||
const struct format_capacity_descriptor *fcd) | |||||
{ | |||||
bzero(fdt, sizeof(*fdt)); | |||||
fdt->sectrac = flexpage->sec_per_track; | |||||
/* | |||||
* Convert sector size to a secsize code for fd_type. | |||||
* Assume that 'sector' and 'block' mean the same thing here. | |||||
* Assume that the sector size is always a perfect power of two. | |||||
*/ | |||||
fdt->secsize = ffs(scsi_3btol((u_int8_t *)&fcd->block_length)) - 8; | |||||
/*fdt->secsize = ffs(scsi_2btoul(&flexpage->bytes_s_1)) - 8;*/ | |||||
fdt->datalen = 0; /* XXX: Gross assumption */ | |||||
fdt->gap = 0; /* XXX: Gross assumption */ | |||||
fdt->tracks = scsi_2btoul((u_int8_t *)&flexpage->ncyl_1); | |||||
fdt->size = scsi_4btoul((u_int8_t *)&fcd->nblocks); /* # LBAs */ | |||||
fdt->trans = FDC_500KBPS; /* XXX: Gross assumption */ | |||||
/*scsi_2btoul(&flexpage->xfr_rate_1);*/ | |||||
fdt->heads = flexpage->nheads; | |||||
fdt->f_gap = 0; /* XXX */ | |||||
fdt->f_inter = 0; /* XXX */ | |||||
fdt->offset_side2 = 0; /* XXX */ | |||||
fdt->flags = FL_MFM; /* not specified within UFI */ | |||||
} | |||||
/* | |||||
* Fill-out the additional parameter data required by UFI/SFF8070i | |||||
* for the 12-byte FORMAT_UNIT command. | |||||
* | |||||
* Notes: | |||||
* ufid points to the structure to be filled out. | |||||
* cap points to the disk format to be used, as returned by the | |||||
* GET_FORMAT_CAPACITIES command. | |||||
* track is the current track to be formatted, or FUFI_ALL_TRACKS | |||||
* for a complete format of the unit. | |||||
* side is the current side to be formatted, or FUFI_SIDE_NONE for | |||||
* a complete format of the unit. | |||||
*/ | |||||
void | |||||
scsi_fillout_format_ufi_data(struct format_ufi_data *ufid, | |||||
u_int8_t track, u_int8_t side, | |||||
struct format_capacity_descriptor *cap) | |||||
{ | |||||
bzero(ufid, sizeof(*ufid)); | |||||
/* | |||||
* Always set the Format Options Valid (FOV) and Disable | |||||
* CeRTification bits. All other bits should be cleared. | |||||
* | |||||
* Set the single track bit if a track-by-track format | |||||
* was specified. | |||||
*/ | |||||
ufid->fdh.byte2 = FU_DLH_FOV | FU_DLH_DCRT; | |||||
ufid->fdh.byte2 |= (track == FUFI_ALL_TRACKS) ? 0 : FU_DLH_STPF; | |||||
/* | |||||
* Set the 'upper side' bit if both a track-by-track forat | |||||
* and a side were specified. | |||||
* Specifies which side if it's a track-by-track format; | |||||
* 0 is the bottom side (lower LBA), 1 is the upper side. | |||||
*/ | |||||
if (track != FUFI_ALL_TRACKS && side == FUFI_SIDE_UPPER) | |||||
ufid->fdh.byte2 |= FU_DLH_VS; | |||||
/* Fillout format_capacity_descriptor with chosen LBA format. */ | |||||
bcopy(cap, &ufid->fcd, sizeof(*cap)); | |||||
ufid->fcd.byte4 = 0x00; | |||||
/* Fillout defect_list_length. */ | |||||
scsi_ulto2b(sizeof(ufid->fcd), ufid->fdh.defect_list_length); | |||||
} | |||||
/* | |||||
* 12-byte version of FORMAT_UNIT, for use with UFI and SFF8070i devices. | |||||
* | |||||
* Notes: | |||||
* The track number will come from the unit's flexible disk geometry | |||||
* according to the current position in the format. | |||||
* The interleave should normally be set to 0. | |||||
* The LUN doesn't get filled out here. | |||||
* The param_buf is a struct format_unit_ufi_data which was previously | |||||
* filled out by scsi_format_unit_ufi_data(). | |||||
*/ | |||||
void | |||||
scsi_format_unit_12(struct ccb_scsiio *csio, u_int32_t retries, | |||||
void (*cbfcnp)(struct cam_periph *, union ccb *), | |||||
u_int8_t tag_action, u_int8_t byte2, u_int8_t track, | |||||
u_int16_t interleave, u_int8_t *param_buf, u_int32_t param_len, | |||||
u_int8_t sense_len, u_int32_t timeout) | |||||
{ | |||||
struct scsi_format_unit_12 *scsi_cmd; | |||||
scsi_cmd = (struct scsi_format_unit_12 *)&csio->cdb_io.cdb_bytes; | |||||
bzero(scsi_cmd, sizeof(*scsi_cmd)); | |||||
scsi_cmd->opcode = FORMAT_UNIT; | |||||
scsi_cmd->byte2 = byte2 | FU_FMT_DATA | 0x07; /* LUN etc */ | |||||
scsi_cmd->track = track; | |||||
scsi_ulto2b(interleave, scsi_cmd->interleave); | |||||
scsi_ulto2b(param_len, scsi_cmd->param_len); | |||||
cam_fill_csio(csio, | |||||
retries, | |||||
cbfcnp, | |||||
CAM_DIR_OUT, | |||||
tag_action, | |||||
param_buf, | |||||
param_len, | |||||
sense_len, | |||||
sizeof(*scsi_cmd), | |||||
timeout); | |||||
} | |||||
/* | |||||
* Prepare a CDB which will return the result of the READ_FORMAT_CAPACITIES | |||||
* command into the user-provided buffer fmt_buf, with length fmt_len, | |||||
* once executed. | |||||
* | |||||
* XXX: Move this to kernel headers when ready. | |||||
*/ | |||||
void | |||||
scsi_read_format_capacities(struct ccb_scsiio *csio, u_int32_t retries, | |||||
void (*cbfcnp)(struct cam_periph *, union ccb *), | |||||
u_int8_t tag_action, u_int8_t byte2, u_int8_t *fmt_buf, | |||||
u_int32_t fmt_len, u_int8_t sense_len, u_int32_t timeout) | |||||
{ | |||||
struct scsi_read_format_capacities *scsi_cmd; | |||||
cam_fill_csio(csio, | |||||
retries, | |||||
cbfcnp, | |||||
CAM_DIR_IN, | |||||
tag_action, | |||||
fmt_buf, | |||||
fmt_len, | |||||
sense_len, | |||||
sizeof(*scsi_cmd), | |||||
timeout); | |||||
scsi_cmd = (struct scsi_read_format_capacities *) | |||||
&csio->cdb_io.cdb_bytes; | |||||
bzero(scsi_cmd, sizeof(*scsi_cmd)); | |||||
scsi_cmd->opcode = READ_FORMAT_CAPACITIES; | |||||
scsi_cmd->byte2 = byte2; /* LUN */ | |||||
scsi_ulto2b(fmt_len, scsi_cmd->alloc_length); | |||||
} | |||||
/* | |||||
* Execute and return the result of the READ_FORMAT_CAPACITIES command. | |||||
*/ | |||||
int | |||||
scsi_get_format_capacities(struct cam_device *device, int retry_count, | |||||
int timeout, u_int8_t *fmt_buf, u_int32_t fmt_len) | |||||
{ | |||||
union ccb *ccb; | |||||
int error = 0; | |||||
ccb = cam_getccb(device); | |||||
if (ccb == NULL) { | |||||
warnx("couldn't allocate CCB"); | |||||
return (1); | |||||
} | |||||
bzero(&(&ccb->ccb_h)[1], | |||||
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); | |||||
bzero(fmt_buf, fmt_len); | |||||
scsi_read_format_capacities(&ccb->csio, | |||||
retry_count, | |||||
NULL, | |||||
MSG_SIMPLE_Q_TAG, | |||||
0, /* XXX: byte2 should contain the LUN */ | |||||
fmt_buf, | |||||
fmt_len, | |||||
SSD_FULL_SIZE, | |||||
timeout ? timeout : 5000); | |||||
/* Disable freezing the device queue */ | |||||
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; | |||||
#if 0 | |||||
if (arglist & CAM_ARG_ERR_RECOVER) | |||||
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; | |||||
#endif | |||||
if (cam_send_ccb(device, ccb) < 0) { | |||||
perror("error sending SCSI get_format_capacities"); | |||||
if (camverbose) | |||||
cam_error_print(device, ccb, CAM_ESF_ALL, | |||||
CAM_EPF_ALL, stderr); | |||||
cam_freeccb(ccb); | |||||
return (1); | |||||
} | |||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { | |||||
error = 1; | |||||
if (camverbose) | |||||
cam_error_print(device, ccb, CAM_ESF_ALL, | |||||
CAM_EPF_ALL, stderr); | |||||
} | |||||
cam_freeccb(ccb); | |||||
return (0); | |||||
} | |||||
/* | |||||
* Perform a SCSI INQUIRY command and return the results in the user | |||||
* provided buffer inq_buf. | |||||
*/ | |||||
int | |||||
scsi_get_inquiry(struct cam_device *device, int retry_count, | |||||
int timeout, struct scsi_inquiry_data *inq_buf) | |||||
{ | |||||
imp: there should be a libcam function to do this already.... | |||||
union ccb *ccb; | |||||
int error = 0; | |||||
ccb = cam_getccb(device); | |||||
if (ccb == NULL) { | |||||
warnx("couldn't allocate CCB"); | |||||
return (1); | |||||
} | |||||
bzero(&(&ccb->ccb_h)[1], | |||||
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); | |||||
bzero(inq_buf, sizeof(*inq_buf)); | |||||
scsi_inquiry(&ccb->csio, | |||||
/* retries */ retry_count, | |||||
/* cbfcnp */ NULL, | |||||
/* tag_action */ MSG_SIMPLE_Q_TAG, | |||||
/* inq_buf */ (u_int8_t *)inq_buf, | |||||
/* inq_len */ SHORT_INQUIRY_LENGTH, | |||||
/* evpd */ 0, | |||||
/* page_code */ 0, | |||||
/* sense_len */ SSD_FULL_SIZE, | |||||
/* timeout */ timeout ? timeout : 5000); | |||||
/* Disable freezing the device queue */ | |||||
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; | |||||
#if 0 | |||||
if (arglist & CAM_ARG_ERR_RECOVER) | |||||
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; | |||||
#endif | |||||
if (cam_send_ccb(device, ccb) < 0) { | |||||
perror("error sending SCSI inquiry"); | |||||
if (camverbose) | |||||
cam_error_print(device, ccb, CAM_ESF_ALL, | |||||
CAM_EPF_ALL, stderr); | |||||
cam_freeccb(ccb); | |||||
return (1); | |||||
} | |||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { | |||||
error = 1; | |||||
if (camverbose) | |||||
cam_error_print(device, ccb, CAM_ESF_ALL, | |||||
CAM_EPF_ALL, stderr); | |||||
} | |||||
cam_freeccb(ccb); | |||||
return (error); | |||||
} | |||||
/* | |||||
* Return the results of MODE_SENSE for the SMS_FLEXIBLE_GEOMETRY_PAGE | |||||
* into the buffer page_data, but don't attempt to parse it. | |||||
*/ | |||||
int | |||||
scsi_get_flexible_disk_page(struct cam_device *cam_dev, | |||||
struct flexible_disk_page *page_data, | |||||
int min_mode_cmd_size, int timeout) | |||||
{ | |||||
struct scsi_mode_header_6 *mh6; | |||||
struct scsi_mode_header_10 *mh10; | |||||
union disk_pages *mode_pars; /* embeds the scsi_mode_page_header */ | |||||
u_int8_t *data; | |||||
size_t data_len; | |||||
int retval; | |||||
data_len = sizeof(union disk_pages); | |||||
data_len += (min_mode_cmd_size < 10) ? | |||||
sizeof(struct scsi_mode_header_6) : | |||||
sizeof(struct scsi_mode_header_10); | |||||
data = malloc(data_len); | |||||
if (data == NULL) | |||||
errx(EX_OSERR, "error allocating mode sense buffer"); | |||||
retval = scsi_get_mode_sense(cam_dev, SMS_FLEXIBLE_GEOMETRY_PAGE, | |||||
SMS_PAGE_CTRL_CURRENT, 0, 0, timeout, | |||||
(u_int8_t *)data, data_len, min_mode_cmd_size); | |||||
if (retval != 0) { | |||||
free(data); | |||||
errx(EX_OSERR, "error sending mode sense command"); | |||||
} | |||||
mh6 = (struct scsi_mode_header_6 *)data; | |||||
mh10 = (struct scsi_mode_header_10 *)data; | |||||
mode_pars = (union disk_pages *)((min_mode_cmd_size < 10) ? | |||||
MODE_PAGE_HEADER_6(mh6) : MODE_PAGE_HEADER_10(mh10)); | |||||
bcopy(&mode_pars->flexible_disk, page_data, | |||||
sizeof(struct flexible_disk_page)); | |||||
free(data); | |||||
return (retval); | |||||
} | |||||
/* | |||||
* Return the result of the MODE SENSE command into the user-provided | |||||
* buffer 'data'. | |||||
*/ | |||||
int | |||||
scsi_get_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, int min_mode_cmd_size) | |||||
impUnsubmitted Not Done Inline Actionsshould be a libcam routine to do this already imp: should be a libcam routine to do this already | |||||
{ | |||||
union ccb *ccb; | |||||
int retval; | |||||
ccb = cam_getccb(device); | |||||
if (ccb == NULL) | |||||
errx(1, "mode_sense: couldn't allocate CCB"); | |||||
bzero(&(&ccb->ccb_h)[1], | |||||
sizeof(struct ccb_scsiio) - sizeof(struct ccb_hdr)); | |||||
bzero(data, datalen); | |||||
scsi_mode_sense_len(&ccb->csio, | |||||
/* retries */ retry_count, | |||||
/* cbfcnp */ NULL, | |||||
/* tag_action */ MSG_SIMPLE_Q_TAG, | |||||
/* dbd */ dbd, | |||||
/* page_code */ page_control << 6, | |||||
/* page */ mode_page, | |||||
/* param_buf */ data, | |||||
/* param_len */ datalen, | |||||
/* minimum_cmd_size */ min_mode_cmd_size, | |||||
/* sense_len */ SSD_FULL_SIZE, | |||||
/* timeout */ timeout ? timeout : 5000); | |||||
/* Disable freezing the device queue */ | |||||
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; | |||||
if (((retval = cam_send_ccb(device, ccb)) < 0) | |||||
|| ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) { | |||||
if (camverbose) | |||||
cam_error_print(device, ccb, CAM_ESF_ALL, | |||||
CAM_EPF_ALL, stderr); | |||||
retval = 1; | |||||
} | |||||
cam_freeccb(ccb); | |||||
return (retval); | |||||
} | |||||
void | |||||
sfds_dump_desc(char *hdr, int dump_code_type, | |||||
struct format_capacity_descriptor *pfd) | |||||
{ | |||||
u_int32_t block_length, nblocks; | |||||
char *type = ""; | |||||
nblocks = scsi_4btoul(pfd->nblocks); | |||||
block_length = scsi_3btol(pfd->block_length); | |||||
switch (pfd->byte4 & FCD_CODE_MASK) { | |||||
case FCD_UNFORMATTED: | |||||
type = "unformatted"; | |||||
break; | |||||
case FCD_FORMATTED: | |||||
type = "formatted"; | |||||
break; | |||||
case FCD_NOMEDIA: | |||||
type = "no disk present"; | |||||
break; | |||||
} | |||||
if (dump_code_type) { | |||||
hdr = ((pfd->byte4 & FCD_CODE_MASK) == FCD_NOMEDIA) ? | |||||
"maximum" : "current"; | |||||
} | |||||
fprintf(stderr, "%s: %lu blocks, %lu bytes-per-block %s\n", hdr, | |||||
(unsigned long)nblocks, (unsigned long)block_length, type); | |||||
} | |||||
void | |||||
sfds_dump_supported_formats(struct supported_format_descriptors *sfds) | |||||
{ | |||||
char hdr[16]; | |||||
struct format_capacity_descriptor *pfd; | |||||
int i, cnt, listlen; | |||||
listlen = sfds->fh.capacity_list_length; | |||||
cnt = listlen / sizeof(sfds->fdsupp[0]) - 1; | |||||
if ((listlen % sizeof(sfds->fdsupp[0])) > 0) { | |||||
fprintf(stderr, | |||||
"WARNING: format capacity list may have been truncated.\n"); | |||||
} | |||||
sfds_dump_desc("currmax", 1, &sfds->fdcurrmax); | |||||
pfd = &sfds->fdsupp[0]; | |||||
for (i = 0; i < cnt; i++, pfd++) { | |||||
snprintf(hdr, sizeof(hdr), "%d", i); | |||||
sfds_dump_desc(hdr, 0, pfd); | |||||
} | |||||
} | |||||
int | |||||
scsi_format_track(struct cam_device *cam_dev, union ccb *ccb, | |||||
int timeout, int cyl, int head, struct format_capacity_descriptor *cap) | |||||
{ | |||||
struct format_ufi_data ufid; | |||||
scsi_fillout_format_ufi_data(&ufid, cyl, head, cap); | |||||
scsi_format_unit_12(&ccb->csio, | |||||
0, | |||||
NULL, | |||||
MSG_SIMPLE_Q_TAG, | |||||
0, /* XXX: lun */ | |||||
cyl, | |||||
0, /* XXX: use default interleave */ | |||||
(u_int8_t *)&ufid, | |||||
sizeof(ufid), | |||||
SSD_FULL_SIZE, | |||||
timeout); | |||||
return (cam_send_ccb(cam_dev, ccb)); | |||||
} | |||||
void | |||||
scsi_verify(struct ccb_scsiio *csio, u_int32_t retries, | |||||
void (*cbfcnp)(struct cam_periph *, union ccb *), | |||||
u_int8_t tag_action, u_int8_t byte2, u_int32_t lba_addr, | |||||
u_int16_t nblocks, u_int8_t sense_len, u_int32_t timeout) | |||||
{ | |||||
struct scsi_verify_12 *scsi_cmd; | |||||
scsi_cmd = (struct scsi_verify_12 *)&csio->cdb_io.cdb_bytes; | |||||
bzero(scsi_cmd, sizeof(*scsi_cmd)); | |||||
scsi_cmd->opcode = VERIFY; | |||||
scsi_cmd->byte2 = byte2; | |||||
scsi_ulto4b(lba_addr, scsi_cmd->addr); | |||||
scsi_ulto2b(nblocks, scsi_cmd->length); | |||||
cam_fill_csio(csio, | |||||
retries, | |||||
cbfcnp, | |||||
CAM_DIR_NONE, | |||||
tag_action, | |||||
NULL, | |||||
0, | |||||
sense_len, | |||||
sizeof(*scsi_cmd), | |||||
timeout); | |||||
} | |||||
/* | |||||
* XXX: Makes some assumptions about drive geometry right now. | |||||
* Assumes 2 heads, 1:1 interleave between heads. | |||||
*/ | |||||
int | |||||
scsi_verify_track(struct cam_device *cam_dev, union ccb *ccb, | |||||
int timeout, int cyl, int head, int spt) | |||||
{ | |||||
u_int32_t lba_addr; | |||||
u_int16_t nblocks; | |||||
#define NHEADS 2 | |||||
lba_addr = cyl * spt * NHEADS; | |||||
nblocks = spt * NHEADS; | |||||
scsi_verify(&ccb->csio, | |||||
0, | |||||
NULL, | |||||
MSG_SIMPLE_Q_TAG, | |||||
0, /* XXX: byte2, lun */ | |||||
lba_addr, | |||||
nblocks, | |||||
SSD_FULL_SIZE, | |||||
timeout); | |||||
return (cam_send_ccb(cam_dev, ccb)); | |||||
#undef NHEADS | |||||
} |
there should be a libcam function to do this already....