#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,
[0x03] = 1,
[0x08] = 1,
[0x12] = 1,
[0x1a] = 1,
[0x1b] = 1,
[0x23] = 1,
[0x25] = 1,
[0x28] = 1,
[0x2b] = 1,
[0x2f] = 1,
[0x3c] = 1,
[0x3e] = 1,
[0x42] = 1,
[0x43] = 1,
[0x44] = 1,
[0x45] = 1,
[0x46] = 1,
[0x47] = 1,
[0x48] = 1,
[0x4a] = 1,
[0x4b] = 1,
[0x4e] = 1,
[0x51] = 1,
[0x52] = 1,
[0x5a] = 1,
[0x88] = 1,
[0x8f] = 1,
[0xa4] = 1,
[0xa5] = 1,
[0xa8] = 1,
[0xac] = 1,
[0xad] = 1,
[0xb9] = 1,
[0xba] = 1,
[0xbc] = 1,
[0xbd] = 1,
[0xbe] = 1
};
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);
xs->timeout = screq->timeout;
xs->retries = 0;
scsi_xs_sync(xs);
screq->retsts = 0;
screq->status = xs->status;
switch (xs->error) {
case XS_NOERROR:
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);
xs->retries = 0;
scsi_xs_sync(xs);
atareq->retsts = ATACMD_ERROR;
switch (xs->error) {
case XS_SENSE:
case XS_SHORTSENSE:
SC_DEBUG_SENSE(xs);
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;
}
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)))
sca->type = TYPE_SCSI;
else
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;
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);
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
return 0;
}
}