/*
 * ptsim2.ko -- DF-5550 live-path rig: prove that
 *   xpt_remove_periph()  (cam_xpt.c:1602-1617)
 *     -> camq_resize(&device->drvq, array_size - 1)
 * shrinks a drvq that still COUNTS A QUEUED PERIPH
 * (camperiphfree at cam_periph.c:602 never dequeues from drvq),
 * blowing the camq_resize precondition new_size >= entries
 * (guard is #ifdef DIAGNOSTIC -> compiled out on stock kernels),
 * producing the heap OOB write + entries>array_size corruption whose
 * observable aftermath is the DF-5462 lead panic
 *   camq_remove+0x35: movl $0xffffffff,0x8(%r12).
 *
 * Unlike the DF-5462 rig, EVERY entry point here takes the sim lock
 * (pts_lock) -- the previous rig's sysctl rescan path violated the CAM
 * contract, so its panic was confounded.  This rig is contract-clean.
 *
 * Knobs:
 *   debug.ptsim2.ntargets  targets answering INQUIRY (others -> SEL_TIMEOUT)
 *   debug.ptsim2.rescan=1  XPT_SCAN_BUS (sim lock held)
 *   debug.ptsim2.delay     completion delay in ticks (fills the devq so
 *                           periphs get xpt_schedule()d onto drvq and STAY
 *                           there while waiting for alloc openings)
 *   debug.ptsim2.stats     read-only counters
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kthread.h>
#include <sys/sysctl.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/types.h>
#include <sys/spinlock2.h>
#include <sys/queue.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/cam_sim.h>
#include <bus/cam/cam_periph.h>
#include <bus/cam/cam_xpt.h>
#include <bus/cam/cam_xpt_sim.h>
#include <bus/cam/cam_xpt_periph.h>
#include <bus/cam/scsi/scsi_all.h>
#include <bus/cam/scsi/scsi_message.h>

static struct lock		 pts2_lock;
static struct cam_sim		*pts2_sim;
static int			 pts2_ntargets = 8;
static int			 pts2_maxtarget = 8;
static int			 pts2_delay = 0;	/* ticks */
static int			 pts2_hold;		/* stop draining */
static int			 pts2_rescans;
static int			 pts2_ios;
static int			 pts2_deferred;

static TAILQ_HEAD(, ccb_hdr)	 pts2_backlog =
				    TAILQ_HEAD_INITIALIZER(pts2_backlog);
static struct spinlock		 pts2_spin;

static void pts2_action(struct cam_sim *sim, union ccb *ccb);
static void pts2_scan_done(struct cam_periph *periph, union ccb *done_ccb);
static void pts2_poll(struct cam_sim *sim);

static u_int8_t pts2_inq36[36] = {
	0x00,			/* T_DIRECT, qual 0 -> da+pass attach */
	0x00, 0x02, 0x02, 0x00, 0x00, 0x00,
	'E','V','I','L','D','A',' ',' ',
	'P','R','O','C',' ',' ',' ',' ',
	'1','.','0',' ',
};

static void
pts2_readcap10(struct ccb_scsiio *csio)
{
	u_int8_t b[8];

	/* last LBA 0xFFFFF, blocksize 512 */
	b[0] = 0x00; b[1] = 0x0F; b[2] = 0xFF; b[3] = 0xFF;
	b[4] = 0x00; b[5] = 0x00; b[6] = 0x02; b[7] = 0x00;
	csio->data_ptr[0] = b[0];	/* bcopy into user ccb data */
	bcopy(b, csio->data_ptr, 8);
	csio->resid = csio->dxfer_len - 8;
	csio->scsi_status = SCSI_STATUS_OK;
}

static void
pts2_complete(union ccb *ccb, cam_status st)
{
	ccb->ccb_h.status = st;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
pts2_fill_data(struct ccb_scsiio *csio, u_int8_t *src, u_int32_t srclen)
{
	u_int32_t dxfer = csio->dxfer_len;
	u_int32_t copy = dxfer < srclen ? dxfer : srclen;

	if (csio->data_ptr != NULL && copy)
		bcopy(src, csio->data_ptr, copy);
	csio->resid = dxfer - copy;
	csio->scsi_status = SCSI_STATUS_OK;
}

static void
pts2_finish(struct ccb_scsiio *csio, cam_status st)
{
	if (pts2_delay > 0 &&
	    (csio->ccb_h.func_code == XPT_SCSI_IO)) {
		/*
		 * Defer: park it on the backlog; the completer kthread
		 * finishes it delay ticks later.  xpt_done() is
		 * spin-protected and lock-agnostic (cam_xpt.c:4891).
		 */
		spin_lock(&pts2_spin);
		TAILQ_INSERT_TAIL(&pts2_backlog, &csio->ccb_h,
		    sim_links.tqe);
		spin_unlock(&pts2_spin);
		atomic_add_int(&pts2_deferred, 1);
		return;
	}
	pts2_complete((union ccb *)csio, st);
}

static void
pts2_io(struct ccb_scsiio *csio)
{
	u_int8_t *cdb = csio->cdb_io.cdb_bytes;

	if (csio->ccb_h.flags & CAM_CDB_POINTER)
		cdb = csio->cdb_io.cdb_ptr;

	switch (cdb[0]) {
	case 0x12:		/* INQUIRY */
		pts2_fill_data(csio, pts2_inq36, 36);
		pts2_finish(csio, CAM_REQ_CMP);
		return;
	case 0x25:		/* READ CAPACITY (10) */
		pts2_readcap10(csio);
		pts2_finish(csio, CAM_REQ_CMP);
		return;
	case 0x28:		/* READ (10) */
	case 0x08:		/* READ (6) */
		if (csio->data_ptr != NULL && csio->dxfer_len)
			memset(csio->data_ptr, 0x5A, csio->dxfer_len);
		csio->resid = 0;
		csio->scsi_status = SCSI_STATUS_OK;
		pts2_finish(csio, CAM_REQ_CMP);
		return;
	case 0x35:		/* SYNCHRONIZE CACHE */
	case 0x0A:		/* SEND */
	case 0x00:		/* TUR */
	case 0x03:		/* REQUEST SENSE */
		csio->scsi_status = SCSI_STATUS_OK;
		csio->resid = csio->dxfer_len;
		pts2_finish(csio, CAM_REQ_CMP);
		return;
	default:
		pts2_finish(csio, CAM_REQ_INVALID);
		return;
	}
}

static void
pts2_scsi_io(struct ccb_scsiio *csio)
{
	atomic_add_int(&pts2_ios, 1);
	if (csio->ccb_h.target_id >= (target_id_t)pts2_ntargets)
		pts2_complete((union ccb *)csio, CAM_SEL_TIMEOUT);
	else
		pts2_io(csio);
}

static void
pts2_action(struct cam_sim *sim, union ccb *ccb)
{
	switch (ccb->ccb_h.func_code) {
	case XPT_PATH_INQ:
	{
		struct ccb_pathinq *cpi = &ccb->cpi;

		cpi->version_num = 1;
		cpi->hba_inquiry = 0;
		cpi->hba_misc = 0;
		cpi->max_target = pts2_maxtarget - 1;
		cpi->max_lun = 0;
		cpi->initiator_id = 15;
		cpi->unit_number = cam_sim_unit(sim);
		cpi->bus_id = cam_sim_bus(sim);
		cpi->base_transfer_speed = 3300;
		cpi->transport = XPORT_SPI;
		cpi->transport_version = 2;
		cpi->protocol = PROTO_SCSI;
		cpi->protocol_version = SCSI_REV_2;
		cpi->maxio = 65536;
		strncpy(cpi->sim_vid, "EVIL", SIM_IDLEN);
		strncpy(cpi->hba_vid, "EVIL", HBA_IDLEN);
		strncpy(cpi->dev_name, "ptsim2", DEV_IDLEN);
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_GET_TRAN_SETTINGS:
		ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
		ccb->cts.transport = XPORT_SPI;
		ccb->cts.transport_version = 2;
		ccb->cts.protocol = PROTO_SCSI;
		ccb->cts.protocol_version = SCSI_REV_2;
		ccb->cts.xport_specific.spi.valid = 0;
		ccb->cts.proto_specific.scsi.valid = 0;
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	case XPT_SET_TRAN_SETTINGS:
		ccb->ccb_h.status = CAM_REQ_INVALID;
		break;
	case XPT_SCSI_IO:
		pts2_scsi_io(&ccb->csio);
		return;
	default:
		pts2_complete(ccb, CAM_REQ_INVALID);
		return;
	}
	xpt_done(ccb);
}

static void
pts2_poll(struct cam_sim *sim)
{
}

/* ---------------- deferred completer kthread ---------------- */

static void
pts2_completer(void *arg)
{
	struct ccb_hdr *ch;

	for (;;) {
		int n = 0;

		if (pts2_hold) {
			tsleep(&pts2_hold, 0, "pt2h", 1);
			continue;
		}
		spin_lock(&pts2_spin);
		while ((ch = TAILQ_FIRST(&pts2_backlog)) != NULL) {
			TAILQ_REMOVE(&pts2_backlog, ch, sim_links.tqe);
			spin_unlock(&pts2_spin);
			if (pts2_delay > 1)
				tsleep(&pts2_delay, 0, "pt2d", pts2_delay);
			pts2_complete((union ccb *)ch, CAM_REQ_CMP);
			atomic_subtract_int(&pts2_deferred, 1);
			n++;
			spin_lock(&pts2_spin);
		}
		spin_unlock(&pts2_spin);
		if (n == 0)
			tsleep(&pts2_backlog, 0, "pt2w", 1);
		else
			lwkt_yield();
	}
	/* NOTREACHED */
}

/* ---------------- knobs (ALL sim-locked) ---------------- */

static int
pts2_sys_rescan(SYSCTL_HANDLER_ARGS)
{
	int do_it = 0, error;
	struct cam_path *path;
	union ccb *ccb;

	error = sysctl_handle_int(oidp, &do_it, 0, req);
	if (error != 0 || req->newptr == NULL || do_it == 0)
		return (error);
	if (pts2_sim == NULL)
		return (ENXIO);

	/* DF-5550 rig: hold the sim lock across the whole scan, unlike
	 * the DF-5462 rig which entered here unlocked. */
	cam_sim_lock(&pts2_lock);
	if (xpt_create_path(&path, xpt_periph, cam_sim_path(pts2_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) == CAM_REQ_CMP) {
		ccb = xpt_alloc_ccb();
		xpt_setup_ccb(&ccb->ccb_h, path, 5);
		ccb->ccb_h.func_code = XPT_SCAN_BUS;
		ccb->ccb_h.cbfcnp = pts2_scan_done;
		xpt_action(ccb);
		atomic_add_int(&pts2_rescans, 1);
	}
	cam_sim_unlock(&pts2_lock);
	return (0);
}

static int
pts2_sys_ntargets(SYSCTL_HANDLER_ARGS)
{
	int v = pts2_ntargets, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v < 0 || v > 512)
		return (EINVAL);
	cam_sim_lock(&pts2_lock);
	pts2_ntargets = v;
	cam_sim_unlock(&pts2_lock);
	return (0);
}

static int
pts2_sys_delay(SYSCTL_HANDLER_ARGS)
{
	int v = pts2_delay, error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v < 0 || v > hz)
		return (EINVAL);
	pts2_delay = v;
	wakeup(&pts2_backlog);
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, ptsim2, CTLFLAG_RW, 0, "ptsim2 control");
SYSCTL_PROC(_debug_ptsim2, OID_AUTO, ntargets, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, pts2_sys_ntargets, "I", "targets answering INQUIRY (locked)");
SYSCTL_PROC(_debug_ptsim2, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, pts2_sys_rescan, "I", "rescan the ptsim2 bus (locked)");
SYSCTL_PROC(_debug_ptsim2, OID_AUTO, delay, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, pts2_sys_delay, "I", "completion delay in ticks");
SYSCTL_INT(_debug_ptsim2, OID_AUTO, rescans, CTLFLAG_RD, &pts2_rescans, 0,
    "rescans issued");
SYSCTL_INT(_debug_ptsim2, OID_AUTO, ios, CTLFLAG_RD, &pts2_ios, 0,
    "scsi ios seen");
SYSCTL_INT(_debug_ptsim2, OID_AUTO, deferred, CTLFLAG_RD, &pts2_deferred, 0,
    "ccbs sitting in the backlog");
SYSCTL_INT(_debug_ptsim2, OID_AUTO, hold, CTLFLAG_RW, &pts2_hold, 0,
    "completer stops draining (openings never return; periphs stay "
    "parked on drvq)");

/* ---------------- boot choreography ---------------- */

static void
pts2_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	xpt_free_path(done_ccb->ccb_h.path);
	xpt_free_ccb(&done_ccb->ccb_h);
}

static void
pts2_choreography(void *arg)
{
	struct cam_path *path;
	union ccb *ccb;
	cam_status status;

	pts2_sim = cam_sim_alloc(pts2_action, pts2_poll, "ptsim2", NULL, 0,
	    &pts2_lock, 16, 16, NULL);
	if (pts2_sim == NULL) {
		kprintf("[PTSIM2] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&pts2_lock);
	if (xpt_bus_register(pts2_sim, 0) != CAM_SUCCESS) {
		kprintf("[PTSIM2] xpt_bus_register failed\n");
		cam_sim_unlock(&pts2_lock);
		kthread_exit();
	}

	status = xpt_create_path(&path, xpt_periph, cam_sim_path(pts2_sim),
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[PTSIM2] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&pts2_lock);
		kthread_exit();
	}
	ccb = xpt_alloc_ccb();
	xpt_setup_ccb(&ccb->ccb_h, path, 5);
	ccb->ccb_h.func_code = XPT_SCAN_BUS;
	ccb->ccb_h.cbfcnp = pts2_scan_done;
	xpt_action(ccb);

	kprintf("[PTSIM2] initial bus scan done (%d targets)\n",
	    pts2_ntargets);
	cam_sim_unlock(&pts2_lock);
	kthread_exit();
}

static int
ptsim2_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		lockinit(&pts2_lock, "ptsim2", 0, LK_CANRECURSE);
		spin_init(&pts2_spin, "ptsim2");
		kthread_create(pts2_choreography, NULL, NULL, "ptsim2");
		kthread_create(pts2_completer, NULL, NULL, "ptsim2c");
		break;
	case MOD_UNLOAD:
		kprintf("[PTSIM2] unload not supported\n");
		error = EBUSY;
		break;
	default:
		break;
	}
	return (error);
}

static moduledata_t ptsim2_mod = { "ptsim2", ptsim2_modevent, NULL };
DECLARE_MODULE(ptsim2, ptsim2_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
