/*-
 * Copyright (c) 2010 by Panasas, Inc.
 * 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 immediately at the beginning of the file, without modification,
 *    this list of conditions, and the following disclaimer.
 * 2. 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 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 AUTHOR 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.
 */
/*
 * "Faulty" Device. Victimize random commands with a Selection Timeout.
 */
#include "vhba.h"

#define	MAX_TGT		VHBA_MAXTGT
#define	MAX_LUN		4

#define	DISK_SIZE	32
#define	DISK_SHIFT	9
#define	DISK_NBLKS	((DISK_SIZE << 20) >> DISK_SHIFT)
#define	PSEUDO_SPT	64
#define	PSEUDO_HDS	64
#define	PSEUDO_SPC	(PSEUDO_SPT * PSEUDO_HDS)

typedef struct {
	vhba_softc_t *	vhba;
	uint8_t *	disk;
	size_t		disk_size;
	uint32_t	ctr;
	uint32_t	dead;
	struct task	qt;
} faulty_t;

static void vhba_task(void *, int);
static void faulty_act(faulty_t *, struct ccb_scsiio *);

void
vhba_init(vhba_softc_t *vhba)
{
	static faulty_t vhbastatic;
	vhbastatic.vhba = vhba;
	vhbastatic.disk_size = DISK_SIZE << 20;
	vhbastatic.disk = malloc(vhbastatic.disk_size, M_DEVBUF, M_WAITOK|M_ZERO);
	vhba->private = &vhbastatic;
	vhbastatic.ctr = (arc4random() & 0xffff) + 1;
	TASK_INIT(&vhbastatic.qt, 0, vhba_task, &vhbastatic);
}


void
vhba_fini(vhba_softc_t *vhba)
{
	faulty_t *vhbas = vhba->private;
	vhba->private = NULL;
	free(vhbas->disk, M_DEVBUF);
}

void
vhba_kick(vhba_softc_t *vhba)
{
	faulty_t *vhbas = vhba->private;
	taskqueue_enqueue(taskqueue_swi, &vhbas->qt);
}

static void
vhba_task(void *arg, int pending)
{
	faulty_t *vhbas = arg;
	struct ccb_hdr *ccbh;

	mtx_lock(&vhbas->vhba->lock);
	while ((ccbh = TAILQ_FIRST(&vhbas->vhba->actv)) != NULL) {
		TAILQ_REMOVE(&vhbas->vhba->actv, ccbh, sim_links.tqe);
                faulty_act(vhbas, (struct ccb_scsiio *)ccbh);
		if (--vhbas->ctr == 0) {
			vhbas->dead = 1;
			vhbas->ctr = (arc4random() & 0xff) + 1;
		}
	}
	while ((ccbh = TAILQ_FIRST(&vhbas->vhba->done)) != NULL) {
		TAILQ_REMOVE(&vhbas->vhba->done, ccbh, sim_links.tqe);
		xpt_done((union ccb *)ccbh);
	}
	mtx_unlock(&vhbas->vhba->lock);
}

static void
faulty_act(faulty_t *vhbas, struct ccb_scsiio *csio)
{
	char junk[128];
	cam_status camstatus;
	uint8_t *cdb, *ptr, status;
	uint32_t data_len;
	uint64_t off;
	    
	data_len = 0;
	status = SCSI_STATUS_OK;

	memset(&csio->sense_data, 0, sizeof (csio->sense_data));
	cdb = csio->cdb_io.cdb_bytes;

	if (csio->ccb_h.target_id >=  MAX_TGT) {
		vhba_set_status(&csio->ccb_h, CAM_SEL_TIMEOUT);
		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
		return;
	}
	if (vhbas->dead) {
		vhbas->dead = 0;
		vhba_set_status(&csio->ccb_h, CAM_SEL_TIMEOUT);
		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
		return;
	}
	if (csio->ccb_h.target_lun >= MAX_LUN && cdb[0] != INQUIRY && cdb[0] != REPORT_LUNS && cdb[0] != REQUEST_SENSE) {
		vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x25, 0x0);
		TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
		return;
	}

	switch (cdb[0]) {
	case MODE_SENSE:
	case MODE_SENSE_10:
	{
		unsigned int nbyte;
		uint8_t page = cdb[2] & SMS_PAGE_CODE;
		uint8_t pgctl = cdb[2] & SMS_PAGE_CTRL_MASK;

		switch (page) {
		case SMS_FORMAT_DEVICE_PAGE:
		case SMS_GEOMETRY_PAGE:
		case SMS_CACHE_PAGE:
		case SMS_CONTROL_MODE_PAGE:
		case SMS_ALL_PAGES_PAGE:
			break;
		default:
			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
			TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
			return;
		}
		memset(junk, 0, sizeof (junk));
		if (cdb[1] & SMS_DBD) {
			ptr = &junk[4];
		} else {
			ptr = junk;
			ptr[3] = 8;
			ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
			ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
			ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
			ptr[7] = ((1 << DISK_SHIFT)) & 0xff;

			ptr[8] = (DISK_NBLKS >> 24) & 0xff;
			ptr[9] = (DISK_NBLKS >> 16) & 0xff;
			ptr[10] = (DISK_NBLKS >> 8) & 0xff;
			ptr[11] = DISK_NBLKS & 0xff;
			ptr += 12;
		}

		if (page == SMS_ALL_PAGES_PAGE || page == SMS_FORMAT_DEVICE_PAGE) {
			ptr[0] = SMS_FORMAT_DEVICE_PAGE;
			ptr[1] = 24;
			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
				/* tracks per zone */
				/* ptr[2] = 0; */
				/* ptr[3] = 0; */
				/* alternate sectors per zone */
				/* ptr[4] = 0; */
				/* ptr[5] = 0; */
				/* alternate tracks per zone */
				/* ptr[6] = 0; */
				/* ptr[7] = 0; */
				/* alternate tracks per logical unit */
				/* ptr[8] = 0; */
				/* ptr[9] = 0; */
				/* sectors per track */
				ptr[10] = (PSEUDO_SPT >> 8) & 0xff;
				ptr[11] = PSEUDO_SPT & 0xff;
				/* data bytes per physical sector */
				ptr[12] = ((1 << DISK_SHIFT) >> 8) & 0xff;
				ptr[13] = (1 << DISK_SHIFT) & 0xff;
				/* interleave */
				/* ptr[14] = 0; */
				/* ptr[15] = 1; */
				/* track skew factor */
				/* ptr[16] = 0; */
				/* ptr[17] = 0; */
				/* cylinder skew factor */
				/* ptr[18] = 0; */
				/* ptr[19] = 0; */
				/* SSRC, HSEC, RMB, SURF */
			}
			ptr += 26;
		}

		if (page == SMS_ALL_PAGES_PAGE || page == SMS_GEOMETRY_PAGE) {
			ptr[0] = SMS_GEOMETRY_PAGE;
			ptr[1] = 24;
			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
				uint32_t cyl = (DISK_NBLKS + ((PSEUDO_SPC - 1))) / PSEUDO_SPC;
				/* number of cylinders */
				ptr[2] = (cyl >> 24) & 0xff;
				ptr[3] = (cyl >> 16) & 0xff;
				ptr[4] = cyl & 0xff;
				/* number of heads */
				ptr[5] = PSEUDO_HDS;
				/* starting cylinder- write precompensation */
				/* ptr[6] = 0; */
				/* ptr[7] = 0; */
				/* ptr[8] = 0; */
				/* starting cylinder- reduced write current */
				/* ptr[9] = 0; */
				/* ptr[10] = 0; */
				/* ptr[11] = 0; */
				/* drive step rate */
				/* ptr[12] = 0; */
				/* ptr[13] = 0; */
				/* landing zone cylinder */
				/* ptr[14] = 0; */
				/* ptr[15] = 0; */
				/* ptr[16] = 0; */
				/* RPL */
				/* ptr[17] = 0; */
				/* rotational offset */
				/* ptr[18] = 0; */
				/* medium rotation rate -  7200 RPM */
				ptr[20] = 0x1c;
				ptr[21] = 0x20;
			}
			ptr += 26;
		}

		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CACHE_PAGE) {
			ptr[0] = SMS_CACHE_PAGE;
			ptr[1] = 18;
			ptr[2] = 1 << 2;
			ptr += 20;
		}

		if (page == SMS_ALL_PAGES_PAGE || page == SMS_CONTROL_MODE_PAGE) {
			ptr[0] = SMS_CONTROL_MODE_PAGE;
			ptr[1] = 10;
			if (pgctl != SMS_PAGE_CTRL_CHANGEABLE) {
				ptr[3] = 1 << 4; /* unrestricted reordering allowed */
				ptr[8] = 0x75;   /* 30000 ms */
				ptr[9] = 0x30;
			}
			ptr += 12;
		}
		nbyte = (char *)ptr - &junk[0];
		ptr[0] = nbyte - 4;

		if (cdb[0] == MODE_SENSE) {
			data_len = min(cdb[4], csio->dxfer_len);
		} else {
			uint16_t tw = (cdb[7] << 8) | cdb[8];
			data_len = min(tw, csio->dxfer_len);
		}
		data_len = min(data_len, nbyte);
		if (data_len) {
			memcpy(csio->data_ptr, junk, data_len);
		}
		csio->resid = csio->dxfer_len - data_len;
		break;
	}
	case READ_6:
	case READ_10:
	case READ_12:
	case READ_16:
	case WRITE_6:
	case WRITE_10:
	case WRITE_12:
	case WRITE_16:
		if (vhba_rwparm(cdb, &off, &data_len, DISK_NBLKS, DISK_SHIFT)) {
			vhba_fill_sense(csio, SSD_KEY_ILLEGAL_REQUEST, 0x24, 0x0);
			break;
		}
		if (data_len) {
			if ((cdb[0] & 0xf) == 8) {
				memcpy(csio->data_ptr, &vhbas->disk[off], data_len);
			} else {
				memcpy(&vhbas->disk[off], csio->data_ptr, data_len);
			}
			csio->resid = csio->dxfer_len - data_len;
		} else {
			csio->resid = csio->dxfer_len;
		}
		break;

	case READ_CAPACITY:
		if (cdb[2] || cdb[3] || cdb[4] || cdb[5]) {
			vhba_fill_sense(csio, SSD_KEY_UNIT_ATTENTION, 0x24, 0x0);
			break;
		}
		if (cdb[8] & 0x1) { /* PMI */
			csio->data_ptr[0] = 0xff;
			csio->data_ptr[1] = 0xff;
			csio->data_ptr[2] = 0xff;
			csio->data_ptr[3] = 0xff;
		} else {
			uint64_t last_blk = DISK_NBLKS - 1;
			if (last_blk < 0xffffffffULL) {
			    csio->data_ptr[0] = (last_blk >> 24) & 0xff;
			    csio->data_ptr[1] = (last_blk >> 16) & 0xff;
			    csio->data_ptr[2] = (last_blk >>  8) & 0xff;
			    csio->data_ptr[3] = (last_blk) & 0xff;
			} else {
			    csio->data_ptr[0] = 0xff;
			    csio->data_ptr[1] = 0xff;
			    csio->data_ptr[2] = 0xff;
			    csio->data_ptr[3] = 0xff;
			}
		}
		csio->data_ptr[4] = ((1 << DISK_SHIFT) >> 24) & 0xff;
		csio->data_ptr[5] = ((1 << DISK_SHIFT) >> 16) & 0xff;
		csio->data_ptr[6] = ((1 << DISK_SHIFT) >>  8) & 0xff;
		csio->data_ptr[7] = ((1 << DISK_SHIFT)) & 0xff;
		break;
	default:
		vhba_default_cmd(csio, MAX_LUN, NULL);
		break;
	}
	if (csio->scsi_status != SCSI_STATUS_OK) {
		camstatus = CAM_SCSI_STATUS_ERROR;
		if (csio->scsi_status == SCSI_STATUS_CHECK_COND) {
			camstatus |= CAM_AUTOSNS_VALID;
		}
	} else {
		csio->scsi_status = SCSI_STATUS_OK;
		camstatus = CAM_REQ_CMP;
	}
	vhba_set_status(&csio->ccb_h, camstatus);
	TAILQ_INSERT_TAIL(&vhbas->vhba->done, &csio->ccb_h, sim_links.tqe);
}
DEV_MODULE(vhba_faulty, vhba_modprobe, NULL);
