[PATCH] coresight: etm4x: add CPU hotplug support for probing

Tamas Zsoldos tamas.zsoldos at arm.com
Thu Jun 16 08:18:57 PDT 2022


etm4x devices cannot be successfully probed when their CPU is offline.
This adds a mechanism to delay the probing in this case and try again
once the CPU is brought online.

Signed-off-by: Tamas Zsoldos <tamas.zsoldos at arm.com>
---
 .../coresight/coresight-etm4x-core.c          | 195 ++++++++++++++----
 1 file changed, 155 insertions(+), 40 deletions(-)

diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
index 7f416a12000e..f0bff08e9de9 100644
--- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
+++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
@@ -70,6 +70,14 @@ struct etm4_init_arg {
 	struct csdev_access	*csa;
 };
 
+struct etm4_delayed_probe {
+	u32			etm_pid;
+	struct device		*dev;
+};
+
+static DEFINE_PER_CPU(struct etm4_delayed_probe *, delayed_probe);
+static enum cpuhp_state hp_probe;
+
 /*
  * Check if TRCSSPCICRn(i) is implemented for a given instance.
  *
@@ -1938,48 +1946,20 @@ static void etm4_pm_clear(void)
 	}
 }
 
-static int etm4_probe(struct device *dev, void __iomem *base, u32 etm_pid)
+static int etm4_add_coresight_dev(struct device *dev,
+				  struct csdev_access *access)
 {
 	int ret;
 	struct coresight_platform_data *pdata = NULL;
-	struct etmv4_drvdata *drvdata;
+	struct etmv4_drvdata *drvdata = dev_get_drvdata(dev);
 	struct coresight_desc desc = { 0 };
-	struct etm4_init_arg init_arg = { 0 };
 	u8 major, minor;
 	char *type_name;
 
-	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
 	if (!drvdata)
-		return -ENOMEM;
-
-	dev_set_drvdata(dev, drvdata);
-
-	if (pm_save_enable == PARAM_PM_SAVE_FIRMWARE)
-		pm_save_enable = coresight_loses_context_with_cpu(dev) ?
-			       PARAM_PM_SAVE_SELF_HOSTED : PARAM_PM_SAVE_NEVER;
-
-	if (pm_save_enable != PARAM_PM_SAVE_NEVER) {
-		drvdata->save_state = devm_kmalloc(dev,
-				sizeof(struct etmv4_save_state), GFP_KERNEL);
-		if (!drvdata->save_state)
-			return -ENOMEM;
-	}
-
-	drvdata->base = base;
-
-	spin_lock_init(&drvdata->spinlock);
-
-	drvdata->cpu = coresight_get_cpu(dev);
-	if (drvdata->cpu < 0)
-		return drvdata->cpu;
-
-	init_arg.drvdata = drvdata;
-	init_arg.csa = &desc.access;
-	init_arg.pid = etm_pid;
+		return -EINVAL;
 
-	if (smp_call_function_single(drvdata->cpu,
-				etm4_init_arch_data,  &init_arg, 1))
-		dev_err(dev, "ETM arch init failed\n");
+	desc.access = *access;
 
 	if (!drvdata->arch)
 		return -EINVAL;
@@ -2050,6 +2030,69 @@ static int etm4_probe(struct device *dev, void __iomem *base, u32 etm_pid)
 	return 0;
 }
 
+static int etm4_probe(struct device *dev, void __iomem *base, u32 etm_pid)
+{
+	struct etmv4_drvdata *drvdata;
+	struct csdev_access access = { 0 };
+	struct etm4_init_arg init_arg = { 0 };
+	struct etm4_delayed_probe *delayed;
+
+	drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+	if (!drvdata)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, drvdata);
+
+	if (pm_save_enable == PARAM_PM_SAVE_FIRMWARE)
+		pm_save_enable = coresight_loses_context_with_cpu(dev) ?
+			       PARAM_PM_SAVE_SELF_HOSTED : PARAM_PM_SAVE_NEVER;
+
+	if (pm_save_enable != PARAM_PM_SAVE_NEVER) {
+		drvdata->save_state = devm_kmalloc(dev,
+				sizeof(struct etmv4_save_state), GFP_KERNEL);
+		if (!drvdata->save_state)
+			return -ENOMEM;
+	}
+
+	drvdata->base = base;
+
+	spin_lock_init(&drvdata->spinlock);
+
+	drvdata->cpu = coresight_get_cpu(dev);
+	if (drvdata->cpu < 0)
+		return drvdata->cpu;
+
+	init_arg.drvdata = drvdata;
+	init_arg.csa = &access;
+	init_arg.pid = etm_pid;
+
+	/*
+	 * Serialize against CPUHP callbacks to avoid race condition
+	 * between the smp call and saving the delayed probe.
+	 */
+	cpus_read_lock();
+	if (smp_call_function_single(drvdata->cpu,
+				etm4_init_arch_data,  &init_arg, 1)) {
+		/* The CPU was offline, try again once it comes online. */
+		delayed = devm_kmalloc(dev, sizeof(*delayed), GFP_KERNEL);
+		if (!delayed) {
+			cpus_read_unlock();
+			return -ENOMEM;
+		}
+
+		delayed->etm_pid = etm_pid;
+		delayed->dev = dev;
+
+		per_cpu(delayed_probe, drvdata->cpu) = delayed;
+
+		cpus_read_unlock();
+		return 0;
+	}
+	cpus_read_unlock();
+
+	return etm4_add_coresight_dev(dev, &access);
+}
+
 static int etm4_probe_amba(struct amba_device *adev, const struct amba_id *id)
 {
 	void __iomem *base;
@@ -2088,6 +2131,64 @@ static int etm4_probe_platform_dev(struct platform_device *pdev)
 	return ret;
 }
 
+static int etm4_probe_cpu(unsigned int cpu)
+{
+	int ret;
+	struct etm4_delayed_probe di;
+	struct csdev_access access = { 0 };
+	struct etm4_init_arg init_arg;
+	struct etm4_delayed_probe *dp = *this_cpu_ptr(&delayed_probe);
+
+	if (!dp)
+		return 0;
+
+	di = *dp;
+	devm_kfree(di.dev, dp);
+	*this_cpu_ptr(&delayed_probe) = NULL;
+
+	ret = pm_runtime_resume_and_get(di.dev);
+	if (ret < 0) {
+		dev_err(di.dev, "Failed to get PM runtime!\n");
+		return 0;
+	}
+
+	init_arg.drvdata =  dev_get_drvdata(di.dev);
+	if (!init_arg.drvdata)
+		return 0;
+
+	init_arg.csa = &access;
+	init_arg.pid = di.etm_pid;
+	etm4_init_arch_data(&init_arg);
+
+	etm4_add_coresight_dev(di.dev, &access);
+
+	pm_runtime_put(di.dev);
+	return 0;
+}
+
+static int etm4_setup_cpuhp_probe(void)
+{
+	int ret;
+
+	ret = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
+					"arm/coresight4:delayed_probe",
+					etm4_probe_cpu, NULL);
+
+	if (ret > 0) {
+		hp_probe = ret;
+		return 0;
+	}
+	return ret;
+}
+
+static void etm4_remove_cpuhp_probe(void)
+{
+	if (hp_probe)
+		cpuhp_remove_state_nocalls(hp_probe);
+
+	hp_probe = 0;
+}
+
 static struct amba_cs_uci_id uci_id_etm4[] = {
 	{
 		/*  ETMv4 UCI data */
@@ -2102,16 +2203,20 @@ static void clear_etmdrvdata(void *info)
 	int cpu = *(int *)info;
 
 	etmdrvdata[cpu] = NULL;
+	per_cpu(delayed_probe, cpu) = NULL;
 }
 
 static int __exit etm4_remove_dev(struct etmv4_drvdata *drvdata)
 {
-	etm_perf_symlink(drvdata->csdev, false);
+	bool had_dev;
 	/*
 	 * Taking hotplug lock here to avoid racing between etm4_remove_dev()
 	 * and CPU hotplug call backs.
 	 */
 	cpus_read_lock();
+
+	had_dev = etmdrvdata[drvdata->cpu];
+
 	/*
 	 * The readers for etmdrvdata[] are CPU hotplug call backs
 	 * and PM notification call backs. Change etmdrvdata[i] on
@@ -2119,12 +2224,15 @@ static int __exit etm4_remove_dev(struct etmv4_drvdata *drvdata)
 	 * inside one call back function.
 	 */
 	if (smp_call_function_single(drvdata->cpu, clear_etmdrvdata, &drvdata->cpu, 1))
-		etmdrvdata[drvdata->cpu] = NULL;
+		clear_etmdrvdata(&drvdata->cpu);
 
 	cpus_read_unlock();
 
-	cscfg_unregister_csdev(drvdata->csdev);
-	coresight_unregister(drvdata->csdev);
+	if (had_dev) {
+		etm_perf_symlink(drvdata->csdev, false);
+		cscfg_unregister_csdev(drvdata->csdev);
+		coresight_unregister(drvdata->csdev);
+	}
 
 	return 0;
 }
@@ -2217,12 +2325,18 @@ static int __init etm4x_init(void)
 	}
 
 	ret = platform_driver_register(&etm4_platform_driver);
-	if (!ret)
+	if (ret) {
+		pr_err("Error registering etm4x platform driver\n");
+		goto clear_amba;
+	}
+
+	if (!etm4_setup_cpuhp_probe())
 		return 0;
 
-	pr_err("Error registering etm4x platform driver\n");
-	amba_driver_unregister(&etm4x_amba_driver);
+	pr_err("Error setting up CPUHP delayed probe\n");
 
+clear_amba:
+	amba_driver_unregister(&etm4x_amba_driver);
 clear_pm:
 	etm4_pm_clear();
 	return ret;
@@ -2230,6 +2344,7 @@ static int __init etm4x_init(void)
 
 static void __exit etm4x_exit(void)
 {
+	etm4_remove_cpuhp_probe();
 	amba_driver_unregister(&etm4x_amba_driver);
 	platform_driver_unregister(&etm4_platform_driver);
 	etm4_pm_clear();
-- 
2.17.1




More information about the linux-arm-kernel mailing list