DragonFlyBSD Kernel Audit
DF-1977 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/dev/drm/include/linux/workqueue.h b/sys/dev/drm/include/linux/workqueue.h
--- a/sys/dev/drm/include/linux/workqueue.h
+++ b/sys/dev/drm/include/linux/workqueue.h
@@ -55,6 +55,8 @@
 	STAILQ_HEAD(ws_list, work_struct) ws_list_head;
 	struct thread *worker_thread;
 	struct lock worker_lock;
+	volatile int		in_flight;	/* # of works currently executing */
+	volatile int		exiting;	/* set by destroy_workqueue */
 };
 
 struct workqueue_struct {
--- a/sys/dev/drm/linux_workqueue.c
+++ b/sys/dev/drm/linux_workqueue.c
@@ -85,10 +85,12 @@
 		}
 
 		work->running = true;
+		worker->in_flight++;
 		lockmgr(&worker->worker_lock, LK_RELEASE);
 		work->func(work);
 		lwkt_yield();
 		lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
+		worker->in_flight--;
 		if (work->on_queue == false)
 			work->worker = NULL;
 		didcan = work->canceled;
@@ -105,11 +107,13 @@
 	struct workqueue_worker *worker = arg;
 
 	lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
-	while (1) {
+	while (!worker->exiting) {
 		process_all_work(worker);
 		lksleep(worker, &worker->worker_lock, 0, "wqidle", 0);
 	}
 	lockmgr(&worker->worker_lock, LK_RELEASE);
+	worker->worker_thread = NULL;
+	lwkt_exit();
 }
 
 /*
@@ -243,13 +247,25 @@
 void
 destroy_workqueue(struct workqueue_struct *wq)
 {
+	int i;
+
 	drain_workqueue(wq);
-//	wq->is_draining = true;
-#if 0	/* XXX TODO */
-	kill_all_threads;
-	kfree(wq->wq_threads);
-	kfree(wq);
-#endif
+	wq->is_draining = true;
+
+	for (i = 0; i < wq->num_workers; i++) {
+		struct workqueue_worker *worker = &(*wq->workers)[i];
+		lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
+		worker->exiting = true;
+		wakeup(worker);
+		lockmgr(&worker->worker_lock, LK_RELEASE);
+	}
+	for (i = 0; i < wq->num_workers; i++) {
+		struct workqueue_worker *worker = &(*wq->workers)[i];
+		while (worker->worker_thread != NULL)
+			tsleep(worker, 0, "wqdie", 1);
+	}
+\tkfree(wq->workers);
+\tkfree(wq);
 }
 
 SYSINIT(linux_workqueue_init, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, init_workqueues, NULL);
@@ -267,22 +283,23 @@
 drain_workqueue(struct workqueue_struct *wq)
 {
 	struct workqueue_worker *worker;
+	int i;
 
 	wq->is_draining = true;
 
-	for (int i=0;i < wq->num_workers; i++) {
+	for (i = 0; i < wq->num_workers; i++) {
 		worker = &(*wq->workers)[i];
-
-		lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
-		while (!STAILQ_EMPTY(&worker->ws_list_head)) {
-		/* XXX: introduces latency */
+		for (;;) {
+			bool done;
+			lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
+			done = STAILQ_EMPTY(&worker->ws_list_head) &&
+			       (worker->in_flight == 0);
+			lockmgr(&worker->worker_lock, LK_RELEASE);
+			if (done)
+				break;
 			tsleep(&drain_workqueue, 0, "wkdrain", 1);
 		}
-		lockmgr(&worker->worker_lock, LK_RELEASE);
 	}
-
-	/* XXX: No more work will be queued. is that right ? */
-//	wq->is_draining = false;
 }
 
 bool