DF-3990 / trigger.c
/* * DF-3990 trigger.c — model of the two unbounded device-fed index walks in * ale(4) (DF-3761 alc / DF-3778 jme / DF-3934 age family, ale instance). * * (1) ale_rxeof (sys/dev/netif/ale/if_ale.c:2153-2254) has NO work bound: * the loop runs while (cons < prod) where prod is the controller-DMA'd * CMB word (:2176) and is refreshed from the new page's CMB on every * page flip (ale_rx_update_page :2091). There is no frame budget -- * ALE_PROC_MIN/ALE_PROC_MAX/ALE_PROC_DEFAULT are defined in * if_alevar.h:118-121 and NEVER referenced anywhere (dead knobs, same * as age DF-3934). A line-rate small-frame flood (or faulty device * keeping the CMB ahead) keeps prod > cons forever, so the interrupt * handler spins in m_devget+if_input holding ifp->if_serializer: * NIC + CPU livelock, interface wedge. * * (2) ale_txeof (if_ale.c:2009-2058): prod = CSR_READ_2(ALE_TPD_CONS_IDX) * (:2026) is a 16-bit register value NOT masked to ALE_TX_RING_CNT * (256). The walk `for (; cons != prod; ALE_DESC_INC(cons,256))` * frees tx_m on every descriptor between cons and prod, bounded only * by ale_tx_cnt reaching 0 — a device-fed prod ahead of true * completion over-reclaims: mbufs freed (and dmamaps unloaded) while * the descriptors still own them. * * This model simulates both walks with a device that keeps prod ahead and * prints iteration counts / reclaim extents. */ #include <stdio.h> #include <stdint.h> #define TX_RING_CNT 256 #define TX_CNT_START 40 /* queued descriptors at interrupt entry */ int main(void) { int i; printf("DF-3990 model: unbounded ale_rxeof walk + unmasked ale_txeof " "producer\n\n"); /* (1) rxeof: device keeps CMB prod at cons+1 forever */ printf("[1] ale_rxeof: loop guard is only (cons < prod); prod is\n" " device-fed and re-read on page flip. Simulating a device\n" " that keeps prod == cons+1 for 1,000,000 'delivered' slots:\n"); uint64_t iters = 0; uint32_t cons = 0, prod; for (i = 0; i < 1000000; i++) { prod = cons + 1; /* malicious/flooded CMB */ while (cons < prod) { /* if_ale.c:2178 */ cons += 32; /* one frame consumed */ iters++; /* each did m_devget + if_input */ } if (cons >= 196608) cons = 0; /* page flip re-reads CMB (:2091) */ } printf(" iterations completed=%llu, budget used by driver=0 " "(ALE_PROC_* dead knobs) -> with a real device keeping prod " "ahead this never ends (serializer held)\n\n", (unsigned long long)iters); /* (2) txeof: unmasked 16-bit producer vs 256-ring */ printf("[2] ale_txeof: prod=CSR_READ_2(0x1804) unmasked (0..65535), " "ring=%d\n", TX_RING_CNT); uint32_t cons2 = 100, prod_dev = 1000; /* device-fed garbage */ int tx_cnt = TX_CNT_START, freed = 0, prog = 0; for (; cons2 != prod_dev; prog++, cons2 = (cons2 + 1) % TX_RING_CNT) { if (tx_cnt <= 0) break; /* :2034 only bound */ tx_cnt--; freed++; /* every EOP desc's m_freem+dmamap_unload */ } printf(" prod=%u (not congruent in ring) -> cons never equals it; " "walk bounded ONLY by tx_cnt: freed ALL %d queued frames' " "mbufs+dmamaps regardless of real completion -> OVER-RECLAIM " "(premature m_freem of descriptors the NIC still owns)\n", prod_dev, freed); printf("\nTRIGGER_RC=0 (model OK; hardware-gated on this guest: " "no Atheros NIC)\n"); return (0); } |