// SPDX-License-Identifier: GPL-2.0 /* * OS Noise Tracer: computes the OS Noise suffered by a running thread. * Timerlat Tracer: measures the wakeup latency of a timer triggered IRQ and thread. * * Based on "hwlat_detector" tracer by: * Copyright (C) 2008-2009 Jon Masters, Red Hat, Inc. <[email protected]> * Copyright (C) 2013-2016 Steven Rostedt, Red Hat, Inc. <[email protected]> * With feedback from Clark Williams <[email protected]> * * And also based on the rtsl tracer presented on: * DE OLIVEIRA, Daniel Bristot, et al. Demystifying the real-time linux * scheduling latency. In: 32nd Euromicro Conference on Real-Time Systems * (ECRTS 2020). Schloss Dagstuhl-Leibniz-Zentrum fur Informatik, 2020. * * Copyright (C) 2021 Daniel Bristot de Oliveira, Red Hat, Inc. <[email protected]> */ #include <linux/kthread.h> #include <linux/tracefs.h> #include <linux/uaccess.h> #include <linux/cpumask.h> #include <linux/delay.h> #include <linux/sched/clock.h> #include <uapi/linux/sched/types.h> #include <linux/sched.h> #include "trace.h" #ifdef CONFIG_X86_LOCAL_APIC #include <asm/trace/irq_vectors.h> #undef TRACE_INCLUDE_PATH #undef TRACE_INCLUDE_FILE #endif /* CONFIG_X86_LOCAL_APIC */ #include <trace/events/irq.h> #include <trace/events/sched.h> #define CREATE_TRACE_POINTS #include <trace/events/osnoise.h> /* * Default values. */ #define BANNER … #define DEFAULT_SAMPLE_PERIOD … #define DEFAULT_SAMPLE_RUNTIME … #define DEFAULT_TIMERLAT_PERIOD … #define DEFAULT_TIMERLAT_PRIO … /* * osnoise/options entries. */ enum osnoise_options_index { … }; static const char * const osnoise_options_str[OSN_MAX] = …; #define OSN_DEFAULT_OPTIONS … static unsigned long osnoise_options = …; /* * trace_array of the enabled osnoise/timerlat instances. */ struct osnoise_instance { … }; static struct list_head osnoise_instances; static bool osnoise_has_registered_instances(void) { … } /* * osnoise_instance_registered - check if a tr is already registered */ static int osnoise_instance_registered(struct trace_array *tr) { … } /* * osnoise_register_instance - register a new trace instance * * Register a trace_array *tr in the list of instances running * osnoise/timerlat tracers. */ static int osnoise_register_instance(struct trace_array *tr) { … } /* * osnoise_unregister_instance - unregister a registered trace instance * * Remove the trace_array *tr from the list of instances running * osnoise/timerlat tracers. */ static void osnoise_unregister_instance(struct trace_array *tr) { … } /* * NMI runtime info. */ struct osn_nmi { … }; /* * IRQ runtime info. */ struct osn_irq { … }; #define IRQ_CONTEXT … #define THREAD_CONTEXT … #define THREAD_URET … /* * sofirq runtime info. */ struct osn_softirq { … }; /* * thread runtime info. */ struct osn_thread { … }; /* * Runtime information: this structure saves the runtime information used by * one sampling thread. */ struct osnoise_variables { … }; /* * Per-cpu runtime information. */ static DEFINE_PER_CPU(struct osnoise_variables, per_cpu_osnoise_var); /* * this_cpu_osn_var - Return the per-cpu osnoise_variables on its relative CPU */ static inline struct osnoise_variables *this_cpu_osn_var(void) { … } #ifdef CONFIG_TIMERLAT_TRACER /* * Runtime information for the timer mode. */ struct timerlat_variables { … }; static DEFINE_PER_CPU(struct timerlat_variables, per_cpu_timerlat_var); /* * this_cpu_tmr_var - Return the per-cpu timerlat_variables on its relative CPU */ static inline struct timerlat_variables *this_cpu_tmr_var(void) { … } /* * tlat_var_reset - Reset the values of the given timerlat_variables */ static inline void tlat_var_reset(void) { … } #else /* CONFIG_TIMERLAT_TRACER */ #define tlat_var_reset … #endif /* CONFIG_TIMERLAT_TRACER */ /* * osn_var_reset - Reset the values of the given osnoise_variables */ static inline void osn_var_reset(void) { … } /* * osn_var_reset_all - Reset the value of all per-cpu osnoise_variables */ static inline void osn_var_reset_all(void) { … } /* * Tells NMIs to call back to the osnoise tracer to record timestamps. */ bool trace_osnoise_callback_enabled; /* * osnoise sample structure definition. Used to store the statistics of a * sample run. */ struct osnoise_sample { … }; #ifdef CONFIG_TIMERLAT_TRACER /* * timerlat sample structure definition. Used to store the statistics of * a sample run. */ struct timerlat_sample { … }; #endif /* * Protect the interface. */ static struct mutex interface_lock; /* * Tracer data. */ static struct osnoise_data { … } osnoise_data = …; #ifdef CONFIG_TIMERLAT_TRACER static inline bool timerlat_enabled(void) { … } static inline int timerlat_softirq_exit(struct osnoise_variables *osn_var) { … } static inline int timerlat_thread_exit(struct osnoise_variables *osn_var) { … } #else /* CONFIG_TIMERLAT_TRACER */ static inline bool timerlat_enabled(void) { return false; } static inline int timerlat_softirq_exit(struct osnoise_variables *osn_var) { return 1; } static inline int timerlat_thread_exit(struct osnoise_variables *osn_var) { return 1; } #endif #ifdef CONFIG_PREEMPT_RT /* * Print the osnoise header info. */ static void print_osnoise_headers(struct seq_file *s) { if (osnoise_data.tainted) seq_puts(s, "# osnoise is tainted!\n"); seq_puts(s, "# _-------=> irqs-off\n"); seq_puts(s, "# / _------=> need-resched\n"); seq_puts(s, "# | / _-----=> need-resched-lazy\n"); seq_puts(s, "# || / _----=> hardirq/softirq\n"); seq_puts(s, "# ||| / _---=> preempt-depth\n"); seq_puts(s, "# |||| / _--=> preempt-lazy-depth\n"); seq_puts(s, "# ||||| / _-=> migrate-disable\n"); seq_puts(s, "# |||||| / "); seq_puts(s, " MAX\n"); seq_puts(s, "# ||||| / "); seq_puts(s, " SINGLE Interference counters:\n"); seq_puts(s, "# ||||||| RUNTIME "); seq_puts(s, " NOISE %% OF CPU NOISE +-----------------------------+\n"); seq_puts(s, "# TASK-PID CPU# ||||||| TIMESTAMP IN US "); seq_puts(s, " IN US AVAILABLE IN US HW NMI IRQ SIRQ THREAD\n"); seq_puts(s, "# | | | ||||||| | | "); seq_puts(s, " | | | | | | | |\n"); } #else /* CONFIG_PREEMPT_RT */ static void print_osnoise_headers(struct seq_file *s) { … } #endif /* CONFIG_PREEMPT_RT */ /* * osnoise_taint - report an osnoise error. */ #define osnoise_taint(msg) … /* * Record an osnoise_sample into the tracer buffer. */ static void __trace_osnoise_sample(struct osnoise_sample *sample, struct trace_buffer *buffer) { … } /* * Record an osnoise_sample on all osnoise instances. */ static void trace_osnoise_sample(struct osnoise_sample *sample) { … } #ifdef CONFIG_TIMERLAT_TRACER /* * Print the timerlat header info. */ #ifdef CONFIG_PREEMPT_RT static void print_timerlat_headers(struct seq_file *s) { seq_puts(s, "# _-------=> irqs-off\n"); seq_puts(s, "# / _------=> need-resched\n"); seq_puts(s, "# | / _-----=> need-resched-lazy\n"); seq_puts(s, "# || / _----=> hardirq/softirq\n"); seq_puts(s, "# ||| / _---=> preempt-depth\n"); seq_puts(s, "# |||| / _--=> preempt-lazy-depth\n"); seq_puts(s, "# ||||| / _-=> migrate-disable\n"); seq_puts(s, "# |||||| /\n"); seq_puts(s, "# ||||||| ACTIVATION\n"); seq_puts(s, "# TASK-PID CPU# ||||||| TIMESTAMP ID "); seq_puts(s, " CONTEXT LATENCY\n"); seq_puts(s, "# | | | ||||||| | | "); seq_puts(s, " | |\n"); } #else /* CONFIG_PREEMPT_RT */ static void print_timerlat_headers(struct seq_file *s) { … } #endif /* CONFIG_PREEMPT_RT */ static void __trace_timerlat_sample(struct timerlat_sample *sample, struct trace_buffer *buffer) { … } /* * Record an timerlat_sample into the tracer buffer. */ static void trace_timerlat_sample(struct timerlat_sample *sample) { … } #ifdef CONFIG_STACKTRACE #define MAX_CALLS … /* * Stack trace will take place only at IRQ level, so, no need * to control nesting here. */ struct trace_stack { … }; static DEFINE_PER_CPU(struct trace_stack, trace_stack); /* * timerlat_save_stack - save a stack trace without printing * * Save the current stack trace without printing. The * stack will be printed later, after the end of the measurement. */ static void timerlat_save_stack(int skip) { … } static void __timerlat_dump_stack(struct trace_buffer *buffer, struct trace_stack *fstack, unsigned int size) { … } /* * timerlat_dump_stack - dump a stack trace previously saved */ static void timerlat_dump_stack(u64 latency) { … } #else /* CONFIG_STACKTRACE */ #define timerlat_dump_stack … #define timerlat_save_stack … #endif /* CONFIG_STACKTRACE */ #endif /* CONFIG_TIMERLAT_TRACER */ /* * Macros to encapsulate the time capturing infrastructure. */ #define time_get() … #define time_to_us(x) … #define time_sub(a, b) … /* * cond_move_irq_delta_start - Forward the delta_start of a running IRQ * * If an IRQ is preempted by an NMI, its delta_start is pushed forward * to discount the NMI interference. * * See get_int_safe_duration(). */ static inline void cond_move_irq_delta_start(struct osnoise_variables *osn_var, u64 duration) { … } #ifndef CONFIG_PREEMPT_RT /* * cond_move_softirq_delta_start - Forward the delta_start of a running softirq. * * If a softirq is preempted by an IRQ or NMI, its delta_start is pushed * forward to discount the interference. * * See get_int_safe_duration(). */ static inline void cond_move_softirq_delta_start(struct osnoise_variables *osn_var, u64 duration) { … } #else /* CONFIG_PREEMPT_RT */ #define cond_move_softirq_delta_start … #endif /* * cond_move_thread_delta_start - Forward the delta_start of a running thread * * If a noisy thread is preempted by an softirq, IRQ or NMI, its delta_start * is pushed forward to discount the interference. * * See get_int_safe_duration(). */ static inline void cond_move_thread_delta_start(struct osnoise_variables *osn_var, u64 duration) { … } /* * get_int_safe_duration - Get the duration of a window * * The irq, softirq and thread varaibles need to have its duration without * the interference from higher priority interrupts. Instead of keeping a * variable to discount the interrupt interference from these variables, the * starting time of these variables are pushed forward with the interrupt's * duration. In this way, a single variable is used to: * * - Know if a given window is being measured. * - Account its duration. * - Discount the interference. * * To avoid getting inconsistent values, e.g.,: * * now = time_get() * ---> interrupt! * delta_start -= int duration; * <--- * duration = now - delta_start; * * result: negative duration if the variable duration before the * interrupt was smaller than the interrupt execution. * * A counter of interrupts is used. If the counter increased, try * to capture an interference safe duration. */ static inline s64 get_int_safe_duration(struct osnoise_variables *osn_var, u64 *delta_start) { … } /* * * set_int_safe_time - Save the current time on *time, aware of interference * * Get the time, taking into consideration a possible interference from * higher priority interrupts. * * See get_int_safe_duration() for an explanation. */ static u64 set_int_safe_time(struct osnoise_variables *osn_var, u64 *time) { … } #ifdef CONFIG_TIMERLAT_TRACER /* * copy_int_safe_time - Copy *src into *desc aware of interference */ static u64 copy_int_safe_time(struct osnoise_variables *osn_var, u64 *dst, u64 *src) { … } #endif /* CONFIG_TIMERLAT_TRACER */ /* * trace_osnoise_callback - NMI entry/exit callback * * This function is called at the entry and exit NMI code. The bool enter * distinguishes between either case. This function is used to note a NMI * occurrence, compute the noise caused by the NMI, and to remove the noise * it is potentially causing on other interference variables. */ void trace_osnoise_callback(bool enter) { … } /* * osnoise_trace_irq_entry - Note the starting of an IRQ * * Save the starting time of an IRQ. As IRQs are non-preemptive to other IRQs, * it is safe to use a single variable (ons_var->irq) to save the statistics. * The arrival_time is used to report... the arrival time. The delta_start * is used to compute the duration at the IRQ exit handler. See * cond_move_irq_delta_start(). */ void osnoise_trace_irq_entry(int id) { … } /* * osnoise_irq_exit - Note the end of an IRQ, sava data and trace * * Computes the duration of the IRQ noise, and trace it. Also discounts the * interference from other sources of noise could be currently being accounted. */ void osnoise_trace_irq_exit(int id, const char *desc) { … } /* * trace_irqentry_callback - Callback to the irq:irq_entry traceevent * * Used to note the starting of an IRQ occurece. */ static void trace_irqentry_callback(void *data, int irq, struct irqaction *action) { … } /* * trace_irqexit_callback - Callback to the irq:irq_exit traceevent * * Used to note the end of an IRQ occurece. */ static void trace_irqexit_callback(void *data, int irq, struct irqaction *action, int ret) { … } /* * arch specific register function. */ int __weak osnoise_arch_register(void) { … } /* * arch specific unregister function. */ void __weak osnoise_arch_unregister(void) { … } /* * hook_irq_events - Hook IRQ handling events * * This function hooks the IRQ related callbacks to the respective trace * events. */ static int hook_irq_events(void) { … } /* * unhook_irq_events - Unhook IRQ handling events * * This function unhooks the IRQ related callbacks to the respective trace * events. */ static void unhook_irq_events(void) { … } #ifndef CONFIG_PREEMPT_RT /* * trace_softirq_entry_callback - Note the starting of a softirq * * Save the starting time of a softirq. As softirqs are non-preemptive to * other softirqs, it is safe to use a single variable (ons_var->softirq) * to save the statistics. The arrival_time is used to report... the * arrival time. The delta_start is used to compute the duration at the * softirq exit handler. See cond_move_softirq_delta_start(). */ static void trace_softirq_entry_callback(void *data, unsigned int vec_nr) { … } /* * trace_softirq_exit_callback - Note the end of an softirq * * Computes the duration of the softirq noise, and trace it. Also discounts the * interference from other sources of noise could be currently being accounted. */ static void trace_softirq_exit_callback(void *data, unsigned int vec_nr) { … } /* * hook_softirq_events - Hook softirq handling events * * This function hooks the softirq related callbacks to the respective trace * events. */ static int hook_softirq_events(void) { … } /* * unhook_softirq_events - Unhook softirq handling events * * This function hooks the softirq related callbacks to the respective trace * events. */ static void unhook_softirq_events(void) { … } #else /* CONFIG_PREEMPT_RT */ /* * softirq are threads on the PREEMPT_RT mode. */ static int hook_softirq_events(void) { return 0; } static void unhook_softirq_events(void) { } #endif /* * thread_entry - Record the starting of a thread noise window * * It saves the context switch time for a noisy thread, and increments * the interference counters. */ static void thread_entry(struct osnoise_variables *osn_var, struct task_struct *t) { … } /* * thread_exit - Report the end of a thread noise window * * It computes the total noise from a thread, tracing if needed. */ static void thread_exit(struct osnoise_variables *osn_var, struct task_struct *t) { … } #ifdef CONFIG_TIMERLAT_TRACER /* * osnoise_stop_exception - Stop tracing and the tracer. */ static __always_inline void osnoise_stop_exception(char *msg, int cpu) { … } /* * trace_sched_migrate_callback - sched:sched_migrate_task trace event handler * * his function is hooked to the sched:sched_migrate_task trace event, and monitors * timerlat user-space thread migration. */ static void trace_sched_migrate_callback(void *data, struct task_struct *p, int dest_cpu) { … } static int register_migration_monitor(void) { … } static void unregister_migration_monitor(void) { … } #else static int register_migration_monitor(void) { return 0; } static void unregister_migration_monitor(void) {} #endif /* * trace_sched_switch - sched:sched_switch trace event handler * * This function is hooked to the sched:sched_switch trace event, and it is * used to record the beginning and to report the end of a thread noise window. */ static void trace_sched_switch_callback(void *data, bool preempt, struct task_struct *p, struct task_struct *n, unsigned int prev_state) { … } /* * hook_thread_events - Hook the instrumentation for thread noise * * Hook the osnoise tracer callbacks to handle the noise from other * threads on the necessary kernel events. */ static int hook_thread_events(void) { … } /* * unhook_thread_events - unhook the instrumentation for thread noise * * Unook the osnoise tracer callbacks to handle the noise from other * threads on the necessary kernel events. */ static void unhook_thread_events(void) { … } /* * save_osn_sample_stats - Save the osnoise_sample statistics * * Save the osnoise_sample statistics before the sampling phase. These * values will be used later to compute the diff betwneen the statistics * before and after the osnoise sampling. */ static void save_osn_sample_stats(struct osnoise_variables *osn_var, struct osnoise_sample *s) { … } /* * diff_osn_sample_stats - Compute the osnoise_sample statistics * * After a sample period, compute the difference on the osnoise_sample * statistics. The struct osnoise_sample *s contains the statistics saved via * save_osn_sample_stats() before the osnoise sampling. */ static void diff_osn_sample_stats(struct osnoise_variables *osn_var, struct osnoise_sample *s) { … } /* * osnoise_stop_tracing - Stop tracing and the tracer. */ static __always_inline void osnoise_stop_tracing(void) { … } /* * osnoise_has_tracing_on - Check if there is at least one instance on */ static __always_inline int osnoise_has_tracing_on(void) { … } /* * notify_new_max_latency - Notify a new max latency via fsnotify interface. */ static void notify_new_max_latency(u64 latency) { … } /* * run_osnoise - Sample the time and look for osnoise * * Used to capture the time, looking for potential osnoise latency repeatedly. * Different from hwlat_detector, it is called with preemption and interrupts * enabled. This allows irqs, softirqs and threads to run, interfering on the * osnoise sampling thread, as they would do with a regular thread. */ static int run_osnoise(void) { … } static struct cpumask osnoise_cpumask; static struct cpumask save_cpumask; /* * osnoise_sleep - sleep until the next period */ static void osnoise_sleep(bool skip_period) { … } /* * osnoise_migration_pending - checks if the task needs to migrate * * osnoise/timerlat threads are per-cpu. If there is a pending request to * migrate the thread away from the current CPU, something bad has happened. * Play the good citizen and leave. * * Returns 0 if it is safe to continue, 1 otherwise. */ static inline int osnoise_migration_pending(void) { … } /* * osnoise_main - The osnoise detection kernel thread * * Calls run_osnoise() function to measure the osnoise for the configured runtime, * every period. */ static int osnoise_main(void *data) { … } #ifdef CONFIG_TIMERLAT_TRACER /* * timerlat_irq - hrtimer handler for timerlat. */ static enum hrtimer_restart timerlat_irq(struct hrtimer *timer) { … } /* * wait_next_period - Wait for the next period for timerlat */ static int wait_next_period(struct timerlat_variables *tlat) { … } /* * timerlat_main- Timerlat main */ static int timerlat_main(void *data) { … } #else /* CONFIG_TIMERLAT_TRACER */ static int timerlat_main(void *data) { return 0; } #endif /* CONFIG_TIMERLAT_TRACER */ /* * stop_kthread - stop a workload thread */ static void stop_kthread(unsigned int cpu) { … } /* * stop_per_cpu_kthread - Stop per-cpu threads * * Stop the osnoise sampling htread. Use this on unload and at system * shutdown. */ static void stop_per_cpu_kthreads(void) { … } /* * start_kthread - Start a workload tread */ static int start_kthread(unsigned int cpu) { … } /* * start_per_cpu_kthread - Kick off per-cpu osnoise sampling kthreads * * This starts the kernel thread that will look for osnoise on many * cpus. */ static int start_per_cpu_kthreads(void) { … } #ifdef CONFIG_HOTPLUG_CPU static void osnoise_hotplug_workfn(struct work_struct *dummy) { … } static DECLARE_WORK(osnoise_hotplug_work, osnoise_hotplug_workfn); /* * osnoise_cpu_init - CPU hotplug online callback function */ static int osnoise_cpu_init(unsigned int cpu) { … } /* * osnoise_cpu_die - CPU hotplug offline callback function */ static int osnoise_cpu_die(unsigned int cpu) { … } static void osnoise_init_hotplug_support(void) { … } #else /* CONFIG_HOTPLUG_CPU */ static void osnoise_init_hotplug_support(void) { return; } #endif /* CONFIG_HOTPLUG_CPU */ /* * seq file functions for the osnoise/options file. */ static void *s_options_start(struct seq_file *s, loff_t *pos) { … } static void *s_options_next(struct seq_file *s, void *v, loff_t *pos) { … } static int s_options_show(struct seq_file *s, void *v) { … } static void s_options_stop(struct seq_file *s, void *v) { … } static const struct seq_operations osnoise_options_seq_ops = …; static int osnoise_options_open(struct inode *inode, struct file *file) { return seq_open(file, &osnoise_options_seq_ops); }; /** * osnoise_options_write - Write function for "options" entry * @filp: The active open file structure * @ubuf: The user buffer that contains the value to write * @cnt: The maximum number of bytes to write to "file" * @ppos: The current position in @file * * Writing the option name sets the option, writing the "NO_" * prefix in front of the option name disables it. * * Writing "DEFAULTS" resets the option values to the default ones. */ static ssize_t osnoise_options_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { … } /* * osnoise_cpus_read - Read function for reading the "cpus" file * @filp: The active open file structure * @ubuf: The userspace provided buffer to read value into * @cnt: The maximum number of bytes to read * @ppos: The current "file" position * * Prints the "cpus" output into the user-provided buffer. */ static ssize_t osnoise_cpus_read(struct file *filp, char __user *ubuf, size_t count, loff_t *ppos) { … } /* * osnoise_cpus_write - Write function for "cpus" entry * @filp: The active open file structure * @ubuf: The user buffer that contains the value to write * @cnt: The maximum number of bytes to write to "file" * @ppos: The current position in @file * * This function provides a write implementation for the "cpus" * interface to the osnoise trace. By default, it lists all CPUs, * in this way, allowing osnoise threads to run on any online CPU * of the system. It serves to restrict the execution of osnoise to the * set of CPUs writing via this interface. Why not use "tracing_cpumask"? * Because the user might be interested in tracing what is running on * other CPUs. For instance, one might run osnoise in one HT CPU * while observing what is running on the sibling HT CPU. */ static ssize_t osnoise_cpus_write(struct file *filp, const char __user *ubuf, size_t count, loff_t *ppos) { … } #ifdef CONFIG_TIMERLAT_TRACER static int timerlat_fd_open(struct inode *inode, struct file *file) { struct osnoise_variables *osn_var; struct timerlat_variables *tlat; long cpu = (long) inode->i_cdev; mutex_lock(&interface_lock); /* * This file is accessible only if timerlat is enabled, and * NO_OSNOISE_WORKLOAD is set. */ if (!timerlat_enabled() || test_bit(OSN_WORKLOAD, &osnoise_options)) { mutex_unlock(&interface_lock); return -EINVAL; } migrate_disable(); osn_var = this_cpu_osn_var(); /* * The osn_var->pid holds the single access to this file. */ if (osn_var->pid) { mutex_unlock(&interface_lock); migrate_enable(); return -EBUSY; } /* * timerlat tracer is a per-cpu tracer. Check if the user-space too * is pinned to a single CPU. The tracer laters monitor if the task * migrates and then disables tracer if it does. However, it is * worth doing this basic acceptance test to avoid obviusly wrong * setup. */ if (current->nr_cpus_allowed > 1 || cpu != smp_processor_id()) { mutex_unlock(&interface_lock); migrate_enable(); return -EPERM; } /* * From now on, it is good to go. */ file->private_data = inode->i_cdev; get_task_struct(current); osn_var->kthread = current; osn_var->pid = current->pid; /* * Setup is done. */ mutex_unlock(&interface_lock); tlat = this_cpu_tmr_var(); tlat->count = 0; hrtimer_init(&tlat->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS_PINNED_HARD); tlat->timer.function = timerlat_irq; migrate_enable(); return 0; }; /* * timerlat_fd_read - Read function for "timerlat_fd" file * @file: The active open file structure * @ubuf: The userspace provided buffer to read value into * @cnt: The maximum number of bytes to read * @ppos: The current "file" position * * Prints 1 on timerlat, the number of interferences on osnoise, -1 on error. */ static ssize_t timerlat_fd_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) { … } static int timerlat_fd_release(struct inode *inode, struct file *file) { … } #endif /* * osnoise/runtime_us: cannot be greater than the period. */ static struct trace_min_max_param osnoise_runtime = …; /* * osnoise/period_us: cannot be smaller than the runtime. */ static struct trace_min_max_param osnoise_period = …; /* * osnoise/stop_tracing_us: no limit. */ static struct trace_min_max_param osnoise_stop_tracing_in = …; /* * osnoise/stop_tracing_total_us: no limit. */ static struct trace_min_max_param osnoise_stop_tracing_total = …; #ifdef CONFIG_TIMERLAT_TRACER /* * osnoise/print_stack: print the stacktrace of the IRQ handler if the total * latency is higher than val. */ static struct trace_min_max_param osnoise_print_stack = …; /* * osnoise/timerlat_period: min 100 us, max 1 s */ static u64 timerlat_min_period = …; static u64 timerlat_max_period = …; static struct trace_min_max_param timerlat_period = …; static const struct file_operations timerlat_fd_fops = …; #endif static const struct file_operations cpus_fops = …; static const struct file_operations osnoise_options_fops = …; #ifdef CONFIG_TIMERLAT_TRACER #ifdef CONFIG_STACKTRACE static int init_timerlat_stack_tracefs(struct dentry *top_dir) { … } #else /* CONFIG_STACKTRACE */ static int init_timerlat_stack_tracefs(struct dentry *top_dir) { return 0; } #endif /* CONFIG_STACKTRACE */ static int osnoise_create_cpu_timerlat_fd(struct dentry *top_dir) { … } /* * init_timerlat_tracefs - A function to initialize the timerlat interface files */ static int init_timerlat_tracefs(struct dentry *top_dir) { … } #else /* CONFIG_TIMERLAT_TRACER */ static int init_timerlat_tracefs(struct dentry *top_dir) { return 0; } #endif /* CONFIG_TIMERLAT_TRACER */ /* * init_tracefs - A function to initialize the tracefs interface files * * This function creates entries in tracefs for "osnoise" and "timerlat". * It creates these directories in the tracing directory, and within that * directory the use can change and view the configs. */ static int init_tracefs(void) { … } static int osnoise_hook_events(void) { … } static void osnoise_unhook_events(void) { … } /* * osnoise_workload_start - start the workload and hook to events */ static int osnoise_workload_start(void) { … } /* * osnoise_workload_stop - stop the workload and unhook the events */ static void osnoise_workload_stop(void) { … } static void osnoise_tracer_start(struct trace_array *tr) { … } static void osnoise_tracer_stop(struct trace_array *tr) { … } static int osnoise_tracer_init(struct trace_array *tr) { … } static void osnoise_tracer_reset(struct trace_array *tr) { … } static struct tracer osnoise_tracer __read_mostly = …; #ifdef CONFIG_TIMERLAT_TRACER static void timerlat_tracer_start(struct trace_array *tr) { … } static void timerlat_tracer_stop(struct trace_array *tr) { … } static int timerlat_tracer_init(struct trace_array *tr) { … } static void timerlat_tracer_reset(struct trace_array *tr) { … } static struct tracer timerlat_tracer __read_mostly = …; __init static int init_timerlat_tracer(void) { … } #else /* CONFIG_TIMERLAT_TRACER */ __init static int init_timerlat_tracer(void) { return 0; } #endif /* CONFIG_TIMERLAT_TRACER */ __init static int init_osnoise_tracer(void) { … } late_initcall(init_osnoise_tracer);