/*
 * splzcrit.c — DF-5586 reproducer (faithful signal-structure model of
 * sys/platform/vkernel64/x86_64/exception.c running on the DragonFly host).
 *
 * What it models, element for element:
 *
 *   ipisig()        [exception.c:76-95]   -> SIGUSR1 handler "ipi":
 *        critcount==0 ? process IPI inline : need_ipiq() -> set RQF_IPIQ
 *   exc_segfault()  [exception.c:262-272] -> SIGSEGV handler "fault":
 *        installed SA_SIGINFO|SA_NODEFER + empty sa_mask [exception.c:222-229],
 *        recoverable fault (== trap_pfault -> vm_fault SUCCESS on the
 *        MAP_VPAGETABLE guest-KVA window), then tail
 *        splz()                                                    [exception.c:270]
 *   splz()          [machintr.c:122-158] -> processes RQF_IPIQ (calls the
 *        IPI callback) with NO td_critcount / gd_intr_nesting_level /
 *        td_nest_count guard.
 *   pc64 doreti     [ipl.s:132-146]       -> the real kernel's gate:
 *        "If we are in a critical section we cannot run any pending ints"
 *        testl $-1,TD_CRITCOUNT(%rbx) ; jne 5f  (skip ALL dispatch)
 *   lwkt_maybe_splz [lwkt_thread.c:1091-1101] -> crit_exit dispatches only on
 *        the 1->0 critcount transition (critcount==0 && nesting==0 &&
 *        nest_count<2).
 *
 * The critical section protects a non-atomic two-field update (lo then hi;
 * invariant lo==hi).  The IPI callback does hi=lo (what a real IPI callback
 * does: read crit-protected state, write crit-protected state — e.g. a pmap
 * shootdown).  If the deferred IPI is dispatched inside the critical
 * section, the callback reads a TORN mid-update state and its write is then
 * clobbered by the second half of the update — lost atomicity.
 *
 * Modes:
 *   ./splzcrit vkernel  — exc_segfault tail splz() unguarded (this tree)
 *   ./splzcrit pc64     — doreti/lwkt_maybe_splz gate (the real kernel)
 *
 * Success criterion:
 *   vkernel -> "INVARIANT_BROKEN" + "CALLBACK_CRITCOUNT=1" (dispatched
 *              inside the critical section; torn state observed)
 *   pc64    -> "INVARIANT_OK"    + "CALLBACK_CRITCOUNT=0" (deferred to
 *              the crit_exit transition; no torn state)
 */

#include <sys/mman.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static volatile int critcount;        /* ~ td->td_critcount            */
static volatile int rqf_ipiq;         /* ~ gd->gd_reqflags RQF_IPIQ    */
static volatile int callback_critcount = -1;
static volatile int callback_torn;    /* did callback see lo != hi ?   */

static volatile struct { long lo, hi; } victim;   /* crit-protected    */

static char *faultpage;               /* PROT_NONE page: recoverable fault */

static int guarded;                   /* 0 = vkernel, 1 = pc64 doreti gate */

/*
 * model of the IPI callback body (what lwkt_process_ipiq executes:
 * e.g. pmap_update interrupt, lwkt_send_msg) — reads and writes
 * critical-section-protected state.
 */
static void
ipiq_callback(void)
{
	callback_critcount = critcount;
	callback_torn = (victim.lo != victim.hi);
	victim.hi = victim.lo;             /* normalize from (possibly torn) read */
}

/* ~ splz() [machintr.c:122-158]: no guards of any kind */
static void
splz_model(void)
{
	if (rqf_ipiq) {
		rqf_ipiq = 0;
		ipiq_callback();           /* lwkt_process_ipiq() */
	}
}

/* ~ crit_exit -> lwkt_maybe_splz [lwkt_thread.c:1091]: 1->0 transition only */
static void
crit_exit_maybe_splz(void)
{
	if (critcount == 0)
		splz_model();
}

/*
 * ~ ipisig() [exception.c:76-95]
 */
static void
ipi_handler(int sig, siginfo_t *si, void *ctx)
{
	int save = errno;
	if (critcount == 0) {
		critcount++;             /* crit_enter_quick */
		ipiq_callback();         /* lwkt_process_ipiq inline */
		critcount--;
	} else {
		rqf_ipiq = 1;            /* need_ipiq(): deferred */
	}
	errno = save;
}

/*
 * ~ exc_segfault() [exception.c:262-272] — SA_NODEFER, empty sa_mask.
 * "Recoverable" fault: make the page writable (== vm_fault SUCCESS),
 * i.e. kern_trap returns having resolved the fault, then:
 *
 *      splz();                          <- exception.c:270, unconditional
 *
 * In 'pc64' mode we model doreti's TD_CRITCOUNT gate instead:
 * pending ints are NOT dispatched if we return into a critical section;
 * the crit_exit transition (lwkt_maybe_splz) dispatches them later.
 */
static void
fault_handler(int sig, siginfo_t *si, void *ctx)
{
	int save = errno;

	if ((char *)si->si_addr >= faultpage &&
	    (char *)si->si_addr < faultpage + 4096) {
		/* recoverable: map the page (vm_fault SUCCESS) */
		if (mmap(faultpage, 4096, PROT_READ|PROT_WRITE,
			 MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0)
		    == MAP_FAILED) {
			_exit(9);
		}
		/* exc_segfault tail: */
		if (!guarded) {
			splz_model();     /* exception.c:270 — unguarded     */
		} else {
			;                 /* doreti: critcount!=0 -> jne 5f  */
		}
	} else {
		const char m[] = "UNEXPECTED_FAULT_ADDR\n";
		write(2, m, sizeof(m) - 1);
		_exit(8);
	}
	errno = save;
}

int
main(int argc, char **argv)
{
	struct sigaction sa;

	if (argc < 2 || strcmp(argv[1], "vkernel") == 0)
		guarded = 0;
	else if (strcmp(argv[1], "pc64") == 0)
		guarded = 1;
	else {
		dprintf(2, "usage: %s [vkernel|pc64]\n", argv[0]);
		return (2);
	}

	/* fault page: mapped but PROT_NONE -> deterministic recoverable fault */
	faultpage = mmap(NULL, 4096, PROT_NONE,
			 MAP_PRIVATE|MAP_ANON, -1, 0);
	if (faultpage == MAP_FAILED) {
		perror("mmap faultpage");
		return (2);
	}

	/* ~ init_exceptions() [exception.c:222-229] */
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = fault_handler;
	sa.sa_flags = SA_SIGINFO | SA_NODEFER;   /* exact: exception.c:224 */
	sigemptyset(&sa.sa_mask);                /* exact: exception.c:225 */
	if (sigaction(SIGSEGV, &sa, NULL) < 0) {
		perror("sigaction SEGV");
		return (2);
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = ipi_handler;
	sa.sa_flags = SA_SIGINFO;                /* ~SA_NODEFER cleared:231 */
	sigemptyset(&sa.sa_mask);
	if (sigaction(SIGUSR1, &sa, NULL) < 0) {
		perror("sigaction USR1");
		return (2);
	}

	victim.lo = 0;
	victim.hi = 0;

	/*
	 * vkernel kernel thread enters a critical section (they are held all
	 * over the guest kernel: lwkt tokens, interrupt handlers, drivers).
	 */
	critcount = 1;                       /* crit_enter() */

	/*
	 * A cross-CPU IPI arrives (guest userland churn: TLB shootdown...).
	 * Delivered as a host signal; ipisig sees critcount!=0 -> defers.
	 */
	raise(SIGUSR1);                      /* -> need_ipiq(): RQF_IPIQ=1 */

	/*
	 * The critical section performs its non-atomic protected update:
	 * first half...
	 */
	victim.lo++;

	/*
	 * ...and takes a perfectly ordinary, RECOVERABLE kernel-mode fault in
	 * the middle (guest-KVA demand VPTE install on the MAP_VPAGETABLE
	 * window — the vkernel's normal kernel page-fault mechanism):
	 * exc_segfault -> kern_trap -> trap_pfault SUCCESS -> return ->
	 * splz() at whatever critcount the interrupted context holds.
	 */
	{
		volatile char *p = (volatile char *)faultpage;
		*p = 1;                       /* SIGSEGV -> fault_handler */
	}

	/* second half of the protected update */
	victim.hi++;

	/* crit_exit(): 1->0 transition */
	critcount = 0;
	if (guarded)
		crit_exit_maybe_splz();     /* lwkt_maybe_splz */
	else
		crit_exit_maybe_splz();     /* (nothing left pending by now) */

	printf("mode=%s victim.lo=%ld victim.hi=%ld "
	       "callback_critcount=%d callback_torn=%d\n",
	       guarded ? "pc64" : "vkernel",
	       victim.lo, victim.hi, callback_critcount, callback_torn);

	if (victim.lo != victim.hi) {
		printf("INVARIANT_BROKEN (IPI callback ran inside the "
		       "critical section: torn state + lost update)\n");
		return (1);
	}
	if (guarded && callback_critcount != 0) {
		printf("UNEXPECTED: pc64 gate dispatched at critcount=%d\n",
		       callback_critcount);
		return (1);
	}
	printf("INVARIANT_OK\n");
	return (0);
}
