Module Name:    src
Committed By:   riastradh
Date:           Mon Aug 27 14:57:21 UTC 2018

Modified Files:
        src/sys/external/bsd/common/include/linux: workqueue.h
        src/sys/external/bsd/common/linux: linux_work.c

Log Message:
Draft rewrite of Linux workqueue reimplementation.

Just use an explicit thread; don't attempt to fudge it with
workqueue(9).  No doubt there are various mistakes in here, but they
should be easier to get right than the mega-kludgerific nonsense that
preceded this draft.


To generate a diff of this commit:
cvs rdiff -u -r1.8 -r1.9 \
    src/sys/external/bsd/common/include/linux/workqueue.h
cvs rdiff -u -r1.11 -r1.12 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/include/linux/workqueue.h
diff -u src/sys/external/bsd/common/include/linux/workqueue.h:1.8 src/sys/external/bsd/common/include/linux/workqueue.h:1.9
--- src/sys/external/bsd/common/include/linux/workqueue.h:1.8	Mon Aug 27 07:46:28 2018
+++ src/sys/external/bsd/common/include/linux/workqueue.h	Mon Aug 27 14:57:21 2018
@@ -1,7 +1,7 @@
-/*	$NetBSD: workqueue.h,v 1.8 2018/08/27 07:46:28 riastradh Exp $	*/
+/*	$NetBSD: workqueue.h,v 1.9 2018/08/27 14:57:21 riastradh Exp $	*/
 
 /*-
- * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2013, 2018 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
@@ -32,12 +32,10 @@
 #ifndef _LINUX_WORKQUEUE_H_
 #define _LINUX_WORKQUEUE_H_
 
-#include <sys/types.h>
-#include <sys/callout.h>
 #include <sys/queue.h>
-#include <sys/workqueue.h>
+#include <sys/stdbool.h>
 
-#include <linux/kernel.h>
+#include <linux/kernel.h>	/* container_of */
 
 #define	INIT_DELAYED_WORK		linux_INIT_DELAYED_WORK
 #define	INIT_WORK			linux_INIT_WORK
@@ -64,25 +62,21 @@
 struct workqueue_struct;
 
 struct work_struct {
-	struct work		w_wk;
-	__cpu_simple_lock_t	w_lock; /* XXX */
-	enum {
-		WORK_IDLE,
-		WORK_DELAYED,
-		WORK_PENDING,
-		WORK_INVOKED,
-		WORK_CANCELLED,
-		WORK_DELAYED_CANCELLED,
-	}			w_state;
-	struct workqueue_struct	*w_wq;
-	void			(*func)(struct work_struct *);
+	struct workqueue_struct	*volatile work_queue;
+	TAILQ_ENTRY(work_struct)	work_entry;
+	void	(*func)(struct work_struct *); /* Linux API name */
 };
 
 struct delayed_work {
-	/* Not dw_work; name must match Linux.  */
-	struct work_struct		work;
+	struct work_struct		work; /* Linux API name */
 	struct callout			dw_callout;
 	TAILQ_ENTRY(delayed_work)	dw_entry;
+	enum {
+		DELAYED_WORK_IDLE,
+		DELAYED_WORK_SCHEDULED,
+		DELAYED_WORK_RESCHEDULED,
+		DELAYED_WORK_CANCELLED,
+	}				dw_state;
 };
 
 static inline struct delayed_work *
@@ -110,8 +104,9 @@ void	flush_scheduled_work(void);
 void	INIT_WORK(struct work_struct *, void (*)(struct work_struct *));
 bool	schedule_work(struct work_struct *);
 bool	queue_work(struct workqueue_struct *, struct work_struct *);
+bool	cancel_work(struct work_struct *);
 bool	cancel_work_sync(struct work_struct *);
-void	flush_work(struct work_struct *);
+bool	flush_work(struct work_struct *);
 
 void	INIT_DELAYED_WORK(struct delayed_work *,
 	    void (*)(struct work_struct *));
@@ -122,7 +117,7 @@ bool	mod_delayed_work(struct workqueue_s
 	    unsigned long ticks);
 bool	cancel_delayed_work(struct delayed_work *);
 bool	cancel_delayed_work_sync(struct delayed_work *);
-void	flush_delayed_work(struct delayed_work *);
+bool	flush_delayed_work(struct delayed_work *);
 
 struct work_struct *
 	current_work(void);

Index: src/sys/external/bsd/common/linux/linux_work.c
diff -u src/sys/external/bsd/common/linux/linux_work.c:1.11 src/sys/external/bsd/common/linux/linux_work.c:1.12
--- src/sys/external/bsd/common/linux/linux_work.c:1.11	Mon Aug 27 14:48:47 2018
+++ src/sys/external/bsd/common/linux/linux_work.c	Mon Aug 27 14:57:21 2018
@@ -1,7 +1,7 @@
-/*	$NetBSD: linux_work.c,v 1.11 2018/08/27 14:48:47 riastradh Exp $	*/
+/*	$NetBSD: linux_work.c,v 1.12 2018/08/27 14:57:21 riastradh Exp $	*/
 
 /*-
- * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * Copyright (c) 2018 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
@@ -30,213 +30,91 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.11 2018/08/27 14:48:47 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.12 2018/08/27 14:57:21 riastradh Exp $");
 
 #include <sys/types.h>
-#include <sys/param.h>
 #include <sys/atomic.h>
 #include <sys/callout.h>
 #include <sys/condvar.h>
 #include <sys/errno.h>
-#include <sys/intr.h>
 #include <sys/kmem.h>
+#include <sys/kthread.h>
+#include <sys/lwp.h>
 #include <sys/mutex.h>
 #include <sys/queue.h>
-#include <sys/systm.h>
-#include <sys/workqueue.h>
-#include <sys/cpu.h>
-
-#include <machine/lock.h>
 
 #include <linux/workqueue.h>
 
 struct workqueue_struct {
-	struct workqueue		*wq_workqueue;
-
-	struct rb_node			wq_node;
-	struct lwp			*wq_lwp;
-
-	/* XXX The following should all be per-CPU.  */
 	kmutex_t			wq_lock;
-
-	/*
-	 * Condvar for when any state related to this workqueue
-	 * changes.  XXX Could split this into multiple condvars for
-	 * different purposes, but whatever...
-	 */
 	kcondvar_t			wq_cv;
-
 	TAILQ_HEAD(, delayed_work)	wq_delayed;
+	TAILQ_HEAD(, work_struct)	wq_queue;
 	struct work_struct		*wq_current_work;
+	int				wq_flags;
+	struct lwp			*wq_lwp;
+	uint64_t			wq_gen;
+	bool				wq_requeued:1;
+	bool				wq_dying:1;
 };
 
-static void	linux_work_lock_init(struct work_struct *);
-static void	linux_work_lock(struct work_struct *);
-static void	linux_work_unlock(struct work_struct *);
-static bool	linux_work_locked(struct work_struct *) __diagused;
-
-static void	linux_wq_barrier(struct work_struct *);
-
-static void	linux_wait_for_cancelled_work(struct work_struct *);
-static void	linux_wait_for_invoked_work(struct work_struct *);
-static void	linux_worker(struct work *, void *);
-
-static void	linux_cancel_delayed_work_callout(struct delayed_work *, bool);
-static void	linux_wait_for_delayed_cancelled_work(struct delayed_work *);
-static void	linux_worker_intr(void *);
-
-struct workqueue_struct		*system_wq;
-struct workqueue_struct		*system_long_wq;
-struct workqueue_struct		*system_power_efficient_wq;
-
-static struct {
-	kmutex_t		lock;
-	struct rb_tree		tree;
-} workqueues __cacheline_aligned;
-
-static const rb_tree_ops_t	workqueues_rb_ops;
+static void __dead	linux_workqueue_thread(void *);
+static void		linux_workqueue_timeout(void *);
+static void		queue_delayed_work_anew(struct workqueue_struct *,
+			    struct delayed_work *, unsigned long);
+
+static specificdata_key_t workqueue_key __read_mostly;
+
+struct workqueue_struct	*system_wq __read_mostly;
+struct workqueue_struct	*system_long_wq __read_mostly;
+struct workqueue_struct	*system_power_efficient_wq __read_mostly;
 
 int
 linux_workqueue_init(void)
 {
+	int error;
 
-	mutex_init(&workqueues.lock, MUTEX_DEFAULT, IPL_VM);
-	rb_tree_init(&workqueues.tree, &workqueues_rb_ops);
+	error = lwp_specific_key_create(&workqueue_key, NULL);
+	if (error)
+		goto fail0;
 
 	system_wq = alloc_ordered_workqueue("lnxsyswq", 0);
-	if (system_wq == NULL)
-		goto fail0;
+	if (system_wq == NULL) {
+		error = ENOMEM;
+		goto fail1;
+	}
 
 	system_long_wq = alloc_ordered_workqueue("lnxlngwq", 0);
-	if (system_long_wq == NULL)
-		goto fail1;
+	if (system_long_wq == NULL) {
+		error = ENOMEM;
+		goto fail2;
+	}
 
 	system_power_efficient_wq = alloc_ordered_workqueue("lnxpwrwq", 0);
-	if (system_long_wq == NULL)
-		goto fail2;
+	if (system_long_wq == NULL) {
+		error = ENOMEM;
+		goto fail3;
+	}
 
 	return 0;
 
-fail3: __unused
+fail4: __unused
 	destroy_workqueue(system_power_efficient_wq);
-fail2:	destroy_workqueue(system_long_wq);
-fail1:	destroy_workqueue(system_wq);
-fail0:	mutex_destroy(&workqueues.lock);
-	return ENOMEM;
+fail3:	destroy_workqueue(system_long_wq);
+fail2:	destroy_workqueue(system_wq);
+fail1:	lwp_specific_key_delete(workqueue_key);
+fail0:	KASSERT(error);
+	return error;
 }
 
 void
 linux_workqueue_fini(void)
 {
 
+	destroy_workqueue(system_power_efficient_wq);
 	destroy_workqueue(system_long_wq);
-	system_long_wq = NULL;
 	destroy_workqueue(system_wq);
-	system_wq = NULL;
-	KASSERT(RB_TREE_MIN(&workqueues.tree) == NULL);
-	mutex_destroy(&workqueues.lock);
-}
-
-/*
- * Table of workqueue LWPs for validation -- assumes there is only one
- * thread per workqueue.
- *
- * XXX Mega-kludgerific!
- */
-
-static int
-compare_nodes(void *cookie, const void *va, const void *vb)
-{
-	const struct workqueue_struct *wa = va;
-	const struct workqueue_struct *wb = vb;
-
-	if ((uintptr_t)wa->wq_lwp < (uintptr_t)wb->wq_lwp)
-		return -1;
-	if ((uintptr_t)wa->wq_lwp > (uintptr_t)wb->wq_lwp)
-		return +1;
-	return 0;
-}
-
-static int
-compare_key(void *cookie, const void *vn, const void *vk)
-{
-	const struct workqueue_struct *w = vn;
-	const struct lwp *lwp = vk;
-
-	if ((uintptr_t)w->wq_lwp < (uintptr_t)lwp)
-		return -1;
-	if ((uintptr_t)w->wq_lwp > (uintptr_t)lwp)
-		return +1;
-	return 0;
-}
-
-static const rb_tree_ops_t workqueues_rb_ops = {
-	.rbto_compare_nodes = compare_nodes,
-	.rbto_compare_key = compare_key,
-	.rbto_node_offset = offsetof(struct workqueue_struct, wq_node),
-};
-
-struct wq_whoami_work {
-	kmutex_t		www_lock;
-	kcondvar_t		www_cv;
-	struct workqueue_struct	*www_wq;
-	struct work_struct	www_work;
-};
-
-static void
-workqueue_whoami_work(struct work_struct *work)
-{
-	struct wq_whoami_work *www = container_of(work, struct wq_whoami_work,
-	    www_work);
-	struct workqueue_struct *wq = www->www_wq;
-
-	KASSERT(wq->wq_lwp == NULL);
-	wq->wq_lwp = curlwp;
-
-	mutex_enter(&www->www_lock);
-	cv_broadcast(&www->www_cv);
-	mutex_exit(&www->www_lock);
-}
-
-static void
-workqueue_whoami(struct workqueue_struct *wq)
-{
-	struct wq_whoami_work www;
-	struct workqueue_struct *collision __diagused;
-
-	mutex_init(&www.www_lock, MUTEX_DEFAULT, IPL_NONE);
-	cv_init(&www.www_cv, "wqwhoami");
-
-	INIT_WORK(&www.www_work, &workqueue_whoami_work);
-	queue_work(wq, &www.www_work);
-
-	mutex_enter(&www.www_lock);
-	while (wq->wq_lwp == NULL)
-		cv_wait(&www.www_cv, &www.www_lock);
-	mutex_exit(&www.www_lock);
-
-	cv_destroy(&www.www_cv);
-	mutex_destroy(&www.www_lock);
-
-	mutex_enter(&workqueues.lock);
-	collision = rb_tree_insert_node(&workqueues.tree, wq);
-	mutex_exit(&workqueues.lock);
-
-	KASSERT(collision == wq);
-}
-
-struct work_struct *
-current_work(void)
-{
-	struct workqueue_struct *wq;
-	struct work_struct *work;
-
-	mutex_enter(&workqueues.lock);
-	wq = rb_tree_find_node(&workqueues.tree, curlwp);
-	work = (wq == NULL ? NULL : wq->wq_current_work);
-	mutex_exit(&workqueues.lock);
-
-	return work;
+	lwp_specific_key_delete(workqueue_key);
 }
 
 /*
@@ -244,33 +122,35 @@ current_work(void)
  */
 
 struct workqueue_struct *
-alloc_ordered_workqueue(const char *name, int linux_flags)
+alloc_ordered_workqueue(const char *name, int flags)
 {
 	struct workqueue_struct *wq;
-	int flags = WQ_MPSAFE;
 	int error;
 
-	KASSERT(linux_flags == 0);
+	KASSERT(flags == 0);
 
 	wq = kmem_alloc(sizeof(*wq), KM_SLEEP);
-	error = workqueue_create(&wq->wq_workqueue, name, &linux_worker,
-	    wq, PRI_NONE, IPL_VM, flags);
-	if (error) {
-		kmem_free(wq, sizeof(*wq));
-		return NULL;
-	}
 
-	mutex_init(&wq->wq_lock, MUTEX_DEFAULT, IPL_VM);
+	mutex_init(&wq->wq_lock, MUTEX_DEFAULT, IPL_NONE);
 	cv_init(&wq->wq_cv, name);
 	TAILQ_INIT(&wq->wq_delayed);
+	TAILQ_INIT(&wq->wq_queue);
 	wq->wq_current_work = NULL;
 
-	if (0) {		/* XXX broken garbage */
-		workqueue_whoami(wq);
-		KASSERT(wq->wq_lwp != NULL);
-	}
+	error = kthread_create(PRI_NONE,
+	    KTHREAD_MPSAFE|KTHREAD_TS|KTHREAD_MUSTJOIN, NULL,
+	    &linux_workqueue_thread, wq, &wq->wq_lwp, "%s", name);
+	if (error)
+		goto fail0;
 
 	return wq;
+
+fail0:	KASSERT(TAILQ_EMPTY(&wq->wq_queue));
+	KASSERT(TAILQ_EMPTY(&wq->wq_delayed));
+	cv_destroy(&wq->wq_cv);
+	mutex_destroy(&wq->wq_lock);
+	kmem_free(wq, sizeof(*wq));
+	return NULL;
 }
 
 void
@@ -278,35 +158,38 @@ destroy_workqueue(struct workqueue_struc
 {
 
 	/*
-	 * Cancel all delayed work.
+	 * Cancel all delayed work.  We do this first because any
+	 * delayed work that that has already timed out, which we can't
+	 * cancel, may have queued new work.
 	 */
 	for (;;) {
-		struct delayed_work *dw;
+		struct delayed_work *dw = NULL;
 
 		mutex_enter(&wq->wq_lock);
-		if (TAILQ_EMPTY(&wq->wq_delayed)) {
-			dw = NULL;
-		} else {
+		if (!TAILQ_EMPTY(&wq->wq_delayed)) {
 			dw = TAILQ_FIRST(&wq->wq_delayed);
-			TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
+			if (!callout_halt(&dw->dw_callout, &wq->wq_lock))
+				TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
 		}
 		mutex_exit(&wq->wq_lock);
 
 		if (dw == NULL)
 			break;
-
 		cancel_delayed_work_sync(dw);
 	}
 
-	/*
-	 * workqueue_destroy empties the queue; we need not wait for
-	 * completion explicitly.  However, we can't destroy the
-	 * condvar or mutex until this is done.
-	 */
-	workqueue_destroy(wq->wq_workqueue);
-	KASSERT(wq->wq_current_work == NULL);
-	wq->wq_workqueue = NULL;
+	/* Tell the thread to exit.  */
+	mutex_enter(&wq->wq_lock);
+	wq->wq_dying = true;
+	cv_broadcast(&wq->wq_cv);
+	mutex_exit(&wq->wq_lock);
 
+	/* Wait for it to exit.  */
+	(void)kthread_join(wq->wq_lwp);
+
+	KASSERT(wq->wq_current_work == NULL);
+	KASSERT(TAILQ_EMPTY(&wq->wq_queue));
+	KASSERT(TAILQ_EMPTY(&wq->wq_delayed));
 	cv_destroy(&wq->wq_cv);
 	mutex_destroy(&wq->wq_lock);
 
@@ -314,158 +197,108 @@ destroy_workqueue(struct workqueue_struc
 }
 
 /*
- * Flush
- *
- * Note:  This doesn't cancel or wait for delayed work.  This seems to
- * match what Linux does (or, doesn't do).
+ * Work thread and callout
  */
 
-void
-flush_scheduled_work(void)
+static void __dead
+linux_workqueue_thread(void *cookie)
 {
-	flush_workqueue(system_wq);
-}
-
-struct wq_flush_work {
-	struct work_struct	wqfw_work;
-	struct wq_flush		*wqfw_flush;
-};
-
-struct wq_flush {
-	kmutex_t	wqf_lock;
-	kcondvar_t	wqf_cv;
-	unsigned int	wqf_n;
-};
+	struct workqueue_struct *const wq = cookie;
+	TAILQ_HEAD(, work_struct) tmp;
 
-void
-flush_work(struct work_struct *work)
-{
-	struct workqueue_struct *const wq = work->w_wq;
+	lwp_setspecific(workqueue_key, wq);
 
-	if (wq != NULL)
-		flush_workqueue(wq);
-}
+	mutex_enter(&wq->wq_lock);
+	for (;;) {
+		/* Wait until there's activity.  If we're dying, stop.  */
+		while (TAILQ_EMPTY(&wq->wq_queue) && !wq->wq_dying)
+			cv_wait(&wq->wq_cv, &wq->wq_lock);
+		if (wq->wq_dying)
+			break;
 
-void
-flush_workqueue(struct workqueue_struct *wq)
-{
-	static const struct wq_flush zero_wqf;
-	struct wq_flush wqf = zero_wqf;
+		/* Grab a batch of work off the queue.  */
+		KASSERT(!TAILQ_EMPTY(&wq->wq_queue));
+		TAILQ_INIT(&tmp);
+		TAILQ_CONCAT(&tmp, &wq->wq_queue, work_entry);
+
+		/* Process each work item in the batch.  */
+		while (!TAILQ_EMPTY(&tmp)) {
+			struct work_struct *const work = TAILQ_FIRST(&tmp);
+
+			TAILQ_REMOVE(&tmp, work, work_entry);
+			KASSERT(wq->wq_current_work == NULL);
+			wq->wq_current_work = work;
 
-	mutex_init(&wqf.wqf_lock, MUTEX_DEFAULT, IPL_NONE);
-	cv_init(&wqf.wqf_cv, "lnxwflsh");
+			mutex_exit(&wq->wq_lock);
+			(*work->func)(work);
+			mutex_enter(&wq->wq_lock);
 
-	if (1) {
-		struct wq_flush_work *const wqfw = kmem_zalloc(sizeof(*wqfw),
-		    KM_SLEEP);
-
-		wqf.wqf_n = 1;
-		wqfw->wqfw_flush = &wqf;
-		INIT_WORK(&wqfw->wqfw_work, &linux_wq_barrier);
-		wqfw->wqfw_work.w_wq = wq;
-		wqfw->wqfw_work.w_state = WORK_PENDING;
-		workqueue_enqueue(wq->wq_workqueue, &wqfw->wqfw_work.w_wk,
-		    NULL);
-	} else {
-		struct cpu_info *ci;
-		CPU_INFO_ITERATOR cii;
-		struct wq_flush_work *wqfw;
-
-		panic("per-CPU Linux workqueues don't work yet!");
-
-		wqf.wqf_n = 0;
-		for (CPU_INFO_FOREACH(cii, ci)) {
-			wqfw = kmem_zalloc(sizeof(*wqfw), KM_SLEEP);
-			mutex_enter(&wqf.wqf_lock);
-			wqf.wqf_n++;
-			mutex_exit(&wqf.wqf_lock);
-			wqfw->wqfw_flush = &wqf;
-			INIT_WORK(&wqfw->wqfw_work, &linux_wq_barrier);
-			wqfw->wqfw_work.w_state = WORK_PENDING;
-			wqfw->wqfw_work.w_wq = wq;
-			workqueue_enqueue(wq->wq_workqueue,
-			    &wqfw->wqfw_work.w_wk, ci);
+			KASSERT(wq->wq_current_work == work);
+			KASSERT(work->work_queue == wq);
+			if (wq->wq_requeued)
+				wq->wq_requeued = false;
+			else
+				work->work_queue = NULL;
+			wq->wq_current_work = NULL;
+			cv_broadcast(&wq->wq_cv);
 		}
-	}
-
-	mutex_enter(&wqf.wqf_lock);
-	while (0 < wqf.wqf_n)
-		cv_wait(&wqf.wqf_cv, &wqf.wqf_lock);
-	mutex_exit(&wqf.wqf_lock);
 
-	cv_destroy(&wqf.wqf_cv);
-	mutex_destroy(&wqf.wqf_lock);
-}
-
-static void
-linux_wq_barrier(struct work_struct *work)
-{
-	struct wq_flush_work *const wqfw = container_of(work,
-	    struct wq_flush_work, wqfw_work);
-	struct wq_flush *const wqf = wqfw->wqfw_flush;
-
-	mutex_enter(&wqf->wqf_lock);
-	if (--wqf->wqf_n == 0)
-		cv_broadcast(&wqf->wqf_cv);
-	mutex_exit(&wqf->wqf_lock);
-
-	kmem_free(wqfw, sizeof(*wqfw));
-}
-
-/*
- * Work locking
- *
- * We use __cpu_simple_lock(9) rather than mutex(9) because Linux code
- * does not destroy work, so there is nowhere to call mutex_destroy.
- *
- * XXX This is getting out of hand...  Really, work items shouldn't
- * have locks in them at all; instead the workqueues should.
- */
-
-static void
-linux_work_lock_init(struct work_struct *work)
-{
+		/* Notify flush that we've completed a batch of work.  */
+		wq->wq_gen++;
+		cv_broadcast(&wq->wq_cv);
+	}
+	mutex_exit(&wq->wq_lock);
 
-	__cpu_simple_lock_init(&work->w_lock);
+	kthread_exit(0);
 }
 
 static void
-linux_work_lock(struct work_struct *work)
+linux_workqueue_timeout(void *cookie)
 {
-	struct cpu_info *ci;
-	int cnt, s;
-
-	/* XXX Copypasta of MUTEX_SPIN_SPLRAISE.  */
-	s = splvm();
-	ci = curcpu();
-	cnt = ci->ci_mtx_count--;
-	__insn_barrier();
-	if (cnt == 0)
-		ci->ci_mtx_oldspl = s;
+	struct delayed_work *const dw = cookie;
+	struct workqueue_struct *const wq = dw->work.work_queue;
 
-	__cpu_simple_lock(&work->w_lock);
+	KASSERT(wq != NULL);
+	mutex_enter(&wq->wq_lock);
+	switch (dw->dw_state) {
+	case DELAYED_WORK_IDLE:
+		panic("delayed work callout uninitialized: %p", dw);
+	case DELAYED_WORK_SCHEDULED:
+		dw->dw_state = DELAYED_WORK_IDLE;
+		callout_destroy(&dw->dw_callout);
+		TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
+		TAILQ_INSERT_TAIL(&wq->wq_queue, &dw->work, work_entry);
+		cv_broadcast(&wq->wq_cv);
+		break;
+	case DELAYED_WORK_RESCHEDULED:
+		dw->dw_state = DELAYED_WORK_SCHEDULED;
+		break;
+	case DELAYED_WORK_CANCELLED:
+		dw->dw_state = DELAYED_WORK_IDLE;
+		callout_destroy(&dw->dw_callout);
+		TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
+		break;
+	default:
+		panic("delayed work callout in bad state: %p", dw);
+	}
+	mutex_exit(&wq->wq_lock);
 }
 
-static void
-linux_work_unlock(struct work_struct *work)
+struct work_struct *
+current_work(void)
 {
-	struct cpu_info *ci;
-	int s;
-
-	__cpu_simple_unlock(&work->w_lock);
+	struct workqueue_struct *wq = lwp_getspecific(workqueue_key);
 
-	/* XXX Copypasta of MUTEX_SPIN_SPLRESTORE.  */
-	ci = curcpu();
-	s = ci->ci_mtx_oldspl;
-	__insn_barrier();
-	if (++ci->ci_mtx_count == 0)
-		splx(s);
-}
+	/* If we're not a workqueue thread, then there's no work.  */
+	if (wq == NULL)
+		return NULL;
 
-static bool __diagused
-linux_work_locked(struct work_struct *work)
-{
-	return __SIMPLELOCK_LOCKED_P(&work->w_lock);
+	/*
+	 * Otherwise, this should be possible only while work is in
+	 * progress.  Return the current work item.
+	 */
+	KASSERT(wq->wq_current_work != NULL);
+	return wq->wq_current_work;
 }
 
 /*
@@ -476,201 +309,82 @@ void
 INIT_WORK(struct work_struct *work, void (*fn)(struct work_struct *))
 {
 
-	linux_work_lock_init(work);
-	work->w_state = WORK_IDLE;
-	work->w_wq = NULL;
+	work->work_queue = NULL;
 	work->func = fn;
 }
 
 bool
 schedule_work(struct work_struct *work)
 {
+
 	return queue_work(system_wq, work);
 }
 
 bool
 queue_work(struct workqueue_struct *wq, struct work_struct *work)
 {
-	/* True if we put it on the queue, false if it was already there.  */
+	struct workqueue_struct *wq0;
 	bool newly_queued;
 
 	KASSERT(wq != NULL);
 
-	linux_work_lock(work);
-	switch (work->w_state) {
-	case WORK_IDLE:
-	case WORK_INVOKED:
-		work->w_state = WORK_PENDING;
-		work->w_wq = wq;
-		workqueue_enqueue(wq->wq_workqueue, &work->w_wk, NULL);
+	mutex_enter(&wq->wq_lock);
+	if (__predict_true((wq0 = atomic_cas_ptr(&work->work_queue, NULL, wq))
+		== NULL)) {
+		TAILQ_INSERT_TAIL(&wq->wq_queue, work, work_entry);
 		newly_queued = true;
-		break;
-
-	case WORK_DELAYED:
-		panic("queue_work(delayed work %p)", work);
-		break;
-
-	case WORK_PENDING:
-		KASSERT(work->w_wq == wq);
-		newly_queued = false;
-		break;
-
-	case WORK_CANCELLED:
+	} else {
+		KASSERT(wq0 == wq);
 		newly_queued = false;
-		break;
-
-	case WORK_DELAYED_CANCELLED:
-		panic("queue_work(delayed work %p)", work);
-		break;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-		break;
 	}
-	linux_work_unlock(work);
+	mutex_exit(&wq->wq_lock);
 
 	return newly_queued;
 }
 
 bool
-cancel_work_sync(struct work_struct *work)
+cancel_work(struct work_struct *work)
 {
+	struct workqueue_struct *wq;
 	bool cancelled_p = false;
 
-	linux_work_lock(work);
-	switch (work->w_state) {
-	case WORK_IDLE:		/* Nothing to do.  */
-		break;
-
-	case WORK_DELAYED:
-		panic("cancel_work_sync(delayed work %p)", work);
-		break;
-
-	case WORK_PENDING:
-		work->w_state = WORK_CANCELLED;
-		linux_wait_for_cancelled_work(work);
+	wq = work->work_queue;
+	mutex_enter(&wq->wq_lock);
+	if (__predict_false(work->work_queue != wq)) {
+		cancelled_p = false;
+	} else if (wq->wq_current_work == work) {
+		cancelled_p = false;
+	} else {
+		TAILQ_REMOVE(&wq->wq_queue, work, work_entry);
 		cancelled_p = true;
-		break;
-
-	case WORK_INVOKED:
-		linux_wait_for_invoked_work(work);
-		break;
-
-	case WORK_CANCELLED:	/* Already done.  */
-		break;
-
-	case WORK_DELAYED_CANCELLED:
-		panic("cancel_work_sync(delayed work %p)", work);
-		break;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-		break;
 	}
-	linux_work_unlock(work);
+	mutex_exit(&wq->wq_lock);
 
 	return cancelled_p;
 }
 
-static void
-linux_wait_for_cancelled_work(struct work_struct *work)
-{
-	struct workqueue_struct *wq;
-
-	KASSERT(linux_work_locked(work));
-	KASSERT(work->w_state == WORK_CANCELLED);
-
-	wq = work->w_wq;
-	do {
-		mutex_enter(&wq->wq_lock);
-		linux_work_unlock(work);
-		cv_wait(&wq->wq_cv, &wq->wq_lock);
-		mutex_exit(&wq->wq_lock);
-		linux_work_lock(work);
-	} while ((work->w_state == WORK_CANCELLED) && (work->w_wq == wq));
-}
-
-static void
-linux_wait_for_invoked_work(struct work_struct *work)
+bool
+cancel_work_sync(struct work_struct *work)
 {
 	struct workqueue_struct *wq;
+	bool cancelled_p = false;
 
-	KASSERT(linux_work_locked(work));
-	KASSERT(work->w_state == WORK_INVOKED);
-
-	wq = work->w_wq;
+	wq = work->work_queue;
 	mutex_enter(&wq->wq_lock);
-	linux_work_unlock(work);
-	while (wq->wq_current_work == work)
-		cv_wait(&wq->wq_cv, &wq->wq_lock);
+	if (__predict_false(work->work_queue != wq)) {
+		cancelled_p = false;
+	} else if (wq->wq_current_work == work) {
+		do {
+			cv_wait(&wq->wq_cv, &wq->wq_lock);
+		} while (wq->wq_current_work == work);
+		cancelled_p = false;
+	} else {
+		TAILQ_REMOVE(&wq->wq_queue, work, work_entry);
+		cancelled_p = true;
+	}
 	mutex_exit(&wq->wq_lock);
 
-	linux_work_lock(work);	/* XXX needless relock */
-}
-
-static void
-linux_worker(struct work *wk, void *arg)
-{
-	struct work_struct *const work = container_of(wk, struct work_struct,
-	    w_wk);
-	struct workqueue_struct *const wq = arg;
-
-	linux_work_lock(work);
-	switch (work->w_state) {
-	case WORK_IDLE:
-		panic("idle work %p got queued: %p", work, wq);
-		break;
-
-	case WORK_DELAYED:
-		panic("delayed work %p got queued: %p", work, wq);
-		break;
-
-	case WORK_PENDING:
-		KASSERT(work->w_wq == wq);
-
-		/* Get ready to invoke this one.  */
-		mutex_enter(&wq->wq_lock);
-		work->w_state = WORK_INVOKED;
-		KASSERT(wq->wq_current_work == NULL);
-		wq->wq_current_work = work;
-		mutex_exit(&wq->wq_lock);
-
-		/* Unlock it and do it.  Can't use work after this.  */
-		linux_work_unlock(work);
-		(*work->func)(work);
-
-		/* All done.  Notify anyone waiting for completion.  */
-		mutex_enter(&wq->wq_lock);
-		KASSERT(wq->wq_current_work == work);
-		wq->wq_current_work = NULL;
-		cv_broadcast(&wq->wq_cv);
-		mutex_exit(&wq->wq_lock);
-		return;
-
-	case WORK_INVOKED:
-		panic("invoked work %p got requeued: %p", work, wq);
-		break;
-
-	case WORK_CANCELLED:
-		KASSERT(work->w_wq == wq);
-
-		/* Return to idle; notify anyone waiting for cancellation.  */
-		mutex_enter(&wq->wq_lock);
-		work->w_state = WORK_IDLE;
-		work->w_wq = NULL;
-		cv_broadcast(&wq->wq_cv);
-		mutex_exit(&wq->wq_lock);
-		break;
-
-	case WORK_DELAYED_CANCELLED:
-		panic("cancelled delayed work %p got uqeued: %p", work, wq);
-		break;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-		break;
-	}
-	linux_work_unlock(work);
+	return cancelled_p;
 }
 
 /*
@@ -680,72 +394,75 @@ linux_worker(struct work *wk, void *arg)
 void
 INIT_DELAYED_WORK(struct delayed_work *dw, void (*fn)(struct work_struct *))
 {
+
 	INIT_WORK(&dw->work, fn);
+	dw->dw_state = DELAYED_WORK_IDLE;
+
+	/*
+	 * Defer callout_init until we are going to schedule the
+	 * callout, which can then callout_destroy it, because
+	 * otherwise since there's no DESTROY_DELAYED_WORK or anything
+	 * we have no opportunity to call callout_destroy.
+	 */
 }
 
 bool
 schedule_delayed_work(struct delayed_work *dw, unsigned long ticks)
 {
+
 	return queue_delayed_work(system_wq, dw, ticks);
 }
 
-bool
-queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dw,
+static void
+queue_delayed_work_anew(struct workqueue_struct *wq, struct delayed_work *dw,
     unsigned long ticks)
 {
-	bool newly_queued;
-
-	KASSERT(wq != NULL);
 
-	linux_work_lock(&dw->work);
-	switch (dw->work.w_state) {
-	case WORK_IDLE:
-	case WORK_INVOKED:
-		if (ticks == 0) {
-			/* Skip the delay and queue it now.  */
-			dw->work.w_state = WORK_PENDING;
-			dw->work.w_wq = wq;
-			workqueue_enqueue(wq->wq_workqueue, &dw->work.w_wk,
-			    NULL);
+	KASSERT(mutex_owned(&wq->wq_lock));
+	KASSERT(dw->work.work_queue == wq);
+	KASSERT((dw->dw_state == DELAYED_WORK_IDLE) ||
+	    (dw->dw_state == DELAYED_WORK_SCHEDULED));
+
+	if (ticks == 0) {
+		if (dw->dw_state == DELAYED_WORK_SCHEDULED) {
+			callout_destroy(&dw->dw_callout);
+			TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
 		} else {
+			KASSERT(dw->dw_state == DELAYED_WORK_IDLE);
+		}
+		TAILQ_INSERT_TAIL(&wq->wq_queue, &dw->work, work_entry);
+		dw->dw_state = DELAYED_WORK_IDLE;
+	} else {
+		if (dw->dw_state == DELAYED_WORK_IDLE) {
 			callout_init(&dw->dw_callout, CALLOUT_MPSAFE);
-			callout_reset(&dw->dw_callout, ticks,
-			    &linux_worker_intr, dw);
-			dw->work.w_state = WORK_DELAYED;
-			dw->work.w_wq = wq;
-			mutex_enter(&wq->wq_lock);
+			callout_reset(&dw->dw_callout, MIN(INT_MAX, ticks),
+			    &linux_workqueue_timeout, dw);
 			TAILQ_INSERT_HEAD(&wq->wq_delayed, dw, dw_entry);
-			mutex_exit(&wq->wq_lock);
+		} else {
+			KASSERT(dw->dw_state == DELAYED_WORK_SCHEDULED);
 		}
-		newly_queued = true;
-		break;
-
-	case WORK_DELAYED:
-		/*
-		 * Timer is already ticking.  Leave it to time out
-		 * whenever it was going to time out, as Linux does --
-		 * neither speed it up nor postpone it.
-		 */
-		newly_queued = false;
-		break;
+		dw->dw_state = DELAYED_WORK_SCHEDULED;
+	}
+}
 
-	case WORK_PENDING:
-		KASSERT(dw->work.w_wq == wq);
-		newly_queued = false;
-		break;
+bool
+queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dw,
+    unsigned long ticks)
+{
+	struct workqueue_struct *wq0;
+	bool newly_queued;
 
-	case WORK_CANCELLED:
-	case WORK_DELAYED_CANCELLED:
-		/* XXX Wait for cancellation and then queue?  */
+	mutex_enter(&wq->wq_lock);
+	if (__predict_true((wq0 = atomic_cas_ptr(&dw->work.work_queue, NULL,
+			wq)) == NULL)) {
+		KASSERT(dw->dw_state == DELAYED_WORK_IDLE);
+		queue_delayed_work_anew(wq, dw, ticks);
+		newly_queued = true;
+	} else {
+		KASSERT(wq0 == wq);
 		newly_queued = false;
-		break;
-
-	default:
-		panic("delayed work %p in bad state: %d", dw,
-		    (int)dw->work.w_state);
-		break;
 	}
-	linux_work_unlock(&dw->work);
+	mutex_exit(&wq->wq_lock);
 
 	return newly_queued;
 }
@@ -754,58 +471,71 @@ bool
 mod_delayed_work(struct workqueue_struct *wq, struct delayed_work *dw,
     unsigned long ticks)
 {
+	struct workqueue_struct *wq0;
 	bool timer_modified;
 
-	KASSERT(wq != NULL);
-
-	linux_work_lock(&dw->work);
-	switch (dw->work.w_state) {
-	case WORK_IDLE:
-	case WORK_INVOKED:
-		if (ticks == 0) {
-			/* Skip the delay and queue it now.  */
-			dw->work.w_state = WORK_PENDING;
-			dw->work.w_wq = wq;
-			workqueue_enqueue(wq->wq_workqueue, &dw->work.w_wk,
-			    NULL);
-		} else {
-			callout_init(&dw->dw_callout, CALLOUT_MPSAFE);
-			callout_reset(&dw->dw_callout, ticks,
-			    &linux_worker_intr, dw);
-			dw->work.w_state = WORK_DELAYED;
-			dw->work.w_wq = wq;
-			mutex_enter(&wq->wq_lock);
-			TAILQ_INSERT_HEAD(&wq->wq_delayed, dw, dw_entry);
-			mutex_exit(&wq->wq_lock);
-		}
-		timer_modified = false;
-		break;
-
-	case WORK_DELAYED:
-		/*
-		 * Timer is already ticking.  Reschedule it.
-		 */
-		callout_schedule(&dw->dw_callout, ticks);
-		timer_modified = true;
-		break;
-
-	case WORK_PENDING:
-		KASSERT(dw->work.w_wq == wq);
-		timer_modified = false;
-		break;
-
-	case WORK_CANCELLED:
-	case WORK_DELAYED_CANCELLED:
-		/* XXX Wait for cancellation and then queue?  */
+	mutex_enter(&wq->wq_lock);
+	if ((wq0 = atomic_cas_ptr(&dw->work.work_queue, NULL, wq)) == NULL) {
+		KASSERT(dw->dw_state == DELAYED_WORK_IDLE);
+		queue_delayed_work_anew(wq, dw, ticks);
 		timer_modified = false;
-		break;
-
-	default:
-		panic("delayed work %p in bad state: %d", dw,
-		    (int)dw->work.w_state);
-		break;
+	} else {
+		KASSERT(wq0 == wq);
+		switch (dw->dw_state) {
+		case DELAYED_WORK_IDLE:
+			if (wq->wq_current_work != &dw->work) {
+				/* Work is queued, but hasn't started yet.  */
+				TAILQ_REMOVE(&wq->wq_queue, &dw->work,
+				    work_entry);
+				queue_delayed_work_anew(wq, dw, ticks);
+				timer_modified = true;
+			} else {
+				/*
+				 * Too late.  Queue it anew.  If that
+				 * would skip the callout because it's
+				 * immediate, notify the workqueue.
+				 */
+				wq->wq_requeued = ticks == 0;
+				queue_delayed_work_anew(wq, dw, ticks);
+				timer_modified = false;
+			}
+			break;
+		case DELAYED_WORK_SCHEDULED:
+			if (callout_stop(&dw->dw_callout)) {
+				/*
+				 * Too late to stop, but we got in
+				 * before the callout acquired the
+				 * lock.  Reschedule it and tell it
+				 * we've done so.
+				 */
+				dw->dw_state = DELAYED_WORK_RESCHEDULED;
+				callout_schedule(&dw->dw_callout,
+				    MIN(INT_MAX, ticks));
+			} else {
+				/* Stopped it.  Queue it anew.  */
+				queue_delayed_work_anew(wq, dw, ticks);
+			}
+			timer_modified = true;
+			break;
+		case DELAYED_WORK_RESCHEDULED:
+		case DELAYED_WORK_CANCELLED:
+			/*
+			 * Someone modified the timer _again_, or
+			 * cancelled it, after the callout started but
+			 * before the poor thing even had a chance to
+			 * acquire the lock.  Just reschedule it once
+			 * more.
+			 */
+			callout_schedule(&dw->dw_callout, MIN(INT_MAX, ticks));
+			dw->dw_state = DELAYED_WORK_RESCHEDULED;
+			timer_modified = true;
+			break;
+		default:
+			panic("invalid delayed work state: %d",
+			    dw->dw_state);
+		}
 	}
-	linux_work_unlock(&dw->work);
+	mutex_exit(&wq->wq_lock);
 
 	return timer_modified;
 }
@@ -813,37 +543,49 @@ mod_delayed_work(struct workqueue_struct
 bool
 cancel_delayed_work(struct delayed_work *dw)
 {
-	bool cancelled_p = false;
-
-	linux_work_lock(&dw->work);
-	switch (dw->work.w_state) {
-	case WORK_IDLE:		/* Nothing to do.  */
-		break;
-
-	case WORK_DELAYED:
-		dw->work.w_state = WORK_DELAYED_CANCELLED;
-		linux_cancel_delayed_work_callout(dw, false);
-		cancelled_p = true;
-		break;
-
-	case WORK_PENDING:
-		dw->work.w_state = WORK_CANCELLED;
-		cancelled_p = true;
-		break;
-
-	case WORK_INVOKED:	/* Don't wait!  */
-		break;
-
-	case WORK_CANCELLED:	/* Already done.  */
-	case WORK_DELAYED_CANCELLED:
-		break;
+	struct workqueue_struct *wq;
+	bool cancelled_p;
 
-	default:
-		panic("delayed work %p in bad state: %d", dw,
-		    (int)dw->work.w_state);
-		break;
+	wq = dw->work.work_queue;
+	mutex_enter(&wq->wq_lock);
+	if (__predict_false(dw->work.work_queue != wq)) {
+		cancelled_p = false;
+	} else {
+		switch (dw->dw_state) {
+		case DELAYED_WORK_IDLE:
+			if (wq->wq_current_work == &dw->work) {
+				/* Too late, it's already running.  */
+				cancelled_p = false;
+			} else {
+				/* Got in before it started.  Remove it.  */
+				TAILQ_REMOVE(&wq->wq_queue, &dw->work,
+				    work_entry);
+				cancelled_p = true;
+			}
+			break;
+		case DELAYED_WORK_SCHEDULED:
+		case DELAYED_WORK_RESCHEDULED:
+		case DELAYED_WORK_CANCELLED:
+			if (callout_stop(&dw->dw_callout)) {
+				/*
+				 * Too late to stop, but we got in
+				 * before the callout acquired the
+				 * lock.  Tell it to give up.
+				 */
+				dw->dw_state = DELAYED_WORK_CANCELLED;
+			} else {
+				/* Stopped it.  Kill it.  */
+				callout_destroy(&dw->dw_callout);
+				TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
+				dw->dw_state = DELAYED_WORK_IDLE;
+			}
+			cancelled_p = true;
+		default:
+			panic("invalid delayed work state: %d",
+			    dw->dw_state);
+		}
 	}
-	linux_work_unlock(&dw->work);
+	mutex_exit(&wq->wq_lock);
 
 	return cancelled_p;
 }
@@ -851,161 +593,124 @@ cancel_delayed_work(struct delayed_work 
 bool
 cancel_delayed_work_sync(struct delayed_work *dw)
 {
-	bool cancelled_p = false;
-
-	linux_work_lock(&dw->work);
-	switch (dw->work.w_state) {
-	case WORK_IDLE:		/* Nothing to do.  */
-		break;
-
-	case WORK_DELAYED:
-		dw->work.w_state = WORK_DELAYED_CANCELLED;
-		linux_cancel_delayed_work_callout(dw, true);
-		cancelled_p = true;
-		break;
-
-	case WORK_PENDING:
-		dw->work.w_state = WORK_CANCELLED;
-		linux_wait_for_cancelled_work(&dw->work);
-		cancelled_p = true;
-		break;
-
-	case WORK_INVOKED:
-		linux_wait_for_invoked_work(&dw->work);
-		break;
-
-	case WORK_CANCELLED:	/* Already done.  */
-		break;
-
-	case WORK_DELAYED_CANCELLED:
-		linux_wait_for_delayed_cancelled_work(dw);
-		break;
+	struct workqueue_struct *wq;
+	bool cancelled_p;
 
-	default:
-		panic("delayed work %p in bad state: %d", dw,
-		    (int)dw->work.w_state);
-		break;
+	wq = dw->work.work_queue;
+	mutex_enter(&wq->wq_lock);
+	if (__predict_false(dw->work.work_queue != wq)) {
+		cancelled_p = false;
+	} else {
+retry:		switch (dw->dw_state) {
+		case DELAYED_WORK_IDLE:
+			if (wq->wq_current_work == &dw->work) {
+				/* Too late, it's already running.  Wait.  */
+				do {
+					cv_wait(&wq->wq_cv, &wq->wq_lock);
+				} while (wq->wq_current_work == &dw->work);
+				cancelled_p = false;
+			} else {
+				/* Got in before it started.  Remove it.  */
+				TAILQ_REMOVE(&wq->wq_queue, &dw->work,
+				    work_entry);
+				cancelled_p = true;
+			}
+			break;
+		case DELAYED_WORK_SCHEDULED:
+		case DELAYED_WORK_RESCHEDULED:
+		case DELAYED_WORK_CANCELLED:
+			/*
+			 * If it has started, tell it to stop, and wait
+			 * for it to complete.  We drop the lock, so by
+			 * the time the callout has completed, we must
+			 * review the state again.
+			 */
+			dw->dw_state = DELAYED_WORK_CANCELLED;
+			callout_halt(&dw->dw_callout, &wq->wq_lock);
+			goto retry;
+		default:
+			panic("invalid delayed work state: %d",
+			    dw->dw_state);
+		}
 	}
-	linux_work_unlock(&dw->work);
+	mutex_exit(&wq->wq_lock);
 
 	return cancelled_p;
 }
+
+/*
+ * Flush
+ */
 
 void
-flush_delayed_work(struct delayed_work *dw)
+flush_scheduled_work(void)
 {
 
-	if (cancel_delayed_work_sync(dw)) {
-		/*
-		 * Cancelled it.  Run it now.
-		 *
-		 * XXX What if it's supposed to run on a different
-		 * workqueue?  Let's just hope it's not...
-		 */
-		mod_delayed_work(system_wq, dw, 0);
-		flush_workqueue(system_wq);
-	} else {
-		/* Work ran to completion already.  We're done.  */
-	}
+	flush_workqueue(system_wq);
 }
 
-static void
-linux_cancel_delayed_work_callout(struct delayed_work *dw, bool wait)
+void
+flush_workqueue(struct workqueue_struct *wq)
 {
-	bool fired_p;
-
-	KASSERT(linux_work_locked(&dw->work));
-	KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
+	uint64_t gen;
 
-	if (wait) {
-		/*
-		 * We unlock, halt, and then relock, rather than
-		 * passing an interlock to callout_halt, for two
-		 * reasons:
-		 *
-		 * (1) The work lock is not a mutex(9), so we can't use it.
-		 * (2) The WORK_DELAYED_CANCELLED state serves as an interlock.
-		 */
-		linux_work_unlock(&dw->work);
-		fired_p = callout_halt(&dw->dw_callout, NULL);
-		linux_work_lock(&dw->work);
-	} else {
-		fired_p = callout_stop(&dw->dw_callout);
-	}
-
-	/*
-	 * fired_p means we didn't cancel the callout, so it must have
-	 * already begun and will clean up after itself.
-	 *
-	 * !fired_p means we cancelled it so we have to clean up after
-	 * it.  Nobody else should have changed the state in that case.
-	 */
-	if (!fired_p) {
-		struct workqueue_struct *wq;
-
-		KASSERT(linux_work_locked(&dw->work));
-		KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
-
-		wq = dw->work.w_wq;
-		mutex_enter(&wq->wq_lock);
-		TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
-		callout_destroy(&dw->dw_callout);
-		dw->work.w_state = WORK_IDLE;
-		dw->work.w_wq = NULL;
-		cv_broadcast(&wq->wq_cv);
-		mutex_exit(&wq->wq_lock);
-	}
+	mutex_enter(&wq->wq_lock);
+	gen = wq->wq_gen;
+	do {
+		cv_wait(&wq->wq_cv, &wq->wq_lock);
+	} while (gen == wq->wq_gen);
+	mutex_exit(&wq->wq_lock);
 }
 
-static void
-linux_wait_for_delayed_cancelled_work(struct delayed_work *dw)
+bool
+flush_work(struct work_struct *work)
 {
-	struct workqueue_struct *wq;
+	struct workqueue_struct *const wq = work->work_queue;
 
-	KASSERT(linux_work_locked(&dw->work));
-	KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
+	if (wq == NULL)
+		return false;
 
-	wq = dw->work.w_wq;
-	do {
-		mutex_enter(&wq->wq_lock);
-		linux_work_unlock(&dw->work);
-		cv_wait(&wq->wq_cv, &wq->wq_lock);
-		mutex_exit(&wq->wq_lock);
-		linux_work_lock(&dw->work);
-	} while ((dw->work.w_state == WORK_DELAYED_CANCELLED) &&
-	    (dw->work.w_wq == wq));
+	flush_workqueue(wq);
+	return true;
 }
 
-static void
-linux_worker_intr(void *arg)
+bool
+flush_delayed_work(struct delayed_work *dw)
 {
-	struct delayed_work *dw = arg;
-	struct workqueue_struct *wq;
-
-	linux_work_lock(&dw->work);
+	struct workqueue_struct *const wq = dw->work.work_queue;
+	bool do_flush = false;
 
-	KASSERT((dw->work.w_state == WORK_DELAYED) ||
-	    (dw->work.w_state == WORK_DELAYED_CANCELLED));
+	if (wq == NULL)
+		return false;
 
-	wq = dw->work.w_wq;
 	mutex_enter(&wq->wq_lock);
-
-	/* Queue the work, or return it to idle and alert any cancellers.  */
-	if (__predict_true(dw->work.w_state == WORK_DELAYED)) {
-		dw->work.w_state = WORK_PENDING;
-		workqueue_enqueue(dw->work.w_wq->wq_workqueue, &dw->work.w_wk,
-		    NULL);
+	if (__predict_false(dw->work.work_queue != wq)) {
+		do_flush = true;
 	} else {
-		KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
-		dw->work.w_state = WORK_IDLE;
-		dw->work.w_wq = NULL;
-		cv_broadcast(&wq->wq_cv);
+retry:		switch (dw->dw_state) {
+		case DELAYED_WORK_IDLE:
+			if (wq->wq_current_work != &dw->work) {
+				TAILQ_REMOVE(&wq->wq_queue, &dw->work,
+				    work_entry);
+			} else {
+				do_flush = true;
+			}
+			break;
+		case DELAYED_WORK_SCHEDULED:
+		case DELAYED_WORK_RESCHEDULED:
+		case DELAYED_WORK_CANCELLED:
+			dw->dw_state = DELAYED_WORK_CANCELLED;
+			callout_halt(&dw->dw_callout, &wq->wq_lock);
+			goto retry;
+		default:
+			panic("invalid delayed work state: %d",
+			    dw->dw_state);
+		}
 	}
+	mutex_exit(&wq->wq_lock);
 
-	/* Either way, the callout is done.  */
-	TAILQ_REMOVE(&wq->wq_delayed, dw, dw_entry);
-	callout_destroy(&dw->dw_callout);
+	if (do_flush)
+		flush_workqueue(wq);
 
-	mutex_exit(&wq->wq_lock);
-	linux_work_unlock(&dw->work);
+	return true;
 }

Reply via email to