src/sys/scsi/scsi_ioctl.c

357 lines
9.4 KiB
C

/* $OpenBSD: scsi_ioctl.c,v 1.67 2020/09/22 19:32:53 krw Exp $ */
/* $NetBSD: scsi_ioctl.c,v 1.23 1996/10/12 23:23:17 christos Exp $ */
/*
* Copyright (c) 1994 Charles Hannum. 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Charles Hannum.
* 4. 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 ``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 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.
*/
/*
* Contributed by HD Associates (hd@world.std.com).
* Copyright (c) 1992, 1993 HD Associates
*
* Berkeley style copyright.
*/
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/systm.h>
#include <sys/pool.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <scsi/scsi_all.h>
#include <scsi/scsi_debug.h>
#include <scsi/scsiconf.h>
#include <sys/scsiio.h>
#include <sys/ataio.h>
int scsi_ioc_cmd(struct scsi_link *, scsireq_t *);
int scsi_ioc_ata_cmd(struct scsi_link *, atareq_t *);
const unsigned char scsi_readsafe_cmd[256] = {
[0x00] = 1, /* TEST UNIT READY */
[0x03] = 1, /* REQUEST SENSE */
[0x08] = 1, /* READ(6) */
[0x12] = 1, /* INQUIRY */
[0x1a] = 1, /* MODE SENSE */
[0x1b] = 1, /* START STOP */
[0x23] = 1, /* READ FORMAT CAPACITIES */
[0x25] = 1, /* READ CDVD CAPACITY */
[0x28] = 1, /* READ(10) */
[0x2b] = 1, /* SEEK */
[0x2f] = 1, /* VERIFY(10) */
[0x3c] = 1, /* READ BUFFER */
[0x3e] = 1, /* READ LONG */
[0x42] = 1, /* READ SUBCHANNEL */
[0x43] = 1, /* READ TOC PMA ATIP */
[0x44] = 1, /* READ HEADER */
[0x45] = 1, /* PLAY AUDIO(10) */
[0x46] = 1, /* GET CONFIGURATION */
[0x47] = 1, /* PLAY AUDIO MSF */
[0x48] = 1, /* PLAY AUDIO TI */
[0x4a] = 1, /* GET EVENT STATUS NOTIFICATION */
[0x4b] = 1, /* PAUSE RESUME */
[0x4e] = 1, /* STOP PLAY SCAN */
[0x51] = 1, /* READ DISC INFO */
[0x52] = 1, /* READ TRACK RZONE INFO */
[0x5a] = 1, /* MODE SENSE(10) */
[0x88] = 1, /* READ(16) */
[0x8f] = 1, /* VERIFY(16) */
[0xa4] = 1, /* REPORT KEY */
[0xa5] = 1, /* PLAY AUDIO(12) */
[0xa8] = 1, /* READ(12) */
[0xac] = 1, /* GET PERFORMANCE */
[0xad] = 1, /* READ DVD STRUCTURE */
[0xb9] = 1, /* READ CD MSF */
[0xba] = 1, /* SCAN */
[0xbc] = 1, /* PLAY CD */
[0xbd] = 1, /* MECHANISM STATUS */
[0xbe] = 1 /* READ CD */
};
int
scsi_ioc_cmd(struct scsi_link *link, scsireq_t *screq)
{
struct scsi_xfer *xs;
int err = 0;
if (screq->cmdlen > sizeof(struct scsi_generic))
return EFAULT;
if (screq->datalen > MAXPHYS)
return EINVAL;
xs = scsi_xs_get(link, 0);
if (xs == NULL)
return ENOMEM;
memcpy(&xs->cmd, screq->cmd, screq->cmdlen);
xs->cmdlen = screq->cmdlen;
if (screq->datalen > 0) {
xs->data = dma_alloc(screq->datalen, PR_WAITOK | PR_ZERO);
if (xs->data == NULL) {
err = ENOMEM;
goto err;
}
xs->datalen = screq->datalen;
}
if (ISSET(screq->flags, SCCMD_READ))
SET(xs->flags, SCSI_DATA_IN);
if (ISSET(screq->flags, SCCMD_WRITE)) {
if (screq->datalen > 0) {
err = copyin(screq->databuf, xs->data, screq->datalen);
if (err != 0)
goto err;
}
SET(xs->flags, SCSI_DATA_OUT);
}
SET(xs->flags, SCSI_SILENT); /* User is responsible for errors. */
xs->timeout = screq->timeout;
xs->retries = 0; /* user must do the retries *//* ignored */
scsi_xs_sync(xs);
screq->retsts = 0;
screq->status = xs->status;
switch (xs->error) {
case XS_NOERROR:
/* probably rubbish */
screq->datalen_used = xs->datalen - xs->resid;
screq->retsts = SCCMD_OK;
break;
case XS_SENSE:
SC_DEBUG_SENSE(xs);
screq->senselen_used = min(sizeof(xs->sense),
sizeof(screq->sense));
memcpy(screq->sense, &xs->sense, screq->senselen_used);
screq->retsts = SCCMD_SENSE;
break;
case XS_SHORTSENSE:
SC_DEBUG_SENSE(xs);
printf("XS_SHORTSENSE\n");
screq->senselen_used = min(sizeof(xs->sense),
sizeof(screq->sense));
memcpy(screq->sense, &xs->sense, screq->senselen_used);
screq->retsts = SCCMD_UNKNOWN;
break;
case XS_DRIVER_STUFFUP:
screq->retsts = SCCMD_UNKNOWN;
break;
case XS_TIMEOUT:
screq->retsts = SCCMD_TIMEOUT;
break;
case XS_BUSY:
screq->retsts = SCCMD_BUSY;
break;
default:
screq->retsts = SCCMD_UNKNOWN;
break;
}
if (screq->datalen > 0 && ISSET(screq->flags, SCCMD_READ)) {
err = copyout(xs->data, screq->databuf, screq->datalen);
if (err != 0)
goto err;
}
err:
if (xs->data)
dma_free(xs->data, screq->datalen);
scsi_xs_put(xs);
return err;
}
int
scsi_ioc_ata_cmd(struct scsi_link *link, atareq_t *atareq)
{
struct scsi_xfer *xs;
struct scsi_ata_passthru_12 *cdb;
int err = 0;
if (atareq->datalen > MAXPHYS)
return EINVAL;
xs = scsi_xs_get(link, 0);
if (xs == NULL)
return ENOMEM;
cdb = (struct scsi_ata_passthru_12 *)&xs->cmd;
cdb->opcode = ATA_PASSTHRU_12;
if (atareq->datalen > 0) {
if (ISSET(atareq->flags, ATACMD_READ)) {
cdb->count_proto = ATA_PASSTHRU_PROTO_PIO_DATAIN;
cdb->flags = ATA_PASSTHRU_T_DIR_READ;
} else {
cdb->count_proto = ATA_PASSTHRU_PROTO_PIO_DATAOUT;
cdb->flags = ATA_PASSTHRU_T_DIR_WRITE;
}
SET(cdb->flags, ATA_PASSTHRU_T_LEN_SECTOR_COUNT);
} else {
cdb->count_proto = ATA_PASSTHRU_PROTO_NON_DATA;
cdb->flags = ATA_PASSTHRU_T_LEN_NONE;
}
cdb->features = atareq->features;
cdb->sector_count = atareq->sec_count;
cdb->lba_low = atareq->sec_num;
cdb->lba_mid = atareq->cylinder;
cdb->lba_high = atareq->cylinder >> 8;
cdb->device = atareq->head & 0x0f;
cdb->command = atareq->command;
xs->cmdlen = sizeof(*cdb);
if (atareq->datalen > 0) {
xs->data = dma_alloc(atareq->datalen, PR_WAITOK | PR_ZERO);
if (xs->data == NULL) {
err = ENOMEM;
goto err;
}
xs->datalen = atareq->datalen;
}
if (ISSET(atareq->flags, ATACMD_READ))
SET(xs->flags, SCSI_DATA_IN);
if (ISSET(atareq->flags, ATACMD_WRITE)) {
if (atareq->datalen > 0) {
err = copyin(atareq->databuf, xs->data,
atareq->datalen);
if (err != 0)
goto err;
}
SET(xs->flags, SCSI_DATA_OUT);
}
SET(xs->flags, SCSI_SILENT); /* User is responsible for errors. */
xs->retries = 0; /* user must do the retries *//* ignored */
scsi_xs_sync(xs);
atareq->retsts = ATACMD_ERROR;
switch (xs->error) {
case XS_SENSE:
case XS_SHORTSENSE:
SC_DEBUG_SENSE(xs);
/* XXX this is not right */
case XS_NOERROR:
atareq->retsts = ATACMD_OK;
break;
default:
atareq->retsts = ATACMD_ERROR;
break;
}
if (atareq->datalen > 0 && ISSET(atareq->flags, ATACMD_READ)) {
err = copyout(xs->data, atareq->databuf, atareq->datalen);
if (err != 0)
goto err;
}
err:
if (xs->data)
dma_free(xs->data, atareq->datalen);
scsi_xs_put(xs);
return err;
}
/*
* Something (e.g. another driver) has called us
* with a scsi_link for a target/lun/adapter, and a scsi
* specific ioctl to perform, better try.
*/
int
scsi_do_ioctl(struct scsi_link *link, u_long cmd, caddr_t addr, int flag)
{
SC_DEBUG(link, SDEV_DB2, ("scsi_do_ioctl(0x%lx)\n", cmd));
switch(cmd) {
case SCIOCIDENTIFY: {
struct scsi_addr *sca = (struct scsi_addr *)addr;
if (!ISSET(link->flags, (SDEV_ATAPI | SDEV_UMASS)))
/* A 'real' SCSI target. */
sca->type = TYPE_SCSI;
else
/* An 'emulated' SCSI target. */
sca->type = TYPE_ATAPI;
sca->scbus = link->bus->sc_dev.dv_unit;
sca->target = link->target;
sca->lun = link->lun;
return 0;
}
case SCIOCCOMMAND:
if (scsi_readsafe_cmd[((scsireq_t *)addr)->cmd[0]])
break;
/* FALLTHROUGH */
case ATAIOCCOMMAND:
case SCIOCDEBUG:
if (!ISSET(flag, FWRITE))
return EPERM;
break;
default:
if (link->bus->sb_adapter->ioctl)
return (link->bus->sb_adapter->ioctl)(link, cmd, addr, flag);
else
return ENOTTY;
}
switch(cmd) {
case SCIOCCOMMAND:
return scsi_ioc_cmd(link, (scsireq_t *)addr);
case ATAIOCCOMMAND:
return scsi_ioc_ata_cmd(link, (atareq_t *)addr);
case SCIOCDEBUG: {
int level = *((int *)addr);
SC_DEBUG(link, SDEV_DB3, ("debug set to %d\n", level));
CLR(link->flags, SDEV_DBX); /* clear debug bits */
if (level & 1)
SET(link->flags, SDEV_DB1);
if (level & 2)
SET(link->flags, SDEV_DB2);
if (level & 4)
SET(link->flags, SDEV_DB3);
if (level & 8)
SET(link->flags, SDEV_DB4);
return 0;
}
default:
#ifdef DIAGNOSTIC
panic("scsi_do_ioctl: impossible cmd (%#lx)", cmd);
#endif /* DIAGNOSTIC */
return 0;
}
}