/*
 * DF-4796 PoC — exclusive-fence reference leak in the GFP_KERNEL
 * slow-path retry of reservation_object_get_fences_rcu().
 *
 * sys/dev/drm/linux_reservation.c:356-357 takes a reference on
 * obj->fence_excl at the top of the do-while body:
 *
 *	fence_excl = rcu_dereference(obj->fence_excl);
 *	if (fence_excl && !dma_fence_get_rcu(fence_excl))
 *		goto unlock;
 *
 * When the krealloc(shared, sz, GFP_NOWAIT) at :365-366 fails and the
 * GFP_KERNEL retry at :369-370 succeeds, the code does `continue`
 * (:373) WITHOUT putting that reference — the next loop iteration
 * takes a fresh one.  One dma_fence reference leaks per
 * NOWAIT-fail/WAITOK-success event.
 *
 * This KLD replicates the call with a reservation object whose
 * fobj->shared_max is large (sz = 8*shared_max bytes), under memory
 * pressure created by the caller (see run.sh): the NOWAIT krealloc
 * fails, the WAITOK krealloc succeeds, and the exclusive fence's
 * refcount grows by one per event instead of returning to baseline.
 *
 * sysctl kern.df4796.run=1   run the loop (kern.df4796_iters times)
 * sysctl kern.df4796_max     fobj->shared_max (entries) — sz = 8*max
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

#include <linux/slab.h>
#include <linux/kref.h>
#include <linux/dma-fence.h>
#include <linux/reservation.h>

static int		df4796_run;
static int		df4796_iters = 64;
static int		df4796_max = 8388608;	/* 8M entries -> 64MB krealloc */
static int		df4796_leaks;		/* refcount growth observed */
static int		df4796_lastcnt;

static struct reservation_object	obj;
static struct dma_fence		*fence;
static struct lock		fence_lock;
struct reservation_object_list	*fobj;

static const char *
dfname(struct dma_fence *f __unused) { return ("df4796"); }

static const struct dma_fence_ops df4796_ops = {
	.get_driver_name	= dfname,
	.get_timeline_name	= dfname,
};

static int
df4796_execute(void)
{
	unsigned count = 0;
	struct dma_fence **shared = NULL;
	struct dma_fence *excl = NULL;
	int base, after, ret, i;

	/* re-size the crafted list for this run (sz = 8 * shared_max) */
	fobj->shared_max = df4796_max;

	base = kref_read(&fence->refcount);

	for (i = 0; i < df4796_iters; i++) {
		ret = reservation_object_get_fences_rcu(&obj, &excl,
		    &count, &shared);
		if (ret) {
			kprintf("df4796: iter %d ret=%d (alloc failed both "
			    "ways — no leak event)\n", i, ret);
			continue;
		}
		if (excl)
			dma_fence_put(excl);
		if (shared) {
			while (count--)
				dma_fence_put(shared[count]);
			kfree(shared);
			shared = NULL;
		}
		excl = NULL;
	}

	after = kref_read(&fence->refcount);
	df4796_lastcnt = after;
	df4796_leaks = after - base;
	kprintf("df4796: base=%d after=%d leaked=%d (%d iterations)\n",
	    base, after, df4796_leaks, df4796_iters);
	if (df4796_leaks > 0)
		kprintf("df4796: LEAK CONFIRMED — %d excl-fence refs leaked "
		    "by the GFP_KERNEL slow-path 'continue'\n", df4796_leaks);
	else
		kprintf("df4796: balanced (no NOWAIT failure observed — "
		    "increase memory pressure / sz)\n");
	return 0;
}

static int
df4796_sysctl_run(SYSCTL_HANDLER_ARGS)
{
	int error, v = df4796_run;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v != 0 && df4796_run == 0)
		df4796_execute();
	return (0);
}

static int
df4796_load(struct module *m, int what, void *arg __unused)
{
	switch (what) {
	case MOD_LOAD:
		lockinit(&fence_lock, "df4796lk", 0, 0);
		fence = kzalloc(sizeof(*fence), GFP_KERNEL);
		dma_fence_init(fence, &df4796_ops, &fence_lock, 0, 1);
		reservation_object_init(&obj);
		/*
		 * Craft a live shared-list whose shared_max is huge but
		 * which holds no fences: the loop body allocates
		 * sz = 8*shared_max each iteration, all of the copy loop
		 * iterations are skipped, and the seqcount never moves.
		 */
		fobj = kzalloc(offsetof(typeof(*fobj), shared[1]), GFP_KERNEL);
		fobj->shared_count = 0;
		fobj->shared_max = df4796_max;
		RCU_INIT_POINTER(obj.fence, fobj);
		RCU_INIT_POINTER(obj.fence_excl, fence);
		kprintf("df4796: loaded (fence refs=%d, sz=%dKB)\n",
		    kref_read(&fence->refcount), df4796_max * 8 / 1024);
		break;
	case MOD_UNLOAD:
		kfree(fence);
		kfree(fobj);
		kprintf("df4796: unloaded\n");
		break;
	}
	return (0);
}

static moduledata_t df4796_mod = { "df4796", df4796_load, NULL };
DECLARE_MODULE(df4796, df4796_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

SYSCTL_NODE(_kern, OID_AUTO, df4796, CTLFLAG_RW, 0, "DF-4796 PoC");
SYSCTL_PROC(_kern_df4796, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df4796_sysctl_run, "I", "run the leak loop once");
SYSCTL_INT(_kern_df4796, OID_AUTO, iters, CTLFLAG_RW,
    &df4796_iters, 0, "iterations per run");
SYSCTL_INT(_kern_df4796, OID_AUTO, max, CTLFLAG_RW,
    &df4796_max, 0, "crafted shared_max (entries; sz = 8*max)");
SYSCTL_INT(_kern_df4796, OID_AUTO, leaks, CTLFLAG_RD,
    &df4796_leaks, 0, "refs leaked by last run");
SYSCTL_INT(_kern_df4796, OID_AUTO, lastcnt, CTLFLAG_RD,
    &df4796_lastcnt, 0, "fence refcount after last run");
