Fix PM save on ETE, which is an issue that showed up on the FVP when
booted with ACPI and the newly enabled idle states. Then refactor to
clarify and avoid any probe order issues.
Signed-off-by: James Clark <james.clark(a)linaro.org>
---
James Clark (2):
coresight: ete: Always save state on power down
coresight: etm4x: Refactor pm_save_enable handling
drivers/hwtracing/coresight/coresight-etm4x-core.c | 55 ++++++++++++++++------
drivers/hwtracing/coresight/coresight-etm4x.h | 1 +
2 files changed, 42 insertions(+), 14 deletions(-)
---
base-commit: 971f3474f8898ae8bbab19a9b547819a5e6fbcf1
change-id: 20260420-james-cs-ete-pm_save_enable-4e994e35cdac
Best regards,
--
James Clark <james.clark(a)linaro.org>
On Tue, Apr 28, 2026 at 10:25:11AM +0800, Yingchao Deng (Consultant) wrote:
[...]
> > tg->used_mask = bitmap_zalloc(nr_filter_sigs, GFP_KERNEL);
> "nr_filter_sigs" is the count of entries in the DT property array, if the DT
> property is:
> arm,trig-filters = <22 23>;
> Here nr_filter_sigs=2, so bitmap_zalloc(2) allocates only 1 unsigned long
> (64 bits). set_bit(22/23, used_mask) still fits, but it's logically an OOB,
> and any index >=64 would
> write past the end.
Thanks for explanation. It is correct for me.
On Sun, Apr 26, 2026 at 05:44:39PM +0800, Yingchao Deng wrote:
> Introduce a small encoding to carry the register index together with the
> base offset in a single u32, and use a common helper to compute the final
> MMIO address. This refactors register access to be based on the encoded
> (reg, nr) pair, reducing duplicated arithmetic and making it easier to
> support variants that bank or relocate trigger-indexed registers.
>
> Signed-off-by: Yingchao Deng <yingchao.deng(a)oss.qualcomm.com>
> ---
> drivers/hwtracing/coresight/coresight-cti-core.c | 31 +++++++++++++++--------
> drivers/hwtracing/coresight/coresight-cti-sysfs.c | 4 +--
> drivers/hwtracing/coresight/coresight-cti.h | 16 ++++++++++--
> 3 files changed, 36 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/hwtracing/coresight/coresight-cti-core.c b/drivers/hwtracing/coresight/coresight-cti-core.c
> index 4e7d12bd2d3e..c4cbeb64365b 100644
> --- a/drivers/hwtracing/coresight/coresight-cti-core.c
> +++ b/drivers/hwtracing/coresight/coresight-cti-core.c
> @@ -42,6 +42,14 @@ static DEFINE_MUTEX(ect_mutex);
> #define csdev_to_cti_drvdata(csdev) \
> dev_get_drvdata(csdev->dev.parent)
>
> +static void __iomem *cti_reg_addr(struct cti_drvdata *drvdata, int reg)
> +{
> + u32 offset = CTI_REG_CLR_NR(reg);
> + u32 nr = CTI_REG_GET_NR(reg);
> +
> + return drvdata->base + offset + sizeof(u32) * nr;
> +}
Could you try below change, which is more straightforward?
static void __iomem *__reg_addr(struct cti_drvdata *drvdata, int off,
int index)
{
return drvdata->base + offset + sizeof(u32) * index;
}
#define reg_addr(drvdata, off) \
__reg_addr((drvdata), (off), 0)
#define reg_index_addr(drvdata, off, i) \
__reg_addr((drvdata), (off), (i))
> +
> /* write set of regs to hardware - call with spinlock claimed */
> void cti_write_all_hw_regs(struct cti_drvdata *drvdata)
> {
> @@ -55,16 +63,17 @@ void cti_write_all_hw_regs(struct cti_drvdata *drvdata)
>
> /* write the CTI trigger registers */
> for (i = 0; i < config->nr_trig_max; i++) {
> - writel_relaxed(config->ctiinen[i], drvdata->base + CTIINEN(i));
> + writel_relaxed(config->ctiinen[i],
> + cti_reg_addr(drvdata, CTI_REG_SET_NR(CTIINEN, i)));
writel_relaxed(config->ctiinen[i],
reg_index_addr(drvdata, CTIINEN, i));
> writel_relaxed(config->ctiouten[i],
> - drvdata->base + CTIOUTEN(i));
> + cti_reg_addr(drvdata, CTI_REG_SET_NR(CTIOUTEN, i)));
writel_relaxed(config->ctiouten[i],
reg_index_addr(drvdata, CTIOUTEN, i));
[...]
> +/*
> + * Encode CTI register offset and register index in one u32:
> + * - bits[0:11] : base register offset (0x000 to 0xFFF)
> + * - bits[24:31] : register index (nr)
> + */
> +#define CTI_REG_NR_MASK GENMASK(31, 24)
> +#define CTI_REG_GET_NR(reg) FIELD_GET(CTI_REG_NR_MASK, (reg))
> +#define CTI_REG_SET_NR_CONST(reg, nr) ((reg) | FIELD_PREP_CONST(CTI_REG_NR_MASK, (nr)))
> +#define CTI_REG_SET_NR(reg, nr) ((reg) | FIELD_PREP(CTI_REG_NR_MASK, (nr)))
> +#define CTI_REG_CLR_NR(reg) ((reg) & (~CTI_REG_NR_MASK))
I know this might come from my suggestion, and it is also will be
heavily used in patch 04. We can have strightforward way to
implement this, please drop these macros.
I will reply in patch 04 separately. Sorry my review might cause
extra effort.
Thanks,
Leo
On Sun, Apr 26, 2026 at 05:44:38PM +0800, Yingchao Deng wrote:
[...]
> @@ -316,23 +316,33 @@ static int cti_plat_process_filter_sigs(struct cti_drvdata *drvdata,
> {
> struct cti_trig_grp *tg = NULL;
> int err = 0, nr_filter_sigs;
> + int nr_trigs = drvdata->config.nr_trig_max;
>
> nr_filter_sigs = cti_plat_count_sig_elements(fwnode,
> CTI_DT_FILTER_OUT_SIGS);
> if (nr_filter_sigs == 0)
> return 0;
>
> - if (nr_filter_sigs > drvdata->config.nr_trig_max)
> + if (nr_filter_sigs > nr_trigs)
> return -EINVAL;
>
> tg = kzalloc_obj(*tg);
> if (!tg)
> return -ENOMEM;
>
> + tg->used_mask = bitmap_zalloc(nr_trigs, GFP_KERNEL);
Here would be:
tg->used_mask = bitmap_zalloc(nr_filter_sigs, GFP_KERNEL);
> + if (!tg->used_mask) {
> + kfree(tg);
> + return -ENOMEM;
> + }
> +
It is likely this will have merge conflict with the new patch [1].
You might need to rebase this patch on the top of [1]. We need to
give [1] priority as it is a fix.
[1] https://lore.kernel.org/linux-arm-kernel/20260426-nr_sigs-v1-1-3b9df99dab97…
Otherwise, LGTM:
Reviewed-by: Leo Yan <leo.yan(a)arm.com>
On Sun, Apr 26, 2026 at 05:59:34PM +0800, Yingchao Deng wrote:
> In cti_plat_process_filter_sigs(), after allocating a temporary
> cti_trig_grp struct via kzalloc_obj(), the code never assigns tg->nr_sigs
> = nr_filter_sigs. Since kzalloc zero-initialises the struct, tg->nr_sigs
> remains 0. cti_plat_read_trig_group() guards with:
> if (!tgrp->nr_sigs)
> return 0;
>
> so it returns immediately without reading any signal indices from DT.
>
> Fix by assigning tg->nr_sigs before calling cti_plat_read_trig_group().
>
> Fixes: a5614770ab97 ("coresight: cti: Add device tree support for custom CTI")
> Signed-off-by: Yingchao Deng <yingchao.deng(a)oss.qualcomm.com>
> ---
> drivers/hwtracing/coresight/coresight-cti-platform.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/hwtracing/coresight/coresight-cti-platform.c b/drivers/hwtracing/coresight/coresight-cti-platform.c
> index 4eff96f48594..d6d5388705c3 100644
> --- a/drivers/hwtracing/coresight/coresight-cti-platform.c
> +++ b/drivers/hwtracing/coresight/coresight-cti-platform.c
> @@ -329,6 +329,7 @@ static int cti_plat_process_filter_sigs(struct cti_drvdata *drvdata,
> if (!tg)
> return -ENOMEM;
>
> + tg->nr_sigs = nr_filter_sigs;
> err = cti_plat_read_trig_group(tg, fwnode, CTI_DT_FILTER_OUT_SIGS);
I checked the naming, seems tg->nr_sigs is the right field to store
the number. LGTM:
Reviewed-by: Leo Yan <leo.yan(a)arm.com>
Hi,
On 4/21/26 11:30, Yeoreum Yun wrote:
>> Hi Mike,
>>
>>> Hi,
>>>
>>> This register [bit 31] indicates if a single shot comparator has matched. So
>>> read-back provides information to the user post run to determine which if
>>> any of the comparators set in this way has actually matched.
>>
>> Okay. so after disable sysfs session, to check former session
>> check whether comprator has matched.
>>
>>>
>>> Moreover, the specification states "Software must reset this bit to 0 to
>>> re-enable single-shot control" and "Reset state is unknown. STATUS must be
>>> written to set an initial state...."
>>>
>>> Therefore this register must be written as part of any configuration so
>>> should be available in the drvdata->config for both read and write,
>>
>> But I don't think this is the reason for locate ss_status into "config"
>> since its write purpose is not to configure but the "clear" former bit.
>> That's why I think it's enough to clear when the new sysfs session starts.
>>
>
> IOW, I think it's better to remove ss_status from configfs item
> and
> - add field ss_cmp in etm4_cpas
> - add another field ss_status under "etm4_drvdata" to show "PENDING
> and STATUS" bits to sysfs after finishing session.
>
> Is is valid for you?
>
No. Why two different locations for a single register read? If I have
the ETMv4 hardware manual I am going to look for a something that is
recognizable as being related to the single shot comparator status
register(s).
So in sysfs I would expect to see all the bits from the register,
displayed, without masking off the STATUS and PENDING bits as happens now.
In the code I would expect to see a single location with a sensible name
- ss_cmp doesn't really correlate terribly well with TRCSSCSR. If you do
not like the original ss_status, then ss_cmp_status may actually be
better, ss_cmp could be either the ss comparator status or control
register.
Regards
Mike
>> --
>> Sincerely,
>> Yeoreum Yun
>>
>
> --
> Sincerely,
> Yeoreum Yun
On Tue, Apr 21, 2026 at 12:14:20PM +0100, Yeoreum Yun wrote:
[...]
> > > - There is a risk of corrupting drvdata->config if a perf session enables
> > > tracing while cscfg_csdev_disable_active_config() is being handled in
> > > etm4_disable_sysfs().
> >
> > Similiar to above, cscfg_csdev_disable_active_config() is not
> > protected in etm4_disable_sysfs().
>
> This is not true.
> at the time of etm4_disable_sysfs() "mode" is already taken
> (whether sysfs or perf). In this situation, it's enough to
> call cscfg_csdev_disable_active_config() before chaning
> mode to DISABLED.
To be clear, I am trying to understand issue _before_ your patch.
Without this patch, cscfg_csdev_disable_active_config() is not
protected by the mode.
> > > struct etm4_enable_arg {
> > > struct etmv4_drvdata *drvdata;
> > > + struct etmv4_config config;
> >
> > We don't need this. We can defer to get drvdata->config in SMP call.
>
> This is not true ane make a situation more complex.
> If we get config in SMP call, that would be a problem when some user is
> trying to modify config.
>
> IOW, while user modifying config via sysfs, and SMP call happens,
> it makes a dead lock. so for this if we try to grab the lock in SMP call,
> we should change every sysfs raw_spin_lock() into raw_spin_lock_irqsave().
>
> Instead of this it would be much clear and simpler to get config in here
> rather than to add some latencies via sysfs.
Thanks for info. If so, it is fine for me to add "config".
> > > @@ -1386,6 +1401,7 @@ static void etm4_init_arch_data(void *info)
> > > drvdata = dev_get_drvdata(init_arg->dev);
> > > caps = &drvdata->caps;
> > > csa = init_arg->csa;
> > > + config = &drvdata->active_config;
> >
> > Should we init drvdata->config instead so make it has sane values ?
> >
> > In other words, drvdata->active_config are always set at the runtime,
> > so don't need to init it at all, right?
>
> No. at least when the initialise, I think we should fill the its
> contesnt with the "etm4_set_default()".
>
> That's why the consequence call etm4_set_default() called with
> active_config and config is coped with the default configutation.
I'm concerned that some config fields may be reused across sessions.
For example, a sysfs session copies drvdata->config into
drvdata->active_config, and later a perf session may reuse stale
values. The same issue can happen in the reverse direction.
A clean approach would be to treat drvdata->active_config as
per-session state:
1) clear drvdata->active_config at session start
2) apply the session-specific config
2.1) sysfs: use drvdata->config
2.2) perf: use event config
3) then apply configfs configuration
So we should clear drvdata->active_config at the start of each session
and rebuild it with the correct configuration.
Thanks,
Leo
On Wed, Apr 15, 2026 at 05:55:23PM +0100, Yeoreum Yun wrote:
> The current ETM4x configuration via sysfs can lead to
> several inconsistencies:
>
> - If the configuration is modified via sysfs while a perf session is
> active, the running configuration may differ before a sched-out and
> after a subsequent sched-in.
>
> - If a perf session and a sysfs session enable tracing concurrently,
> the configuration from configfs may become corrupted.
I think this happens because the sysfs path calls
cscfg_csdev_enable_active_config() without first acquiring the mode,
allowing a perf session to acquire the mode and call the same function
concurrently.
> - There is a risk of corrupting drvdata->config if a perf session enables
> tracing while cscfg_csdev_disable_active_config() is being handled in
> etm4_disable_sysfs().
Similiar to above, cscfg_csdev_disable_active_config() is not
protected in etm4_disable_sysfs().
> To resolve these issues, separate the configuration into:
>
> - active_config: the configuration applied to the current session
> - config: the configuration set via sysfs
>
> Additionally:
>
> - Apply the configuration from configfs after taking the appropriate mode.
>
> - Since active_config and related fields are accessed only by the local CPU
> in etm4_enable/disable_sysfs_smp_call() (similar to perf enable/disable),
> remove the lock/unlock from the sysfs enable/disable path and
> startup/dying_cpu except when to access config fields.
>
> Signed-off-by: Yeoreum Yun <yeoreum.yun(a)arm.com>
> ---
> .../hwtracing/coresight/coresight-etm4x-cfg.c | 2 +-
> .../coresight/coresight-etm4x-core.c | 107 ++++++++++--------
> drivers/hwtracing/coresight/coresight-etm4x.h | 2 +
> 3 files changed, 63 insertions(+), 48 deletions(-)
>
> diff --git a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
> index d14d7c8a23e5..0553771d04e7 100644
> --- a/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
> +++ b/drivers/hwtracing/coresight/coresight-etm4x-cfg.c
> @@ -47,7 +47,7 @@ static int etm4_cfg_map_reg_offset(struct etmv4_drvdata *drvdata,
> struct cscfg_regval_csdev *reg_csdev, u32 offset)
> {
> int err = -EINVAL, idx;
> - struct etmv4_config *drvcfg = &drvdata->config;
> + struct etmv4_config *drvcfg = &drvdata->active_config;
> u32 off_mask;
>
> if (((offset >= TRCEVENTCTL0R) && (offset <= TRCVIPCSSCTLR)) ||
> diff --git a/drivers/hwtracing/coresight/coresight-etm4x-core.c b/drivers/hwtracing/coresight/coresight-etm4x-core.c
> index b199aebbdb60..15aaf4a898e1 100644
> --- a/drivers/hwtracing/coresight/coresight-etm4x-core.c
> +++ b/drivers/hwtracing/coresight/coresight-etm4x-core.c
> @@ -245,6 +245,10 @@ void etm4_release_trace_id(struct etmv4_drvdata *drvdata)
>
> struct etm4_enable_arg {
> struct etmv4_drvdata *drvdata;
> + unsigned long cfg_hash;
> + int preset;
Since smp call cannot directly call cscfg_config_sysfs_get_active_cfg()
due to it runs in atomic context but ...active_cfg() tries to acquire
mutex. So we firstly retrieve pass 'cfg_hash' and 'preset' in sleepable
context and deliver them to the SMP call.
After coresight cfg refactoring, we should remove cfg_hash/preset from
ETM driver, the ETM driver only needs to retrieve register list and can
do this in smp call.
Before we finish cfg refactoring, it is fine for me to add these two
parameters into etm4_enable_arg.
> + u8 trace_id;
Can we add 'path' instead ? The SMP call can retrieve path->trace_id.
This can benefit for future clean up (e.g., we can store config into
path so we can retrieve config from path pointer), and this allows us
for further refactoring to unify etm4_enable_sysfs_smp_call() and
etm4_enable_perf().
> + struct etmv4_config config;
We don't need this. We can defer to get drvdata->config in SMP call.
> int rc;
> };
[...]
> @@ -918,40 +946,29 @@ static int etm4_enable_sysfs(struct coresight_device *csdev, struct coresight_pa
>
> /* enable any config activated by configfs */
> cscfg_config_sysfs_get_active_cfg(&cfg_hash, &preset);
> - if (cfg_hash) {
> - ret = cscfg_csdev_enable_active_config(csdev, cfg_hash, preset);
> - if (ret) {
> - etm4_release_trace_id(drvdata);
> - return ret;
> - }
> - }
> -
> - raw_spin_lock(&drvdata->spinlock);
> -
> - drvdata->trcid = path->trace_id;
> -
> - /* Tracer will never be paused in sysfs mode */
> - drvdata->paused = false;
>
> /*
> * Executing etm4_enable_hw on the cpu whose ETM is being enabled
> * ensures that register writes occur when cpu is powered.
> */
> arg.drvdata = drvdata;
> + arg.cfg_hash = cfg_hash;
> + arg.preset = preset;
> + arg.trace_id = path->trace_id;
> +
> + raw_spin_lock(&drvdata->spinlock);
> + arg.config = drvdata->config;
> + raw_spin_unlock(&drvdata->spinlock);
Can we defer this in smp call ? And we can consolidate a bit
configurations, we can consider to use a separate patch for this.
etm4x_apply_config(drvdata, event, hash, preset)
{
/* perf mode */
if (event) {
etm4_parse_event_config(drvdata->csdev, event);
} else if (mode == CS_MODE_PERF) {
scoped_guard(raw_spinlock, &drvdata->spinlock)
&drvdata->active_config = drvdata->config;
}
/* At the end, we always apply the config */
cscfg_csdev_enable_active_config(drvdata->csdev, hash, preset);
}
> +
> ret = smp_call_function_single(drvdata->cpu,
> etm4_enable_sysfs_smp_call, &arg, 1);
> if (!ret)
> ret = arg.rc;
> if (!ret)
> - drvdata->sticky_enable = true;
> -
> - if (ret)
> + dev_dbg(&csdev->dev, "ETM tracing enabled\n");
> + else
> etm4_release_trace_id(drvdata);
>
> - raw_spin_unlock(&drvdata->spinlock);
> -
> - if (!ret)
> - dev_dbg(&csdev->dev, "ETM tracing enabled\n");
> return ret;
> }
>
> @@ -1038,7 +1055,7 @@ static void etm4_disable_hw(struct etmv4_drvdata *drvdata)
> {
> u32 control;
> const struct etmv4_caps *caps = &drvdata->caps;
> - struct etmv4_config *config = &drvdata->config;
> + struct etmv4_config *config = &drvdata->active_config;
> struct coresight_device *csdev = drvdata->csdev;
> struct csdev_access *csa = &csdev->access;
> int i;
> @@ -1074,6 +1091,8 @@ static void etm4_disable_sysfs_smp_call(void *info)
>
> etm4_disable_hw(drvdata);
>
> + cscfg_csdev_disable_active_config(drvdata->csdev);
> +
> coresight_set_mode(drvdata->csdev, CS_MODE_DISABLED);
> }
>
> @@ -1124,7 +1143,6 @@ static void etm4_disable_sysfs(struct coresight_device *csdev)
> * DYING hotplug callback is serviced by the ETM driver.
> */
> cpus_read_lock();
> - raw_spin_lock(&drvdata->spinlock);
>
> /*
> * Executing etm4_disable_hw on the cpu whose ETM is being disabled
> @@ -1133,10 +1151,6 @@ static void etm4_disable_sysfs(struct coresight_device *csdev)
> smp_call_function_single(drvdata->cpu, etm4_disable_sysfs_smp_call,
> drvdata, 1);
>
> - raw_spin_unlock(&drvdata->spinlock);
> -
> - cscfg_csdev_disable_active_config(csdev);
> -
> cpus_read_unlock();
>
> /*
> @@ -1379,6 +1393,7 @@ static void etm4_init_arch_data(void *info)
> struct etm4_init_arg *init_arg = info;
> struct etmv4_drvdata *drvdata;
> struct etmv4_caps *caps;
> + struct etmv4_config *config;
> struct csdev_access *csa;
> struct device *dev = init_arg->dev;
> int i;
> @@ -1386,6 +1401,7 @@ static void etm4_init_arch_data(void *info)
> drvdata = dev_get_drvdata(init_arg->dev);
> caps = &drvdata->caps;
> csa = init_arg->csa;
> + config = &drvdata->active_config;
Should we init drvdata->config instead so make it has sane values ?
In other words, drvdata->active_config are always set at the runtime,
so don't need to init it at all, right?
Thanks,
Leo
On Wed, Apr 15, 2026 at 05:55:20PM +0100, Yeoreum Yun wrote:
[...]
> @@ -573,11 +573,9 @@ static int etm4_enable_hw(struct etmv4_drvdata *drvdata)
> etm4x_relaxed_write32(csa, config->res_ctrl[i], TRCRSCTLRn(i));
>
> for (i = 0; i < caps->nr_ss_cmp; i++) {
> - /* always clear status bit on restart if using single-shot */
> - if (config->ss_ctrl[i] || config->ss_pe_cmp[i])
> - config->ss_status[i] &= ~TRCSSCSRn_STATUS;
> etm4x_relaxed_write32(csa, config->ss_ctrl[i], TRCSSCCRn(i));
> - etm4x_relaxed_write32(csa, config->ss_status[i], TRCSSCSRn(i));
> + /* always clear status and pending bits on restart if using single-shot */
> + etm4x_relaxed_write32(csa, 0x0, TRCSSCSRn(i));
In Arm ARM, D24.4.60 TRCSSCSR<n>, bits[0..3] are RO. I think it is
fine for directly clear the regiser with zero (means it will only
clear status / pending bits).
[...]
> @@ -1841,10 +1839,11 @@ static ssize_t sshot_status_show(struct device *dev,
> {
> unsigned long val;
> struct etmv4_drvdata *drvdata = dev_get_drvdata(dev->parent);
> + const struct etmv4_caps *caps = &drvdata->caps;
> struct etmv4_config *config = &drvdata->config;
>
> raw_spin_lock(&drvdata->spinlock);
> - val = config->ss_status[config->ss_idx];
> + val = caps->ss_cmp[config->ss_idx];
> raw_spin_unlock(&drvdata->spinlock);
> return scnprintf(buf, PAGE_SIZE, "%#lx\n", val);
> }
This sysfs knob never can print out a realtime status for sshot, I am
fine for only printing caps->ss_cmp, this can avoid any misleading.
@Suzuki, @Mike, do you agree with the change above?
If maintainers agree with this, as Jie suggested, it is good to add a
comment in the code and update the document:
Documentation/trace/coresight/coresight-etm4x-reference.rst
Thanks,
Leo