DF-4739 / alpm_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 | /* * DF-4739 demo: alpm_wait() off-by-one — exact replica of the loop in * sys/dev/powermng/alpm/alpm.c:297-332, driven with fixture register reads. * * Bug family: DF-4549 (viapm_wait), DF-4737 (amdpm_wait). * * Case A (genuine SMBus timeout, slave never completes): * SMI_I_STS never sets during all 10000 polls. while(count--) exits with * count == -1, so `if (!count)` is false and SMB_ETIMEOUT is never * reported -> alpm_wait() returns SMB_ENOERR and every alpm_*x*() caller * reads STALE SMBHDATA/SMBHDATB/SMBHBLOCK register data as if the * transaction succeeded (alpm.c:413-414, 467-468, 523-528, 603-615). * * Case B (completion on the final poll): * SMI_I_STS sets on poll #10000 (count entered the loop test as 1 and was * post-decremented to 0): break leaves count == 0 -> `!count` is true and * SMB_ETIMEOUT is spuriously OR'd into a transfer that actually finished. * * Case C (fixed loop, `if (count < 0)`): shows the corrected behaviour. * * Build: cc -Wall -Wextra -o /tmp/df4739 /tmp/df4739.c */ #include <stdio.h> #define SMBCMD 0x1 #define SMBHDATA 0x4 #define SMBHDATB 0x5 #define SMBHBLOCK 0x6 #define TERMINATE 0x80 #define BUS_COLLI 0x40 #define DEVICE_ERR 0x20 #define SMI_I_STS 0x10 #define HST_BSY 0x08 #define IDL_STS 0x04 #define SMB_ENOERR 0x0 #define SMB_ENOACK 0x4 #define SMB_EABORT 0x10 #define SMB_ETIMEOUT 0x20 #define SMB_EBUSERR 0x1 static unsigned char fixture_sts; /* stands in for ALPM_SMBINB(sc, SMBSTS) */ static int fixture_set_on_poll; /* 0 = never; N>0 = set SMI_I_STS on poll N */ static int polls; static unsigned char ALPM_SMBINB(int reg) { (void)reg; polls++; if (fixture_set_on_poll > 0 && polls >= fixture_set_on_poll) return (fixture_sts | SMI_I_STS); return (fixture_sts); } /* verbatim structure of alpm_wait(), alpm.c:298-332 */ static int alpm_wait_orig(void) { int count = 10000; u_char sts = 0; int error; while (count--) { /* DELAY(10) elided */ sts = ALPM_SMBINB(SMBCMD); if (sts & SMI_I_STS) break; } error = SMB_ENOERR; if (!count) error |= SMB_ETIMEOUT; if (sts & TERMINATE) error |= SMB_EABORT; if (sts & BUS_COLLI) error |= SMB_ENOACK; if (sts & DEVICE_ERR) error |= SMB_EBUSERR; return (error); } /* corrected loop guard (the proposed fix) */ static int alpm_wait_fixed(void) { int count = 10000; u_char sts = 0; int error; while (count--) { sts = ALPM_SMBINB(SMBCMD); if (sts & SMI_I_STS) break; } error = SMB_ENOERR; if (count < 0) /* loop exhaustion leaves count == -1 */ error |= SMB_ETIMEOUT; if (sts & TERMINATE) error |= SMB_EABORT; if (sts & BUS_COLLI) error |= SMB_ENOACK; if (sts & DEVICE_ERR) error |= SMB_EBUSERR; return (error); } int main(void) { int r; polls = 0; fixture_sts = 0; fixture_set_on_poll = 0; r = alpm_wait_orig(); printf("A: genuine timeout (never completes, %d polls): " "error=0x%x %s\n", polls, r, (r & SMB_ETIMEOUT) ? "ETIMEOUT reported (correct)" : "BUG: ETIMEOUT NEVER reported -> alpm_*x*() returns success and " "copies stale SMBHDATA/SMBHBLOCK to the caller"); polls = 0; fixture_sts = 0; fixture_set_on_poll = 10000; r = alpm_wait_orig(); printf("B: completes on final poll #10000: " "error=0x%x %s\n", r, (r & SMB_ETIMEOUT) ? "BUG: spurious ETIMEOUT on a transfer that " "completed on the last poll" : "correct"); polls = 0; fixture_sts = 0; fixture_set_on_poll = 0; r = alpm_wait_fixed(); printf("C-fix: genuine timeout: error=0x%x %s\n", r, (r & SMB_ETIMEOUT) ? "ETIMEOUT reported (correct)" : "still wrong"); polls = 0; fixture_sts = 0; fixture_set_on_poll = 10000; r = alpm_wait_fixed(); printf("D-fix: completes on final poll: error=0x%x %s\n", r, (r & SMB_ETIMEOUT) ? "still wrong" : "success, no spurious error (correct)"); return (0); } |