NetBSD/usr.sbin/mmcformat/uscsi_subr.c

581 lines
13 KiB
C

/* $NetBSD: uscsi_subr.c,v 1.5 2022/05/28 21:14:57 andvar 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
* <reinoud@netbsd.org>.
*
*/
/*
* SCSI support subroutines.
*/
#include <sys/param.h>
#include <sys/ioctl.h>
#include <err.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <inttypes.h>
#include <assert.h>
#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 <sys/scsiio.h>
#ifdef __OpenBSD__
#include <scsi/uscsi_all.h>
#else
#include <dev/scsipi/scsipi_all.h>
#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 original NetBSD
* scsipi implementation.
*/
#include <scsi/sg.h>
#include <scsi/scsi.h>
#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 functions also used by the sense printing functionality.
* FreeBSD support has it already 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 receiving 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 receive 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 receiving 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 */