/*
 * DF-4737 -- replica of amdpm_wait() (sys/dev/powermng/amdpm/amdpm.c:299-335)
 *
 * The exact loop/test pair from the kernel source is replicated verbatim
 * (DELAY() dropped -- it has no effect on the control flow), with the
 * AMDSMB_GLOBAL_STATUS register read replaced by a fixture.
 *
 * Register bits and SMB_* error codes are the real values from
 * sys/dev/powermng/amdpm/amdpm.c:88-95 and sys/bus/smbus/smbconf.h:55-63.
 *
 * Fixtures:
 *   A  controller never clears HST_STS (perpetual busy = genuine timeout)
 *   B  HST_STS clears exactly on the 10000th (final) poll -> transfer OK
 *   C  HST_STS clears on the 5000th poll -> normal completion
 *   D  same fixtures A/B against the one-line-fixed loop (count < 0)
 */
#include <stdio.h>

#define AMDSMB_GS_HST_STS	(1<<3)	/* amdpm.c:91 */

#define SMB_ENOERR	0x0	/* smbus/smbconf.h:55 */
#define SMB_EBUSERR	0x1
#define SMB_ENOACK	0x4
#define SMB_EABORT	0x10
#define SMB_ETIMEOUT	0x20

static unsigned short	mock_status;
static int		mock_reads;
static int		mock_clear_on_read;	/* 0 = never */

static unsigned short
AMDPM_SMBINW_status(void)
{
	if (++mock_reads == mock_clear_on_read)
		return (mock_status & ~(unsigned short)AMDSMB_GS_HST_STS);
	return (mock_status);
}

/* verbatim amdpm.c:299-335 */
static int
amdpm_wait_buggy(void)
{
	int count = 10000;
	unsigned short sts = 0;
	int error;

	/* Wait for command to complete (SMBus controller is idle) */
	while(count--) {
		sts = AMDPM_SMBINW_status();
		if (!(sts & AMDSMB_GS_HST_STS))
			break;
	}

	error = SMB_ENOERR;

	if (!count)
		error |= SMB_ETIMEOUT;

	if (sts & (1<<0))
		error |= SMB_EABORT;
	if (sts & (1<<1))
		error |= SMB_ENOACK;
	if (sts & (1<<2))
		error |= SMB_EBUSERR;

	return (error);
}

/* the proposed one-line fix: `if (count < 0)` */
static int
amdpm_wait_fixed(void)
{
	int count = 10000;
	unsigned short sts = 0;
	int error;

	while(count--) {
		sts = AMDPM_SMBINW_status();
		if (!(sts & AMDSMB_GS_HST_STS))
			break;
	}

	error = SMB_ENOERR;

	if (count < 0)			/* <-- only change */
		error |= SMB_ETIMEOUT;

	if (sts & (1<<0))
		error |= SMB_EABORT;
	if (sts & (1<<1))
		error |= SMB_ENOACK;
	if (sts & (1<<2))
		error |= SMB_EBUSERR;

	return (error);
}

static int
run_case(const char *name, int clear_on_read, int use_fix)
{
	int error, polls;

	mock_reads = 0;
	mock_status = AMDSMB_GS_HST_STS;	/* busy + no error bits */
	mock_clear_on_read = clear_on_read;

	error = use_fix ? amdpm_wait_fixed() : amdpm_wait_buggy();
	polls = mock_reads;

	printf("%-6s clear_on_read=%-5d polls=%-6d error=0x%-4x %s\n",
	    name, clear_on_read, polls, error,
	    error & SMB_ETIMEOUT ? "TIMEOUT-REPORTED" : "NO-TIMEOUT");
	return (error);
}

int
main(void)
{
	int eA, eB, eC, fA, fB;
	int bugs = 0;

	printf("== buggy loop (verbatim amdpm.c:308/319 `while(count--)` + `if (!count)`) ==\n");
	eA = run_case("A", 0, 0);	/* never completes */
	eB = run_case("B", 10000, 0);	/* completes on final poll */
	eC = run_case("C", 5000, 0);	/* completes midway */

	printf("\n== expectations ==\n");
	printf("A (perpetual busy): SMB_ETIMEOUT SHOULD be set -> ");
	if (!(eA & SMB_ETIMEOUT)) { printf("MISSING (reported success 0x%x) -- BUG\n", eA); bugs++; }
	else printf("ok\n");
	printf("B (final-poll done): SMB_ETIMEOUT should NOT be set -> ");
	if (eB & SMB_ETIMEOUT) { printf("SPURIOUS SMB_ETIMEOUT -- BUG\n"); bugs++; }
	else printf("ok\n");
	printf("C (midway done): no error expected -> %s\n",
	    eC == SMB_ENOERR ? "ok" : "BUG");
	/* sanity: A must not claim hard error either -- pure silent success */
	if (eA == SMB_ENOERR)
		printf("A also returned SMB_ENOERR (0x0): callers (amdpm_recvb/readb/readw\n"
		       "amdpm.c:419-420/475-476/531-532; amdpm_bread amdpm.c:610-622)\n"
		       "return stale HSTDATA/FIFO data as a SUCCESSFUL transfer.\n");

	printf("\n== fixed loop (`if (count < 0)`) ==\n");
	fA = run_case("A", 0, 1);
	fB = run_case("B", 10000, 1);
	printf("fixed A reports TIMEOUT: %s ; fixed B reports success: %s\n",
	    (fA & SMB_ETIMEOUT) ? "yes" : "NO (fix wrong)",
	    (fB & SMB_ETIMEOUT) ? "NO (fix wrong)" : "yes");

	printf("\nbugs demonstrated in buggy loop: %d\n", bugs);
	return (bugs == 2 && (fA & SMB_ETIMEOUT) && !(fB & SMB_ETIMEOUT) ? 0 : 1);
}
