Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ac61ccd authored by Clarence Ip's avatar Clarence Ip
Browse files

drm/msm: support optional messaging in event log



This patch updates the event logging facility to allow
for more fine grained control of the debug messages that
are recorded by the event log. A new 'VERBOSE' macro is
provided to specify messages that may be optionally omitted,
and a debugfs entry is provided to specify a logging filter
based on partial function names. If a filter is specified,
only messages from functions containing any of the filter
keys as part of their name will be logged.

The filtering may be disabled by echoing an empty string
to the debugfs file.

CRs-Fixed: 2023394
Change-Id: Id1476b7f09839e46ef6122837b2f11372fd6a05e
Signed-off-by: default avatarClarence Ip <cip@codeaurora.org>
parent 436b4a5f
Loading
Loading
Loading
Loading
+80 −2
Original line number Diff line number Diff line
@@ -2436,7 +2436,7 @@ void sde_dbg_dump(bool queue_work, const char *name, ...)
	struct sde_dbg_reg_base **blk_arr;
	u32 blk_len;

	if (!sde_evtlog_is_enabled(sde_dbg_base.evtlog, SDE_EVTLOG_DEFAULT))
	if (!sde_evtlog_is_enabled(sde_dbg_base.evtlog, SDE_EVTLOG_ALWAYS))
		return;

	if (queue_work && work_pending(&sde_dbg_base.dump_work))
@@ -2558,6 +2558,82 @@ static const struct file_operations sde_evtlog_fops = {
	.write = sde_evtlog_dump_write,
};

/*
 * sde_evtlog_filter_show - read callback for evtlog filter
 * @s: pointer to seq_file object
 * @data: pointer to private data
 */
static int sde_evtlog_filter_show(struct seq_file *s, void *data)
{
	struct sde_dbg_evtlog *evtlog;
	char buffer[64];
	int i;

	if (!s || !s->private)
		return -EINVAL;

	evtlog = s->private;

	for (i = 0; !sde_evtlog_get_filter(
				evtlog, i, buffer, ARRAY_SIZE(buffer)); ++i)
		seq_printf(s, "*%s*\n", buffer);
	return 0;
}

/*
 * sde_evtlog_filter_open - debugfs open handler for evtlog filter
 * @inode: debugfs inode
 * @file: file handle
 * Returns: zero on success
 */
static int sde_evtlog_filter_open(struct inode *inode, struct file *file)
{
	if (!file)
		return -EINVAL;

	return single_open(file, sde_evtlog_filter_show, inode->i_private);
}

/*
 * sde_evtlog_filter_write - write callback for evtlog filter
 * @file: pointer to file structure
 * @user_buf: pointer to incoming user data
 * @count: size of incoming user buffer
 * @ppos: pointer to file offset
 */
static ssize_t sde_evtlog_filter_write(struct file *file,
	const char __user *user_buf, size_t count, loff_t *ppos)
{
	char *tmp_filter = NULL;
	ssize_t rc = 0;

	if (count > 0) {
		/* copy user provided string and null terminate it */
		tmp_filter = kzalloc(count + 1, GFP_KERNEL);
		if (!tmp_filter)
			rc = -ENOMEM;
		else if (copy_from_user(tmp_filter, user_buf, count))
			rc = -EFAULT;
	}

	/* update actual filter configuration on success */
	if (!rc) {
		sde_evtlog_set_filter(sde_dbg_base.evtlog, tmp_filter);
		rc = count;
	}
	kfree(tmp_filter);

	return rc;
}

static const struct file_operations sde_evtlog_filter_fops = {
	.open =		sde_evtlog_filter_open,
	.write =	sde_evtlog_filter_write,
	.read =		seq_read,
	.llseek =	seq_lseek,
	.release =	seq_release
};

/**
 * sde_dbg_reg_base_release - release allocated reg dump file private data
 * @inode: debugfs inode
@@ -2799,12 +2875,14 @@ int sde_dbg_debugfs_register(struct dentry *debugfs_root)
			&sde_evtlog_fops);
	debugfs_create_u32("enable", 0644, sde_dbg_base.root,
			&(sde_dbg_base.evtlog->enable));
	debugfs_create_file("filter", 0644, sde_dbg_base.root,
			sde_dbg_base.evtlog,
			&sde_evtlog_filter_fops);
	debugfs_create_u32("panic", 0644, sde_dbg_base.root,
			&sde_dbg_base.panic_on_err);
	debugfs_create_u32("reg_dump", 0644, sde_dbg_base.root,
			&sde_dbg_base.enable_reg_dump);


	if (dbg->dbgbus_sde.entries) {
		dbg->dbgbus_sde.cmn.name = DBGBUS_NAME_SDE;
		snprintf(debug_name, sizeof(debug_name), "%s_dbgbus",
+48 −6
Original line number Diff line number Diff line
@@ -24,9 +24,10 @@
#define SDE_DBG_DUMP_DATA_LIMITER (NULL)

enum sde_dbg_evtlog_flag {
	SDE_EVTLOG_DEFAULT = BIT(0),
	SDE_EVTLOG_CRITICAL = BIT(0),
	SDE_EVTLOG_IRQ = BIT(1),
	SDE_EVTLOG_ALL = BIT(7)
	SDE_EVTLOG_VERBOSE = BIT(2),
	SDE_EVTLOG_ALWAYS = -1
};

enum sde_dbg_dump_flag {
@@ -35,7 +36,7 @@ enum sde_dbg_dump_flag {
};

#ifdef CONFIG_DRM_SDE_EVTLOG_DEBUG
#define SDE_EVTLOG_DEFAULT_ENABLE 1
#define SDE_EVTLOG_DEFAULT_ENABLE SDE_EVTLOG_CRITICAL
#else
#define SDE_EVTLOG_DEFAULT_ENABLE 0
#endif
@@ -72,6 +73,9 @@ struct sde_dbg_evtlog_log {
	int pid;
};

/**
 * @filter_list: Linked list of currently active filter strings
 */
struct sde_dbg_evtlog {
	struct sde_dbg_evtlog_log logs[SDE_EVTLOG_ENTRY];
	u32 first;
@@ -80,6 +84,7 @@ struct sde_dbg_evtlog {
	u32 next;
	u32 enable;
	spinlock_t spin_lock;
	struct list_head filter_list;
};

extern struct sde_dbg_evtlog *sde_dbg_base_evtlog;
@@ -89,7 +94,15 @@ extern struct sde_dbg_evtlog *sde_dbg_base_evtlog;
 * ... - variable arguments
 */
#define SDE_EVT32(...) sde_evtlog_log(sde_dbg_base_evtlog, __func__, \
		__LINE__, SDE_EVTLOG_DEFAULT, ##__VA_ARGS__, \
		__LINE__, SDE_EVTLOG_ALWAYS, ##__VA_ARGS__, \
		SDE_EVTLOG_DATA_LIMITER)

/**
 * SDE_EVT32_VERBOSE - Write a list of 32bit values for verbose event logging
 * ... - variable arguments
 */
#define SDE_EVT32_VERBOSE(...) sde_evtlog_log(sde_dbg_base_evtlog, __func__, \
		__LINE__, SDE_EVTLOG_VERBOSE, ##__VA_ARGS__, \
		SDE_EVTLOG_DATA_LIMITER)

/**
@@ -244,6 +257,24 @@ void sde_dbg_reg_register_dump_range(const char *base_name,
		const char *range_name, u32 offset_start, u32 offset_end,
		uint32_t xin_id);

/**
 * sde_evtlog_set_filter - update evtlog filtering
 * @evtlog:	pointer to evtlog
 * @filter:     pointer to optional function name filter, set to NULL to disable
 */
void sde_evtlog_set_filter(struct sde_dbg_evtlog *evtlog, char *filter);

/**
 * sde_evtlog_get_filter - query configured evtlog filters
 * @evtlog:	pointer to evtlog
 * @index:	filter index to retrieve
 * @buf:	pointer to output filter buffer
 * @bufsz:	size of output filter buffer
 * Returns:	zero if a filter string was returned
 */
int sde_evtlog_get_filter(struct sde_dbg_evtlog *evtlog, int index,
		char *buf, size_t bufsz);

#else
static inline struct sde_dbg_evtlog *sde_evtlog_init(void)
{
@@ -275,7 +306,7 @@ static inline ssize_t sde_evtlog_dump_to_buffer(struct sde_dbg_evtlog *evtlog,
	return 0;
}

void sde_dbg_init_dbg_buses(u32 hwversion)
static inline void sde_dbg_init_dbg_buses(u32 hwversion)
{
}

@@ -285,7 +316,7 @@ static inline int sde_dbg_init(struct device *dev,
	return 0;
}

int sde_dbg_debugfs_register(struct dentry *debugfs_root)
static inline int sde_dbg_debugfs_register(struct dentry *debugfs_root)
{
	return 0;
}
@@ -310,6 +341,17 @@ static inline void sde_dbg_reg_register_dump_range(const char *base_name,
{
}

static inline void sde_evtlog_set_filter(
		struct sde_dbg_evtlog *evtlog, char *filter)
{
}

static inline int sde_evtlog_get_filter(struct sde_dbg_evtlog *evtlog,
		int index, char *buf, size_t bufsz)
{
	return -EINVAL;
}

#endif /* defined(CONFIG_DEBUG_FS) */


+147 −27
Original line number Diff line number Diff line
@@ -23,13 +23,40 @@
#include "sde_dbg.h"
#include "sde_trace.h"

bool sde_evtlog_is_enabled(struct sde_dbg_evtlog *evtlog, u32 flag)
#define SDE_EVTLOG_FILTER_STRSIZE	64

struct sde_evtlog_filter {
	struct list_head list;
	char filter[SDE_EVTLOG_FILTER_STRSIZE];
};

static bool _sde_evtlog_is_filtered_no_lock(
		struct sde_dbg_evtlog *evtlog, const char *str)
{
	if (!evtlog)
		return false;
	struct sde_evtlog_filter *filter_node;
	bool rc;

	if (!str)
		return true;

	/*
	 * Filter the incoming string IFF the list is not empty AND
	 * a matching entry is not in the list.
	 */
	rc = !list_empty(&evtlog->filter_list);
	list_for_each_entry(filter_node, &evtlog->filter_list, list)
		if (strnstr(str, filter_node->filter,
					SDE_EVTLOG_FILTER_STRSIZE - 1)) {
			rc = false;
			break;
		}

	return (flag & evtlog->enable) ||
		(flag == SDE_EVTLOG_ALL && evtlog->enable);
	return rc;
}

bool sde_evtlog_is_enabled(struct sde_dbg_evtlog *evtlog, u32 flag)
{
	return evtlog && (evtlog->enable & flag);
}

void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, const char *name, int line,
@@ -47,6 +74,10 @@ void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, const char *name, int line,
		return;

	spin_lock_irqsave(&evtlog->spin_lock, flags);

	if (_sde_evtlog_is_filtered_no_lock(evtlog, name))
		goto exit;

	log = &evtlog->logs[evtlog->curr];
	log->time = ktime_to_us(ktime_get());
	log->name = name;
@@ -70,27 +101,20 @@ void sde_evtlog_log(struct sde_dbg_evtlog *evtlog, const char *name, int line,

	trace_sde_evtlog(name, line, i > 0 ? log->data[0] : 0,
			i > 1 ? log->data[1] : 0);

exit:
	spin_unlock_irqrestore(&evtlog->spin_lock, flags);
}

/* always dump the last entries which are not dumped yet */
static bool _sde_evtlog_dump_calc_range(struct sde_dbg_evtlog *evtlog)
{
	bool need_dump = true;
	unsigned long flags;

	if (!evtlog)
		return false;

	spin_lock_irqsave(&evtlog->spin_lock, flags);

	evtlog->first = evtlog->next;

	if (evtlog->last == evtlog->first) {
		need_dump = false;
		goto dump_exit;
	}
	if (evtlog->last == evtlog->first)
		return false;

	if (evtlog->last < evtlog->first) {
		evtlog->first %= SDE_EVTLOG_ENTRY;
@@ -99,16 +123,14 @@ static bool _sde_evtlog_dump_calc_range(struct sde_dbg_evtlog *evtlog)
	}

	if ((evtlog->last - evtlog->first) > SDE_EVTLOG_PRINT_ENTRY) {
		pr_warn("evtlog buffer overflow before dump: %d\n",
			evtlog->last - evtlog->first);
		pr_info("evtlog skipping %d entries, last=%d\n",
			evtlog->last - evtlog->first - SDE_EVTLOG_PRINT_ENTRY,
			evtlog->last - 1);
		evtlog->first = evtlog->last - SDE_EVTLOG_PRINT_ENTRY;
	}
	evtlog->next = evtlog->first + 1;

dump_exit:
	spin_unlock_irqrestore(&evtlog->spin_lock, flags);

	return need_dump;
	return true;
}

ssize_t sde_evtlog_dump_to_buffer(struct sde_dbg_evtlog *evtlog,
@@ -122,16 +144,15 @@ ssize_t sde_evtlog_dump_to_buffer(struct sde_dbg_evtlog *evtlog,
	if (!evtlog || !evtlog_buf)
		return 0;

	spin_lock_irqsave(&evtlog->spin_lock, flags);

	/* update markers, exit if nothing to print */
	if (!_sde_evtlog_dump_calc_range(evtlog))
		return 0;

	spin_lock_irqsave(&evtlog->spin_lock, flags);
		goto exit;

	log = &evtlog->logs[evtlog->first % SDE_EVTLOG_ENTRY];

	prev_log = &evtlog->logs[(evtlog->first - 1) %
		SDE_EVTLOG_ENTRY];
	prev_log = &evtlog->logs[(evtlog->first - 1) % SDE_EVTLOG_ENTRY];

	off = snprintf((evtlog_buf + off), (evtlog_buf_size - off), "%s:%-4d",
		log->name, log->line);
@@ -150,7 +171,7 @@ ssize_t sde_evtlog_dump_to_buffer(struct sde_dbg_evtlog *evtlog,
			"%x ", log->data[i]);

	off += snprintf((evtlog_buf + off), (evtlog_buf_size - off), "\n");

exit:
	spin_unlock_irqrestore(&evtlog->spin_lock, flags);

	return off;
@@ -178,10 +199,109 @@ struct sde_dbg_evtlog *sde_evtlog_init(void)
	spin_lock_init(&evtlog->spin_lock);
	evtlog->enable = SDE_EVTLOG_DEFAULT_ENABLE;

	INIT_LIST_HEAD(&evtlog->filter_list);

	return evtlog;
}

int sde_evtlog_get_filter(struct sde_dbg_evtlog *evtlog, int index,
		char *buf, size_t bufsz)
{
	struct sde_evtlog_filter *filter_node;
	unsigned long flags;
	int rc = -EFAULT;

	if (!evtlog || !buf || !bufsz || index < 0)
		return -EINVAL;

	spin_lock_irqsave(&evtlog->spin_lock, flags);
	list_for_each_entry(filter_node, &evtlog->filter_list, list) {
		if (index--)
			continue;

		/* don't care about return value */
		(void)strlcpy(buf, filter_node->filter, bufsz);
		rc = 0;
		break;
	}
	spin_unlock_irqrestore(&evtlog->spin_lock, flags);

	return rc;
}

void sde_evtlog_set_filter(struct sde_dbg_evtlog *evtlog, char *filter)
{
	struct sde_evtlog_filter *filter_node, *tmp;
	struct list_head free_list;
	unsigned long flags;
	char *flt;

	if (!evtlog)
		return;

	INIT_LIST_HEAD(&free_list);

	/*
	 * Clear active filter list and cache filter_nodes locally
	 * to reduce memory fragmentation.
	 */
	spin_lock_irqsave(&evtlog->spin_lock, flags);
	list_for_each_entry_safe(filter_node, tmp, &evtlog->filter_list, list) {
		list_del_init(&filter_node->list);
		list_add_tail(&filter_node->list, &free_list);
	}
	spin_unlock_irqrestore(&evtlog->spin_lock, flags);

	/*
	 * Parse incoming filter request string and build up a new
	 * filter list. New filter nodes are taken from the local
	 * free list, if available, and allocated from the system
	 * heap once the free list is empty.
	 */
	while (filter && (flt = strsep(&filter, "|\r\n\t ")) != NULL) {
		if (!*flt)
			continue;

		if (list_empty(&free_list)) {
			filter_node = kzalloc(sizeof(*filter_node), GFP_KERNEL);
			if (!filter_node)
				break;

			INIT_LIST_HEAD(&filter_node->list);
		} else {
			filter_node = list_first_entry(&free_list,
					struct sde_evtlog_filter, list);
			list_del_init(&filter_node->list);
		}

		/* don't care if copy truncated */
		(void)strlcpy(filter_node->filter, flt,
				SDE_EVTLOG_FILTER_STRSIZE);

		spin_lock_irqsave(&evtlog->spin_lock, flags);
		list_add_tail(&filter_node->list, &evtlog->filter_list);
		spin_unlock_irqrestore(&evtlog->spin_lock, flags);
	}

	/*
	 * Free any unused filter_nodes back to the system.
	 */
	list_for_each_entry_safe(filter_node, tmp, &free_list, list) {
		list_del(&filter_node->list);
		kfree(filter_node);
	}
}

void sde_evtlog_destroy(struct sde_dbg_evtlog *evtlog)
{
	struct sde_evtlog_filter *filter_node, *tmp;

	if (!evtlog)
		return;

	list_for_each_entry_safe(filter_node, tmp, &evtlog->filter_list, list) {
		list_del(&filter_node->list);
		kfree(filter_node);
	}
	kfree(evtlog);
}