/*
 * storm.c — DF-5586/lead-2 in-vkernel fault storm (runs as GUEST USERLAND
 * inside the booted vkernel; a normal unprivileged static DragonFly binary).
 *
 * Hammers every fault class the vkernel must reflect for guest-user code
 * (the reservation's evidence classes):
 *   T_DIVIDE   integer divide by zero        -> SIGFPE
 *   T_XMMFLT   unmasked 1.0/0.0 (SSE)        -> SIGFPE
 *   ud2        invalid opcode                -> SIGILL
 *   T_PAGEFLT  write to read-only page       -> SIGSEGV
 *   T_PAGEFLT  access to PROT_NONE page      -> SIGSEGV
 * plus, from a forked sibling sharing a mapped region: mprotect churn that
 * forces cross-CPU TLB shootdowns (SIGUSR1 IPIs inside the -n 2 vkernel)
 * while the storm runs — pressure on the exact signal fabric exception.c
 * installs.
 *
 * None of these guest-user faults may reach the vkernel's exc_segfault
 * (they return via vmspace_ctl -> go_user -> user_trap, trap.c:1407-1439).
 * Success criterion: STORM_DONE with counters, vkernel still ALIVE.
 */


#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

static void *volatile g_resume;
static char *ro_page, *bad_page, *sh_page;
static volatile unsigned long n_fpe, n_xmm, n_ill, n_prot, n_map;

#define FLUSH(x) do { volatile unsigned long *_p = (unsigned long *)&(x); (void)_p; } while (0)

static void
h_fpe(int sig, siginfo_t *si, void *ctx)
{
	(void)sig; (void)si;
	n_fpe++;
	((ucontext_t *)ctx)->uc_mcontext.mc_rip = (register_t)g_resume;
}
static void
h_ill(int sig, siginfo_t *si, void *ctx)
{
	(void)sig; (void)si;
	n_ill++;
	((ucontext_t *)ctx)->uc_mcontext.mc_rip = (register_t)g_resume;
}
static void
h_segv(int sig, siginfo_t *si, void *ctx)
{
	(void)sig; (void)si;
	if ((char *)si->si_addr == bad_page ||
	    ((char *)si->si_addr >= bad_page &&
	     (char *)si->si_addr < bad_page + 4096))
		n_map++;
	else
		n_prot++;
	/* rearm whichever page faulted, then resume at g_resume */
	mprotect(bad_page, 4096, PROT_READ|PROT_WRITE);
	mprotect(ro_page, 4096, PROT_READ|PROT_WRITE);
	((ucontext_t *)ctx)->uc_mcontext.mc_rip = (register_t)g_resume;
}

int
main(int argc, char **argv)
{
	struct sigaction sa;
	volatile unsigned long one = 1, zero = 0, q;
	volatile double d = 1.0, dz = 0.0, dq;
	pid_t churn;
	int i, st, xmm_on = 1, iters = 400000;

	ro_page  = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANON, -1, 0);
	bad_page = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
			MAP_PRIVATE|MAP_ANON, -1, 0);
	sh_page  = mmap(NULL, 4096 * 32, PROT_READ|PROT_WRITE,
			MAP_SHARED|MAP_ANON, -1, 0);
	if (ro_page == MAP_FAILED || bad_page == MAP_FAILED ||
	    sh_page == MAP_FAILED) {
		dprintf(2, "mmap failed\n");
		return (2);
	}
	*ro_page = 1;
	*bad_page = 1;
	memset(sh_page, 3, 4096 * 32);

	/*
	 * Unmask SSE divide-by-zero in MXCSR (feenableexcept not in the
	 * static libc): bit 2 = DIV_BY_ZERO_EXC mask.
	 */
	{
		unsigned int mx;
		__asm __volatile("stmxcsr %0" : "=m" (mx));
		mx &= ~(1u << 2);
		__asm __volatile("ldmxcsr %0" :: "m" (mx));
	}

	/* shared SIGFPE/SIGILL dispatch via mc_trapno-free trampoline */
	memset(&sa, 0, sizeof(sa));
	sa.sa_flags = SA_SIGINFO | SA_NODEFER;   /* storm-safety: rearm fast */
	sigemptyset(&sa.sa_mask);

	sa.sa_sigaction = h_fpe;   sigaction(SIGFPE, &sa, NULL);
	sa.sa_sigaction = h_ill;   sigaction(SIGILL, &sa, NULL);
	sa.sa_sigaction = h_segv;  sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	if (argc > 1)
		iters = atoi(argv[1]);

	/*
	 * Sibling sharing sh_page: mprotect churn against a mapping that is
	 * ALSO live in the parent => cross-CPU TLB shootdown IPIs inside the
	 * 2-cpu vkernel for the whole storm.
	 */
	if (argc > 2 && atoi(argv[2]) == 0) {
		churn = -1;
		goto nochurn;
	}
	churn = fork();
	if (churn == 0) {
		unsigned k = 0;
		for (;;) {
			mprotect(sh_page + ((k & 31) * 4096), 4096,
				 (k & 1) ? (PROT_READ) : (PROT_READ|PROT_WRITE));
			sh_page[(k & 31) * 4096] = (char)k;
			k++;
		}
		_exit(0);
	}

nochurn:
	for (i = 0; i < iters; i++) {
		if ((i % 25) == 0) {
			char pb[128];
			int pl = snprintf(pb, sizeof pb,
				"PROGRESS i=%d fpe=%lu ill=%lu prot=%lu map=%lu xmm=%lu\n",
				i, n_fpe, n_ill, n_prot, n_map, n_xmm);
			write(2, pb, pl);
		}
		write(2,"a",1);
		g_resume = &&L1; q = one / zero;		L1: FLUSH(q);
		write(2,"b",1);
		g_resume = &&L2; __asm __volatile("ud2");	L2: ;
		write(2,"c",1);
		g_resume = &&L3;
		{
			volatile char *p = ro_page;
			mprotect(ro_page, 4096, PROT_READ);
			*p = (char)(i + 1);                     /* SIGSEGV */
		}
L3:
		write(2,"d",1);
		g_resume = &&L4;
		{
			volatile char *p = bad_page;
			mprotect(bad_page, 4096, PROT_NONE);
			*p = 2;                                  /* SIGSEGV */
		}
L4:
		write(2,"e",1);
		if (xmm_on) {
			g_resume = &&L5; dq = d / dz;	L5: FLUSH(dq);
		}
		write(2,"f\n",2);
		/* keep the shared mapping hot in this process too */
		if ((i & 63) == 0)
			sh_page[(i & 31) * 4096]++;
	}

	if (churn > 0) {
		kill(churn, SIGKILL);
		waitpid(churn, &st, 0);
	}

	printf("STORM_DONE iters=%d intdiv=%lu ill=%lu prot=%lu "
	       "map=%lup xmm=%lu(on=%d)\n",
	       i, n_fpe, n_ill, n_prot, n_map, n_xmm, xmm_on);
	printf("VKERNEL_SURVIVED_IF_YOU_SEE_THIS\n");
	return (0);
}
