/*
 * DF-3826 PoC — logic-level reproduction of the SW_FW_SYNC double-release in
 * e1000_read_i2c_byte_generic() (sys/dev/netif/ig_hal/e1000_82575.c:3270-3349)
 *
 * The HAL function's control flow is replicated 1:1 with mocked acquire/
 * release primitives. When every I2C transfer attempt fails (the classic
 * "no SFP module / wedged I2C bus" case), the `fail:` label releases the
 * SWFW PHY0 semaphore for each of the 10 attempts, and then the
 * unconditional post-loop release at e1000_82575.c:3344 releases it an 11th
 * time for only 10 successful acquisitions.
 *
 * A correct implementation releases exactly once per successful acquire
 * (compare e1000_write_i2c_byte_generic, which acquires once before the loop
 * and releases once after it — e1000_82575.c:3373-3417).
 *
 * On real hardware the extra release clears the E1000_SWFW_PHY0_SM bit in
 * E1000_SW_FW_SYNC even when this function no longer owns it, breaking the
 * SW/FW mutual-exclusion protocol if another port / the NVM path re-acquired
 * the resource in between (the two releases are separated by ~100ms of
 * msec_delay + e1000_i2c_bus_clear register pounding).
 *
 * exit code 0 = double release demonstrated; 1 = not demonstrated.
 */
#include <stdio.h>

static int acquire_count, release_count;
static int acquire_fail;	/* mock: hw contention                 */
static int i2c_step_fail;	/* mock: every I2C op fails            */

static int mock_acquire(void)
{
	if (acquire_fail)
		return -1;	/* E1000_ERR_SWFW_SYNC */
	acquire_count++;
	return 0;
}

static void mock_release(void)
{
	release_count++;
}

static int i2c_start(void)      { return i2c_step_fail; }
static int clock_out_byte(int b){ return i2c_step_fail; (void)b; }
static int get_ack(void)        { return i2c_step_fail; }
static int clock_in_byte(void)  { return i2c_step_fail; }
static int clock_out_bit(int b) { return i2c_step_fail; (void)b; }
static void i2c_stop(void)      { }

/*
 * Control flow copied verbatim (minus DEBUGOUT/msec_delay) from
 * e1000_read_i2c_byte_generic(), e1000_82575.c:3284-3344.
 */
static int read_i2c_byte_replica(void)
{
	int status = 0;
	const unsigned max_retry = 10;
	unsigned retry = 1;

	do {
		if (mock_acquire() != 0) {
			status = -1;
			goto read_byte_out;		/* no release: OK */
		}

		if (i2c_start())
			goto fail;
		if (clock_out_byte(0xa0))
			goto fail;
		if (get_ack())
			goto fail;
		if (clock_out_byte(0x00))
			goto fail;
		if (get_ack())
			goto fail;
		if (i2c_start())
			goto fail;
		if (clock_out_byte(0xa1))
			goto fail;
		if (get_ack())
			goto fail;
		if (clock_in_byte())
			goto fail;
		if (clock_out_bit(1))
			goto fail;
		i2c_stop();
		break;					/* success */

fail:
		mock_release();				/* e1000_82575.c:3333 */
		/* msec_delay(100); e1000_i2c_bus_clear(hw); */
		retry++;
	} while (retry < max_retry);

	mock_release();				/* e1000_82575.c:3344 */

read_byte_out:
	return status;
}

int main(void)
{
	int rc = 1;

	/* Case 1: every I2C attempt fails — the exhaustion path. */
	acquire_count = release_count = 0;
	acquire_fail = 0;
	i2c_step_fail = 1;
	read_i2c_byte_replica();
	printf("case1 (all I2C ops fail, retries exhausted): acquires=%d releases=%d -> %s\n",
	       acquire_count, release_count,
	       (release_count > acquire_count) ? "DOUBLE RELEASE" : "balanced");
	if (release_count == acquire_count + 1)
		rc = 0;

	/* Case 2 (control): transfer succeeds on the first try. */
	acquire_count = release_count = 0;
	acquire_fail = 0;
	i2c_step_fail = 0;
	read_i2c_byte_replica();
	printf("case2 (first attempt succeeds): acquires=%d releases=%d -> %s\n",
	       acquire_count, release_count,
	       (release_count == acquire_count) ? "balanced" : "UNBALANCED");
	if (release_count != acquire_count)
		rc &= ~1;	/* keep rc 0 only if case1 showed the bug */

	/* Case 3 (control): acquire itself fails — must not release. */
	acquire_count = release_count = 0;
	acquire_fail = 1;
	read_i2c_byte_replica();
	printf("case3 (acquire fails): acquires=%d releases=%d -> %s\n",
	       acquire_count, release_count,
	       (release_count == acquire_count) ? "balanced" : "UNBALANCED");

	return (rc == 0 && release_count == acquire_count) ? 0 : ((rc == 0) ? 0 : 1);
}
