/*
 * DF-4230 harness: uninitialized `regs` read in cyapa_poll_thread()
 * (sys/dev/smbus/cyapa/cyapa.c:1161-1197)
 *
 *     struct cyapa_regs regs;                    <-- cyapa.c:1164, uninitialized
 *     error = smbus_request_bus(bus, sc->dev, SMB_WAIT);
 *     if (error == 0) {
 *             error = smbus_trans(..., (void *)&regs, sizeof(regs), NULL);
 *             if (error == 0)
 *                     isidle = cyapa_raw_input(sc, &regs);
 *     }
 *     if (cyapa_reset ||
 *         ((regs.stat & CYAPA_STAT_RUNNING) == 0 &&          <-- :1192
 *          (unsigned)(ticks - last_reset) > TIME_TO_RESET)) {
 *             ...
 *             init_device(sc->dev, NULL, sc->addr, 2);       <-- :1196
 *     }
 *
 * When smbus_trans() fails (device NAK / bus error) it does not write
 * `regs`; on the FIRST failing iteration regs.stat is an uninitialized
 * stack byte that gates the re-init logic.  After 3s of consecutive
 * failures the garbage byte decides whether the driver hammers the bus
 * with bootloader handshakes every 3 seconds.
 *
 * Demonstration: stack-poison + failure stub, exact branch structure.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define CYAPA_MAX_MT 5
#define CYAPA_STAT_RUNNING 0x80
#define TIME_TO_RESET 300				/* hz*3 with hz=100 */

struct cyapa_regs {
	uint8_t stat;
	uint8_t fngr;
	struct {
		uint8_t xy_high, x_low, y_low, pressure, id;
	} touch[CYAPA_MAX_MT];
} __packed;

static int smbus_trans_fails = 1;		/* device absent/NAK */
static int ticks = 1000;
static int cyapa_reset_sysctl = 0;

/* model of the reset decision, verbatim branch structure of :1191-1193 */
static int poll_iteration(struct cyapa_regs *regs, int last_reset)
{
	int error;

	error = smbus_trans_fails;			/* smbus_trans fails */
	if (error == 0)
		return 0;				/* raw_input skipped */

	if (cyapa_reset_sysctl ||
	    ((regs->stat & CYAPA_STAT_RUNNING) == 0 &&	/* cyapa.c:1192 */
	     (unsigned)(ticks - last_reset) > TIME_TO_RESET))
		return 1;				/* init_device() called */
	return 0;
}

/*
 * regs is a local of cyapa_poll_thread (cyapa.c:1164); give the model the
 * same shape so its stack slot lands inside the poisoned region.
 */
static int poll_thread_model(void)
{
	struct cyapa_regs regs;				/* cyapa.c:1164 */
	int last_reset = ticks;
	int reinited = 0, iters = 0;

	/*
	 * Simulate 40 seconds of polling with the device failing every
	 * transaction (smbus_trans never writes `regs`).
	 */
	for (iters = 0; iters < 40; iters++) {
		if (poll_iteration(&regs, last_reset)) {
			reinited = 1;
			break;
		}
		ticks += 10;				/* 10Hz idle poll */
	}
	printf("after %d failing poll iterations: reinited=%d "
	       "(garbage regs.stat=0x%02x decided)\n",
	       iters, reinited, regs.stat);
	return reinited;
}

static unsigned char poison_byte = 0xCD;

static void poison_stack(void)
{
	volatile unsigned char junk[8192];

	memset((void *)junk, poison_byte, sizeof(junk));
}

int main(int argc, char **argv)
{
	int r;

	if (argc > 1)
		poison_byte = (unsigned char)strtoul(argv[1], NULL, 0);
	printf("poison byte = 0x%02x (bit7 RUNNING = %s)\n",
	       poison_byte, (poison_byte & 0x80) ? "set" : "clear");
	poison_stack();
	r = poll_thread_model();
	if (r) {
		printf("RESULT: re-init decision taken from UNINITIALIZED "
		       "regs.stat (byte 0x%02x, RUNNING bit clear)\n",
		       poison_byte);
		return 0;
	}
	printf("RESULT: no re-init (byte 0x%02x had RUNNING bit set)\n",
	       poison_byte);
	return 0;
}
