DF-4159 / livelock_harness.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 | /* * DF-4159 logic-model harness โ RX livelock in sys/dev/netif/xe/if_xe.c xe_intr() * * This is NOT the kernel and NOT a guest run: the Xircom PCMCIA hardware does * not exist in the QEMU guest, so this harness models the card's register * interface with the semantics documented in sys/dev/netif/xe/if_xereg.h and * cross-validated against Linux's xirc2ps_cs.c driver for the same silicon: * * - XE_ESR_FULL_PACKET_RX (ESR bit 0) stays asserted while an un-released * (not skipped) full packet sits in the card RX buffer. * - The ONLY way to release/skip a packet is XE_OUTW(XE_DO, 0x8000) * (XE_DO_SKIP_RX_PACKET). Linux issues this unconditionally at the end * of every iteration โ including drops โ with the comment * "clear the received/dropped/error packet". * * The loop below is copied verbatim (control-flow identical) from * if_xe.c:742-844. MODE selects which drop paths release the packet: * MODE 0 = DragonFly code as written (continue WITHOUT XE_DO write) * MODE 1 = proposed fix (XE_DO write before each continue) * FAIL_MGET / FAIL_MCL / LEN_ZERO select the error path taken. * The loop is capped at 200000 iterations; hitting the cap means the loop * would spin forever in the kernel. */ #include <stdio.h> #include <stdlib.h> #include <string.h> /* ---- mocked card register file -------------------------------------- */ static unsigned char reg_esr, reg_rsr; static unsigned short reg_rbc, reg_do; static unsigned long do_skip_writes; /* count of XE_DO=0x8000 writes */ static unsigned long esr_reads; #define XE_ESR_FULL_PACKET_RX 0x01 #define XE_RSR_RX_OK 0x20 /* per if_xereg.h */ #define XE_DO_SKIP_RX_PACKET 0x8000 #define ETHER_CRC_LEN 4 static unsigned char XE_INB_ESR(void) { esr_reads++; return reg_esr; } static unsigned short XE_INW_RBC(void) { return reg_rbc; } static void XE_OUTW_DO(unsigned short v) { if (v == XE_DO_SKIP_RX_PACKET) { do_skip_writes++; /* packet released: next ESR read shows buffer empty */ reg_esr &= ~XE_ESR_FULL_PACKET_RX; } reg_do = v; } /* one received frame queued in the card buffer */ static void card_deliver_frame(unsigned short rbc) { reg_rbc = rbc; reg_rsr = XE_RSR_RX_OK; reg_esr |= XE_ESR_FULL_PACKET_RX; } int main(int argc, char **argv) { int mode = atoi(argv[1]); /* 0 = code as written, 1 = fixed */ int fail_mget = atoi(argv[2]); int fail_mcl = atoi(argv[3]); int len_zero = atoi(argv[4]); unsigned long iter = 0, iqdrops = 0; const unsigned long CAP = 200000; card_deliver_frame(len_zero ? ETHER_CRC_LEN : 1518); /* ==== loop copied from if_xe.c:742-844 (drop paths parameterized) == */ while ((reg_esr = XE_INB_ESR()) & XE_ESR_FULL_PACKET_RX) { unsigned char rsr = reg_rsr; if (++iter > CAP) { printf("RESULT: LIVELOCK โ loop exceeded %lu " "iterations; packet NEVER released " "(XE_DO skip writes: %lu)\n", CAP, do_skip_writes); return 2; } if (rsr & XE_RSR_RX_OK) { unsigned short len = XE_INW_RBC() - ETHER_CRC_LEN; if (len == 0) { iqdrops++; if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET); continue; /* if_xe.c:758 */ } /* MGETHDR */ if (fail_mget) { iqdrops++; if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET); continue; /* if_xe.c:772 */ } /* MCLGET for len+3 > MHLEN */ if (len + 3 > 224 && fail_mcl) { iqdrops++; if (mode) XE_OUTW_DO(XE_DO_SKIP_RX_PACKET); continue; /* if_xe.c:779 */ } /* ... packet copy + if_input elided: irrelevant to * the livelock (never reached on drop paths) ... */ } /* Skip to next packet, if there is one */ XE_OUTW_DO(XE_DO_SKIP_RX_PACKET); /* if_xe.c:843 */ } /* ==================================================================== */ printf("RESULT: loop terminated normally after %lu iterations " "(drops: %lu, XE_DO skip writes: %lu, ESR reads: %lu)\n", iter, iqdrops, do_skip_writes, esr_reads); return (iqdrops && !do_skip_writes) ? 2 : 0; } |