DF-4737 / amdpm_wait_offbyone_demo.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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /* * 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); } |