/*
 * DF-5007 reproduction module.
 *
 * Reuses the STOCK sys/dev/crypto/tpm/tpm20.c (verbatim #include below) so
 * that tpm20_read / tpm20_write / tpm20_discard_buffer / the discard-callout
 * arming+stopping / the dev_lock + buf_cv protocol are the real shipped code.
 * Only the TPM MMIO transport is stubbed: ok_transmit() mimics the successful
 * epilogue of tpmcrb_transmit() (tpm_crb.c:384-402): write a canned response
 * header + body into sc->buf and set sc->pending_data_length.
 *
 * Bug under test (tpm20.c:78-83):
 *   tpm20_read() calls callout_stop(&sc->discard_buffer_callout) BEFORE
 *   validating sc->owner_tid. A read() that FAILS the ownership check with
 *   EPERM still permanently disarms the only automatic recovery (0.5 s
 *   discard timer) that clears pending_data_length. Every later write(2)
 *   then blocks forever in the non-interruptible cv_wait loop at
 *   tpm20.c:126-127 (DF cv_wait has no PCATCH, kern_condvar.c:23-25,41).
 */
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/sysproto.h>	/* SYSCTL_HANDLER_ARGS glue on DF */
#include "tpm20.c"		/* STOCK, verbatim */

static struct tpm_sc *gsc;

static int
ok_transmit(struct tpm_sc *sc, size_t length)
{
	/* canned TPM2 GetCapability-style success response:
	 * tag 0x8001, size 0x0000000c, rc 0, then payload padding */
	uint8_t resp[32];

	memset(resp, 0, sizeof(resp));
	resp[0] = 0x80; resp[1] = 0x01;
	resp[2] = 0x00; resp[3] = 0x00; resp[4] = 0x00; resp[5] = 0x0c;
	memcpy(sc->buf, resp, sizeof(resp));

	/* tpm_crb.c:402 epilogue */
	sc->pending_data_length = length;
	kprintf("hangsim: transmit(len=%zu) ok -> pending=%zu\n",
	    length, sc->pending_data_length);
	return (0);
}

static int
hangsim_modevent(module_t mod, int type, void *arg)
{
	struct tpm_sc *sc;

	switch (type) {
	case MOD_LOAD:
		gsc = kmalloc(sizeof(struct tpm_sc), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		sc = gsc;

		/* verbatim tpm20_init (tpm20.c:190-207) minus device_get_unit.
		 * sc->dev must be a valid device_t: the stock discard callout
		 * ends with device_printf(sc->dev, ...) (tpm20.c:164); the
		 * real backends set it at attach. Borrow the root bus. */
		sc->dev = root_bus;
		sc->buf = kmalloc(TPM_BUFSIZE, M_TPM20, M_WAITOK);
		lockinit(&sc->dev_lock, "TPM driver lock", 0, LK_CANRECURSE);
		cv_init(&sc->buf_cv, "TPM buffer cv");
		callout_init_mp(&sc->discard_buffer_callout);
		sc->pending_data_length = 0;
		sc->sc_cdev = make_dev(&tpm20_ops, 0, UID_ROOT, GID_WHEEL,
		    0600, "hangsim0");
		sc->sc_cdev->si_drv1 = sc;
		sc->transmit = ok_transmit;

		kprintf("hangsim: loaded; /dev/hangsim0 is stock tpm20.c\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);	/* never unload: victim threads may be stuck */
	}
	return (0);
}
DEV_MODULE(hangsim, hangsim_modevent, NULL);
