/*
 * DF-1095 — userspace harness for iicsmb_bwrite missing iicbus_stop.
 *
 * The kernel path (sys/bus/iicbus/iicsmb.c:iicsmb_bwrite) is reachable
 * only when an iicbus-backed SMBus bridge is present and a slave NACKs
 * a byte mid-transaction. The audit QEMU guest has no iicbus/iicsmb
 * device in dmesg, so this harness models the iicbus state machine
 * (sc->started) and the call graph to demonstrate that the unpatched
 * bwrite leaves the bus permanently STARTED.
 *
 * Build:  cc -O0 -o df1095_harness df1095_harness.c
 *   or:   cc -O0 -DFIX -o df1095_harness_fix df1095_harness.c
 * Run:    ./df1095_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

/* Mirror iicbus_softc's relevant state */
static int started;     /* mirrors sc->started */
static int start_calls, stop_calls, write_calls;

#define EINVAL 22
#define LSB    1

static int
iicbus_start(int slave, int timeout)
{
	(void)timeout;
	start_calls++;
	if (started)
		return EINVAL;
	started = slave;
	return 0;
}

static int
iicbus_stop(void)
{
	stop_calls++;
	started = 0;
	return 0;
}

/* write returns error on NACK; we let caller inject failure */
static int write_should_fail;
static int
iicbus_write(const char *buf, int len, int *sent, int timeout)
{
	(void)buf; (void)len; (void)timeout;
	write_calls++;
	*sent = 0;
	return write_should_fail;
}

/* Mirror iicsmb_bwrite exactly. UNPATCHED = the bug; PATCHED = the fix. */
static int
iicsmb_bwrite(unsigned char slave, char cmd, unsigned char count, char *buf)
{
	int error, sent;

#ifndef FIX
	/* UNPATCHED — exact copy of iicsmb.c:462-482 */
	if ((error = iicbus_start(slave & ~LSB, 0)))
		goto error;
	if ((error = iicbus_write(&cmd, 1, &sent, 0)))
		goto error;
	if ((error = iicbus_write(buf, (int)count, &sent, 0)))
		goto error;
	if ((error = iicbus_stop()))
		goto error;
error:
	return (error);
#else
	/* PATCHED — mirrors iicsmb_bread pattern */
	if ((error = iicbus_start(slave & ~LSB, 0)))
		return (error);
	if ((error = iicbus_write(&cmd, 1, &sent, 0)))
		goto error;
	if ((error = iicbus_write(buf, (int)count, &sent, 0)))
		goto error;
error:
	iicbus_stop();
	return (error);
#endif
}

int
main(void)
{
	char buf[32] = {0};
	int rc;

	printf("=== DF-1095 iicsmb_bwrite STARTED-leak harness (%s) ===\n",
#ifdef FIX
	    "PATCHED"
#else
	    "UNPATCHED"
#endif
	    );

	started = 0;
	start_calls = stop_calls = write_calls = 0;

	/* Phase 1: bwrite where slave ACKs address (start succeeds) but
	 * NACKs the data byte (write fails). */
	printf("Phase 1: iicbus_start succeeds, iicbus_write NACKs data byte\n");
	write_should_fail = -1;
	rc = iicsmb_bwrite(0x50, 0x00, 1, buf);
	printf("  bwrite rc=%d, start=%d stop=%d write=%d, started=%d\n",
	    rc, start_calls, stop_calls, write_calls, started);

	/* Phase 2: try a second bwrite — the bridge should still be usable */
	printf("Phase 2: second bwrite attempt\n");
	start_calls = stop_calls = write_calls = 0;
	write_should_fail = 0;
	rc = iicsmb_bwrite(0x50, 0x00, 1, buf);
	printf("  bwrite rc=%d, start=%d stop=%d write=%d, started=%d\n",
	    rc, start_calls, stop_calls, write_calls, started);

#ifndef FIX
	if (started != 0)
		printf("\nBUG: bus still STARTED after error return (rc!=0)\n"
		       "     subsequent iicbus_start returns EINVAL forever\n"
		       "     ===== SMBus bridge permanently wedged =====\n");
#else
	if (started == 0)
		printf("\nOK: bus returned to idle, subsequent transactions work\n");
#endif
	return (0);
}
