/*
 * DF-5605 fix-validation twin of lockowned_demo.c:
 * identical phases, but the decision shape uses the PRECISE check that
 * fix.diff installs in ua_mixer_set()/ua_mixer_setrecsrc():
 *
 *   if (lockstatus(lock, curthread) == LK_EXCLUSIVE)   (fixed)
 *   instead of
 *   if (lockowned(lock))                               (shipped)
 *
 * Expected: P3 now takes the acquire branch; because the foreign thread
 * holds the lock shared, the acquire blocks until it releases (~3s),
 * proving real exclusion is re-established.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/kthread.h>
#include <sys/thread.h>

static struct lock	 dance_lock;
static struct thread	*sh_tp;
static volatile int	 sh_state;

static void
shared_holder(void *arg __unused)
{
	lockmgr(&dance_lock, LK_SHARED);
	sh_state = 1;
	tsleep(&sh_state, 0, "df5605fsh", 3 * hz);
	lockmgr(&dance_lock, LK_RELEASE);
	sh_state = 2;
	wakeup(&sh_state);
	kthread_exit();
}

static int
fixed_dance(void)
{
	struct lock	*lock = &dance_lock;
	uint8_t		 do_unlock;

	if (lockstatus(lock, curthread) == LK_EXCLUSIVE) {
		do_unlock = 0;
	} else {
		do_unlock = 1;
		lockmgr(lock, LK_EXCLUSIVE);
	}
	if (do_unlock)
		lockmgr(lock, LK_RELEASE);
	return (do_unlock);
}

static void
snap(const char *phase)
{
	int	 st = lockstatus(&dance_lock, curthread);
	int	 t0, t1, do_unlock;

	t0 = ticks;
	do_unlock = fixed_dance();
	t1 = ticks;
	kprintf("DF5605FIX %s: lockstatus=%d -> FIXED dance do_unlock=%d "
	    "(took %d ticks) %s\n", phase, st, do_unlock, t1 - t0,
	    do_unlock == 0 ? "(self-held: skip reacquire, correct)"
			   : "(acquired exclusive properly)");
}

static int
df5605fix_modevent(module_t mod, int type, void *arg)
{
	int		i;

	switch (type) {
	case MOD_LOAD:
		lockinit(&dance_lock, "df5605fmix", 0, LK_CANRECURSE);
		sh_state = 0;
		snap("P1-free");
		lockmgr(&dance_lock, LK_EXCLUSIVE);
		snap("P2-self-exclusive");
		lockmgr(&dance_lock, LK_RELEASE);
		if (kthread_create(shared_holder, NULL, &sh_tp,
		    "df5605fix_sh") != 0) {
			lockuninit(&dance_lock);
			return (ENOMEM);
		}
		for (i = 0; i < 10 && sh_state == 0; i++)
			tsleep(&sh_state, 0, "df5605fw", hz);
		if (sh_state != 1)
			break;
		snap("P3-foreign-SHARED");	/* blocks ~3s under the fix */
		for (i = 0; i < 10 && sh_state != 2; i++) {
			wakeup(&sh_state);
			tsleep(&sh_state, 0, "df5605fw", hz);
		}
		snap("P4-released");
		kprintf("DF5605FIX DONE\n");
		break;
	case MOD_UNLOAD:
		for (i = 0; i < 10 && sh_state == 1; i++) {
			wakeup(&sh_state);
			tsleep(&sh_state, 0, "df5605fw", hz);
		}
		lockuninit(&dance_lock);
		kprintf("DF5605FIX unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t df5605fix_mod = { "df5605fix_demo", df5605fix_modevent, NULL };
DECLARE_MODULE(df5605fix_demo, df5605fix_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df5605fix_demo, 1);
