diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-17 13:57:44 +0300 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-06-17 13:57:44 +0300 |
| commit | d4d9d39f046012ff330e81dcd9b1beadf3759f7e (patch) | |
| tree | f7870380ce580744dd71dfc6abfff7e8184286ce | |
| parent | 59a6c7ac0a47154774cb44c59a8735f6a16b75f7 (diff) | |
| parent | 581ceea813b6c2d923caaa639395117d3423cde3 (diff) | |
| download | linux-d4d9d39f046012ff330e81dcd9b1beadf3759f7e.tar.xz | |
Merge tag 'wq-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq
Pull workqueue updates from Tejun Heo:
- Continued progress toward making alloc_workqueue() unbound by
default: more callers converted to WQ_PERCPU / system_percpu_wq /
system_dfl_wq, and new warnings for queues that use neither WQ_PERCPU
nor WQ_UNBOUND or the legacy system_wq / system_unbound_wq.
- Misc: drop the now-trivial apply_wqattrs_lock()/unlock() wrappers,
forbid the TEST_WORKQUEUE benchmark from being built-in, and fix a
spurious pointer level in the worker debug-dump path.
* tag 'wq-for-7.2' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/wq:
drm/bridge: anx7625: Add WQ_PERCPU add to alloc_workqueue
wifi: ath6kl: fix invalid workqueue flags in ath6kl_usb_create()
btrfs: Drop WQ_PERCPU from ordered_flags in btrfs_init_workqueues()
workqueue: Add warnings and ensure one among WQ_PERCPU or WQ_UNBOUND is present
workqueue: Add warnings and fallback if system_{unbound}_wq is used
workqueue: drop spurious '*' from print_worker_info() fn declaration
workqueue: forbid TEST_WORKQUEUE from being built-in
workqueue: drop apply_wqattrs_lock()/unlock() wrappers
umh: replace use of system_unbound_wq with system_dfl_wq
rapidio: rio: add WQ_PERCPU to alloc_workqueue users
media: ddbridge: add WQ_PERCPU to alloc_workqueue users
platform: cznic: turris-omnia-mcu: replace use of system_wq with system_percpu_wq
media: synopsys: hdmirx: replace use of system_unbound_wq with system_dfl_wq
virt: acrn: Add WQ_PERCPU to alloc_workqueue users
| -rw-r--r-- | drivers/gpu/drm/bridge/analogix/anx7625.c | 3 | ||||
| -rw-r--r-- | drivers/media/pci/ddbridge/ddbridge-core.c | 2 | ||||
| -rw-r--r-- | drivers/media/platform/synopsys/hdmirx/snps_hdmirx.c | 8 | ||||
| -rw-r--r-- | drivers/net/wireless/ath/ath6kl/usb.c | 2 | ||||
| -rw-r--r-- | drivers/platform/cznic/turris-omnia-mcu-gpio.c | 2 | ||||
| -rw-r--r-- | drivers/rapidio/rio.c | 2 | ||||
| -rw-r--r-- | drivers/virt/acrn/irqfd.c | 2 | ||||
| -rw-r--r-- | fs/btrfs/disk-io.c | 2 | ||||
| -rw-r--r-- | include/linux/workqueue.h | 1 | ||||
| -rw-r--r-- | kernel/umh.c | 2 | ||||
| -rw-r--r-- | kernel/workqueue.c | 71 | ||||
| -rw-r--r-- | lib/Kconfig.debug | 5 |
12 files changed, 62 insertions, 40 deletions
diff --git a/drivers/gpu/drm/bridge/analogix/anx7625.c b/drivers/gpu/drm/bridge/analogix/anx7625.c index 513c11cdbc74..fffcd6154c71 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; 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); 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)); } 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; 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; 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; 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; diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 0a7d80da9c94..274e2f0826b6 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1951,7 +1951,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); 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/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; diff --git a/kernel/workqueue.c b/kernel/workqueue.c index 0c265eac903a..78068ae8f28a 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -2281,6 +2281,14 @@ static void __queue_work(int cpu, struct workqueue_struct *wq, 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 * queued or lose PENDING. Grabbing PENDING and queueing should @@ -5312,16 +5320,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 @@ -5818,7 +5816,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) @@ -5842,6 +5840,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 @@ -5877,7 +5892,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; @@ -5891,7 +5906,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; @@ -5899,7 +5914,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() @@ -5914,7 +5929,7 @@ err_free_wq: kfree(wq); return NULL; err_unlock_destroy: - apply_wqattrs_unlock(); + mutex_unlock(&wq_pool_mutex); err_destroy: destroy_workqueue(wq); return NULL; @@ -6310,7 +6325,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; @@ -7315,7 +7330,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) @@ -7328,7 +7343,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; } @@ -7354,7 +7369,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) @@ -7365,7 +7380,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; } @@ -7401,13 +7416,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; } @@ -7432,13 +7447,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; } @@ -7479,12 +7494,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; @@ -8037,12 +8052,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); diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index d9d7556bd7b9..3ca343f1a8d0 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 |
