Merge "drm/msm: add event log for profiling msm drm driver events" into msm-4.8
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 0ce53b7..4a34ffb 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -62,7 +62,8 @@
 	msm_smmu.o \
 	msm_perf.o \
 	msm_rd.o \
-	msm_ringbuffer.o
+	msm_ringbuffer.o \
+	msm_evtlog.o \
 
 msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o
 msm-$(CONFIG_SYNC) += sde/sde_fence.o
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index b8bbfbe..44b2d32 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -43,6 +43,8 @@
 #include <drm/msm_drm.h>
 #include <drm/drm_gem.h>
 
+#include "msm_evtlog.h"
+
 struct msm_kms;
 struct msm_gpu;
 struct msm_mmu;
@@ -185,6 +187,8 @@
 	 * ioctl.
 	 */
 	struct task_struct *struct_mutex_task;
+
+	struct msm_evtlog evtlog;
 };
 
 struct msm_format {
diff --git a/drivers/gpu/drm/msm/msm_evtlog.c b/drivers/gpu/drm/msm/msm_evtlog.c
new file mode 100644
index 0000000..0562be4
--- /dev/null
+++ b/drivers/gpu/drm/msm/msm_evtlog.c
@@ -0,0 +1,172 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt)	"msm_evtlog:[%s] " fmt, __func__
+
+#include "msm_evtlog.h"
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <asm-generic/current.h>
+#include <linux/uaccess.h>
+#include <linux/debugfs.h>
+
+#define SIZE_MASK(x) (x - 1)
+
+static int msm_evtlog_debugfs_dump(struct seq_file *s, void *data)
+{
+	struct msm_evtlog *log = s->private;
+	unsigned long cnt;	/* # of samples since clear */
+	unsigned long n;	/* # of samples to print, also head index */
+	unsigned long i;
+	struct timespec timespec;
+
+	/**
+	 * Prints in chronological order, oldest -> newest
+	 * Note due to lock-less design, the first few printed entries
+	 * may be corrupted by new writer not oldest.
+	 * This is a tradeoff for speed of sampling
+	 */
+	cnt = atomic_read(&log->cnt);
+	if (!cnt)
+		return 0;
+
+	n = cnt & SIZE_MASK(log->size);
+
+	/**
+	 * If not full, print from first log
+	 * (which is index 1 since atomic_inc_return is prefix operator)
+	 */
+	i = (cnt < log->size) ? 0 : n;
+
+	seq_puts(s, "time_ns, pid, func, line, val1, val2, msg\n");
+	do {
+		i = (i + 1) & SIZE_MASK(log->size);
+		timespec = ktime_to_timespec(log->events[i].ktime);
+		seq_printf(s, "[%5lu.%06lu], %d, %s, %d, %llu, %llu, %s\n",
+				timespec.tv_sec,
+				timespec.tv_nsec / 1000,
+				log->events[i].pid,
+				log->events[i].func,
+				log->events[i].line,
+				log->events[i].val1,
+				log->events[i].val2,
+				log->events[i].msg);
+	} while (i != n);
+
+	return 0;
+}
+
+static int msm_evtlog_debugfs_open_dump(struct inode *inode, struct file *file)
+{
+	return single_open(file, msm_evtlog_debugfs_dump, inode->i_private);
+}
+
+static ssize_t msm_evtlog_debugfs_write(
+		struct file *file,
+		const char __user *user_buf,
+		size_t size,
+		loff_t *ppos)
+{
+	struct seq_file *s = file->private_data;
+	struct msm_evtlog *log = s->private;
+	char buf[64];
+	int buf_size;
+
+	buf_size = min(size, (sizeof(buf) - 1));
+	if (strncpy_from_user(buf, user_buf, buf_size) < 0)
+		return -EFAULT;
+	buf[buf_size] = 0;
+
+	if (strcmp(buf, "0") == 0)
+		atomic_set(&log->cnt, 0);
+
+	return size;
+
+}
+
+static const struct file_operations msm_evtlog_fops = {
+	.open =		msm_evtlog_debugfs_open_dump,
+	.read =		seq_read,
+	.write =	msm_evtlog_debugfs_write,
+	.llseek =	seq_lseek,
+	.release =	single_release,
+};
+
+int msm_evtlog_init(
+		struct msm_evtlog *log,
+		int size,
+		struct dentry *parent)
+{
+	if (!log || size < 1) {
+		pr_err("Invalid params\n");
+		return -EINVAL;
+	}
+
+	memset(log, 0, sizeof(*log));
+	log->size = roundup_pow_of_two(size);
+	log->events = kcalloc(log->size, sizeof(struct msm_evtlog_evt),
+			GFP_KERNEL);
+
+	if (!log->events) {
+		pr_err("Insufficient memory\n");
+		return -ENOMEM;
+	}
+
+	atomic_set(&log->cnt, 0);
+
+	log->dentry = debugfs_create_file("evtlog", 0644, parent,
+			log, &msm_evtlog_fops);
+
+	if (IS_ERR_OR_NULL(log->dentry)) {
+		int rc = PTR_ERR(log->dentry);
+
+		pr_err("debugfs create file failed, rc=%d\n", rc);
+		kfree(log->events);
+		return rc;
+	}
+
+	return 0;
+}
+
+void msm_evtlog_destroy(struct msm_evtlog *log)
+{
+	debugfs_remove(log->dentry);
+
+	/* Caller needs to make sure that log sampling has stopped */
+	kfree(log->events);
+
+}
+
+void msm_evtlog_sample(
+		struct msm_evtlog *log,
+		const char *func,
+		const char *msg,
+		uint64_t val1,
+		uint64_t val2,
+		uint32_t line)
+{
+	unsigned long i;
+
+	/**
+	 * Since array sized with pow of 2, roll to 0 when cnt overflows
+	 * mod the value with the size to get current idx into array
+	 */
+	i = (unsigned long)(atomic_inc_return(&log->cnt)) &
+			SIZE_MASK(log->size);
+	log->events[i].ktime = ktime_get();
+	log->events[i].func = func;
+	log->events[i].msg = msg;
+	log->events[i].val1 = val1;
+	log->events[i].val2 = val2;
+	log->events[i].line = line;
+	log->events[i].pid = current->pid;
+}
diff --git a/drivers/gpu/drm/msm/msm_evtlog.h b/drivers/gpu/drm/msm/msm_evtlog.h
new file mode 100644
index 0000000..15b5e96
--- /dev/null
+++ b/drivers/gpu/drm/msm/msm_evtlog.h
@@ -0,0 +1,102 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#ifndef MSM_MSM_EVTLOG_H_
+#define MSM_MSM_EVTLOG_H_
+
+#include <linux/ktime.h>
+#include <linux/atomic.h>
+#include <linux/dcache.h>
+
+/**
+ * struct msm_evtlog_evt - Event log entry
+ * @ktime:     Timestamp of event
+ * @func:      Calling function name
+ * @msg:       User provided string
+ * @val1:      User provided value
+ * @val2:      User provided value
+ * @line:      Line number of caller
+ * @pid:       Process id of logger
+ */
+struct msm_evtlog_evt {
+	ktime_t ktime;
+	const char *func;
+	const char *msg;
+	uint64_t val1;
+	uint64_t val2;
+	uint32_t line;
+	uint32_t pid;
+};
+
+/**
+ * struct msm_evtlog - current driver state information
+ * @events:    Pointer to dynamically allocated event log buffer
+ * @cnt:       Atomic number of events since clear. Can be used to calculate
+ *             the current index. Note: The count does not wrap.
+ *             Reset the event log by setting to zero.
+ *             Used for lock-less producer synchronization.
+ * @size:      Size of events array. Must be power of 2 to facilitate fast
+ *             increments by using a bitmask to get rollover.
+ * @dentry:    Filesystem entry of debugfs registration
+ */
+struct msm_evtlog {
+	struct msm_evtlog_evt *events;
+	atomic_t cnt;
+	unsigned long size;
+	struct dentry *dentry;
+};
+
+/**
+ * msm_evtlog_init() - Create an event log, registered with debugfs.
+ * @log:     Event log handle
+ * @size:    Max # of events in buffer. Will be rounded up to power of 2.
+ * @parent:  Parent directory entry for debugfs registration
+ *
+ * Return: error code.
+ */
+int msm_evtlog_init(struct msm_evtlog *log, int size, struct dentry *parent);
+
+/**
+ * msm_evtlog_destroy() - Destroy event log
+ * @log:            Event log handle
+ *
+ * Unregisters debugfs node and frees memory.
+ * Caller needs to make sure that log sampling has stopped.
+ */
+void msm_evtlog_destroy(struct msm_evtlog *log);
+
+/**
+ * msm_evtlog_sample() - Add entry to the event log
+ * @evtlog:            Event log handle
+ * @func:              Calling function name
+ * @msg:               User provided string
+ * @val1:              User provided value
+ * @val2:              User provided value
+ * @line:              Line number of caller
+ */
+void msm_evtlog_sample(
+		struct msm_evtlog *log,
+		const char *func,
+		const char *msg,
+		uint64_t val1,
+		uint64_t val2,
+		uint32_t line);
+
+/**
+ * Helper macros
+ */
+#define MSM_EVT_MSG(log, msg, x, y) ( \
+		msm_evtlog_sample((log), __func__, (msg), \
+			((uint64_t) x), (uint64_t)(y), __LINE__))
+#define MSM_EVT(log, x, y) MSM_EVT_MSG((log), 0, (x), (y))
+
+#endif /* MSM_MSM_EVTLOG_H_ */
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 2b63240..cfd690d 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -363,6 +363,7 @@
 	struct sde_crtc *sde_crtc = to_sde_crtc(crtc);
 	struct sde_kms *sde_kms = get_kms(crtc);
 	struct drm_device *dev = sde_kms->dev;
+	struct msm_drm_private *priv = dev->dev_private;
 	unsigned int pending;
 
 	pending = atomic_xchg(&sde_crtc->pending, 0);
@@ -373,6 +374,7 @@
 	if (sde_crtc->drm_requested_vblank) {
 		drm_handle_vblank(dev, sde_crtc->id);
 		DBG_IRQ("");
+		MSM_EVT(&priv->evtlog, sde_crtc->id, 0);
 	}
 }
 
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 55c200d..69b2206 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -27,6 +27,10 @@
 #define MAX_PHYS_ENCODERS_PER_VIRTUAL \
 	(MAX_H_TILES_PER_DISPLAY * NUM_PHYS_ENCODER_TYPES)
 
+#define EVTLOG(enc) (!enc ? NULL : \
+		!enc->base.dev ? NULL : \
+		&((struct msm_drm_private *)enc->base.dev->dev_private)->evtlog)
+
 /**
  * struct sde_encoder_virt - virtual encoder. Container of one or more physical
  *	encoders. Virtual encoder manages one "logical" display. Physical
@@ -205,6 +209,7 @@
 	}
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
+	MSM_EVT(EVTLOG(sde_enc), 0, 0);
 
 	for (i = 0; i < sde_enc->num_phys_encs; i++) {
 		struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
@@ -226,6 +231,8 @@
 	/* Call to populate mode->crtc* information required by framework */
 	drm_mode_set_crtcinfo(adj_mode, 0);
 
+	MSM_EVT(EVTLOG(sde_enc), adj_mode->flags, adj_mode->private_flags);
+
 	return ret;
 }
 
@@ -244,6 +251,7 @@
 	}
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
+	MSM_EVT(EVTLOG(sde_enc), 0, 0);
 
 	for (i = 0; i < sde_enc->num_phys_encs; i++) {
 		struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
@@ -272,6 +280,7 @@
 	}
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
+	MSM_EVT(EVTLOG(sde_enc), 0, 0);
 
 	bs_set(sde_enc, 1);
 
@@ -308,6 +317,7 @@
 	}
 
 	sde_enc = to_sde_encoder_virt(drm_enc);
+	MSM_EVT(EVTLOG(sde_enc), 0, 0);
 
 	for (i = 0; i < sde_enc->num_phys_encs; i++) {
 		struct sde_encoder_phys *phys = sde_enc->phys_encs[i];
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index 7a25ffa..c45e6c4 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -581,7 +581,7 @@
 	sde_debugfs_init(sde_kms);
 	msm_evtlog_init(&priv->evtlog, SDE_EVTLOG_SIZE,
 			sde_debugfs_get_root(sde_kms));
-	MSM_EVT(dev, 0, 0);
+	MSM_EVT(&priv->evtlog, 0, 0);
 
 	/*
 	 * modeset_init should create the DRM related objects i.e. CRTCs,