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: Lina Iyer <ilina@codeaurora.org>
This commit is contained in:
Lina Iyer 2020-05-29 15:28:33 -06:00 • committed by Gerrit - the friendly Code Review server
commit 278afe2c3a
3 changed files with 310 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,300 @@
// 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");