/*
 * DF-4764 / DF-4765 / DF-4766 mechanism harness for
 * sys/dev/raid/hptmv/hptproc.c (DragonFly 6.5-DEVELOPMENT guest).
 *
 * It compiles the REAL driver structure definitions
 * (<dev/raid/hptmv/global.h>) and embeds VERBATIM copies of:
 *   - hpt_copy_array_info  (hptproc.c:402-478)
 *   - hpt_copy_disk_info   (hptproc.c:372-399)
 *   - get_disk_name        (hptproc.c:339-349)
 *   - the hpt_get_info device-list loop (hptproc.c:516-549, adapted to a
 *     stand-alone VBus)
 * and then triggers the three defect paths on faithfully constructed
 * objects. CTASSERTs first prove the exact union layout the analysis
 * depends on.
 *
 * sysctl dev.h1harness.trigger:
 *   1 = DF-4764: RAID-1 member[0] disk passed to hpt_copy_array_info
 *                (hptproc.c:535 goto is_array) -> union type confusion ->
 *                divide-by-zero (SIGFPE) in "Rebuilding %d%%" (hptproc.c:457)
 *   2 = DF-4765: REAL RAID-1 array with bArnMember==1 (createable via
 *                CREATE_ARRAY AT_RAID1 nDisk=1, gui_lib.c:614) while
 *                rf_verifying -> same divide-by-zero (hptproc.c:462)
 *   3 = DF-4766: hpt_copy_disk_info with 16-byte non-NUL ArrayName ->
 *                %s overread past arrayname[16] (hptproc.c:388,396)
 *
 * Cases 1 and 2 are expected to PANIC the guest kernel (SIGFPE).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/types.h>

#include <dev/raid/hptmv/global.h>

typedef struct sysctl_req HPT_GET_INFO;		/* hptproc.c:57 */

/*--------------------------------------------------------------------
 * Layout proof (LP64, DragonFly/amd64, _RAID5N_ defined by global.h).
 * If any of these fire, the static analysis is wrong and the findings
 * must be withdrawn.
 *------------------------------------------------------------------*/
CTASSERT(offsetof(RaidArray, bArnMember) == 0);
CTASSERT(offsetof(RaidArray, dArStamp) == 4);
CTASSERT(offsetof(RaidArray, RebuildSectors) == 32);
CTASSERT(offsetof(RaidArray, pMember) == 40);
CTASSERT(offsetof(RaidArray, ArrayName) == 104);
CTASSERT(offsetof(Device, dDeRealCapacity) == 24);
CTASSERT(offsetof(Device, HeadPosition) == 32);
CTASSERT(offsetof(Device, mv) == 40);
CTASSERT(sizeof(Device) == 48);
CTASSERT(offsetof(VDevice, u) == 120);	/* _RAID5N_ adds CacheEntry/range_lock */

/*===================== VERBATIM hptproc.c:337-349 ==================*/
#define shortswap(w) ((WORD)((w)>>8 | ((w) & 0xFF)<<8))

static void
get_disk_name(char *name, PDevice pDev)
{
	int i;
	MV_SATA_CHANNEL *pMvSataChannel = pDev->mv;
	IDENTIFY_DATA2 *pIdentifyData = (IDENTIFY_DATA2 *)pMvSataChannel->identifyDevice;

	for (i = 0; i < 10; i++)
		((WORD*)name)[i] = shortswap(pIdentifyData->ModelNumber[i]);
	name[20] = '\0';
}

/*===================== hpt_copy_info stand-in ======================
 * Same kvsnprintf-into-buffer semantics as hptproc.c:353-370; output
 * goes to console instead of SYSCTL_OUT so we can observe it.
 *==================================================================*/
static char hptproc_buffer[512];
static int hpt_copy_info(HPT_GET_INFO *, char *, ...) __printf0like(2, 3);

static int
hpt_copy_info(HPT_GET_INFO *pinfo, char *fmt, ...)
{
	__va_list ap;

	if (fmt == NULL) {
		kprintf("\n");
		return (0);
	}
	__va_start(ap, fmt);
	kvsnprintf(hptproc_buffer, sizeof(hptproc_buffer), fmt, ap);
	__va_end(ap);
	kprintf("%s", hptproc_buffer);
	return ((int)strlen(hptproc_buffer));
}

/*===================== VERBATIM hptproc.c:372-399 ==================*/
static void
hpt_copy_disk_info(HPT_GET_INFO *pinfo, PVDevice pVDev, UINT iChan)
{
	char name[32], arrayname[16], *status;

	get_disk_name(name, &pVDev->u.disk);

	if (!pVDev->u.disk.df_on_line)
		status = "Disabled";
	else if (pVDev->VDeviceType==VD_SPARE)
		status = "Spare   ";
	else
		status = "Normal  ";

#ifdef SUPPORT_ARRAY
	if(pVDev->pParent) {
		memcpy(arrayname, pVDev->pParent->u.array.ArrayName, MAX_ARRAY_NAME);
		if (pVDev->pParent->u.array.CriticalMembers & (1<<pVDev->bSerialNumber))
			status = "Degraded";
	}
	else
#endif
		arrayname[0]=0;

	hpt_copy_info(pinfo, "Channel %d  %s  %5lluMB  %s %s\n",
		iChan+1,
		name, pVDev->VDeviceCapacity>>11, status, arrayname);
}

/*===================== VERBATIM hptproc.c:402-478 ==================*/
#ifdef SUPPORT_ARRAY
static void
hpt_copy_array_info(HPT_GET_INFO *pinfo, int nld, PVDevice pArray)
{
	int i;
	char *sType=NULL, *sStatus=NULL;
	char buf[32];
    PVDevice pTmpArray;

	switch (pArray->VDeviceType) {
		case VD_RAID_0:
			for (i = 0; (UCHAR)i < pArray->u.array.bArnMember; i++)
				if(pArray->u.array.pMember[i])	{
					if(mIsArray(pArray->u.array.pMember[i]))
						sType = "RAID 1/0   ";
						/* TO DO */
					else
						sType = "RAID 0     ";
					break;
				}
			break;

		case VD_RAID_1:
			sType = "RAID 1     ";
			break;

		case VD_JBOD:
			sType = "JBOD       ";
			break;

		case VD_RAID_5:
			sType = "RAID 5     ";
			break;

		default:
			sType = "N/A        ";
			break;
	}

	if (pArray->vf_online == 0)
		sStatus = "Disabled";
	else if (pArray->u.array.rf_broken)
		sStatus = "Critical";
	for (i = 0; (UCHAR)i < pArray->u.array.bArnMember; i++)
	{
		if (!sStatus)
		{
			if(mIsArray(pArray->u.array.pMember[i]))
				pTmpArray = pArray->u.array.pMember[i];
			else
				pTmpArray = pArray;

			if (pTmpArray->u.array.rf_rebuilding) {
#ifdef DEBUG
				ksprintf(buf, "Rebuilding %lldMB", (pTmpArray->u.array.RebuildSectors>>11));
#else
				ksprintf(buf, "Rebuilding %d%%", (UINT)((pTmpArray->u.array.RebuildSectors>>11)*100/((pTmpArray->VDeviceCapacity/(pTmpArray->u.array.bArnMember-1))>>11)));
#endif
				sStatus = buf;
			}
			else if (pTmpArray->u.array.rf_verifying) {
				ksprintf(buf, "Verifying %d%%", (UINT)((pTmpArray->u.array.RebuildSectors>>11)*100/((pTmpArray->VDeviceCapacity/(pTmpArray->u.array.bArnMember-1))>>11)));
				sStatus = buf;
			}
			else if (pTmpArray->u.array.rf_need_rebuild)
				sStatus = "Critical";
			else if (pTmpArray->u.array.rf_broken)
				sStatus = "Critical";

			if(pTmpArray == pArray) goto out;
		}
		else
			goto out;
	}
out:
	if (!sStatus) sStatus = "Normal";
	hpt_copy_info(pinfo, "%2d  %11s  %-20s  %5lldMB  %-16s", nld, sType, pArray->u.array.ArrayName, pArray->VDeviceCapacity>>11, sStatus);
}
#endif

/*===================== test objects ================================*/
static VDevice f_disk, f_array, f_disk2, f_array3;
static MV_SATA_CHANNEL f_chan, f_chan2;
static VBus f_vbus;

/*===================== trigger 1: DF-4764 ==========================
 * hpt_get_info loop (hptproc.c:516-549) walks a VBus that still lists
 * the RAID-1 source disk. The disk was added to a mirror after driver
 * load ("it may be add to an array after driver loaded, check it" --
 * hptproc.c:530), so pParent is set and pVDev == pMember[0].
 * hptproc.c:534-535 then jumps to is_array *with the disk*.
 *==================================================================*/
static int
fire_h1(void)
{
	int j;
	UINT idx;
	PVDevice pVDev;

	memset(&f_chan, 0, sizeof(f_chan));
	memset(&f_disk, 0, sizeof(f_disk));
	memset(&f_array, 0, sizeof(f_array));
	memset(&f_vbus, 0, sizeof(f_vbus));

	f_chan.channelNumber = 0;			/* mIsArray(mv) == false */

	f_disk.VDeviceType = VD_SINGLE_DISK;
	f_disk.vf_online = 1;
	f_disk.u.disk.df_on_line = 1;			/* puns bArnMember = 1 */
	f_disk.u.disk.dDeRealCapacity = 0x7470DA70;	/* 1TB: bit16=0, bit21=1 */
	f_disk.u.disk.mv = &f_chan;			/* puns pMember[0] */
	f_disk.VDeviceCapacity = 0x7470DA70;

	f_array.VDeviceType = VD_RAID_1;
	f_array.vf_online = 1;
	f_array.u.array.bArnMember = 2;
	f_array.u.array.pMember[0] = &f_disk;

	f_disk.pParent = &f_array;

	f_vbus.pVDevice[0] = &f_disk;

	kprintf("H1: punned disk fields seen by hpt_copy_array_info:\n");
	kprintf("H1:   bArnMember     = df_on_line    = %u\n",
	    *(UCHAR *)&f_disk.u);
	kprintf("H1:   rf_broken      = bit16(dDeRealCapacity) = %u\n",
	    (f_disk.u.disk.dDeRealCapacity >> 16) & 1);
	kprintf("H1:   rf_rebuilding  = bit21(dDeRealCapacity) = %u\n",
	    (f_disk.u.disk.dDeRealCapacity >> 21) & 1);
	kprintf("H1:   rf_verifying   = bit22(dDeRealCapacity) = %u\n",
	    (f_disk.u.disk.dDeRealCapacity >> 22) & 1);
	kprintf("H1:   pMember[0]     = Device.mv    = %p\n",
	    *(void **)((char *)&f_disk.u + 40));
	kprintf("H1: entering hpt_get_info loop -> goto is_array(disk)\n");

	/* --- VERBATIM loop body of hpt_get_info, hptproc.c:515-549 --- */
	j = 1;
	for (idx = 0; idx < MAX_VDEVICE_PER_VBUS; idx++) {
		pVDev = f_vbus.pVDevice[idx];
		if (pVDev) {
			j = idx + 1;
#ifdef SUPPORT_ARRAY
			if (mIsArray(pVDev))
			{
			is_array:
				hpt_copy_array_info(NULL, j, pVDev);
			}
			else
#endif
			{
				char name[32];
#ifdef SUPPORT_ARRAY
				if (pVDev->pParent)
					if (pVDev->pParent->VDeviceType==VD_RAID_1 &&
					    pVDev==pVDev->pParent->u.array.pMember[0])
						goto is_array;		/* hptproc.c:535 */
#endif
				get_disk_name(name, &pVDev->u.disk);
				hpt_copy_info(NULL, "%2d  %s  %s  %5lluMB  %-16s",
					j, "Single disk", name,
					pVDev->VDeviceCapacity>>11, "Normal");
			}
		}
	}
	kprintf("\nH1: SURVIVED (unexpected)\n");
	return (0);
}

/*===================== trigger 2: DF-4765 ==========================
 * A REAL RAID-1 array with bArnMember == 1 (AT_RAID1 with nDisk=1 is
 * accepted by gui_lib.c:614 "if(pParam->nDisk <= 2)") that is in
 * rf_verifying state. Same ksprintf, same divide-by-zero.
 *==================================================================*/
static int
fire_h2(void)
{
	memset(&f_disk2, 0, sizeof(f_disk2));
	memset(&f_array, 0, sizeof(f_array));

	f_disk2.VDeviceType = VD_SINGLE_DISK;
	f_disk2.vf_online = 1;

	f_array.VDeviceType = VD_RAID_1;	/* passes hptproc.c:182-183 */
	f_array.vf_online = 1;
	f_array.u.array.bArnMember = 1;		/* -> denominator (1-1)==0 */
	f_array.u.array.bArRealnMember = 1;
	f_array.u.array.rf_verifying = 1;
	f_array.u.array.RebuildSectors = 0x100000;
	f_array.VDeviceCapacity = 0x7470DA70;
	f_array.u.array.pMember[0] = &f_disk2;	/* plain disk: pTmpArray=pArray */

	kprintf("H2: real RAID-1 array, bArnMember=%u rf_verifying=%u\n",
	    f_array.u.array.bArnMember, f_array.u.array.rf_verifying);
	kprintf("H2: entering hpt_copy_array_info -> ksprintf div/0\n");
	hpt_copy_array_info(NULL, 1, &f_array);
	kprintf("\nH2: SURVIVED (unexpected)\n");
	return (0);
}

/*===================== trigger 3: DF-4766 ==========================
 * hpt_copy_disk_info (verbatim) with a 16-byte, non-NUL-terminated
 * ArrayName in the parent array: memcpy fills arrayname[16] completely
 * and %s keeps reading stack past the buffer.
 *==================================================================*/
static int
fire_h3(void)
{
	int i;

	memset(&f_chan2, 0, sizeof(f_chan2));
	memset(&f_disk2, 0, sizeof(f_disk2));
	memset(&f_array3, 0, sizeof(f_array3));

	for (i = 0; i < 10; i++) {			/* model "WDC 16x'X'..." */
		f_chan2.identifyDevice[27 + i] = shortswap('A' + i);
	}
	f_disk2.VDeviceType = VD_SINGLE_DISK;
	f_disk2.vf_online = 1;
	f_disk2.u.disk.df_on_line = 1;
	f_disk2.u.disk.mv = &f_chan2;
	f_disk2.VDeviceCapacity = 0x7470DA70;
	f_disk2.pParent = &f_array3;
	f_disk2.bSerialNumber = 0;

	memset(f_array3.u.array.ArrayName, 'N', MAX_ARRAY_NAME); /* 16, no NUL */

	kprintf("H3: ArrayName = 16x'N' (no NUL)\n");
	kprintf("H3: hpt_copy_disk_info output follows (watch the tail of the line):\n");
	hpt_copy_disk_info(NULL, &f_disk2, 0);
	kprintf("H3: done - if the row above shows more than 16 'N's, the %%s overread past arrayname[16] is demonstrated\n");
	return (0);
}

/*===================== FIXED variant (fix.diff applied) =============
 * hpt_copy_array_info with the three fixes from fix.diff:
 *   F1 (DF-4764): caller passes the parent array, not the member disk
 *   F2 (DF-4765): guarded rebuild/verify percentage (bArnMember>1 and
 *                 nonzero denominator)
 *   F3 (DF-4766): arrayname[MAX_ARRAY_NAME] = '\0' after the memcpy
 *==================================================================*/
static void
hpt_copy_disk_info_fixed(HPT_GET_INFO *pinfo, PVDevice pVDev, UINT iChan)
{
	char name[32], arrayname[MAX_ARRAY_NAME + 1], *status;

	get_disk_name(name, &pVDev->u.disk);

	if (!pVDev->u.disk.df_on_line)
		status = "Disabled";
	else if (pVDev->VDeviceType==VD_SPARE)
		status = "Spare   ";
	else
		status = "Normal  ";

	if(pVDev->pParent) {
		memcpy(arrayname, pVDev->pParent->u.array.ArrayName, MAX_ARRAY_NAME);
		arrayname[MAX_ARRAY_NAME] = '\0';		/* F3 */
		if (pVDev->pParent->u.array.CriticalMembers & (1<<pVDev->bSerialNumber))
			status = "Degraded";
	}
	else
		arrayname[0]=0;

	hpt_copy_info(pinfo, "Channel %d  %s  %5lluMB  %s %s\n",
		iChan+1,
		name, pVDev->VDeviceCapacity>>11, status, arrayname);
}

static void
hpt_copy_array_info_fixed(HPT_GET_INFO *pinfo, int nld, PVDevice pArray)
{
	int i;
	char *sType=NULL, *sStatus=NULL;
	char buf[32];
	PVDevice pTmpArray;

	switch (pArray->VDeviceType) {
		case VD_RAID_0: sType = "RAID 0     "; break;
		case VD_RAID_1: sType = "RAID 1     "; break;
		case VD_JBOD:   sType = "JBOD       "; break;
		case VD_RAID_5: sType = "RAID 5     "; break;
		default:        sType = "N/A        "; break;
	}

	if (pArray->vf_online == 0)
		sStatus = "Disabled";
	else if (pArray->u.array.rf_broken)
		sStatus = "Critical";
	for (i = 0; (UCHAR)i < pArray->u.array.bArnMember && i < MAX_MEMBERS; i++)
	{
		if (!sStatus)
		{
			if(mIsArray(pArray->u.array.pMember[i]))
				pTmpArray = pArray->u.array.pMember[i];
			else
				pTmpArray = pArray;

			if (pTmpArray->u.array.rf_rebuilding ||
			    pTmpArray->u.array.rf_verifying) {
				LBA_T denom = 0;			/* F2 */
				if (pTmpArray->u.array.bArnMember > 1)
					denom = (pTmpArray->VDeviceCapacity /
						 (pTmpArray->u.array.bArnMember - 1)) >> 11;
				if (denom == 0)
					ksprintf(buf, "%s 0%%",
					    pTmpArray->u.array.rf_rebuilding ?
					    "Rebuilding" : "Verifying");
				else
					ksprintf(buf, "%s %d%%",
					    pTmpArray->u.array.rf_rebuilding ?
					    "Rebuilding" : "Verifying",
					    (UINT)((pTmpArray->u.array.RebuildSectors>>11)*100/denom));
				sStatus = buf;
			}
			else if (pTmpArray->u.array.rf_need_rebuild)
				sStatus = "Critical";
			else if (pTmpArray->u.array.rf_broken)
				sStatus = "Critical";

			if(pTmpArray == pArray) goto out;
		}
		else
			goto out;
	}
out:
	if (!sStatus) sStatus = "Normal";
	hpt_copy_info(pinfo, "%2d  %11s  %-20s  %5lldMB  %-16s", nld, sType, pArray->u.array.ArrayName, pArray->VDeviceCapacity>>11, sStatus);
}

static int
fire_h1_fixed(void)
{
	int j;
	UINT idx;
	PVDevice pVDev;

	memset(&f_chan, 0, sizeof(f_chan));
	memset(&f_disk, 0, sizeof(f_disk));
	memset(&f_disk2, 0, sizeof(f_disk2));
	memset(&f_array, 0, sizeof(f_array));
	memset(&f_vbus, 0, sizeof(f_vbus));

	f_chan.channelNumber = 0;

	f_disk.VDeviceType = VD_SINGLE_DISK;
	f_disk.vf_online = 1;
	f_disk.u.disk.df_on_line = 1;
	f_disk.u.disk.dDeRealCapacity = 0x7470DA70;
	f_disk.u.disk.mv = &f_chan;
	f_disk.VDeviceCapacity = 0x7470DA70;

	f_disk2.VDeviceType = VD_SINGLE_DISK;
	f_disk2.vf_online = 1;
	f_disk2.u.disk.df_on_line = 1;
	f_disk2.u.disk.mv = &f_chan;
	f_disk2.VDeviceCapacity = 0x7470DA70;

	f_array.VDeviceType = VD_RAID_1;
	f_array.vf_online = 1;
	f_array.u.array.bArnMember = 2;
	f_array.u.array.pMember[0] = &f_disk;
	f_array.u.array.pMember[1] = &f_disk2;
	f_array.VDeviceCapacity = 0x7470DA70;

	f_disk.pParent = &f_array;
	f_disk2.pParent = &f_array;
	f_vbus.pVDevice[0] = &f_disk;

	kprintf("H1F: entering hpt_get_info loop with FIX (pVDev=pVDev->pParent)\n");

	j = 1;
	for (idx = 0; idx < MAX_VDEVICE_PER_VBUS; idx++) {
		pVDev = f_vbus.pVDevice[idx];
		if (pVDev) {
			j = idx + 1;
			if (mIsArray(pVDev))
			{
			is_array_f:
				hpt_copy_array_info_fixed(NULL, j, pVDev);
			}
			else
			{
				char name[32];
				if (pVDev->pParent)
					if (pVDev->pParent->VDeviceType==VD_RAID_1 &&
					    pVDev==pVDev->pParent->u.array.pMember[0])
					{
						pVDev = pVDev->pParent;	/* F1 */
						goto is_array_f;
					}
				get_disk_name(name, &pVDev->u.disk);
				hpt_copy_info(NULL, "%2d  %s  %s  %5lluMB  %-16s",
					j, "Single disk", name,
					pVDev->VDeviceCapacity>>11, "Normal");
			}
		}
	}
	kprintf("\nH1F: SURVIVED with fix (expected)\n");
	return (0);
}

static int
fire_h2_fixed(void)
{
	memset(&f_disk2, 0, sizeof(f_disk2));
	memset(&f_array, 0, sizeof(f_array));

	f_disk2.VDeviceType = VD_SINGLE_DISK;
	f_disk2.vf_online = 1;

	f_array.VDeviceType = VD_RAID_1;
	f_array.vf_online = 1;
	f_array.u.array.bArnMember = 1;		/* still degenerate */
	f_array.u.array.bArRealnMember = 1;
	f_array.u.array.rf_verifying = 1;
	f_array.u.array.RebuildSectors = 0x100000;
	f_array.VDeviceCapacity = 0x7470DA70;
	f_array.u.array.pMember[0] = &f_disk2;

	kprintf("H2F: bArnMember=1 rf_verifying=1 with guarded divisor\n");
	hpt_copy_array_info_fixed(NULL, 1, &f_array);
	kprintf("\nH2F: SURVIVED with fix (expected)\n");
	return (0);
}

static int
fire_h3_fixed(void)
{
	memset(&f_chan2, 0, sizeof(f_chan2));
	memset(&f_disk2, 0, sizeof(f_disk2));
	memset(&f_array3, 0, sizeof(f_array3));

	f_disk2.VDeviceType = VD_SINGLE_DISK;
	f_disk2.vf_online = 1;
	f_disk2.u.disk.df_on_line = 1;
	f_disk2.u.disk.mv = &f_chan2;
	f_disk2.VDeviceCapacity = 0x7470DA70;
	f_disk2.pParent = &f_array3;
	f_disk2.bSerialNumber = 0;

	memset(f_array3.u.array.ArrayName, 'N', MAX_ARRAY_NAME);

	kprintf("H3F: ArrayName = 16x'N' (no NUL), with F3 terminator\n");
	hpt_copy_disk_info_fixed(NULL, &f_disk2, 0);
	kprintf("H3F: done - exactly 16 'N's expected, no overread\n");
	return (0);
}

/*===================== module plumbing =============================*/
static int
h1_trigger(SYSCTL_HANDLER_ARGS)
{
	int v = 0, error;

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

	switch (v) {
	case 1: kprintf("=== H1 (DF-4764) type confusion -> div/0 ===\n");
		fire_h1();	/* expected to panic */
		break;
	case 2: kprintf("=== H2 (DF-4765) bArnMember==1 -> div/0 ===\n");
		fire_h2();	/* expected to panic */
		break;
	case 3: kprintf("=== H3 (DF-4766) arrayname non-NUL %%s overread ===\n");
		fire_h3();
		break;
	case 4: kprintf("=== H1F (DF-4764 fix) parent passed, expect survival ===\n");
		fire_h1_fixed();
		break;
	case 5: kprintf("=== H2F (DF-4765 fix) guarded divisor, expect survival ===\n");
		fire_h2_fixed();
		break;
	case 6: kprintf("=== H3F (DF-4766 fix) terminated name, expect 16 N's ===\n");
		fire_h3_fixed();
		break;
	default:
		kprintf("h1harness: unknown trigger %d\n", v);
	}
	return (0);
}
SYSCTL_NODE(, OID_AUTO, h1harness, CTLFLAG_RW, 0, "DF-4764..66 harness");
SYSCTL_PROC(_h1harness, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, h1_trigger, "I", "1=H1 2=H2 3=H3");

static int
h1harness_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("h1harness: loaded (CTASSERT layout proof passed)\n");
		return (0);
	case MOD_UNLOAD:
		kprintf("h1harness: unloaded\n");
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t h1harness_mod = {
	"h1harness",
	h1harness_modevent,
	NULL
};
DECLARE_MODULE(h1harness, h1harness_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
