Module Name: src Committed By: riastradh Date: Sun Dec 19 12:11:28 UTC 2021
Modified Files: src/sys/external/bsd/common/linux: linux_work.c Log Message: linux: Fix flush_workqueue. Simplify mechanism: we know there's only a single thread here that processes scheduled work in FIFO order (no multi-CPU workqueues -- would have to adapt if there were), so just schedule a work item that notifies of completion. The previous mechanism of counting 0, 1, or 2 generation numbers was broken by an earlier change to avoid abuse of tailqs arising from clever TAILQ_CONCAT. To generate a diff of this commit: cvs rdiff -u -r1.56 -r1.57 src/sys/external/bsd/common/linux/linux_work.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/external/bsd/common/linux/linux_work.c diff -u src/sys/external/bsd/common/linux/linux_work.c:1.56 src/sys/external/bsd/common/linux/linux_work.c:1.57 --- src/sys/external/bsd/common/linux/linux_work.c:1.56 Sun Dec 19 12:11:21 2021 +++ src/sys/external/bsd/common/linux/linux_work.c Sun Dec 19 12:11:28 2021 @@ -1,4 +1,4 @@ -/* $NetBSD: linux_work.c,v 1.56 2021/12/19 12:11:21 riastradh Exp $ */ +/* $NetBSD: linux_work.c,v 1.57 2021/12/19 12:11:28 riastradh Exp $ */ /*- * Copyright (c) 2018 The NetBSD Foundation, Inc. @@ -30,7 +30,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.56 2021/12/19 12:11:21 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.57 2021/12/19 12:11:28 riastradh Exp $"); #include <sys/types.h> #include <sys/atomic.h> @@ -461,7 +461,7 @@ linux_workqueue_thread(void *cookie) TAILQ_REMOVE(q[i], &marker, work_entry); } - /* Notify flush that we've completed a batch of work. */ + /* Notify cancel that we've completed a batch of work. */ wq->wq_gen++; cv_broadcast(&wq->wq_cv); SDT_PROBE1(sdt, linux, work, batch__done, wq); @@ -1428,46 +1428,22 @@ flush_scheduled_work(void) flush_workqueue(system_wq); } -/* - * flush_workqueue_locked(wq) - * - * Wait for all work queued on wq to complete. This does not - * include delayed work. True if there was work to be flushed, - * false it the queue was empty. - * - * Caller must hold wq's lock. - */ -static bool -flush_workqueue_locked(struct workqueue_struct *wq) -{ - uint64_t gen; - bool work_queued = false; - - KASSERT(mutex_owned(&wq->wq_lock)); - - /* Get the current generation number. */ - gen = wq->wq_gen; - - /* - * If there's any work in progress -- whether currently running - * or queued to run -- we must wait for the worker thread to - * finish that batch. - */ - if (wq->wq_current_work != NULL || - !TAILQ_EMPTY(&wq->wq_queue) || - !TAILQ_EMPTY(&wq->wq_dqueue)) { - gen++; - work_queued = true; - } +struct flush_work { + kmutex_t fw_lock; + kcondvar_t fw_cv; + struct work_struct fw_work; + bool fw_done; +}; - /* Wait until the generation number has caught up. */ - SDT_PROBE1(sdt, linux, work, flush__start, wq); - while (wq->wq_gen < gen) - cv_wait(&wq->wq_cv, &wq->wq_lock); - SDT_PROBE1(sdt, linux, work, flush__done, wq); +static void +flush_work_cb(struct work_struct *work) +{ + struct flush_work *fw = container_of(work, struct flush_work, fw_work); - /* Return whether we had to wait for anything. */ - return work_queued; + mutex_enter(&fw->fw_lock); + fw->fw_done = true; + cv_broadcast(&fw->fw_cv); + mutex_exit(&fw->fw_lock); } /* @@ -1479,10 +1455,26 @@ flush_workqueue_locked(struct workqueue_ void flush_workqueue(struct workqueue_struct *wq) { + struct flush_work fw; - mutex_enter(&wq->wq_lock); - (void)flush_workqueue_locked(wq); - mutex_exit(&wq->wq_lock); + mutex_init(&fw.fw_lock, MUTEX_DEFAULT, IPL_VM); + cv_init(&fw.fw_cv, "lxwqflsh"); + INIT_WORK(&fw.fw_work, &flush_work_cb); + fw.fw_done = false; + + SDT_PROBE1(sdt, linux, work, flush__start, wq); + queue_work(wq, &fw.fw_work); + + mutex_enter(&fw.fw_lock); + while (!fw.fw_done) + cv_wait(&fw.fw_cv, &fw.fw_lock); + mutex_exit(&fw.fw_lock); + SDT_PROBE1(sdt, linux, work, flush__done, wq); + + KASSERT(fw.fw_done); + /* no DESTROY_WORK */ + cv_destroy(&fw.fw_cv); + mutex_destroy(&fw.fw_lock); } /* @@ -1494,15 +1486,20 @@ void drain_workqueue(struct workqueue_struct *wq) { unsigned ntries = 0; + bool done; - mutex_enter(&wq->wq_lock); - while (flush_workqueue_locked(wq)) { + do { if (ntries++ == 10 || (ntries % 100) == 0) printf("linux workqueue %s" ": still clogged after %u flushes", wq->wq_name, ntries); - } - mutex_exit(&wq->wq_lock); + flush_workqueue(wq); + mutex_enter(&wq->wq_lock); + done = wq->wq_current_work == NULL; + done &= TAILQ_EMPTY(&wq->wq_queue); + done &= TAILQ_EMPTY(&wq->wq_dqueue); + mutex_exit(&wq->wq_lock); + } while (!done); } /* @@ -1599,7 +1596,9 @@ flush_delayed_work(struct delayed_work * * Waiting for the whole queue to flush is overkill, * but doesn't hurt. */ - (void)flush_workqueue_locked(wq); + mutex_exit(&wq->wq_lock); + flush_workqueue(wq); + mutex_enter(&wq->wq_lock); waited = true; } mutex_exit(&wq->wq_lock);