/*
 * df2712_storm5.c -- PULSED unprivileged kick-burst storm for DF-2712.
 *
 * Usage: df2712_storm5 <victim_cpu> <n_victim> <n_sender_per_cpu> <cycle_secs> <cycles>
 *
 * Each cycle forks a fresh wave (n_victim pinned to victim_cpu +
 * n_sender_per_cpu pinned to every other cpu, all tight sched_yield) with
 * lifetime cycle_secs, waits for the wave to finish, sleeps 3 s so the
 * system goes quiet (bsd4_rdyprocmask bits re-arm via the helper threads),
 * then repeats.  Every wave reproduces the fresh-boot burst conditions
 * that generate spin-held kick storms in bsd4_chooseproc_cc.
 *
 * Unprivileged: lwp_setaffinity self-pin + sched_yield only.
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sched.h>
#include <errno.h>

#define SYS_lwp_setaffinity 544

static double now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return ts.tv_sec + ts.tv_nsec / 1e9;
}

static int pin_to_cpu(int cpu)
{
	unsigned long mask = 1UL << cpu;
	return syscall(SYS_lwp_setaffinity, 0, -1, &mask);
}

static void
wave_worker(int cpu, double deadline)
{
	volatile unsigned long n = 0;

	if (pin_to_cpu(cpu) < 0) { }
	while (now() < deadline)
		n += sched_yield();
	_exit(0);
}

int
main(int argc, char **argv)
{
	int vcpu = 5, ncpu = 6, cy;
	long nvic = 180, nspc = 12, csec = 3, cycles = 15;
	long i, c, total, ok, bad;
	pid_t pid;
	double t0, dl;
	size_t sz = sizeof(ncpu);

	if (argc > 1) vcpu = atoi(argv[1]);
	if (argc > 2) nvic = atol(argv[2]);
	if (argc > 3) nspc = atol(argv[3]);
	if (argc > 4) csec = atol(argv[4]);
	if (argc > 5) cycles = atol(argv[5]);
	sysctlbyname("hw.ncpu", &ncpu, &sz, NULL, 0);
	if (vcpu >= ncpu) vcpu = ncpu - 1;

	for (cy = 0; cy < cycles; ++cy) {
		t0 = now();
		dl = t0 + csec;
		total = ok = bad = 0;
		for (i = 0; i < nvic; ++i) {
			pid = fork();
			if (pid == 0) wave_worker(vcpu, dl);
			if (pid < 0) break;
			++total;
		}
		for (c = 0; c < ncpu; ++c) {
			if ((int)c == vcpu) continue;
			for (i = 0; i < nspc; ++i) {
				pid = fork();
				if (pid == 0) wave_worker((int)c, dl);
				if (pid < 0) break;
				++total;
			}
		}
		for (i = 0; i < total; ++i) {
			int st = 0;
			if (wait(&st) < 0) {
				if (errno == EINTR) { --i; continue; }
				break;
			}
			if (WIFEXITED(st) && WEXITSTATUS(st) == 0) ++ok;
			else ++bad;
		}
		printf("STORM5-CYCLE %d/%ld ok=%ld bad=%ld\n",
		    cy + 1, cycles, ok, bad);
		fflush(stdout);
		sleep(3);	/* let rdyprocmask re-arm */
	}
	printf("STORM5-DONE cycles=%ld\n", cycles);
	return 0;
}
