From 5d19e03587f325f07dec9d453c4469032ee3d408 Mon Sep 17 00:00:00 2001
From: Philippe Langlais <philippe.langlais@stericsson.com>
Date: Thu, 26 May 2011 10:40:42 +0200
Subject: [PATCH 1/3] trace: Initial hardware trace device framework

Signed-off-by: Philippe Langlais <philippe.langlais@linaro.org>
---
 drivers/Kconfig         |    2 +
 drivers/Makefile        |    1 +
 drivers/trace/Kconfig   |   85 +++++++++++++++++
 drivers/trace/Makefile  |    5 +
 drivers/trace/trace.c   |  159 +++++++++++++++++++++++++++++++
 include/linux/hwtrace.h |  242 +++++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 494 insertions(+), 0 deletions(-)
 create mode 100644 drivers/trace/Kconfig
 create mode 100644 drivers/trace/Makefile
 create mode 100644 drivers/trace/trace.c
 create mode 100644 include/linux/hwtrace.h

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 3bb154d..c854452 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -126,4 +126,6 @@ source "drivers/hwspinlock/Kconfig"
 
 source "drivers/clocksource/Kconfig"
 
+source "drivers/trace/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 6b17f58..c467927 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -116,6 +116,7 @@ obj-$(CONFIG_VLYNQ)		+= vlynq/
 obj-$(CONFIG_STAGING)		+= staging/
 obj-y				+= platform/
 obj-y				+= ieee802154/
+obj-y 				+= trace/
 #common clk code
 obj-y				+= clk/
 
diff --git a/drivers/trace/Kconfig b/drivers/trace/Kconfig
new file mode 100644
index 0000000..0383c92
--- /dev/null
+++ b/drivers/trace/Kconfig
@@ -0,0 +1,85 @@
+#
+# Trace devices
+#
+
+menuconfig TRACE_DEVICES
+	bool "trace devices"
+	---help---
+	  Say Y here to get to see options for trace device drivers
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if TRACE_DEVICES
+
+menu "Tracers"
+config TRACE_DUP_PRINTK
+	bool "Duplicate console printk output"
+	default y
+	help
+	  Duplicate console printk output on trace device
+
+config TRACE_FTRACE
+	bool "functions tracing"
+	depends on FTRACE
+	default y
+	help
+	  Output function tracing
+
+config TRACE_CTX_SWITCH
+	bool "Context switch tracing"
+	depends on CONTEXT_SWITCH_TRACER
+	default y
+	help
+	  Output scheduler context switch on trace device
+
+config TRACE_WAKEUP
+	bool "Scheduler wakeup tracing"
+	depends on CONTEXT_SWITCH_TRACER
+	default y
+	help
+	  Output scheduler wakeup on trace device
+
+config TRACE_STACK
+	bool "Stacks tracing"
+	depends on STACKTRACE
+	default y
+	help
+	  Output stack tracing on trace device
+
+config TRACE_PRINTK
+	bool "trace printk & binary printk support"
+	depends on TRACING
+	default y
+	help
+	  Duplicate trace printk output on trace device
+
+config TRACE_BLK_DEV_IO
+	bool "trace block IO support"
+	depends on BLK_DEV_IO_TRACE
+	default y
+	help
+	  trace block IO on trace device
+
+config TRACE_BRANCH
+	bool "trace branch support"
+	depends on TRACE_BRANCH_PROFILING
+	default y
+	help
+	  trace branch on trace device
+
+config TRACE_FUNC_GRAPH
+	bool "Graphical function tracer support"
+	depends on FUNCTION_GRAPH_TRACER
+	default y
+	help
+	  Kernel Function Graph Tracer on trace device
+
+config TRACE_MMIO
+	bool "Memory mapped IO tracing"
+	depends on MMIOTRACE
+	default y
+	help
+	  Memory mapped IO tracing on trace device
+endmenu
+
+endif # TRACE_DEVICES
diff --git a/drivers/trace/Makefile b/drivers/trace/Makefile
new file mode 100644
index 0000000..fcef682
--- /dev/null
+++ b/drivers/trace/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for trace devices
+#
+
+obj-$(CONFIG_TRACE_DEVICES) 	+= trace.o
diff --git a/drivers/trace/trace.c b/drivers/trace/trace.c
new file mode 100644
index 0000000..3482c06
--- /dev/null
+++ b/drivers/trace/trace.c
@@ -0,0 +1,159 @@
+/*
+ * Generic trace device routines
+ *
+ * Copyright (C) 2011 ST-Ericsson SA
+ * Written on behalf of Linaro
+ *
+ * Author: Philippe Langlais <philippe.Langlais@linaro.org> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#include <linux/module.h>
+
+#include <linux/cdev.h>
+#include <linux/hwtrace.h>
+#include <linux/proc_fs.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+
+static int trace_major;
+
+/*
+ * Head entry for the doubly linked trace_device list
+ */
+LIST_HEAD(trace_list);
+static DEFINE_MUTEX(trace_mtx);
+
+/*
+ * Assigned numbers, used for dynamic minors
+ */
+#define DYNAMIC_MINORS 32
+static DECLARE_BITMAP(trace_minors, DYNAMIC_MINORS);
+
+static struct class *trace_class;
+
+/**
+ * trace_device_register - create and register a new object of
+ *   trace_device class, the minor is always dynamically allocated.
+ * @name: the name of the new trace device object
+ * @parent: a pointer to the parent device
+ * @tops: the trace routines operations structure.
+ * @fops: the trace driver operations structure.
+ *
+ * Creates and registers new trace device. Returns either an
+ * ERR_PTR() or a pointer to the newly allocated device.
+ */
+struct trace_device *trace_device_register(const char *name,
+		struct device *parent,
+		const struct trace_ops *tops,
+		const struct file_operations *fops)
+{
+	struct trace_device *td;
+	dev_t dev;
+	int i, err = 0;
+
+	pr_debug("trace_device_register: name=%s\n", name);
+
+	td = kzalloc(sizeof(struct trace_device), GFP_KERNEL);
+	if (!td)
+		return ERR_PTR(-ENOMEM);
+
+	cdev_init(&td->cdev, fops);
+	td->cdev.owner = THIS_MODULE;
+	td->tops = tops;
+
+	INIT_LIST_HEAD(&td->list);
+
+	mutex_lock(&trace_mtx);
+
+	/*  Dynamically allocate a minor */
+	i = find_first_zero_bit(trace_minors, DYNAMIC_MINORS);
+	if (i >= DYNAMIC_MINORS) {
+		mutex_unlock(&trace_mtx);
+		err = -EBUSY;
+		goto fail_minor;
+	}
+	td->minor = i;
+	set_bit(i, trace_minors);
+
+	dev = MKDEV(trace_major, td->minor);
+
+	err = cdev_add(&td->cdev, dev, 1);
+	if (err)
+		goto fail_dev_create;
+
+	td->this_device = device_create(trace_class, parent, dev,
+					  NULL, name);
+	if (IS_ERR(td->this_device)) {
+		err = PTR_ERR(td->this_device);
+		goto fail_dev_create;
+	}
+
+	/*
+	 * Add it to the front, so that later devices can "override"
+	 * earlier defaults
+	 */
+	list_add(&td->list, &trace_list);
+	mutex_unlock(&trace_mtx);
+	return td;
+
+fail_dev_create:
+	clear_bit(td->minor, trace_minors);
+fail_minor:
+	kfree(td);
+	mutex_unlock(&trace_mtx);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL(trace_device_register);
+
+/**
+ * trace_device_unregister - unregisters a trace device object.
+ * @td: the trace device object to be unregistered and freed.
+ *
+ * Unregisters a previously registered via trace_device_register object.
+ */
+int trace_device_unregister(struct trace_device *td)
+{
+	int minor = td->minor;
+
+	if (WARN_ON(list_empty(&td->list)))
+		return -EINVAL;
+
+	mutex_lock(&trace_mtx);
+	list_del(&td->list);
+	device_destroy(trace_class, MKDEV(trace_major, minor));
+	cdev_del(&td->cdev);
+	if (minor < DYNAMIC_MINORS && minor >= 0)
+		clear_bit(minor, trace_minors);
+	mutex_unlock(&trace_mtx);
+	return 0;
+}
+EXPORT_SYMBOL(trace_device_unregister);
+
+static int __init trace_init(void)
+{
+	int err;
+	dev_t dev;
+
+	trace_class = class_create(THIS_MODULE, "trace");
+	if (IS_ERR(trace_class)) {
+		err = PTR_ERR(trace_class);
+		printk(KERN_WARNING "Unable to create trace class err=%d\n",
+				err);
+		goto fail_class;
+	}
+
+	err = alloc_chrdev_region(&dev, 0, DYNAMIC_MINORS, "trace");
+	if (err)
+		goto fail_chrdev_region;
+	trace_major = MAJOR(dev);
+	return 0;
+
+fail_chrdev_region:
+	printk("unable to get major for trace devices\n");
+	class_destroy(trace_class);
+fail_class:
+	return err;
+}
+postcore_initcall(trace_init);
diff --git a/include/linux/hwtrace.h b/include/linux/hwtrace.h
new file mode 100644
index 0000000..8e55e1b
--- /dev/null
+++ b/include/linux/hwtrace.h
@@ -0,0 +1,242 @@
+/*
+ * Hardware trace device
+ *
+ * Copyright (C) 2011 ST-Ericsson SA
+ * Written on behalf of Linaro
+ *
+ * Author: Philippe Langlais <philippe.Langlais@linaro.org> for ST-Ericsson
+ *
+ * License terms: GNU General Public License (GPL), version 2.
+ */
+
+#ifndef _LINUX_TRACE_DEVICE_H
+#define _LINUX_TRACE_DEVICE_H
+
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/blktrace_api.h>
+
+/*
+ * Head entry for the doubly linked trace_device list
+ * For Internal use only (optimized static inline functions)
+ */
+extern struct list_head trace_list;
+
+/* Trace routines ops */
+struct trace_ops {
+	/* Duplicate printk output function to trace */
+	void (*printk_buf)(const char *buf, size_t size);
+	void (*trace_printk_buf)(unsigned long ip,
+		const char *buf, size_t size);
+	void (*trace_bprintk_buf)(unsigned long ip, const char *fmt,
+		const void *buf, size_t size);
+	void (*ftrace)(unsigned long ip, unsigned long parent_ip);
+	void (*sched_trace)(bool is_wakeup,
+		u32 prev_pid, u8 prev_prio, u8 prev_state,
+		u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu);
+	void (*stacks_trace)(bool is_user, size_t nr_entries,
+		const unsigned long *callers);
+	void (*blk_trace)(const struct blk_io_trace *blk);
+	void (*trace_branch)(unsigned int line, const char *func,
+		const char *file, char correct);
+	void (*trace_func_graph)(unsigned long func, int depth,
+		unsigned long long calltime, unsigned long long rettime,
+		unsigned long overrun);
+	void (*trace_mmio)(const void *data, size_t size);
+};
+
+struct trace_device {
+	int minor;
+	const struct trace_ops *tops;
+	struct list_head list;
+	struct cdev cdev;
+	struct device *this_device;
+};
+
+extern struct trace_device *trace_device_register(const char *name,
+	struct device *parent,
+	const struct trace_ops *ops,
+	const struct file_operations *fops);
+extern int trace_device_unregister(struct trace_device *td);
+
+/* printk equivalent for Hardware Trace driver */
+int hwtrace_printk(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
+
+#if defined(CONFIG_TRACE_DUP_PRINTK)
+static inline void hwtrace_dup_printk(char *buf, size_t size)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->printk_buf)
+			td->tops->printk_buf(buf, size);
+	}
+}
+#else
+static inline void hwtrace_dup_printk(char *buf, size_t size)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_PRINTK)
+static inline void hwtrace_trace_printk_buf(
+		unsigned long ip, const char *buf, size_t size)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->trace_printk_buf)
+			td->tops->trace_printk_buf(ip, buf, size);
+	}
+}
+
+static inline void hwtrace_trace_bprintk_buf(
+		unsigned long ip, const char *fmt, const void *buf, size_t size)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->trace_bprintk_buf)
+			td->tops->trace_bprintk_buf(ip, fmt, buf, size);
+	}
+}
+#else
+static inline void hwtrace_trace_printk_buf(
+		unsigned long ip, const char *buf, size_t size)
+{
+}
+
+static inline void hwtrace_trace_bprintk_buf(
+		unsigned long ip, const char *fmt, const void *buf, size_t size)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_FTRACE)
+static inline void hwtrace_ftrace(unsigned long ip, unsigned long parent_ip)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->ftrace)
+			td->tops->ftrace(ip, parent_ip);
+	}
+}
+#else
+static inline void hwtrace_ftrace(unsigned long ip, unsigned long parent_ip)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_CTX_SWITCH) || defined(CONFIG_TRACE_WAKEUP)
+static inline void hwtrace_sched(bool is_wakeup,
+	u32 prev_pid, u8 prev_prio, u8 prev_state,
+	u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->sched_trace)
+			td->tops->sched_trace(is_wakeup,
+				prev_pid, prev_prio, prev_state,
+				next_pid, next_prio, next_state, next_cpu);
+	}
+}
+#else
+static inline void hwtrace_sched(bool is_wakeup,
+	u32 prev_pid, u8 prev_prio, u8 prev_state,
+	u32 next_pid, u8 next_prio, u8 next_state, u32 next_cpu)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_STACK)
+static inline void hwtrace_stack_trace(bool is_user, size_t nr_entries,
+		unsigned long *callers)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->stacks_trace)
+			td->tops->stacks_trace(is_user, nr_entries, callers);
+	}
+}
+#else
+static inline void hwtrace_stack_trace(bool is_user, size_t nr_entries,
+		unsigned long *callers)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_BLK_DEV_IO)
+static inline void hwtrace_blk_trace(struct blk_io_trace *blk)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->blk_trace)
+			td->tops->blk_trace(blk);
+	}
+}
+#else
+static inline void hwtrace_blk_trace(struct blk_io_trace *blk)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_BRANCH)
+static inline void hwtrace_trace_branch(unsigned int line, const char *func,
+		const char *file, char correct)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->trace_branch)
+			td->tops->trace_branch(line, func, file, correct);
+	}
+}
+#else
+static inline void hwtrace_trace_branch(unsigned int line, const char *func,
+		const char *file, char correct)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_FUNC_GRAPH)
+static inline void hwtrace_func_graph(unsigned long func, int depth,
+		unsigned long long calltime, unsigned long long rettime,
+		unsigned long overrun)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->trace_func_graph)
+			td->tops->trace_func_graph(func, depth, calltime,
+				rettime, overrun);
+	}
+}
+#else
+static inline void hwtrace_func_graph(unsigned long func, int depth,
+		unsigned long long calltime, unsigned long long rettime,
+		unsigned long overrun)
+{
+}
+#endif
+
+#if defined(CONFIG_TRACE_MMIO)
+static inline void hwtrace_mmio(const void *data, size_t size)
+{
+	struct trace_device *td;
+
+	list_for_each_entry(td, &trace_list, list) {
+		if (td->tops->trace_mmio)
+			td->tops->trace_mmio(data, size);
+	}
+}
+#else
+static inline void hwtrace_mmio(const void *data, size_t size)
+{
+}
+#endif
+
+#endif
-- 
1.7.5

