[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