DF-5138 / df5138_wwproof.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 | /* * DF-5138 runtime proof: DragonFly's ww_mutex never returns -EALREADY * for a same-ticket relock, so ttm_eu_reserve_buffers()'s duplicate-BO * separation branch (ttm_execbuf_util.c:119-124) is dead code and a * duplicate BO in a CS list is silently recursively double-locked. * * This module performs the exact operation sequence ttm_eu_reserve_buffers() * performs when the SAME buffer object appears twice in the validation list: * * ww_acquire_init(&ticket, &reservation_ww_class) * __ttm_bo_reserve(bo, ...) -> ww_mutex_lock(&bo->resv->lock, ticket) # 1st * __ttm_bo_reserve(bo, ...) -> ww_mutex_lock(&bo->resv->lock, ticket) # 2nd, same bo * * Linux contract: 2nd call returns -EALREADY, ttm_eu moves the entry to the * dups list, the resv stays locked exactly once. * DragonFly fact: 2nd call returns 0 (LK_CANRECURSE silent recursion, * linux_wwmutex.c:62 + kern_lock.c:125-143), both entries * stay in the list, and ww_mutex_unlock() decrements * ticket->acquired only once (first unlock NULLs ww->ctx, * second unlock skips the decrement) -> accounting desync. * * It then performs the two unlocks ttm_eu_fence_buffer_objects() would do * and reports ticket->acquired (Linux semantics: 0; DFly: 1) and whether * the lock is still held (must be: no). * * Needs drm.ko loaded (ww_mutex implementation lives in drm.ko on DFly): * kldload drm * Build: make SYSDIR=/usr/src/sys * Run: kldload ./df5138_wwproof.ko ; dmesg | tail ; kldunload df5138_wwproof */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/errno.h> #include <linux/ww_mutex.h> /* Own ww class: keeps the module self-contained; ww_mutex_init() uses it * only for the wmesg name. The ww_mutex_lock()/ww_mutex_unlock() code under * test is the drm.ko implementation (sys/dev/drm/linux_wwmutex.c). */ static DEFINE_WW_CLASS(df5138_ww_class); static struct ww_mutex ww; static struct ww_acquire_ctx ctx; static int df5138_handler(module_t mod, int what, void *arg) { int r_first, r_second, result; switch (what) { case MOD_LOAD: ww_mutex_init(&ww, &df5138_ww_class); ww_acquire_init(&ctx, &df5138_ww_class); /* ttm_execbuf_util.c:113 for the 1st occurrence of the BO */ r_first = ww_mutex_lock(&ww, &ctx); /* ttm_execbuf_util.c:113 for the 2nd occurrence of the SAME BO. * On Linux this is -EALREADY and lines 119-124 move the entry * to the dups list. On DragonFly it recurses silently. */ r_second = ww_mutex_lock(&ww, &ctx); kprintf("DF5138-PROOF: first_lock=%d second_lock=%d " "(EALREADY would be -%d)\n", r_first, r_second, EALREADY); kprintf("DF5138-PROOF: ticket->acquired after both locks = %d " "(Linux dups path never gets here: 2nd lock = -EALREADY," " entry moved aside, value stays 1)\n", ctx.acquired); /* ttm_eu_fence_buffer_objects() unlocks each entry that is * still in the list once. With a working -EALREADY the dup * entry was moved to the dups list and is NOT unlocked again; * with the current DFly recursion both entries stay in the * list and both get unlocked. Mirror exactly that. */ ww_mutex_unlock(&ww); if (r_second == 0) ww_mutex_unlock(&ww); /* dup still in list (unfixed) */ kprintf("DF5138-PROOF: after unlocks ticket->acquired = %d" " (correct value: 0; 1 proves the desync)\n", ctx.acquired); result = (int)ww_mutex_is_locked(&ww); kprintf("DF5138-PROOF: resv still locked? %d " "(0 = correctly released, lockmgr recursion is " "count-balanced)\n", result); ww_acquire_fini(&ctx); ww_mutex_destroy(&ww); if (r_first == 0 && r_second == 0 && ctx.acquired == 1 && result == 0) { kprintf("DF5138-PROOF: RESULT=REPRODUCED " "(no -EALREADY; silent recursive double-lock;" " ticket accounting desync)\n"); } else if (r_first == 0 && r_second == -EALREADY && ctx.acquired == 0 && result == 0) { kprintf("DF5138-PROOF: RESULT=FIXED " "(-EALREADY returned; dup separated; " "accounting correct)\n"); } else { kprintf("DF5138-PROOF: RESULT=NOT-AS-PREDICTED " "(first=%d second=%d acquired=%d locked=%d)\n", r_first, r_second, ctx.acquired, result); } break; case MOD_UNLOAD: kprintf("DF5138-PROOF: unloaded\n"); break; default: return (EOPNOTSUPP); } return (0); } DEV_MODULE(df5138_wwproof, df5138_handler, NULL); |