From bc7a5b645ca60b4067b51cda1d4a0f0bf5616169 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:40:57 +0200 Subject: [PATCH 01/14] virt: acrn: Add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. In order to keep alloc_workqueue() behavior identical, explicitly request WQ_PERCPU. Cc: Fei Li Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo --- drivers/virt/acrn/irqfd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/virt/acrn/irqfd.c b/drivers/virt/acrn/irqfd.c index acf8cd5f8f8c..aab15f94166a 100644 --- a/drivers/virt/acrn/irqfd.c +++ b/drivers/virt/acrn/irqfd.c @@ -206,7 +206,7 @@ int acrn_irqfd_init(struct acrn_vm *vm) { INIT_LIST_HEAD(&vm->irqfds); mutex_init(&vm->irqfds_lock); - vm->irqfd_wq = alloc_workqueue("acrn_irqfd-%u", 0, 0, vm->vmid); + vm->irqfd_wq = alloc_workqueue("acrn_irqfd-%u", WQ_PERCPU, 0, vm->vmid); if (!vm->irqfd_wq) return -ENOMEM; From 54eff31301a0ef4e9472bbd7f45e279fa55f1440 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:40:58 +0200 Subject: [PATCH 02/14] media: synopsys: hdmirx: replace use of system_unbound_wq with system_dfl_wq This patch continues the effort to refactor workqueue APIs, which has begun with the changes introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The point of the refactoring is to eventually alter the default behavior of workqueues to become unbound by default so that their workload placement is optimized by the scheduler. Before that to happen, workqueue users must be converted to the better named new workqueues with no intended behaviour changes: system_wq -> system_percpu_wq system_unbound_wq -> system_dfl_wq This way the old obsolete workqueues (system_wq, system_unbound_wq) can be removed in the future. Cc: Mauro Carvalho Chehab Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo Acked-by: Dmitry Osipenko --- drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c index 61ad20b18b8d..1061ab50dd64 100644 --- a/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c +++ b/drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c @@ -1776,7 +1776,7 @@ static void process_signal_change(struct snps_hdmirx_dev *hdmirx_dev) FIFO_UNDERFLOW_INT_EN | HDMIRX_AXI_ERROR_INT_EN, 0); hdmirx_reset_dma(hdmirx_dev); - queue_delayed_work(system_unbound_wq, + queue_delayed_work(system_dfl_wq, &hdmirx_dev->delayed_work_res_change, msecs_to_jiffies(50)); } @@ -2238,7 +2238,7 @@ static void hdmirx_delayed_work_res_change(struct work_struct *work) if (hdmirx_wait_signal_lock(hdmirx_dev)) { hdmirx_plugout(hdmirx_dev); - queue_delayed_work(system_unbound_wq, + queue_delayed_work(system_dfl_wq, &hdmirx_dev->delayed_work_hotplug, msecs_to_jiffies(200)); } else { @@ -2253,7 +2253,7 @@ static irqreturn_t hdmirx_5v_det_irq_handler(int irq, void *dev_id) { struct snps_hdmirx_dev *hdmirx_dev = dev_id; - queue_delayed_work(system_unbound_wq, + queue_delayed_work(system_dfl_wq, &hdmirx_dev->delayed_work_hotplug, msecs_to_jiffies(10)); @@ -2518,7 +2518,7 @@ static void hdmirx_enable_irq(struct device *dev) enable_irq(hdmirx_dev->dma_irq); enable_irq(hdmirx_dev->det_irq); - queue_delayed_work(system_unbound_wq, + queue_delayed_work(system_dfl_wq, &hdmirx_dev->delayed_work_hotplug, msecs_to_jiffies(110)); } From f58e25aeb4a530610870d3de5c0f4bd1767c7db4 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:40:59 +0200 Subject: [PATCH 03/14] platform: cznic: turris-omnia-mcu: replace use of system_wq with system_percpu_wq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. This patch continues the effort to refactor worqueue APIs, which has begun with the change introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") Replace system_wq with system_percpu_wq, keeping the same behavior. The old wq (system_wq) will be kept for a few release cycles. Cc: Marek Behun Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo Acked-by: Marek Behún --- drivers/platform/cznic/turris-omnia-mcu-gpio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/cznic/turris-omnia-mcu-gpio.c b/drivers/platform/cznic/turris-omnia-mcu-gpio.c index 7f0ada4fa606..4e430d6c3fc4 100644 --- a/drivers/platform/cznic/turris-omnia-mcu-gpio.c +++ b/drivers/platform/cznic/turris-omnia-mcu-gpio.c @@ -893,7 +893,7 @@ static bool omnia_irq_read_pending_old(struct omnia_mcu *mcu, if (status & OMNIA_STS_BUTTON_PRESSED) { mcu->button_pressed_emul = true; - mod_delayed_work(system_wq, &mcu->button_release_emul_work, + mod_delayed_work(system_percpu_wq, &mcu->button_release_emul_work, msecs_to_jiffies(FRONT_BUTTON_RELEASE_DELAY_MS)); } else if (mcu->button_pressed_emul) { status |= OMNIA_STS_BUTTON_PRESSED; From df7d9add13aa426120e32bd4ed0bfa282cc76917 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:41:00 +0200 Subject: [PATCH 04/14] media: ddbridge: add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. In order to keep alloc_workqueue() behavior identical, explicitly request WQ_PERCPU. Cc: Mauro Carvalho Chehab Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo --- drivers/media/pci/ddbridge/ddbridge-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/ddbridge/ddbridge-core.c b/drivers/media/pci/ddbridge/ddbridge-core.c index 40e6c873c36d..d240e291ba4f 100644 --- a/drivers/media/pci/ddbridge/ddbridge-core.c +++ b/drivers/media/pci/ddbridge/ddbridge-core.c @@ -3430,7 +3430,7 @@ int ddb_init_ddbridge(void) if (ddb_class_create() < 0) return -1; - ddb_wq = alloc_workqueue("ddbridge", 0, 0); + ddb_wq = alloc_workqueue("ddbridge", WQ_PERCPU, 0); if (!ddb_wq) return ddb_exit_ddbridge(1, -1); From bdefddd9d1b371db1fa4de82be290dfb922b825d Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:41:01 +0200 Subject: [PATCH 05/14] rapidio: rio: add WQ_PERCPU to alloc_workqueue users MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. In order to keep alloc_workqueue() behavior identical, explicitly request WQ_PERCPU. Cc: Matt Porter Cc: Alexandre Bounine Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo --- drivers/rapidio/rio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/rapidio/rio.c b/drivers/rapidio/rio.c index 0c175e6d424f..f45e58b0971f 100644 --- a/drivers/rapidio/rio.c +++ b/drivers/rapidio/rio.c @@ -1994,7 +1994,7 @@ int rio_init_mports(void) * TODO: Implement restart of discovery process for all or * individual discovering mports. */ - rio_wq = alloc_workqueue("riodisc", 0, 0); + rio_wq = alloc_workqueue("riodisc", WQ_PERCPU, 0); if (!rio_wq) { pr_err("RIO: unable allocate rio_wq\n"); goto no_disc; From 7fcb7afb9b5eecb85904b16747b6eb04b0880c3b Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Tue, 5 May 2026 14:41:02 +0200 Subject: [PATCH 06/14] umh: replace use of system_unbound_wq with system_dfl_wq This patch continues the effort to refactor workqueue APIs, which has begun with the changes introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The point of the refactoring is to eventually alter the default behavior of workqueues to become unbound by default so that their workload placement is optimized by the scheduler. Before that to happen, workqueue users must be converted to the better named new workqueues with no intended behaviour changes: system_wq -> system_percpu_wq system_unbound_wq -> system_dfl_wq This way the old obsolete workqueues (system_wq, system_unbound_wq) can be removed in the future. Cc: Luis Chamberlain Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo Reviewed-by: Frederic Weisbecker --- kernel/umh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/umh.c b/kernel/umh.c index cffda97d961c..48117c569e1a 100644 --- a/kernel/umh.c +++ b/kernel/umh.c @@ -430,7 +430,7 @@ int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait) sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done; sub_info->wait = wait; - queue_work(system_unbound_wq, &sub_info->work); + queue_work(system_dfl_wq, &sub_info->work); if (wait == UMH_NO_WAIT) /* task has freed sub_info */ goto unlock; From dfca46365afc030fb09bb40226514c500202dcdc Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 11 May 2026 05:14:18 -0700 Subject: [PATCH 07/14] workqueue: drop apply_wqattrs_lock()/unlock() wrappers The apply_wqattrs_lock()/unlock() helpers were introduced by commit a0111cf6710b ("workqueue: separate out and refactor the locking of applying attrs") to encapsulate the get_online_cpus() (later cpus_read_lock()) + mutex_lock(&wq_pool_mutex) acquire pair that was duplicated across the apply-attrs paths. Since commit 19af45757383 ("workqueue: Remove cpus_read_lock() from apply_wqattrs_lock()") removed the cpus_read_lock() (pwq creation and installation now operate on wq_online_cpumask, so CPU hotplug no longer needs to be excluded), the wrappers have been one-line forwarders to mutex_lock(&wq_pool_mutex)/mutex_unlock(&wq_pool_mutex). They no longer encode any non-trivial locking rule and obscure the fact that callers just take the existing wq_pool_mutex. This align with the "unnecessary" helpers that got discussed in [1] Inline the eight call sites and remove the wrappers. No functional change. Link: https://lore.kernel.org/all/afs_44-6ToJJVZTn@gmail.com/ [1] Signed-off-by: Breno Leitao Signed-off-by: Tejun Heo --- kernel/workqueue.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 3d2e3b2ec528..9adee917e2bb 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5300,16 +5300,6 @@ static struct pool_workqueue *alloc_unbound_pwq(struct workqueue_struct *wq, return pwq; } -static void apply_wqattrs_lock(void) -{ - mutex_lock(&wq_pool_mutex); -} - -static void apply_wqattrs_unlock(void) -{ - mutex_unlock(&wq_pool_mutex); -} - /** * wq_calc_pod_cpumask - calculate a wq_attrs' cpumask for a pod * @attrs: the wq_attrs of the default pwq of the target workqueue @@ -5863,7 +5853,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, * wq_pool_mutex protects the workqueues list, allocations of PWQs, * and the global freeze state. */ - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); if (alloc_and_link_pwqs(wq) < 0) goto err_unlock_free_node_nr_active; @@ -5877,7 +5867,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, if (wq_online && init_rescuer(wq) < 0) goto err_unlock_destroy; - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); if ((wq->flags & WQ_SYSFS) && workqueue_sysfs_register(wq)) goto err_destroy; @@ -5885,7 +5875,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, return wq; err_unlock_free_node_nr_active: - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); /* * Failed alloc_and_link_pwqs() may leave pending pwq->release_work, * flushing the pwq_release_worker ensures that the pwq_release_workfn() @@ -5900,7 +5890,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, kfree(wq); return NULL; err_unlock_destroy: - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); err_destroy: destroy_workqueue(wq); return NULL; @@ -7301,7 +7291,7 @@ static ssize_t wq_nice_store(struct device *dev, struct device_attribute *attr, struct workqueue_attrs *attrs; int ret = -ENOMEM; - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); attrs = wq_sysfs_prep_attrs(wq); if (!attrs) @@ -7314,7 +7304,7 @@ static ssize_t wq_nice_store(struct device *dev, struct device_attribute *attr, ret = -EINVAL; out_unlock: - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); free_workqueue_attrs(attrs); return ret ?: count; } @@ -7340,7 +7330,7 @@ static ssize_t wq_cpumask_store(struct device *dev, struct workqueue_attrs *attrs; int ret = -ENOMEM; - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); attrs = wq_sysfs_prep_attrs(wq); if (!attrs) @@ -7351,7 +7341,7 @@ static ssize_t wq_cpumask_store(struct device *dev, ret = apply_workqueue_attrs_locked(wq, attrs); out_unlock: - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); free_workqueue_attrs(attrs); return ret ?: count; } @@ -7387,13 +7377,13 @@ static ssize_t wq_affn_scope_store(struct device *dev, if (affn < 0) return affn; - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); attrs = wq_sysfs_prep_attrs(wq); if (attrs) { attrs->affn_scope = affn; ret = apply_workqueue_attrs_locked(wq, attrs); } - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); free_workqueue_attrs(attrs); return ret ?: count; } @@ -7418,13 +7408,13 @@ static ssize_t wq_affinity_strict_store(struct device *dev, if (sscanf(buf, "%d", &v) != 1) return -EINVAL; - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); attrs = wq_sysfs_prep_attrs(wq); if (attrs) { attrs->affn_strict = (bool)v; ret = apply_workqueue_attrs_locked(wq, attrs); } - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); free_workqueue_attrs(attrs); return ret ?: count; } @@ -7465,12 +7455,12 @@ static int workqueue_set_unbound_cpumask(cpumask_var_t cpumask) cpumask_and(cpumask, cpumask, cpu_possible_mask); if (!cpumask_empty(cpumask)) { ret = 0; - apply_wqattrs_lock(); + mutex_lock(&wq_pool_mutex); if (!cpumask_equal(cpumask, wq_unbound_cpumask)) ret = workqueue_apply_unbound_cpumask(cpumask); if (!ret) cpumask_copy(wq_requested_unbound_cpumask, cpumask); - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); } return ret; From 1503043fd75e29ad49c7d506232e272f6951d07d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Mon, 11 May 2026 05:26:55 -0700 Subject: [PATCH 08/14] workqueue: forbid TEST_WORKQUEUE from being built-in The benchmark drives the workqueue's affinity_scope through sysfs by filp_open()'ing /sys/bus/workqueue/devices/bench_wq/affinity_scope. When CONFIG_TEST_WORKQUEUE=y, the module_init runs during kernel init before userspace has mounted sysfs, so every open returns -ENOENT and the benchmark loop spins emitting: test_workqueue: open /sys/bus/workqueue/devices/bench_wq/affinity_scope failed: -2 Mirror the TEST_BPF pattern and add "depends on m" so Kconfig will not let this be built into the kernel image, and document the reason in the help text. Fixes: 24b2e73f9700 ("workqueue: add test_workqueue benchmark module") Signed-off-by: Breno Leitao Signed-off-by: Tejun Heo --- lib/Kconfig.debug | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 8ff5adcfe1e0..040f4e077435 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2649,12 +2649,17 @@ config TEST_VMALLOC config TEST_WORKQUEUE tristate "Test module for stress/performance analysis of workqueue" + depends on m default n help This builds the "test_workqueue" module for benchmarking workqueue throughput under contention. Useful for evaluating affinity scope changes (e.g., cache_shard vs cache). + The test drives sysfs to switch affinity scopes, so it must be + loaded after userspace has mounted sysfs; building it in (=y) + would run module_init before /sys is available. + If unsure, say N. config TEST_BPF From 611583a76ea97991b0f65ec1ff099eac7fe0bae4 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Sun, 24 May 2026 08:19:56 -0700 Subject: [PATCH 09/14] workqueue: drop spurious '*' from print_worker_info() fn declaration print_worker_info() declares its local 'fn' as work_func_t * but worker->current_func has type work_func_t (a function pointer). The extra level of indirection is wrong and only happens to be harmless today because every supported Linux architecture has sizeof(work_func_t) == sizeof(work_func_t *): copy_from_kernel_nofault() reads the correct number of bytes by accident, and %ps still resolves the printed address because the stored value is the function address regardless of declared type. On any future ABI where sizeof(void (*)()) differs from sizeof(void *), the nofault copy would transfer the wrong number of bytes and the subsequent %ps would print an incorrect address. Match the field type so the intent is explicit and the code does not silently rely on equal pointer sizes. Fixes: 3d1cb2059d93 ("workqueue: include workqueue info when printing debug dump of a worker task") Signed-off-by: Breno Leitao Signed-off-by: Tejun Heo --- kernel/workqueue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 9adee917e2bb..35b0c8f4f10f 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -6286,7 +6286,7 @@ EXPORT_SYMBOL_GPL(set_worker_desc); */ void print_worker_info(const char *log_lvl, struct task_struct *task) { - work_func_t *fn = NULL; + work_func_t fn = NULL; char name[WQ_NAME_LEN] = { }; char desc[WORKER_DESC_LEN] = { }; struct pool_workqueue *pwq = NULL; From 64d8eae3f89583821f599c0aa398dd2e69b31a89 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 29 May 2026 15:06:39 +0200 Subject: [PATCH 10/14] workqueue: Add warnings and fallback if system_{unbound}_wq is used Currently many users transitioned already to the new introduced workqueue (system_percpu_wq, system_dfl_wq), but there are new users who still use the older system_wq and system_unbound_wq. This change try to push this transition forward, by warning whether the old workqueues are used. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo --- include/linux/workqueue.h | 1 + kernel/workqueue.c | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index 6177624539b3..a283766a192a 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -409,6 +409,7 @@ enum wq_flags { __WQ_DRAINING = 1 << 16, /* internal: workqueue is draining */ __WQ_ORDERED = 1 << 17, /* internal: workqueue is ordered */ __WQ_LEGACY = 1 << 18, /* internal: create*_workqueue() */ + __WQ_DEPRECATED = 1 << 19, /* internal: workqueue is deprecated */ /* BH wq only allows the following flags */ __WQ_BH_ALLOWS = WQ_BH | WQ_HIGHPRI | WQ_PERCPU, diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 35b0c8f4f10f..088cd9b70fca 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2280,6 +2280,14 @@ static void __queue_work(int cpu, struct workqueue_struct *wq, unsigned int work_flags; unsigned int req_cpu = cpu; + /* + * NOTE: Check whether the used workqueue is deprecated and warn + */ + if (unlikely(wq->flags & __WQ_DEPRECATED)) + pr_warn_once("workqueue: work func %ps enqueued on deprecated workqueue. " + "Use system_{percpu|dfl}_wq instead.\n", + work->func); + /* * While a work item is PENDING && off queue, a task trying to * steal the PENDING will busy-loop waiting for it to either get @@ -8013,12 +8021,12 @@ void __init workqueue_init_early(void) ordered_wq_attrs[i] = attrs; } - system_wq = alloc_workqueue("events", WQ_PERCPU, 0); + system_wq = alloc_workqueue("events", WQ_PERCPU | __WQ_DEPRECATED, 0); system_percpu_wq = alloc_workqueue("events", WQ_PERCPU, 0); system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI | WQ_PERCPU, 0); system_long_wq = alloc_workqueue("events_long", WQ_PERCPU, 0); - system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_MAX_ACTIVE); + system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND | __WQ_DEPRECATED, WQ_MAX_ACTIVE); system_dfl_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_MAX_ACTIVE); system_freezable_wq = alloc_workqueue("events_freezable", WQ_FREEZABLE | WQ_PERCPU, 0); From 21c05ca88a548ca1353cbef189c97d4f03b90692 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Fri, 29 May 2026 15:06:40 +0200 Subject: [PATCH 11/14] workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present Currently there are no checks in order to enforce the use of one between WQ_PERCPU or WQ_UNBOUND. So act as following: - if neither of them is present, set WQ_PERCPU - if both are present, remove WQ_PERCPU Along with this change, WARN_ONCE(), so that the code still uses both or neither of them, can be changed. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Tejun Heo --- kernel/workqueue.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 088cd9b70fca..34c7c61c69aa 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -5802,7 +5802,7 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, /* see the comment above the definition of WQ_POWER_EFFICIENT */ if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient) - flags |= WQ_UNBOUND; + flags = (flags & ~WQ_PERCPU) | WQ_UNBOUND; /* allocate wq and format name */ if (flags & WQ_UNBOUND) @@ -5826,6 +5826,23 @@ static struct workqueue_struct *__alloc_workqueue(const char *fmt, pr_warn_once("workqueue: name exceeds WQ_NAME_LEN. Truncating to: %s\n", wq->name); + /* + * One among WQ_PERCPU and WQ_UNBOUND must be set, but not both. + * - If neither is set, default to WQ_PERCPU + * - If both are set, default to WQ_UNBOUND + * + * This code can be removed after workqueue are unbound by default + */ + if (unlikely(!(flags & (WQ_UNBOUND | WQ_PERCPU)))) { + WARN_ONCE(1, "workqueue: %s is using neither WQ_PERCPU or WQ_UNBOUND. " + "Setting WQ_PERCPU.\n", wq->name); + flags |= WQ_PERCPU; + } else if (unlikely((flags & WQ_PERCPU) && (flags & WQ_UNBOUND))) { + WARN_ONCE(1, "workqueue: %s uses both WQ_PERCPU and WQ_UNBOUND. " + "Dropped WQ_PERCPU, keeping WQ_UNBOUND.\n", wq->name); + flags &= ~WQ_PERCPU; + } + if (flags & WQ_BH) { /* * BH workqueues always share a single execution context per CPU From c7703f05d85f71153f5e241184397bc34da305e3 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 1 Jun 2026 19:46:26 -0700 Subject: [PATCH 12/14] btrfs: Drop WQ_PERCPU from ordered_flags in btrfs_init_workqueues() After commit 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present"), there is a warning from the btrfs-qgroup-rescan workqueue at run time: workqueue: btrfs-qgroup-rescan uses both WQ_PERCPU and WQ_UNBOUND. Dropped WQ_PERCPU, keeping WQ_UNBOUND. WQ_PERCPU is included in ordered_flags after commit 69635d7f4b34 ("fs: WQ_PERCPU added to alloc_workqueue users") and WQ_UNBOUND is set in alloc_ordered_workqueue(), which btrfs_alloc_ordered_workqueue() calls. Drop WQ_PERCPU from ordered_flags, as alloc_ordered_workqueue() notes that only WQ_FREEZABLE and WQ_MEM_RECLAIM are meaningful. Fixes: 69635d7f4b34 ("fs: WQ_PERCPU added to alloc_workqueue users") Fixes: 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present") Signed-off-by: Nathan Chancellor Reviewed-by: Breno Leitao Acked-by: Marco Crivellari Acked-by: David Sterba Signed-off-by: Tejun Heo --- fs/btrfs/disk-io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 8a11be02eeb9..f2a3b0705486 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1928,7 +1928,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) { u32 max_active = fs_info->thread_pool_size; unsigned int flags = WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_UNBOUND; - unsigned int ordered_flags = WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_PERCPU; + unsigned int ordered_flags = WQ_MEM_RECLAIM | WQ_FREEZABLE; fs_info->workers = btrfs_alloc_workqueue(fs_info, "worker", flags, max_active, 16); From 311c9ff097fd5c06d4d7dc28b61eecffb1051e81 Mon Sep 17 00:00:00 2001 From: wuyankun Date: Thu, 11 Jun 2026 09:55:45 +0800 Subject: [PATCH 13/14] wifi: ath6kl: fix invalid workqueue flags in ath6kl_usb_create() ath6kl_usb_create() currently creates ath6kl_wq with flags set to 0: alloc_workqueue("ath6kl_wq", 0, 0) This triggers a runtime warning in __alloc_workqueue() because the queue is created with neither WQ_PERCPU nor WQ_UNBOUND set: workqueue: ath6kl_wq is using neither WQ_PERCPU or WQ_UNBOUND. Setting WQ_PERCPU. Set WQ_PERCPU explicitly to match the actual execution model and remove the warning during device probe. No functional change intended. Fixes: 21c05ca88a54 ("workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present") Reported-by: syzbot+f80c62f371ba6a1e7d79@syzkaller.appspotmail.com Link: https://lore.kernel.org/all/6a289c01.39669fcc.33b062.00aa.GAE@google.com/T/ Signed-off-by: wuyankun Signed-off-by: Tejun Heo --- drivers/net/wireless/ath/ath6kl/usb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath6kl/usb.c b/drivers/net/wireless/ath/ath6kl/usb.c index 79c18f5ee02b..945984c3dbe6 100644 --- a/drivers/net/wireless/ath/ath6kl/usb.c +++ b/drivers/net/wireless/ath/ath6kl/usb.c @@ -636,7 +636,7 @@ static struct ath6kl_usb *ath6kl_usb_create(struct usb_interface *interface) ar_usb = kzalloc_obj(struct ath6kl_usb); if (ar_usb == NULL) return NULL; - ar_usb->wq = alloc_workqueue("ath6kl_wq", 0, 0); + ar_usb->wq = alloc_workqueue("ath6kl_wq", WQ_PERCPU, 0); if (!ar_usb->wq) { kfree(ar_usb); return NULL; From 581ceea813b6c2d923caaa639395117d3423cde3 Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Thu, 14 May 2026 17:01:22 +0200 Subject: [PATCH 14/14] drm/bridge: anx7625: Add WQ_PERCPU add to alloc_workqueue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This continues the effort to refactor workqueue APIs, which began with the introduction of new workqueues and a new alloc_workqueue flag in: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") The refactoring is going to alter the default behavior of alloc_workqueue() to be unbound by default. With the introduction of the WQ_PERCPU flag (equivalent to !WQ_UNBOUND), any alloc_workqueue() caller that doesn’t explicitly specify WQ_UNBOUND must now use WQ_PERCPU. For more details see the Link tag below. In order to keep alloc_workqueue() behavior identical, explicitly request WQ_PERCPU. Link: https://lore.kernel.org/all/20250221112003.1dSuoGyc@linutronix.de/ Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Reviewed-by: Thomas Zimmermann Signed-off-by: Tejun Heo --- drivers/gpu/drm/bridge/analogix/anx7625.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 54b02242d6db..8c3154817896 100644 --- a/drivers/gpu/drm/bridge/analogix/anx7625.c +++ b/drivers/gpu/drm/bridge/analogix/anx7625.c @@ -2849,7 +2849,8 @@ static int anx7625_i2c_probe(struct i2c_client *client) if (platform->pdata.intp_irq) { INIT_WORK(&platform->work, anx7625_work_func); platform->workqueue = alloc_workqueue("anx7625_work", - WQ_FREEZABLE | WQ_MEM_RECLAIM, 1); + WQ_FREEZABLE | WQ_MEM_RECLAIM | WQ_PERCPU, + 1); if (!platform->workqueue) { DRM_DEV_ERROR(dev, "fail to create work queue\n"); ret = -ENOMEM;