From nobody Fri Oct 2 06:59:09 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5557441A57D for ; Thu, 1 Oct 2026 16:07:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870849; cv=none; b=ATNnlLAYEwklZlZG9ggkdS7dHlW3+EgUV58yyOTfbalGG8r+PP9H7UrcHH0biOisIjMTCzV5Kkc+z/TfQdyDieW85ZMXDOT2qaxjbx4+kNEjdRXLx0dgJVBeAVrTc+5kufL9TUYuPJ4vv1NGAPsVURpvmOjxfNKMxYjS19xa2Zs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870849; c=relaxed/simple; bh=d4ILIYkS8paOjHCpXE6BaUZ3/WlSlEe9BZ9QeJd1Jik=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fUjqjKAohG8Rxx46eRI1Y3a+kbJC6WcZEj/RFVNtggNX4gvUv4QJ1wpddLeTfUZuM2aJoVSmeH1iare7J5/DRXhqSOFtK/4gX8B5vjYJ7Xa/iff/sRwrgFz3HEj44GCxfooch4mZREkqKoHDbgkcsMogD7YbYW03RQj/0hOtflI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=ounXHvoD; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="ounXHvoD" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=Qa6MVRp2Z/2Dh1lxhJG6WikCgZ6jgdymcEiDlcJwzks=; b= ounXHvoDv1Bsv7T2SWZRZ7vx57Blq+/IwoVJZE/lf1+IFu3EZRobfghZFBoqQdDy5bNnP9tRwhJ5y rw575gv5dNSAcMxxhzqhvv5InvaxI/HyRNAgZkuFhO1tiLaVA8mo0xsZzGXmlgP0/PkCMwZPFrvsY mixKtjsdwa2auGxNaYA3A+RMqsCidjFhZRdfmZnciec73/zoZcrQwbU5vn++q2S1mSILJXo+X5HFh LF3ILxQzCH5qnquK5U6SSQwGfIHvxTAJmwbj2PUNetuZmEcrXPM+nXnlIrXhsxN3o4Xy9urnGyD21 Xb6Fr03zc0l2CSASQ2o3N9OgqbK32895hg==; Received: from [81.79.79.1] (helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1xCJJU-00ABvp-CH; Thu, 01 Oct 2026 18:07:16 +0200 From: Tvrtko Ursulin To: linux-kernel@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, kernel-dev@igalia.com, Tvrtko Ursulin , Boris Brezillon , Bradley Morgan , Chia-I Wu , Liviu Dudau , Matthew Brost , Steven Price , Tejun Heo Subject: [RFC v6 1/3] workqueue: Simplify unbound sysfs attribute registration Date: Thu, 1 Oct 2026 17:07:09 +0100 Message-ID: <20261001160711.59888-2-tvrtko.ursulin@igalia.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> References: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Instead of manually registering each attribute we can put them in an attribute group with a visibility check and device core will handle the rest, which simplifies the registration and error unwind. Signed-off-by: Tvrtko Ursulin Cc: Boris Brezillon Cc: Bradley Morgan Cc: Chia-I Wu Cc: Liviu Dudau Cc: Matthew Brost Cc: Steven Price Cc: Tejun Heo --- v2: * Fix accidental rename of affinity_scope. --- kernel/workqueue.c | 98 ++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/kernel/workqueue.c b/kernel/workqueue.c index e618108c6127..c83d68d7d0ee 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -7599,8 +7599,8 @@ static const struct attribute_group wq_sysfs_group = =3D { }; __ATTRIBUTE_GROUPS(wq_sysfs); =20 -static ssize_t wq_nice_show(struct device *dev, struct device_attribute *a= ttr, - char *buf) +static ssize_t nice_show(struct device *dev, struct device_attribute *attr, + char *buf) { struct workqueue_struct *wq =3D dev_to_wq(dev); int written; @@ -7627,8 +7627,8 @@ static struct workqueue_attrs *wq_sysfs_prep_attrs(st= ruct workqueue_struct *wq) return attrs; } =20 -static ssize_t wq_nice_store(struct device *dev, struct device_attribute *= attr, - const char *buf, size_t count) +static ssize_t nice_store(struct device *dev, struct device_attribute *att= r, + const char *buf, size_t count) { struct workqueue_struct *wq =3D dev_to_wq(dev); struct workqueue_attrs *attrs; @@ -7652,8 +7652,8 @@ static ssize_t wq_nice_store(struct device *dev, stru= ct device_attribute *attr, return ret ?: count; } =20 -static ssize_t wq_cpumask_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t unbound_cpumask_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct workqueue_struct *wq =3D dev_to_wq(dev); int written; @@ -7665,9 +7665,9 @@ static ssize_t wq_cpumask_show(struct device *dev, return written; } =20 -static ssize_t wq_cpumask_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t unbound_cpumask_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct workqueue_struct *wq =3D dev_to_wq(dev); struct workqueue_attrs *attrs; @@ -7689,8 +7689,8 @@ static ssize_t wq_cpumask_store(struct device *dev, return ret ?: count; } =20 -static ssize_t wq_affn_scope_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t affinity_scope_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct workqueue_struct *wq =3D dev_to_wq(dev); int written; @@ -7708,9 +7708,9 @@ static ssize_t wq_affn_scope_show(struct device *dev, return written; } =20 -static ssize_t wq_affn_scope_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t affinity_scope_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct workqueue_struct *wq =3D dev_to_wq(dev); struct workqueue_attrs *attrs; @@ -7731,8 +7731,8 @@ static ssize_t wq_affn_scope_store(struct device *dev, return ret ?: count; } =20 -static ssize_t wq_affinity_strict_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t affinity_strict_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct workqueue_struct *wq =3D dev_to_wq(dev); =20 @@ -7740,9 +7740,9 @@ static ssize_t wq_affinity_strict_show(struct device = *dev, wq->attrs->affn_strict); } =20 -static ssize_t wq_affinity_strict_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t affinity_strict_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct workqueue_struct *wq =3D dev_to_wq(dev); struct workqueue_attrs *attrs; @@ -7762,14 +7762,40 @@ static ssize_t wq_affinity_strict_store(struct devi= ce *dev, return ret ?: count; } =20 -static struct device_attribute wq_sysfs_unbound_attrs[] =3D { - __ATTR(nice, 0644, wq_nice_show, wq_nice_store), - __ATTR(cpumask, 0644, wq_cpumask_show, wq_cpumask_store), - __ATTR(affinity_scope, 0644, wq_affn_scope_show, wq_affn_scope_store), - __ATTR(affinity_strict, 0644, wq_affinity_strict_show, wq_affinity_strict= _store), - __ATTR_NULL, +static DEVICE_ATTR_RW(nice); +static DEVICE_ATTR_RW(affinity_scope); +static DEVICE_ATTR_RW(affinity_strict); +/* Avoid naming clash with the other cpumask */ +static struct device_attribute dev_attr_unbound_cpumask =3D + __ATTR(cpumask, 0644, unbound_cpumask_show, unbound_cpumask_store); + +static struct attribute *wq_sysfs_unbound_attrs[] =3D { + &dev_attr_nice.attr, + &dev_attr_unbound_cpumask.attr, + &dev_attr_affinity_scope.attr, + &dev_attr_affinity_strict.attr, + NULL, }; =20 +static umode_t wq_sysfs_unbound_group_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev =3D kobj_to_dev(kobj); + struct workqueue_struct *wq =3D dev_to_wq(dev); + + if (!(wq->flags & WQ_UNBOUND)) + return SYSFS_GROUP_INVISIBLE; + + return attr->mode; +} + +static const struct attribute_group wq_sysfs_unbound_group =3D { + .is_visible =3D wq_sysfs_unbound_group_visible, + .attrs =3D wq_sysfs_unbound_attrs, +}; + +__ATTRIBUTE_GROUPS(wq_sysfs_unbound); + static const struct bus_type wq_subsys =3D { .name =3D "workqueue", .dev_groups =3D wq_sysfs_groups, @@ -7907,14 +7933,9 @@ int workqueue_sysfs_register(struct workqueue_struct= *wq) wq_dev->wq =3D wq; wq_dev->dev.bus =3D &wq_subsys; wq_dev->dev.release =3D wq_device_release; + wq_dev->dev.groups =3D wq_sysfs_unbound_groups; dev_set_name(&wq_dev->dev, "%s", wq->name); =20 - /* - * attrs are created separately. Suppress uevent until - * everything is ready. - */ - dev_set_uevent_suppress(&wq_dev->dev, true); - ret =3D device_register(&wq_dev->dev); if (ret) { put_device(&wq_dev->dev); @@ -7922,21 +7943,6 @@ int workqueue_sysfs_register(struct workqueue_struct= *wq) return ret; } =20 - if (wq->flags & WQ_UNBOUND) { - struct device_attribute *attr; - - for (attr =3D wq_sysfs_unbound_attrs; attr->attr.name; attr++) { - ret =3D device_create_file(&wq_dev->dev, attr); - if (ret) { - device_unregister(&wq_dev->dev); - wq->wq_dev =3D NULL; - return ret; - } - } - } - - dev_set_uevent_suppress(&wq_dev->dev, false); - kobject_uevent(&wq_dev->dev.kobj, KOBJ_ADD); return 0; } =20 --=20 2.55.0 From nobody Fri Oct 2 06:59:09 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 55BEC45041A for ; Thu, 1 Oct 2026 16:07:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870849; cv=none; b=PW5TwIHuPW+dnEQYUv0Ow5ZjE3VZnuk0hNfGmCUHLv7Q1FuALW+K9B86njsHQmSilNra786r+utVWif+bKAAa6KblXAl5GEiIoyEqojcQS+sHXHAifbnLqN5P6o5njJ04kwu+lSVfVV0CZ8eYlR1go3P9nY0133/dCHuIKFMCKQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870849; c=relaxed/simple; bh=FzCnPUizZbxMp0sGu3XmU2KmdYbwiRn7VkCTh9CVp4s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=QPFTQZAnxaqgbi+mzSrYIgAdCycBufDYyiezWRgs/n64KR0ROxid6I86ghCttvhvgH/bCSyOGVIYjBVRzPteVqYdJ8GOH0+IGB8dM8JL0xJEcSWycFJi25yH4imh0vykN3PXnxGzLjvl2XkGx7P52+UGz8rrAqCG99G73Mej6Bc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=i6XkEaCQ; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="i6XkEaCQ" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=CJ3R2JUY4xO9H62iGWpET+BpJYBXdE9+xoIpWRMkabI=; b= i6XkEaCQv1s0tHoRBT7DrY5/RDKVkz5OVqIPc7g7srSiHiJLDHM/P7pehcPmmzn1ZfIuHRPJbeZFI kpJzDyLVDSBCYuL61JVJIhaPQ+jE0Yfi8Hb1FTl+s+yf/4euDqd6fdy4ryCTchsXgY7A6pK+6MglX QmgpA2D/tlg17YvUP9IBWt40fKwZRLgzswxtMV+lPjT786quX5IUDCHvPk4eZQNPJK2HQtGux8DCk DKDOzbKnu6ZkJWifGjZ12ff2MyHHS01nx0CYntTVYKORFo7Hdeu2jmB2VWw+YMyzw+mzxcEfUaRB5 rPTVSk8pIpS1/OxHv5Q4jlH32+opp+kTbw==; Received: from [81.79.79.1] (helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1xCJJV-00ABvr-34; Thu, 01 Oct 2026 18:07:17 +0200 From: Tvrtko Ursulin To: linux-kernel@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, kernel-dev@igalia.com, Tvrtko Ursulin , Boris Brezillon , Bradley Morgan , Chia-I Wu , Liviu Dudau , Matthew Brost , Steven Price , Tejun Heo Subject: [RFC v6 2/3] workqueue: Add support for real-time workers Date: Thu, 1 Oct 2026 17:07:10 +0100 Message-ID: <20261001160711.59888-3-tvrtko.ursulin@igalia.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> References: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" For use cases such as the DRM scheduler submitting work to the GPU on behalf of low latency userspace applications, where latter have sufficient privileges to have had successfully obtained realtime Vulkan global priority, competing with random background CPU load can create large latency spikes which gets in the way of a smooth user experience. For these situations the existing WQ_HIGHPRI does not bring a noticeable improvement and a stronger hint is needed. Lets add WQ_RT which creates workers with a SCHED_FIFO scheduling class to improve this. We use a minimum priority level since we only care about winning the contest against normal background CPU load. Signed-off-by: Tvrtko Ursulin Cc: Boris Brezillon Cc: Bradley Morgan Cc: Chia-I Wu Cc: Liviu Dudau Cc: Matthew Brost Cc: Steven Price Cc: Tejun Heo --- v2: * Limit WQ_RTPRI to unbound workqueues and make it have strict CPU affinitity. (Tejun) * Fixed commit message typos. (AI) * Fixed sysfs handling, max_active setting and user modified nice application. (AI) v3: * Fix worker->pool null pointer dereference race by moving the global decrement to detach_dying_workers(). * Rebase for upstream changes. v4: * Fixed onion unwind. * Moved affinity setting to default attributes. v5: * Dropped global and local limits. * Documented in workqueue.rst. * Added NR_WQ_ATTRIBUTES. * Reverted BH handling changes. v6: * Dropped separate attr->prio in favour of RTPRI_NICE_LEVEL checks. (Tejun) * Reworked on top of tj/for-7.4. v7: * Convert to attrs->prio encoded analoguous to task_struct->prio. * Rename flag to WQ_PRIO and do not re-order enums. * Forbid WQ_RT affinity modifications via sysfs. * Added wq_dump.py support. --- Documentation/core-api/workqueue.rst | 8 +++ include/linux/workqueue.h | 5 +- kernel/workqueue.c | 101 +++++++++++++++++++-------- tools/workqueue/wq_dump.py | 9 ++- 4 files changed, 90 insertions(+), 33 deletions(-) diff --git a/Documentation/core-api/workqueue.rst b/Documentation/core-api/= workqueue.rst index bb770f556568..d699c3832b19 100644 --- a/Documentation/core-api/workqueue.rst +++ b/Documentation/core-api/workqueue.rst @@ -225,6 +225,14 @@ resources, scheduled and executed. each other. Each maintains its separate pool of workers and implements concurrency management among its workers. =20 +``WQ_RT`` + Real-time priority workqueues must be created as unbound and will be + configured with the strict CPU affinity set. Their worker threads use th= e FIFO + scheduling policy with the lowest applicable priority. + + To be used sparingly for use cases such as the real-time GPU rendering + contexts accessible to privileged clients. + ``WQ_CPU_INTENSIVE`` Work items of a CPU intensive wq do not contribute to the concurrency level. In other words, runnable CPU intensive diff --git a/include/linux/workqueue.h b/include/linux/workqueue.h index a283766a192a..ebec9dcc9e5f 100644 --- a/include/linux/workqueue.h +++ b/include/linux/workqueue.h @@ -147,9 +147,9 @@ enum wq_affn_scope { */ struct workqueue_attrs { /** - * @nice: nice level + * @prio: priority encoded analoguous to task_struct->prio. */ - int nice; + int prio; =20 /** * @cpumask: allowed CPUs @@ -404,6 +404,7 @@ enum wq_flags { */ WQ_POWER_EFFICIENT =3D 1 << 7, WQ_PERCPU =3D 1 << 8, /* bound to a specific cpu */ + WQ_RT =3D 1 << 9, /* real-time priority, valid only with WQ_UNBOUND */ =20 __WQ_DESTROYING =3D 1 << 15, /* internal: workqueue is destroying */ __WQ_DRAINING =3D 1 << 16, /* internal: workqueue is draining */ diff --git a/kernel/workqueue.c b/kernel/workqueue.c index c83d68d7d0ee..afe39a18ad9e 100644 --- a/kernel/workqueue.c +++ b/kernel/workqueue.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -126,7 +127,8 @@ enum wq_internal_consts { * all cpus. Give MIN_NICE. */ RESCUER_NICE_LEVEL =3D MIN_NICE, - HIGHPRI_NICE_LEVEL =3D MIN_NICE, + HIGHPRI_PRIORITY =3D NICE_TO_PRIO(MIN_NICE), + RT_PRIORITY =3D MAX_PRIO, =20 WQ_NAME_LEN =3D 32, WORKER_ID_LEN =3D 10 + WQ_NAME_LEN, /* "kworker/R-" + WQ_NAME_LEN */ @@ -1275,7 +1277,7 @@ static bool assign_work(struct work_struct *work, str= uct worker *worker, =20 static struct irq_work *bh_pool_irq_work(struct worker_pool *pool) { - int high =3D pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL ? 1 : 0; + int high =3D pool->attrs->prio =3D=3D HIGHPRI_PRIORITY ? 1 : 0; =20 return &per_cpu(bh_pool_irq_works, pool->cpu)[high]; } @@ -1290,7 +1292,7 @@ static void kick_bh_pool(struct worker_pool *pool) return; } #endif - if (pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL) + if (pool->attrs->prio =3D=3D HIGHPRI_PRIORITY) raise_softirq_irqoff(HI_SOFTIRQ); else raise_softirq_irqoff(TASKLET_SOFTIRQ); @@ -2959,7 +2961,8 @@ static int format_worker_id(char *buf, size_t size, s= truct worker *worker, if (pool->cpu >=3D 0) return scnprintf(buf, size, "kworker/%d:%d%s", pool->cpu, worker->id, - pool->attrs->nice < 0 ? "H" : ""); + pool->attrs->prio < NICE_TO_PRIO(0) ? + "H" : ""); else return scnprintf(buf, size, "kworker/u%d:%d", pool->id, worker->id); @@ -3018,7 +3021,12 @@ static struct worker *create_worker(struct worker_po= ol *pool) goto fail; } =20 - set_user_nice(worker->task, pool->attrs->nice); + if (rt_prio(pool->attrs->prio)) + sched_set_fifo_low(worker->task); + else + set_user_nice(worker->task, + PRIO_TO_NICE(pool->attrs->prio)); + kthread_bind_mask(worker->task, pool_allowed_cpus(pool)); } =20 @@ -3910,7 +3918,7 @@ static void bh_worker(struct worker *worker) =20 if (budget_exhausted) trace_workqueue_bh_budget_yield(pool, restarts, timeout, - pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL); + pool->attrs->prio =3D=3D HIGHPRI_PRIORITY); } =20 /* @@ -3969,7 +3977,7 @@ static void drain_dead_softirq_workfn(struct work_str= uct *work) * don't hog this CPU's BH. */ if (repeat) { - if (pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL) + if (pool->attrs->prio =3D=3D HIGHPRI_PRIORITY) queue_work(system_bh_highpri_wq, work); else queue_work(system_bh_wq, work); @@ -4001,7 +4009,7 @@ void workqueue_softirq_dead(unsigned int cpu) dead_work.pool =3D pool; init_completion(&dead_work.done); =20 - if (pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL) + if (pool->attrs->prio =3D=3D HIGHPRI_PRIORITY) queue_work(system_bh_highpri_wq, &dead_work.work); else queue_work(system_bh_wq, &dead_work.work); @@ -5015,7 +5023,7 @@ struct workqueue_attrs *alloc_workqueue_attrs_noprof(= void) static void copy_workqueue_attrs(struct workqueue_attrs *to, const struct workqueue_attrs *from) { - to->nice =3D from->nice; + to->prio =3D from->prio; cpumask_copy(to->cpumask, from->cpumask); cpumask_copy(to->__pod_cpumask, from->__pod_cpumask); to->affn_strict =3D from->affn_strict; @@ -5046,7 +5054,7 @@ static u32 wqattrs_hash(const struct workqueue_attrs = *attrs) { u32 hash =3D 0; =20 - hash =3D jhash_1word(attrs->nice, hash); + hash =3D jhash_1word(attrs->prio, hash); hash =3D jhash_1word(attrs->affn_strict, hash); hash =3D jhash(cpumask_bits(attrs->__pod_cpumask), BITS_TO_LONGS(nr_cpumask_bits) * sizeof(long), hash); @@ -5060,7 +5068,7 @@ static u32 wqattrs_hash(const struct workqueue_attrs = *attrs) static bool wqattrs_equal(const struct workqueue_attrs *a, const struct workqueue_attrs *b) { - if (a->nice !=3D b->nice) + if (a->prio !=3D b->prio) return false; if (a->affn_strict !=3D b->affn_strict) return false; @@ -5928,8 +5936,19 @@ static struct workqueue_attrs *alloc_wq_std_attrs(st= ruct workqueue_struct *wq) if (!attrs) return NULL; =20 - if (wq->flags & WQ_HIGHPRI) - attrs->nice =3D HIGHPRI_NICE_LEVEL; + if (wq->flags & WQ_RT) { + attrs->prio =3D RT_PRIORITY; + /* + * RT workqueues have strict CPU affinity for low + * latency execution. + */ + attrs->affn_scope =3D WQ_AFFN_CPU; + attrs->affn_strict =3D true; + } else if (wq->flags & WQ_HIGHPRI) { + attrs->prio =3D HIGHPRI_PRIORITY; + } else { + attrs->prio =3D DEFAULT_PRIO; + } =20 if (wq->flags & __WQ_ORDERED) attrs->ordered =3D true; @@ -6115,6 +6134,12 @@ static struct workqueue_struct *__alloc_workqueue(co= nst char *fmt, return NULL; } =20 + if (flags & WQ_RT) { + if (WARN_ON_ONCE((flags & (WQ_HIGHPRI | WQ_UNBOUND)) !=3D + WQ_UNBOUND)) + return NULL; + } + /* see the comment above the definition of WQ_POWER_EFFICIENT */ if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient) flags =3D (flags & ~WQ_PERCPU) | WQ_UNBOUND; @@ -6671,9 +6696,9 @@ static void pr_cont_pool_info(struct worker_pool *poo= l) pr_cont(" flags=3D0x%x", pool->flags); if (pool->flags & POOL_BH) pr_cont(" bh%s", - pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL ? "-hi" : ""); + pool->attrs->prio =3D=3D HIGHPRI_PRIORITY ? "-hi" : ""); else - pr_cont(" nice=3D%d", pool->attrs->nice); + pr_cont(" nice=3D%d", PRIO_TO_NICE(pool->attrs->prio)); } =20 static void pr_cont_worker_id(struct worker *worker) @@ -6682,7 +6707,7 @@ static void pr_cont_worker_id(struct worker *worker) =20 if (pool->flags & POOL_BH) pr_cont("bh%s", - pool->attrs->nice =3D=3D HIGHPRI_NICE_LEVEL ? "-hi" : ""); + pool->attrs->prio =3D=3D HIGHPRI_PRIORITY ? "-hi" : ""); else pr_cont("%d%s", task_pid_nr(worker->task), worker->rescue_wq ? "(RESCUER)" : ""); @@ -7606,7 +7631,11 @@ static ssize_t nice_show(struct device *dev, struct = device_attribute *attr, int written; =20 mutex_lock(&wq->mutex); - written =3D scnprintf(buf, PAGE_SIZE, "%d\n", wq->attrs->nice); + if (wq->attrs->prio =3D=3D RT_PRIORITY) + written =3D scnprintf(buf, PAGE_SIZE, "rt\n"); + else + written =3D scnprintf(buf, PAGE_SIZE, "%d\n", + PRIO_TO_NICE(wq->attrs->prio)); mutex_unlock(&wq->mutex); =20 return written; @@ -7632,19 +7661,21 @@ static ssize_t nice_store(struct device *dev, struc= t device_attribute *attr, { struct workqueue_struct *wq =3D dev_to_wq(dev); struct workqueue_attrs *attrs; - int ret =3D -ENOMEM; + int ret, nice =3D 0; + + if (sscanf(buf, "%d", &nice) !=3D 1 || nice < MIN_NICE || nice > MAX_NICE) + return -EINVAL; =20 mutex_lock(&wq_pool_mutex); =20 attrs =3D wq_sysfs_prep_attrs(wq); - if (!attrs) + if (!attrs) { + ret =3D -ENOMEM; goto out_unlock; + } =20 - if (sscanf(buf, "%d", &attrs->nice) =3D=3D 1 && - attrs->nice >=3D MIN_NICE && attrs->nice <=3D MAX_NICE) - ret =3D apply_workqueue_attrs_locked(wq, attrs); - else - ret =3D -EINVAL; + attrs->prio =3D NICE_TO_PRIO(nice); + ret =3D apply_workqueue_attrs_locked(wq, attrs); =20 out_unlock: mutex_unlock(&wq_pool_mutex); @@ -7716,6 +7747,10 @@ static ssize_t affinity_scope_store(struct device *d= ev, struct workqueue_attrs *attrs; int affn, ret =3D -ENOMEM; =20 + /* Do not allow affinity changes for RT workers. */ + if (wq->flags & WQ_RT) + return -EINVAL; + affn =3D parse_affn_scope(buf); if (affn < 0) return affn; @@ -7748,6 +7783,10 @@ static ssize_t affinity_strict_store(struct device *= dev, struct workqueue_attrs *attrs; int v, ret =3D -ENOMEM; =20 + /* Do not allow affinity changes for RT workers. */ + if (wq->flags & WQ_RT) + return -EINVAL; + if (sscanf(buf, "%d", &v) !=3D 1) return -EINVAL; =20 @@ -7786,6 +7825,10 @@ static umode_t wq_sysfs_unbound_group_visible(struct= kobject *kobj, if (!(wq->flags & WQ_UNBOUND)) return SYSFS_GROUP_INVISIBLE; =20 + /* Do not allow priority changes for RT workers. */ + if ((wq->flags & WQ_RT) && !strcmp(attr->name, "nice")) + return 0444; + return attr->mode; } =20 @@ -8310,13 +8353,13 @@ static void __init restrict_unbound_cpumask(const c= har *name, const struct cpuma cpumask_and(wq_unbound_cpumask, wq_unbound_cpumask, mask); } =20 -static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu,= int nice) +static void __init init_cpu_worker_pool(struct worker_pool *pool, int cpu,= int prio) { BUG_ON(init_worker_pool(pool)); pool->cpu =3D cpu; cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu)); cpumask_copy(pool->attrs->__pod_cpumask, cpumask_of(cpu)); - pool->attrs->nice =3D nice; + pool->attrs->prio =3D prio; pool->attrs->affn_strict =3D true; pool->node =3D cpu_to_node(cpu); =20 @@ -8339,7 +8382,7 @@ static void __init init_cpu_worker_pool(struct worker= _pool *pool, int cpu, int n void __init workqueue_init_early(void) { struct wq_pod_type *pt =3D &wq_pod_types[WQ_AFFN_SYSTEM]; - int std_nice[NR_STD_WORKER_POOLS] =3D { 0, HIGHPRI_NICE_LEVEL }; + int std_prio[NR_STD_WORKER_POOLS] =3D { DEFAULT_PRIO, HIGHPRI_PRIORITY }; void (*irq_work_fns[NR_STD_WORKER_POOLS])(struct irq_work *) =3D { bh_pool_kick_normal, bh_pool_kick_highpri }; int i, cpu; @@ -8391,7 +8434,7 @@ void __init workqueue_init_early(void) =20 i =3D 0; for_each_bh_worker_pool(pool, cpu) { - init_cpu_worker_pool(pool, cpu, std_nice[i]); + init_cpu_worker_pool(pool, cpu, std_prio[i]); pool->flags |=3D POOL_BH; init_irq_work(bh_pool_irq_work(pool), irq_work_fns[i]); i++; @@ -8399,7 +8442,7 @@ void __init workqueue_init_early(void) =20 i =3D 0; for_each_cpu_worker_pool(pool, cpu) - init_cpu_worker_pool(pool, cpu, std_nice[i++]); + init_cpu_worker_pool(pool, cpu, std_prio[i++]); } =20 system_wq =3D alloc_workqueue("events", WQ_PERCPU | __WQ_DEPRECATED, 0); diff --git a/tools/workqueue/wq_dump.py b/tools/workqueue/wq_dump.py index 9313ebe0c525..371601b086ca 100644 --- a/tools/workqueue/wq_dump.py +++ b/tools/workqueue/wq_dump.py @@ -24,7 +24,7 @@ Worker Pools Lists all worker pools indexed by their ID. For each pool: =20 ref number of pool_workqueue's associated with this pool - nice nice value of the worker threads in the pool + prio priority of the worker threads in the pool idle number of idle workers workers number of all workers cpu CPU the pool is associated with (per-cpu pool) @@ -122,6 +122,8 @@ POOL_BH =3D prog['POOL_BH'] WQ_NAME_LEN =3D prog['WQ_NAME_LEN'].value_() cpumask_str_len =3D len(cpumask_str(wq_unbound_cpumask)) =20 +rt_prio =3D prog.constant('RT_PRIORITY', filename=3D'kernel/workqueue.c') + print('Affinity Scopes') print('=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D') =20 @@ -163,7 +165,10 @@ for pi, pool in idr_for_each(worker_pool_idr): =20 for pi, pool in idr_for_each(worker_pool_idr): pool =3D drgn.Object(prog, 'struct worker_pool', address=3Dpool) - print(f'pool[{pi:0{max_pool_id_len}}] flags=3D0x{pool.flags.value_():0= 2x} ref=3D{pool.refcnt.value_():{max_ref_len}} nice=3D{pool.attrs.nice.valu= e_():3} ', end=3D'') + prio =3D pool.attrs.prio.value_() + if prio =3D=3D rt_prio: + prio =3D 'rt' + print(f'pool[{pi:0{max_pool_id_len}}] flags=3D0x{pool.flags.value_():0= 2x} ref=3D{pool.refcnt.value_():{max_ref_len}} prio=3D{prio:3} ', end=3D'') print(f'idle/workers=3D{pool.nr_idle.value_():3}/{pool.nr_workers.valu= e_():3} ', end=3D'') if pool.cpu >=3D 0: print(f'cpu=3D{pool.cpu.value_():3}', end=3D'') --=20 2.55.0 From nobody Fri Oct 2 06:59:09 2026 Received: from fanzine2.igalia.com (fanzine2.igalia.com [213.97.179.56]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7DF0941A57D for ; Thu, 1 Oct 2026 16:07:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=213.97.179.56 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870843; cv=none; b=jzg/maMnrMlLz1DirMJBccFZQf5cE7ZXV98cOz0JJK5tjmRkPdbaKeRv9Gam2FwsN5p+2xn/JVPRllz1OwRQqFDCaS1umU0IxD1ScZGuCCGjPT6G33Q1lL2+E2pV9rTjBAtS2iJEUWu5mnAqAMDnxwZ2oxgCKzw5Z7NfLV3O+Bo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790870843; c=relaxed/simple; bh=nJ8thWtX0BR62hoBu0k165fv9DNktquCGNZn1jHoWYA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VovZZ/Pu8uYy7KW1aIH8mF3nmVu0Us1W4AJNsxNR+g7wVAU/pUJo7L9AEDP5FiYoX7DQaNELQmXNWyt/eg6b+8s98o9Cz+uIt5NPTrmiCtR2JWPveg/bpv/5H69uToxJOjov5qhMX0Ga4tskQ0Oliz52HrQ+cIh0ZZ+oZlAwGdY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com; spf=pass smtp.mailfrom=igalia.com; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b=sZF/a/Gm; arc=none smtp.client-ip=213.97.179.56 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=igalia.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=igalia.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=igalia.com header.i=@igalia.com header.b="sZF/a/Gm" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=igalia.com; s=20170329; h=Content-Transfer-Encoding:MIME-Version:Message-ID:Date:Subject: Cc:To:From:From:Reply-To; bh=VkkAeWhMQotf/bduL267u6rKPFOe7GrQo4YVproPxHQ=; b= sZF/a/Gmco+AM03+a519Or6eOC1D43q4fCgDdxp6kyUxJMDSSiYtENY5xxCu1G8mTSyXBInc/5GDj y0I6JNdSZ/NCVO/2QIAIZkosdElMB69tOMCR71DANR2nsD45GT29pGrPvs+L5dm5RKbQ4SL9YrtGX hcddsMU8+4azC0TrwF6Az9saGQSb8s3zBVjkNshyS89IxHsyu67/YMXO+jj2020jDFTtddOScDtvC LnV70zr//IfZWLZy2qizEtvzH7N3k7jxTx/fmv6/3INasxsJyL4A3D+UOew3XYbXTemh8rXk6r8FF JfkUIE8+oR/NHtY7wdv1ZOsiN/iXuXHh7Q==; Received: from [81.79.79.1] (helo=localhost) by fanzine2.igalia.com with esmtpsa (Cipher TLS1.3:ECDHE_SECP256R1__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim) id 1xCJJV-00ABvw-QP; Thu, 01 Oct 2026 18:07:17 +0200 From: Tvrtko Ursulin To: linux-kernel@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, kernel-dev@igalia.com, Tvrtko Ursulin , Boris Brezillon , Chia-I Wu , Liviu Dudau , Matthew Brost , Steven Price , Tejun Heo Subject: [RFC v6 3/3] drm/panthor: Create per queue priority workqueues Date: Thu, 1 Oct 2026 17:07:11 +0100 Message-ID: <20261001160711.59888-4-tvrtko.ursulin@igalia.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> References: <20261001160711.59888-1-tvrtko.ursulin@igalia.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Split the single workqueue shared between the driver internal logic and DRM scheduler use into separate ones, where the DRM scheduler one is created per GPU priority level using the appropriate mapping to workqueue priorities. Low and medium GPU priority are served by a normal workqueue, high is server by a WQ_HIGHPRI instance, while realtime GPU priority is using the newly added WQ_RTPRI flag for lowest possible latency. These workqueues are device global and for all three we set the maximum concurrency to two in order to keep the GPU optimally fed with work. Signed-off-by: Tvrtko Ursulin Cc: Boris Brezillon Cc: Chia-I Wu Cc: Liviu Dudau Cc: Matthew Brost Cc: Steven Price Cc: Tejun Heo --- drivers/gpu/drm/panthor/panthor_sched.c | 37 ++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/pant= hor/panthor_sched.c index 5b34032deff8..257895e5e48b 100644 --- a/drivers/gpu/drm/panthor/panthor_sched.c +++ b/drivers/gpu/drm/panthor/panthor_sched.c @@ -152,11 +152,18 @@ struct panthor_scheduler { * * Used for the scheduler tick, group update or other kind of FW * event processing that can't be handled in the threaded interrupt - * path. Also passed to the drm_gpu_scheduler instances embedded - * in panthor_queue. + * path. */ struct workqueue_struct *wq; =20 + /** + * @submit_wq: Per priority workqueues for the DRM scheduler + * + * Passed to the drm_gpu_scheduler instances embedded + * in panthor_queue based on the queue priority. + */ + struct workqueue_struct *submit_wq[PANTHOR_CSG_PRIORITY_COUNT]; + /** * @heap_alloc_wq: Workqueue used to schedule tiler_oom works. * @@ -3582,8 +3589,14 @@ group_create_queue(struct panthor_group *group, goto err_free_queue; } =20 + if (group->priority >=3D ARRAY_SIZE(group->ptdev->scheduler->submit_wq) || + !group->ptdev->scheduler->submit_wq[group->priority]) { + ret =3D -EINVAL; + goto err_free_queue; + } + sched_args.name =3D queue->name; - + sched_args.submit_wq =3D group->ptdev->scheduler->submit_wq[group->priori= ty]; ret =3D drm_sched_init(&queue->scheduler, &sched_args); if (ret) goto err_free_queue; @@ -4073,6 +4086,15 @@ static void panthor_sched_fini(struct drm_device *dd= ev, void *res) if (!sched || !sched->csg_slot_count) return; =20 + if (sched->submit_wq[PANTHOR_CSG_PRIORITY_MEDIUM]) + destroy_workqueue(sched->submit_wq[PANTHOR_CSG_PRIORITY_MEDIUM]); + + if (sched->submit_wq[PANTHOR_CSG_PRIORITY_HIGH]) + destroy_workqueue(sched->submit_wq[PANTHOR_CSG_PRIORITY_HIGH]); + + if (sched->submit_wq[PANTHOR_CSG_PRIORITY_RT]) + destroy_workqueue(sched->submit_wq[PANTHOR_CSG_PRIORITY_RT]); + if (sched->wq) destroy_workqueue(sched->wq); =20 @@ -4174,7 +4196,14 @@ int panthor_sched_init(struct panthor_device *ptdev) */ sched->heap_alloc_wq =3D alloc_workqueue("panthor-heap-alloc", WQ_UNBOUND= , 0); sched->wq =3D alloc_workqueue("panthor-csf-sched", WQ_MEM_RECLAIM | WQ_UN= BOUND, 0); - if (!sched->wq || !sched->heap_alloc_wq) { + sched->submit_wq[PANTHOR_CSG_PRIORITY_MEDIUM] =3D alloc_workqueue("pantho= r-drm", WQ_MEM_RECLAIM | WQ_UNBOUND, 2); + sched->submit_wq[PANTHOR_CSG_PRIORITY_LOW] =3D sched->submit_wq[PANTHOR_C= SG_PRIORITY_MEDIUM]; + sched->submit_wq[PANTHOR_CSG_PRIORITY_HIGH] =3D alloc_workqueue("panthor-= drm-high", WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_UNBOUND, 2); + sched->submit_wq[PANTHOR_CSG_PRIORITY_RT] =3D alloc_workqueue("panthor-dr= m-rt", WQ_RT | WQ_MEM_RECLAIM | WQ_UNBOUND, 2); + if (!sched->wq || !sched->heap_alloc_wq || + !sched->submit_wq[PANTHOR_CSG_PRIORITY_MEDIUM] || + !sched->submit_wq[PANTHOR_CSG_PRIORITY_HIGH] || + !sched->submit_wq[PANTHOR_CSG_PRIORITY_RT]) { panthor_sched_fini(&ptdev->base, sched); drm_err(&ptdev->base, "Failed to allocate the workqueues"); return -ENOMEM; --=20 2.55.0