diff --git a/drivers/interconnect/qcom/Kconfig b/drivers/interconnect/qcom/Kconfig index b2997f115d56..334978510c9d 100644 --- a/drivers/interconnect/qcom/Kconfig +++ b/drivers/interconnect/qcom/Kconfig @@ -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. diff --git a/drivers/interconnect/qcom/Makefile b/drivers/interconnect/qcom/Makefile index c4fdc45579df..b303950b30ff 100644 --- a/drivers/interconnect/qcom/Makefile +++ b/drivers/interconnect/qcom/Makefile @@ -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 diff --git a/drivers/interconnect/qcom/icc-debug.c b/drivers/interconnect/qcom/icc-debug.c new file mode 100644 index 000000000000..302b7c09e25d --- /dev/null +++ b/drivers/interconnect/qcom/icc-debug.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2021, The Linux Foundation. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/interconnect/qcom/icc-debug.h b/drivers/interconnect/qcom/icc-debug.h new file mode 100644 index 000000000000..d747854749c0 --- /dev/null +++ b/drivers/interconnect/qcom/icc-debug.h @@ -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 + +int qcom_icc_debug_register(struct icc_provider *provider); +int qcom_icc_debug_unregister(struct icc_provider *provider); + +#endif diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c index 1e3da6b98fe9..609c0d37b624 100644 --- a/drivers/interconnect/qcom/icc-rpmh.c +++ b/drivers/interconnect/qcom/icc-rpmh.c @@ -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 @@ -13,6 +13,7 @@ #include #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);