/*
 * passsim.c -- fake CAM SIM for scsi_pass.c pass-2 PoC (DF-5499 evidence rig,
 * built on the DF-5240 evilsim / DF-5375 targsim pattern).
 *
 * Provides:
 *   - registered SIM ("passsim") with a direct-access device at (bus,0,0),
 *     normal INQUIRY/TUR so probe announces it and da+pass periphs attach
 *     (a new /dev/passN appears).
 *   - sysctl hw.passsim.hang=1   : hold every XPT_SCSI_IO except INQUIRY
 *                                  (CAMIOCOMMAND threads block in
 *                                  cam_periph_runccb/ccbwait forever).
 *   - sysctl hw.passsim.lost=1   : xpt_async(AC_LOST_DEVICE) on (bus,0,0)
 *                                  -> pass periph invalidate; refcount==0
 *                                  -> camperiphfree() frees periph+softc+path
 *                                  under the still-blocked ioctl thread.
 *   - sysctl hw.passsim.poison=1 : kmalloc(M_DEVBUF) objects of
 *                                  sizeof(cam_periph)/sizeof(pass_softc
 *                                  clone)/sizeof(cam_path) filled 0x41 to
 *                                  reclaim+poison the freed memory.
 *   - sysctl hw.passsim.complete=1 : xpt_done() all held CCBs (CAM_REQ_CMP)
 *                                  -> completion walks the freed ccb->path
 *                                  and wakes the ioctl thread into the
 *                                  freed-softc devstat path.
 *   - sysctl hw.passsim.path_id : bus path id (informational).
 */
#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/lock.h>
#include <sys/thread.h>
#include <sys/sysctl.h>
#include <sys/callout.h>
#include <sys/devicestat.h>

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

/* verbatim clone of scsi_pass.c:58-83 so poison allocations match the
 * kfree'd object sizes exactly */
typedef enum { PASS_SIM_FLAG_OPEN = 0x01 } pass_sim_flags;
typedef enum { PASS_SIM_STATE_NORMAL } pass_sim_state;
struct pass_sim_softc {
	pass_sim_state	state;
	pass_sim_flags	flags;
	u_int8_t	pd_type;
	union ccb	saved_ccb;
	struct devstat	device_stats;
};

static struct lock	ev_lock;
static struct sysctl_ctx_list ev_sysctl_ctx;
static struct cam_sim	*ev_sim;
static int		ev_path_id = -1;
static int		hang;		/* hold SCSI_IO (except INQ) */
static int		held_total;
static union ccb	*held[256];
static int		nheld;

static void ev_action(struct cam_sim *sim, union ccb *ccb);
static void ev_poll(struct cam_sim *sim);
static void ev_scan_done(struct cam_periph *periph, union ccb *done_ccb);
static int ev_sysctl_lost(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_poison(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_complete(SYSCTL_HANDLER_ARGS);
static int ev_sysctl_stat(SYSCTL_HANDLER_ARGS);

static u_int8_t ev_inq36[36] = {
	0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00,
	'E','V','I','L','S','I','M',' ',
	'P','A','S','S','U','A','F',' ',
	'1','.','0',' ',
};

static void
ev_complete_ok(union ccb *ccb)
{
	ccb->ccb_h.status = CAM_REQ_CMP;
	if ((ccb->ccb_h.func_code & XPT_FC_QUEUED) != 0)
		xpt_done(ccb);
}

static void
ev_hold(union ccb *ccb)
{
	held_total++;
	if (nheld < (int)NELEM(held)) {
		held[nheld++] = ccb;
		kprintf("[PASSSIM] holding CCB %p (func=%#x)\n",
		    ccb, ccb->ccb_h.func_code);
	} else {
		ev_complete_ok(ccb);
	}
}

static void
ev_fill_pathinq(struct ccb_pathinq *cpi, struct cam_sim *sim)
{
	cpi->version_num = 1;
	cpi->hba_inquiry = 0;
	cpi->target_sprt = 0;
	cpi->hba_misc = 0;
	cpi->hba_eng_cnt = 0;
	cpi->max_target = 0;
	cpi->max_lun = 0;
	cpi->initiator_id = 7;
	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, "passsim", DEV_IDLEN);
	cpi->ccb_h.status = CAM_REQ_CMP;
}

static void
ev_action(struct cam_sim *sim, union ccb *ccb)
{
	struct ccb_hdr *ccbh = &ccb->ccb_h;

	switch (ccbh->func_code) {
	case XPT_PATH_INQ:
		ev_fill_pathinq(&ccb->cpi, sim);
		break;			/* immediate */

	case XPT_SCSI_IO:
	{
		struct ccb_scsiio *csio = &ccb->csio;
		u_int8_t *cdb;
		int copy, dxfer = csio->dxfer_len;

		if (ccbh->flags & CAM_CDB_POINTER)
			cdb = csio->cdb_io.cdb_ptr;
		else
			cdb = csio->cdb_io.cdb_bytes;

		if (hang && cdb[0] != 0x12) {	/* hold all but INQUIRY */
			ev_hold(ccb);
			return;
		}
		switch (cdb[0]) {
		case 0x12:		/* INQUIRY */
			copy = dxfer < 36 ? dxfer : 36;
			bcopy(ev_inq36, csio->data_ptr, copy);
			csio->resid = dxfer - copy;
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		case 0x00:		/* TEST UNIT READY */
		case 0x03:		/* REQUEST SENSE */
		case 0x08:		/* READ(6) */
		case 0x25:		/* READ CAPACITY */
			csio->scsi_status = SCSI_STATUS_OK;
			ev_complete_ok(ccb);
			return;
		default:
			ccb->ccb_h.status = CAM_REQ_INVALID;
			if (ccbh->func_code & XPT_FC_QUEUED)
				xpt_done(ccb);
			return;
		}
	}

	default:
		ccbh->status = CAM_REQ_INVALID;
		if (ccbh->func_code & XPT_FC_QUEUED)
			xpt_done(ccb);
		break;
	}
}

static void
ev_poll(struct cam_sim *sim)
{
}

static void
ev_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 int
ev_sysctl_stat(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	struct cam_path *path;
	struct cam_periph *periph;
	cam_status status;

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

	/* deliberately NOT taking ev_lock: this must work even while
	 * other CAM paths are wedged, to report the pass periph state */
	status = xpt_create_path(&path, NULL, ev_path_id, 0, 0);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] stat: create_path failed %#x\n", status);
		return (0);
	}
	periph = cam_periph_find(path, "pass");
	if (periph == NULL) {
		kprintf("[PASSSIM] stat: pass periph NOT FOUND "
		    "(freed or never attached)\n");
	} else {
		kprintf("[PASSSIM] stat: pass periph %p refcount=%u "
		    "flags=%#x %s\n", periph, periph->refcount,
		    periph->flags,
		    (periph->flags & 0x08) ? "INVALID" : "valid");
	}
	xpt_free_path(path);
	return (0);
}

static int
ev_sysctl_lost(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	struct cam_path *path;
	cam_status status;

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

	kprintf("[PASSSIM] lost: acquiring sim lock\n");
	cam_sim_lock(&ev_lock);
	kprintf("[PASSSIM] lost: got sim lock; create path\n");
	status = xpt_create_path(&path, NULL, ev_path_id, 0, 0);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] lost: create_path failed %#x\n", status);
		cam_sim_unlock(&ev_lock);
		return (0);
	}
	kprintf("[PASSSIM] lost: broadcasting AC_LOST_DEVICE on bus %d\n",
	    ev_path_id);
	xpt_async(AC_LOST_DEVICE, path, NULL);
	xpt_free_path(path);
	kprintf("[PASSSIM] lost: AC_LOST_DEVICE done\n");
	cam_sim_unlock(&ev_lock);
	return (0);
}

static int
ev_sysctl_poison(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;
	int i;
	void *z[6];
	size_t sz[3];
	int nalloc = 0;

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

	sz[0] = sizeof(struct cam_periph);
	sz[1] = sizeof(struct pass_sim_softc);
	sz[2] = 32;	/* sizeof(struct cam_path) (opaque outside cam_xpt.c) */
	kprintf("[PASSSIM] poison: spraying sizes %zu/%zu/%zu\n",
	    sz[0], sz[1], sz[2]);
	for (i = 0; i < (int)NELEM(z); i++) {
		z[i] = kmalloc(sz[i % 3], M_DEVBUF, M_INTWAIT);
		if (z[i]) {
			memset(z[i], 0x41, sz[i % 3]);
			nalloc++;
		}
	}
	kprintf("[PASSSIM] poison: %d objects allocated+painted 0x41\n",
	    nalloc);
	return (0);
}

static int
ev_sysctl_complete(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;

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

	cam_sim_lock(&ev_lock);
	kprintf("[PASSSIM] completing %d held CCBs\n", nheld);
	while (nheld > 0) {
		union ccb *ccb = held[--nheld];
		ccb->ccb_h.status = CAM_REQ_CMP;
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		if (ccb->ccb_h.func_code & XPT_FC_QUEUED)
			xpt_done(ccb);
	}
	cam_sim_unlock(&ev_lock);
	return (0);
}

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

	ev_sim = cam_sim_alloc(ev_action, ev_poll, "passsim", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[PASSSIM] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[PASSSIM] xpt_bus_register failed\n");
		cam_sim_unlock(&ev_lock);
		kthread_exit();
	}
	ev_path_id = cam_sim_path(ev_sim);

	status = xpt_create_path(&path, xpt_periph, ev_path_id,
	    CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD);
	if (status != CAM_REQ_CMP) {
		kprintf("[PASSSIM] cannot create scan path: %#x\n", status);
		cam_sim_unlock(&ev_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 = ev_scan_done;
	xpt_action(ccb);

	kprintf("[PASSSIM] bus %d registered+scanned; pass/da periphs soon\n",
	    ev_path_id);
	cam_sim_unlock(&ev_lock);
	kthread_exit();
}

static int
passsim_modevent(module_t mod, int type, void *data)
{
	int error = 0;
	struct sysctl_ctx_list *ctx;
	struct sysctl_oid *tree;

	switch (type) {
	case MOD_LOAD:
		lockinit(&ev_lock, "passsim", 0, LK_CANRECURSE);
		hang = 0;
		nheld = 0;
		sysctl_ctx_init(&ev_sysctl_ctx);
		ctx = &ev_sysctl_ctx;
		tree = SYSCTL_ADD_NODE(ctx, SYSCTL_STATIC_CHILDREN(_hw),
		    OID_AUTO, "passsim", CTLFLAG_RD, NULL, "passsim rig");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "path_id", CTLFLAG_RD, &ev_path_id, 0, "bus path id");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "hang", CTLFLAG_RW, &hang, 0, "hold SCSI_IO");
		SYSCTL_ADD_INT(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "held_total", CTLFLAG_RD, &held_total, 0,
		    "total CCBs ever held");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "stat", CTLFLAG_RW, 0, 0, ev_sysctl_stat, "I",
		    "report pass periph refcount/flags");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "lost", CTLFLAG_RW, 0, 0, ev_sysctl_lost, "I",
		    "broadcast AC_LOST_DEVICE");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "poison", CTLFLAG_RW, 0, 0, ev_sysctl_poison, "I",
		    "spray/paint freed-size objects 0x41");
		SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "complete", CTLFLAG_RW, 0, 0, ev_sysctl_complete, "I",
		    "complete all held CCBs");
		kthread_create(ev_choreography, NULL, NULL, "passsim");
		break;
	case MOD_UNLOAD:
		kprintf("[PASSSIM] unload not supported\n");
		error = EBUSY;
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}


static moduledata_t passsim_mod = { "passsim", passsim_modevent, NULL };
DECLARE_MODULE(passsim, passsim_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(passsim, cam, 1, 1, 1);
