/*
 * DF-2788 trigger module for sys/kern/kern_timeout.c (DragonFly 6.5-DEVELOPMENT)
 *
 * Proves the sync fast-path in _callout_cancel_or_stop() (kern_timeout.c:888-891)
 * returns 0 unconditionally even when THIS stop/cancel/drain call itself dequeued
 * a pending callout (CALLOUT_PREVENTED was set on the backend _callout at
 * kern_timeout.c:229/:276 and is never read on that path).  Documented contract
 * (callout.h / kern_timeout.c:1052-1094):
 *
 *   callout_stop        "Returns whether the STOP operation was responsible
 *                        for removing a queued or pending callout."
 *   callout_stop_async  same wording
 *   callout_cancel      "Returns 1 if the cancel is responsible for stopping
 *                        the callout."
 *   callout_drain       "Returns 1 if the drain is responsible for stopping
 *                        the callout."
 *
 * Deterministic: no race is required.  For each API we arm a callout hz ticks
 * out, verify callout_pending()==1, invoke the API, verify callout_pending()==0
 * (proving THIS call removed it from the wheel) and record the return value.
 * Contract satisfied  => ret == 1.
 * Observed (bug)      => ret == 0 while pending flipped 1 -> 0.
 *
 * Control case: let the callout fire normally, then callout_stop() must
 * return 0 (nothing was pending) -- shows the return value is supposed to
 * distinguish "I removed it" from "nothing to remove", and that both states
 * are indistinguishable on the stock kernel.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/callout.h>

static struct callout g_c;
static volatile int g_fired;
static int g_summary;

static void
df2788_cb(void *arg __unused)
{
	g_fired = 1;
	wakeup(&g_fired);
}

static int
df2788_run_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0;
	int error;
	int pend_before, ret_stop, pend_after;
	int pend_b, ret_async, pend_ab;
	int pend_c, ret_cancel, pend_ac;
	int pend_d, ret_drain, pend_ad;
	int fired, ret_ctl;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (mode != 1)
		return (EINVAL);

	/* A) callout_stop() on a PENDING callout */
	callout_init_mp(&g_c);
	callout_reset(&g_c, hz, df2788_cb, NULL);
	pend_before = callout_pending(&g_c);
	ret_stop = callout_stop(&g_c);
	pend_after = callout_pending(&g_c);
	kprintf("DF2788 A stop:      pending %d -> %d, ret=%d (contract: 1)\n",
		pend_before, pend_after, ret_stop);

	/* B) callout_stop_async() on a PENDING callout */
	callout_reset(&g_c, hz, df2788_cb, NULL);
	pend_b = callout_pending(&g_c);
	ret_async = callout_stop_async(&g_c);
	pend_ab = callout_pending(&g_c);
	kprintf("DF2788 B stop_async: pending %d -> %d, ret=%d (contract: 1)\n",
		pend_b, pend_ab, ret_async);

	/* C) callout_cancel() on a PENDING callout */
	callout_reset(&g_c, hz, df2788_cb, NULL);
	pend_c = callout_pending(&g_c);
	ret_cancel = callout_cancel(&g_c);
	pend_ac = callout_pending(&g_c);
	kprintf("DF2788 C cancel:    pending %d -> %d, ret=%d (contract: 1)\n",
		pend_c, pend_ac, ret_cancel);

	/* D) callout_drain() on a PENDING callout */
	callout_reset(&g_c, hz, df2788_cb, NULL);
	pend_d = callout_pending(&g_c);
	ret_drain = callout_drain(&g_c);
	pend_ad = callout_pending(&g_c);
	kprintf("DF2788 D drain:     pending %d -> %d, ret=%d (contract: 1)\n",
		pend_d, pend_ad, ret_drain);

	/* E) control: let it fire, then stop() must return 0 */
	g_fired = 0;
	callout_reset(&g_c, 2, df2788_cb, NULL);
	tsleep(&g_fired, 0, "df2788w", 10 * hz);
	fired = g_fired;
	ret_ctl = callout_stop(&g_c);
	kprintf("DF2788 E control:   fired=%d, ret=%d (contract: 0)\n",
		fired, ret_ctl);

	callout_terminate(&g_c);

	/*
	 * bug = every dequeue case flipped pending 1->0 (this call removed the
	 * callout) yet returned 0, while the control correctly returns 0 for
	 * "nothing pending".  1 = bug reproduced, 0 = contract honored.
	 */
	g_summary = (pend_before == 1 && pend_after == 0 && ret_stop == 0 &&
		     pend_b == 1 && pend_ab == 0 && ret_async == 0 &&
		     pend_c == 1 && pend_ac == 0 && ret_cancel == 0 &&
		     pend_d == 1 && pend_ad == 0 && ret_drain == 0 &&
		     fired == 1 && ret_ctl == 0);
	kprintf("DF2788 RESULT: stop=%d async=%d cancel=%d drain=%d "
		"ctl_fired=%d ctl_ret=%d BUG=%d\n",
		ret_stop, ret_async, ret_cancel, ret_drain, fired, ret_ctl,
		g_summary);

	return (0);
}

static int
df2788_sum_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = g_summary;
	return (sysctl_handle_int(oidp, &v, 0, req));
}

SYSCTL_PROC(_debug, OID_AUTO, df2788, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df2788_run_sysctl, "I", "DF-2788 trigger: write 1 to run");
SYSCTL_PROC(_debug, OID_AUTO, df2788_result, CTLTYPE_INT | CTLFLAG_RD,
    0, 0, df2788_sum_sysctl, "I", "DF-2788 last result (1=bug reproduced)");

static int
df2788_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		g_summary = -1;
		kprintf("df2788: loaded, run: sysctl -n debug.df2788=1\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t df2788_mod = { "df2788", df2788_modevent, 0 };
DECLARE_MODULE(df2788, df2788_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
