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); } /*