/*
 * DF-5235 — proof of concept
 *
 * MAX_SCHEDULE_TIMEOUT (LONG_MAX) truncates to -1 when passed to
 * lksleep()'s `int timo` inside __wait_for_completion_generic()
 * (sys/dev/drm/include/linux/completion.h:90).  _callout_setup_quick()
 * clamps a non-positive tick delta to the NEXT tick
 * (sys/kern/kern_timeout.c:971-972), so the "infinite" completion wait
 * times out after ~1 tick (10 ms at hz=100) and returns success
 * without the completion ever having fired.
 *
 * This module compiles the REAL upstream entry points (a verbatim copy
 * of sys/dev/drm/linux_completion.c — sha256 recorded in README.md)
 * against the REAL completion.h from the guest's /usr/src.
 *
 * Build:  make            (see build.sh)
 * Run:    kldload ./compbug.ko ; dmesg | tail  (see run.sh)
 *
 * Expected on the unpatched kernel:
 *   compbug[1] interruptible: ret=0 elapsed=1..2 ticks  -> BUG
 *   compbug[2] wait_for_completion: elapsed=1..2 ticks  -> BUG
 *   compbug[3] control (timeout=10 ticks, no completer): ret=0
 *             elapsed=~10..12 ticks                     -> OK
 *
 * Expected with fix.diff applied (timeout>=INT_MAX -> timo=0):
 *   compbug[1]/[2]: elapsed ~= 500 ticks (5 s, the completer) -> OK
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/callout.h>

#include <linux/completion.h>

/*
 * Linkage-only stubs.  linux_wait.c (compiled in verbatim for
 * __init_waitqueue_head) also contains the wait_event-family helpers,
 * which touch `current' (asm/current.h) and thus drag in
 * linux_task_alloc/linux_task_drop/linux_proc_drop from linux_sched.c.
 * The completion path under test never executes any of them; these
 * stubs exist purely to satisfy the module linker.
 */
struct task_struct *linux_task_alloc(struct thread *td) { return NULL; }
void linux_task_drop(struct thread *td) { }
void linux_proc_drop(struct proc *p) { }

static struct completion comp_i;	/* interruptible, MAX_SCHEDULE_TIMEOUT */
static struct completion comp_w;	/* wait_for_completion() (void)        */
static struct completion comp_t;	/* control: explicit small timeout     */
static struct callout co_i, co_w, co_dbg;

static void
completer_i(void *x)
{
	kprintf("compbug: completer_i fired at tick %d\n", ticks);
	complete(&comp_i);
}

static void
completer_w(void *x)
{
	kprintf("compbug: completer_w fired at tick %d\n", ticks);
	complete(&comp_w);
}

static void
dbg_tick(void *x)
{
	kprintf("compbug: still waiting at tick %d (bug not present?)\n", ticks);
}

static int
compbug_modevent(module_t mod, int type, void *data)
{
	int t0, t1, ret;
	long rt;

	switch (type) {
	case MOD_LOAD:
		init_completion(&comp_i);
		init_completion(&comp_w);
		init_completion(&comp_t);
		callout_init(&co_i);
		callout_init(&co_w);
		callout_init(&co_dbg);

		/*
		 * Case 1 — wait_for_completion_interruptible():
		 * completer scheduled 5 s (500 ticks) out.  Correct
		 * behavior: block ~500 ticks, ret 0.  Buggy: ret 0
		 * after ~1 tick.
		 */
		t0 = ticks;
		callout_reset(&co_i, 5 * hz, completer_i, NULL);
		callout_reset(&co_dbg, 2 * hz, dbg_tick, NULL);
		ret = wait_for_completion_interruptible(&comp_i);
		t1 = ticks;
		callout_stop(&co_i);
		callout_stop(&co_dbg);
		kprintf("compbug[1] interruptible: ret=%d elapsed=%d ticks "
		    "(completer due at %d): %s\n", ret, t1 - t0, 5 * hz,
		    ((t1 - t0) < 4 * hz) ?
		    "BUG: returned before completion" : "OK");

		/*
		 * Case 2 — wait_for_completion(): completer 5 s out.
		 * Correct: block ~500 ticks.  Buggy: ~1 tick.
		 */
		t0 = ticks;
		callout_reset(&co_w, 5 * hz, completer_w, NULL);
		callout_reset(&co_dbg, 2 * hz, dbg_tick, NULL);
		wait_for_completion(&comp_w);
		t1 = ticks;
		callout_stop(&co_w);
		callout_stop(&co_dbg);
		kprintf("compbug[2] wait_for_completion: elapsed=%d ticks "
		    "(completer due at %d): %s\n", t1 - t0, 5 * hz,
		    ((t1 - t0) < 4 * hz) ?
		    "BUG: returned before completion" : "OK");

		/*
		 * Case 3 — control: explicit timeout of 10 ticks, no
		 * completer.  timeout fits in int, must NOT be affected
		 * by the truncation: ret==0, elapsed ~10..13.
		 */
		t0 = ticks;
		rt = wait_for_completion_timeout(&comp_t, 10);
		t1 = ticks;
		kprintf("compbug[3] control timeout=10: ret=%ld elapsed=%d "
		    "ticks: %s\n", rt, t1 - t0,
		    (rt == 0 && (t1 - t0) >= 9 && (t1 - t0) <= 14) ?
		    "OK" : "UNEXPECTED");

		kprintf("compbug: hz=%d, LONG_MAX=%jd -> int timo=%d "
		    "(LKSLEEP SEES %d)\n", hz,
		    (intmax_t)LONG_MAX, (int)LONG_MAX, (int)LONG_MAX);
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t compbug_mod = { "compbug", compbug_modevent, NULL };
DECLARE_MODULE(compbug, compbug_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(compbug, 1);
