From 3d4ac374b1724ae81f4681991c91279b3db0cd23 Mon Sep 17 00:00:00 2001 From: Yue Wang <1939455790@qq.com> Date: Mon, 24 Aug 2026 20:11:05 +0800 Subject: [PATCH 1/3] tkernel: irqlatency: centralize runtime state transitions The detector exposes its proc controls before CPU hotplug callbacks are registered. A concurrent enable can therefore start uninitialized timers, and hotplug registration can attempt to start those timers a second time. Timer callbacks also rearm without checking whether shutdown has begun. Represent stopped, IRQ, softirq, and exiting modes with one state machine. Initialize every possible CPU before registering hotplug callbacks, publish the proc controls last, and make per-CPU timer start and stop idempotent. Make callbacks stop rearming once their CPU or the detector is inactive. Set the exiting state before removing proc entries and let cpuhp_remove_state() take the CPU read lock internally while it runs all teardown callbacks. This also avoids the nested CPU read lock proposed by pull request 974. Signed-off-by: Yue Wang <1939455790@qq.com> --- kernel/tkernel/irqlatency/irqlatency.c | 264 ++++++++++++++++--------- 1 file changed, 169 insertions(+), 95 deletions(-) diff --git a/kernel/tkernel/irqlatency/irqlatency.c b/kernel/tkernel/irqlatency/irqlatency.c index dc7f89b7c6bd..6d7661d2a1c1 100644 --- a/kernel/tkernel/irqlatency/irqlatency.c +++ b/kernel/tkernel/irqlatency/irqlatency.c @@ -81,32 +81,54 @@ struct latency_snapshot { struct per_cpu_detect_data { unsigned int soft_in_irq; + bool timers_active; struct timer_list softirq_timer; struct hrtimer irq_timer; struct latency_data irq_data; struct latency_data softirq_data; }; +enum irqlatency_state { + IRQLATENCY_STOPPED, + IRQLATENCY_IRQ_RUNNING, + IRQLATENCY_SOFTIRQ_RUNNING, + IRQLATENCY_EXITING, +}; + static u64 freq_ms = 10; static u64 irq_latency_ms = 30; -static unsigned int check_enable; +static enum irqlatency_state detector_state; static int irqlatency_hp_state; static DEFINE_MUTEX(control_lock); +static struct proc_dir_entry *latency_dir; static struct per_cpu_detect_data __percpu *detect_data; +static bool irqlatency_running(enum irqlatency_state state) +{ + return state == IRQLATENCY_IRQ_RUNNING || + state == IRQLATENCY_SOFTIRQ_RUNNING; +} + +static unsigned int irqlatency_mode(void) +{ + enum irqlatency_state state = READ_ONCE(detector_state); + + return irqlatency_running(state) ? state : 0; +} + /* * Note: Must be called with irq disabled. */ -static bool save_stack(u64 latency, unsigned int isirq, unsigned int soft_in_irq) +static bool save_stack(struct per_cpu_detect_data *detect, u64 latency, + unsigned int isirq, unsigned int soft_in_irq) { unsigned long nr_entries, stack_index; struct per_stack *pstack; struct latency_data *lat_data; bool saved = false; - lat_data = isirq ? this_cpu_ptr(&detect_data->irq_data) : - this_cpu_ptr(&detect_data->softirq_data); + lat_data = isirq ? &detect->irq_data : &detect->softirq_data; raw_spin_lock(&lat_data->lock); stack_index = lat_data->stack_index; @@ -145,7 +167,8 @@ static bool save_stack(u64 latency, unsigned int isirq, unsigned int soft_in_irq return saved; } -static bool record_latency(u64 delta, unsigned int isirq, unsigned int soft_in_irq) +static bool record_latency(struct per_cpu_detect_data *detect, u64 delta, + unsigned int isirq, unsigned int soft_in_irq) { int index = 0; u64 frequency = READ_ONCE(freq_ms); @@ -155,7 +178,7 @@ static bool record_latency(u64 delta, unsigned int isirq, unsigned int soft_in_i return false; if (unlikely(delta >= READ_ONCE(irq_latency_ms))) - save_stack(delta, isirq, soft_in_irq); + save_stack(detect, delta, isirq, soft_in_irq); delta -= frequency; delta >>= 1; @@ -170,12 +193,12 @@ static bool record_latency(u64 delta, unsigned int isirq, unsigned int soft_in_i if (isirq) { atomic_long_t *count; - count = this_cpu_ptr(&detect_data->irq_data.latency_count[index]); + count = &detect->irq_data.latency_count[index]; atomic_long_inc(count); } else if (!soft_in_irq) { atomic_long_t *count; - count = this_cpu_ptr(&detect_data->softirq_data.latency_count[index]); + count = &detect->softirq_data.latency_count[index]; atomic_long_inc(count); } @@ -205,38 +228,55 @@ static void reset_latency_trace(void *data) static void softirq_timer_func(struct timer_list *softirq_timer) { + struct per_cpu_detect_data *data = + from_timer(data, softirq_timer, softirq_timer); u64 now = local_clock(), delta; - delta = now - __this_cpu_read(detect_data->softirq_data.last_timestamp); - __this_cpu_write(detect_data->softirq_data.last_timestamp, now); - __this_cpu_write(detect_data->soft_in_irq, 0); + if (!READ_ONCE(data->timers_active) || + !irqlatency_running(READ_ONCE(detector_state))) + return; - record_latency(NS_TO_MS(delta), 0, 0); + delta = now - data->softirq_data.last_timestamp; + data->softirq_data.last_timestamp = now; + data->soft_in_irq = 0; - mod_timer(softirq_timer, - jiffies + msecs_to_jiffies(READ_ONCE(freq_ms))); + record_latency(data, NS_TO_MS(delta), 0, 0); + + if (READ_ONCE(data->timers_active) && + irqlatency_running(READ_ONCE(detector_state))) + mod_timer(softirq_timer, + jiffies + msecs_to_jiffies(READ_ONCE(freq_ms))); } static enum hrtimer_restart irq_hrtimer_func(struct hrtimer *irq_timer) { + struct per_cpu_detect_data *data = + container_of(irq_timer, struct per_cpu_detect_data, irq_timer); u64 now = local_clock(), delta; - delta = now - __this_cpu_read(detect_data->irq_data.last_timestamp); - __this_cpu_write(detect_data->irq_data.last_timestamp, now); + if (!READ_ONCE(data->timers_active) || + !irqlatency_running(READ_ONCE(detector_state))) + return HRTIMER_NORESTART; + + delta = now - data->irq_data.last_timestamp; + data->irq_data.last_timestamp = now; - if (record_latency(NS_TO_MS(delta), 1, 0)) - __this_cpu_write(detect_data->softirq_data.last_timestamp, now); - else if (READ_ONCE(check_enable) == 2 && - !__this_cpu_read(detect_data->soft_in_irq)) { - delta = now - __this_cpu_read( - detect_data->softirq_data.last_timestamp); + if (record_latency(data, NS_TO_MS(delta), 1, 0)) { + data->softirq_data.last_timestamp = now; + } else if (READ_ONCE(detector_state) == IRQLATENCY_SOFTIRQ_RUNNING && + !data->soft_in_irq) { + delta = now - data->softirq_data.last_timestamp; if (unlikely(NS_TO_MS(delta) >= READ_ONCE(irq_latency_ms) + READ_ONCE(freq_ms))) { - record_latency(NS_TO_MS(delta), 0, 1); - __this_cpu_write(detect_data->soft_in_irq, 1); + record_latency(data, NS_TO_MS(delta), 0, 1); + data->soft_in_irq = 1; } } + if (!READ_ONCE(data->timers_active) || + !irqlatency_running(READ_ONCE(detector_state))) + return HRTIMER_NORESTART; + hrtimer_forward_now(irq_timer, ms_to_ktime(READ_ONCE(freq_ms))); return HRTIMER_RESTART; @@ -249,20 +289,32 @@ static void percpu_timers_start(void *data) struct timer_list *softirq_timer = &detect_data->softirq_timer; struct hrtimer *irq_timer = &detect_data->irq_timer; + if (READ_ONCE(detect_data->timers_active)) + return; + detect_data->irq_data.last_timestamp = now; detect_data->softirq_data.last_timestamp = now; + detect_data->soft_in_irq = 0; + WRITE_ONCE(detect_data->timers_active, true); hrtimer_start_range_ns(irq_timer, ms_to_ktime(READ_ONCE(freq_ms)), 0, HRTIMER_MODE_REL_PINNED); - softirq_timer->expires = - jiffies + msecs_to_jiffies(READ_ONCE(freq_ms)); - add_timer_on(softirq_timer, smp_processor_id()); + mod_timer(softirq_timer, + jiffies + msecs_to_jiffies(READ_ONCE(freq_ms))); } -static void percpu_timers_init(unsigned int cpu) +static void percpu_data_init(unsigned int cpu) { struct per_cpu_detect_data *data = per_cpu_ptr(detect_data, cpu); + int i; + + raw_spin_lock_init(&data->irq_data.lock); + raw_spin_lock_init(&data->softirq_data.lock); + for (i = 0; i < MAX_LATENCY_RECORD; i++) { + atomic_long_set(&data->irq_data.latency_count[i], 0); + atomic_long_set(&data->softirq_data.latency_count[i], 0); + } timer_setup(&data->softirq_timer, softirq_timer_func, TIMER_PINNED | TIMER_IRQSAFE); @@ -272,12 +324,18 @@ static void percpu_timers_init(unsigned int cpu) data->irq_timer.function = irq_hrtimer_func; } +static void percpu_timers_stop(struct per_cpu_detect_data *data) +{ + WRITE_ONCE(data->timers_active, false); + del_timer_sync(&data->softirq_timer); + hrtimer_cancel(&data->irq_timer); +} + static int irqlatency_cpu_online(unsigned int cpu) { struct per_cpu_detect_data *data = per_cpu_ptr(detect_data, cpu); - percpu_timers_init(cpu); - if (READ_ONCE(check_enable)) + if (irqlatency_running(READ_ONCE(detector_state))) percpu_timers_start(data); return 0; @@ -287,8 +345,7 @@ static int irqlatency_cpu_offline(unsigned int cpu) { struct per_cpu_detect_data *data = per_cpu_ptr(detect_data, cpu); - del_timer_sync(&data->softirq_timer); - hrtimer_cancel(&data->irq_timer); + percpu_timers_stop(data); return 0; } @@ -307,20 +364,43 @@ static void latency_timers_stop(void) int cpu; for_each_online_cpu(cpu) { - struct timer_list *softirq_timer; - struct hrtimer *irq_timer; + percpu_timers_stop(per_cpu_ptr(detect_data, cpu)); + } +} + +static int irqlatency_set_state(enum irqlatency_state new_state) +{ + enum irqlatency_state old_state; + + lockdep_assert_held(&control_lock); - softirq_timer = per_cpu_ptr(&detect_data->softirq_timer, cpu); - del_timer_sync(softirq_timer); + old_state = READ_ONCE(detector_state); + if (old_state == IRQLATENCY_EXITING) + return -ENODEV; + if (new_state > IRQLATENCY_SOFTIRQ_RUNNING) + return -EINVAL; + if (new_state == old_state) + return 0; - irq_timer = per_cpu_ptr(&detect_data->irq_timer, cpu); - hrtimer_cancel(irq_timer); + if (irqlatency_running(old_state) == irqlatency_running(new_state)) { + WRITE_ONCE(detector_state, new_state); + return 0; } + + cpus_read_lock(); + WRITE_ONCE(detector_state, new_state); + if (irqlatency_running(new_state)) + latency_timers_start(); + else + latency_timers_stop(); + cpus_read_unlock(); + + return 0; } static int enable_show(struct seq_file *m, void *ptr) { - seq_printf(m, "%d\n", READ_ONCE(check_enable)); + seq_printf(m, "%u\n", irqlatency_mode()); return 0; } @@ -334,6 +414,7 @@ static ssize_t enable_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { unsigned int enable; + int ret; if (kstrtouint_from_user(buf, count, 0, &enable)) return -EINVAL; @@ -342,24 +423,10 @@ static ssize_t enable_write(struct file *file, const char __user *buf, return -EINVAL; mutex_lock(&control_lock); - if (enable == check_enable) - goto unlock; - - cpus_read_lock(); - if (!enable) { - WRITE_ONCE(check_enable, 0); - latency_timers_stop(); - } else if (!check_enable) { - WRITE_ONCE(check_enable, enable); - latency_timers_start(); - } else { - WRITE_ONCE(check_enable, enable); - } - cpus_read_unlock(); - -unlock: + ret = irqlatency_set_state(enable); mutex_unlock(&control_lock); - return count; + + return ret ? ret : count; } static const struct proc_ops enable_fops = { @@ -391,7 +458,11 @@ static ssize_t freq_write(struct file *file, const char __user *buf, return -EINVAL; mutex_lock(&control_lock); - if (check_enable) { + if (READ_ONCE(detector_state) == IRQLATENCY_EXITING) { + mutex_unlock(&control_lock); + return -ENODEV; + } + if (irqlatency_running(READ_ONCE(detector_state))) { mutex_unlock(&control_lock); return -EINVAL; } @@ -443,7 +514,11 @@ static ssize_t lat_write(struct file *file, const char __user *buf, return -EINVAL; mutex_lock(&control_lock); - if (check_enable) { + if (READ_ONCE(detector_state) == IRQLATENCY_EXITING) { + mutex_unlock(&control_lock); + return -ENODEV; + } + if (irqlatency_running(READ_ONCE(detector_state))) { mutex_unlock(&control_lock); return -EINVAL; } @@ -691,58 +766,54 @@ static const struct proc_ops trace_dist_fops = { .proc_release = single_release, }; -static int __init trace_latency_init(void) +static int irqlatency_proc_create(void) { - struct proc_dir_entry *latency_dir; - int ret = -ENOMEM; - int cpu, i; - - detect_data = alloc_percpu(struct per_cpu_detect_data); - if (!detect_data) - return -ENOMEM; - for_each_possible_cpu(cpu) { - struct per_cpu_detect_data *data = per_cpu_ptr(detect_data, cpu); - - raw_spin_lock_init(&data->irq_data.lock); - raw_spin_lock_init(&data->softirq_data.lock); - for (i = 0; i < MAX_LATENCY_RECORD; i++) { - atomic_long_set(&data->irq_data.latency_count[i], 0); - atomic_long_set(&data->softirq_data.latency_count[i], 0); - } - } - latency_dir = proc_mkdir("irq_latency", NULL); if (!latency_dir) - goto free_data; - - if (!proc_create("enable", 0600, latency_dir, &enable_fops)) - goto remove_proc; + return -ENOMEM; - if (!proc_create("freq_ms", 0600, latency_dir, &freq_fops)) - goto remove_proc; + if (!proc_create("enable", 0600, latency_dir, &enable_fops) || + !proc_create("freq_ms", 0600, latency_dir, &freq_fops) || + !proc_create("latency_thresh_ms", 0600, latency_dir, &lat_fops) || + !proc_create("trace_stack", 0600, latency_dir, + &trace_stack_fops) || + !proc_create("trace_dist", 0400, latency_dir, &trace_dist_fops)) { + proc_remove(latency_dir); + latency_dir = NULL; + return -ENOMEM; + } - if (!proc_create("latency_thresh_ms", 0600, latency_dir, &lat_fops)) - goto remove_proc; + return 0; +} - if (!proc_create("trace_stack", 0600, latency_dir, &trace_stack_fops)) - goto remove_proc; +static int __init trace_latency_init(void) +{ + int cpu, ret; - if (!proc_create("trace_dist", 0400, latency_dir, &trace_dist_fops)) - goto remove_proc; + detect_data = alloc_percpu(struct per_cpu_detect_data); + if (!detect_data) + return -ENOMEM; + for_each_possible_cpu(cpu) + percpu_data_init(cpu); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "tkernel/irqlatency:online", irqlatency_cpu_online, irqlatency_cpu_offline); if (ret < 0) - goto remove_proc; + goto free_data; irqlatency_hp_state = ret; + ret = irqlatency_proc_create(); + if (ret) + goto remove_hp_state; + pr_info("Load irq latency check module!\n"); return 0; -remove_proc: - remove_proc_subtree("irq_latency", NULL); +remove_hp_state: + WRITE_ONCE(detector_state, IRQLATENCY_EXITING); + cpuhp_remove_state(irqlatency_hp_state); free_data: free_percpu(detect_data); @@ -751,11 +822,13 @@ static int __init trace_latency_init(void) static void __exit trace_latency_exit(void) { - remove_proc_subtree("irq_latency", NULL); mutex_lock(&control_lock); - WRITE_ONCE(check_enable, 0); - cpuhp_remove_state(irqlatency_hp_state); + WRITE_ONCE(detector_state, IRQLATENCY_EXITING); mutex_unlock(&control_lock); + + proc_remove(latency_dir); + latency_dir = NULL; + cpuhp_remove_state(irqlatency_hp_state); free_percpu(detect_data); pr_info("Unload irq latency check module!\n"); } @@ -764,3 +837,4 @@ module_init(trace_latency_init); module_exit(trace_latency_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("shookliu "); +MODULE_DESCRIPTION("TKernel IRQ and softirq latency detector"); -- Gitee From 51caee0250fac4acce5587f3cb004055fb33a49e Mon Sep 17 00:00:00 2001 From: Yue Wang <1939455790@qq.com> Date: Mon, 24 Aug 2026 20:12:05 +0800 Subject: [PATCH 2/3] tkernel: irqlatency: retain records across CPU offline Stack and histogram readers iterate cpu_online_mask without holding the CPU lock. A concurrent hotplug operation can therefore make one report use inconsistent CPU sets. Records from a CPU also disappear from reports as soon as that CPU is taken offline, even though its per-CPU storage remains allocated. Copy the possible and online CPU masks while holding cpus_read_lock(), then release the lock before copying records and formatting output. Report saved stacks for offline CPUs with an explicit marker and include their counters in the aggregate histogram. Clear every possible CPU directly under the existing per-buffer locks so a reset also removes records retained for offline CPUs. Signed-off-by: Yue Wang <1939455790@qq.com> --- kernel/tkernel/irqlatency/irqlatency.c | 63 ++++++++++++++++++++------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/kernel/tkernel/irqlatency/irqlatency.c b/kernel/tkernel/irqlatency/irqlatency.c index 6d7661d2a1c1..ca5b6e070db7 100644 --- a/kernel/tkernel/irqlatency/irqlatency.c +++ b/kernel/tkernel/irqlatency/irqlatency.c @@ -561,11 +561,12 @@ static ssize_t trace_stack_write(struct file *file, const char __user *buf, int cpu; mutex_lock(&control_lock); - cpus_read_lock(); - for_each_online_cpu(cpu) - smp_call_function_single(cpu, reset_latency_trace, - per_cpu_ptr(detect_data, cpu), true); - cpus_read_unlock(); + if (READ_ONCE(detector_state) == IRQLATENCY_EXITING) { + mutex_unlock(&control_lock); + return -ENODEV; + } + for_each_possible_cpu(cpu) + reset_latency_trace(per_cpu_ptr(detect_data, cpu)); mutex_unlock(&control_lock); return count; } @@ -611,11 +612,13 @@ static void trace_stack_print(struct seq_file *m, } static void trace_stack_irq_show(struct seq_file *m, unsigned int isirq, - struct latency_snapshot *snapshot) + struct latency_snapshot *snapshot, + const struct cpumask *cpus, + const struct cpumask *online) { int cpu; - for_each_online_cpu(cpu) { + for_each_cpu(cpu, cpus) { int i; struct latency_data *lat_data; @@ -626,7 +629,10 @@ static void trace_stack_irq_show(struct seq_file *m, unsigned int isirq, if (!snapshot->stack_index) continue; - seq_printf(m, " cpu: %d\n", cpu); + if (cpumask_test_cpu(cpu, online)) + seq_printf(m, " cpu: %d\n", cpu); + else + seq_printf(m, " cpu: %d (offline)\n", cpu); for (i = 0; i < snapshot->stack_index; i++) { seq_printf(m, "%*cCOMMAND: %s PID: %d LATENCY: %llu%s\n", @@ -643,24 +649,40 @@ static void trace_stack_irq_show(struct seq_file *m, unsigned int isirq, static int trace_stack_show(struct seq_file *m, void *v) { + cpumask_var_t cpus, online; struct latency_snapshot *snapshot; + int ret = -ENOMEM; snapshot = kzalloc(sizeof(*snapshot), GFP_KERNEL); if (!snapshot) return -ENOMEM; + if (!zalloc_cpumask_var(&cpus, GFP_KERNEL)) + goto free_snapshot; + if (!zalloc_cpumask_var(&online, GFP_KERNEL)) + goto free_cpus; + + cpus_read_lock(); + cpumask_copy(cpus, cpu_possible_mask); + cpumask_copy(online, cpu_online_mask); + cpus_read_unlock(); seq_printf(m, "irq_latency_ms: %llu\n\n", READ_ONCE(irq_latency_ms)); seq_puts(m, " irq:\n"); - trace_stack_irq_show(m, true, snapshot); + trace_stack_irq_show(m, true, snapshot, cpus, online); seq_putc(m, '\n'); seq_puts(m, " softirq:\n"); - trace_stack_irq_show(m, false, snapshot); + trace_stack_irq_show(m, false, snapshot, cpus, online); + ret = 0; + free_cpumask_var(online); +free_cpus: + free_cpumask_var(cpus); +free_snapshot: kfree(snapshot); - return 0; + return ret; } static int trace_stack_open(struct inode *inode, struct file *file) @@ -724,12 +746,13 @@ static bool trace_histogram_show(struct seq_file *m, const char *header, return true; } -static void trace_dist_show_irq(struct seq_file *m, void *v, unsigned int isirq) +static void trace_dist_show_irq(struct seq_file *m, unsigned int isirq, + const struct cpumask *cpus) { int cpu; unsigned long latency_count[MAX_LATENCY_RECORD] = { 0 }; - for_each_online_cpu(cpu) { + for_each_cpu(cpu, cpus) { int i; atomic_long_t *count; @@ -748,8 +771,18 @@ static void trace_dist_show_irq(struct seq_file *m, void *v, unsigned int isirq) static int trace_dist_show(struct seq_file *m, void *v) { - trace_dist_show_irq(m, v, 1); - trace_dist_show_irq(m, v, 0); + cpumask_var_t cpus; + + if (!zalloc_cpumask_var(&cpus, GFP_KERNEL)) + return -ENOMEM; + + cpus_read_lock(); + cpumask_copy(cpus, cpu_possible_mask); + cpus_read_unlock(); + + trace_dist_show_irq(m, 1, cpus); + trace_dist_show_irq(m, 0, cpus); + free_cpumask_var(cpus); return 0; } -- Gitee From 7bfe59a3ce8b446bb304e614df7df7744f253a3b Mon Sep 17 00:00:00 2001 From: Yue Wang <1939455790@qq.com> Date: Mon, 24 Aug 2026 20:13:41 +0800 Subject: [PATCH 3/3] selftests: tkernel: stress irqlatency lifecycle changes The existing irqlatency test covers individual proc controls but does not exercise the concurrency and hotplug paths that own the per-CPU timers. It also leaves module teardown while detection is active untested. Run concurrent enable mode transitions and verify that the final state is consistent. When a writable secondary CPU is available, offline and online it while detection is active and read both reports while it is offline. When the test loaded the module itself, race stack reads with an enabled module unload and verify that the proc directory is removed. Document the runtime state model, retention of offline CPU records, runtime cost, and the dedicated-system requirement for disruptive selftest cases. Signed-off-by: Yue Wang <1939455790@qq.com> --- Documentation/tkernel/irq-latency.rst | 43 ++++++- tools/testing/selftests/tkernel/irqlatency.sh | 121 ++++++++++++++++-- 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/Documentation/tkernel/irq-latency.rst b/Documentation/tkernel/irq-latency.rst index ef6053a57f17..87ce2168cf33 100644 --- a/Documentation/tkernel/irq-latency.rst +++ b/Documentation/tkernel/irq-latency.rst @@ -48,6 +48,37 @@ The detector is controlled through ``/proc/irq_latency/``: Reports the accumulated IRQ-disable and softirq-disable latency distributions. +Runtime and CPU Hotplug +======================= + +Enable, disable, CPU hotplug, and module exit are serialized through a +single runtime state machine. A CPU that comes online starts its timers +only when detection is enabled. Taking a CPU offline synchronously stops +both of its timers before the hotplug operation completes. + +Stack records and histogram counters are retained when a CPU goes offline. +The stack report marks such CPUs with ``(offline)``, and the distribution +continues to include their counters. Write 0 to ``trace_stack`` to clear +records and counters for both online and offline CPUs. + +Readers copy the possible and online CPU masks while holding the CPU read +lock, then release that lock before copying records and formatting output. +Consequently, a long stack report does not hold up a CPU hotplug operation. + +Module exit changes the detector to an exiting state before removing its +proc controls. The registered CPU hotplug teardown then cancels every +per-CPU timer before the per-CPU storage is released. + +Runtime Impact +============== + +Loading the module allocates fixed-size storage for every possible CPU. +Periodic timers do not run until detection is enabled. While enabled, the +existing sampling cost is controlled by ``freq_ms``; using a lower value +increases timer and interrupt activity on every online CPU. State changes, +CPU hotplug, and trace reads add only bounded synchronization outside the +normal sampling path. + Example ======= @@ -74,4 +105,14 @@ Each record shows: Records are kept per-CPU in fixed-size storage. Once that storage is full, new stack records are dropped until 0 is written to -``trace_stack``. The report separates IRQ and softirq records. +``trace_stack``. Records survive CPU offline and are marked accordingly. +The report separates IRQ and softirq records. + +Selftests +========= + +``tools/testing/selftests/tkernel/irqlatency.sh`` exercises concurrent +state changes, CPU hotplug, trace reads, and module unload. The hotplug and +unload cases are intended for a dedicated test system rather than a +production host. The test restores a CPU that it takes offline and unloads +the module only when the test loaded that module itself. diff --git a/tools/testing/selftests/tkernel/irqlatency.sh b/tools/testing/selftests/tkernel/irqlatency.sh index b8e2d6d63792..9486fd874891 100755 --- a/tools/testing/selftests/tkernel/irqlatency.sh +++ b/tools/testing/selftests/tkernel/irqlatency.sh @@ -7,8 +7,11 @@ FREQ=$PROC_DIR/freq_ms THRESHOLD=$PROC_DIR/latency_thresh_ms TRACE_STACK=$PROC_DIR/trace_stack TRACE_DIST=$PROC_DIR/trace_dist -TESTS=12 +TESTS=15 module_loaded=0 +cpu_offlined=0 +hotplug_file= +reader_pid= script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) . "$script_dir/tkernel.sh" @@ -28,18 +31,54 @@ skip_unavailable() cleanup() { - write_value "$ENABLE" 0 || true - # Lowering the frequency first makes every valid saved threshold - # writable again before the original frequency is restored. - write_value "$FREQ" 5 || true - write_value "$THRESHOLD" "$old_threshold" || true - write_value "$FREQ" "$old_freq" || true - write_value "$ENABLE" "$old_enable" || true + if [ -n "$reader_pid" ]; then + kill "$reader_pid" 2>/dev/null || true + wait "$reader_pid" 2>/dev/null || true + fi + if [ "$cpu_offlined" -eq 1 ] && [ -n "$hotplug_file" ]; then + write_value "$hotplug_file" 1 || true + fi + if [ -e "$ENABLE" ]; then + write_value "$ENABLE" 0 || true + # Lowering the frequency first makes every valid saved threshold + # writable again before the original frequency is restored. + write_value "$FREQ" 5 || true + write_value "$THRESHOLD" "$old_threshold" || true + write_value "$FREQ" "$old_freq" || true + write_value "$ENABLE" "$old_enable" || true + fi if [ "$module_loaded" -eq 1 ]; then modprobe -r irqlatency 2>/dev/null || true fi } +toggle_enable() +{ + i=0 + while [ "$i" -lt 10 ]; do + write_value "$ENABLE" 1 || return 1 + write_value "$ENABLE" 2 || return 1 + write_value "$ENABLE" 0 || return 1 + i=$((i + 1)) + done +} + +find_hotplug_cpu() +{ + for path in /sys/devices/system/cpu/cpu[0-9]*/online; do + [ -w "$path" ] || continue + [ "$(cat "$path" 2>/dev/null)" = 1 ] || continue + cpu=${path%/online} + cpu=${cpu##*/} + cpu=${cpu#cpu} + [ "$cpu" -ne 0 ] || continue + printf '%s\n' "$path" + return 0 + done + + return 1 +} + echo "TAP version 13" [ "$(id -u)" -eq 0 ] || ksft_skip_all "root privileges are required" @@ -125,4 +164,70 @@ rc=$? [ "$rc" -eq 0 ] && [ "$(cat "$ENABLE")" -eq 0 ] ksft_result $? "latency detection can be stopped after use" +pids= +for worker in 1 2 3 4; do + toggle_enable & + pids="$pids $!" +done +rc=0 +for pid in $pids; do + wait "$pid" || rc=1 +done +write_value "$ENABLE" 0 || rc=1 +[ "$(cat "$ENABLE")" -eq 0 ] || rc=1 +ksft_result "$rc" "concurrent state transitions remain consistent" + +hotplug_file=$(find_hotplug_cpu || true) +if [ -z "$hotplug_file" ]; then + ksft_result_skip "CPU hotplug preserves detector state" \ + "no writable secondary CPU online control" +else + rc=0 + write_value "$ENABLE" 1 || rc=1 + if [ "$rc" -eq 0 ] && write_value "$hotplug_file" 0; then + cpu_offlined=1 + [ "$(cat "$hotplug_file")" = 0 ] || rc=1 + cat "$TRACE_STACK" >/dev/null || rc=1 + cat "$TRACE_DIST" >/dev/null || rc=1 + if write_value "$hotplug_file" 1; then + cpu_offlined=0 + else + rc=1 + fi + [ "$(cat "$hotplug_file" 2>/dev/null)" = 1 ] || rc=1 + [ "$(cat "$ENABLE")" = 1 ] || rc=1 + write_value "$ENABLE" 0 || rc=1 + ksft_result "$rc" "CPU hotplug preserves detector state" + else + write_value "$ENABLE" 0 || true + hotplug_file= + ksft_result_skip "CPU hotplug preserves detector state" \ + "the kernel rejected CPU offline" + fi +fi + +if [ "$module_loaded" -ne 1 ]; then + ksft_result_skip "enabled detector unload is safe" \ + "the test did not load a removable module" +else + rc=0 + write_value "$ENABLE" 1 || rc=1 + ( + i=0 + while [ "$i" -lt 100 ] && [ -r "$TRACE_STACK" ]; do + cat "$TRACE_STACK" >/dev/null 2>&1 || break + i=$((i + 1)) + done + ) & + reader_pid=$! + modprobe -r irqlatency 2>/dev/null || rc=1 + wait "$reader_pid" 2>/dev/null || true + reader_pid= + if [ "$rc" -eq 0 ]; then + module_loaded=0 + [ ! -e "$PROC_DIR" ] || rc=1 + fi + ksft_result "$rc" "enabled detector unload is safe" +fi + ksft_finished -- Gitee