The Driver Tracing Interface (DTI) code.
Signed-off-by: Tom Zanussi <[email protected]>
Signed-off-by: David Wilder <[email protected]>
---
drivers/base/Kconfig | 11
drivers/base/Makefile | 1
drivers/base/dti.c | 836 +++++++++++++++++++++++++++++++++++++++++
drivers/base/dti_merged_view.c | 332 ++++++++++++++++
include/linux/dti.h | 293 ++++++++++++++
5 files changed, 1473 insertions(+)
diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig
index 5d6312e..fbc9c0e 100644
--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -49,6 +49,17 @@ config DEBUG_DEVRES
If you are unsure about this, Say N here.
+config DTI
+ bool "Driver Tracing Interface (DTI)"
+ select GTSC
+ help
+ Provides functions to write variable length trace records
+ into a wraparound memory trace buffer. One purpose of
+ this is to inspect the debug traces after a system crash in order to
+ analyze the reason for the failure. The traces are accessable from
+ system dumps via dump analysis tools like crash or lcrash. In live
+ systems the traces can be read via a debugfs interface.
+
config SYS_HYPERVISOR
bool
default n
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index b39ea3f..7caa5f5 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_NUMA) += node.o
obj-$(CONFIG_MEMORY_HOTPLUG_SPARSE) += memory.o
obj-$(CONFIG_SMP) += topology.o
obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o
+obj-$(CONFIG_DTI) += dti.o dti_merged_view.o
ifeq ($(CONFIG_DEBUG_DRIVER),y)
EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/base/dti.c b/drivers/base/dti.c
new file mode 100644
index 0000000..2feec11
--- /dev/null
+++ b/drivers/base/dti.c
@@ -0,0 +1,836 @@
+/*
+ * Linux Driver Tracing Interface.
+ *
+ * Copyright (C) IBM Corp. 2007
+ * Author(s): Tom Zanussi <[email protected]>
+ * Dave Wilder <[email protected]>
+ * Michael Holzheu <[email protected]>
+ */
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/relay.h>
+#include <linux/module.h>
+#include <linux/hardirq.h>
+#include <linux/dti.h>
+
+extern int dti_create_merged_views(struct dti_info *dti);
+extern void dti_remove_merged_views(struct dti_info *dti);
+struct file_operations level_fops;
+
+static inline int nr_sub(int size)
+{
+ if (size < 4)
+ return 0;
+
+ if (size >= 8 * PAGE_SIZE)
+ return 8;
+ else
+ return 4;
+}
+
+static inline int sub_size(int size)
+{
+ if (size < 4)
+ return 0;
+
+ if (size >= 8 * PAGE_SIZE)
+ return size / 8;
+ else
+ return size / 4;
+}
+
+/*
+ * For dti_printk, maximum size of klog formatting buffer beyond which
+ * truncation will occur
+ */
+#define DTI_PRINTF_TMPBUF_SIZE (1024)
+
+/* per-cpu dti_printf formatting temporary buffer */
+static char dti_printf_tmpbuf[NR_CPUS][DTI_PRINTF_TMPBUF_SIZE];
+
+/*
+ * Low-level registration functions
+ */
+
+static struct dti_info *__dti_register_level(const char *name, int level,
+ int sub_size, int nr_sub,
+ struct dti_handle *handle)
+{
+ struct dti_info *dti;
+
+ dti = kzalloc(sizeof(*dti), GFP_KERNEL);
+ if(!dti)
+ return NULL;
+
+ dti->trace = trace_setup("dti", name, sub_size, nr_sub,
+ TRACE_FLIGHT_CHANNEL | TRACE_DISABLE_STATE);
+ if (!dti->trace)
+ goto setup_failed;
+
+ dti->handle = handle;
+ dti->level = level;
+ dti->level_ctrl = debugfs_create_file("level", 0,
+ dti->trace->dir, dti,
+ &level_fops);
+ if (!dti->level_ctrl) {
+ printk("Couldn't create level control file\n");
+ goto level_failed;
+ }
+
+ strncpy(dti->name, name, NAME_MAX);
+
+ return dti;
+
+level_failed:
+ trace_cleanup(dti->trace);
+setup_failed:
+ kfree(dti);
+
+ return NULL;
+}
+
+/**
+ * dti_register_level: create trace dir and level ctrl file
+ *
+ * Internal - exported for setup macros.
+ */
+struct dti_info *dti_register_level(const char *name, int level,
+ struct dti_handle *handle)
+{
+ return __dti_register_level(name, level, sub_size(handle->size),
+ nr_sub(handle->size), handle);
+}
+EXPORT_SYMBOL_GPL(dti_register_level);
+
+static void dti_unregister_level(struct dti_info *dti)
+{
+ debugfs_remove(dti->level_ctrl);
+ trace_cleanup(dti->trace);
+ kfree(dti);
+}
+
+/**
+ * dti_register_channel: create channel part of new trace
+ */
+static int dti_register_channel(struct dti_info *dti)
+{
+ int rc = 0;
+
+ rc = trace_start(dti->trace);
+ if (rc)
+ return rc;
+
+ if (dti_create_merged_views(dti)) {
+ rc = -ENOMEM;
+ goto failed_view;
+ }
+
+ return rc;
+
+failed_view:
+ trace_cleanup(dti->trace);
+ return rc;
+}
+
+static void dti_unregister_channel(struct dti_info *dti)
+{
+ dti_remove_merged_views(dti);
+ trace_cleanup_channel(dti->trace);
+}
+
+/**
+ * __dti_register: create new trace, explicitly specifying subbuffer sizes
+ * @name: name of trace
+ * @sub_size: size of subbuffer
+ * @nr_sub: number of subbuffers
+ * @init_level: initial log level e.g. DTI_LEVEL_DEFAULT, DTI_LEVEL_OFF, etc
+ *
+ * returns trace handle or NULL, if register failed.
+ *
+ * NOTE: use dti_register() if you don't care about sub-buffer details.
+ */
+struct dti_info *__dti_register(const char *name, int sub_size, int nr_sub,
+ int init_level)
+{
+ struct dti_info *dti;
+
+ dti = __dti_register_level(name, init_level, sub_size, nr_sub, NULL);
+ if (!dti)
+ return NULL;
+
+ if (dti_register_channel(dti)) {
+ dti_unregister_level(dti);
+ return NULL;
+ }
+
+ return dti;
+}
+EXPORT_SYMBOL_GPL(__dti_register);
+
+/**
+ * dti_register: main registration function, creates new trace
+ * @name: name of trace
+ * @size: total size of buffer
+ * @init_level: initial log level e.g. DTI_LEVEL_DEFAULT, DTI_LEVEL_OFF, etc
+ *
+ * returns trace handle or NULL, if register failed.
+ */
+struct dti_info *dti_register(const char *name, int size, int init_level)
+{
+ int subsize = sub_size(size);
+ int nrsub = nr_sub(size);
+
+ if (subsize == 0 || nrsub == 0)
+ return NULL;
+
+ return __dti_register(name, subsize, nrsub, init_level);
+}
+EXPORT_SYMBOL_GPL(dti_register);
+
+/**
+ * dti_unregister: unregistration function, destroys trace
+ * @trace: trace handle
+ */
+void dti_unregister(struct dti_info *dti)
+{
+ if (!dti)
+ return;
+
+ dti_unregister_channel(dti);
+ dti_unregister_level(dti);
+}
+EXPORT_SYMBOL_GPL(dti_unregister);
+
+/**
+ * dti_register_work - register a channel asynchronously
+ * @work: work struct that contains the the dti handle
+ *
+ * This is the work function used to register a dti channel
+ * asynchronously.
+ *
+ * Internal - exported for setup macros.
+ */
+void dti_register_work(struct work_struct *work)
+{
+ struct dti_handle *handle =
+ container_of(work, struct dti_handle, work.work);
+
+ dti_register_channel(handle->info);
+}
+EXPORT_SYMBOL_GPL(dti_register_work);
+
+static void dti_register_async(struct dti_handle *handle)
+{
+ if (!handle->registered) {
+ handle->registered = 1;
+ schedule_delayed_work(&handle->work, 1);
+ }
+}
+
+/*
+ * Create channel now if possible, otherwise defer. Return 0 only if the
+ * channel was successfully created and ready for use after this call.
+ */
+static int complete_channel(struct dti_handle *handle)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&handle->lock, flags);
+ if (handle->registered) {
+ spin_unlock_irqrestore(&handle->lock, flags);
+ return -EAGAIN;
+ }
+
+ if (in_atomic() || irqs_disabled()) {
+ dti_register_async(handle);
+ spin_unlock_irqrestore(&handle->lock, flags);
+ return -EAGAIN;
+ }
+
+ handle->registered = 1;
+ spin_unlock_irqrestore(&handle->lock, flags);
+
+ return dti_register_channel(handle->info);
+}
+
+/**
+ * dti_initbuf_reserve - reserve slot in static channel buffer
+ */
+static inline void *dti_initbuf_reserve(struct dti_handle *handle,
+ size_t length)
+{
+ void *reserved;
+ unsigned int subbuf_size = handle->initbuf_size >> 1;
+
+ /* can't log events > initbuf_size / 2 */
+ if (length > subbuf_size)
+ return NULL;
+
+ if (unlikely(handle->initbuf_offset + length > handle->initbuf_size)) {
+ handle->initbuf_pad[1] =
+ handle->initbuf_size - handle->initbuf_offset;
+ handle->initbuf_wrapped = 1;
+ handle->initbuf_offset = 0;
+ }
+
+ if (unlikely(handle->initbuf_offset < subbuf_size &&
+ handle->initbuf_offset + length >= subbuf_size)) {
+ handle->initbuf_pad[0] = subbuf_size - handle->initbuf_offset;
+ handle->initbuf_offset = subbuf_size;
+ }
+
+ reserved = handle->initbuf + handle->initbuf_offset;
+ handle->initbuf_offset += length;
+
+ return reserved;
+}
+
+/**
+ * __dti_reserve: reserve space in trace buffer, low-level (nonhandle) version
+ * @trace: trace handle
+ * @prio: priority of event (the lower, the higher the priority)
+ * @len: length to reserve
+ *
+ * returns pointer to event payload, if event is reserved. Otherwise NULL.
+ *
+ * NOTE: this is the main reserve function for non-handle event logging.
+ * dti_reserve, the handle version, uses it.
+ */
+void *__dti_reserve(struct dti_info *dti, int prio, size_t len)
+{
+ struct dti_event *event;
+ int event_len;
+
+ if (!dti)
+ return NULL;
+
+ if (prio > dti->level)
+ return NULL;
+
+ event_len = len + sizeof(*event);
+
+ event = relay_reserve(dti->trace->rchan, event_len);
+ if (!event)
+ return NULL;
+
+ event->time = sched_clock();
+ event->len = event_len;
+
+ return (void *)event + sizeof(*event);
+}
+EXPORT_SYMBOL_GPL(__dti_reserve);
+
+/**
+ * dti_reserve_early_handle: reserve in early trace buffer, handle version
+ */
+static void *dti_reserve_early_handle(struct dti_handle *handle, int prio,
+ size_t len)
+{
+ struct dti_early_event *event;
+ int event_len;
+
+ if (!handle)
+ return NULL;
+
+ event_len = len + sizeof(*event);
+
+ event = dti_initbuf_reserve(handle, event_len);
+ if (!event)
+ return NULL;
+
+ event->cpu = smp_processor_id();
+ event->event.time = sched_clock();
+ event->event.len = len + sizeof(struct dti_event);
+
+ return (void *)event + sizeof(*event);
+}
+
+/**
+ * dti_reserve_early_fn: early reserve dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+void *dti_reserve_early_fn(struct dti_handle *handle,
+ int prio, size_t len)
+{
+ void *rc = NULL;
+
+ if (handle->initbuf && prio <= handle->initlevel)
+ rc = dti_reserve_early_handle(handle, prio, len);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_reserve_early_fn);
+
+/* dti_reserve_normal_fn: normal reserve dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+void *dti_reserve_normal_fn(struct dti_handle *handle,
+ int prio, size_t len)
+{
+ void *rc = NULL;
+
+ if (handle->info->trace->rchan)
+ rc = __dti_reserve(handle->info, prio, len);
+ else if (prio <= handle->info->level) {
+ if (complete_channel(handle) == 0)
+ rc = __dti_reserve(handle->info, prio, len);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_reserve_normal_fn);
+
+/**
+ * dti_reserve_handle: reserve space in tracing buffer, handle version
+ * @handle: trace handle
+ * @prio: priority of event (the lower, the higher the priority)
+ * @len: length to reserve
+ *
+ * returns pointer to event payload, if event is reserved. Otherwise NULL.
+ *
+ * NOTE: dti.h defines dti_reserve() to use this on the static handles
+ * defined using DEFINE_DTI_HANDLE, so normally you'd use dti_reserve()
+ * instead of using this directly.
+ */
+void *dti_reserve_handle(struct dti_handle *handle, int prio, size_t len)
+{
+ return handle->reserve(handle, prio, len);
+}
+EXPORT_SYMBOL_GPL(dti_reserve_handle);
+
+static int vprintk_normal(struct dti_info *dti, int prio, const char* fmt,
+ va_list args)
+{
+ struct dti_event *event;
+ void *buf;
+ int len, event_len, rc = -1;
+ unsigned long flags;
+
+ if (!dti)
+ return -1;
+
+ if (prio > dti->level)
+ return -1;
+
+ local_irq_save(flags);
+ buf = dti_printf_tmpbuf[smp_processor_id()];
+ len = vsnprintf(buf, DTI_PRINTF_TMPBUF_SIZE, fmt, args);
+ event_len = len + sizeof(*event);
+ event = relay_reserve(dti->trace->rchan, event_len);
+ if (event) {
+ event->time = sched_clock();
+ event->len = event_len;
+ memcpy(event->data, buf, len);
+ rc = 0;
+ }
+ local_irq_restore(flags);
+
+ return rc;
+}
+
+/**
+ * __dti_printk: write formatted string to trace, low-level (nonhandle) version
+ * @trace: trace struct pointer
+ * @fmt: format string
+ * @...: parameters
+ *
+ * returns 0, if event is written. Otherwise -1.
+ *
+ * NOTE: this is the main printk function for non-handle printing.
+ * dti_printk, the handle version, uses it.
+ */
+int __dti_printk(struct dti_info *dti, int prio, const char* fmt, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, fmt);
+ rc = vprintk_normal(dti, prio, fmt, args);
+ va_end(args);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(__dti_printk);
+
+static int vprintk_early(struct dti_handle *handle, int prio, const char* fmt,
+ va_list args)
+{
+ struct dti_early_event *event;
+ void *buf;
+ int len, event_len, rc = -1;
+ unsigned long flags;
+
+ if (!handle)
+ return -1;
+
+ local_irq_save(flags);
+ buf = dti_printf_tmpbuf[smp_processor_id()];
+ len = vsnprintf(buf, DTI_PRINTF_TMPBUF_SIZE, fmt, args);
+ event_len = len + sizeof(*event);
+ event = dti_initbuf_reserve(handle, event_len);
+ if (event) {
+ event->cpu = smp_processor_id();
+ event->event.time = sched_clock();
+ event->event.len = len + sizeof(struct dti_event);
+ memcpy(event->event.data, buf, len);
+ rc = 0;
+ }
+ local_irq_restore(flags);
+
+ return rc;
+}
+
+/**
+ * dti_printk_early: write formatted string to trace, early version
+ */
+static int dti_printk_early(struct dti_handle *handle, int prio,
+ const char* fmt, ...)
+{
+ va_list args;
+ int rc;
+
+ va_start(args, fmt);
+ rc = vprintk_early(handle, prio, fmt, args);
+ va_end(args);
+
+ return rc;
+}
+
+/**
+ * dti_printk_early_fn: early printk dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+int dti_printk_early_fn(struct dti_handle *handle,
+ int prio, const char* fmt, va_list args)
+{
+ int rc = -1;
+
+ if (handle->initbuf) {
+ if (prio <= handle->initlevel)
+ rc = dti_printk_early(handle, prio, fmt, args);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_printk_early_fn);
+
+/**
+ * dti_printk_normal_fn: normal printk dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+int dti_printk_normal_fn(struct dti_handle *handle,
+ int prio, const char* fmt, va_list args)
+{
+ int rc = -1;
+
+ if (handle->info->trace->rchan)
+ rc = vprintk_normal(handle->info, prio, fmt, args);
+ else if (prio <= handle->info->level) {
+ if (complete_channel(handle) == 0)
+ rc = vprintk_normal(handle->info, prio, fmt, args);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_printk_normal_fn);
+
+/**
+ * dti_printk_handle: write formatted string to trace, handle version
+ * @handle: trace handle
+ * @fmt: format string
+ * @...: parameters
+ *
+ * returns 0, if event is written. Otherwise -1.
+ *
+ * NOTE: dti.h defines dti_printk() to use this on the static handles
+ * defined using DEFINE_DTI_HANDLE, so normally you'd use dti_printk()
+ * instead of using this directly.
+ */
+int dti_printk_handle(struct dti_handle *handle, int prio,
+ const char* fmt, ...)
+{
+ va_list args;
+ int rc = -1;
+
+ va_start(args, fmt);
+ rc = handle->printk(handle, prio, fmt, args);
+ va_end(args);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_printk_handle);
+
+/**
+ * __dti_event: write buffer to trace, low-level (nonhandle) version
+ * @trace: trace handle
+ * @prio: priority of event (the lower, the higher the priority)
+ * @buf: buffer to write
+ * @len: length of buffer
+ *
+ * returns 0, if event is written. Otherwise -1.
+ *
+ * NOTE: this is the main event function for non-handle event logging.
+ * dti_event, the handle version, uses it.
+ */
+int __dti_event(struct dti_info *dti,
+ int prio, const void* buf, size_t len)
+{
+ struct dti_event *event;
+ int event_len, rc = -1;
+ unsigned long flags;
+
+ if (!dti)
+ return -1;
+
+ if (prio > dti->level)
+ return -1;
+
+ event_len = len + sizeof(*event);
+
+ local_irq_save(flags);
+ event = relay_reserve(dti->trace->rchan, event_len);
+ if (event) {
+ event->time = sched_clock();
+ event->len = event_len;
+ memcpy(event->data, buf, len);
+ rc = 0;
+ }
+ local_irq_restore(flags);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(__dti_event);
+
+/**
+ * dti_event_early: write buffer to trace, early version
+ */
+static int dti_event_early(struct dti_handle *handle,
+ int prio, const void* buf, size_t len)
+{
+ struct dti_early_event *event;
+ int event_len, rc = -1;
+ unsigned long flags;
+
+ if (!handle)
+ return -1;
+
+ event_len = len + sizeof(*event);
+
+ local_irq_save(flags);
+ event = dti_initbuf_reserve(handle, event_len);
+ if (event) {
+ event->cpu = smp_processor_id();
+ event->event.time = sched_clock();
+ event->event.len = len + sizeof(struct dti_event);
+ memcpy(event->event.data, buf, len);
+ rc = 0;
+ }
+ local_irq_restore(flags);
+
+ return rc;
+}
+
+/**
+ * dti_event_early_fn: early dti_event dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+int dti_event_early_fn(struct dti_handle *handle,
+ int prio, const void* buf, size_t len)
+{
+ int rc = -1;
+
+ if (handle->initbuf && prio <= handle->initlevel)
+ rc = dti_event_early(handle, prio, buf, len);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_event_early_fn);
+
+/**
+ * dti_event_normal_fn: normal dti_event dispatch function
+ *
+ * Internal - exported for setup macros.
+ */
+int dti_event_normal_fn(struct dti_handle *handle,
+ int prio, const void* buf, size_t len)
+{
+ int rc = -1;
+
+ if (handle->info->trace->rchan)
+ rc = __dti_event(handle->info, prio, buf, len);
+ else if (prio <= handle->info->level) {
+ if (complete_channel(handle) == 0)
+ rc = __dti_event(handle->info, prio, buf, len);
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_event_normal_fn);
+
+/**
+ * dti_event_handle: write buffer to trace, handle version
+ * @handle: trace handle
+ * @prio: priority of event (the lower, the higher the priority)
+ * @buf: buffer to write
+ * @len: length of buffer
+ *
+ * returns 0, if event is written. Otherwise -1.
+ *
+ * NOTE: dti.h defines dti_event() to use this on the static handles
+ * defined using DEFINE_DTI_HANDLE, so normally you'd use dti_event()
+ * instead of using this directly.
+ */
+int dti_event_handle(struct dti_handle *handle,
+ int prio, const void* buf, size_t len)
+{
+ return handle->event(handle, prio, buf, len);
+}
+EXPORT_SYMBOL_GPL(dti_event_handle);
+
+/**
+ * dti_relog_initbuf - re-log static initbuf into real relay channel
+ * @work: work struct that contains the the dti handle
+ *
+ * The global initbuf may contain events from multiple cpus. These
+ * events must be put into their respective cpu buffers once the
+ * per-cpu channel is available.
+ *
+ * Internal - exported for setup macros.
+ */
+int dti_relog_initbuf(struct dti_handle *handle)
+{
+ void *initbuf, *reserved;
+ struct dti_early_event *event;
+ int rc = 0;
+ unsigned int left, i;
+ unsigned int n_subbufs = 2;
+ unsigned int subbuf_size = handle->initbuf_size >> 1;
+ unsigned int relog_idx = 0;
+ unsigned int relog_n = n_subbufs;
+ unsigned int sizediff = (sizeof(*event) - sizeof(struct dti_event));
+
+ rc = dti_register_channel(handle->info);
+ if (rc)
+ return rc;
+
+ if (handle->initbuf_wrapped && handle->initbuf_offset <= subbuf_size)
+ relog_idx = 1;
+
+ if (!handle->initbuf_wrapped && handle->initbuf_offset < subbuf_size)
+ relog_n = 1;
+
+ for (i = 0; i < relog_n; i++) {
+ initbuf = handle->initbuf + relog_idx * subbuf_size;
+ if (i == 0 && relog_n == 2)
+ left = subbuf_size - handle->initbuf_pad[relog_idx];
+ else
+ left = handle->initbuf_offset % subbuf_size;
+
+ while (left) {
+ event = initbuf;
+ reserved = relay_reserve_cpu(handle->info->trace->rchan,
+ event->event.len,
+ event->cpu);
+ if (reserved)
+ memcpy(reserved, &event->event,
+ event->event.len);
+ left -= event->event.len + sizediff;
+ initbuf += event->event.len + sizediff;
+ }
+ if (++relog_idx == n_subbufs)
+ relog_idx = 0;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(dti_relog_initbuf);
+
+/*
+ * control files
+ */
+static int level_open(struct inode *inode, struct file *filp)
+{
+ filp->private_data = inode->i_private;
+ return 0;
+}
+
+static ssize_t level_read(struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct dti_info *dti = filp->private_data;
+ char buf[32];
+
+ sprintf(buf, "%d\n", dti->level);
+ return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
+static ssize_t level_write(struct file *filp, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ char buf[16] = { '\0' };
+ char *tmp;
+ struct dti_info *dti = filp->private_data;
+ int new_level;
+
+ if (count > sizeof(buf) - 1)
+ return -EINVAL;
+ if (copy_from_user(buf, buffer, count))
+ return -EFAULT;
+ buf[count] = '\0';
+ if (strcmp(buf, DTI_LEVEL_OFF_STR) == 0) {
+ dti->level = DTI_LEVEL_OFF;
+ return count;
+ }
+ if (strcmp(buf, DTI_LEVEL_DESTROY_STR) == 0)
+ new_level = DTI_LEVEL_DESTROY;
+ else {
+ new_level = simple_strtol(buf, &tmp, 10);
+ if (tmp == buf)
+ return -EINVAL;
+ if ((new_level > DTI_LEVEL_MAX) ||
+ (new_level == DTI_LEVEL_DESTROY && !dti->handle) ||
+ (new_level < DTI_LEVEL_DESTROY))
+ return -EINVAL;
+ }
+
+ if (new_level == DTI_LEVEL_DESTROY) {
+ dti_unregister_channel(dti);
+ dti->level = DTI_LEVEL_OFF;
+ }
+
+ dti->level = new_level;
+
+ return count;
+}
+
+struct file_operations level_fops = {
+ .owner = THIS_MODULE,
+ .open = level_open,
+ .read = level_read,
+ .write = level_write
+};
+
+/**
+ * dti_set_level: set trace level
+ * @trace: trace handle
+ */
+void dti_set_level(struct dti_info *dti, int new_level)
+{
+ if ((new_level > DTI_LEVEL_MAX) || (new_level < DTI_LEVEL_OFF))
+ return;
+
+ dti->level = new_level;
+}
+EXPORT_SYMBOL_GPL(dti_set_level);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tom Zanussi <[email protected]>,"
+ "Dave Wilder <[email protected]>,"
+ "Michael Holzheu <[email protected]>");
+MODULE_DESCRIPTION("Linux Driver Tracing Interface");
diff --git a/drivers/base/dti_merged_view.c b/drivers/base/dti_merged_view.c
new file mode 100644
index 0000000..b3fee0f
--- /dev/null
+++ b/drivers/base/dti_merged_view.c
@@ -0,0 +1,332 @@
+/*
+ * Provides the user with a merged view of DTI's per-cpu buffers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) David Wilder, IBM Corporation, 2007
+ *
+ * 2007-April Created by David Wilder <[email protected]>.
+ * Sorting code adapted from dti-user (libdti.c)
+ * created by Tom Zanussi <[email protected]>
+ */
+
+#include <linux/fs.h>
+#include <linux/debugfs.h>
+#include <linux/relay.h>
+#include <linux/module.h>
+#include <linux/dti.h>
+
+struct dti_merged_view_info {
+ void *next_event; /* NULL if at end of buffer */
+ struct timeval last_read;
+ int cpu;
+ unsigned long long bytes_left;
+ void *buf;
+ loff_t pos;
+};
+
+struct dti_merged_view {
+ void *current_header; /* header currently being read */
+ ssize_t header_bytes_left;
+ char header[80];
+ void *current_event; /* record currently being read */
+ ssize_t event_bytes_left;
+ struct rchan *chan;
+ int show_timestamps;
+ struct dti_merged_view_info info[NR_CPUS]; /* per-cpu buffer info */
+} __attribute__ ((packed));
+
+struct file_operations dti_merged_view_fops;
+struct file_operations dti_merged_ts_view_fops;
+
+/**
+ * dti_remove_merged_views: remove merged views
+ * @trace: trace handle
+ */
+void dti_remove_merged_views(struct dti_info *trace)
+{
+ if (trace->merged_view)
+ debugfs_remove(trace->merged_view);
+ trace->merged_view = NULL;
+
+ if (trace->merged_ts_view)
+ debugfs_remove(trace->merged_ts_view);
+ trace->merged_ts_view = NULL;
+}
+
+/**
+ * dti_create_merged_views:
+ * Creates merged view files in the trace's parent.
+ *
+ * @trace: trace handle to create view of
+ *
+ * returns 0 on sucess.
+ */
+int dti_create_merged_views(struct dti_info *dti)
+{
+ struct dentry *parent = dti->trace->dir;
+
+ dti->merged_view = debugfs_create_file("merged", 0, parent, dti,
+ &dti_merged_view_fops);
+
+ if (dti->merged_view == NULL ||
+ dti->merged_view == (struct dentry *)-ENODEV)
+ goto cleanup;
+
+ dti->merged_ts_view = debugfs_create_file("merged-ts",
+ 0, parent, dti,
+ &dti_merged_ts_view_fops);
+
+ if (dti->merged_ts_view == NULL ||
+ dti->merged_ts_view == (struct dentry *)-ENODEV)
+ goto cleanup;
+
+ return 0;
+cleanup:
+ dti_remove_merged_views(dti);
+
+ return -ENOMEM;
+}
+
+static int dti_merged_view_open(struct inode *inode, struct file *filp)
+{
+ struct dti_merged_view *view;
+ struct dti_info *dti = inode->i_private;
+ struct rchan *chan = dti->trace->rchan;
+ int i;
+ int trace_level;
+
+ view = kzalloc(sizeof(struct dti_merged_view), GFP_KERNEL);
+ if (!view )
+ return -ENOMEM;
+ filp->private_data = view;
+
+ /* Tracing must be shut off when copying the buffers */
+ trace_level = dti->level;
+ dti_set_level(dti, DTI_LEVEL_OFF);
+ relay_reset_consumed(chan);
+ view->chan = chan;
+
+ for_each_online_cpu(i){
+ view->info[i].buf = (void *)__get_free_page(GFP_KERNEL);
+ if (!view->info[i].buf )
+ goto free_buf;
+ view->info[i].cpu = i;
+ }
+
+ dti_set_level(dti, trace_level);
+
+ return 0;
+free_buf:
+ for_each_possible_cpu(i)
+ if (view->info[i].buf)
+ free_page((unsigned long)view->info[i].buf);
+ kfree(view);
+
+ return -ENOMEM;
+}
+
+static int dti_merged_ts_view_open(struct inode *inode, struct file *filp)
+{
+ struct dti_merged_view *view;
+ int ret;
+
+ ret = dti_merged_view_open(inode, filp);
+ if (!ret) {
+ view = filp->private_data;
+ view->show_timestamps = 1;
+ }
+
+ return ret;
+}
+
+static int dti_merged_view_close(struct inode *inode, struct file *filp)
+{
+ int i;
+ struct dti_merged_view *view = filp->private_data;
+
+ for_each_possible_cpu(i)
+ if (view->info[i].buf)
+ free_page((unsigned long)view->info[i].buf);
+ kfree(view);
+
+ return 0;
+}
+
+static int compare_recs(const void *rec1, const void *rec2)
+{
+ const struct dti_event *event1 = rec1;
+ const struct dti_event *event2 = rec2;
+
+ if (event1->time < event2->time)
+ return -1;
+ else if (event1->time > event2->time)
+ return 1;
+
+ return 0;
+}
+
+static inline int header_bytes_left(struct dti_merged_view *view)
+{
+ if (view->show_timestamps)
+ return view->header_bytes_left;
+
+ return 0;
+}
+
+static inline void build_header(struct dti_merged_view *view, unsigned int cpu,
+ struct dti_event *event)
+{
+ unsigned long nanosec_rem;
+
+ /* build header */
+ nanosec_rem = do_div(event->time, 1000000000);
+ view->header_bytes_left = sprintf(view->header,
+ "[%5lu.%06lu][%d]",
+ (unsigned long)event->time,
+ nanosec_rem/1000,
+ cpu);
+
+ view->current_header = view->header;
+}
+
+static int events_left(int cpu, struct dti_merged_view *view)
+{
+ size_t bytes_read;
+ int bytes_left = view->info[cpu].bytes_left;
+ struct dti_event *event = view->info[cpu].next_event;
+
+ if (bytes_left && bytes_left < sizeof(struct dti_event)) {
+ unsigned int offset = PAGE_SIZE - bytes_left;
+ void *header = view->info[cpu].buf + offset;
+ memcpy(view->info[cpu].buf, header, bytes_left);
+ } else if (bytes_left && bytes_left < event->len)
+ memcpy(view->info[cpu].buf, (void *)event, bytes_left);
+ else if (bytes_left)
+ return 1;
+
+ bytes_read = relay_kernel_read(view->chan->buf[cpu],
+ view->info[cpu].buf + bytes_left,
+ PAGE_SIZE - bytes_left,
+ &view->info[cpu].pos);
+
+ view->info[cpu].bytes_left += bytes_read;
+
+ if (view->info[cpu].bytes_left) {
+ view->info[cpu].next_event = view->info[cpu].buf;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void *next_smallest(int *smallest_cpu, struct dti_merged_view *view )
+{
+ int i;
+ void *next, *smallest = NULL;
+
+ for_each_possible_cpu(i) {
+ if (!events_left(i, view))
+ continue;
+
+ next = view->info[i].next_event;
+ if (next) {
+ if (!smallest) {
+ smallest = next;
+ *smallest_cpu = i;
+ continue;
+ }
+ if (compare_recs(next, smallest) < 0) {
+ smallest = next;
+ *smallest_cpu = i;
+ continue;
+ }
+ }
+ }
+
+ return smallest;
+}
+
+static ssize_t dti_merged_view_read(struct file *filp, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct dti_event *event;
+ loff_t pos = *ppos;
+ int smallest_cpu=0;
+ struct dti_merged_view *view = filp->private_data;
+
+ if (pos < 0)
+ return -EINVAL;
+
+ if (!header_bytes_left(view) && !view->event_bytes_left) {
+ event = (struct dti_event *)next_smallest(&smallest_cpu, view);
+ if (!event)
+ return 0;
+
+ view->current_event = event->data;
+ view->event_bytes_left = event->len - sizeof(*event);
+ view->info[smallest_cpu].next_event += event->len;
+ view->info[smallest_cpu].bytes_left -= event->len;
+
+ if (view->show_timestamps)
+ build_header(view, smallest_cpu, event);
+ }
+
+ if ((header_bytes_left(view) < 0) || view->event_bytes_left < 0)
+ return -EINVAL;
+
+ if (header_bytes_left(view)) {
+ if (count > view->header_bytes_left)
+ count = view->header_bytes_left;
+
+ if (copy_to_user(buffer, view->current_header, count))
+ return -EFAULT;
+
+ view->header_bytes_left -= count;
+ view->current_header += count;
+
+ return count;
+ }
+
+ if (view->event_bytes_left) {
+ if (count > view->event_bytes_left)
+ count = view->event_bytes_left;
+
+ if (copy_to_user(buffer, view->current_event, count))
+ return -EFAULT;
+
+ view->event_bytes_left -= count;
+ view->current_event += count;
+
+ return count;
+ }
+
+ return 0; /* not reached */
+}
+
+struct file_operations dti_merged_view_fops = {
+ .owner = THIS_MODULE,
+ .open = dti_merged_view_open,
+ .read = dti_merged_view_read,
+ .release = dti_merged_view_close
+};
+
+struct file_operations dti_merged_ts_view_fops = {
+ .owner = THIS_MODULE,
+ .open = dti_merged_ts_view_open,
+ .read = dti_merged_view_read,
+ .release = dti_merged_view_close
+};
+
diff --git a/include/linux/dti.h b/include/linux/dti.h
new file mode 100644
index 0000000..3206e6a
--- /dev/null
+++ b/include/linux/dti.h
@@ -0,0 +1,293 @@
+/*
+ * Driver Tracing Interface.
+ *
+ * Copyright (C) IBM Corp. 2007
+ */
+
+#ifndef _LINUX_DTI_H
+#define _LINUX_DTI_H
+
+#include <linux/gtsc.h>
+
+/*
+ * DTI 'log levels'
+ */
+
+#define DTI_LEVEL_MAX 7
+#define DTI_LEVEL_DEFAULT 3
+#define DTI_LEVEL_OFF_STR "off"
+#define DTI_LEVEL_OFF -1
+#define DTI_LEVEL_DESTROY_STR "destroy"
+#define DTI_LEVEL_DESTROY -2
+
+/* These match printk loglevels */
+#define DTI_LEVEL_EMERG 0
+#define DTI_LEVEL_ALERT 1
+#define DTI_LEVEL_CRIT 2
+#define DTI_LEVEL_ERR 3
+#define DTI_LEVEL_WARNING 4
+#define DTI_LEVEL_NOTICE 5
+#define DTI_LEVEL_INFO 6
+#define DTI_LEVEL_DEBUG 7
+
+extern struct dentry *dti_trace_root;
+struct dti_handle;
+
+/*
+ * raw channel struct
+ */
+struct dti_info {
+ char name[NAME_MAX + 1];
+ struct trace_info *trace;
+ int level;
+ atomic_t dropped;
+ struct dentry *level_ctrl;
+ struct dentry *merged_view;
+ struct dentry *merged_ts_view;
+ struct dti_handle *handle;
+};
+
+/*
+ * autoregistering channel handle
+ */
+struct dti_handle {
+ char *name;
+ int size;
+ int initlevel;
+ struct dti_info *info;
+ spinlock_t lock;
+ int registered;
+ struct delayed_work work;
+ void *initbuf;
+ unsigned int initbuf_size;
+ unsigned int initbuf_offset;
+ unsigned int initbuf_wrapped;
+ unsigned int initbuf_pad[2];
+
+ int (*printk) (struct dti_handle *handle,
+ int prio,
+ const char *fmt,
+ va_list args);
+ int (*event) (struct dti_handle *handle,
+ int prio,
+ const void* buf,
+ size_t len);
+ void *(*reserve) (struct dti_handle *handle,
+ int prio,
+ size_t len);
+};
+
+/*
+ * DTI data header
+ */
+struct dti_event {
+ __u16 len;
+ __u64 time;
+ char data[0];
+} __attribute__ ((packed));
+
+struct dti_early_event {
+ __u32 cpu;
+ struct dti_event event;
+} __attribute__ ((packed));
+
+/*
+ * DTI handle-based API
+ */
+#ifdef CONFIG_DTI
+#define DEFINE_DTI_KERNEL_HANDLE(_handle, _name, _size, _initlevel, _initbuf, _initbuf_size) \
+ struct dti_handle _handle = { \
+ .name = _name, \
+ .printk = dti_printk_early_fn, \
+ .event = dti_event_early_fn, \
+ .reserve = dti_reserve_early_fn, \
+ .size = _size, \
+ .initlevel = _initlevel, \
+ .lock = SPIN_LOCK_UNLOCKED, \
+ .work = __DELAYED_WORK_INITIALIZER(_handle.work, \
+ dti_register_work), \
+ .initbuf = _initbuf, \
+ .initbuf_size = _initbuf_size, \
+ .initbuf_offset = 0, \
+ .initbuf_pad = {0}, \
+ .initbuf_wrapped = 0, \
+ .info = NULL, \
+ }; \
+ static int _handle ## _dti_init(void) \
+ { \
+ INIT_DTI_HANDLE(_handle); \
+ return 0; \
+ } \
+ postcore_initcall(_handle ## _dti_init) \
+
+#define DEFINE_DTI_MODULE_HANDLE(_handle, _name, _size, _initlevel) \
+ struct dti_handle _handle = { \
+ .name = _name, \
+ .printk = dti_printk_normal_fn, \
+ .event = dti_event_normal_fn, \
+ .reserve = dti_reserve_normal_fn, \
+ .size = _size, \
+ .initlevel = _initlevel, \
+ .lock = SPIN_LOCK_UNLOCKED, \
+ .work = __DELAYED_WORK_INITIALIZER(_handle.work, \
+ dti_register_work), \
+ .info = NULL, \
+ }
+
+#define INIT_DTI_HANDLE(_handle) \
+ do { \
+ if(_handle.info) \
+ break; \
+ _handle.info = dti_register_level(_handle.name, \
+ _handle.initlevel, &_handle); \
+ if (!_handle.info) \
+ return -ENOMEM; \
+ _handle.printk = dti_printk_normal_fn; \
+ _handle.event = dti_event_normal_fn; \
+ _handle.reserve = dti_reserve_normal_fn; \
+ if (_handle.initbuf) \
+ dti_relog_initbuf(&_handle); \
+ } while (0)
+
+#define CLEANUP_DTI_HANDLE(_handle) \
+ do { \
+ dti_unregister(_handle.info); \
+ } while (0)
+
+#ifdef MODULE
+#define DEFINE_DTI_HANDLE(_handle, _name, _size, _initlevel) \
+ DEFINE_DTI_MODULE_HANDLE(_handle, _name, _size, _initlevel)
+#define DEFINE_DTI_EARLY_HANDLE(_handle, _name, _size, _initlevel, \
+ _initbuf, _initbuf_size) \
+ DEFINE_DTI_KERNEL_HANDLE(_handle, _name, _size, _initlevel, NULL, 0)
+#else
+#define DEFINE_DTI_HANDLE(_handle, _name, _size, _initlevel) \
+ DEFINE_DTI_KERNEL_HANDLE(_handle, _name, _size, _initlevel, NULL, 0)
+#define DEFINE_DTI_EARLY_HANDLE(_handle, _name, _size, _initlevel, \
+ _initbuf, _initbuf_size) \
+ DEFINE_DTI_KERNEL_HANDLE(_handle, _name, _size, _initlevel, \
+ _initbuf, _initbuf_size)
+#endif /* MODULE */
+#else
+#define DEFINE_DTI_KERNEL_HANDLE(_handle, _name, _size, _initlevel, \
+ _initbuf, _initbuf_size)
+#define DEFINE_DTI_MODULE_HANDLE(_handle, _name, _size, _initlevel)
+#define INIT_DTI_HANDLE(_handle)
+#define CLEANUP_DTI_HANDLE(_handle)
+#define DEFINE_DTI_HANDLE(_handle, _name, _size, _initlevel)
+#define DEFINE_DTI_EARLY_HANDLE(_handle, _name, _size, _initlevel, \
+ _initbuf, _initbuf_size)
+#endif /* CONFIG_DTI */
+
+/*
+ * handle-based logging functions
+ */
+
+#ifdef CONFIG_DTI
+#define dti_printk(_handle, _prio, _fmt, _args...) \
+ dti_printk_handle(&(_handle), _prio, _fmt, ## _args)
+#define dti_event(_handle, _prio, _buf, _len) \
+ dti_event_handle(&(_handle), _prio, _buf, _len)
+#define dti_reserve(_handle, _prio, _len) \
+ dti_reserve_handle(&(_handle), _prio, _len)
+#else
+#define dti_printk(_handle, _prio, _fmt, ...)
+#define dti_event(_handle, _prio, _buf, _len)
+#define dti_reserve(_handle, _prio, _len)
+#endif
+
+/**
+ * dti_assert: stop tracing if assertion fails, handle version
+ * @_handle: trace handle
+ * @_expr: expression to test
+ *
+ * If _expr evaluates false, tracing is stopped for _handle
+ */
+#ifdef CONFIG_DTI
+#define dti_assert(_handle, _expr) \
+ do { \
+ if (!(_expr) && _handle.info) \
+ dti_set_level(_handle.info, DTI_LEVEL_OFF); \
+ } while (0)
+#else
+#define dti_assert(_handle, _expr)
+#endif
+
+/*
+ * DTI low-level API
+ */
+
+#ifdef CONFIG_DTI
+struct dti_info *dti_register(const char *name, int size, int init_level);
+struct dti_info *__dti_register(const char *name, int size, int nr_sub,
+ int init_level);
+void dti_unregister(struct dti_info *trace);
+int __dti_printk(struct dti_info *trace, int prio, const char* fmt, ...);
+int __dti_event(struct dti_info *trace, int prio, const void* buf, size_t len);
+void *__dti_reserve(struct dti_info *trace, int prio, size_t len);
+void dti_set_level(struct dti_info *trace, int new_level);
+#else
+static inline struct dti_info *dti_register(const char *name, int size,
+ int init_level)
+{
+ return NULL;
+}
+static inline struct dti_info *__dti_register(const char *name, int size,
+ int nr_sub, int init_level)
+{
+ return NULL;
+}
+static inline void dti_unregister(struct dti_info *trace) {}
+static inline int __dti_printk(struct dti_info *trace, int prio,
+ const char* fmt, ...) { return 0; }
+static inline int __dti_event(struct dti_info *trace, int prio,
+ const void* buf, size_t len) { return 0; }
+static inline void *__dti_reserve(struct dti_info *trace, int prio, size_t len)
+{
+ return NULL;
+}
+static inline void dti_set_level(struct dti_info *trace, int new_level) {}
+#endif
+
+/**
+ * __dti_assert_raw: stop tracing if assertion fails, low-level version
+ * @_info: trace info struct
+ * @_expr: expression to test
+ *
+ * If _expr evaluates false, tracing is stopped for _info
+ */
+#ifdef CONFIG_DTI
+#define __dti_assert(_info, _expr) \
+ do { \
+ if (!(_expr) && _info) \
+ dti_set_level(_info, DTI_LEVEL_OFF); \
+ } while (0)
+#else
+#define __dti_assert(_info, _expr)
+#endif
+
+/*
+ * non-API declarations
+ */
+
+struct dti_info *dti_register_level(const char *name, int level,
+ struct dti_handle *handle);
+void dti_register_work(struct work_struct *work);
+int dti_printk_handle(struct dti_handle *handle,int prio,
+ const char* fmt, ...);
+int dti_printk_early_fn(struct dti_handle *handle, int prio, const char* fmt,
+ va_list args);
+int dti_printk_normal_fn(struct dti_handle *handle, int prio, const char* fmt,
+ va_list args);
+int dti_event_handle(struct dti_handle *handle, int prio, const void* buf,
+ size_t len);
+int dti_event_early_fn(struct dti_handle *handle, int prio, const void* buf,
+ size_t len);
+int dti_event_normal_fn(struct dti_handle *handle, int prio, const void* buf,
+ size_t len);
+void *dti_reserve_handle(struct dti_handle *handle, int prio, size_t len);
+void *dti_reserve_early_fn(struct dti_handle *handle, int prio, size_t len);
+void *dti_reserve_normal_fn(struct dti_handle *handle, int prio, size_t len);
+int dti_relog_initbuf(struct dti_handle *handle);
+
+#endif /* _LINUX_DTI_H */
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
[Index of Archives]
[Kernel Newbies]
[Netfilter]
[Bugtraq]
[Photo]
[Stuff]
[Gimp]
[Yosemite News]
[MIPS Linux]
[ARM Linux]
[Linux Security]
[Linux RAID]
[Video 4 Linux]
[Linux for the blind]
[Linux Resources]