interconnect: qcom: Add debug library

Add a debug library that provides QCOM-specific interconnect debug
features. This includes support for printing all enabled interconnect
votes to console when entering suspend.

Change-Id: I61302f95c94176f833bd1c88ddc76b88121b3f79
Signed-off-by: Mike Tipton <mdtipton@codeaurora.org>
This commit is contained in:
Mike Tipton 2021-01-12 12:31:37 -08:00 committed by Mike Tipton
parent 428497d097
commit 98b202c507
5 changed files with 201 additions and 1 deletions

View File

@ -178,3 +178,11 @@ config INTERCONNECT_QCOM_SMD_RPM
config INTERCONNECT_QCOM_QOS
tristate
config INTERCONNECT_QCOM_DEBUG
tristate "QCOM-specific interconnect debug features"
depends on INTERCONNECT_QCOM
help
This driver provides QCOM-specific interconnect debug features. These
features include optionally printing all enabled interconnect votes
when entering suspend.

View File

@ -43,3 +43,4 @@ obj-$(CONFIG_INTERCONNECT_QCOM_SM8350) += qnoc-sm8350.o
obj-$(CONFIG_INTERCONNECT_QCOM_SM8450) += qnoc-sm8450.o
obj-$(CONFIG_INTERCONNECT_QCOM_SMD_RPM) += icc-smd-rpm.o
obj-$(CONFIG_INTERCONNECT_QCOM_QOS) += qnoc-qos.o
obj-$(CONFIG_INTERCONNECT_QCOM_DEBUG) += icc-debug.o

View File

@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2021, The Linux Foundation. All rights reserved. */
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/interconnect-provider.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <trace/events/power.h>
#include "../internal.h"
static LIST_HEAD(icc_providers);
static DEFINE_MUTEX(debug_lock);
static struct dentry *dentry_suspend;
static bool debug_suspend;
struct qcom_icc_debug_provider {
struct list_head list;
struct icc_provider *provider;
};
static int icc_print_enabled(void)
{
struct qcom_icc_debug_provider *dp;
struct icc_provider *provider;
struct icc_node *n;
struct icc_req *r;
u32 avg_bw, peak_bw;
pr_info(" node tag avg peak\n");
pr_info("--------------------------------------------------------------------\n");
list_for_each_entry(dp, &icc_providers, list) {
provider = dp->provider;
list_for_each_entry(n, &provider->nodes, node_list) {
if (!n->avg_bw && !n->peak_bw)
continue;
pr_info("%-42s %12u %12u\n",
n->name, n->avg_bw, n->peak_bw);
hlist_for_each_entry(r, &n->req_list, req_node) {
if (!r->dev)
continue;
if (r->enabled) {
avg_bw = r->avg_bw;
peak_bw = r->peak_bw;
} else {
avg_bw = 0;
peak_bw = 0;
}
if (avg_bw || peak_bw)
pr_info(" %-27s %12u %12u %12u\n",
dev_name(r->dev), r->tag, avg_bw, peak_bw);
}
}
}
return 0;
}
static int icc_debug_suspend_get(void *data, u64 *val)
{
*val = debug_suspend;
return 0;
}
static void icc_debug_suspend_trace_probe(void *unused, const char *action,
int val, bool start)
{
if (start && val > 0 && !strcmp("machine_suspend", action)) {
pr_info("Enabled interconnect votes:\n");
icc_print_enabled();
}
}
static int icc_debug_suspend_set(void *data, u64 val)
{
int ret;
val = !!val;
if (val == debug_suspend)
return 0;
if (val)
ret = register_trace_suspend_resume(icc_debug_suspend_trace_probe, NULL);
else
ret = unregister_trace_suspend_resume(icc_debug_suspend_trace_probe, NULL);
if (ret) {
pr_err("%s: Failed to %sregister suspend trace callback, ret=%d\n",
__func__, val ? "" : "un", ret);
return ret;
}
debug_suspend = val;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(icc_debug_suspend_fops, icc_debug_suspend_get,
icc_debug_suspend_set, "%llu\n");
int qcom_icc_debug_register(struct icc_provider *provider)
{
struct qcom_icc_debug_provider *dp;
dp = kzalloc(sizeof(*dp), GFP_KERNEL);
if (!dp)
return -ENOMEM;
dp->provider = provider;
mutex_lock(&debug_lock);
list_add_tail(&dp->list, &icc_providers);
mutex_unlock(&debug_lock);
return 0;
}
EXPORT_SYMBOL(qcom_icc_debug_register);
int qcom_icc_debug_unregister(struct icc_provider *provider)
{
struct qcom_icc_debug_provider *dp, *temp;
mutex_lock(&debug_lock);
list_for_each_entry_safe(dp, temp, &icc_providers, list) {
if (dp->provider == provider) {
list_del(&dp->list);
kfree(dp);
}
}
mutex_unlock(&debug_lock);
return 0;
}
EXPORT_SYMBOL(qcom_icc_debug_unregister);
static int __init qcom_icc_debug_init(void)
{
static struct dentry *dir;
int ret;
dir = debugfs_lookup("interconnect", NULL);
if (IS_ERR_OR_NULL(dir)) {
ret = PTR_ERR(dir);
pr_err("%s: unable to find root interconnect debugfs directory, ret=%d\n",
__func__, ret);
return 0;
}
dentry_suspend = debugfs_create_file_unsafe("debug_suspend",
0644, dir, NULL,
&icc_debug_suspend_fops);
return 0;
}
module_init(qcom_icc_debug_init);
static void __exit qcom_icc_debug_exit(void)
{
debugfs_remove(dentry_suspend);
if (debug_suspend)
unregister_trace_suspend_resume(icc_debug_suspend_trace_probe, NULL);
}
module_exit(qcom_icc_debug_exit);
MODULE_DESCRIPTION("QCOM ICC debug library");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2021, The Linux Foundation. All rights reserved. */
#ifndef __QCOM_ICC_DEBUG_H__
#define __QCOM_ICC_DEBUG_H__
#include <linux/interconnect-provider.h>
int qcom_icc_debug_register(struct icc_provider *provider);
int qcom_icc_debug_unregister(struct icc_provider *provider);
#endif

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
*/
#include <linux/clk.h>
@ -13,6 +13,7 @@
#include <linux/slab.h>
#include "bcm-voter.h"
#include "icc-debug.h"
#include "icc-rpmh.h"
#include "qnoc-qos.h"
@ -329,6 +330,8 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev)
provider->set = qcom_icc_set;
provider->aggregate = qcom_icc_aggregate;
qcom_icc_debug_register(provider);
mutex_lock(&probe_list_lock);
list_add_tail(&qp->probe_list, &qnoc_probe_list);
mutex_unlock(&probe_list_lock);
@ -347,6 +350,7 @@ int qcom_icc_rpmh_remove(struct platform_device *pdev)
{
struct qcom_icc_provider *qp = platform_get_drvdata(pdev);
qcom_icc_debug_unregister(&qp->provider);
clk_bulk_put_all(qp->num_clks, qp->clks);
icc_nodes_remove(&qp->provider);