Module Name:    src
Committed By:   riastradh
Date:           Mon Dec 30 04:50:12 UTC 2013

Modified Files:
        src/sys/external/bsd/drm2/drm [riastradh-drm2]: drm_module.c
        src/sys/external/bsd/drm2/include/linux [riastradh-drm2]: workqueue.h
        src/sys/modules/drm2 [riastradh-drm2]: Makefile
Added Files:
        src/sys/external/bsd/drm2/linux [riastradh-drm2]: linux_work.c

Log Message:
First (plausible) draft of Linux workqueue implementation rework.

Untested, but this looks better than what was there before, or any of
the drafts leading up to this which got torn out of the typewriter,
crumpled up, and crudely tossed in frustration toward the wastepaper
basket by my desk alongside the empty bottles of Jack Daniels that
fueled them, or something like that.

Can't use multiple CPUs per workqueue.  That requires some explicit
management of per-CPU workqueue state, since NetBSD's workqueue(9)
doesn't provide that or cancellation or flushing.  Oops.


To generate a diff of this commit:
cvs rdiff -u -r1.1.2.6 -r1.1.2.7 src/sys/external/bsd/drm2/drm/drm_module.c
cvs rdiff -u -r1.1.2.10 -r1.1.2.11 \
    src/sys/external/bsd/drm2/include/linux/workqueue.h
cvs rdiff -u -r0 -r1.1.2.1 src/sys/external/bsd/drm2/linux/linux_work.c
cvs rdiff -u -r1.1.2.42 -r1.1.2.43 src/sys/modules/drm2/Makefile

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/drm2/drm/drm_module.c
diff -u src/sys/external/bsd/drm2/drm/drm_module.c:1.1.2.6 src/sys/external/bsd/drm2/drm/drm_module.c:1.1.2.7
--- src/sys/external/bsd/drm2/drm/drm_module.c:1.1.2.6	Sun Sep  8 15:26:24 2013
+++ src/sys/external/bsd/drm2/drm/drm_module.c	Mon Dec 30 04:50:12 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: drm_module.c,v 1.1.2.6 2013/09/08 15:26:24 riastradh Exp $	*/
+/*	$NetBSD: drm_module.c,v 1.1.2.7 2013/12/30 04:50:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: drm_module.c,v 1.1.2.6 2013/09/08 15:26:24 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: drm_module.c,v 1.1.2.7 2013/12/30 04:50:12 riastradh Exp $");
 
 #include <sys/types.h>
 #include <sys/device.h>
@@ -39,6 +39,7 @@ __KERNEL_RCSID(0, "$NetBSD: drm_module.c
 
 #include <linux/highmem.h>
 #include <linux/mutex.h>
+#include <linux/workqueue.h>
 
 #include <drm/drmP.h>
 
@@ -75,12 +76,20 @@ drm2_modcmd(modcmd_t cmd, void *arg __un
 			    " %d", error);
 			return error;
 		}
+		error = linux_workqueue_init();
+		if (error) {
+			aprint_error("drm: unable to initialize workqueues:"
+			    " %d", error);
+			linux_kmap_fini();
+			return error;
+		}
 #ifdef _MODULE
 		error = config_init_component(cfdriver_ioconf_drm,
 		    cfattach_ioconf_drm, cfdata_ioconf_drm);
 		if (error) {
 			aprint_error("drm: unable to init component: %d\n",
 			    error);
+			linux_workqueue_fini();
 			linux_kmap_fini();
 			return error;
 		}
@@ -91,6 +100,7 @@ drm2_modcmd(modcmd_t cmd, void *arg __un
 			    error);
 			(void)config_fini_component(cfdriver_ioconf_drm,
 			    cfattach_ioconf_drm, cfdata_ioconf_drm);
+			linux_workqueue_fini();
 			linux_kmap_fini();
 			return error;
 		}
@@ -108,6 +118,7 @@ drm2_modcmd(modcmd_t cmd, void *arg __un
 			/* XXX Now what?  Reattach the devsw?  */
 			return error;
 #endif
+		linux_workqueue_fini();
 		linux_kmap_fini();
 		linux_mutex_destroy(&drm_global_mutex);
 		return 0;

Index: src/sys/external/bsd/drm2/include/linux/workqueue.h
diff -u src/sys/external/bsd/drm2/include/linux/workqueue.h:1.1.2.10 src/sys/external/bsd/drm2/include/linux/workqueue.h:1.1.2.11
--- src/sys/external/bsd/drm2/include/linux/workqueue.h:1.1.2.10	Sun Sep  8 16:40:36 2013
+++ src/sys/external/bsd/drm2/include/linux/workqueue.h	Mon Dec 30 04:50:12 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: workqueue.h,v 1.1.2.10 2013/09/08 16:40:36 riastradh Exp $	*/
+/*	$NetBSD: workqueue.h,v 1.1.2.11 2013/12/30 04:50:12 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -32,258 +32,80 @@
 #ifndef _LINUX_WORKQUEUE_H_
 #define _LINUX_WORKQUEUE_H_
 
+#include <sys/types.h>
 #include <sys/callout.h>
-#include <sys/condvar.h>
-#include <sys/mutex.h>
+#include <sys/queue.h>
 #include <sys/workqueue.h>
 
-#include <asm/bug.h>
 #include <linux/kernel.h>
 
-/*
- * XXX This implementation is a load of bollocks -- callouts and
- * workqueues are expedient, but wrong, if for no reason other than
- * that there is no destroy operation.
- *
- * XXX The amount of code in here is absurd; it should be given a
- * proper source file.
- */
+#define	INIT_DELAYED_WORK		linux_INIT_DELAYED_WORK
+#define	INIT_WORK			linux_INIT_WORK
+#define	alloc_ordered_workqueue		linux_alloc_ordered_workqueue
+#define	cancel_delayed_work		linux_cancel_delayed_work
+#define	cancel_delayed_work_sync	linux_cancel_delayed_work_sync
+#define	cancel_work			linux_cancel_work
+#define	cancel_work_sync		linux_cancel_work_sync
+#define	destroy_workqueue		linux_destroy_workqueue
+#define	flush_workqueue			linux_flush_workqueue
+#define	queue_delayed_work		linux_queue_delayed_work
+#define	queue_work			linux_queue_work
+#define	schedule_delayed_work		linux_schedule_delayed_work
+#define	schedule_work			linux_schedule_work
+#define	system_wq			linux_system_wq
+#define	to_delayed_work			linux_to_delayed_work
+
+struct workqueue_struct;
 
 struct work_struct {
-	void 			(*w_fn)(struct work_struct *);
-	struct workqueue	*w_wq;
 	struct work		w_wk;
-	kmutex_t		w_lock;
-	kcondvar_t		w_cv;
+	__cpu_simple_lock_t	w_lock; /* XXX */
 	enum {
 		WORK_IDLE,
-		WORK_QUEUED,
+		WORK_DELAYED,
+		WORK_PENDING,
+		WORK_INVOKED,
 		WORK_CANCELLED,
-		WORK_INFLIGHT,
-		WORK_REQUEUED,
+		WORK_DELAYED_CANCELLED,
 	}			w_state;
+	struct workqueue_struct	*w_wq;
+	void			(*w_fn)(struct work_struct *);
 };
 
-static void __unused
-linux_work_fn(struct work *wk __unused, void *arg)
-{
-	struct work_struct *const work = arg;
-
-	mutex_spin_enter(&work->w_lock);
-	switch (work->w_state) {
-	case WORK_IDLE:
-		panic("work ran while idle: %p", work);
-		break;
-
-	case WORK_QUEUED:
-		work->w_state = WORK_INFLIGHT;
-		mutex_spin_exit(&work->w_lock);
-		(*work->w_fn)(work);
-		mutex_spin_enter(&work->w_lock);
-		switch (work->w_state) {
-		case WORK_IDLE:
-		case WORK_QUEUED:
-			panic("work hosed while in flight: %p", work);
-			break;
-
-		case WORK_INFLIGHT:
-		case WORK_CANCELLED:
-			work->w_state = WORK_IDLE;
-			cv_broadcast(&work->w_cv);
-			break;
-
-		case WORK_REQUEUED:
-			workqueue_enqueue(work->w_wq, &work->w_wk, NULL);
-			work->w_state = WORK_QUEUED;
-			break;
-
-		default:
-			panic("work %p in bad state: %d", work,
-			    (int)work->w_state);
-			break;
-		}
-		break;
-
-	case WORK_CANCELLED:
-		work->w_state = WORK_IDLE;
-		cv_broadcast(&work->w_cv);
-		break;
-
-	case WORK_INFLIGHT:
-		panic("work already in flight: %p", work);
-		break;
-
-	case WORK_REQUEUED:
-		panic("work requeued while not in flight: %p", work);
-		break;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-		break;
-	}
-	mutex_spin_exit(&work->w_lock);
-}
-
-static inline void
-INIT_WORK(struct work_struct *work, void (*fn)(struct work_struct *))
-{
-	int error;
-
-	work->w_fn = fn;
-	error = workqueue_create(&work->w_wq, "lnxworkq", &linux_work_fn,
-	    work, PRI_NONE, IPL_VM, WQ_MPSAFE);
-	if (error)
-		panic("workqueue creation failed: %d", error); /* XXX */
-
-	mutex_init(&work->w_lock, MUTEX_DEFAULT, IPL_VM);
-	cv_init(&work->w_cv, "linxwork");
-	work->w_state = WORK_IDLE;
-}
-
-static inline void
-schedule_work(struct work_struct *work)
-{
-
-	mutex_spin_enter(&work->w_lock);
-	switch (work->w_state) {
-	case WORK_IDLE:
-		workqueue_enqueue(work->w_wq, &work->w_wk, NULL);
-		work->w_state = WORK_QUEUED;
-		break;
-
-	case WORK_CANCELLED:
-		break;
-
-	case WORK_INFLIGHT:
-		work->w_state = WORK_REQUEUED;
-		break;
-
-	case WORK_QUEUED:
-	case WORK_REQUEUED:
-		break;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-		break;
-	}
-	mutex_spin_exit(&work->w_lock);
-}
-
-/*
- * XXX This API can't possibly be right because there is no interlock.
- */
-static inline bool
-cancel_work_sync(struct work_struct *work)
-{
-	bool was_pending = false;
-
-	mutex_spin_enter(&work->w_lock);
-retry:	switch (work->w_state) {
-	case WORK_IDLE:
-		break;
-
-	case WORK_QUEUED:
-	case WORK_INFLIGHT:
-	case WORK_REQUEUED:
-		work->w_state = WORK_CANCELLED;
-		/* FALLTHROUGH */
-	case WORK_CANCELLED:
-		cv_wait(&work->w_cv, &work->w_lock);
-		was_pending = true;
-		goto retry;
-
-	default:
-		panic("work %p in bad state: %d", work, (int)work->w_state);
-	}
-	mutex_spin_exit(&work->w_lock);
-
-	return was_pending;
-}
-
 struct delayed_work {
-	struct callout		dw_callout;
-	struct work_struct	work; /* not dw_work; name must match Linux */
+	/* Not dw_work; name must match Linux.  */
+	struct work_struct		work;
+	struct callout			dw_callout;
+	TAILQ_ENTRY(delayed_work)	dw_entry;
 };
 
-static void __unused
-linux_delayed_work_fn(void *arg)
-{
-	struct delayed_work *const dw = arg;
-
-	schedule_work(&dw->work);
-}
-
-static inline void
-INIT_DELAYED_WORK(struct delayed_work *dw, void (*fn)(struct work_struct *))
-{
-	callout_init(&dw->dw_callout, CALLOUT_MPSAFE);
-	callout_setfunc(&dw->dw_callout, linux_delayed_work_fn, dw);
-	INIT_WORK(&dw->work, fn);
-}
-
 static inline struct delayed_work *
 to_delayed_work(struct work_struct *work)
 {
 	return container_of(work, struct delayed_work, work);
 }
 
-static inline void
-schedule_delayed_work(struct delayed_work *dw, unsigned long ticks)
-{
-	KASSERT(ticks < INT_MAX);
-	callout_schedule(&dw->dw_callout, (int)ticks);
-}
-
-static inline bool
-cancel_delayed_work(struct delayed_work *dw)
-{
-	return !callout_stop(&dw->dw_callout);
-}
-
-static inline bool
-cancel_delayed_work_sync(struct delayed_work *dw)
-{
-	const bool callout_was_pending = !callout_stop(&dw->dw_callout);
-	const bool work_was_pending = cancel_work_sync(&dw->work);
-
-	return (callout_was_pending || work_was_pending);
-}
-
-/*
- * XXX Bogus stubs for Linux work queues.
- */
-
-struct workqueue_struct;
-
-static inline struct workqueue_struct *
-alloc_ordered_workqueue(const char *name __unused, int flags __unused)
-{
-	return (void *)(uintptr_t)0xdeadbeef;
-}
-
-static inline void
-destroy_workqueue(struct workqueue_struct *wq __unused)
-{
-	KASSERT(wq == (void *)(uintptr_t)0xdeadbeef);
-}
-
-#define	flush_workqueue(wq)		WARN(true, "Can't flush workqueues!");
-#define	flush_scheduled_work(wq)	WARN(true, "Can't flush workqueues!");
+extern struct workqueue_struct	*system_wq;
 
-static inline void
-queue_work(struct workqueue_struct *wq __unused, struct work_struct *work)
-{
-	KASSERT(wq == (void *)(uintptr_t)0xdeadbeef);
-	schedule_work(work);
-}
+int	linux_workqueue_init(void);
+void	linux_workqueue_fini(void);
 
-static inline void
-queue_delayed_work(struct workqueue_struct *wq __unused,
-    struct delayed_work *dw,
-    unsigned int ticks)
-{
-	KASSERT(wq == (void *)(uintptr_t)0xdeadbeef);
-	schedule_delayed_work(dw, ticks);
-}
+struct workqueue_struct *
+	alloc_ordered_workqueue(const char *, int);
+void	destroy_workqueue(struct workqueue_struct *);
+void	flush_workqueue(struct workqueue_struct *);
+
+void	INIT_WORK(struct work_struct *, void (*)(struct work_struct *));
+void	schedule_work(struct work_struct *);
+void	queue_work(struct workqueue_struct *, struct work_struct *);
+bool	cancel_work_sync(struct work_struct *);
+
+void	INIT_DELAYED_WORK(struct delayed_work *,
+	    void (*)(struct work_struct *));
+void	schedule_delayed_work(struct delayed_work *, unsigned long);
+void	queue_delayed_work(struct workqueue_struct *, struct delayed_work *,
+	    unsigned long ticks);
+bool	cancel_delayed_work(struct delayed_work *);
+bool	cancel_delayed_work_sync(struct delayed_work *);
 
 #endif  /* _LINUX_WORKQUEUE_H_ */

Index: src/sys/modules/drm2/Makefile
diff -u src/sys/modules/drm2/Makefile:1.1.2.42 src/sys/modules/drm2/Makefile:1.1.2.43
--- src/sys/modules/drm2/Makefile:1.1.2.42	Sun Sep  8 16:38:51 2013
+++ src/sys/modules/drm2/Makefile	Mon Dec 30 04:50:12 2013
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.1.2.42 2013/09/08 16:38:51 riastradh Exp $
+# $NetBSD: Makefile,v 1.1.2.43 2013/12/30 04:50:12 riastradh Exp $
 
 .include "../Makefile.inc"
 .include "Makefile.inc"
@@ -57,6 +57,7 @@ SRCS+=	linux_kmap.c
 SRCS+=	linux_i2c.c
 SRCS+=	linux_idr.c
 SRCS+=	linux_list_sort.c
+SRCS+=	linux_work.c
 
 COPTS.drm_crtc.c+=	-Wno-shadow
 COPTS.drm_edid.c+=	-Wno-shadow

Added files:

Index: src/sys/external/bsd/drm2/linux/linux_work.c
diff -u /dev/null src/sys/external/bsd/drm2/linux/linux_work.c:1.1.2.1
--- /dev/null	Mon Dec 30 04:50:12 2013
+++ src/sys/external/bsd/drm2/linux/linux_work.c	Mon Dec 30 04:50:12 2013
@@ -0,0 +1,714 @@
+/*	$NetBSD: linux_work.c,v 1.1.2.1 2013/12/30 04:50:12 riastradh Exp $	*/
+
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Taylor R. Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: linux_work.c,v 1.1.2.1 2013/12/30 04:50:12 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/mutex.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/workqueue.h>
+
+#include <machine/lock.h>
+
+#include <linux/workqueue.h>
+
+struct workqueue_struct {
+	struct workqueue		*wq_workqueue;
+
+	/* 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;
+	struct work_struct		*wq_current_work;
+};
+
+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 *);
+
+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;
+
+int
+linux_workqueue_init(void)
+{
+
+	system_wq = alloc_ordered_workqueue("lnxsyswq", 0);
+	if (system_wq == NULL)
+		return ENOMEM;
+
+	return 0;
+}
+
+void
+linux_workqueue_fini(void)
+{
+	destroy_workqueue(system_wq);
+	system_wq = NULL;
+}
+
+/*
+ * Workqueues
+ */
+
+struct workqueue_struct *
+alloc_ordered_workqueue(const char *name, int linux_flags)
+{
+	struct workqueue_struct *wq;
+	int flags = WQ_MPSAFE;
+	int error;
+
+	KASSERT(linux_flags == 0);
+
+	error = workqueue_create(&wq->wq_workqueue, name, &linux_worker,
+	    NULL, PRI_NONE, IPL_NONE, flags);
+	if (error)
+		return NULL;
+
+	mutex_init(&wq->wq_lock, MUTEX_DEFAULT, IPL_NONE);
+	cv_init(&wq->wq_cv, name);
+	TAILQ_INIT(&wq->wq_delayed);
+	wq->wq_current_work = NULL;
+
+	return wq;
+}
+
+void
+destroy_workqueue(struct workqueue_struct *wq)
+{
+
+	/*
+	 * Cancel all delayed work.
+	 */
+	for (;;) {
+		struct delayed_work *dw;
+
+		mutex_enter(&wq->wq_lock);
+		if (TAILQ_EMPTY(&wq->wq_delayed)) {
+			dw = NULL;
+		} else {
+			dw = TAILQ_FIRST(&wq->wq_delayed);
+			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;
+
+	cv_destroy(&wq->wq_cv);
+	mutex_destroy(&wq->wq_lock);
+}
+
+/*
+ * Flush
+ *
+ * Note:  This doesn't cancel or wait for delayed work.  This seems to
+ * match what Linux does (or, doesn't do).
+ */
+
+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;
+};
+
+void
+flush_workqueue(struct workqueue_struct *wq)
+{
+	static const struct wq_flush zero_wqf;
+	struct wq_flush wqf = zero_wqf;
+
+	mutex_init(&wqf.wqf_lock, MUTEX_DEFAULT, IPL_NONE);
+	cv_init(&wqf.wqf_cv, "lnxwflsh");
+
+	if (1) {
+		static const struct wq_flush_work zero_wqfw;
+		struct wq_flush_work wqfw = zero_wqfw;
+
+		wqf.wqf_n = 1;
+		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);
+			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);
+		}
+	}
+
+	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.
+ */
+
+static void
+linux_work_lock_init(struct work_struct *work)
+{
+	__cpu_simple_lock_init(&work->w_lock);
+}
+
+static void
+linux_work_lock(struct work_struct *work)
+{
+	__cpu_simple_lock(&work->w_lock);
+}
+
+static void
+linux_work_unlock(struct work_struct *work)
+{
+	__cpu_simple_unlock(&work->w_lock);
+}
+
+static bool
+linux_work_locked(struct work_struct *work)
+{
+	return __SIMPLELOCK_LOCKED_P(&work->w_lock);
+}
+
+/*
+ * Work
+ */
+
+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->w_fn = fn;
+}
+
+void
+schedule_work(struct work_struct *work)
+{
+	queue_work(system_wq, work);
+}
+
+void
+queue_work(struct workqueue_struct *wq, struct work_struct *work)
+{
+
+	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);
+		break;
+
+	case WORK_DELAYED:
+		panic("queue_work(delayed work %p)", work);
+		break;
+
+	case WORK_PENDING:
+		KASSERT(work->w_wq == wq);
+		break;
+
+	case WORK_CANCELLED:
+		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);
+}
+
+bool
+cancel_work_sync(struct work_struct *work)
+{
+	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);
+		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);
+
+	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)
+{
+	struct workqueue_struct *wq;
+
+	KASSERT(linux_work_locked(work));
+	KASSERT(work->w_state == WORK_INVOKED);
+
+	wq = work->w_wq;
+	mutex_enter(&wq->wq_lock);
+	linux_work_unlock(work);
+	while (wq->wq_current_work == work)
+		cv_wait(&wq->wq_cv, &wq->wq_lock);
+	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:
+		/* 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->w_fn)(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:
+		/* 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);
+}
+
+/*
+ * Delayed work
+ */
+
+void
+INIT_DELAYED_WORK(struct delayed_work *dw, void (*fn)(struct work_struct *))
+{
+	INIT_WORK(&dw->work, fn);
+}
+
+void
+schedule_delayed_work(struct delayed_work *dw, unsigned long ticks)
+{
+	queue_delayed_work(system_wq, dw, ticks);
+}
+
+void
+queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *dw,
+    unsigned long ticks)
+{
+
+	KASSERT(wq != NULL);
+
+	/* If we're not actually delaying, queue the work now.  */
+	if (ticks == 0) {
+		queue_work(wq, &dw->work);
+		return;
+	}
+
+	linux_work_lock(&dw->work);
+	switch (dw->work.w_state) {
+	case WORK_IDLE:
+	case WORK_INVOKED:
+		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;
+		break;
+
+	case WORK_DELAYED:
+		callout_schedule(&dw->dw_callout, ticks);
+		break;
+
+	case WORK_PENDING:
+		KASSERT(dw->work.w_wq == wq);
+		break;
+
+	case WORK_CANCELLED:
+	case WORK_DELAYED_CANCELLED:
+		/* XXX Wait for cancellation and then queue?  */
+		break;
+
+	default:
+		panic("delayed work %p in bad state: %d", dw,
+		    (int)dw->work.w_state);
+		break;
+	}
+	linux_work_unlock(&dw->work);
+}
+
+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;
+
+	default:
+		panic("delayed work %p in bad state: %d", dw,
+		    (int)dw->work.w_state);
+		break;
+	}
+	linux_work_unlock(&dw->work);
+
+	return cancelled_p;
+}
+
+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;
+
+	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;
+
+	default:
+		panic("delayed work %p in bad state: %d", dw,
+		    (int)dw->work.w_state);
+		break;
+	}
+	linux_work_unlock(&dw->work);
+
+	return cancelled_p;
+}
+
+static void
+linux_cancel_delayed_work_callout(struct delayed_work *dw, bool wait)
+{
+	bool fired_p;
+
+	KASSERT(linux_work_locked(&dw->work));
+	KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
+
+	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);
+	}
+}
+
+static void
+linux_wait_for_delayed_cancelled_work(struct delayed_work *dw)
+{
+	struct workqueue_struct *wq;
+
+	KASSERT(linux_work_locked(&dw->work));
+	KASSERT(dw->work.w_state == WORK_DELAYED_CANCELLED);
+
+	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));
+}
+
+static void
+linux_worker_intr(void *arg)
+{
+	struct delayed_work *dw = arg;
+	struct workqueue_struct *wq;
+
+	linux_work_lock(&dw->work);
+
+	KASSERT((dw->work.w_state == WORK_DELAYED) ||
+	    (dw->work.w_state == WORK_DELAYED_CANCELLED));
+
+	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);
+	} 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);
+	}
+
+	/* Either way, the callout is done.  */
+	TAILQ_REMOVE(&dw->work.w_wq->wq_delayed, dw, dw_entry);
+	callout_destroy(&dw->dw_callout);
+
+	mutex_exit(&wq->wq_lock);
+	linux_work_unlock(&dw->work);
+}

Reply via email to