diff --git a/Documentation/tkernel/irq-latency.rst b/Documentation/tkernel/irq-latency.rst index ef6053a57f175e7aebf5d665df2bd8123c332103..87ce2168cf33b77b72c522d01d2b07be2b91569c 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/kernel/tkernel/irqlatency/irqlatency.c b/kernel/tkernel/irqlatency/irqlatency.c index dc7f89b7c6bd9a0dbbc81ab297a4145dfc896202..ca5b6e070db74ef07a528790881ec0e204c183d1 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; - softirq_timer = per_cpu_ptr(&detect_data->softirq_timer, cpu); - del_timer_sync(softirq_timer); + lockdep_assert_held(&control_lock); - irq_timer = per_cpu_ptr(&detect_data->irq_timer, cpu); - hrtimer_cancel(irq_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; + + 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; } @@ -486,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; } @@ -536,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; @@ -551,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", @@ -568,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) @@ -649,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; @@ -673,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; } @@ -691,58 +799,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 +855,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 +870,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"); diff --git a/tools/testing/selftests/tkernel/irqlatency.sh b/tools/testing/selftests/tkernel/irqlatency.sh index b8e2d6d63792f5182f9efb3090e5e50f64ac629b..9486fd874891c110888d36e21a827d3b25cca267 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