Module Name:    src
Committed By:   riastradh
Date:           Sun Sep  8 16:40:36 UTC 2013

Modified Files:
        src/sys/external/bsd/drm2/include/linux [riastradh-drm2]: workqueue.h

Log Message:
Rework Linux `work' to use NetBSD workqueues, not callouts.

Callers expect to be able to allocate in the workers, which callouts
don't allow.

Delayed work uses callouts only to delay enqueueing work.

Linux `workqueues' are still stubs.


To generate a diff of this commit:
cvs rdiff -u -r1.1.2.9 -r1.1.2.10 \
    src/sys/external/bsd/drm2/include/linux/workqueue.h

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/include/linux/workqueue.h
diff -u src/sys/external/bsd/drm2/include/linux/workqueue.h:1.1.2.9 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.9	Sun Sep  8 15:58:24 2013
+++ src/sys/external/bsd/drm2/include/linux/workqueue.h	Sun Sep  8 16:40:36 2013
@@ -1,4 +1,4 @@
-/*	$NetBSD: workqueue.h,v 1.1.2.9 2013/09/08 15:58:24 riastradh Exp $	*/
+/*	$NetBSD: workqueue.h,v 1.1.2.10 2013/09/08 16:40:36 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2013 The NetBSD Foundation, Inc.
@@ -33,81 +33,220 @@
 #define _LINUX_WORKQUEUE_H_
 
 #include <sys/callout.h>
+#include <sys/condvar.h>
+#include <sys/mutex.h>
+#include <sys/workqueue.h>
 
 #include <asm/bug.h>
 #include <linux/kernel.h>
 
 /*
- * XXX This implementation is a load of bollocks -- callouts are
- * expedient, but wrong, if for no reason other than that we never call
- * callout_destroy.
+ * 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.
  */
 
 struct work_struct {
-	struct callout ws_callout;
+	void 			(*w_fn)(struct work_struct *);
+	struct workqueue	*w_wq;
+	struct work		w_wk;
+	kmutex_t		w_lock;
+	kcondvar_t		w_cv;
+	enum {
+		WORK_IDLE,
+		WORK_QUEUED,
+		WORK_CANCELLED,
+		WORK_INFLIGHT,
+		WORK_REQUEUED,
+	}			w_state;
 };
 
-struct delayed_work {
-	struct work_struct work;
-};
+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;
 
-	callout_init(&work->ws_callout, 0);
-
-	/* XXX This cast business is sketchy.  */
-	callout_setfunc(&work->ws_callout, (void (*)(void *))fn, work);
+	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
-INIT_DELAYED_WORK(struct delayed_work *dw, void (*fn)(struct work_struct *))
+schedule_work(struct work_struct *work)
 {
-	INIT_WORK(&dw->work, fn);
+
+	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);
 }
 
-static inline struct delayed_work *
-to_delayed_work(struct work_struct *work)
+/*
+ * XXX This API can't possibly be right because there is no interlock.
+ */
+static inline bool
+cancel_work_sync(struct work_struct *work)
 {
-	return container_of(work, struct delayed_work, 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;
 }
 
-static inline void
-schedule_work(struct work_struct *work)
+struct delayed_work {
+	struct callout		dw_callout;
+	struct work_struct	work; /* not dw_work; name must match Linux */
+};
+
+static void __unused
+linux_delayed_work_fn(void *arg)
 {
-	callout_schedule(&work->ws_callout, 0);
+	struct delayed_work *const dw = arg;
+
+	schedule_work(&dw->work);
 }
 
 static inline void
-schedule_delayed_work(struct delayed_work *dw, unsigned long ticks)
+INIT_DELAYED_WORK(struct delayed_work *dw, void (*fn)(struct work_struct *))
 {
-	KASSERT(ticks < INT_MAX);
-	callout_schedule(&dw->work.ws_callout, (int)ticks);
+	callout_init(&dw->dw_callout, CALLOUT_MPSAFE);
+	callout_setfunc(&dw->dw_callout, linux_delayed_work_fn, dw);
+	INIT_WORK(&dw->work, fn);
 }
 
-static inline bool
-cancel_work(struct work_struct *work)
+static inline struct delayed_work *
+to_delayed_work(struct work_struct *work)
 {
-	return !callout_stop(&work->ws_callout);
+	return container_of(work, struct delayed_work, work);
 }
 
-static inline bool
-cancel_work_sync(struct work_struct *work)
+static inline void
+schedule_delayed_work(struct delayed_work *dw, unsigned long ticks)
 {
-	return !callout_halt(&work->ws_callout, NULL);
+	KASSERT(ticks < INT_MAX);
+	callout_schedule(&dw->dw_callout, (int)ticks);
 }
 
 static inline bool
 cancel_delayed_work(struct delayed_work *dw)
 {
-	return cancel_work(&dw->work);
+	return !callout_stop(&dw->dw_callout);
 }
 
 static inline bool
 cancel_delayed_work_sync(struct delayed_work *dw)
 {
-	return cancel_work_sync(&dw->work);
+	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);
 }
 
 /*

Reply via email to