|
@@ -46,6 +46,7 @@
|
|
|
#include <linux/rculist.h>
|
|
|
#include <linux/nodemask.h>
|
|
|
#include <linux/moduleparam.h>
|
|
|
+#include <linux/uaccess.h>
|
|
|
|
|
|
#include "workqueue_internal.h"
|
|
|
|
|
@@ -2197,6 +2198,7 @@ __acquires(&pool->lock)
|
|
|
worker->current_work = NULL;
|
|
|
worker->current_func = NULL;
|
|
|
worker->current_pwq = NULL;
|
|
|
+ worker->desc_valid = false;
|
|
|
pwq_dec_nr_in_flight(pwq, work_color);
|
|
|
}
|
|
|
|
|
@@ -4365,6 +4367,83 @@ unsigned int work_busy(struct work_struct *work)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(work_busy);
|
|
|
|
|
|
+/**
|
|
|
+ * set_worker_desc - set description for the current work item
|
|
|
+ * @fmt: printf-style format string
|
|
|
+ * @...: arguments for the format string
|
|
|
+ *
|
|
|
+ * This function can be called by a running work function to describe what
|
|
|
+ * the work item is about. If the worker task gets dumped, this
|
|
|
+ * information will be printed out together to help debugging. The
|
|
|
+ * description can be at most WORKER_DESC_LEN including the trailing '\0'.
|
|
|
+ */
|
|
|
+void set_worker_desc(const char *fmt, ...)
|
|
|
+{
|
|
|
+ struct worker *worker = current_wq_worker();
|
|
|
+ va_list args;
|
|
|
+
|
|
|
+ if (worker) {
|
|
|
+ va_start(args, fmt);
|
|
|
+ vsnprintf(worker->desc, sizeof(worker->desc), fmt, args);
|
|
|
+ va_end(args);
|
|
|
+ worker->desc_valid = true;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * print_worker_info - print out worker information and description
|
|
|
+ * @log_lvl: the log level to use when printing
|
|
|
+ * @task: target task
|
|
|
+ *
|
|
|
+ * If @task is a worker and currently executing a work item, print out the
|
|
|
+ * name of the workqueue being serviced and worker description set with
|
|
|
+ * set_worker_desc() by the currently executing work item.
|
|
|
+ *
|
|
|
+ * This function can be safely called on any task as long as the
|
|
|
+ * task_struct itself is accessible. While safe, this function isn't
|
|
|
+ * synchronized and may print out mixups or garbages of limited length.
|
|
|
+ */
|
|
|
+void print_worker_info(const char *log_lvl, struct task_struct *task)
|
|
|
+{
|
|
|
+ work_func_t *fn = NULL;
|
|
|
+ char name[WQ_NAME_LEN] = { };
|
|
|
+ char desc[WORKER_DESC_LEN] = { };
|
|
|
+ struct pool_workqueue *pwq = NULL;
|
|
|
+ struct workqueue_struct *wq = NULL;
|
|
|
+ bool desc_valid = false;
|
|
|
+ struct worker *worker;
|
|
|
+
|
|
|
+ if (!(task->flags & PF_WQ_WORKER))
|
|
|
+ return;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This function is called without any synchronization and @task
|
|
|
+ * could be in any state. Be careful with dereferences.
|
|
|
+ */
|
|
|
+ worker = probe_kthread_data(task);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Carefully copy the associated workqueue's workfn and name. Keep
|
|
|
+ * the original last '\0' in case the original contains garbage.
|
|
|
+ */
|
|
|
+ probe_kernel_read(&fn, &worker->current_func, sizeof(fn));
|
|
|
+ probe_kernel_read(&pwq, &worker->current_pwq, sizeof(pwq));
|
|
|
+ probe_kernel_read(&wq, &pwq->wq, sizeof(wq));
|
|
|
+ probe_kernel_read(name, wq->name, sizeof(name) - 1);
|
|
|
+
|
|
|
+ /* copy worker description */
|
|
|
+ probe_kernel_read(&desc_valid, &worker->desc_valid, sizeof(desc_valid));
|
|
|
+ if (desc_valid)
|
|
|
+ probe_kernel_read(desc, worker->desc, sizeof(desc) - 1);
|
|
|
+
|
|
|
+ if (fn || name[0] || desc[0]) {
|
|
|
+ printk("%sWorkqueue: %s %pf", log_lvl, name, fn);
|
|
|
+ if (desc[0])
|
|
|
+ pr_cont(" (%s)", desc);
|
|
|
+ pr_cont("\n");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* CPU hotplug.
|
|
|
*
|