DF-5148 / loop_offbyone.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | /* * DF-5148 analysis harness — proves the control-flow arithmetic of the AUX * completion poll loop in sys/dev/drm/radeon/radeon_dp_auxch.c:152-164. * * The kernel loop (exact shape): * * int retry_count = 0; * do { * tmp = RREG32(AUX_SW_STATUS + aux_offset[instance]); * if (tmp & AUX_SW_DONE) * break; * usleep_range(100, 200); -> DELAY(min), bounded * } while (retry_count++ < 1000); * * if (retry_count >= 1000) { <- keyed on the COUNTER * DRM_ERROR("auxch hw never signalled completion ..."); * ret = -EIO; * goto done; * } * * Body executes with retry_count == 0..1000 (1001 executions). If the HW * asserts AUX_SW_DONE during the 1001st body (retry_count == 1000 at entry), * the loop breaks with retry_count == 1000 and the counter-based gate then * misreports a *completed* transaction as -EIO. * * This harness models RREG32 with a "DONE asserts at body-entry index N" * stimulus (or never) and compares the shipped gate against the proposed * status-based gate. It is a control-flow proof, not an on-HW reproduction: * exercising the real loop requires radeon DisplayPort hardware. * * Build: cc -O2 -o loop_offbyone loop_offbyone.c * Run: ./loop_offbyone */ #include <stdio.h> #include <stdint.h> #define AUX_SW_DONE (1u << 0) #define EIO_LIKE (-5) /* stands in for -EIO */ /* stimulus: DONE appears when the body is entered with retry_count == done_at; * done_at < 0 means the HW never asserts DONE */ static uint32_t mock_status(int retry_count, int done_at) { return (done_at >= 0 && retry_count >= done_at) ? AUX_SW_DONE : 0; } /* shipped code: gate on the counter (radeon_dp_auxch.c:152-164) */ static int classify_current(int done_at) { int retry_count = 0; uint32_t tmp = 0; int guard = 0; do { if (guard++ > 100000) /* paranoia: model must terminate */ return -99; tmp = mock_status(retry_count, done_at); if (tmp & AUX_SW_DONE) break; } while (retry_count++ < 1000); if (retry_count >= 1000) return EIO_LIKE; /* "never signalled completion" */ return 0; /* success path continues */ } /* proposed fix: gate on the observed status word */ static int classify_fixed(int done_at) { int retry_count = 0; uint32_t tmp = 0; int guard = 0; do { if (guard++ > 100000) return -99; tmp = mock_status(retry_count, done_at); if (tmp & AUX_SW_DONE) break; } while (retry_count++ < 1000); if (!(tmp & AUX_SW_DONE)) return EIO_LIKE; return 0; } int main(void) { struct { int done_at; const char *what; } cases[] = { { 0, "DONE on 1st poll (rc=0)" }, { 500, "DONE on 501st poll (rc=500)" }, { 999, "DONE on 1000th poll (rc=999)" }, { 1000, "DONE on 1001st poll (rc=1000) <-- last body" }, { -1, "never DONE (true HW timeout)" }, }; size_t i; int bad = 0; printf("%-38s %-14s %-14s %s\n", "stimulus", "shipped gate", "fixed gate", "expected"); for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { int cur = classify_current(cases[i].done_at); int fix = classify_fixed(cases[i].done_at); int exp = (cases[i].done_at < 0) ? EIO_LIKE : 0; printf("%-38s %-14d %-14d %d%s\n", cases[i].what, cur, fix, exp, (cur != exp) ? " BUG: completed xfer misreported as -EIO" : (fix != exp ? " FIX WRONG" : "")); if ((cur != exp) || (fix != exp)) bad |= (cur != exp) ? 1 : 2; } if (bad & 1) printf("\nRESULT: shipped loop misclassifies a DONE-on-last-poll " "transfer as -EIO (radeon_dp_auxch.c:160).\n"); if (bad & 2) printf("\nRESULT: proposed fix changes behavior for cases it " "must not (harness error).\n"); if (!bad) printf("\nRESULT: no divergence found.\n"); return (bad & 1) ? 1 : 0; } |