/* $NetBSD: uscsi_subr.c,v 1.1 2008/05/14 16:49:48 reinoud Exp $ */ /*- * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Charles M. Hannum; Jason R. Thorpe of the Numerical Aerospace * Simulation Facility, NASA Ames Research Center. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``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 FOUNDATION OR CONTRIBUTORS * 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. * * Small changes, generalisations and Linux support by Reinoud Zandijk * . * */ /* * SCSI support subroutines. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "uscsilib.h" int uscsilib_verbose = 0; #ifdef USCSI_SCSIPI /* * scsipi is a integrated SCSI and ATAPI layer under NetBSD and exists * in a modified form under OpenBSD and possibly also under other * operating systems. */ #include #ifdef __OpenBSD__ #include #else #include #endif int uscsi_open(struct uscsi_dev *disc) { struct stat dstat; disc->fhandle = open(disc->dev_name, O_RDWR, 0); /* no create */ if (disc->fhandle<0) { perror("Failure to open device or file"); return ENODEV; } if (fstat(disc->fhandle, &dstat) < 0) { perror("Can't stat device or file"); uscsi_close(disc); return ENODEV; } return 0; } int uscsi_close(struct uscsi_dev * disc) { close(disc->fhandle); disc->fhandle = -1; return 0; } int uscsi_command(int flags, struct uscsi_dev *disc, void *cmd, size_t cmdlen, void *data, size_t datalen, uint32_t timeout, struct uscsi_sense *uscsi_sense) { scsireq_t req; memset(&req, 0, sizeof(req)); if (uscsi_sense) bzero(uscsi_sense, sizeof(struct uscsi_sense)); memcpy(req.cmd, cmd, cmdlen); req.cmdlen = cmdlen; req.databuf = data; req.datalen = datalen; req.timeout = timeout; req.flags = flags; req.senselen = SENSEBUFLEN; if (ioctl(disc->fhandle, SCIOCCOMMAND, &req) == -1) err(1, "SCIOCCOMMAND"); if (req.retsts == SCCMD_OK) return 0; /* Some problem; report it and exit. */ if (req.retsts == SCCMD_TIMEOUT) { if (uscsilib_verbose) fprintf(stderr, "%s: SCSI command timed out\n", disc->dev_name); return EAGAIN; } else if (req.retsts == SCCMD_BUSY) { if (uscsilib_verbose) fprintf(stderr, "%s: device is busy\n", disc->dev_name); return EBUSY; } else if (req.retsts == SCCMD_SENSE) { if (uscsi_sense) { uscsi_sense->asc = req.sense[12]; uscsi_sense->ascq = req.sense[13]; uscsi_sense->skey_valid = req.sense[15] & 128; uscsi_sense->sense_key = (req.sense[16] << 8) | (req.sense[17]); } if (uscsilib_verbose) uscsi_print_sense((char *) disc->dev_name, req.cmd, req.cmdlen, req.sense, req.senselen_used, 1); return EIO; } else if (uscsilib_verbose) fprintf(stderr, "%s: device had unknown status %x\n", disc->dev_name, req.retsts); return EFAULT; } /* * The reasoning behind this explicit copy is for compatibility with changes * in our uscsi_addr structure. */ int uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr) { struct scsi_addr raddr; int error; bzero(saddr, sizeof(struct scsi_addr)); error = ioctl(disc->fhandle, SCIOCIDENTIFY, &raddr); if (error) return error; #ifdef __NetBSD__ /* scsi and atapi are split up like in uscsi_addr */ if (raddr.type == 0) { saddr->type = USCSI_TYPE_SCSI; saddr->addr.scsi.scbus = raddr.addr.scsi.scbus; saddr->addr.scsi.target = raddr.addr.scsi.target; saddr->addr.scsi.lun = raddr.addr.scsi.lun; } else { saddr->type = USCSI_TYPE_ATAPI; saddr->addr.atapi.atbus = raddr.addr.atapi.atbus; saddr->addr.atapi.drive = raddr.addr.atapi.drive; } #endif #ifdef __OpenBSD__ /* atapi's are shown as SCSI devices */ if (raddr.type == 0) { saddr->type = USCSI_TYPE_SCSI; saddr->addr.scsi.scbus = raddr.scbus; saddr->addr.scsi.target = raddr.target; saddr->addr.scsi.lun = raddr.lun; } else { saddr->type = USCSI_TYPE_ATAPI; saddr->addr.atapi.atbus = raddr.scbus; /* overload */ saddr->addr.atapi.drive = raddr.target; /* overload */ } #endif return 0; } int uscsi_check_for_scsi(struct uscsi_dev *disc) { struct uscsi_addr saddr; return uscsi_identify(disc, &saddr); } #endif /* SCSILIB_SCSIPI */ #ifdef USCSI_LINUX_SCSI /* * Support code for Linux SCSI code. It uses the ioctl() way of * communicating since this is more close to the origional NetBSD * scsipi implementation. */ #include #include #define SENSEBUFLEN 48 int uscsi_open(struct uscsi_dev * disc) { int flags; struct stat stat; /* in Linux we are NOT allowed to open it blocking */ /* no create! */ disc->fhandle = open(disc->dev_name, O_RDWR | O_NONBLOCK, 0); if (disc->fhandle<0) { perror("Failure to open device or file"); return ENODEV; } /* explicitly mark it non blocking (again) (silly Linux) */ flags = fcntl(disc->fhandle, F_GETFL); flags &= ~O_NONBLOCK; fcntl(disc->fhandle, F_SETFL, flags); if (fstat(disc->fhandle, &stat) < 0) { perror("Can't stat device or file"); uscsi_close(disc); return ENODEV; } return 0; } int uscsi_close(struct uscsi_dev * disc) { close(disc->fhandle); disc->fhandle = -1; return 0; } int uscsi_command(int flags, struct uscsi_dev *disc, void *cmd, size_t cmdlen, void *data, size_t datalen, uint32_t timeout, struct uscsi_sense *uscsi_sense) { struct sg_io_hdr req; uint8_t sense_buffer[SENSEBUFLEN]; int error; bzero(&req, sizeof(req)); if (flags == SG_DXFER_FROM_DEV) bzero(data, datalen); req.interface_id = 'S'; req.dxfer_direction = flags; req.cmd_len = cmdlen; req.mx_sb_len = SENSEBUFLEN; req.iovec_count = 0; req.dxfer_len = datalen; req.dxferp = data; req.cmdp = cmd; req.sbp = sense_buffer; req.flags = 0; req.timeout = timeout; error = ioctl(disc->fhandle, SG_IO, &req); if (req.status) { /* Is this OK? */ if (uscsi_sense) { uscsi_sense->asc = sense_buffer[12]; uscsi_sense->ascq = sense_buffer[13]; uscsi_sense->skey_valid = sense_buffer[15] & 128; uscsi_sense->sense_key = (sense_buffer[16] << 8) | (sense_buffer[17]); } if (uscsilib_verbose) { uscsi_print_sense((char *) disc->dev_name, cmd, cmdlen, sense_buffer, req.sb_len_wr, 1); } } return error; } int uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr) { struct sg_scsi_id sg_scsi_id; struct sg_id { /* target | lun << 8 | channel << 16 | low_ino << 24 */ uint32_t tlci; uint32_t uniq_id; } sg_id; int emulated; int error; /* clean result */ bzero(saddr, sizeof(struct uscsi_addr)); /* check if its really SCSI or emulated SCSI (ATAPI f.e.) */ saddr->type = USCSI_TYPE_SCSI; ioctl(disc->fhandle, SG_EMULATED_HOST, &emulated); if (emulated) saddr->type = USCSI_TYPE_ATAPI; /* try 2.4 kernel or older */ error = ioctl(disc->fhandle, SG_GET_SCSI_ID, &sg_scsi_id); if (!error) { saddr->addr.scsi.target = sg_scsi_id.scsi_id; saddr->addr.scsi.lun = sg_scsi_id.lun; saddr->addr.scsi.scbus = sg_scsi_id.channel; return 0; } /* 2.6 kernel or newer */ error = ioctl(disc->fhandle, SCSI_IOCTL_GET_IDLUN, &sg_id); if (error) return error; saddr->addr.scsi.target = (sg_id.tlci ) & 0xff; saddr->addr.scsi.lun = (sg_id.tlci >> 8) & 0xff; saddr->addr.scsi.scbus = (sg_id.tlci >> 16) & 0xff; return 0; } int uscsi_check_for_scsi(struct uscsi_dev *disc) { struct uscsi_addr saddr; return uscsi_identify(disc, &saddr); } #endif /* USCSI_LINUX_SCSI */ #ifdef USCSI_FREEBSD_CAM int uscsi_open(struct uscsi_dev *disc) { disc->devhandle = cam_open_device(disc->dev_name, O_RDWR); if (disc->devhandle == NULL) { disc->fhandle = open(disc->dev_name, O_RDWR | O_NONBLOCK, 0); if (disc->fhandle < 0) { perror("Failure to open device or file"); return ENODEV; } } return 0; } int uscsi_close(struct uscsi_dev *disc) { if (disc->devhandle != NULL) { cam_close_device(disc->devhandle); disc->devhandle = NULL; } else { close(disc->fhandle); disc->fhandle = -1; } return 0; } int uscsi_command(int flags, struct uscsi_dev *disc, void *cmd, size_t cmdlen, void *data, size_t datalen, uint32_t timeout, struct uscsi_sense *uscsi_sense) { struct cam_device *cam_dev; struct scsi_sense_data *cam_sense_data; union ccb ccb; uint32_t cam_sense; uint8_t *keypos; int camflags; memset(&ccb, 0, sizeof(ccb)); cam_dev = (struct cam_device *) disc->devhandle; if (datalen == 0) flags = SCSI_NODATACMD; /* optional : */ /* if (data) assert(flags == SCSI_NODATACMD); */ camflags = CAM_DIR_NONE; if (flags & SCSI_READCMD) camflags = CAM_DIR_IN; if (flags & SCSI_WRITECMD) camflags = CAM_DIR_OUT; cam_fill_csio( &ccb.csio, 0, /* retries */ NULL, /* cbfcnp */ camflags, /* flags */ MSG_SIMPLE_Q_TAG, /* tag_action */ (u_int8_t *) data, /* data_ptr */ datalen, /* dxfer_len */ SSD_FULL_SIZE, /* sense_len */ cmdlen, /* cdb_len */ timeout /* timeout */ ); /* Disable freezing the device queue */ ccb.ccb_h.flags |= CAM_DEV_QFRZDIS; memcpy(ccb.csio.cdb_io.cdb_bytes, cmd, cmdlen); /* Send the command down via the CAM interface */ if (cam_send_ccb(cam_dev, &ccb) < 0) { err(1, "cam_send_ccb"); } if ((ccb.ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) return 0; /* print error using the uscsi_sense routines? */ cam_sense = (ccb.ccb_h.status & (CAM_STATUS_MASK | CAM_AUTOSNS_VALID)); if (cam_sense != (CAM_SCSI_STATUS_ERROR | CAM_AUTOSNS_VALID)) return EFAULT; /* drive responds with sense information */ if (!uscsilib_verbose) return EFAULT; /* print sense info */ cam_sense_data = &ccb.csio.sense_data; if (uscsi_sense) { uscsi_sense->asc = cam_sense_data->add_sense_code; uscsi_sense->ascq = cam_sense_data->add_sense_code_qual; keypos = cam_sense_data->sense_key_spec; uscsi_sense->skey_valid = keypos[0] & 128; uscsi_sense->sense_key = (keypos[1] << 8) | (keypos[2]); } uscsi_print_sense((char *) disc->dev_name, cmd, cmdlen, (uint8_t *) cam_sense_data, 8 + cam_sense_data->extra_len, 1); return EFAULT; } int uscsi_identify(struct uscsi_dev *disc, struct uscsi_addr *saddr) { struct cam_device *cam_dev; /* clean result */ bzero(saddr, sizeof(struct uscsi_addr)); cam_dev = (struct cam_device *) disc->devhandle; if (!cam_dev) return ENODEV; /* check if its really SCSI or emulated SCSI (ATAPI f.e.) ? */ saddr->type = USCSI_TYPE_SCSI; saddr->addr.scsi.target = cam_dev->target_id; saddr->addr.scsi.lun = cam_dev->target_lun; saddr->addr.scsi.scbus = cam_dev->bus_id; return 0; } int uscsi_check_for_scsi(struct uscsi_dev *disc) { struct uscsi_addr saddr; return uscsi_identify(disc, &saddr); } #endif /* USCSI_FREEBSD_CAM */ /* * Generic SCSI funtions also used by the sense printing functionality. * FreeBSD support has it allready asked for by the CAM. */ int uscsi_mode_sense(struct uscsi_dev *dev, uint8_t pgcode, uint8_t pctl, void *buf, size_t len) { scsicmd cmd; bzero(buf, len); /* initialise recieving buffer */ bzero(cmd, SCSI_CMD_LEN); cmd[ 0] = 0x1a; /* MODE SENSE */ cmd[ 1] = 0; /* - */ cmd[ 2] = pgcode | pctl; /* page code and control flags */ cmd[ 3] = 0; /* - */ cmd[ 4] = len; /* length of recieve buffer */ cmd[ 5] = 0; /* control */ return uscsi_command(SCSI_READCMD, dev, &cmd, 6, buf, len, 10000, NULL); } int uscsi_mode_select(struct uscsi_dev *dev, uint8_t byte2, void *buf, size_t len) { scsicmd cmd; bzero(cmd, SCSI_CMD_LEN); cmd[ 0] = 0x15; /* MODE SELECT */ cmd[ 1] = 0x10 | byte2; /* SCSI-2 page format select */ cmd[ 4] = len; /* length of page settings */ cmd[ 5] = 0; /* control */ return uscsi_command(SCSI_WRITECMD, dev, &cmd, 6, buf, len, 10000, NULL); } int uscsi_request_sense(struct uscsi_dev *dev, void *buf, size_t len) { scsicmd cmd; bzero(buf, len); /* initialise recieving buffer */ bzero(cmd, SCSI_CMD_LEN); cmd[ 0] = 0x03; /* REQUEST SENSE */ cmd[ 4] = len; /* length of data to be read */ cmd[ 5] = 0; /* control */ return uscsi_command(SCSI_WRITECMD, dev, &cmd, 6, buf, len, 10000, NULL); } /* end of uscsi_subr.c */