[PATCH v2 02/12] coresight-funnel: Support CPU cluster funnel initialization

Yuanfang Zhang
2025-12-18T08:10:46Z

Funnels associated with CPU clusters reside in the cluster's power domain.
Unlike dynamic funnels (which are typically system-wide), these per-cluster
funnels are only accessible when the cluster is powered on. Standard
runtime PM may not suffice to wake up a cluster from low-power states,
making direct register access unreliable.

Enhance the funnel driver to support these per-cluster devices:

1. Safe Initialization:
- Identify CPU cluster funnels via "qcom,cpu-bound-components".
- Use smp_call_function_single() to perform hardware initialization
(claim tag clearing) on a CPU within the cluster.
- Refactor the probe flow to encapsulate device registration in
funnel_add_coresight_dev().

2. Cross-CPU Enablement:
- Update funnel_enable() to use smp_call_function_single() when
enabling the hardware on a cluster-bound funnel.

3. Debug Interface Support:
- Update funnel_ctrl_show() to safely read the control register via
cross-CPU calls when necessary.

This ensures that funnel operations remain safe and functional even when
the associated CPU cluster is in aggressive low-power states.

Signed-off-by: Yuanfang Zhang <yuanfang.zhang@oss.qualcomm.com>
---
drivers/hwtracing/coresight/coresight-funnel.c | 183 ++++++++++++++++++++-----
1 file changed, 152 insertions(+), 31 deletions(-)

diff --git a/drivers/hwtracing/coresight/coresight-funnel.c b/drivers/hwtracing/coresight/coresight-funnel.c
index 3b248e54471a38f501777fe162fea850d1c851b3..a1264df84ab4c625c63dfbb9b7710b983a10c6b4 100644
--- a/drivers/hwtracing/coresight/coresight-funnel.c
+++ b/drivers/hwtracing/coresight/coresight-funnel.c
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
#include <linux/pm_runtime.h>
#include <linux/coresight.h>
#include <linux/amba/bus.h>
@@ -40,6 +41,7 @@ DEFINE_CORESIGHT_DEVLIST(funnel_devs, "funnel");
* @csdev: component vitals needed by the framework.
* @priority: port selection order.
* @spinlock: serialize enable/disable operations.
+ * @supported_cpus: Represent the CPUs related to this funnel.
*/
struct funnel_drvdata {
void __iomem *base;
@@ -48,6 +50,13 @@ struct funnel_drvdata {
struct coresight_device *csdev;
unsigned long priority;
raw_spinlock_t spinlock;
+ struct cpumask *supported_cpus;
+};
+
+struct funnel_smp_arg {
+ struct funnel_drvdata *drvdata;
+ int port;
+ int rc;
};

static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
@@ -76,6 +85,33 @@ static int dynamic_funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
return rc;
}

+static void funnel_enable_hw_smp_call(void *info)
+{
+ struct funnel_smp_arg *arg = info;
+
+ arg->rc = dynamic_funnel_enable_hw(arg->drvdata, arg->port);
+}
+
+static int funnel_enable_hw(struct funnel_drvdata *drvdata, int port)
+{
+ int cpu, ret;
+ struct funnel_smp_arg arg = { 0 };
+
+ if (!drvdata->supported_cpus)
+ return dynamic_funnel_enable_hw(drvdata, port);
+
+ arg.drvdata = drvdata;
+ arg.port = port;
+
+ for_each_cpu(cpu, drvdata->supported_cpus) {
+ ret = smp_call_function_single(cpu,
+ funnel_enable_hw_smp_call, &arg, 1);
+ if (!ret)
+ return arg.rc;
+ }
+ return ret;
+}
+
static int funnel_enable(struct coresight_device *csdev,
struct coresight_connection *in,
struct coresight_connection *out)
@@ -86,19 +122,24 @@ static int funnel_enable(struct coresight_device *csdev,
bool first_enable = false;

raw_spin_lock_irqsave(&drvdata->spinlock, flags);
- if (in->dest_refcnt == 0) {
- if (drvdata->base)
- rc = dynamic_funnel_enable_hw(drvdata, in->dest_port);
- if (!rc)
- first_enable = true;
- }
- if (!rc)
+
+ if (in->dest_refcnt == 0)
+ first_enable = true;
+ else
in->dest_refcnt++;
+
raw_spin_unlock_irqrestore(&drvdata->spinlock, flags);

- if (first_enable)
- dev_dbg(&csdev->dev, "FUNNEL inport %d enabled\n",
- in->dest_port);
+ if (first_enable) {
+ if (drvdata->base)
+ rc = funnel_enable_hw(drvdata, in->dest_port);
+ if (!rc) {
+ in->dest_refcnt++;
+ dev_dbg(&csdev->dev, "FUNNEL inport %d enabled\n",
+ in->dest_port);
+ }
+ }
+
return rc;
}

@@ -188,15 +229,39 @@ static u32 get_funnel_ctrl_hw(struct funnel_drvdata *drvdata)
return functl;
}

+static void get_funnel_ctrl_smp_call(void *info)
+{
+ struct funnel_smp_arg *arg = info;
+
+ arg->rc = get_funnel_ctrl_hw(arg->drvdata);
+}
+
static ssize_t funnel_ctrl_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
u32 val;
+ int cpu, ret;
struct funnel_drvdata *drvdata = dev_get_drvdata(dev->parent);
+ struct funnel_smp_arg arg = { 0 };

pm_runtime_get_sync(dev->parent);
-
- val = get_funnel_ctrl_hw(drvdata);
+ if (!drvdata->supported_cpus) {
+ val = get_funnel_ctrl_hw(drvdata);
+ } else {
+ arg.drvdata = drvdata;
+ for_each_cpu(cpu, drvdata->supported_cpus) {
+ ret = smp_call_function_single(cpu,
+ get_funnel_ctrl_smp_call, &arg, 1);
+ if (!ret)
+ break;
+ }
+ if (!ret) {
+ val = arg.rc;
+ } else {
+ pm_runtime_put(dev->parent);
+ return ret;
+ }
+ }

pm_runtime_put(dev->parent);

@@ -211,22 +276,68 @@ static struct attribute *coresight_funnel_attrs[] = {
};
ATTRIBUTE_GROUPS(coresight_funnel);

+static void funnel_clear_self_claim_tag(struct funnel_drvdata *drvdata)
+{
+ struct csdev_access access = CSDEV_ACCESS_IOMEM(drvdata->base);
+
+ coresight_clear_self_claim_tag(&access);
+}
+
+static void funnel_init_on_cpu(void *info)
+{
+ struct funnel_drvdata *drvdata = info;
+
+ funnel_clear_self_claim_tag(drvdata);
+}
+
+static int funnel_add_coresight_dev(struct device *dev)
+{
+ struct coresight_desc desc = { 0 };
+ struct funnel_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (drvdata->base) {
+ desc.groups = coresight_funnel_groups;
+ desc.access = CSDEV_ACCESS_IOMEM(drvdata->base);
+ }
+
+ desc.name = coresight_alloc_device_name(&funnel_devs, dev);
+ if (!desc.name)
+ return -ENOMEM;
+
+ desc.type = CORESIGHT_DEV_TYPE_LINK;
+ desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
+ desc.ops = &funnel_cs_ops;
+ desc.pdata = dev->platform_data;
+ desc.dev = dev;
+
+ drvdata->csdev = coresight_register(&desc);
+ if (IS_ERR(drvdata->csdev))
+ return PTR_ERR(drvdata->csdev);
+ return 0;
+}
+
+static struct cpumask *funnel_get_supported_cpus(struct device *dev)
+{
+ struct generic_pm_domain *pd;
+
+ pd = pd_to_genpd(dev->pm_domain);
+ if (pd)
+ return pd->cpus;
+
+ return NULL;
+}
+
static int funnel_probe(struct device *dev, struct resource *res)
{
void __iomem *base;
struct coresight_platform_data *pdata = NULL;
struct funnel_drvdata *drvdata;
- struct coresight_desc desc = { 0 };
- int ret;
+ int cpu, ret;

if (is_of_node(dev_fwnode(dev)) &&
of_device_is_compatible(dev->of_node, "arm,coresight-funnel"))
dev_warn_once(dev, "Uses OBSOLETE CoreSight funnel binding\n");

- desc.name = coresight_alloc_device_name(&funnel_devs, dev);
- if (!desc.name)
- return -ENOMEM;
-
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
@@ -244,9 +355,6 @@ static int funnel_probe(struct device *dev, struct resource *res)
if (IS_ERR(base))
return PTR_ERR(base);
drvdata->base = base;
- desc.groups = coresight_funnel_groups;
- desc.access = CSDEV_ACCESS_IOMEM(base);
- coresight_clear_self_claim_tag(&desc.access);
}

dev_set_drvdata(dev, drvdata);
@@ -258,23 +366,36 @@ static int funnel_probe(struct device *dev, struct resource *res)
dev->platform_data = pdata;

raw_spin_lock_init(&drvdata->spinlock);
- desc.type = CORESIGHT_DEV_TYPE_LINK;
- desc.subtype.link_subtype = CORESIGHT_DEV_SUBTYPE_LINK_MERG;
- desc.ops = &funnel_cs_ops;
- desc.pdata = pdata;
- desc.dev = dev;
- drvdata->csdev = coresight_register(&desc);
- if (IS_ERR(drvdata->csdev))
- return PTR_ERR(drvdata->csdev);

- return 0;
+ if (fwnode_property_present(dev_fwnode(dev), "qcom,cpu-bound-components")) {
+ drvdata->supported_cpus = funnel_get_supported_cpus(dev);
+ if (!drvdata->supported_cpus)
+ return -EINVAL;
+
+ cpus_read_lock();
+ for_each_cpu(cpu, drvdata->supported_cpus) {
+ ret = smp_call_function_single(cpu,
+ funnel_init_on_cpu, drvdata, 1);
+ if (!ret)
+ break;
+ }
+ cpus_read_unlock();
+
+ if (ret)
+ return 0;
+ } else if (res) {
+ funnel_clear_self_claim_tag(drvdata);
+ }
+
+ return funnel_add_coresight_dev(dev);
}

static int funnel_remove(struct device *dev)
{
struct funnel_drvdata *drvdata = dev_get_drvdata(dev);

- coresight_unregister(drvdata->csdev);
+ if (drvdata->csdev)
+ coresight_unregister(drvdata->csdev);

return 0;
}

--
2.34.1