# DF-5148 — AUX completion poll off-by-one misreports a completed transfer as -EIO

**File:** `sys/dev/drm/radeon/radeon_dp_auxch.c`
**Severity:** Info (correctness; no memory-safety impact)
**Status:** `untested` (control-flow arithmetic proved by executed harness; on-HW
reproduction impossible in the audit guest — no AMD/ATI PCI device, QEMU
emulated VGA only, so `radeon_dp_aux_transfer_native()` can never attach/run
there; see `env.txt`)

## What

`radeon_dp_aux_transfer_native()` polls the AUX_SW_STATUS register for
`AUX_SW_DONE` (radeon_dp_auxch.c:152-158):

```c
	int retry_count = 0;
	do {
		tmp = RREG32(AUX_SW_STATUS + aux_offset[instance]);
		if (tmp & AUX_SW_DONE) {
			break;
		}
		usleep_range(100, 200);
	} while (retry_count++ < 1000);

	if (retry_count >= 1000) {              /* keyed on the COUNTER */
		DRM_ERROR("auxch hw never signalled completion, error %08x\n", tmp);
		ret = -EIO;
		goto done;
	}
```

The do-while executes the body with `retry_count == 0..1000` (1001 body
executions): after the body entered with `retry_count == 999`, the guard
`retry_count++ < 1000` increments to 1000 and re-enters the body one last
time. If the hardware asserts `AUX_SW_DONE` during that 1001st poll, the loop
breaks with `retry_count == 1000` — and the counter-keyed error gate then
reports a *completed* transaction as `-EIO` ("auxch hw never signalled
completion"). The correct signal is available in `tmp` at that point; the
gate just tests the wrong variable.

Secondary aggravations (same loop, noted for completeness):
* DFBSD's `usleep_range(100,200)` maps to `DELAY(100)` (bounded busy-wait,
  sys/dev/drm/include/linux/delay.h:55-58), so a wedged AUX block busy-waits
  up to ~100 ms per transaction **with `chan->mutex` held** (locked at
  radeon_dp_auxch.c:96) — 100x upstream Linux's `udelay(1)`-based ~1 ms.
  Bounded, but a latency wart under retry storms.
* The error `goto done` paths (lines 163/168/174) skip the second
  `AUX_SW_DONE_ACK` clear at line 194; stale DONE state is however re-acked
  by the unconditional write at line 145 of the *next* call before GO is
  set, so this is cosmetic.

## Impact

A DisplayPort AUX transaction that completes on the final poll iteration is
spuriously failed with -EIO. Upper layers (drm_dp_aux / DPCD accessors /
link training) retry aux transactions, so the practical effect is a rare
transient failure (EDI D/EDID read, DPCD access, link training step), not a
memory-safety issue. Info severity.

Related known findings (pass 1, NOT part of this one): DF-2146 (unbounded
AUX_SW_REPLY byte count vs 16-byte buffer), DF-2147 (unguarded
`aux_offset[instance]`).

## Reproduce (control-flow proof)

```
./build.sh    # compiles loop_offbyone.c on the audit QEMU guest (or any POSIX cc)
./run.sh      # runs the stimulus table
```

Expected decisive output (see run.log):

```
DONE on 1001st poll  (rc=1000)  <-- last body -5             0              0   BUG: completed xfer misreported as -EIO
never DONE (true HW timeout)           -5             -5             -5

RESULT: shipped loop misclassifies a DONE-on-last-poll transfer as -EIO (radeon_dp_auxch.c:160).
```

The "fixed gate" column shows the proposed `if (!(tmp & AUX_SW_DONE))` gate
behaves identically to the shipped gate for every other stimulus, i.e. the
fix only flips the misclassified case.

## Fix

`fix.diff` — gate the timeout on the observed status word, not the counter.
```
