powercap: idle_inject: Create threads on demand
Subsystems (3)
cover powercap: idle_inject: Create threads on demand
Idle injection currently registers per-CPU kernel threads during early initialization, including on CPUs for which no idle injection device will be registered.
This series makes thread creation follow the lifetime and CPU mask of each idle injection device.
The first patch adds an optional CPU mask to smpboot thread descriptors. The mask restricts thread creation, CPU hotplug handling and teardown, while preserving the existing behavior for descriptors without a mask.
The second patch uses this support to register idle injection threads when a device registers and destroy them when it unregisters. CPU ownership claims, registration rollback and thread teardown are serialized to prevent overlapping registrations from sharing threads.
Validation includes W=1 builds of the affected objects, whitespace checks and static review of the teardown locking paths. Concurrent registration and CPU hotplug runtime testing remains outstanding.
The series was tested on Rockchip RK3588.
Daniel Lezcano (2):
smpboot: Allow restricting per-CPU threads to a CPU mask
powercap: idle_inject: Create per-CPU threads on registration
drivers/powercap/idle_inject.c | 69 +++++++++++++++++++++------------- include/linux/smpboot.h | 4 ++ kernel/smpboot.c | 17 ++++++++- 3 files changed, 61 insertions(+), 29 deletions(-)
base-commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
Signature · 6 lines
2.43.0 _______________________________________________ Linux-rockchip mailing list [email protected] http://lists.infradead.org/mailman/listinfo/linux-rockchip
PATCH 1/2 smpboot: Allow restricting per-CPU threads to a CPU mask
smpboot_register_percpu_thread() creates a thread for every online CPU and registers the descriptor for subsequent CPU hotplug operations. There is currently no way to restrict these threads to a subset of CPUs, even when the user only needs work performed on that subset.
For example, an idle injection device targets a specific CPU mask. Creating threads outside that mask consumes resources unnecessarily. Supporting multiple such devices also requires each descriptor to manage only the threads associated with its own CPUs.
Add an optional CPU mask to struct smp_hotplug_thread and apply it throughout the thread lifecycle. Registration creates threads only for online CPUs in the mask. CPU bring-up creates threads for selected CPUs that were offline at registration, while parking and unparking are restricted to the same mask.
Apply the restriction to thread destruction as well, both during unregistration and registration failure cleanup. This is necessary when descriptors with disjoint masks share the same per-CPU task
storage: tearing down one descriptor must not stop or clear threads owned by another descriptor.
The caller must keep the mask valid and unchanged until the descriptor is unregistered. Callers sharing per-CPU task storage must ensure their masks do not overlap and serialize ownership changes themselves.
A NULL mask preserves the existing behavior, so current users continue to manage threads on all CPUs.
--- include/linux/smpboot.h | 4 ++++ kernel/smpboot.c | 17 +++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-)
include/linux/smpboot.h +4 -0
@@ -5,6 +5,7 @@#include <linux/types.h> struct task_struct ; +struct cpumask ;/* Cookie handed to the thread_fn*/ struct smpboot_thread_data ;
@@ -27,6 +28,8 @@ struct smpboot_thread_data;* unparked (cpu online) * @selfparking: Thread is not parked by the park function. * @thread_comm: The base name of the thread +* @cpumask: Optional mask of CPUs on which to create threads. Must remain +* valid and unchanged until the descriptor is unregistered. */ struct smp_hotplug_thread {struct task_struct *__percpu *store;
@@ -40,6 +43,7 @@ struct smp_hotplug_thread {void (*unpark)(unsigned int cpu);bool selfparking;const char *thread_comm; +const struct cpumask *cpumask; };int smpboot_register_percpu_thread (struct smp_hotplug_thread *plug_thread);
kernel/smpboot.c +15 -2
@@ -168,6 +168,9 @@ __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu);struct smpboot_thread_data *td; +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return 0 ; +if (tsk)return 0 ;
@@ -225,6 +228,9 @@ static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cp {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return ; +if (!ht->selfparking)kthread_unpark (tsk); }
@@ -244,6 +250,9 @@ static void smpboot_park_thread(struct smp_hotplug_thread *ht, unsigned int cpu) {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return ; +if (tsk && !ht->selfparking)kthread_park (tsk); }
@@ -267,6 +276,9 @@ static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)for_each_possible_cpu (cpu) {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +continue ; +if (tsk) {kthread_stop_put (tsk); *per_cpu_ptr (ht->store, cpu) =NULL ;
@@ -279,7 +291,8 @@ static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)* to hotplug * @plug_thread: Hotplug thread descriptor * -* Creates and starts the threads on all online cpus. +* Creates and starts the threads on online CPUs selected by the descriptor's +* cpumask, or all online CPUs if no mask is specified. */ int smpboot_register_percpu_thread (struct smp_hotplug_thread *plug_thread) {
@@ -308,7 +321,7 @@ EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);* smpboot_unregister_percpu_thread - Unregister a per_cpu thread related to hotplug * @plug_thread: Hotplug thread descriptor * -* Stops all threads on all possible cpus. +* Stops all threads associated with the descriptor, including offline CPUs. */ void smpboot_unregister_percpu_thread (struct smp_hotplug_thread *plug_thread) {
Signature · 1 line
2.43.0
Re: [PATCH 1/2] smpboot: Allow restricting per-CPU threads to a CPU mask
Quoted 22 lines · Daniel Lezcano
smpboot_register_percpu_thread()creates a thread for every online CPU and registers the descriptor for subsequent CPU hotplug operations. There is currently no way to restrict these threads to a subset of CPUs, even when the user only needs work performed on that subset.For example, an idle injection device targets a specific CPU mask. Creating threads outside that mask consumes resources unnecessarily. Supporting multiple such devices also requires each descriptor to manage only the threads associated with its own CPUs.
Add an optional CPU mask to struct
smp_hotplug_threadand apply it throughout the thread lifecycle. Registration creates threads only for online CPUs in the mask. CPU bring-up creates threads for selected CPUs that were offline at registration, while parking and unparking are restricted to the same mask.Apply the restriction to thread destruction as well, both during unregistration and registration failure cleanup. This is necessary when descriptors with disjoint masks share the same per-CPU task
storage: tearing down one descriptor must not stop or clear threads owned by another descriptor.
Quoted 7 lines
The caller must keep the mask valid and unchanged until the descriptor is unregistered. Callers sharing per-CPU task storage must ensure their masks do not overlap and serialize ownership changes themselves.
A NULL mask preserves the existing behavior, so current users continue to manage threads on all CPUs.
This is simple as it gets, thanks.
Quoted 98 lines
Signed-off-by Daniel Lezcano <[email protected]>---
include/linux/smpboot.h| 4 ++++kernel/smpboot.c| 17 +++++++++++++++--2 files changed, 19 insertions(+), 2 deletions(-)include/linux/smpboot.h +4
-0@@ -5,6 +5,7 @@#include <linux/types.h> struct task_struct ; +struct cpumask ;/* Cookie handed to the thread_fn*/ struct smpboot_thread_data ;@@ -27,6 +28,8 @@ struct smpboot_thread_data;* unparked (cpu online) * @selfparking: Thread is not parked by the park function. * @thread_comm: The base name of the thread +* @cpumask: Optional mask of CPUs on which to create threads. Must remain +* valid and unchanged until the descriptor is unregistered. */ struct smp_hotplug_thread {struct task_struct *__percpu *store;@@ -40,6 +43,7 @@ struct smp_hotplug_thread {void (*unpark)(unsigned int cpu);bool selfparking;const char *thread_comm; +const struct cpumask *cpumask; };int
smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread);kernel/smpboot.c +14
-1@@ -168,6 +168,9 @@ __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu);struct smpboot_thread_data *td; +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return 0 ; +if (tsk)return 0 ;@@ -225,6 +228,9 @@ static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cp {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return ; +if (!ht->selfparking)kthread_unpark (tsk); }@@ -244,6 +250,9 @@ static void smpboot_park_thread(struct smp_hotplug_thread *ht, unsigned int cpu) {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +return ; +if (tsk && !ht->selfparking)kthread_park (tsk); }@@ -267,6 +276,9 @@ static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)for_each_possible_cpu (cpu) {struct task_struct *tsk = *per_cpu_ptr (ht->store, cpu); +if (ht->cpumask && !cpumask_test_cpu (cpu, ht->cpumask)) +continue ; +if (tsk) {kthread_stop_put (tsk); *per_cpu_ptr (ht->store, cpu) =NULL ;@@ -279,7 +291,8 @@ static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)* to hotplug * @plug_thread: Hotplug thread descriptor * -* Creates and starts the threads on all online cpus. +* Creates and starts the threads on online CPUs selected by the descriptor's +* cpumask, or all online CPUs if no mask is specified. */ int
smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{kernel/smpboot.c +1
-1@@ -308,7 +321,7 @@ EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);* smpboot_unregister_percpu_thread - Unregister a per_cpu thread related to hotplug * @plug_thread: Hotplug thread descriptor * -* Stops all threads on all possible cpus. +* Stops all threads associated with the descriptor, including offline CPUs. */
Quoted 4 lines
void
smpboot_unregister_percpu_thread(struct smp_hotplug_thread *plug_thread)
{
--- Thanks!
PATCH 2/2 powercap: idle_inject: Create per-CPU threads on registration
In reply to Daniel LezcanoCreate idle injection threads when a device registers instead of creating them unconditionally during early initialization. Destroy the threads when the device unregisters.
Use a per-device smpboot descriptor with the device's CPU mask so thread creation and CPU hotplug handling are limited to the CPUs registered by that device. Allow stopping devices whose offline CPUs have not yet had a thread created, and update the registration documentation to describe the thread lifecycle and possible failures.
Serialize CPU ownership checks and claims with a framework-wide mutex. Without serialization, registrations with overlapping masks can both observe an unclaimed CPU and overwrite each other's device pointer. Their smpboot descriptors can then share the same per-CPU task, allowing one device's teardown to destroy a thread belonging to another device. Registration rollback can also clear another caller's ownership.
Hold the mutex through smpboot registration and failure rollback, and during thread unregistration through ownership release. This prevents a CPU from being claimed again until its previous thread is destroyed.
Stop idle injection before acquiring the mutex, keeping timer callback cancellation and stop-side hotplug exclusion outside the ownership critical section. Neither the idle injection threads nor their smpboot hotplug paths acquire this mutex. The existing caller-side lifecycle synchronization requirement remains in effect.
--- drivers/powercap/idle_inject.c | 69 +++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 27 deletions(-)
drivers/powercap/idle_inject.c +42 -27
@@ -10,11 +10,9 @@* It relies on the smpboot kthreads feature providing common code for CPU * hotplug and thread [un]parking. * -* All of the kthreads used for idle injection are created at init time. -* -* Next, the users of the idle injection framework provide a cpumask via -* its register function. The kthreads will be synchronized with respect to -* this cpumask. +* Users of the idle injection framework provide a cpumask via its register +* function. The kthreads are created on demand for CPUs in this mask and +* will be synchronized with respect to it. * * The idle + run duration is specified via separate helpers and that allows * idle injection to be started.
@@ -40,6 +38,7 @@#include <linux/cpu.h> #include <linux/hrtimer.h> #include <linux/kthread.h> +#include <linux/mutex.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/smpboot.h>
@@ -60,6 +59,7 @@ struct idle_inject_thread {/** * struct idle_inject_device - idle injection data * @timer: idle injection period timer +* @threads: per-device CPU hotplug thread descriptor * @idle_duration_us: duration of CPU idle time to inject * @run_duration_us: duration of CPU run time to allow * @latency_us: max allowed latency
@@ -82,6 +82,7 @@ struct idle_inject_thread {*/ struct idle_inject_device {struct hrtimer timer; +struct smp_hotplug_thread threads;unsigned int idle_duration_us;unsigned int run_duration_us;unsigned int latency_us;
@@ -92,6 +93,9 @@ struct idle_inject_device { static DEFINE_PER_CPU(struct idle_inject_thread, idle_inject_thread); static DEFINE_PER_CPU(struct idle_inject_device *, idle_inject_device); +/* Serialize CPU ownership changes and the associated thread lifecycle. */ +static DEFINE_MUTEX (idle_inject_lock); +/** * idle_inject_wakeup - Wake up idle injection threads * @ii_dev: target idle injection device
@@ -279,7 +283,8 @@ void idle_inject_stop(struct idle_inject_device *ii_dev) iit =per_cpu_ptr (&idle_inject_thread, cpu); iit->should_run =0 ; -wait_task_inactive (iit->tsk,TASK_ANY ); +if (iit->tsk) +wait_task_inactive (iit->tsk,TASK_ANY ); }cpu_hotplug_enable ();
@@ -319,12 +324,12 @@ static int idle_inject_should_run(unsigned int cpu)* idle * * This function creates an idle injection control device structure for the -* given set of CPUs and initializes the timer associated with it. This +* given set of CPUs, registers their kthreads and initializes the timer. This * function also allows to register update()callback. * It does not start any injection cycles. * -* Return: NULL if memory allocation fails, idle injection control device -* pointer on success. +* Return: NULL if allocation or thread registration fails, or a CPU is already +* registered; idle injection control device pointer on success. */ struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,
@@ -342,6 +347,8 @@ struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask, ii_dev->latency_us =UINT_MAX ; ii_dev->update = update; +mutex_lock (&idle_inject_lock); +for_each_cpu (cpu,to_cpumask (ii_dev->cpumask)) {if (per_cpu (idle_inject_device, cpu)) {
@@ -352,6 +359,20 @@ struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,per_cpu (idle_inject_device, cpu) = ii_dev; } + ii_dev->threads = (struct smp_hotplug_thread ) { + .store = &idle_inject_thread.tsk, + .setup = idle_inject_setup, + .thread_fn = idle_inject_fn, + .thread_comm ="idle_inject/%u" , + .thread_should_run = idle_inject_should_run, + .cpumask =to_cpumask (ii_dev->cpumask), + }; + +if (smpboot_register_percpu_thread (&ii_dev->threads)) + goto out_rollback; + +mutex_unlock (&idle_inject_lock); +return ii_dev; out_rollback:
@@ -361,6 +382,8 @@ struct idle_inject_device *idle_inject_register_full(struct cpumask *cpumask,per_cpu (idle_inject_device, cpu_rb) =NULL ; } +mutex_unlock (&idle_inject_lock); +kfree (ii_dev);return NULL ;
@@ -372,11 +395,11 @@ EXPORT_SYMBOL_NS_GPL(idle_inject_register_full, "IDLE_INJECT");* @cpumask: CPUs to be affected by idle injection * * This function creates an idle injection control device structure for the -* given set of CPUs and initializes the timer associated with it. It does not -* start any injection cycles. +* given set of CPUs, registers their kthreads and initializes the timer. It +* does not start any injection cycles. * -* Return: NULL if memory allocation fails, idle injection control device -* pointer on success. +* Return: NULL if allocation or thread registration fails, or a CPU is already +* registered; idle injection control device pointer on success. */ struct idle_inject_device *idle_inject_register (struct cpumask *cpumask) {
@@ -398,23 +421,15 @@ void idle_inject_unregister(struct idle_inject_device *ii_dev)idle_inject_stop (ii_dev); +mutex_lock (&idle_inject_lock); + +smpboot_unregister_percpu_thread (&ii_dev->threads); +for_each_cpu (cpu,to_cpumask (ii_dev->cpumask))per_cpu (idle_inject_device, cpu) =NULL ; +mutex_unlock (&idle_inject_lock); +kfree (ii_dev); }EXPORT_SYMBOL_NS_GPL (idle_inject_unregister,"IDLE_INJECT" ); - -static struct smp_hotplug_thread idle_inject_threads = { - .store = &idle_inject_thread.tsk, - .setup = idle_inject_setup, - .thread_fn = idle_inject_fn, - .thread_comm ="idle_inject/%u" , - .thread_should_run = idle_inject_should_run, -}; - -static int __init idle_inject_init (void ) -{ -return smpboot_register_percpu_thread (&idle_inject_threads); -} -early_initcall (idle_inject_init);
Signature · 1 line
2.43.0
Quoted 16 lines · Daniel Lezcano
Idle injection currently registers per-CPU kernel threads during early initialization, including on CPUs for which no idle injection device will be registered.
This series makes thread creation follow the lifetime and CPU mask of each idle injection device.
The first patch adds an optional CPU mask to smpboot thread descriptors. The mask restricts thread creation, CPU hotplug handling and teardown, while preserving the existing behavior for descriptors without a mask.
The second patch uses this support to register idle injection threads when a device registers and destroy them when it unregisters. CPU ownership claims, registration rollback and thread teardown are serialized to prevent overlapping registrations from sharing threads.
Quoted 5 lines
Validation includes W=1 builds of the affected objects, whitespace checks and static review of the teardown locking paths. Concurrent registration and CPU hotplug runtime testing remains outstanding.
The series was tested on Rockchip RK3588.
One thing not specified in this cover is which tree these changes have to go through ?
Quoted 7 lines
Cc Andrew Morton <[email protected]>Cc Rafael J. Wysocki <[email protected]>Daniel Lezcano (2):
smpboot: Allow restricting per-CPU threads to a CPU mask
powercap:idle_inject: Create per-CPU threads on registration
Quoted 7 lines
drivers/powercap/idle_inject.c | 69 +++++++++++++++++++++------------- include/linux/smpboot.h | 4 ++ kernel/smpboot.c | 17 ++++++++- 3 files changed, 61 insertions(+), 29 deletions(-)base-commit:
c03c1a14507dfaf8b856c2608f9ac216021fc222
_______________________________________________
Linux-rockchip mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-rockchip
Quoted 17 lines · On Thu, 5 Nov 2026 19:46:13 +0100 Daniel Lezcano…
Quoted 16 lines · Daniel Lezcano
Idle injection currently registers per-CPU kernel threads during early initialization, including on CPUs for which no idle injection device will be registered.
This series makes thread creation follow the lifetime and CPU mask of each idle injection device.
The first patch adds an optional CPU mask to smpboot thread descriptors. The mask restricts thread creation, CPU hotplug handling and teardown, while preserving the existing behavior for descriptors without a mask.
The second patch uses this support to register idle injection threads when a device registers and destroy them when it unregisters. CPU ownership claims, registration rollback and thread teardown are serialized to prevent overlapping registrations from sharing threads.
Quoted 8 lines
Quoted 5 lines
Validation includes W=1 builds of the affected objects, whitespace checks and static review of the teardown locking paths. Concurrent registration and CPU hotplug runtime testing remains outstanding.
The series was tested on Rockchip RK3588.
One thing not specified in this cover is which tree these changes have to go through ?
I'd volunteer Rafael's, after peterz/tglx review.
Please! Removing one kernel thread per cpu makes the world a better place.
Thomas Gleixner
maintainerQuoted 25 lines · Andrew Morton
Quoted 23 lines · On Thu, 5 Nov 2026 19:46:13 +0100 Daniel Lezcano…
Quoted 21 lines · Daniel Lezcano
Idle injection currently registers per-CPU kernel threads during early initialization, including on CPUs for which no idle injection device will be registered.
This series makes thread creation follow the lifetime and CPU mask of each idle injection device.
The first patch adds an optional CPU mask to smpboot thread descriptors. The mask restricts thread creation, CPU hotplug handling and teardown, while preserving the existing behavior for descriptors without a mask.
The second patch uses this support to register idle injection threads when a device registers and destroy them when it unregisters. CPU ownership claims, registration rollback and thread teardown are serialized to prevent overlapping registrations from sharing threads.
Validation includes W=1 builds of the affected objects, whitespace checks and static review of the teardown locking paths. Concurrent registration and CPU hotplug runtime testing remains outstanding.
The series was tested on Rockchip RK3588.
Quoted 7 lines
Quoted 2 lines
One thing not specified in this cover is which tree these changes have to go through ?
I'd volunteer Rafael's, after peterz/tglx review.
Please! Removing one kernel thread per cpu makes the world a better place.
I'll look at it in the next days with brain awake...
_______________________________________________
Linux-rockchip mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-rockchip