/*
 * DF-5294 simulation harness - rum_ratectl_task() re-arms
 * rvp->ratectl_ch UNCONDITIONALLY, defeating rum_vap_delete()'s
 * drain order and surviving into a freed rum_vap.
 *
 * Models, line-for-line:
 *
 *  rum_ratectl_timeout()     if_rum.c:3096-3104
 *    - ieee80211_runtask(ic, &rvp->ratectl_task)   (just enqueue)
 *
 *  rum_ratectl_task()        if_rum.c:3106-3142
 *    - ... RUM_LOCK ... read STA registers, ratectl update ...
 *    - 3140: usb_callout_reset(&rvp->ratectl_ch, hz, ...)   <-- ALWAYS,
 *      no ratectl_run guard (compare if_run.c:2497-2498 and
 *      if_mtw.c:2401-2402, both guarded by a mask that vap_delete clears
 *      BEFORE draining).
 *
 *  rum_vap_delete()          if_rum.c:709-721
 *    - 716: usb_callout_drain(&rvp->ratectl_ch)   (= callout_drain)
 *    - 717: ieee80211_draintask(ratectl_task)     (= taskqueue_drain)
 *    - 719: ieee80211_vap_detach(vap) ->
 *             net80211 ieee80211_vap_detach() calls ieee80211_stop(vap)
 *             -> rum_newstate() -> usb_callout_stop (if_rum.c:934),
 *             but this happens AFTER both drains, and it does NOT drain a
 *             task that the callout enqueued between re-arm and stop.
 *    - 720: kfree(rvp, M_80211_VAP)
 *
 * Failure interleaving (all steps faithful to the shipped order):
 *   T0   callout fires            -> task queued
 *   T1   vap_delete: callout_drain() returns (callout not pending)
 *   T2   vap_delete: taskqueue_drain() waits; task runs; task RE-ARMS the
 *        callout at its very end (3140) because there is no guard
 *   T3   vap_delete: ieee80211_vap_detach() ... ieee80211_stop() stops the
 *        re-armed callout - but if the callout FIRES inside the detach
 *        window (ratectl_deinit + ether_ifdetach can take a long time),
 *        it enqueues the task AGAIN
 *   T4   kfree(rvp)
 *   T5   the task queued at T3 runs rum_ratectl_task(rvp==freed) and
 *        re-arms the callout on freed memory -> ieee80211_runtask() writes
 *        into the freed rum_vap (task enqueue) and rum_ratectl_task
 *        dereferences vap->iv_ic / vap->iv_bss garbage.
 *
 * Scaled timings (1 s real -> 4 ms sim; detach window 2-6 ms sim).
 * Mode 0 = rum as shipped (unguarded re-arm).  Mode 1 = if_run/if_mtw-style
 * guard (ratectl_run cleared before the drains; every re-arm conditional).
 */
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>

static pthread_mutex_t lk = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t fire_cv = PTHREAD_COND_INITIALIZER;
static pthread_cond_t task_cv = PTHREAD_COND_INITIALIZER;
static pthread_cond_t idle_cv = PTHREAD_COND_INITIALIZER;

static int callout_armed;	/* usb_callout state */
static int callout_firing;	/* callback executing */
static int task_pending, task_running;
static int ratectl_run;		/* the guard if_run/if_mtw have and rum lacks */
static atomic_int vap_freed;
static atomic_long post_free_fires, post_free_tasks, cycles;
static volatile int stop;
static int guarded;

static void
callout_fire(void)
{
	pthread_mutex_lock(&lk);
	callout_firing = 1;
	pthread_mutex_unlock(&lk);

	/* rum_ratectl_timeout: enqueue the task */
	pthread_mutex_lock(&lk);
	task_pending++;
	pthread_cond_signal(&task_cv);
	pthread_mutex_unlock(&lk);

	pthread_mutex_lock(&lk);
	callout_firing = 0;
	pthread_cond_broadcast(&idle_cv);
	pthread_mutex_unlock(&lk);
}

static void *
thr_callout(void *arg)
{
	(void)arg;
	for (;;) {
		pthread_mutex_lock(&lk);
		while (!callout_armed && !stop)
			pthread_cond_wait(&fire_cv, &lk);
		if (stop) {
			pthread_mutex_unlock(&lk);
			break;
		}
		callout_armed = 0;	/* firing */
		pthread_mutex_unlock(&lk);

		if (atomic_load(&vap_freed))
			atomic_fetch_add(&post_free_fires, 1);
		callout_fire();

		usleep(4000);		/* hz tick */
	}
	return NULL;
}

static void *
thr_taskqueue(void *arg)
{
	(void)arg;
	for (;;) {
		pthread_mutex_lock(&lk);
		while (!task_pending && !stop)
			pthread_cond_wait(&task_cv, &lk);
		if (stop && !task_pending) {
			pthread_mutex_unlock(&lk);
			break;
		}
		task_pending--;
		task_running = 1;
		pthread_mutex_unlock(&lk);

		/* rum_ratectl_task body (register read + ratectl update) */
		usleep(300);

		/* :3140 re-arm - guarded exactly like if_run.c:2497 */
		pthread_mutex_lock(&lk);
		if (!guarded || ratectl_run) {
			callout_armed = 1;
			pthread_cond_signal(&fire_cv);
		}
		task_running = 0;
		pthread_cond_broadcast(&idle_cv);
		pthread_mutex_unlock(&lk);

		if (atomic_load(&vap_freed))
			atomic_fetch_add(&post_free_tasks, 1);
	}
	return NULL;
}

/* usb_callout_drain: wait until not armed and not firing */
static void
callout_drain(void)
{
	pthread_mutex_lock(&lk);
	callout_armed = 0;		/* stop() semantic of drain */
	while (callout_firing)
		pthread_cond_wait(&idle_cv, &lk);
	pthread_mutex_unlock(&lk);
}

/* taskqueue_drain: wait until neither pending nor running */
static void
task_drain(void)
{
	pthread_mutex_lock(&lk);
	while (task_pending || task_running)
		pthread_cond_wait(&idle_cv, &lk);
	pthread_mutex_unlock(&lk);
}

static void *
thr_destroyer(void *arg)
{
	(void)arg;
	while (!stop) {
		/* vap up: ratectl running (armed at S_RUN, if_rum.c:3093) */
		pthread_mutex_lock(&lk);
		ratectl_run = 1;
		callout_armed = 1;
		pthread_cond_signal(&fire_cv);
		pthread_mutex_unlock(&lk);

		usleep(2000 + random() % 9000);	/* run for a while */

		/* rum_vap_delete (716-720) */
		if (guarded)		/* the fix: clear the run mask FIRST */
			ratectl_run = 0;	/* (as run_vap_delete does)   */
		callout_drain();		/* :716 */
		task_drain();			/* :717 */
		/* :718 ratectl_deinit + :719 ieee80211_vap_detach:
		 * ether_ifdetach + ieee80211_stop(+callout_stop) - the stop
		 * at the END of this window cannot un-queue a task whose
		 * callout fired inside the window. */
		usleep(2000 + random() % 4000);
		atomic_store(&vap_freed, 1);	/* :720 kfree(rvp) */

		usleep(12000);			/* post-free observation */
		atomic_store(&vap_freed, 0);	/* slot reused by next vap */
		atomic_fetch_add(&cycles, 1);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	int secs = argc > 1 ? atoi(argv[1]) : 10;
	guarded = argc > 2 ? atoi(argv[2]) : 0;
	pthread_t tc, tt, td;

	srandom(4321);
	pthread_create(&tc, NULL, thr_callout, NULL);
	pthread_create(&tt, NULL, thr_taskqueue, NULL);
	pthread_create(&td, NULL, thr_destroyer, NULL);
	sleep(secs);
	stop = 1;
	pthread_mutex_lock(&lk);
	pthread_cond_broadcast(&fire_cv);
	pthread_cond_broadcast(&task_cv);
	pthread_mutex_unlock(&lk);
	pthread_join(tc, NULL);
	pthread_join(tt, NULL);
	pthread_join(td, NULL);

	printf("rum ratectl drain model (%d s, mode=%s): destroy_cycles=%ld "
	    "POST-FREE callout fires=%ld POST-FREE task runs=%ld\n",
	    secs, guarded ? "GUARDED(fixed)" : "SHIPPED(unguarded :3140)",
	    atomic_load(&cycles), atomic_load(&post_free_fires),
	    atomic_load(&post_free_tasks));
	return 0;
}
