162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * trace_hwlat.c - A simple Hardware Latency detector. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Use this tracer to detect large system latencies induced by the behavior of 662306a36Sopenharmony_ci * certain underlying system hardware or firmware, independent of Linux itself. 762306a36Sopenharmony_ci * The code was developed originally to detect the presence of SMIs on Intel 862306a36Sopenharmony_ci * and AMD systems, although there is no dependency upon x86 herein. 962306a36Sopenharmony_ci * 1062306a36Sopenharmony_ci * The classical example usage of this tracer is in detecting the presence of 1162306a36Sopenharmony_ci * SMIs or System Management Interrupts on Intel and AMD systems. An SMI is a 1262306a36Sopenharmony_ci * somewhat special form of hardware interrupt spawned from earlier CPU debug 1362306a36Sopenharmony_ci * modes in which the (BIOS/EFI/etc.) firmware arranges for the South Bridge 1462306a36Sopenharmony_ci * LPC (or other device) to generate a special interrupt under certain 1562306a36Sopenharmony_ci * circumstances, for example, upon expiration of a special SMI timer device, 1662306a36Sopenharmony_ci * due to certain external thermal readings, on certain I/O address accesses, 1762306a36Sopenharmony_ci * and other situations. An SMI hits a special CPU pin, triggers a special 1862306a36Sopenharmony_ci * SMI mode (complete with special memory map), and the OS is unaware. 1962306a36Sopenharmony_ci * 2062306a36Sopenharmony_ci * Although certain hardware-inducing latencies are necessary (for example, 2162306a36Sopenharmony_ci * a modern system often requires an SMI handler for correct thermal control 2262306a36Sopenharmony_ci * and remote management) they can wreak havoc upon any OS-level performance 2362306a36Sopenharmony_ci * guarantees toward low-latency, especially when the OS is not even made 2462306a36Sopenharmony_ci * aware of the presence of these interrupts. For this reason, we need a 2562306a36Sopenharmony_ci * somewhat brute force mechanism to detect these interrupts. In this case, 2662306a36Sopenharmony_ci * we do it by hogging all of the CPU(s) for configurable timer intervals, 2762306a36Sopenharmony_ci * sampling the built-in CPU timer, looking for discontiguous readings. 2862306a36Sopenharmony_ci * 2962306a36Sopenharmony_ci * WARNING: This implementation necessarily introduces latencies. Therefore, 3062306a36Sopenharmony_ci * you should NEVER use this tracer while running in a production 3162306a36Sopenharmony_ci * environment requiring any kind of low-latency performance 3262306a36Sopenharmony_ci * guarantee(s). 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Copyright (C) 2008-2009 Jon Masters, Red Hat, Inc. <jcm@redhat.com> 3562306a36Sopenharmony_ci * Copyright (C) 2013-2016 Steven Rostedt, Red Hat, Inc. <srostedt@redhat.com> 3662306a36Sopenharmony_ci * 3762306a36Sopenharmony_ci * Includes useful feedback from Clark Williams <williams@redhat.com> 3862306a36Sopenharmony_ci * 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_ci#include <linux/kthread.h> 4162306a36Sopenharmony_ci#include <linux/tracefs.h> 4262306a36Sopenharmony_ci#include <linux/uaccess.h> 4362306a36Sopenharmony_ci#include <linux/cpumask.h> 4462306a36Sopenharmony_ci#include <linux/delay.h> 4562306a36Sopenharmony_ci#include <linux/sched/clock.h> 4662306a36Sopenharmony_ci#include "trace.h" 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic struct trace_array *hwlat_trace; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci#define U64STR_SIZE 22 /* 20 digits max */ 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci#define BANNER "hwlat_detector: " 5362306a36Sopenharmony_ci#define DEFAULT_SAMPLE_WINDOW 1000000 /* 1s */ 5462306a36Sopenharmony_ci#define DEFAULT_SAMPLE_WIDTH 500000 /* 0.5s */ 5562306a36Sopenharmony_ci#define DEFAULT_LAT_THRESHOLD 10 /* 10us */ 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_cistatic struct dentry *hwlat_sample_width; /* sample width us */ 5862306a36Sopenharmony_cistatic struct dentry *hwlat_sample_window; /* sample window us */ 5962306a36Sopenharmony_cistatic struct dentry *hwlat_thread_mode; /* hwlat thread mode */ 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cienum { 6262306a36Sopenharmony_ci MODE_NONE = 0, 6362306a36Sopenharmony_ci MODE_ROUND_ROBIN, 6462306a36Sopenharmony_ci MODE_PER_CPU, 6562306a36Sopenharmony_ci MODE_MAX 6662306a36Sopenharmony_ci}; 6762306a36Sopenharmony_cistatic char *thread_mode_str[] = { "none", "round-robin", "per-cpu" }; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci/* Save the previous tracing_thresh value */ 7062306a36Sopenharmony_cistatic unsigned long save_tracing_thresh; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci/* runtime kthread data */ 7362306a36Sopenharmony_cistruct hwlat_kthread_data { 7462306a36Sopenharmony_ci struct task_struct *kthread; 7562306a36Sopenharmony_ci /* NMI timestamp counters */ 7662306a36Sopenharmony_ci u64 nmi_ts_start; 7762306a36Sopenharmony_ci u64 nmi_total_ts; 7862306a36Sopenharmony_ci int nmi_count; 7962306a36Sopenharmony_ci int nmi_cpu; 8062306a36Sopenharmony_ci}; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic struct hwlat_kthread_data hwlat_single_cpu_data; 8362306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct hwlat_kthread_data, hwlat_per_cpu_data); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci/* Tells NMIs to call back to the hwlat tracer to record timestamps */ 8662306a36Sopenharmony_cibool trace_hwlat_callback_enabled; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* If the user changed threshold, remember it */ 8962306a36Sopenharmony_cistatic u64 last_tracing_thresh = DEFAULT_LAT_THRESHOLD * NSEC_PER_USEC; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci/* Individual latency samples are stored here when detected. */ 9262306a36Sopenharmony_cistruct hwlat_sample { 9362306a36Sopenharmony_ci u64 seqnum; /* unique sequence */ 9462306a36Sopenharmony_ci u64 duration; /* delta */ 9562306a36Sopenharmony_ci u64 outer_duration; /* delta (outer loop) */ 9662306a36Sopenharmony_ci u64 nmi_total_ts; /* Total time spent in NMIs */ 9762306a36Sopenharmony_ci struct timespec64 timestamp; /* wall time */ 9862306a36Sopenharmony_ci int nmi_count; /* # NMIs during this sample */ 9962306a36Sopenharmony_ci int count; /* # of iterations over thresh */ 10062306a36Sopenharmony_ci}; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci/* keep the global state somewhere. */ 10362306a36Sopenharmony_cistatic struct hwlat_data { 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci struct mutex lock; /* protect changes */ 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci u64 count; /* total since reset */ 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci u64 sample_window; /* total sampling window (on+off) */ 11062306a36Sopenharmony_ci u64 sample_width; /* active sampling portion of window */ 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci int thread_mode; /* thread mode */ 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci} hwlat_data = { 11562306a36Sopenharmony_ci .sample_window = DEFAULT_SAMPLE_WINDOW, 11662306a36Sopenharmony_ci .sample_width = DEFAULT_SAMPLE_WIDTH, 11762306a36Sopenharmony_ci .thread_mode = MODE_ROUND_ROBIN 11862306a36Sopenharmony_ci}; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_cistatic struct hwlat_kthread_data *get_cpu_data(void) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci if (hwlat_data.thread_mode == MODE_PER_CPU) 12362306a36Sopenharmony_ci return this_cpu_ptr(&hwlat_per_cpu_data); 12462306a36Sopenharmony_ci else 12562306a36Sopenharmony_ci return &hwlat_single_cpu_data; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic bool hwlat_busy; 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_cistatic void trace_hwlat_sample(struct hwlat_sample *sample) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; 13362306a36Sopenharmony_ci struct trace_event_call *call = &event_hwlat; 13462306a36Sopenharmony_ci struct trace_buffer *buffer = tr->array_buffer.buffer; 13562306a36Sopenharmony_ci struct ring_buffer_event *event; 13662306a36Sopenharmony_ci struct hwlat_entry *entry; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci event = trace_buffer_lock_reserve(buffer, TRACE_HWLAT, sizeof(*entry), 13962306a36Sopenharmony_ci tracing_gen_ctx()); 14062306a36Sopenharmony_ci if (!event) 14162306a36Sopenharmony_ci return; 14262306a36Sopenharmony_ci entry = ring_buffer_event_data(event); 14362306a36Sopenharmony_ci entry->seqnum = sample->seqnum; 14462306a36Sopenharmony_ci entry->duration = sample->duration; 14562306a36Sopenharmony_ci entry->outer_duration = sample->outer_duration; 14662306a36Sopenharmony_ci entry->timestamp = sample->timestamp; 14762306a36Sopenharmony_ci entry->nmi_total_ts = sample->nmi_total_ts; 14862306a36Sopenharmony_ci entry->nmi_count = sample->nmi_count; 14962306a36Sopenharmony_ci entry->count = sample->count; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (!call_filter_check_discard(call, entry, buffer, event)) 15262306a36Sopenharmony_ci trace_buffer_unlock_commit_nostack(buffer, event); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci/* Macros to encapsulate the time capturing infrastructure */ 15662306a36Sopenharmony_ci#define time_type u64 15762306a36Sopenharmony_ci#define time_get() trace_clock_local() 15862306a36Sopenharmony_ci#define time_to_us(x) div_u64(x, 1000) 15962306a36Sopenharmony_ci#define time_sub(a, b) ((a) - (b)) 16062306a36Sopenharmony_ci#define init_time(a, b) (a = b) 16162306a36Sopenharmony_ci#define time_u64(a) a 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_civoid trace_hwlat_callback(bool enter) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct hwlat_kthread_data *kdata = get_cpu_data(); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci if (!kdata->kthread) 16862306a36Sopenharmony_ci return; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci /* 17162306a36Sopenharmony_ci * Currently trace_clock_local() calls sched_clock() and the 17262306a36Sopenharmony_ci * generic version is not NMI safe. 17362306a36Sopenharmony_ci */ 17462306a36Sopenharmony_ci if (!IS_ENABLED(CONFIG_GENERIC_SCHED_CLOCK)) { 17562306a36Sopenharmony_ci if (enter) 17662306a36Sopenharmony_ci kdata->nmi_ts_start = time_get(); 17762306a36Sopenharmony_ci else 17862306a36Sopenharmony_ci kdata->nmi_total_ts += time_get() - kdata->nmi_ts_start; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (enter) 18262306a36Sopenharmony_ci kdata->nmi_count++; 18362306a36Sopenharmony_ci} 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci/* 18662306a36Sopenharmony_ci * hwlat_err - report a hwlat error. 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_ci#define hwlat_err(msg) ({ \ 18962306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; \ 19062306a36Sopenharmony_ci \ 19162306a36Sopenharmony_ci trace_array_printk_buf(tr->array_buffer.buffer, _THIS_IP_, msg); \ 19262306a36Sopenharmony_ci}) 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci/** 19562306a36Sopenharmony_ci * get_sample - sample the CPU TSC and look for likely hardware latencies 19662306a36Sopenharmony_ci * 19762306a36Sopenharmony_ci * Used to repeatedly capture the CPU TSC (or similar), looking for potential 19862306a36Sopenharmony_ci * hardware-induced latency. Called with interrupts disabled and with 19962306a36Sopenharmony_ci * hwlat_data.lock held. 20062306a36Sopenharmony_ci */ 20162306a36Sopenharmony_cistatic int get_sample(void) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci struct hwlat_kthread_data *kdata = get_cpu_data(); 20462306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; 20562306a36Sopenharmony_ci struct hwlat_sample s; 20662306a36Sopenharmony_ci time_type start, t1, t2, last_t2; 20762306a36Sopenharmony_ci s64 diff, outer_diff, total, last_total = 0; 20862306a36Sopenharmony_ci u64 sample = 0; 20962306a36Sopenharmony_ci u64 thresh = tracing_thresh; 21062306a36Sopenharmony_ci u64 outer_sample = 0; 21162306a36Sopenharmony_ci int ret = -1; 21262306a36Sopenharmony_ci unsigned int count = 0; 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci do_div(thresh, NSEC_PER_USEC); /* modifies interval value */ 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci kdata->nmi_total_ts = 0; 21762306a36Sopenharmony_ci kdata->nmi_count = 0; 21862306a36Sopenharmony_ci /* Make sure NMIs see this first */ 21962306a36Sopenharmony_ci barrier(); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci trace_hwlat_callback_enabled = true; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci init_time(last_t2, 0); 22462306a36Sopenharmony_ci start = time_get(); /* start timestamp */ 22562306a36Sopenharmony_ci outer_diff = 0; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci do { 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci t1 = time_get(); /* we'll look for a discontinuity */ 23062306a36Sopenharmony_ci t2 = time_get(); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (time_u64(last_t2)) { 23362306a36Sopenharmony_ci /* Check the delta from outer loop (t2 to next t1) */ 23462306a36Sopenharmony_ci outer_diff = time_to_us(time_sub(t1, last_t2)); 23562306a36Sopenharmony_ci /* This shouldn't happen */ 23662306a36Sopenharmony_ci if (outer_diff < 0) { 23762306a36Sopenharmony_ci hwlat_err(BANNER "time running backwards\n"); 23862306a36Sopenharmony_ci goto out; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci if (outer_diff > outer_sample) 24162306a36Sopenharmony_ci outer_sample = outer_diff; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci last_t2 = t2; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci total = time_to_us(time_sub(t2, start)); /* sample width */ 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci /* Check for possible overflows */ 24862306a36Sopenharmony_ci if (total < last_total) { 24962306a36Sopenharmony_ci hwlat_err("Time total overflowed\n"); 25062306a36Sopenharmony_ci break; 25162306a36Sopenharmony_ci } 25262306a36Sopenharmony_ci last_total = total; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci /* This checks the inner loop (t1 to t2) */ 25562306a36Sopenharmony_ci diff = time_to_us(time_sub(t2, t1)); /* current diff */ 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (diff > thresh || outer_diff > thresh) { 25862306a36Sopenharmony_ci if (!count) 25962306a36Sopenharmony_ci ktime_get_real_ts64(&s.timestamp); 26062306a36Sopenharmony_ci count++; 26162306a36Sopenharmony_ci } 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci /* This shouldn't happen */ 26462306a36Sopenharmony_ci if (diff < 0) { 26562306a36Sopenharmony_ci hwlat_err(BANNER "time running backwards\n"); 26662306a36Sopenharmony_ci goto out; 26762306a36Sopenharmony_ci } 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci if (diff > sample) 27062306a36Sopenharmony_ci sample = diff; /* only want highest value */ 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci } while (total <= hwlat_data.sample_width); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci barrier(); /* finish the above in the view for NMIs */ 27562306a36Sopenharmony_ci trace_hwlat_callback_enabled = false; 27662306a36Sopenharmony_ci barrier(); /* Make sure nmi_total_ts is no longer updated */ 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci ret = 0; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci /* If we exceed the threshold value, we have found a hardware latency */ 28162306a36Sopenharmony_ci if (sample > thresh || outer_sample > thresh) { 28262306a36Sopenharmony_ci u64 latency; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci ret = 1; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* We read in microseconds */ 28762306a36Sopenharmony_ci if (kdata->nmi_total_ts) 28862306a36Sopenharmony_ci do_div(kdata->nmi_total_ts, NSEC_PER_USEC); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci hwlat_data.count++; 29162306a36Sopenharmony_ci s.seqnum = hwlat_data.count; 29262306a36Sopenharmony_ci s.duration = sample; 29362306a36Sopenharmony_ci s.outer_duration = outer_sample; 29462306a36Sopenharmony_ci s.nmi_total_ts = kdata->nmi_total_ts; 29562306a36Sopenharmony_ci s.nmi_count = kdata->nmi_count; 29662306a36Sopenharmony_ci s.count = count; 29762306a36Sopenharmony_ci trace_hwlat_sample(&s); 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci latency = max(sample, outer_sample); 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci /* Keep a running maximum ever recorded hardware latency */ 30262306a36Sopenharmony_ci if (latency > tr->max_latency) { 30362306a36Sopenharmony_ci tr->max_latency = latency; 30462306a36Sopenharmony_ci latency_fsnotify(tr); 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci } 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ciout: 30962306a36Sopenharmony_ci return ret; 31062306a36Sopenharmony_ci} 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_cistatic struct cpumask save_cpumask; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_cistatic void move_to_next_cpu(void) 31562306a36Sopenharmony_ci{ 31662306a36Sopenharmony_ci struct cpumask *current_mask = &save_cpumask; 31762306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; 31862306a36Sopenharmony_ci int next_cpu; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* 32162306a36Sopenharmony_ci * If for some reason the user modifies the CPU affinity 32262306a36Sopenharmony_ci * of this thread, then stop migrating for the duration 32362306a36Sopenharmony_ci * of the current test. 32462306a36Sopenharmony_ci */ 32562306a36Sopenharmony_ci if (!cpumask_equal(current_mask, current->cpus_ptr)) 32662306a36Sopenharmony_ci goto change_mode; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci cpus_read_lock(); 32962306a36Sopenharmony_ci cpumask_and(current_mask, cpu_online_mask, tr->tracing_cpumask); 33062306a36Sopenharmony_ci next_cpu = cpumask_next(raw_smp_processor_id(), current_mask); 33162306a36Sopenharmony_ci cpus_read_unlock(); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci if (next_cpu >= nr_cpu_ids) 33462306a36Sopenharmony_ci next_cpu = cpumask_first(current_mask); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci if (next_cpu >= nr_cpu_ids) /* Shouldn't happen! */ 33762306a36Sopenharmony_ci goto change_mode; 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci cpumask_clear(current_mask); 34062306a36Sopenharmony_ci cpumask_set_cpu(next_cpu, current_mask); 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci set_cpus_allowed_ptr(current, current_mask); 34362306a36Sopenharmony_ci return; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci change_mode: 34662306a36Sopenharmony_ci hwlat_data.thread_mode = MODE_NONE; 34762306a36Sopenharmony_ci pr_info(BANNER "cpumask changed while in round-robin mode, switching to mode none\n"); 34862306a36Sopenharmony_ci} 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci/* 35162306a36Sopenharmony_ci * kthread_fn - The CPU time sampling/hardware latency detection kernel thread 35262306a36Sopenharmony_ci * 35362306a36Sopenharmony_ci * Used to periodically sample the CPU TSC via a call to get_sample. We 35462306a36Sopenharmony_ci * disable interrupts, which does (intentionally) introduce latency since we 35562306a36Sopenharmony_ci * need to ensure nothing else might be running (and thus preempting). 35662306a36Sopenharmony_ci * Obviously this should never be used in production environments. 35762306a36Sopenharmony_ci * 35862306a36Sopenharmony_ci * Executes one loop interaction on each CPU in tracing_cpumask sysfs file. 35962306a36Sopenharmony_ci */ 36062306a36Sopenharmony_cistatic int kthread_fn(void *data) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci u64 interval; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci while (!kthread_should_stop()) { 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci if (hwlat_data.thread_mode == MODE_ROUND_ROBIN) 36762306a36Sopenharmony_ci move_to_next_cpu(); 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci local_irq_disable(); 37062306a36Sopenharmony_ci get_sample(); 37162306a36Sopenharmony_ci local_irq_enable(); 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci mutex_lock(&hwlat_data.lock); 37462306a36Sopenharmony_ci interval = hwlat_data.sample_window - hwlat_data.sample_width; 37562306a36Sopenharmony_ci mutex_unlock(&hwlat_data.lock); 37662306a36Sopenharmony_ci 37762306a36Sopenharmony_ci do_div(interval, USEC_PER_MSEC); /* modifies interval value */ 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* Always sleep for at least 1ms */ 38062306a36Sopenharmony_ci if (interval < 1) 38162306a36Sopenharmony_ci interval = 1; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci if (msleep_interruptible(interval)) 38462306a36Sopenharmony_ci break; 38562306a36Sopenharmony_ci } 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci return 0; 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci/* 39162306a36Sopenharmony_ci * stop_stop_kthread - Inform the hardware latency sampling/detector kthread to stop 39262306a36Sopenharmony_ci * 39362306a36Sopenharmony_ci * This kicks the running hardware latency sampling/detector kernel thread and 39462306a36Sopenharmony_ci * tells it to stop sampling now. Use this on unload and at system shutdown. 39562306a36Sopenharmony_ci */ 39662306a36Sopenharmony_cistatic void stop_single_kthread(void) 39762306a36Sopenharmony_ci{ 39862306a36Sopenharmony_ci struct hwlat_kthread_data *kdata = get_cpu_data(); 39962306a36Sopenharmony_ci struct task_struct *kthread; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci cpus_read_lock(); 40262306a36Sopenharmony_ci kthread = kdata->kthread; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci if (!kthread) 40562306a36Sopenharmony_ci goto out_put_cpus; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci kthread_stop(kthread); 40862306a36Sopenharmony_ci kdata->kthread = NULL; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ciout_put_cpus: 41162306a36Sopenharmony_ci cpus_read_unlock(); 41262306a36Sopenharmony_ci} 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci/* 41662306a36Sopenharmony_ci * start_single_kthread - Kick off the hardware latency sampling/detector kthread 41762306a36Sopenharmony_ci * 41862306a36Sopenharmony_ci * This starts the kernel thread that will sit and sample the CPU timestamp 41962306a36Sopenharmony_ci * counter (TSC or similar) and look for potential hardware latencies. 42062306a36Sopenharmony_ci */ 42162306a36Sopenharmony_cistatic int start_single_kthread(struct trace_array *tr) 42262306a36Sopenharmony_ci{ 42362306a36Sopenharmony_ci struct hwlat_kthread_data *kdata = get_cpu_data(); 42462306a36Sopenharmony_ci struct cpumask *current_mask = &save_cpumask; 42562306a36Sopenharmony_ci struct task_struct *kthread; 42662306a36Sopenharmony_ci int next_cpu; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci cpus_read_lock(); 42962306a36Sopenharmony_ci if (kdata->kthread) 43062306a36Sopenharmony_ci goto out_put_cpus; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci kthread = kthread_create(kthread_fn, NULL, "hwlatd"); 43362306a36Sopenharmony_ci if (IS_ERR(kthread)) { 43462306a36Sopenharmony_ci pr_err(BANNER "could not start sampling thread\n"); 43562306a36Sopenharmony_ci cpus_read_unlock(); 43662306a36Sopenharmony_ci return -ENOMEM; 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci /* Just pick the first CPU on first iteration */ 44062306a36Sopenharmony_ci cpumask_and(current_mask, cpu_online_mask, tr->tracing_cpumask); 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci if (hwlat_data.thread_mode == MODE_ROUND_ROBIN) { 44362306a36Sopenharmony_ci next_cpu = cpumask_first(current_mask); 44462306a36Sopenharmony_ci cpumask_clear(current_mask); 44562306a36Sopenharmony_ci cpumask_set_cpu(next_cpu, current_mask); 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci set_cpus_allowed_ptr(kthread, current_mask); 45062306a36Sopenharmony_ci 45162306a36Sopenharmony_ci kdata->kthread = kthread; 45262306a36Sopenharmony_ci wake_up_process(kthread); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ciout_put_cpus: 45562306a36Sopenharmony_ci cpus_read_unlock(); 45662306a36Sopenharmony_ci return 0; 45762306a36Sopenharmony_ci} 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci/* 46062306a36Sopenharmony_ci * stop_cpu_kthread - Stop a hwlat cpu kthread 46162306a36Sopenharmony_ci */ 46262306a36Sopenharmony_cistatic void stop_cpu_kthread(unsigned int cpu) 46362306a36Sopenharmony_ci{ 46462306a36Sopenharmony_ci struct task_struct *kthread; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci kthread = per_cpu(hwlat_per_cpu_data, cpu).kthread; 46762306a36Sopenharmony_ci if (kthread) 46862306a36Sopenharmony_ci kthread_stop(kthread); 46962306a36Sopenharmony_ci per_cpu(hwlat_per_cpu_data, cpu).kthread = NULL; 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci/* 47362306a36Sopenharmony_ci * stop_per_cpu_kthreads - Inform the hardware latency sampling/detector kthread to stop 47462306a36Sopenharmony_ci * 47562306a36Sopenharmony_ci * This kicks the running hardware latency sampling/detector kernel threads and 47662306a36Sopenharmony_ci * tells it to stop sampling now. Use this on unload and at system shutdown. 47762306a36Sopenharmony_ci */ 47862306a36Sopenharmony_cistatic void stop_per_cpu_kthreads(void) 47962306a36Sopenharmony_ci{ 48062306a36Sopenharmony_ci unsigned int cpu; 48162306a36Sopenharmony_ci 48262306a36Sopenharmony_ci cpus_read_lock(); 48362306a36Sopenharmony_ci for_each_online_cpu(cpu) 48462306a36Sopenharmony_ci stop_cpu_kthread(cpu); 48562306a36Sopenharmony_ci cpus_read_unlock(); 48662306a36Sopenharmony_ci} 48762306a36Sopenharmony_ci 48862306a36Sopenharmony_ci/* 48962306a36Sopenharmony_ci * start_cpu_kthread - Start a hwlat cpu kthread 49062306a36Sopenharmony_ci */ 49162306a36Sopenharmony_cistatic int start_cpu_kthread(unsigned int cpu) 49262306a36Sopenharmony_ci{ 49362306a36Sopenharmony_ci struct task_struct *kthread; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* Do not start a new hwlatd thread if it is already running */ 49662306a36Sopenharmony_ci if (per_cpu(hwlat_per_cpu_data, cpu).kthread) 49762306a36Sopenharmony_ci return 0; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci kthread = kthread_run_on_cpu(kthread_fn, NULL, cpu, "hwlatd/%u"); 50062306a36Sopenharmony_ci if (IS_ERR(kthread)) { 50162306a36Sopenharmony_ci pr_err(BANNER "could not start sampling thread\n"); 50262306a36Sopenharmony_ci return -ENOMEM; 50362306a36Sopenharmony_ci } 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci per_cpu(hwlat_per_cpu_data, cpu).kthread = kthread; 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_ci return 0; 50862306a36Sopenharmony_ci} 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci#ifdef CONFIG_HOTPLUG_CPU 51162306a36Sopenharmony_cistatic void hwlat_hotplug_workfn(struct work_struct *dummy) 51262306a36Sopenharmony_ci{ 51362306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; 51462306a36Sopenharmony_ci unsigned int cpu = smp_processor_id(); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci mutex_lock(&trace_types_lock); 51762306a36Sopenharmony_ci mutex_lock(&hwlat_data.lock); 51862306a36Sopenharmony_ci cpus_read_lock(); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci if (!hwlat_busy || hwlat_data.thread_mode != MODE_PER_CPU) 52162306a36Sopenharmony_ci goto out_unlock; 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_ci if (!cpumask_test_cpu(cpu, tr->tracing_cpumask)) 52462306a36Sopenharmony_ci goto out_unlock; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci start_cpu_kthread(cpu); 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ciout_unlock: 52962306a36Sopenharmony_ci cpus_read_unlock(); 53062306a36Sopenharmony_ci mutex_unlock(&hwlat_data.lock); 53162306a36Sopenharmony_ci mutex_unlock(&trace_types_lock); 53262306a36Sopenharmony_ci} 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_cistatic DECLARE_WORK(hwlat_hotplug_work, hwlat_hotplug_workfn); 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci/* 53762306a36Sopenharmony_ci * hwlat_cpu_init - CPU hotplug online callback function 53862306a36Sopenharmony_ci */ 53962306a36Sopenharmony_cistatic int hwlat_cpu_init(unsigned int cpu) 54062306a36Sopenharmony_ci{ 54162306a36Sopenharmony_ci schedule_work_on(cpu, &hwlat_hotplug_work); 54262306a36Sopenharmony_ci return 0; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_ci/* 54662306a36Sopenharmony_ci * hwlat_cpu_die - CPU hotplug offline callback function 54762306a36Sopenharmony_ci */ 54862306a36Sopenharmony_cistatic int hwlat_cpu_die(unsigned int cpu) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci stop_cpu_kthread(cpu); 55162306a36Sopenharmony_ci return 0; 55262306a36Sopenharmony_ci} 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_cistatic void hwlat_init_hotplug_support(void) 55562306a36Sopenharmony_ci{ 55662306a36Sopenharmony_ci int ret; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "trace/hwlat:online", 55962306a36Sopenharmony_ci hwlat_cpu_init, hwlat_cpu_die); 56062306a36Sopenharmony_ci if (ret < 0) 56162306a36Sopenharmony_ci pr_warn(BANNER "Error to init cpu hotplug support\n"); 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci return; 56462306a36Sopenharmony_ci} 56562306a36Sopenharmony_ci#else /* CONFIG_HOTPLUG_CPU */ 56662306a36Sopenharmony_cistatic void hwlat_init_hotplug_support(void) 56762306a36Sopenharmony_ci{ 56862306a36Sopenharmony_ci return; 56962306a36Sopenharmony_ci} 57062306a36Sopenharmony_ci#endif /* CONFIG_HOTPLUG_CPU */ 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci/* 57362306a36Sopenharmony_ci * start_per_cpu_kthreads - Kick off the hardware latency sampling/detector kthreads 57462306a36Sopenharmony_ci * 57562306a36Sopenharmony_ci * This starts the kernel threads that will sit on potentially all cpus and 57662306a36Sopenharmony_ci * sample the CPU timestamp counter (TSC or similar) and look for potential 57762306a36Sopenharmony_ci * hardware latencies. 57862306a36Sopenharmony_ci */ 57962306a36Sopenharmony_cistatic int start_per_cpu_kthreads(struct trace_array *tr) 58062306a36Sopenharmony_ci{ 58162306a36Sopenharmony_ci struct cpumask *current_mask = &save_cpumask; 58262306a36Sopenharmony_ci unsigned int cpu; 58362306a36Sopenharmony_ci int retval; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci cpus_read_lock(); 58662306a36Sopenharmony_ci /* 58762306a36Sopenharmony_ci * Run only on CPUs in which hwlat is allowed to run. 58862306a36Sopenharmony_ci */ 58962306a36Sopenharmony_ci cpumask_and(current_mask, cpu_online_mask, tr->tracing_cpumask); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci for_each_cpu(cpu, current_mask) { 59262306a36Sopenharmony_ci retval = start_cpu_kthread(cpu); 59362306a36Sopenharmony_ci if (retval) 59462306a36Sopenharmony_ci goto out_error; 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci cpus_read_unlock(); 59762306a36Sopenharmony_ci 59862306a36Sopenharmony_ci return 0; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ciout_error: 60162306a36Sopenharmony_ci cpus_read_unlock(); 60262306a36Sopenharmony_ci stop_per_cpu_kthreads(); 60362306a36Sopenharmony_ci return retval; 60462306a36Sopenharmony_ci} 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_cistatic void *s_mode_start(struct seq_file *s, loff_t *pos) 60762306a36Sopenharmony_ci{ 60862306a36Sopenharmony_ci int mode = *pos; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci mutex_lock(&hwlat_data.lock); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (mode >= MODE_MAX) 61362306a36Sopenharmony_ci return NULL; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci return pos; 61662306a36Sopenharmony_ci} 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_cistatic void *s_mode_next(struct seq_file *s, void *v, loff_t *pos) 61962306a36Sopenharmony_ci{ 62062306a36Sopenharmony_ci int mode = ++(*pos); 62162306a36Sopenharmony_ci 62262306a36Sopenharmony_ci if (mode >= MODE_MAX) 62362306a36Sopenharmony_ci return NULL; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci return pos; 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_cistatic int s_mode_show(struct seq_file *s, void *v) 62962306a36Sopenharmony_ci{ 63062306a36Sopenharmony_ci loff_t *pos = v; 63162306a36Sopenharmony_ci int mode = *pos; 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci if (mode == hwlat_data.thread_mode) 63462306a36Sopenharmony_ci seq_printf(s, "[%s]", thread_mode_str[mode]); 63562306a36Sopenharmony_ci else 63662306a36Sopenharmony_ci seq_printf(s, "%s", thread_mode_str[mode]); 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci if (mode < MODE_MAX - 1) /* if mode is any but last */ 63962306a36Sopenharmony_ci seq_puts(s, " "); 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci return 0; 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic void s_mode_stop(struct seq_file *s, void *v) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci seq_puts(s, "\n"); 64762306a36Sopenharmony_ci mutex_unlock(&hwlat_data.lock); 64862306a36Sopenharmony_ci} 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_cistatic const struct seq_operations thread_mode_seq_ops = { 65162306a36Sopenharmony_ci .start = s_mode_start, 65262306a36Sopenharmony_ci .next = s_mode_next, 65362306a36Sopenharmony_ci .show = s_mode_show, 65462306a36Sopenharmony_ci .stop = s_mode_stop 65562306a36Sopenharmony_ci}; 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_cistatic int hwlat_mode_open(struct inode *inode, struct file *file) 65862306a36Sopenharmony_ci{ 65962306a36Sopenharmony_ci return seq_open(file, &thread_mode_seq_ops); 66062306a36Sopenharmony_ci}; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_cistatic void hwlat_tracer_start(struct trace_array *tr); 66362306a36Sopenharmony_cistatic void hwlat_tracer_stop(struct trace_array *tr); 66462306a36Sopenharmony_ci 66562306a36Sopenharmony_ci/** 66662306a36Sopenharmony_ci * hwlat_mode_write - Write function for "mode" entry 66762306a36Sopenharmony_ci * @filp: The active open file structure 66862306a36Sopenharmony_ci * @ubuf: The user buffer that contains the value to write 66962306a36Sopenharmony_ci * @cnt: The maximum number of bytes to write to "file" 67062306a36Sopenharmony_ci * @ppos: The current position in @file 67162306a36Sopenharmony_ci * 67262306a36Sopenharmony_ci * This function provides a write implementation for the "mode" interface 67362306a36Sopenharmony_ci * to the hardware latency detector. hwlatd has different operation modes. 67462306a36Sopenharmony_ci * The "none" sets the allowed cpumask for a single hwlatd thread at the 67562306a36Sopenharmony_ci * startup and lets the scheduler handle the migration. The default mode is 67662306a36Sopenharmony_ci * the "round-robin" one, in which a single hwlatd thread runs, migrating 67762306a36Sopenharmony_ci * among the allowed CPUs in a round-robin fashion. The "per-cpu" mode 67862306a36Sopenharmony_ci * creates one hwlatd thread per allowed CPU. 67962306a36Sopenharmony_ci */ 68062306a36Sopenharmony_cistatic ssize_t hwlat_mode_write(struct file *filp, const char __user *ubuf, 68162306a36Sopenharmony_ci size_t cnt, loff_t *ppos) 68262306a36Sopenharmony_ci{ 68362306a36Sopenharmony_ci struct trace_array *tr = hwlat_trace; 68462306a36Sopenharmony_ci const char *mode; 68562306a36Sopenharmony_ci char buf[64]; 68662306a36Sopenharmony_ci int ret, i; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci if (cnt >= sizeof(buf)) 68962306a36Sopenharmony_ci return -EINVAL; 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci if (copy_from_user(buf, ubuf, cnt)) 69262306a36Sopenharmony_ci return -EFAULT; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci buf[cnt] = 0; 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci mode = strstrip(buf); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci ret = -EINVAL; 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci /* 70162306a36Sopenharmony_ci * trace_types_lock is taken to avoid concurrency on start/stop 70262306a36Sopenharmony_ci * and hwlat_busy. 70362306a36Sopenharmony_ci */ 70462306a36Sopenharmony_ci mutex_lock(&trace_types_lock); 70562306a36Sopenharmony_ci if (hwlat_busy) 70662306a36Sopenharmony_ci hwlat_tracer_stop(tr); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci mutex_lock(&hwlat_data.lock); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ci for (i = 0; i < MODE_MAX; i++) { 71162306a36Sopenharmony_ci if (strcmp(mode, thread_mode_str[i]) == 0) { 71262306a36Sopenharmony_ci hwlat_data.thread_mode = i; 71362306a36Sopenharmony_ci ret = cnt; 71462306a36Sopenharmony_ci } 71562306a36Sopenharmony_ci } 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci mutex_unlock(&hwlat_data.lock); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci if (hwlat_busy) 72062306a36Sopenharmony_ci hwlat_tracer_start(tr); 72162306a36Sopenharmony_ci mutex_unlock(&trace_types_lock); 72262306a36Sopenharmony_ci 72362306a36Sopenharmony_ci *ppos += cnt; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci return ret; 72862306a36Sopenharmony_ci} 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci/* 73162306a36Sopenharmony_ci * The width parameter is read/write using the generic trace_min_max_param 73262306a36Sopenharmony_ci * method. The *val is protected by the hwlat_data lock and is upper 73362306a36Sopenharmony_ci * bounded by the window parameter. 73462306a36Sopenharmony_ci */ 73562306a36Sopenharmony_cistatic struct trace_min_max_param hwlat_width = { 73662306a36Sopenharmony_ci .lock = &hwlat_data.lock, 73762306a36Sopenharmony_ci .val = &hwlat_data.sample_width, 73862306a36Sopenharmony_ci .max = &hwlat_data.sample_window, 73962306a36Sopenharmony_ci .min = NULL, 74062306a36Sopenharmony_ci}; 74162306a36Sopenharmony_ci 74262306a36Sopenharmony_ci/* 74362306a36Sopenharmony_ci * The window parameter is read/write using the generic trace_min_max_param 74462306a36Sopenharmony_ci * method. The *val is protected by the hwlat_data lock and is lower 74562306a36Sopenharmony_ci * bounded by the width parameter. 74662306a36Sopenharmony_ci */ 74762306a36Sopenharmony_cistatic struct trace_min_max_param hwlat_window = { 74862306a36Sopenharmony_ci .lock = &hwlat_data.lock, 74962306a36Sopenharmony_ci .val = &hwlat_data.sample_window, 75062306a36Sopenharmony_ci .max = NULL, 75162306a36Sopenharmony_ci .min = &hwlat_data.sample_width, 75262306a36Sopenharmony_ci}; 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_cistatic const struct file_operations thread_mode_fops = { 75562306a36Sopenharmony_ci .open = hwlat_mode_open, 75662306a36Sopenharmony_ci .read = seq_read, 75762306a36Sopenharmony_ci .llseek = seq_lseek, 75862306a36Sopenharmony_ci .release = seq_release, 75962306a36Sopenharmony_ci .write = hwlat_mode_write 76062306a36Sopenharmony_ci}; 76162306a36Sopenharmony_ci/** 76262306a36Sopenharmony_ci * init_tracefs - A function to initialize the tracefs interface files 76362306a36Sopenharmony_ci * 76462306a36Sopenharmony_ci * This function creates entries in tracefs for "hwlat_detector". 76562306a36Sopenharmony_ci * It creates the hwlat_detector directory in the tracing directory, 76662306a36Sopenharmony_ci * and within that directory is the count, width and window files to 76762306a36Sopenharmony_ci * change and view those values. 76862306a36Sopenharmony_ci */ 76962306a36Sopenharmony_cistatic int init_tracefs(void) 77062306a36Sopenharmony_ci{ 77162306a36Sopenharmony_ci int ret; 77262306a36Sopenharmony_ci struct dentry *top_dir; 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci ret = tracing_init_dentry(); 77562306a36Sopenharmony_ci if (ret) 77662306a36Sopenharmony_ci return -ENOMEM; 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci top_dir = tracefs_create_dir("hwlat_detector", NULL); 77962306a36Sopenharmony_ci if (!top_dir) 78062306a36Sopenharmony_ci return -ENOMEM; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci hwlat_sample_window = tracefs_create_file("window", TRACE_MODE_WRITE, 78362306a36Sopenharmony_ci top_dir, 78462306a36Sopenharmony_ci &hwlat_window, 78562306a36Sopenharmony_ci &trace_min_max_fops); 78662306a36Sopenharmony_ci if (!hwlat_sample_window) 78762306a36Sopenharmony_ci goto err; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci hwlat_sample_width = tracefs_create_file("width", TRACE_MODE_WRITE, 79062306a36Sopenharmony_ci top_dir, 79162306a36Sopenharmony_ci &hwlat_width, 79262306a36Sopenharmony_ci &trace_min_max_fops); 79362306a36Sopenharmony_ci if (!hwlat_sample_width) 79462306a36Sopenharmony_ci goto err; 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci hwlat_thread_mode = trace_create_file("mode", TRACE_MODE_WRITE, 79762306a36Sopenharmony_ci top_dir, 79862306a36Sopenharmony_ci NULL, 79962306a36Sopenharmony_ci &thread_mode_fops); 80062306a36Sopenharmony_ci if (!hwlat_thread_mode) 80162306a36Sopenharmony_ci goto err; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci return 0; 80462306a36Sopenharmony_ci 80562306a36Sopenharmony_ci err: 80662306a36Sopenharmony_ci tracefs_remove(top_dir); 80762306a36Sopenharmony_ci return -ENOMEM; 80862306a36Sopenharmony_ci} 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_cistatic void hwlat_tracer_start(struct trace_array *tr) 81162306a36Sopenharmony_ci{ 81262306a36Sopenharmony_ci int err; 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci if (hwlat_data.thread_mode == MODE_PER_CPU) 81562306a36Sopenharmony_ci err = start_per_cpu_kthreads(tr); 81662306a36Sopenharmony_ci else 81762306a36Sopenharmony_ci err = start_single_kthread(tr); 81862306a36Sopenharmony_ci if (err) 81962306a36Sopenharmony_ci pr_err(BANNER "Cannot start hwlat kthread\n"); 82062306a36Sopenharmony_ci} 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_cistatic void hwlat_tracer_stop(struct trace_array *tr) 82362306a36Sopenharmony_ci{ 82462306a36Sopenharmony_ci if (hwlat_data.thread_mode == MODE_PER_CPU) 82562306a36Sopenharmony_ci stop_per_cpu_kthreads(); 82662306a36Sopenharmony_ci else 82762306a36Sopenharmony_ci stop_single_kthread(); 82862306a36Sopenharmony_ci} 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_cistatic int hwlat_tracer_init(struct trace_array *tr) 83162306a36Sopenharmony_ci{ 83262306a36Sopenharmony_ci /* Only allow one instance to enable this */ 83362306a36Sopenharmony_ci if (hwlat_busy) 83462306a36Sopenharmony_ci return -EBUSY; 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_ci hwlat_trace = tr; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci hwlat_data.count = 0; 83962306a36Sopenharmony_ci tr->max_latency = 0; 84062306a36Sopenharmony_ci save_tracing_thresh = tracing_thresh; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci /* tracing_thresh is in nsecs, we speak in usecs */ 84362306a36Sopenharmony_ci if (!tracing_thresh) 84462306a36Sopenharmony_ci tracing_thresh = last_tracing_thresh; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci if (tracer_tracing_is_on(tr)) 84762306a36Sopenharmony_ci hwlat_tracer_start(tr); 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci hwlat_busy = true; 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci return 0; 85262306a36Sopenharmony_ci} 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_cistatic void hwlat_tracer_reset(struct trace_array *tr) 85562306a36Sopenharmony_ci{ 85662306a36Sopenharmony_ci hwlat_tracer_stop(tr); 85762306a36Sopenharmony_ci 85862306a36Sopenharmony_ci /* the tracing threshold is static between runs */ 85962306a36Sopenharmony_ci last_tracing_thresh = tracing_thresh; 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_ci tracing_thresh = save_tracing_thresh; 86262306a36Sopenharmony_ci hwlat_busy = false; 86362306a36Sopenharmony_ci} 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_cistatic struct tracer hwlat_tracer __read_mostly = 86662306a36Sopenharmony_ci{ 86762306a36Sopenharmony_ci .name = "hwlat", 86862306a36Sopenharmony_ci .init = hwlat_tracer_init, 86962306a36Sopenharmony_ci .reset = hwlat_tracer_reset, 87062306a36Sopenharmony_ci .start = hwlat_tracer_start, 87162306a36Sopenharmony_ci .stop = hwlat_tracer_stop, 87262306a36Sopenharmony_ci .allow_instances = true, 87362306a36Sopenharmony_ci}; 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci__init static int init_hwlat_tracer(void) 87662306a36Sopenharmony_ci{ 87762306a36Sopenharmony_ci int ret; 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci mutex_init(&hwlat_data.lock); 88062306a36Sopenharmony_ci 88162306a36Sopenharmony_ci ret = register_tracer(&hwlat_tracer); 88262306a36Sopenharmony_ci if (ret) 88362306a36Sopenharmony_ci return ret; 88462306a36Sopenharmony_ci 88562306a36Sopenharmony_ci hwlat_init_hotplug_support(); 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci init_tracefs(); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci return 0; 89062306a36Sopenharmony_ci} 89162306a36Sopenharmony_cilate_initcall(init_hwlat_tracer); 892