/*
 * DF-4013 replica — verbatim mps diag-buffer state machine from
 * sys/dev/raid/mps/mps_user.c:
 *
 *   mps_get_fw_diag_buffer_number()  :1126-1138
 *   mps_post_fw_diag_buffer()        :1140-1224   (HW post -> stubbed)
 *   mps_release_fw_diag_buffer()     :1226-1312   (HW release -> stubbed)
 *   mps_diag_register()              :1314-1430   (bus_dma trio -> kmalloc
 *                                                  stubs; the STATE LOGIC —
 *                                                  shared sc->fw_diag_* trio,
 *                                                  per-type pBuffer->size,
 *                                                  free-without-NULL — is
 *                                                  verbatim)
 *   mps_diag_unregister()            :1432-1483
 *   mps_diag_read_buffer()           :1556-1621
 *   mps_do_diag_action()             :1662-1771   (REGISTER/UNREGISTER/
 *                                                  READ_BUFFER dispatch)
 *
 * The DMA trio (fw_diag_dmat / fw_diag_buffer / fw_diag_map /
 * fw_diag_busaddr, mpsvar.h:377-380) is single and SHARED across all three
 * buffer types (fw_diag_buffer_list[3], mpsvar.h:409-410) — exactly as the
 * real driver.  Only the hardware interaction is stubbed: "post" succeeds
 * (and sets owned_by_firmware=TRUE exactly like the real success path at
 * :1216-1219), "release" succeeds (clearing the flags like :1249-1251).
 * g_post_fail forces the next post to fail, to exercise the register
 * failure path at :1416-1427.
 *
 * Demonstration sequence (as operator, uid=2, via real MPTIOCTL_DIAG_ACTION
 * on a 0640 root:operator node — the same boundary as /dev/mpsN):
 *   1 REGISTER TRACE     1 MiB uid=0x11111001   -> trio #1 (1 MiB)
 *   2 REGISTER SNAPSHOT  4 KiB uid=0x11111002   -> trio #2 (4 KiB) replaces
 *     the shared pointer; TRACE.size still 1 MiB (STALE SIZE)
 *   3 READ uid=0x11111001 len=64 KiB            -> bounds check passes vs
 *     the stale 1 MiB size, copyout runs off the 4 KiB allocation:
 *     ~60 KiB OOB KERNEL HEAP DISCLOSURE
 *   4 UNREGISTER uid=0x11111001                 -> frees the CURRENT shared
 *     trio (the 4 KiB one registered for uid2): CROSS-TYPE FREE
 *   5 READ uid=0x11111002 len=4 KiB             -> copyout from the freed
 *     allocation: USE-AFTER-FREE READ
 *   6 UNREGISTER uid=0x11111002                 -> frees the same trio
 *     AGAIN: DOUBLE FREE (guest panic expected)
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioccom.h>
#include <sys/malloc.h>
#include <sys/errno.h>

/* ---- verbatim ioctl.h excerpts (mps_ioctl.h) --------------------------- */

#define	MPI2_DIAG_BUF_TYPE_TRACE		0
#define	MPI2_DIAG_BUF_TYPE_SNAPSHOT	1
#define	MPI2_DIAG_BUF_TYPE_EXTENDED	2
#define	MPI2_DIAG_BUF_TYPE_COUNT		3

#define	MPS_FW_DIAGNOSTIC_UID_NOT_FOUND	(0xFF)
#define	MPS_FW_DIAG_NEW			(0x806E6577)
#define	MPS_FW_DIAG_TYPE_REGISTER	(0x00000001)
#define	MPS_FW_DIAG_TYPE_UNREGISTER	(0x00000002)
#define	MPS_FW_DIAG_TYPE_QUERY		(0x00000003)
#define	MPS_FW_DIAG_TYPE_READ_BUFFER	(0x00000004)
#define	MPS_FW_DIAG_TYPE_RELEASE	(0x00000005)
#define	MPS_FW_DIAG_INVALID_UID		(0x00000000)
#define	MPS_DIAG_SUCCESS		0
#define	MPS_DIAG_FAILURE		1
#define	MPS_FW_DIAG_ERROR_SUCCESS		(0x00000000)
#define	MPS_FW_DIAG_ERROR_FAILURE		(0x00000001)
#define	MPS_FW_DIAG_ERROR_INVALID_PARAMETER	(0x00000002)
#define	MPS_FW_DIAG_ERROR_POST_FAILED		(0x00000010)
#define	MPS_FW_DIAG_ERROR_INVALID_UID		(0x00000011)
#define	MPS_FW_DIAG_ERROR_RELEASE_FAILED	(0x00000012)
#define	MPS_FW_DIAG_ERROR_NO_BUFFER		(0x00000013)
#define	MPS_FW_DIAG_ERROR_ALREADY_RELEASED	(0x00000014)
#define	MPS_FW_DIAG_FLAG_REREGISTER	(0x0001)
#define	MPS_FW_DIAG_FLAG_FORCE_RELEASE	(0x0002)

typedef struct mps_fw_diag_register
{
	uint8_t		ExtendedType;
	uint8_t		BufferType;
	uint16_t	ApplicationFlags;
	uint32_t	DiagnosticFlags;
	uint32_t	ProductSpecific[23];
	uint32_t	RequestedBufferSize;
	uint32_t	UniqueId;
} mps_fw_diag_register_t;

typedef struct mps_fw_diag_unregister
{
	uint32_t	UniqueId;
} mps_fw_diag_unregister_t;

typedef struct mps_diag_read_buffer
{
	uint8_t		Status;
	uint8_t		Reserved;
	uint16_t	Flags;
	uint32_t	StartingOffset;
	uint32_t	BytesToRead;
	uint32_t	UniqueId;
	uint64_t	PtrDataBuffer;
} mps_diag_read_buffer_t;

typedef struct mps_diag_action
{
	uint32_t	Action;
	uint32_t	Length;
	uint64_t	PtrDiagAction;
	uint32_t	ReturnCode;
} mps_diag_action_t;

#define	MPTIOCTL_DIAG_ACTION	_IOWR('I', 9, struct mps_diag_action)

/* ---- softc stand-in: the SHARED trio + per-type list — verbatim shape -- */

typedef struct mps_fw_diagnostic_buffer {
	uint8_t		enabled;
	uint8_t		extended_type;
	uint8_t		buffer_type;
	uint8_t		immediate;
	uint8_t		force_release;
	uint8_t		valid_data;
	uint8_t		owned_by_firmware;
	uint32_t	unique_id;
	uint32_t	buffer_type2;	/* padding stand-in */
	uint32_t	product_specific[23];
	uint32_t	size;
} mps_fw_diagnostic_buffer_t;

struct fake_sc {
	uint32_t		fw_diag_busaddr;	/* != 0 once "loaded" */
	void			*fw_diag_buffer;
	void			*fw_diag_dmat;
	void			*fw_diag_map;
	mps_fw_diagnostic_buffer_t
			fw_diag_buffer_list[MPI2_DIAG_BUF_TYPE_COUNT];
};

static struct fake_sc g_sc;		/* zeroed: nothing registered */
static int g_post_fail;		/* trigger knob for the post-failure path */
static int g_alloc_fail;	/* trigger knob: next mem-alloc fails */

static MALLOC_DEFINE(M_DFDIAG, "dfdiag", "DF-4013 diag replica");

/* ---- mpsvar.h:195 excerpt kept verbatim via the struct above ----------- */

/* ---- verbatim mps_user.c:1126-1138 -------------------------------------- */
static uint8_t
mps_get_fw_diag_buffer_number(struct fake_sc *sc, uint32_t unique_id)
{
	uint8_t	index;

	for (index = 0; index < MPI2_DIAG_BUF_TYPE_COUNT; index++) {
		if (sc->fw_diag_buffer_list[index].unique_id == unique_id) {
			return (index);
		}
	}

	return (MPS_FW_DIAGNOSTIC_UID_NOT_FOUND);
}

/* ---- mps_post_fw_diag_buffer :1140-1224 — HW stubbed ------------------- */
static int
mps_post_fw_diag_buffer(struct fake_sc *sc,
    mps_fw_diagnostic_buffer_t *pBuffer, uint32_t *return_code)
{
	/* everything above "Get a command" is bookkeeping; the HW send is
	 * replaced by the knob.  On success the real code sets exactly: */
	if (g_post_fail) {
		g_post_fail = 0;
		*return_code = MPS_FW_DIAG_ERROR_POST_FAILED;
		pBuffer->force_release = 0;
		pBuffer->valid_data = 0;
		pBuffer->owned_by_firmware = 0;
		return (MPS_DIAG_FAILURE);
	}
	pBuffer->force_release = 0;
	pBuffer->valid_data = 1;
	pBuffer->owned_by_firmware = 1;
	*return_code = MPS_FW_DIAG_ERROR_SUCCESS;
	return (MPS_DIAG_SUCCESS);
}

/* ---- mps_release_fw_diag_buffer :1226-1312 — HW stubbed ---------------- */
static int
mps_release_fw_diag_buffer(struct fake_sc *sc,
    mps_fw_diagnostic_buffer_t *pBuffer, uint32_t *return_code,
    uint32_t diag_type)
{
	*return_code = MPS_FW_DIAG_ERROR_RELEASE_FAILED;
	if (!pBuffer->enabled) {
		return (MPS_DIAG_FAILURE);
	}
	pBuffer->force_release = 0;
	pBuffer->valid_data = 0;
	pBuffer->owned_by_firmware = 0;
	*return_code = MPS_FW_DIAG_ERROR_SUCCESS;
	if (diag_type == MPS_FW_DIAG_TYPE_UNREGISTER)
		pBuffer->unique_id = MPS_FW_DIAG_INVALID_UID;
	return (MPS_DIAG_SUCCESS);
}

/* ---- verbatim mps_diag_register :1314-1430 (bus_dma -> kmalloc stubs) -- */
static int
mps_diag_register(struct fake_sc *sc, mps_fw_diag_register_t *diag_register,
    uint32_t *return_code)
{
	mps_fw_diagnostic_buffer_t	*pBuffer;
	uint8_t				extended_type, buffer_type, i;
	uint32_t			buffer_size;
	uint32_t			unique_id;
	int				status;

	extended_type = diag_register->ExtendedType;
	buffer_type = diag_register->BufferType;
	buffer_size = diag_register->RequestedBufferSize;
	unique_id = diag_register->UniqueId;

	if (buffer_type >= MPI2_DIAG_BUF_TYPE_COUNT) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
		return (MPS_DIAG_FAILURE);
	}

	i = mps_get_fw_diag_buffer_number(sc, unique_id);
	pBuffer = &sc->fw_diag_buffer_list[buffer_type];
	if (i != MPS_FW_DIAGNOSTIC_UID_NOT_FOUND) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_UID;
		return (MPS_DIAG_FAILURE);
	}

	if ((pBuffer->unique_id != MPS_FW_DIAG_INVALID_UID) ||
	    (unique_id == MPS_FW_DIAG_INVALID_UID)) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_UID;
		return (MPS_DIAG_FAILURE);
	}

	if (pBuffer->immediate && pBuffer->owned_by_firmware &&
	    (pBuffer->unique_id == MPS_FW_DIAG_INVALID_UID)) {
		pBuffer->immediate = 0;
		pBuffer->unique_id = unique_id;
		return (MPS_DIAG_SUCCESS);
	}

	if (!pBuffer->enabled) {
		*return_code = MPS_FW_DIAG_ERROR_NO_BUFFER;
		return (MPS_DIAG_FAILURE);
	}

#ifdef FIXED
	/* DF-4013 fix, arm 1: the driver owns exactly ONE shared DMA trio;
	 * refuse a second registration while a trio is live (the immediate
	 * takeover path above is the only legal re-use). */
	if (sc->fw_diag_buffer != NULL) {
		kprintf("dfdiag: FIXED: REGISTER refused — shared trio busy "
		    "(buffer=%p)\n", sc->fw_diag_buffer);
		*return_code = MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
		return (MPS_DIAG_FAILURE);
	}
#endif

	/* stubbed bus_dma trio — same single shared set of softc fields */
	if (g_alloc_fail) {
		g_alloc_fail = 0;
		kprintf("dfdiag: stub mem-alloc failed (knob)\n");
		return (ENOMEM);			/* :1390-1394 shape */
	}
	if (sc->fw_diag_dmat != NULL) {
		/* NOTE: the real driver also silently overwrites a live tag
		 * here (bus_dma_tag_create into sc->fw_diag_dmat) — leaking
		 * the previous registration's tag+map+buffer.  The stub
		 * preserves that behavior: nothing is freed. */
		kprintf("dfdiag: register: overwriting LIVE shared trio "
		    "(old buffer=%p, old size=%u) — real driver leaks it\n",
		    sc->fw_diag_buffer,
		    sc->fw_diag_buffer_list[buffer_type].size);
	}
	sc->fw_diag_dmat = kmalloc(16, M_DFDIAG, M_WAITOK | M_ZERO);
	sc->fw_diag_map = sc->fw_diag_dmat;	/* stub: same object */
	sc->fw_diag_buffer = kmalloc(buffer_size, M_DFDIAG, M_WAITOK | M_ZERO);
	memset(sc->fw_diag_buffer, 0xA5, buffer_size); /* "FW diag data" */
	sc->fw_diag_busaddr = 0xDEADB000;	/* stub "loaded" marker */
	pBuffer->size = buffer_size;

	pBuffer->buffer_type = buffer_type;
	pBuffer->immediate = 0;
	pBuffer->extended_type = extended_type;
	pBuffer->unique_id = unique_id;
	status = mps_post_fw_diag_buffer(sc, pBuffer, return_code);

	/*
	 * In case there was a failure, free the DMA buffer.
	 * (VERBATIM bug shape: no NULLing of sc->fw_diag_* — and the freed
	 * trio may be the ONLY one while pBuffer stays registered.)
	 */
	if (status == MPS_DIAG_FAILURE) {
		if (sc->fw_diag_busaddr != 0)
			;			/* stub bus_dmamap_unload */
		if (sc->fw_diag_buffer != NULL)
			kfree(sc->fw_diag_buffer, M_DFDIAG);
		if (sc->fw_diag_dmat != NULL) {
			kfree(sc->fw_diag_dmat, M_DFDIAG);
		}
#ifdef FIXED
		/* DF-4013 fix, arm 2: never leave the shared trio dangling */
		sc->fw_diag_buffer = NULL;
		sc->fw_diag_dmat = NULL;
		sc->fw_diag_map = NULL;
		sc->fw_diag_busaddr = 0;
		pBuffer->size = 0;
#endif
	}

	return (status);
}

/* ---- verbatim mps_diag_unregister :1432-1483 --------------------------- */
static int
mps_diag_unregister(struct fake_sc *sc,
    mps_fw_diag_unregister_t *diag_unregister, uint32_t *return_code)
{
	mps_fw_diagnostic_buffer_t	*pBuffer;
	uint8_t				i;
	uint32_t			unique_id;
	int				status;

	unique_id = diag_unregister->UniqueId;

	i = mps_get_fw_diag_buffer_number(sc, unique_id);
	if (i == MPS_FW_DIAGNOSTIC_UID_NOT_FOUND) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_UID;
		return (MPS_DIAG_FAILURE);
	}

	pBuffer = &sc->fw_diag_buffer_list[i];

	if (!pBuffer->owned_by_firmware) {
		status = MPS_DIAG_SUCCESS;
	} else {
		status = mps_release_fw_diag_buffer(sc, pBuffer, return_code,
		    MPS_FW_DIAG_TYPE_UNREGISTER);
	}

	/*
	 * At this point, return the current status no matter what happens
	 * with the DMA buffer.   (VERBATIM :1471)
	 */
	pBuffer->unique_id = MPS_FW_DIAG_INVALID_UID;
	if (status == MPS_DIAG_SUCCESS) {
		if (sc->fw_diag_busaddr != 0)
			;			/* stub bus_dmamap_unload */
		if (sc->fw_diag_buffer != NULL)
			kfree(sc->fw_diag_buffer, M_DFDIAG);  /* <-- NO NULL */
		if (sc->fw_diag_dmat != NULL)
			kfree(sc->fw_diag_dmat, M_DFDIAG);    /* <-- NO NULL */
#ifdef FIXED
		/* DF-4013 fix, arm 2: NULL the trio after free */
		sc->fw_diag_buffer = NULL;
		sc->fw_diag_dmat = NULL;
		sc->fw_diag_map = NULL;
		sc->fw_diag_busaddr = 0;
#endif
	}

	return (status);
}

/* ---- verbatim mps_diag_read_buffer :1556-1621 -------------------------- */
static int
mps_diag_read_buffer(struct fake_sc *sc,
    mps_diag_read_buffer_t *diag_read_buffer, uint8_t *ioctl_buf,
    uint32_t *return_code)
{
	mps_fw_diagnostic_buffer_t	*pBuffer;
	uint8_t				i, *pData;
	uint32_t			unique_id;
	int				status;

	unique_id = diag_read_buffer->UniqueId;

	i = mps_get_fw_diag_buffer_number(sc, unique_id);
	if (i == MPS_FW_DIAGNOSTIC_UID_NOT_FOUND) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_UID;
		return (MPS_DIAG_FAILURE);
	}

	pBuffer = &sc->fw_diag_buffer_list[i];

#ifdef FIXED
	/* DF-4013 fix, arm 3: never read through a freed/absent trio */
	if (sc->fw_diag_buffer == NULL) {
		kprintf("dfdiag: FIXED: READ refused — no live trio\n");
		*return_code = MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
		return (MPS_DIAG_FAILURE);
	}
#endif

	/* Make sure requested read is within limits (against the PER-TYPE
	 * size — not the size of the SHARED allocation actually pointed to
	 * by sc->fw_diag_buffer) */
	if (diag_read_buffer->StartingOffset + diag_read_buffer->BytesToRead >
	    pBuffer->size) {
		*return_code = MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
		return (MPS_DIAG_FAILURE);
	}

	pData = (uint8_t *)((char *)sc->fw_diag_buffer +
	    diag_read_buffer->StartingOffset);
	if (copyout(pData, ioctl_buf, diag_read_buffer->BytesToRead) != 0)
		return (MPS_DIAG_FAILURE);
	diag_read_buffer->Status = 0;

	if (pBuffer->force_release) {
		diag_read_buffer->Flags |= MPS_FW_DIAG_FLAG_FORCE_RELEASE;
	} else {
		diag_read_buffer->Flags &= ~MPS_FW_DIAG_FLAG_FORCE_RELEASE;
	}

	status = MPS_DIAG_SUCCESS;
	if (!pBuffer->owned_by_firmware) {
		if (diag_read_buffer->Flags & MPS_FW_DIAG_FLAG_REREGISTER) {
			status = mps_post_fw_diag_buffer(sc, pBuffer,
			    return_code);
		}
	}

	return (status);
}

#define	PTRIN(v)	(void *)(uintptr_t)(v)

/* ---- verbatim mps_do_diag_action dispatch (REGISTER/UNREGISTER/READ) -- */
static int
mps_do_diag_action(struct fake_sc *sc, uint32_t action, uint8_t *diag_action,
    uint32_t length, uint32_t *return_code)
{
	mps_fw_diag_register_t		diag_register;
	mps_fw_diag_unregister_t	diag_unregister;
	mps_diag_read_buffer_t		diag_read_buffer;
	int				status = MPS_DIAG_SUCCESS;
	uint32_t			original_return_code;

	original_return_code = *return_code;
	*return_code = MPS_FW_DIAG_ERROR_SUCCESS;

	switch (action) {
		case MPS_FW_DIAG_TYPE_REGISTER:
			if (!length) {
				*return_code =
				    MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
				status = MPS_DIAG_FAILURE;
				break;
			}
			if (copyin(diag_action, &diag_register,
			    sizeof(diag_register)) != 0)
				return (MPS_DIAG_FAILURE);
			status = mps_diag_register(sc, &diag_register,
			    return_code);
			break;

		case MPS_FW_DIAG_TYPE_UNREGISTER:
			if (length < sizeof(diag_unregister)) {
				*return_code =
				    MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
				status = MPS_DIAG_FAILURE;
				break;
			}
			if (copyin(diag_action, &diag_unregister,
			    sizeof(diag_unregister)) != 0)
				return (MPS_DIAG_FAILURE);
			status = mps_diag_unregister(sc, &diag_unregister,
			    return_code);
			break;

		case MPS_FW_DIAG_TYPE_READ_BUFFER:
			if (copyin(diag_action, &diag_read_buffer,
			    sizeof(diag_read_buffer)) != 0)
				return (MPS_DIAG_FAILURE);
			if (length < diag_read_buffer.BytesToRead) {
				*return_code =
				    MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
				status = MPS_DIAG_FAILURE;
				break;
			}
			status = mps_diag_read_buffer(sc, &diag_read_buffer,
			    PTRIN(diag_read_buffer.PtrDataBuffer),
			    return_code);
			if (status == MPS_DIAG_SUCCESS) {
				if (copyout(&diag_read_buffer, diag_action,
				    sizeof(diag_read_buffer) -
				    sizeof(diag_read_buffer.PtrDataBuffer)) !=
				    0)
					return (MPS_DIAG_FAILURE);
			}
			break;

		default:
			*return_code = MPS_FW_DIAG_ERROR_INVALID_PARAMETER;
			status = MPS_DIAG_FAILURE;
			break;
	}

	if ((status == MPS_DIAG_FAILURE) &&
	    (original_return_code == MPS_FW_DIAG_NEW) &&
	    (*return_code != MPS_FW_DIAG_ERROR_SUCCESS))
		status = MPS_DIAG_SUCCESS;

	return (status);
}

/* ---- verbatim mps_user_diag_action :1773-1804 (lock stubbed) ----------- */
static int
mps_user_diag_action_replica(struct fake_sc *sc, mps_diag_action_t *data)
{
	int			status;

	if (sc->fw_diag_buffer_list[0].enabled == 0xAA) {
		/* trigger knobs smuggled through ReturnCode (replica only) */
		if (data->Action == 0xF001) { g_post_fail = 1; return (0); }
		if (data->Action == 0xF002) { g_alloc_fail = 1; return (0); }
	}

	if (data->Action == MPS_FW_DIAG_TYPE_REGISTER ||
	    data->Action == MPS_FW_DIAG_TYPE_UNREGISTER ||
	    data->Action == MPS_FW_DIAG_TYPE_QUERY ||
	    data->Action == MPS_FW_DIAG_TYPE_READ_BUFFER ||
	    data->Action == MPS_FW_DIAG_TYPE_RELEASE) {
		status = mps_do_diag_action(sc, data->Action,
		    PTRIN(data->PtrDiagAction), data->Length,
		    &data->ReturnCode);
	} else
		status = EINVAL;

	return (status);
}

/* ---- devfs plumbing mirroring mps_user.c -------------------------------- */

static d_open_t	dfdiag_open;
static d_close_t dfdiag_close;
static d_ioctl_t dfdiag_ioctl_devsw;

static struct dev_ops dfdiag_ops = {
	{ "dfdiag", 0, D_MPSAFE },
	.d_open =	dfdiag_open,
	.d_close =	dfdiag_close,
	.d_ioctl =	dfdiag_ioctl_devsw,
};

static int
dfdiag_open(struct dev_open_args *ap)
{
	return (0);
}

static int
dfdiag_close(struct dev_close_args *ap)
{
	return (0);
}

static int
dfdiag_ioctl(struct cdev *dev, u_long cmd, void *arg, int flag)
{
	switch (cmd) {
	case MPTIOCTL_DIAG_ACTION:
		return (mps_user_diag_action_replica(&g_sc,
		    (mps_diag_action_t *)arg));
	default:
		return (ENOIOCTL);
	}
}

static int
dfdiag_ioctl_devsw(struct dev_ioctl_args *ap)
{
	return (dfdiag_ioctl(ap->a_head.a_dev, ap->a_cmd, ap->a_data,
	    ap->a_fflag));
}

static int
dfdiag_modevent(module_t mod, int type, void *data)
{
	static cdev_t cdev;

	switch (type) {
	case MOD_LOAD:
		cdev = make_dev(&dfdiag_ops, 0, UID_ROOT, GID_OPERATOR, 0640,
		    "dfdiag");		/* same boundary as mps_user.c:188 */
		if (cdev == NULL)
			return (ENOMEM);
		reference_dev(cdev);
		/* mirror mps.c:1089-1097: FW advertises all three types */
		g_sc.fw_diag_buffer_list[MPI2_DIAG_BUF_TYPE_TRACE].enabled = 1;
		g_sc.fw_diag_buffer_list[MPI2_DIAG_BUF_TYPE_SNAPSHOT].enabled = 1;
		g_sc.fw_diag_buffer_list[MPI2_DIAG_BUF_TYPE_EXTENDED].enabled = 1;
		/* arm the knob decode */
		g_sc.fw_diag_buffer_list[0].enabled = 0xAA;
		kprintf("dfdiag: mps diag replica loaded (shared trio, "
		    "per-type sizes, free-without-NULL — mps_user.c:1314-1621)\n");
		return (0);
	case MOD_UNLOAD:
		destroy_dev(cdev);
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(dfdiag, dfdiag_modevent, NULL);
