/*	$OpenBSD: scsi_ioctl.c,v 1.51 2015/06/07 19:13:27 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/types.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/file.h>
#include <sys/pool.h>
#include <sys/device.h>
#include <sys/fcntl.h>

#include <scsi/scsi_all.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 (screq->flags & SCCMD_READ)
		xs->flags |= SCSI_DATA_IN;
	if (screq->flags & SCCMD_WRITE) {
		if (screq->datalen > 0) {
			err = copyin(screq->databuf, xs->data, screq->datalen);
			if (err != 0)
				goto err;
		}

		xs->flags |= SCSI_DATA_OUT;
	}

	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:
#ifdef SCSIDEBUG
		scsi_sense_print_debug(xs);
#endif
		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:
#ifdef SCSIDEBUG
		scsi_sense_print_debug(xs);
#endif
		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 && 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 (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;
		}
		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 (atareq->flags & ATACMD_READ)
		xs->flags |= SCSI_DATA_IN;
	if (atareq->flags & ATACMD_WRITE) {
		if (atareq->datalen > 0) {
			err = copyin(atareq->databuf, xs->data,
			    atareq->datalen);
			if (err != 0)
				goto err;
		}

		xs->flags |= SCSI_DATA_OUT;
	}

	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:
#ifdef SCSIDEBUG
		scsi_sense_print_debug(xs);
#endif
		/* XXX this is not right */
	case XS_NOERROR:
		atareq->retsts = ATACMD_OK;
		break;
	default:
		atareq->retsts = ATACMD_ERROR;
		break;
	}

	if (atareq->datalen > 0 && 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 an sc_link for a target/lun/adapter, and a scsi
 * specific ioctl to perform, better try.
 */
int
scsi_do_ioctl(struct scsi_link *sc_link, u_long cmd, caddr_t addr, int flag)
{
	SC_DEBUG(sc_link, SDEV_DB2, ("scsi_do_ioctl(0x%lx)\n", cmd));

	switch(cmd) {
	case SCIOCIDENTIFY: {
		struct scsi_addr *sca = (struct scsi_addr *)addr;

		if ((sc_link->flags & (SDEV_ATAPI | SDEV_UMASS)) == 0)
			/* A 'real' SCSI target. */
			sca->type = TYPE_SCSI;
		else
			/* An 'emulated' SCSI target. */
			sca->type = TYPE_ATAPI;
		sca->scbus = sc_link->bus->sc_dev.dv_unit;
		sca->target = sc_link->target;
		sca->lun = sc_link->lun;
		return (0);
	}
	case SCIOCCOMMAND:
		if (scsi_readsafe_cmd[((scsireq_t *)addr)->cmd[0]])
			break;
		/* FALLTHROUGH */
	case ATAIOCCOMMAND:
	case SCIOCDEBUG:
		if ((flag & FWRITE) == 0)
			return (EPERM);
		break;
	default:
		if (sc_link->adapter->ioctl)
			return ((sc_link->adapter->ioctl)(sc_link, cmd, addr,
			    flag));
		else
			return (ENOTTY);
	}

	switch(cmd) {
	case SCIOCCOMMAND:
		return (scsi_ioc_cmd(sc_link, (scsireq_t *)addr));
	case ATAIOCCOMMAND:
		return (scsi_ioc_ata_cmd(sc_link, (atareq_t *)addr));
	case SCIOCDEBUG: {
		int level = *((int *)addr);

		SC_DEBUG(sc_link, SDEV_DB3, ("debug set to %d\n", level));
		sc_link->flags &= ~SDEV_DBX; /* clear debug bits */
		if (level & 1)
			sc_link->flags |= SDEV_DB1;
		if (level & 2)
			sc_link->flags |= SDEV_DB2;
		if (level & 4)
			sc_link->flags |= SDEV_DB3;
		if (level & 8)
			sc_link->flags |= SDEV_DB4;
		return (0);
	}
	default:
#ifdef DIAGNOSTIC
		panic("scsi_do_ioctl: impossible cmd (%#lx)", cmd);
#endif
		return (0);
	}
}
