DF-2750 / lkmc.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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | /* * DF-2750 PoC - lockmgr_release() multi-count blind decrement lost-wakeup. * * sys/kern/kern_lock.c lockmgr_release(): the exclusive multi-count branch * does a BLIND atomic_fetchadd(-1) based on a stale pre-read of lk_count. * When lk_lockholder == LK_KERNTHREAD any cpu may legally release the lock * (buffer-lock biodone handoff protocol, sys/sys/buf2.h BUF_KERNPROC), so * two concurrent releasers that both read LKC_XMASK == 2 drive the count * 2 -> 0 with NEITHER executing one of the three single-count cases, i.e.: * * - no wakeup(lkp) is issued (EXREQ2 waiter stranded asleep) * - LKC_EXREQ2 is not cleared * - LKC_CANCEL is not cleared * - LKC_SHARED is not pre-set * * A thread parked in lockmgr_exclusive()'s EXREQ2 tsleep then sleeps on a * lock that is now completely FREE. It stays asleep until unrelated * traffic on the same lock happens to issue a wakeup. * * This module stages the exact precondition deterministically: * lk_count = LKC_XMASK(2) | LKC_EXREQ2 (parked exclusive waiter) * lk_lockholder = LK_KERNTHREAD (any cpu may release) * and then races two lwkt threads pinned to cpu 0 / cpu 1 calling * lockmgr(&lk, LK_RELEASE) simultaneously. * * HIT condition observed by the driver: * after both releases complete, lk_count == 0x08000000 (LKC_EXREQ2 only, * zero XMASK/SMASK) and the waiter is still asleep (only a manual * wakeup() from the driver revives it). * * Harness discipline (v3): * - the three worker threads are created ONCE at module load and persist, * parking in a 1-tick polling tsleep between invocations (immune to * lost schedule IPIs on idle vCPUs); * - every in-round wait is a BOUNDED spin with a diagnostic bail-out, so * the module can never wedge the guest; * - on bail-out the driver drains leftover lock counts (legal because the * holder is LK_KERNTHREAD) so a waiter parked inside lockmgr can * always finish; MOD_UNLOAD refuses unless threads have exited. * * Build: see build.sh (kernel module against in-guest /usr/src) * Run: sysctl -w debug.lkmc=<rounds> (as root; prints result via kprintf) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/lock.h> #include <sys/sysctl.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/types.h> #include <machine/cpufunc.h> #define SPIN_CAP 2000000000LL /* ~1-4s hard cap: bail out, don't wedge */ #define PH_PARKED 0 /* threads idle in polling tsleep */ #define PH_ACTIVE 1 /* running rounds */ #define PH_STOP 2 /* exit */ static struct lock lkmc_lk; static volatile int phase; static volatile int upcnt; /* threads in active phase */ static volatile int parkedcnt; /* threads back in park */ static volatile int nexited; static volatile int rls_go; /* 0=idle 1=go (spin barrier) */ static volatile int rls_done; /* releasers completed this round */ static volatile int rls_idle; /* releasers back at barrier */ static volatile int t3_cmd; /* waiter command: 0=idle >0=round */ static volatile int t3_got; /* waiter completed acquire+release */ static volatile int lkmc_hits; static volatile int lkmc_rounds_run; static volatile uint64_t lkmc_bad; static volatile int lkmc_healed; static volatile int lkmc_stuck; /* nonzero = harness bail-out point */ static volatile int lkmc_busy; static volatile int lkmc_loaded; #define BAIL(code) do { lkmc_stuck = (code); goto out; } while (0) #define WAIT_FOR(cond, code) do { \ int64_t _n = 0; \ while (!(cond)) { \ if (++_n > SPIN_CAP) \ BAIL(code); \ cpu_ccfence(); \ } \ } while (0) /* * Exclusive-request waiter: enters lockmgr_exclusive(), blocks on EXREQ2. * The driver polls for LKC_EXREQ2 to appear - which by construction means * this thread already executed tsleep_interlock() (kern_lock.c:369) before * its fcmpset (:370), so any wakeup() on the lock ident is guaranteed to * reach it. */ static void lkmc_waiter(void *arg __unused) { int64_t n; int cmd; atomic_add_int(&nexited, 0); for (;;) { /* park between invocations (1-tick poll: IPI-loss safe) */ while (phase == PH_PARKED) tsleep(&lkmc_lk, 0, "lkpark", 1); if (phase == PH_STOP) break; atomic_add_int(&upcnt, 1); for (;;) { n = 0; while ((cmd = t3_cmd) == 0) { if (phase != PH_ACTIVE) goto repark; if (++n > SPIN_CAP) goto repark; cpu_ccfence(); } atomic_swap_int(&t3_cmd, 0); if (lockmgr(&lkmc_lk, LK_EXCLUSIVE) == 0) lockmgr(&lkmc_lk, LK_RELEASE); atomic_swap_int(&t3_got, 1); /* continue; driver re-kicks next round */ } repark: atomic_add_int(&parkedcnt, 1); } atomic_add_int(&nexited, 1); lwkt_exit(); } /* * Releaser: pinned to a specific cpu; both instances are released from a * shared spin barrier so they enter lockmgr_release() within nanoseconds * of each other, each reading the stale count (XMASK==2) at function entry. */ static void lkmc_releaser(void *arg __unused) { int64_t n; int go; for (;;) { /* park between invocations (1-tick poll: IPI-loss safe) */ while (phase == PH_PARKED) tsleep(&lkmc_lk, 0, "lkpark", 1); if (phase == PH_STOP) break; atomic_add_int(&upcnt, 1); for (;;) { n = 0; while ((go = rls_go) == 0) { if (phase != PH_ACTIVE) goto repark; if (++n > SPIN_CAP) goto repark; cpu_ccfence(); } lockmgr(&lkmc_lk, LK_RELEASE); atomic_add_int(&rls_done, 1); n = 0; while (rls_go == 1) { if (phase != PH_ACTIVE) goto repark; if (++n > SPIN_CAP) break; /* driver reset late */ cpu_ccfence(); } atomic_add_int(&rls_idle, 1); } repark: atomic_add_int(&parkedcnt, 1); } atomic_add_int(&nexited, 1); lwkt_exit(); } static int sysctl_lkmc(SYSCTL_HANDLER_ARGS) { int error; int rounds = 0; int i; uint64_t c; if (req->newptr == NULL) { int r = lkmc_rounds_run; return (sysctl_handle_int(oidp, &r, 0, req)); } error = sysctl_handle_int(oidp, &rounds, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (rounds <= 0 || rounds > 1000000) return (EINVAL); if (!lkmc_loaded) return (ENXIO); if (atomic_swap_int(&lkmc_busy, 1) != 0) return (EBUSY); lkmc_hits = 0; lkmc_healed = 0; lkmc_bad = 0; lkmc_stuck = 0; lkmc_rounds_run = 0; /* activate persistent threads */ rls_go = 0; rls_done = 0; rls_idle = 0; t3_cmd = 0; t3_got = 0; upcnt = 0; parkedcnt = 0; cpu_ccfence(); phase = PH_ACTIVE; wakeup(&lkmc_lk); tsleep(&lkmc_lk, 0, "lkmcs", 2); /* settle + wake halted cpus */ WAIT_FOR(upcnt >= 3, 10); for (i = 0; i < rounds; i++) { /* * Stage: XMASK==2 (recursive exclusive), KERNTHREAD owner, * and a parked EXREQ2 waiter. */ lockinit(&lkmc_lk, "lkmc", 0, LK_CANRECURSE); lockmgr(&lkmc_lk, LK_EXCLUSIVE); lockmgr(&lkmc_lk, LK_EXCLUSIVE); /* XMASK = 2 */ lockmgr_kernproc(&lkmc_lk); /* any cpu may release */ t3_got = 0; t3_cmd = i + 1; /* kick waiter */ WAIT_FOR((lkmc_lk.lk_count & LKC_EXREQ2) != 0, 1); /* race the two pinned releasers */ rls_done = 0; rls_idle = 0; cpu_ccfence(); rls_go = 1; WAIT_FOR(rls_done >= 2, 2); c = lkmc_lk.lk_count; if ((c & (LKC_XMASK | LKC_SMASK)) == 0 && (c & LKC_EXREQ2) && t3_got == 0) { /* * LOST WAKEUP: lock is FREE (no counts) yet the * waiter is still asleep and EXREQ2 is stranded. */ lkmc_hits++; lkmc_bad = c; wakeup(&lkmc_lk); /* manual heal */ WAIT_FOR(t3_got != 0, 3); lkmc_healed++; } else { /* normal path: case-1 release woke the waiter */ WAIT_FOR(t3_got != 0, 4); } lkmc_rounds_run = i + 1; rls_go = 0; /* reset barrier */ WAIT_FOR(rls_idle >= 2, 5); } out: /* * Drain leftover exclusive counts so a waiter parked inside * lockmgr_exclusive() can always complete. The lock holder is * LK_KERNTHREAD, which by design allows any thread to release. */ { int k; for (k = 0; k < 1000; k++) { if ((lkmc_lk.lk_count & (LKC_SMASK | LKC_XMASK)) == 0) break; lockmgr(&lkmc_lk, LK_RELEASE); wakeup(&lkmc_lk); } wakeup(&lkmc_lk); } kprintf("lkmc: rounds=%d hits=%d healed=%d bad_count=%016jx stuck=%d\n", lkmc_rounds_run, lkmc_hits, lkmc_healed, (uintmax_t)lkmc_bad, lkmc_stuck); if (lkmc_hits) kprintf("lkmc: DEFECT REPRODUCED: concurrent KERNTHREAD " "releases drove XMASK 2->0, no wakeup, waiter slept " "on a free lock\n"); if (lkmc_stuck) kprintf("lkmc: HARNESS BAIL-OUT at wait #%d\n", lkmc_stuck); /* send threads back to park */ parkedcnt = 0; phase = PH_PARKED; wakeup(&lkmc_lk); { int64_t n = 0; while (parkedcnt < 3 && n < SPIN_CAP) { ++n; cpu_ccfence(); } } atomic_swap_int(&lkmc_busy, 0); return (0); } SYSCTL_PROC(_debug, OID_AUTO, lkmc, CTLTYPE_INT | CTLFLAG_RW, 0, 0, sysctl_lkmc, "I", "lockmgr concurrent-release lost-wakeup test"); static int lkmc_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: nexited = 0; phase = PH_PARKED; if (lwkt_create(lkmc_releaser, NULL, NULL, NULL, 0, 0, "lkrel0") || lwkt_create(lkmc_releaser, NULL, NULL, NULL, 0, 1, "lkrel1") || lwkt_create(lkmc_waiter, NULL, NULL, NULL, 0, 2, "lkwait")) { kprintf("lkmc: lwkt_create failed\n"); return (ENOMEM); } lkmc_loaded = 1; kprintf("lkmc: loaded (DF-2750 PoC, persistent threads)\n"); return (0); case MOD_UNLOAD: if (atomic_swap_int(&lkmc_busy, 1) != 0) return (EBUSY); phase = PH_STOP; wakeup(&lkmc_lk); { int64_t n = 0; while (nexited < 3 && n < SPIN_CAP) { ++n; cpu_ccfence(); } if (nexited < 3) { phase = PH_PARKED; /* abort unload */ atomic_swap_int(&lkmc_busy, 0); return (EBUSY); } } kprintf("lkmc: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t lkmc_mod = { "lkmc", lkmc_modevent, 0 }; DECLARE_MODULE(lkmc, lkmc_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |