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

Commit 278afe2c authored by Lina Iyer's avatar Lina Iyer Committed by Gerrit - the friendly Code Review server
Browse files

drivers: qcom: add system PM violators debug driver



Add driver to read MSGRAM and list the DRVs preventing system low power
modes.

sys_pm_violators stats can enabled by writing the mode (cxpc, aoss or
ddr) and periodicity of stats and triggering the AOP to record the stats
by writing to the 'aop_send_message' node.

Example:
echo "{class: lpm_mon, type: cxpc, dur: 1000, flush: 15, ts_adj: 1}" >
	/sys/kernel/debug/aop_send_message

The stats can be read out after the time period by reading the 'stats'
node.

cat /sys/kernel/debug/sys_pm_violators

Change-Id: I9d2501aa19108f015c7381104b2484a0d487cb3f
Signed-off-by: default avatarLina Iyer <ilina@codeaurora.org>
parent be77d9f5
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -716,6 +716,15 @@ config QTI_DDR_STATS_LOG
	  frequency residency and counts. The driver outputs information using
	  sysfs.

config QTI_SYS_PM_VX
	tristate "Qualcomm Technologies Inc (QTI) System PM Violators Driver"
	depends on QMP_DEBUGFS_CLIENT
	help
	  This option enables debug subystems that prevent system low power
	  modes. The user sends a QMP message to AOP to record subsystems
	  preventing deeper system low power modes. The data is stored in the
	  MSGRAM by AOP and read and reported in the debugfs by this driver.

config MSM_JTAGV8
	bool "Debug and ETM trace support across power collapse for ARMv8"
	default y if CORESIGHT_SOURCE_ETM4X
+1 −0
Original line number Diff line number Diff line
@@ -84,3 +84,4 @@ obj-$(CONFIG_QTI_HW_KEY_MANAGER) += hwkm.o crypto-qti-hwkm.o
ifdef CONFIG_DEBUG_FS
obj-$(CONFIG_MSM_RPM_SMD)   +=  rpm-smd-debug.o
endif
obj-$(CONFIG_QTI_SYS_PM_VX) += sys_pm_vx.o
+300 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

#define MAX_QMP_MSG_SIZE	96
#define MODE_AOSS		0xaa
#define MODE_CXPC		0xcc
#define MODE_DDR		0xdd
#define MODE_STR(m)		(m == MODE_CXPC ? "CXPC" :	\
				(m == MODE_AOSS ? "AOSS" :	\
				(m == MODE_DDR  ? "DDR"  : "")))

#define VX_MODE_MASK_TYPE		0xFF
#define VX_MODE_MASK_LOGSIZE		0xFF
#define VX_MODE_SHIFT_LOGSIZE		8
#define VX_FLAG_MASK_DUR		0xFFFF
#define VX_FLAG_MASK_TS			0xFF
#define VX_FLAG_SHIFT_TS		16
#define VX_FLAG_MASK_FLUSH_THRESH	0xFF
#define VX_FLAG_SHIFT_FLUSH_THRESH	24

#define read_word(base, itr) ({					\
		u32 v;						\
		v = le32_to_cpu(readl_relaxed(base + itr));	\
		pr_debug("Addr:%p val:%#x\n", base + itr, v);	\
		itr += sizeof(u32);				\
		v;						\
		})

struct vx_header {
	struct {
		u16 unused;
		u8 logsize;
		u8 type;
	} mode;
	struct {
		u8 flush_threshold;
		u8 ts_shift;
		u16 dur_ms;
	} flags;
};

struct vx_data {
	u32 ts;
	u32 *drv_vx;
};

struct vx_log {
	struct vx_header header;
	struct vx_data *data;
	int loglines;
};

struct vx_platform_data {
	void __iomem *base;
	struct dentry *vx_file;
	size_t ndrv;
	const char **drvs;
};

static const char * const drv_names_lahaina[] = {
	"TZ", "HYP", "HLOS", "L3", "SECPROC", "AUDIO", "SENSOR", "AOP",
	"DEBUG", "GPU", "DISPLAY", "COMPUTE", "MDM SW", "MDM HW", "WLAN RF",
	"WLAN BB", "DDR AUX", "ARC CPRF",
	""
};

static int read_vx_data(struct vx_platform_data *pd, struct vx_log *log)
{
	void __iomem *base = pd->base;
	struct vx_header *hdr = &log->header;
	struct vx_data *data;
	u32 *vx, val, itr = 0;
	int i, j, k;

	val = read_word(base, itr);
	if (!val)
		return -ENOENT;

	hdr->mode.type = val & VX_MODE_MASK_TYPE;
	hdr->mode.logsize = (val >> VX_MODE_SHIFT_LOGSIZE) &
				    VX_MODE_MASK_LOGSIZE;

	val = read_word(base, itr);
	if (!val)
		return -ENOENT;

	hdr->flags.dur_ms = val & VX_FLAG_MASK_DUR;
	hdr->flags.ts_shift = (val >> VX_FLAG_SHIFT_TS) & VX_FLAG_MASK_TS;
	hdr->flags.flush_threshold = (val >> VX_FLAG_SHIFT_FLUSH_THRESH) &
					     VX_FLAG_MASK_FLUSH_THRESH;

	data = kcalloc(hdr->mode.logsize, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	for (i = 0; i < hdr->mode.logsize; i++) {
		data[i].ts = read_word(base, itr);
		if (!data[i].ts)
			break;
		vx = kcalloc(ALIGN(pd->ndrv, 4), sizeof(*vx), GFP_KERNEL);
		if (!vx)
			goto no_mem;

		for (j = 0; j < pd->ndrv;) {
			val = read_word(base, itr);
			for (k = 0; k < 4; k++)
				vx[j++] = val >> (8 * k) & 0xFF;
		}
		data[i].drv_vx = vx;
	}

	log->data = data;
	log->loglines = i;

	return 0;
no_mem:
	for (j = 0; j < i; j++)
		kfree(data[j].drv_vx);
	kfree(data);

	return -ENOMEM;
}

static void show_vx_data(struct vx_platform_data *pd, struct vx_log *log,
			 struct seq_file *seq)
{
	int i, j;
	struct vx_header *hdr = &log->header;
	struct vx_data *data;
	u32 prev;

	seq_printf(seq, "Mode           : %s\n"
			"Duration (ms)  : %u\n"
			"Time Shift     : %u\n"
			"Flush Threshold: %u\n"
			"Max Log Entries: %u\n",
			MODE_STR(hdr->mode.type),
			hdr->flags.dur_ms,
			hdr->flags.ts_shift,
			hdr->flags.flush_threshold,
			hdr->mode.logsize);

	seq_puts(seq, "Timestamp|");

	for (i = 0; i < pd->ndrv; i++)
		seq_printf(seq, "%*s|", 8, pd->drvs[i]);
	seq_puts(seq, "\n");

	for (i = 0; i < log->loglines; i++) {
		data = &log->data[i];
		seq_printf(seq, "%*x|", 9, data->ts);
		/* An all-zero line indicates we entered LPM */
		for (j = 0, prev = data->drv_vx[0]; j < pd->ndrv; j++)
			prev |= data->drv_vx[j];
		if (!prev) {
			seq_printf(seq, "%s Enter", MODE_STR(hdr->mode.type));
			continue;
		}
		for (j = 0; j < pd->ndrv; j++)
			seq_printf(seq, "%*u|", 8, data->drv_vx[j]);
		seq_puts(seq, "\n");
	}
}

static int vx_show(struct seq_file *seq, void *data)
{
	struct vx_platform_data *pd = seq->private;
	struct vx_log log;
	int ret;
	int i;

	/*
	 * Read the data into memory to allow for
	 * post processing of data and present it
	 * cleanly.
	 */
	ret = read_vx_data(pd, &log);
	if (ret)
		return ret;

	show_vx_data(pd, &log, seq);

	for (i = 0; i < log.loglines; i++)
		kfree(log.data[i].drv_vx);
	kfree(log.data);

	return 0;
}

static int open_vx(struct inode *inode, struct file *file)
{
	return single_open(file, vx_show, inode->i_private);
}

static const struct file_operations sys_pm_vx_fops = {
	.open = open_vx,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = single_release,
};

static int vx_create_debug_nodes(struct vx_platform_data *pd)
{
	struct dentry *pf;

	pf = debugfs_create_file("sys_pm_violators", 0400, NULL,
				 pd, &sys_pm_vx_fops);
	if (!pf)
		return -EINVAL;

	pd->vx_file = pf;

	return 0;
}

static const struct of_device_id drv_match_table[] = {
	{ .compatible = "qcom,sys-pm-lahaina",
	  .data = drv_names_lahaina },
	{ }
};

static int vx_probe(struct platform_device *pdev)
{
	const struct of_device_id *match_id;
	struct vx_platform_data *pd;
	const char **drvs;
	int i, ret;

	pd = devm_kzalloc(&pdev->dev, sizeof(*pd), GFP_KERNEL);
	if (!pd)
		return -ENOMEM;

	pd->base = of_iomap(pdev->dev.of_node, 0);
	if (IS_ERR_OR_NULL(pd->base))
		return PTR_ERR(pd->base);

	match_id = of_match_node(drv_match_table, pdev->dev.of_node);
	if (!match_id)
		return -ENODEV;

	drvs = (const char **)match_id->data;
	for (i = 0; ; i++) {
		const char *name = (const char *)drvs[i];

		if (!name[0])
			break;
	}
	pd->ndrv = i;
	pd->drvs = drvs;

	ret = vx_create_debug_nodes(pd);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, pd);

	return 0;
}

static int vx_remove(struct platform_device *pdev)
{
	struct vx_platform_data *pd = platform_get_drvdata(pdev);

	debugfs_remove(pd->vx_file);

	return 0;
}

static const struct of_device_id vx_table[] = {
	{ .compatible = "qcom,sys-pm-violators" },
	{ }
};

static struct platform_driver vx_driver = {
	.probe = vx_probe,
	.remove = vx_remove,
	.driver = {
		.name = "sys-pm-violators",
		.of_match_table = vx_table,
	},
};
module_platform_driver(vx_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MSM System PM Violators Driver");
MODULE_ALIAS("platform:msm_sys_pm_vx");