// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. */ #include #include #include #include #include #include #include #define CREATE_TRACE_POINTS #include "preemptirq_long.h" #define IRQSOFF_SENTINEL 0x0fffDEAD static unsigned int sysctl_preemptoff_tracing_threshold_ns = 1000000; static unsigned int sysctl_irqsoff_tracing_threshold_ns = 5000000; static unsigned int sysctl_irqsoff_dmesg_output_enabled; static unsigned int sysctl_irqsoff_crash_sentinel_value; static unsigned int sysctl_irqsoff_crash_threshold_ns = 10000000; static unsigned int half_million = 500000; static unsigned int one_hundred_million = 100000000; static unsigned int one_million = 1000000; static DEFINE_PER_CPU(u64, irq_disabled_ts); /* * preemption disable tracking require additional context * to rule out false positives. see the comment in * test_preempt_disable_long() for more details. */ struct preempt_store { u64 ts; int pid; unsigned long ncsw; }; static DEFINE_PER_CPU(struct preempt_store, the_ps); static void note_irq_disable(void *u1, unsigned long u2, unsigned long u3) { if (is_idle_task(current)) return; /* * We just have to note down the time stamp here. We * use stacktrace trigger feature to print the stacktrace. */ this_cpu_write(irq_disabled_ts, sched_clock()); } static void test_irq_disable_long(void *u1, unsigned long ip, unsigned long parent_ip) { u64 ts = this_cpu_read(irq_disabled_ts); if (!ts) return; this_cpu_write(irq_disabled_ts, 0); ts = sched_clock() - ts; if (ts > sysctl_irqsoff_tracing_threshold_ns) { trace_irq_disable_long(ts, ip, parent_ip, CALLER_ADDR4, CALLER_ADDR5); if (sysctl_irqsoff_dmesg_output_enabled == IRQSOFF_SENTINEL) printk_deferred("irqs off exceeds thresh delta=%llu C:(%ps<-%ps<-%ps<-%ps)\n", ts, (void *)CALLER_ADDR2, (void *)CALLER_ADDR3, (void *)CALLER_ADDR4, (void *)CALLER_ADDR5); } if (sysctl_irqsoff_crash_sentinel_value == IRQSOFF_SENTINEL && ts > sysctl_irqsoff_crash_threshold_ns) { printk_deferred("delta=%llu(ns) > crash_threshold=%u(ns) Task=%s\n", ts, sysctl_irqsoff_crash_threshold_ns, current->comm); BUG_ON(1); } } static void note_preempt_disable(void *u1, unsigned long u2, unsigned long u3) { struct preempt_store *ps = &per_cpu(the_ps, raw_smp_processor_id()); ps->ts = sched_clock(); ps->pid = current->pid; ps->ncsw = current->nvcsw + current->nivcsw; } static void test_preempt_disable_long(void *u1, unsigned long ip, unsigned long parent_ip) { struct preempt_store *ps = &per_cpu(the_ps, raw_smp_processor_id()); u64 delta = 0; if (!ps->ts) return; /* * schedule() calls __schedule() with preemption disabled. * if we had entered idle and exiting idle now, we think * preemption is disabled the whole time. Detect this by * checking if the preemption is disabled across the same * task. There is a possiblity that the same task is scheduled * after idle. To rule out this possibility, compare the * context switch count also. */ if (ps->pid == current->pid && (ps->ncsw == current->nvcsw + current->nivcsw)) delta = sched_clock() - ps->ts; ps->ts = 0; if (delta > sysctl_preemptoff_tracing_threshold_ns) trace_preempt_disable_long(delta, ip, parent_ip, CALLER_ADDR4, CALLER_ADDR5); } static struct ctl_table preemptirq_long_table[] = { { .procname = "preemptoff_tracing_threshold_ns", .data = &sysctl_preemptoff_tracing_threshold_ns, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "irqsoff_tracing_threshold_ns", .data = &sysctl_irqsoff_tracing_threshold_ns, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_douintvec_minmax, .extra1 = &half_million, .extra2 = &one_hundred_million, }, { .procname = "irqsoff_dmesg_output_enabled", .data = &sysctl_irqsoff_dmesg_output_enabled, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "irqsoff_crash_sentinel_value", .data = &sysctl_irqsoff_crash_sentinel_value, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_dointvec, }, { .procname = "irqsoff_crash_threshold_ns", .data = &sysctl_irqsoff_crash_threshold_ns, .maxlen = sizeof(unsigned int), .mode = 0644, .proc_handler = proc_douintvec_minmax, .extra1 = &one_million, .extra2 = &one_hundred_million, }, { } }; int preemptirq_long_init(void) { if (!register_sysctl("preemptirq", preemptirq_long_table)) { pr_err("Fail to register sysctl table\n"); return -EPERM; } register_trace_android_rvh_irqs_disable(note_irq_disable, NULL); register_trace_android_rvh_irqs_enable(test_irq_disable_long, NULL); register_trace_android_rvh_preempt_disable(note_preempt_disable, NULL); register_trace_android_rvh_preempt_enable(test_preempt_disable_long, NULL); return 0; }