1349 lines
43 KiB
Diff
1349 lines
43 KiB
Diff
Subject: hwlatdetect.patch
|
|
From: Carsten Emde <C.Emde@osadl.org>
|
|
Date: Tue, 19 Jul 2011 13:53:12 +0100
|
|
Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/4.8/older/patches-4.8.6-rt5.tar.xz
|
|
|
|
Jon Masters developed this wonderful SMI detector. For details please
|
|
consult Documentation/hwlat_detector.txt. It could be ported to Linux
|
|
3.0 RT without any major change.
|
|
|
|
Signed-off-by: Carsten Emde <C.Emde@osadl.org>
|
|
|
|
---
|
|
Documentation/hwlat_detector.txt | 64 ++
|
|
drivers/misc/Kconfig | 29
|
|
drivers/misc/Makefile | 1
|
|
drivers/misc/hwlat_detector.c | 1212 +++++++++++++++++++++++++++++++++++++++
|
|
4 files changed, 1306 insertions(+)
|
|
|
|
--- /dev/null
|
|
+++ b/Documentation/hwlat_detector.txt
|
|
@@ -0,0 +1,64 @@
|
|
+Introduction:
|
|
+-------------
|
|
+
|
|
+The module hwlat_detector is a special purpose kernel module that is used to
|
|
+detect large system latencies induced by the behavior of certain underlying
|
|
+hardware or firmware, independent of Linux itself. The code was developed
|
|
+originally to detect SMIs (System Management Interrupts) on x86 systems,
|
|
+however there is nothing x86 specific about this patchset. It was
|
|
+originally written for use by the "RT" patch since the Real Time
|
|
+kernel is highly latency sensitive.
|
|
+
|
|
+SMIs are usually not serviced by the Linux kernel, which typically does not
|
|
+even know that they are occuring. SMIs are instead are set up by BIOS code
|
|
+and are serviced by BIOS code, usually for "critical" events such as
|
|
+management of thermal sensors and fans. Sometimes though, SMIs are used for
|
|
+other tasks and those tasks can spend an inordinate amount of time in the
|
|
+handler (sometimes measured in milliseconds). Obviously this is a problem if
|
|
+you are trying to keep event service latencies down in the microsecond range.
|
|
+
|
|
+The hardware latency detector works by hogging all of the cpus for configurable
|
|
+amounts of time (by calling stop_machine()), polling the CPU Time Stamp Counter
|
|
+for some period, then looking for gaps in the TSC data. Any gap indicates a
|
|
+time when the polling was interrupted and since the machine is stopped and
|
|
+interrupts turned off the only thing that could do that would be an SMI.
|
|
+
|
|
+Note that the SMI detector should *NEVER* be used in a production environment.
|
|
+It is intended to be run manually to determine if the hardware platform has a
|
|
+problem with long system firmware service routines.
|
|
+
|
|
+Usage:
|
|
+------
|
|
+
|
|
+Loading the module hwlat_detector passing the parameter "enabled=1" (or by
|
|
+setting the "enable" entry in "hwlat_detector" debugfs toggled on) is the only
|
|
+step required to start the hwlat_detector. It is possible to redefine the
|
|
+threshold in microseconds (us) above which latency spikes will be taken
|
|
+into account (parameter "threshold=").
|
|
+
|
|
+Example:
|
|
+
|
|
+ # modprobe hwlat_detector enabled=1 threshold=100
|
|
+
|
|
+After the module is loaded, it creates a directory named "hwlat_detector" under
|
|
+the debugfs mountpoint, "/debug/hwlat_detector" for this text. It is necessary
|
|
+to have debugfs mounted, which might be on /sys/debug on your system.
|
|
+
|
|
+The /debug/hwlat_detector interface contains the following files:
|
|
+
|
|
+count - number of latency spikes observed since last reset
|
|
+enable - a global enable/disable toggle (0/1), resets count
|
|
+max - maximum hardware latency actually observed (usecs)
|
|
+sample - a pipe from which to read current raw sample data
|
|
+ in the format <timestamp> <latency observed usecs>
|
|
+ (can be opened O_NONBLOCK for a single sample)
|
|
+threshold - minimum latency value to be considered (usecs)
|
|
+width - time period to sample with CPUs held (usecs)
|
|
+ must be less than the total window size (enforced)
|
|
+window - total period of sampling, width being inside (usecs)
|
|
+
|
|
+By default we will set width to 500,000 and window to 1,000,000, meaning that
|
|
+we will sample every 1,000,000 usecs (1s) for 500,000 usecs (0.5s). If we
|
|
+observe any latencies that exceed the threshold (initially 100 usecs),
|
|
+then we write to a global sample ring buffer of 8K samples, which is
|
|
+consumed by reading from the "sample" (pipe) debugfs file interface.
|
|
--- a/drivers/misc/Kconfig
|
|
+++ b/drivers/misc/Kconfig
|
|
@@ -122,6 +122,35 @@ config IBM_ASM
|
|
for information on the specific driver level and support statement
|
|
for your IBM server.
|
|
|
|
+config HWLAT_DETECTOR
|
|
+ tristate "Testing module to detect hardware-induced latencies"
|
|
+ depends on DEBUG_FS
|
|
+ depends on RING_BUFFER
|
|
+ default m
|
|
+ ---help---
|
|
+ A simple hardware latency detector. Use this module to detect
|
|
+ large latencies introduced by the behavior of the underlying
|
|
+ system firmware external to Linux. We do this using periodic
|
|
+ use of stop_machine to grab all available CPUs and measure
|
|
+ for unexplainable gaps in the CPU timestamp counter(s). By
|
|
+ default, the module is not enabled until the "enable" file
|
|
+ within the "hwlat_detector" debugfs directory is toggled.
|
|
+
|
|
+ This module is often used to detect SMI (System Management
|
|
+ Interrupts) on x86 systems, though is not x86 specific. To
|
|
+ this end, we default to using a sample window of 1 second,
|
|
+ during which we will sample for 0.5 seconds. If an SMI or
|
|
+ similar event occurs during that time, it is recorded
|
|
+ into an 8K samples global ring buffer until retreived.
|
|
+
|
|
+ WARNING: This software should never be enabled (it can be built
|
|
+ but should not be turned on after it is loaded) in a production
|
|
+ environment where high latencies are a concern since the
|
|
+ sampling mechanism actually introduces latencies for
|
|
+ regular tasks while the CPU(s) are being held.
|
|
+
|
|
+ If unsure, say N
|
|
+
|
|
config PHANTOM
|
|
tristate "Sensable PHANToM (PCI)"
|
|
depends on PCI
|
|
--- a/drivers/misc/Makefile
|
|
+++ b/drivers/misc/Makefile
|
|
@@ -38,6 +38,7 @@ obj-$(CONFIG_C2PORT) += c2port/
|
|
obj-$(CONFIG_HMC6352) += hmc6352.o
|
|
obj-y += eeprom/
|
|
obj-y += cb710/
|
|
+obj-$(CONFIG_HWLAT_DETECTOR) += hwlat_detector.o
|
|
obj-$(CONFIG_SPEAR13XX_PCIE_GADGET) += spear13xx_pcie_gadget.o
|
|
obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o
|
|
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
|
|
--- /dev/null
|
|
+++ b/drivers/misc/hwlat_detector.c
|
|
@@ -0,0 +1,1212 @@
|
|
+/*
|
|
+ * hwlat_detector.c - A simple Hardware Latency detector.
|
|
+ *
|
|
+ * Use this module to detect large system latencies induced by the behavior of
|
|
+ * certain underlying system hardware or firmware, independent of Linux itself.
|
|
+ * The code was developed originally to detect the presence of SMIs on Intel
|
|
+ * and AMD systems, although there is no dependency upon x86 herein.
|
|
+ *
|
|
+ * The classical example usage of this module is in detecting the presence of
|
|
+ * SMIs or System Management Interrupts on Intel and AMD systems. An SMI is a
|
|
+ * somewhat special form of hardware interrupt spawned from earlier CPU debug
|
|
+ * modes in which the (BIOS/EFI/etc.) firmware arranges for the South Bridge
|
|
+ * LPC (or other device) to generate a special interrupt under certain
|
|
+ * circumstances, for example, upon expiration of a special SMI timer device,
|
|
+ * due to certain external thermal readings, on certain I/O address accesses,
|
|
+ * and other situations. An SMI hits a special CPU pin, triggers a special
|
|
+ * SMI mode (complete with special memory map), and the OS is unaware.
|
|
+ *
|
|
+ * Although certain hardware-inducing latencies are necessary (for example,
|
|
+ * a modern system often requires an SMI handler for correct thermal control
|
|
+ * and remote management) they can wreak havoc upon any OS-level performance
|
|
+ * guarantees toward low-latency, especially when the OS is not even made
|
|
+ * aware of the presence of these interrupts. For this reason, we need a
|
|
+ * somewhat brute force mechanism to detect these interrupts. In this case,
|
|
+ * we do it by hogging all of the CPU(s) for configurable timer intervals,
|
|
+ * sampling the built-in CPU timer, looking for discontiguous readings.
|
|
+ *
|
|
+ * WARNING: This implementation necessarily introduces latencies. Therefore,
|
|
+ * you should NEVER use this module in a production environment
|
|
+ * requiring any kind of low-latency performance guarantee(s).
|
|
+ *
|
|
+ * Copyright (C) 2008-2009 Jon Masters, Red Hat, Inc. <jcm@redhat.com>
|
|
+ *
|
|
+ * Includes useful feedback from Clark Williams <clark@redhat.com>
|
|
+ *
|
|
+ * This file is licensed under the terms of the GNU General Public
|
|
+ * License version 2. This program is licensed "as is" without any
|
|
+ * warranty of any kind, whether express or implied.
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/init.h>
|
|
+#include <linux/ring_buffer.h>
|
|
+#include <linux/stop_machine.h>
|
|
+#include <linux/time.h>
|
|
+#include <linux/hrtimer.h>
|
|
+#include <linux/kthread.h>
|
|
+#include <linux/debugfs.h>
|
|
+#include <linux/seq_file.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/version.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/slab.h>
|
|
+
|
|
+#define BUF_SIZE_DEFAULT 262144UL /* 8K*(sizeof(entry)) */
|
|
+#define BUF_FLAGS (RB_FL_OVERWRITE) /* no block on full */
|
|
+#define U64STR_SIZE 22 /* 20 digits max */
|
|
+
|
|
+#define VERSION "1.0.0"
|
|
+#define BANNER "hwlat_detector: "
|
|
+#define DRVNAME "hwlat_detector"
|
|
+#define DEFAULT_SAMPLE_WINDOW 1000000 /* 1s */
|
|
+#define DEFAULT_SAMPLE_WIDTH 500000 /* 0.5s */
|
|
+#define DEFAULT_LAT_THRESHOLD 10 /* 10us */
|
|
+
|
|
+/* Module metadata */
|
|
+
|
|
+MODULE_LICENSE("GPL");
|
|
+MODULE_AUTHOR("Jon Masters <jcm@redhat.com>");
|
|
+MODULE_DESCRIPTION("A simple hardware latency detector");
|
|
+MODULE_VERSION(VERSION);
|
|
+
|
|
+/* Module parameters */
|
|
+
|
|
+static int debug;
|
|
+static int enabled;
|
|
+static int threshold;
|
|
+
|
|
+module_param(debug, int, 0); /* enable debug */
|
|
+module_param(enabled, int, 0); /* enable detector */
|
|
+module_param(threshold, int, 0); /* latency threshold */
|
|
+
|
|
+/* Buffering and sampling */
|
|
+
|
|
+static struct ring_buffer *ring_buffer; /* sample buffer */
|
|
+static DEFINE_MUTEX(ring_buffer_mutex); /* lock changes */
|
|
+static unsigned long buf_size = BUF_SIZE_DEFAULT;
|
|
+static struct task_struct *kthread; /* sampling thread */
|
|
+
|
|
+/* DebugFS filesystem entries */
|
|
+
|
|
+static struct dentry *debug_dir; /* debugfs directory */
|
|
+static struct dentry *debug_max; /* maximum TSC delta */
|
|
+static struct dentry *debug_count; /* total detect count */
|
|
+static struct dentry *debug_sample_width; /* sample width us */
|
|
+static struct dentry *debug_sample_window; /* sample window us */
|
|
+static struct dentry *debug_sample; /* raw samples us */
|
|
+static struct dentry *debug_threshold; /* threshold us */
|
|
+static struct dentry *debug_enable; /* enable/disable */
|
|
+
|
|
+/* Individual samples and global state */
|
|
+
|
|
+struct sample; /* latency sample */
|
|
+struct data; /* Global state */
|
|
+
|
|
+/* Sampling functions */
|
|
+static int __buffer_add_sample(struct sample *sample);
|
|
+static struct sample *buffer_get_sample(struct sample *sample);
|
|
+static int get_sample(void *unused);
|
|
+
|
|
+/* Threading and state */
|
|
+static int kthread_fn(void *unused);
|
|
+static int start_kthread(void);
|
|
+static int stop_kthread(void);
|
|
+static void __reset_stats(void);
|
|
+static int init_stats(void);
|
|
+
|
|
+/* Debugfs interface */
|
|
+static ssize_t simple_data_read(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos, const u64 *entry);
|
|
+static ssize_t simple_data_write(struct file *filp, const char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos, u64 *entry);
|
|
+static int debug_sample_fopen(struct inode *inode, struct file *filp);
|
|
+static ssize_t debug_sample_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos);
|
|
+static int debug_sample_release(struct inode *inode, struct file *filp);
|
|
+static int debug_enable_fopen(struct inode *inode, struct file *filp);
|
|
+static ssize_t debug_enable_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos);
|
|
+static ssize_t debug_enable_fwrite(struct file *file,
|
|
+ const char __user *user_buffer,
|
|
+ size_t user_size, loff_t *offset);
|
|
+
|
|
+/* Initialization functions */
|
|
+static int init_debugfs(void);
|
|
+static void free_debugfs(void);
|
|
+static int detector_init(void);
|
|
+static void detector_exit(void);
|
|
+
|
|
+/* Individual latency samples are stored here when detected and packed into
|
|
+ * the ring_buffer circular buffer, where they are overwritten when
|
|
+ * more than buf_size/sizeof(sample) samples are received. */
|
|
+struct sample {
|
|
+ u64 seqnum; /* unique sequence */
|
|
+ u64 duration; /* ktime delta */
|
|
+ struct timespec timestamp; /* wall time */
|
|
+ unsigned long lost;
|
|
+};
|
|
+
|
|
+/* keep the global state somewhere. Mostly used under stop_machine. */
|
|
+static struct data {
|
|
+
|
|
+ struct mutex lock; /* protect changes */
|
|
+
|
|
+ u64 count; /* total since reset */
|
|
+ u64 max_sample; /* max hardware latency */
|
|
+ u64 threshold; /* sample threshold level */
|
|
+
|
|
+ u64 sample_window; /* total sampling window (on+off) */
|
|
+ u64 sample_width; /* active sampling portion of window */
|
|
+
|
|
+ atomic_t sample_open; /* whether the sample file is open */
|
|
+
|
|
+ wait_queue_head_t wq; /* waitqeue for new sample values */
|
|
+
|
|
+} data;
|
|
+
|
|
+/**
|
|
+ * __buffer_add_sample - add a new latency sample recording to the ring buffer
|
|
+ * @sample: The new latency sample value
|
|
+ *
|
|
+ * This receives a new latency sample and records it in a global ring buffer.
|
|
+ * No additional locking is used in this case - suited for stop_machine use.
|
|
+ */
|
|
+static int __buffer_add_sample(struct sample *sample)
|
|
+{
|
|
+ return ring_buffer_write(ring_buffer,
|
|
+ sizeof(struct sample), sample);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * buffer_get_sample - remove a hardware latency sample from the ring buffer
|
|
+ * @sample: Pre-allocated storage for the sample
|
|
+ *
|
|
+ * This retrieves a hardware latency sample from the global circular buffer
|
|
+ */
|
|
+static struct sample *buffer_get_sample(struct sample *sample)
|
|
+{
|
|
+ struct ring_buffer_event *e = NULL;
|
|
+ struct sample *s = NULL;
|
|
+ unsigned int cpu = 0;
|
|
+
|
|
+ if (!sample)
|
|
+ return NULL;
|
|
+
|
|
+ mutex_lock(&ring_buffer_mutex);
|
|
+ for_each_online_cpu(cpu) {
|
|
+ e = ring_buffer_consume(ring_buffer, cpu, NULL, &sample->lost);
|
|
+ if (e)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (e) {
|
|
+ s = ring_buffer_event_data(e);
|
|
+ memcpy(sample, s, sizeof(struct sample));
|
|
+ } else
|
|
+ sample = NULL;
|
|
+ mutex_unlock(&ring_buffer_mutex);
|
|
+
|
|
+ return sample;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * get_sample - sample the CPU TSC and look for likely hardware latencies
|
|
+ * @unused: This is not used but is a part of the stop_machine API
|
|
+ *
|
|
+ * Used to repeatedly capture the CPU TSC (or similar), looking for potential
|
|
+ * hardware-induced latency. Called under stop_machine, with data.lock held.
|
|
+ */
|
|
+static int get_sample(void *unused)
|
|
+{
|
|
+ ktime_t start, t1, t2;
|
|
+ s64 diff, total = 0;
|
|
+ u64 sample = 0;
|
|
+ int ret = 1;
|
|
+
|
|
+ start = ktime_get(); /* start timestamp */
|
|
+
|
|
+ do {
|
|
+
|
|
+ t1 = ktime_get(); /* we'll look for a discontinuity */
|
|
+ t2 = ktime_get();
|
|
+
|
|
+ total = ktime_to_us(ktime_sub(t2, start)); /* sample width */
|
|
+ diff = ktime_to_us(ktime_sub(t2, t1)); /* current diff */
|
|
+
|
|
+ /* This shouldn't happen */
|
|
+ if (diff < 0) {
|
|
+ pr_err(BANNER "time running backwards\n");
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (diff > sample)
|
|
+ sample = diff; /* only want highest value */
|
|
+
|
|
+ } while (total <= data.sample_width);
|
|
+
|
|
+ /* If we exceed the threshold value, we have found a hardware latency */
|
|
+ if (sample > data.threshold) {
|
|
+ struct sample s;
|
|
+
|
|
+ data.count++;
|
|
+ s.seqnum = data.count;
|
|
+ s.duration = sample;
|
|
+ s.timestamp = CURRENT_TIME;
|
|
+ __buffer_add_sample(&s);
|
|
+
|
|
+ /* Keep a running maximum ever recorded hardware latency */
|
|
+ if (sample > data.max_sample)
|
|
+ data.max_sample = sample;
|
|
+ }
|
|
+
|
|
+ ret = 0;
|
|
+out:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * kthread_fn - The CPU time sampling/hardware latency detection kernel thread
|
|
+ * @unused: A required part of the kthread API.
|
|
+ *
|
|
+ * Used to periodically sample the CPU TSC via a call to get_sample. We
|
|
+ * use stop_machine, whith does (intentionally) introduce latency since we
|
|
+ * need to ensure nothing else might be running (and thus pre-empting).
|
|
+ * Obviously this should never be used in production environments.
|
|
+ *
|
|
+ * stop_machine will schedule us typically only on CPU0 which is fine for
|
|
+ * almost every real-world hardware latency situation - but we might later
|
|
+ * generalize this if we find there are any actualy systems with alternate
|
|
+ * SMI delivery or other non CPU0 hardware latencies.
|
|
+ */
|
|
+static int kthread_fn(void *unused)
|
|
+{
|
|
+ int err = 0;
|
|
+ u64 interval = 0;
|
|
+
|
|
+ while (!kthread_should_stop()) {
|
|
+
|
|
+ mutex_lock(&data.lock);
|
|
+
|
|
+ err = stop_machine(get_sample, unused, 0);
|
|
+ if (err) {
|
|
+ /* Houston, we have a problem */
|
|
+ mutex_unlock(&data.lock);
|
|
+ goto err_out;
|
|
+ }
|
|
+
|
|
+ wake_up(&data.wq); /* wake up reader(s) */
|
|
+
|
|
+ interval = data.sample_window - data.sample_width;
|
|
+ do_div(interval, USEC_PER_MSEC); /* modifies interval value */
|
|
+
|
|
+ mutex_unlock(&data.lock);
|
|
+
|
|
+ if (msleep_interruptible(interval))
|
|
+ goto out;
|
|
+ }
|
|
+ goto out;
|
|
+err_out:
|
|
+ pr_err(BANNER "could not call stop_machine, disabling\n");
|
|
+ enabled = 0;
|
|
+out:
|
|
+ return err;
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * start_kthread - Kick off the hardware latency sampling/detector kthread
|
|
+ *
|
|
+ * This starts a kernel thread that will sit and sample the CPU timestamp
|
|
+ * counter (TSC or similar) and look for potential hardware latencies.
|
|
+ */
|
|
+static int start_kthread(void)
|
|
+{
|
|
+ kthread = kthread_run(kthread_fn, NULL,
|
|
+ DRVNAME);
|
|
+ if (IS_ERR(kthread)) {
|
|
+ pr_err(BANNER "could not start sampling thread\n");
|
|
+ enabled = 0;
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * stop_kthread - Inform the hardware latency samping/detector kthread to stop
|
|
+ *
|
|
+ * This kicks the running hardware latency sampling/detector kernel thread and
|
|
+ * tells it to stop sampling now. Use this on unload and at system shutdown.
|
|
+ */
|
|
+static int stop_kthread(void)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = kthread_stop(kthread);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * __reset_stats - Reset statistics for the hardware latency detector
|
|
+ *
|
|
+ * We use data to store various statistics and global state. We call this
|
|
+ * function in order to reset those when "enable" is toggled on or off, and
|
|
+ * also at initialization. Should be called with data.lock held.
|
|
+ */
|
|
+static void __reset_stats(void)
|
|
+{
|
|
+ data.count = 0;
|
|
+ data.max_sample = 0;
|
|
+ ring_buffer_reset(ring_buffer); /* flush out old sample entries */
|
|
+}
|
|
+
|
|
+/**
|
|
+ * init_stats - Setup global state statistics for the hardware latency detector
|
|
+ *
|
|
+ * We use data to store various statistics and global state. We also use
|
|
+ * a global ring buffer (ring_buffer) to keep raw samples of detected hardware
|
|
+ * induced system latencies. This function initializes these structures and
|
|
+ * allocates the global ring buffer also.
|
|
+ */
|
|
+static int init_stats(void)
|
|
+{
|
|
+ int ret = -ENOMEM;
|
|
+
|
|
+ mutex_init(&data.lock);
|
|
+ init_waitqueue_head(&data.wq);
|
|
+ atomic_set(&data.sample_open, 0);
|
|
+
|
|
+ ring_buffer = ring_buffer_alloc(buf_size, BUF_FLAGS);
|
|
+
|
|
+ if (WARN(!ring_buffer, KERN_ERR BANNER
|
|
+ "failed to allocate ring buffer!\n"))
|
|
+ goto out;
|
|
+
|
|
+ __reset_stats();
|
|
+ data.threshold = DEFAULT_LAT_THRESHOLD; /* threshold us */
|
|
+ data.sample_window = DEFAULT_SAMPLE_WINDOW; /* window us */
|
|
+ data.sample_width = DEFAULT_SAMPLE_WIDTH; /* width us */
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+out:
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+/*
|
|
+ * simple_data_read - Wrapper read function for global state debugfs entries
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ * @entry: The entry to read from
|
|
+ *
|
|
+ * This function provides a generic read implementation for the global state
|
|
+ * "data" structure debugfs filesystem entries. It would be nice to use
|
|
+ * simple_attr_read directly, but we need to make sure that the data.lock
|
|
+ * spinlock is held during the actual read (even though we likely won't ever
|
|
+ * actually race here as the updater runs under a stop_machine context).
|
|
+ */
|
|
+static ssize_t simple_data_read(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos, const u64 *entry)
|
|
+{
|
|
+ char buf[U64STR_SIZE];
|
|
+ u64 val = 0;
|
|
+ int len = 0;
|
|
+
|
|
+ memset(buf, 0, sizeof(buf));
|
|
+
|
|
+ if (!entry)
|
|
+ return -EFAULT;
|
|
+
|
|
+ mutex_lock(&data.lock);
|
|
+ val = *entry;
|
|
+ mutex_unlock(&data.lock);
|
|
+
|
|
+ len = snprintf(buf, sizeof(buf), "%llu\n", (unsigned long long)val);
|
|
+
|
|
+ return simple_read_from_buffer(ubuf, cnt, ppos, buf, len);
|
|
+
|
|
+}
|
|
+
|
|
+/*
|
|
+ * simple_data_write - Wrapper write function for global state debugfs entries
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to write value from
|
|
+ * @cnt: The maximum number of bytes to write
|
|
+ * @ppos: The current "file" position
|
|
+ * @entry: The entry to write to
|
|
+ *
|
|
+ * This function provides a generic write implementation for the global state
|
|
+ * "data" structure debugfs filesystem entries. It would be nice to use
|
|
+ * simple_attr_write directly, but we need to make sure that the data.lock
|
|
+ * spinlock is held during the actual write (even though we likely won't ever
|
|
+ * actually race here as the updater runs under a stop_machine context).
|
|
+ */
|
|
+static ssize_t simple_data_write(struct file *filp, const char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos, u64 *entry)
|
|
+{
|
|
+ char buf[U64STR_SIZE];
|
|
+ int csize = min(cnt, sizeof(buf));
|
|
+ u64 val = 0;
|
|
+ int err = 0;
|
|
+
|
|
+ memset(buf, '\0', sizeof(buf));
|
|
+ if (copy_from_user(buf, ubuf, csize))
|
|
+ return -EFAULT;
|
|
+
|
|
+ buf[U64STR_SIZE-1] = '\0'; /* just in case */
|
|
+ err = kstrtoull(buf, 10, &val);
|
|
+ if (err)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&data.lock);
|
|
+ *entry = val;
|
|
+ mutex_unlock(&data.lock);
|
|
+
|
|
+ return csize;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_count_fopen - Open function for "count" debugfs entry
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "count" debugfs
|
|
+ * interface to the hardware latency detector.
|
|
+ */
|
|
+static int debug_count_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_count_fread - Read function for "count" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "count" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to read the
|
|
+ * number of latency readings exceeding the configured threshold since
|
|
+ * the detector was last reset (e.g. by writing a zero into "count").
|
|
+ */
|
|
+static ssize_t debug_count_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ return simple_data_read(filp, ubuf, cnt, ppos, &data.count);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_count_fwrite - Write function for "count" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "count" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to write a
|
|
+ * desired value, especially to zero the total count.
|
|
+ */
|
|
+static ssize_t debug_count_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ return simple_data_write(filp, ubuf, cnt, ppos, &data.count);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_enable_fopen - Dummy open function for "enable" debugfs interface
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "enable" debugfs
|
|
+ * interface to the hardware latency detector.
|
|
+ */
|
|
+static int debug_enable_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_enable_fread - Read function for "enable" debugfs interface
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "enable" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to determine
|
|
+ * whether the detector is currently enabled ("0\n" or "1\n" returned).
|
|
+ */
|
|
+static ssize_t debug_enable_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ char buf[4];
|
|
+
|
|
+ if ((cnt < sizeof(buf)) || (*ppos))
|
|
+ return 0;
|
|
+
|
|
+ buf[0] = enabled ? '1' : '0';
|
|
+ buf[1] = '\n';
|
|
+ buf[2] = '\0';
|
|
+ if (copy_to_user(ubuf, buf, strlen(buf)))
|
|
+ return -EFAULT;
|
|
+ return *ppos = strlen(buf);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_enable_fwrite - Write function for "enable" debugfs interface
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "enable" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to enable or
|
|
+ * disable the detector, which will have the side-effect of possibly
|
|
+ * also resetting the global stats and kicking off the measuring
|
|
+ * kthread (on an enable) or the converse (upon a disable).
|
|
+ */
|
|
+static ssize_t debug_enable_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ char buf[4];
|
|
+ int csize = min(cnt, sizeof(buf));
|
|
+ long val = 0;
|
|
+ int err = 0;
|
|
+
|
|
+ memset(buf, '\0', sizeof(buf));
|
|
+ if (copy_from_user(buf, ubuf, csize))
|
|
+ return -EFAULT;
|
|
+
|
|
+ buf[sizeof(buf)-1] = '\0'; /* just in case */
|
|
+ err = kstrtoul(buf, 10, &val);
|
|
+ if (err)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (val) {
|
|
+ if (enabled)
|
|
+ goto unlock;
|
|
+ enabled = 1;
|
|
+ __reset_stats();
|
|
+ if (start_kthread())
|
|
+ return -EFAULT;
|
|
+ } else {
|
|
+ if (!enabled)
|
|
+ goto unlock;
|
|
+ enabled = 0;
|
|
+ err = stop_kthread();
|
|
+ if (err) {
|
|
+ pr_err(BANNER "cannot stop kthread\n");
|
|
+ return -EFAULT;
|
|
+ }
|
|
+ wake_up(&data.wq); /* reader(s) should return */
|
|
+ }
|
|
+unlock:
|
|
+ return csize;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_max_fopen - Open function for "max" debugfs entry
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "max" debugfs
|
|
+ * interface to the hardware latency detector.
|
|
+ */
|
|
+static int debug_max_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_max_fread - Read function for "max" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "max" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to determine
|
|
+ * the maximum latency value observed since it was last reset.
|
|
+ */
|
|
+static ssize_t debug_max_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ return simple_data_read(filp, ubuf, cnt, ppos, &data.max_sample);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_max_fwrite - Write function for "max" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "max" debugfs
|
|
+ * interface to the hardware latency detector. Can be used to reset the
|
|
+ * maximum or set it to some other desired value - if, then, subsequent
|
|
+ * measurements exceed this value, the maximum will be updated.
|
|
+ */
|
|
+static ssize_t debug_max_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ return simple_data_write(filp, ubuf, cnt, ppos, &data.max_sample);
|
|
+}
|
|
+
|
|
+
|
|
+/**
|
|
+ * debug_sample_fopen - An open function for "sample" debugfs interface
|
|
+ * @inode: The in-kernel inode representation of this debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function handles opening the "sample" file within the hardware
|
|
+ * latency detector debugfs directory interface. This file is used to read
|
|
+ * raw samples from the global ring_buffer and allows the user to see a
|
|
+ * running latency history. Can be opened blocking or non-blocking,
|
|
+ * affecting whether it behaves as a buffer read pipe, or does not.
|
|
+ * Implements simple locking to prevent multiple simultaneous use.
|
|
+ */
|
|
+static int debug_sample_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ if (!atomic_add_unless(&data.sample_open, 1, 1))
|
|
+ return -EBUSY;
|
|
+ else
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_sample_fread - A read function for "sample" debugfs interface
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The user buffer that will contain the samples read
|
|
+ * @cnt: The maximum bytes to read from the debugfs "file"
|
|
+ * @ppos: The current position in the debugfs "file"
|
|
+ *
|
|
+ * This function handles reading from the "sample" file within the hardware
|
|
+ * latency detector debugfs directory interface. This file is used to read
|
|
+ * raw samples from the global ring_buffer and allows the user to see a
|
|
+ * running latency history. By default this will block pending a new
|
|
+ * value written into the sample buffer, unless there are already a
|
|
+ * number of value(s) waiting in the buffer, or the sample file was
|
|
+ * previously opened in a non-blocking mode of operation.
|
|
+ */
|
|
+static ssize_t debug_sample_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ int len = 0;
|
|
+ char buf[64];
|
|
+ struct sample *sample = NULL;
|
|
+
|
|
+ if (!enabled)
|
|
+ return 0;
|
|
+
|
|
+ sample = kzalloc(sizeof(struct sample), GFP_KERNEL);
|
|
+ if (!sample)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ while (!buffer_get_sample(sample)) {
|
|
+
|
|
+ DEFINE_WAIT(wait);
|
|
+
|
|
+ if (filp->f_flags & O_NONBLOCK) {
|
|
+ len = -EAGAIN;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ prepare_to_wait(&data.wq, &wait, TASK_INTERRUPTIBLE);
|
|
+ schedule();
|
|
+ finish_wait(&data.wq, &wait);
|
|
+
|
|
+ if (signal_pending(current)) {
|
|
+ len = -EINTR;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ if (!enabled) { /* enable was toggled */
|
|
+ len = 0;
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ len = snprintf(buf, sizeof(buf), "%010lu.%010lu\t%llu\n",
|
|
+ sample->timestamp.tv_sec,
|
|
+ sample->timestamp.tv_nsec,
|
|
+ sample->duration);
|
|
+
|
|
+
|
|
+ /* handling partial reads is more trouble than it's worth */
|
|
+ if (len > cnt)
|
|
+ goto out;
|
|
+
|
|
+ if (copy_to_user(ubuf, buf, len))
|
|
+ len = -EFAULT;
|
|
+
|
|
+out:
|
|
+ kfree(sample);
|
|
+ return len;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_sample_release - Release function for "sample" debugfs interface
|
|
+ * @inode: The in-kernel inode represenation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function completes the close of the debugfs interface "sample" file.
|
|
+ * Frees the sample_open "lock" so that other users may open the interface.
|
|
+ */
|
|
+static int debug_sample_release(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ atomic_dec(&data.sample_open);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_threshold_fopen - Open function for "threshold" debugfs entry
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "threshold" debugfs
|
|
+ * interface to the hardware latency detector.
|
|
+ */
|
|
+static int debug_threshold_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_threshold_fread - Read function for "threshold" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "threshold" debugfs
|
|
+ * interface to the hardware latency detector. It can be used to determine
|
|
+ * the current threshold level at which a latency will be recorded in the
|
|
+ * global ring buffer, typically on the order of 10us.
|
|
+ */
|
|
+static ssize_t debug_threshold_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ return simple_data_read(filp, ubuf, cnt, ppos, &data.threshold);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_threshold_fwrite - Write function for "threshold" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "threshold" debugfs
|
|
+ * interface to the hardware latency detector. It can be used to configure
|
|
+ * the threshold level at which any subsequently detected latencies will
|
|
+ * be recorded into the global ring buffer.
|
|
+ */
|
|
+static ssize_t debug_threshold_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ ret = simple_data_write(filp, ubuf, cnt, ppos, &data.threshold);
|
|
+
|
|
+ if (enabled)
|
|
+ wake_up_process(kthread);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_width_fopen - Open function for "width" debugfs entry
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "width" debugfs
|
|
+ * interface to the hardware latency detector.
|
|
+ */
|
|
+static int debug_width_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_width_fread - Read function for "width" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "width" debugfs
|
|
+ * interface to the hardware latency detector. It can be used to determine
|
|
+ * for how many us of the total window us we will actively sample for any
|
|
+ * hardware-induced latecy periods. Obviously, it is not possible to
|
|
+ * sample constantly and have the system respond to a sample reader, or,
|
|
+ * worse, without having the system appear to have gone out to lunch.
|
|
+ */
|
|
+static ssize_t debug_width_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ return simple_data_read(filp, ubuf, cnt, ppos, &data.sample_width);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_width_fwrite - Write function for "width" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "width" debugfs
|
|
+ * interface to the hardware latency detector. It can be used to configure
|
|
+ * for how many us of the total window us we will actively sample for any
|
|
+ * hardware-induced latency periods. Obviously, it is not possible to
|
|
+ * sample constantly and have the system respond to a sample reader, or,
|
|
+ * worse, without having the system appear to have gone out to lunch. It
|
|
+ * is enforced that width is less that the total window size.
|
|
+ */
|
|
+static ssize_t debug_width_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ char buf[U64STR_SIZE];
|
|
+ int csize = min(cnt, sizeof(buf));
|
|
+ u64 val = 0;
|
|
+ int err = 0;
|
|
+
|
|
+ memset(buf, '\0', sizeof(buf));
|
|
+ if (copy_from_user(buf, ubuf, csize))
|
|
+ return -EFAULT;
|
|
+
|
|
+ buf[U64STR_SIZE-1] = '\0'; /* just in case */
|
|
+ err = kstrtoull(buf, 10, &val);
|
|
+ if (err)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&data.lock);
|
|
+ if (val < data.sample_window)
|
|
+ data.sample_width = val;
|
|
+ else {
|
|
+ mutex_unlock(&data.lock);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ mutex_unlock(&data.lock);
|
|
+
|
|
+ if (enabled)
|
|
+ wake_up_process(kthread);
|
|
+
|
|
+ return csize;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_window_fopen - Open function for "window" debugfs entry
|
|
+ * @inode: The in-kernel inode representation of the debugfs "file"
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ *
|
|
+ * This function provides an open implementation for the "window" debugfs
|
|
+ * interface to the hardware latency detector. The window is the total time
|
|
+ * in us that will be considered one sample period. Conceptually, windows
|
|
+ * occur back-to-back and contain a sample width period during which
|
|
+ * actual sampling occurs.
|
|
+ */
|
|
+static int debug_window_fopen(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_window_fread - Read function for "window" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @ubuf: The userspace provided buffer to read value into
|
|
+ * @cnt: The maximum number of bytes to read
|
|
+ * @ppos: The current "file" position
|
|
+ *
|
|
+ * This function provides a read implementation for the "window" debugfs
|
|
+ * interface to the hardware latency detector. The window is the total time
|
|
+ * in us that will be considered one sample period. Conceptually, windows
|
|
+ * occur back-to-back and contain a sample width period during which
|
|
+ * actual sampling occurs. Can be used to read the total window size.
|
|
+ */
|
|
+static ssize_t debug_window_fread(struct file *filp, char __user *ubuf,
|
|
+ size_t cnt, loff_t *ppos)
|
|
+{
|
|
+ return simple_data_read(filp, ubuf, cnt, ppos, &data.sample_window);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * debug_window_fwrite - Write function for "window" debugfs entry
|
|
+ * @filp: The active open file structure for the debugfs "file"
|
|
+ * @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 the debugfs "file"
|
|
+ *
|
|
+ * This function provides a write implementation for the "window" debufds
|
|
+ * interface to the hardware latency detetector. The window is the total time
|
|
+ * in us that will be considered one sample period. Conceptually, windows
|
|
+ * occur back-to-back and contain a sample width period during which
|
|
+ * actual sampling occurs. Can be used to write a new total window size. It
|
|
+ * is enfoced that any value written must be greater than the sample width
|
|
+ * size, or an error results.
|
|
+ */
|
|
+static ssize_t debug_window_fwrite(struct file *filp,
|
|
+ const char __user *ubuf,
|
|
+ size_t cnt,
|
|
+ loff_t *ppos)
|
|
+{
|
|
+ char buf[U64STR_SIZE];
|
|
+ int csize = min(cnt, sizeof(buf));
|
|
+ u64 val = 0;
|
|
+ int err = 0;
|
|
+
|
|
+ memset(buf, '\0', sizeof(buf));
|
|
+ if (copy_from_user(buf, ubuf, csize))
|
|
+ return -EFAULT;
|
|
+
|
|
+ buf[U64STR_SIZE-1] = '\0'; /* just in case */
|
|
+ err = kstrtoull(buf, 10, &val);
|
|
+ if (err)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&data.lock);
|
|
+ if (data.sample_width < val)
|
|
+ data.sample_window = val;
|
|
+ else {
|
|
+ mutex_unlock(&data.lock);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ mutex_unlock(&data.lock);
|
|
+
|
|
+ return csize;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "count" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations count_fops = {
|
|
+ .open = debug_count_fopen,
|
|
+ .read = debug_count_fread,
|
|
+ .write = debug_count_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "enable" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations enable_fops = {
|
|
+ .open = debug_enable_fopen,
|
|
+ .read = debug_enable_fread,
|
|
+ .write = debug_enable_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "max" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations max_fops = {
|
|
+ .open = debug_max_fopen,
|
|
+ .read = debug_max_fread,
|
|
+ .write = debug_max_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "sample" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations sample_fops = {
|
|
+ .open = debug_sample_fopen,
|
|
+ .read = debug_sample_fread,
|
|
+ .release = debug_sample_release,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "threshold" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations threshold_fops = {
|
|
+ .open = debug_threshold_fopen,
|
|
+ .read = debug_threshold_fread,
|
|
+ .write = debug_threshold_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "width" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations width_fops = {
|
|
+ .open = debug_width_fopen,
|
|
+ .read = debug_width_fread,
|
|
+ .write = debug_width_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * Function pointers for the "window" debugfs file operations
|
|
+ */
|
|
+static const struct file_operations window_fops = {
|
|
+ .open = debug_window_fopen,
|
|
+ .read = debug_window_fread,
|
|
+ .write = debug_window_fwrite,
|
|
+ .owner = THIS_MODULE,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * init_debugfs - A function to initialize the debugfs interface files
|
|
+ *
|
|
+ * This function creates entries in debugfs for "hwlat_detector", including
|
|
+ * files to read values from the detector, current samples, and the
|
|
+ * maximum sample that has been captured since the hardware latency
|
|
+ * dectector was started.
|
|
+ */
|
|
+static int init_debugfs(void)
|
|
+{
|
|
+ int ret = -ENOMEM;
|
|
+
|
|
+ debug_dir = debugfs_create_dir(DRVNAME, NULL);
|
|
+ if (!debug_dir)
|
|
+ goto err_debug_dir;
|
|
+
|
|
+ debug_sample = debugfs_create_file("sample", 0444,
|
|
+ debug_dir, NULL,
|
|
+ &sample_fops);
|
|
+ if (!debug_sample)
|
|
+ goto err_sample;
|
|
+
|
|
+ debug_count = debugfs_create_file("count", 0444,
|
|
+ debug_dir, NULL,
|
|
+ &count_fops);
|
|
+ if (!debug_count)
|
|
+ goto err_count;
|
|
+
|
|
+ debug_max = debugfs_create_file("max", 0444,
|
|
+ debug_dir, NULL,
|
|
+ &max_fops);
|
|
+ if (!debug_max)
|
|
+ goto err_max;
|
|
+
|
|
+ debug_sample_window = debugfs_create_file("window", 0644,
|
|
+ debug_dir, NULL,
|
|
+ &window_fops);
|
|
+ if (!debug_sample_window)
|
|
+ goto err_window;
|
|
+
|
|
+ debug_sample_width = debugfs_create_file("width", 0644,
|
|
+ debug_dir, NULL,
|
|
+ &width_fops);
|
|
+ if (!debug_sample_width)
|
|
+ goto err_width;
|
|
+
|
|
+ debug_threshold = debugfs_create_file("threshold", 0644,
|
|
+ debug_dir, NULL,
|
|
+ &threshold_fops);
|
|
+ if (!debug_threshold)
|
|
+ goto err_threshold;
|
|
+
|
|
+ debug_enable = debugfs_create_file("enable", 0644,
|
|
+ debug_dir, &enabled,
|
|
+ &enable_fops);
|
|
+ if (!debug_enable)
|
|
+ goto err_enable;
|
|
+
|
|
+ else {
|
|
+ ret = 0;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+err_enable:
|
|
+ debugfs_remove(debug_threshold);
|
|
+err_threshold:
|
|
+ debugfs_remove(debug_sample_width);
|
|
+err_width:
|
|
+ debugfs_remove(debug_sample_window);
|
|
+err_window:
|
|
+ debugfs_remove(debug_max);
|
|
+err_max:
|
|
+ debugfs_remove(debug_count);
|
|
+err_count:
|
|
+ debugfs_remove(debug_sample);
|
|
+err_sample:
|
|
+ debugfs_remove(debug_dir);
|
|
+err_debug_dir:
|
|
+out:
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * free_debugfs - A function to cleanup the debugfs file interface
|
|
+ */
|
|
+static void free_debugfs(void)
|
|
+{
|
|
+ /* could also use a debugfs_remove_recursive */
|
|
+ debugfs_remove(debug_enable);
|
|
+ debugfs_remove(debug_threshold);
|
|
+ debugfs_remove(debug_sample_width);
|
|
+ debugfs_remove(debug_sample_window);
|
|
+ debugfs_remove(debug_max);
|
|
+ debugfs_remove(debug_count);
|
|
+ debugfs_remove(debug_sample);
|
|
+ debugfs_remove(debug_dir);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * detector_init - Standard module initialization code
|
|
+ */
|
|
+static int detector_init(void)
|
|
+{
|
|
+ int ret = -ENOMEM;
|
|
+
|
|
+ pr_info(BANNER "version %s\n", VERSION);
|
|
+
|
|
+ ret = init_stats();
|
|
+ if (ret)
|
|
+ goto out;
|
|
+
|
|
+ ret = init_debugfs();
|
|
+ if (ret)
|
|
+ goto err_stats;
|
|
+
|
|
+ if (enabled)
|
|
+ ret = start_kthread();
|
|
+
|
|
+ goto out;
|
|
+
|
|
+err_stats:
|
|
+ ring_buffer_free(ring_buffer);
|
|
+out:
|
|
+ return ret;
|
|
+
|
|
+}
|
|
+
|
|
+/**
|
|
+ * detector_exit - Standard module cleanup code
|
|
+ */
|
|
+static void detector_exit(void)
|
|
+{
|
|
+ int err;
|
|
+
|
|
+ if (enabled) {
|
|
+ enabled = 0;
|
|
+ err = stop_kthread();
|
|
+ if (err)
|
|
+ pr_err(BANNER "cannot stop kthread\n");
|
|
+ }
|
|
+
|
|
+ free_debugfs();
|
|
+ ring_buffer_free(ring_buffer); /* free up the ring buffer */
|
|
+
|
|
+}
|
|
+
|
|
+module_init(detector_init);
|
|
+module_exit(detector_exit);
|