#include <drm/drmP.h>
#include <linux/workqueue.h>
#include <sys/kthread.h>
struct workqueue_struct *system_wq;
struct workqueue_struct *system_highpri_wq;
struct workqueue_struct *system_long_wq;
struct workqueue_struct *system_unbound_wq;
struct workqueue_struct *system_power_efficient_wq;
static inline void
process_all_work(struct workqueue_worker *worker)
{
struct work_struct *work;
bool didcan;
while (STAILQ_FIRST(&worker->ws_list_head)) {
work = STAILQ_FIRST(&worker->ws_list_head);
STAILQ_REMOVE_HEAD(&worker->ws_list_head, ws_entries);
work->on_queue = false;
if (work->running)
continue;
if (work->canceled) {
work->canceled = false;
continue;
}
work->running = true;
lockmgr(&worker->worker_lock, LK_RELEASE);
work->func(work);
lwkt_yield();
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
if (work->on_queue == false)
work->worker = NULL;
didcan = work->canceled;
cpu_sfence();
work->running = false;
if (didcan == true)
wakeup(work);
}
}
static void
wq_worker_thread(void *arg)
{
struct workqueue_worker *worker = arg;
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
while (1) {
process_all_work(worker);
lksleep(worker, &worker->worker_lock, 0, "wqidle", 0);
}
lockmgr(&worker->worker_lock, LK_RELEASE);
}
int
queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
struct workqueue_worker *worker;
int ret = false;
if (wq->is_draining)
return false;
if (wq->num_workers > 1)
worker = &(*wq->workers)[mycpuid];
else
worker = &(*wq->workers)[0];
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
work->canceled = false;
if (work->on_queue == false || work->running == false) {
if (work->on_queue == false) {
STAILQ_INSERT_TAIL(&worker->ws_list_head, work,
ws_entries);
work->on_queue = true;
work->worker = worker;
wakeup_one(worker);
}
ret = true;
}
lockmgr(&worker->worker_lock, LK_RELEASE);
return ret;
}
static inline void
_delayed_work_fn(void *arg)
{
struct delayed_work *dw = arg;
queue_work(system_wq, &dw->work);
}
int
queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work,
unsigned long delay)
{
int pending = work->work.on_queue;
if (delay != 0) {
callout_reset(&work->timer, delay, _delayed_work_fn, work);
} else {
_delayed_work_fn((void *)work);
}
return (!pending);
}
static int
init_workqueues(void *arg)
{
system_wq = alloc_workqueue("system_wq", 0, 1);
system_highpri_wq = alloc_workqueue("system_highpri_wq", WQ_HIGHPRI, 1);
system_long_wq = alloc_workqueue("system_long_wq", 0, 1);
system_unbound_wq = alloc_workqueue("system_unbound_wq", WQ_UNBOUND, 1);
system_power_efficient_wq = alloc_workqueue("system_power_efficient_wq", 0, 1);
return 0;
}
static int destroy_workqueues(void *arg)
{
destroy_workqueue(system_wq);
destroy_workqueue(system_highpri_wq);
destroy_workqueue(system_long_wq);
destroy_workqueue(system_unbound_wq);
destroy_workqueue(system_power_efficient_wq);
return 0;
}
struct workqueue_struct *
_create_workqueue_common(const char *name, int flags)
{
struct workqueue_struct *wq;
int priority, error;
wq = kmalloc(sizeof(*wq), M_DRM, M_WAITOK | M_ZERO);
if (flags & WQ_HIGHPRI)
priority = TDPRI_INT_SUPPORT;
else
priority = TDPRI_KERN_DAEMON;
if (flags & WQ_UNBOUND) {
wq->num_workers = 1;
} else {
wq->num_workers = ncpus;
}
wq->workers = kmalloc(sizeof(struct workqueue_worker) * wq->num_workers,
M_DRM, M_WAITOK | M_ZERO);
for (int i = 0;i < wq->num_workers; i++) {
struct workqueue_worker *worker = &(*wq->workers)[i];
lockinit(&worker->worker_lock, "lwq", 0, 0);
STAILQ_INIT(&worker->ws_list_head);
if (wq->num_workers > 1) {
error = lwkt_create(wq_worker_thread, worker,
&worker->worker_thread, NULL, TDF_NOSTART, i, "%s/%d", name, i);
} else {
error = lwkt_create(wq_worker_thread, worker,
&worker->worker_thread, NULL, TDF_NOSTART, -1, name);
}
if (error) {
kprintf("%s: lwkt_create(%s/%d): error %d",
__func__, name, i, error);
kfree(wq);
return NULL;
}
lwkt_setpri_initial(worker->worker_thread, priority);
lwkt_schedule(worker->worker_thread);
}
return wq;
}
void
destroy_workqueue(struct workqueue_struct *wq)
{
drain_workqueue(wq);
#if 0
kill_all_threads;
kfree(wq->wq_threads);
kfree(wq);
#endif
}
SYSINIT(linux_workqueue_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, init_workqueues, NULL);
SYSUNINIT(linux_workqueue_destroy, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, destroy_workqueues, NULL);
bool
flush_delayed_work(struct delayed_work *dwork)
{
callout_drain(&dwork->timer);
return flush_work(&dwork->work);
}
void
drain_workqueue(struct workqueue_struct *wq)
{
struct workqueue_worker *worker;
wq->is_draining = true;
for (int i=0;i < wq->num_workers; i++) {
worker = &(*wq->workers)[i];
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
while (!STAILQ_EMPTY(&worker->ws_list_head)) {
tsleep(&drain_workqueue, 0, "wkdrain", 1);
}
lockmgr(&worker->worker_lock, LK_RELEASE);
}
}
bool
work_pending(struct work_struct *work)
{
return work->on_queue;
}
unsigned int
work_busy(struct work_struct *work)
{
return (work->on_queue || work->running);
}
static inline void
__flush_work_func(struct work_struct *work)
{
wakeup_one(work);
}
void
flush_workqueue(struct workqueue_struct *wq)
{
struct work_struct __flush_work;
INIT_WORK(&__flush_work, __flush_work_func);
queue_work(wq, &__flush_work);
while (__flush_work.on_queue || __flush_work.running) {
tsleep(&__flush_work, 0, "flshwq", 0);
}
}
bool
flush_work(struct work_struct *work)
{
int ret = false;
while (work->on_queue || work->running) {
ret = true;
tsleep(&flush_work, 0, "flshwrk", 1);
}
return ret;
}
static inline bool
_cancel_work(struct work_struct *work, bool sync_wait)
{
struct workqueue_worker *worker;
bool ret;
ret = false;
for (;;) {
if (work->on_queue) {
worker = work->worker;
if (worker == NULL)
continue;
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
if (worker != work->worker || work->on_queue == false) {
lockmgr(&worker->worker_lock, LK_RELEASE);
continue;
}
STAILQ_REMOVE(&worker->ws_list_head, work,
work_struct, ws_entries);
work->on_queue = false;
ret = true;
lockmgr(&worker->worker_lock, LK_RELEASE);
}
if (work->running == false)
break;
worker = work->worker;
if (worker == NULL)
continue;
lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
if (worker != work->worker || work->running == false) {
lockmgr(&worker->worker_lock, LK_RELEASE);
continue;
}
work->canceled = true;
ret = true;
if (sync_wait == false) {
lockmgr(&worker->worker_lock, LK_RELEASE);
break;
}
lksleep(work, &worker->worker_lock, 0, "wqcan", 1);
lockmgr(&worker->worker_lock, LK_RELEASE);
}
return ret;
}
bool
cancel_work_sync(struct work_struct *work)
{
return _cancel_work(work, true);
}
bool
cancel_delayed_work(struct delayed_work *dwork)
{
struct work_struct *work = &dwork->work;
work->canceled = true;
callout_cancel(&dwork->timer);
return _cancel_work(work, false);
}
bool
cancel_delayed_work_sync(struct delayed_work *dwork)
{
struct work_struct *work = &dwork->work;
work->canceled = true;
callout_cancel(&dwork->timer);
return _cancel_work(work, true);
}
bool
delayed_work_pending(struct delayed_work *dw)
{
return work_pending(&dw->work);
}
void
destroy_work_on_stack(struct work_struct *work)
{
}
void
destroy_delayed_work_on_stack(struct delayed_work *work)
{
}