* [PATCH v15 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:07 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 2/7] qcom-tgu: Add TGU driver Songwei Chai
` (5 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Rob Herring
The Trigger Generation Unit (TGU) is designed to detect patterns or
sequences within a specific region of the System on Chip (SoC). Once
configured and activated, it monitors sense inputs and can detect a
pre-programmed state or sequence across clock cycles, subsequently
producing a trigger.
TGU configuration space
offset table
x-------------------------x
| |
| |
| | Step configuration
| | space layout
| coresight management | x-------------x
| registers | |---> | |
| | | | reserve |
| | | | |
|-------------------------| | |-------------|
| | | | priority[3] |
| step[7] |<-- | |-------------|
|-------------------------| | | | priority[2] |
| | | | |-------------|
| ... | |Steps region | | priority[1] |
| | | | |-------------|
|-------------------------| | | | priority[0] |
| |<-- | |-------------|
| step[0] |--------------------> | |
|-------------------------| | condition |
| | | |
| control and status | x-------------x
| space | | |
x-------------------------x |Timer/Counter|
| |
x-------------x
TGU Configuration in Hardware
The TGU provides a step region for user configuration, similar
to a flow chart. Each step region consists of three register clusters:
1.Priority Region: Sets the required signals with priority.
2.Condition Region: Defines specific requirements (e.g., signal A
reaches three times) and the subsequent action once the requirement is
met.
3.Timer/Counter (Optional): Provides timing or counting functionality.
Add a new tgu.yaml file to describe the bindings required to
define the TGU in the device trees.
Reviewed-by: Rob Herring (Arm) <robh@kernel.org>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../devicetree/bindings/arm/qcom,tgu.yaml | 71 +++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 Documentation/devicetree/bindings/arm/qcom,tgu.yaml
diff --git a/Documentation/devicetree/bindings/arm/qcom,tgu.yaml b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
new file mode 100644
index 000000000000..76440f2497b9
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+# Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/arm/qcom,tgu.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Trigger Generation Unit - TGU
+
+description: |
+ The Trigger Generation Unit (TGU) is a Data Engine which can be utilized
+ to sense a plurality of signals and create a trigger into the CTI or
+ generate interrupts to processors. The TGU is like the trigger circuit
+ of a Logic Analyzer. The corresponding trigger logic can be realized by
+ configuring the conditions for each step after sensing the signal.
+ Once setup and enabled, it will observe sense inputs and based upon
+ the activity of those inputs, even over clock cycles, may detect a
+ preprogrammed state/sequence and then produce a trigger or interrupt.
+
+ The primary use case of the TGU is to detect patterns or sequences on a
+ given set of signals within some region to identify the issue in time
+ once there is abnormal behavior in the subsystem.
+
+maintainers:
+ - Mao Jinlong <jinlong.mao@oss.qualcomm.com>
+ - Songwei Chai <songwei.chai@oss.qualcomm.com>
+
+# Need a custom select here or 'arm,primecell' will match on lots of nodes
+select:
+ properties:
+ compatible:
+ contains:
+ enum:
+ - qcom,tgu
+ required:
+ - compatible
+
+properties:
+ compatible:
+ items:
+ - const: qcom,tgu
+ - const: arm,primecell
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-names:
+ items:
+ - const: apb_pclk
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+
+additionalProperties: false
+
+examples:
+ - |
+ tgu@10b0e000 {
+ compatible = "qcom,tgu", "arm,primecell";
+ reg = <0x10b0e000 0x1000>;
+
+ clocks = <&aoss_qmp>;
+ clock-names = "apb_pclk";
+ };
+...
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace
2026-07-01 5:55 ` [PATCH v15 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace Songwei Chai
@ 2026-07-01 6:07 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:07 UTC (permalink / raw)
To: Songwei Chai; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The device tree binding schema omits the `interrupts` property despite the hardware description explicitly stating that it generates interrupts.
- [Medium] The binding declares compatibility with `arm,primecell` but fails to reference the core primecell schema, incorrectly restricting inherited properties via `additionalProperties: false`.
--
commit b5849a8f6235b8ea1a67c149957156b0d2165d02
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
dt-bindings: arm: Add support for Qualcomm TGU trace
The Trigger Generation Unit (TGU) is designed to detect patterns or
sequences within a specific region of the System on Chip (SoC). Once
configured and activated, it monitors sense inputs and can detect a
pre-programmed state or sequence across clock cycles, subsequently
producing a trigger.
> diff --git a/Documentation/devicetree/bindings/arm/qcom,tgu.yaml b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
> new file mode 100644
> index 0000000000000..76440f2497b97
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/arm/qcom,tgu.yaml
> @@ -0,0 +1,71 @@
[ ... ]
> +properties:
> + compatible:
> + items:
> + - const: qcom,tgu
> + - const: arm,primecell
[Severity: Medium]
Since this device declares compatibility with "arm,primecell", should it
include a reference to the core primecell schema?
Without an allOf block referencing /schemas/arm/primecell.yaml#, the
schema inheritance is missing for standard PrimeCell properties.
> +
> + reg:
> + maxItems: 1
> +
> + clocks:
> + maxItems: 1
> +
> + clock-names:
> + items:
> + - const: apb_pclk
[Severity: Medium]
The description mentions the hardware can "generate interrupts to processors".
Would it make sense to define the interrupts property here so it can be
accurately described in board device trees?
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> + - clock-names
> +
> +additionalProperties: false
[Severity: Medium]
If the primecell schema reference is added, should this be changed to
unevaluatedProperties: false?
Currently, standard AMBA PrimeCell properties (like arm,primecell-periphid)
will fail validation because they are blocked by this constraint.
Additionally, if a device tree node adds the unlisted interrupts property
to wire up the hardware as described, it will also be rejected by this
constraint.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 2/7] qcom-tgu: Add TGU driver
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
2026-07-01 5:55 ` [PATCH v15 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:17 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 3/7] qcom-tgu: Add signal priority support Songwei Chai
` (4 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Add driver to support device TGU (Trigger Generation Unit).
TGU is a Data Engine which can be utilized to sense a plurality of
signals and create a trigger into the CTI or generate interrupts to
processors. Add probe/enable/disable functions for tgu.
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
Acked-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 9 +
MAINTAINERS | 11 +
drivers/Makefile | 1 +
drivers/hwtracing/Kconfig | 2 +
drivers/hwtracing/qcom/Kconfig | 20 ++
drivers/hwtracing/qcom/Makefile | 3 +
drivers/hwtracing/qcom/tgu.c | 193 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 51 +++++
8 files changed, 290 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
create mode 100644 drivers/hwtracing/qcom/Kconfig
create mode 100644 drivers/hwtracing/qcom/Makefile
create mode 100644 drivers/hwtracing/qcom/tgu.c
create mode 100644 drivers/hwtracing/qcom/tgu.h
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
new file mode 100644
index 000000000000..8eddcf3bb5fe
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -0,0 +1,9 @@
+What: /sys/bus/amba/devices/<tgu-name>/enable_tgu
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the enable/disable status of TGU
+ Accepts only one of the 2 values - 0 or 1.
+ 0 : disable TGU.
+ 1 : enable TGU.
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..d627c27d0592 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22358,6 +22358,17 @@ S: Maintained
F: Documentation/tee/qtee.rst
F: drivers/tee/qcomtee/
+QUALCOMM HARDWARETRACING DRIVER
+M: Songwei Chai <songwei.chai@oss.qualcomm.com>
+M: Jie Gan <jie.gan@oss.qualcomm.com>
+M: Suzuki K Poulose <suzuki.poulose@arm.com>
+L: linux-arm-msm@vger.kernel.org
+S: Maintained
+T: git git://git.kernel.org/pub/scm/linux/kernel/git/coresight/linux.git
+F: Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+F: Documentation/devicetree/bindings/arm/qcom,tgu.yaml
+F: drivers/hwtracing/qcom/
+
QUALCOMM TRUST ZONE MEMORY ALLOCATOR
M: Bartosz Golaszewski <brgl@kernel.org>
L: linux-arm-msm@vger.kernel.org
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..6a2cbe5b340f 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -176,6 +176,7 @@ obj-$(CONFIG_RAS) += ras/
obj-$(CONFIG_USB4) += thunderbolt/
obj-$(CONFIG_CORESIGHT) += hwtracing/coresight/
obj-y += hwtracing/intel_th/
+obj-y += hwtracing/qcom/
obj-$(CONFIG_STM) += hwtracing/stm/
obj-$(CONFIG_HISI_PTT) += hwtracing/ptt/
obj-y += android/
diff --git a/drivers/hwtracing/Kconfig b/drivers/hwtracing/Kconfig
index 911ee977103c..8a640218eed8 100644
--- a/drivers/hwtracing/Kconfig
+++ b/drivers/hwtracing/Kconfig
@@ -7,4 +7,6 @@ source "drivers/hwtracing/intel_th/Kconfig"
source "drivers/hwtracing/ptt/Kconfig"
+source "drivers/hwtracing/qcom/Kconfig"
+
endmenu
diff --git a/drivers/hwtracing/qcom/Kconfig b/drivers/hwtracing/qcom/Kconfig
new file mode 100644
index 000000000000..5c94c75ffa39
--- /dev/null
+++ b/drivers/hwtracing/qcom/Kconfig
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# QCOM specific hwtracing drivers
+#
+menu "Qualcomm specific hwtracing drivers"
+
+config QCOM_TGU
+ tristate "QCOM Trigger Generation Unit driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ depends on ARM_AMBA
+ help
+ This driver provides support for Trigger Generation Unit that is
+ used to detect patterns or sequences on a given set of signals.
+ TGU is used to monitor a particular bus within a given region to
+ detect illegal transaction sequences or slave responses. It is also
+ used to monitor a data stream to detect protocol violations and to
+ provide a trigger point for centering data around a specific event
+ within the trace data buffer.
+
+endmenu
diff --git a/drivers/hwtracing/qcom/Makefile b/drivers/hwtracing/qcom/Makefile
new file mode 100644
index 000000000000..5a0a868c1ea0
--- /dev/null
+++ b/drivers/hwtracing/qcom/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_QCOM_TGU) += tgu.o
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
new file mode 100644
index 000000000000..49c8f710b931
--- /dev/null
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/amba/bus.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+
+#include "tgu.h"
+
+static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
+{
+ TGU_UNLOCK(drvdata->base);
+ /* Enable TGU to program the triggers */
+ writel(1, drvdata->base + TGU_CONTROL);
+ TGU_LOCK(drvdata->base);
+}
+
+static int tgu_enable(struct device *dev)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+
+ guard(spinlock)(&drvdata->lock);
+ drvdata->enabled = true;
+
+ tgu_write_all_hw_regs(drvdata);
+
+ return 0;
+}
+
+static void tgu_do_disable(struct tgu_drvdata *drvdata)
+{
+ TGU_UNLOCK(drvdata->base);
+ writel(0, drvdata->base + TGU_CONTROL);
+ TGU_LOCK(drvdata->base);
+
+ drvdata->enabled = false;
+}
+
+static void tgu_disable(struct device *dev)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+
+ guard(spinlock)(&drvdata->lock);
+ if (!drvdata->enabled)
+ return;
+
+ tgu_do_disable(drvdata);
+}
+
+static ssize_t enable_tgu_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ bool enabled;
+
+ guard(spinlock)(&drvdata->lock);
+ enabled = drvdata->enabled;
+
+ return sysfs_emit(buf, "%d\n", !!enabled);
+}
+
+/* enable_tgu_store - Configure Trace and Gating Unit (TGU) triggers. */
+static ssize_t enable_tgu_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret || val > 1)
+ return -EINVAL;
+
+ if (val) {
+ scoped_guard(spinlock, &drvdata->lock) {
+ if (drvdata->enabled)
+ return -EBUSY;
+ }
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ ret = tgu_enable(dev);
+ if (ret) {
+ pm_runtime_put(dev);
+ return ret;
+ }
+ } else {
+ scoped_guard(spinlock, &drvdata->lock) {
+ if (!drvdata->enabled)
+ return -EINVAL;
+ }
+
+ tgu_disable(dev);
+ pm_runtime_put(dev);
+ }
+
+ return size;
+}
+static DEVICE_ATTR_RW(enable_tgu);
+
+static struct attribute *tgu_common_attrs[] = {
+ &dev_attr_enable_tgu.attr,
+ NULL,
+};
+
+static const struct attribute_group tgu_common_grp = {
+ .attrs = tgu_common_attrs,
+ NULL,
+};
+
+static const struct attribute_group *tgu_attr_groups[] = {
+ &tgu_common_grp,
+ NULL,
+};
+
+static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
+{
+ struct device *dev = &adev->dev;
+ struct tgu_drvdata *drvdata;
+ int ret;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->dev = &adev->dev;
+ dev_set_drvdata(dev, drvdata);
+
+ drvdata->base = devm_ioremap_resource(dev, &adev->res);
+ if (IS_ERR(drvdata->base))
+ return PTR_ERR(drvdata->base);
+
+ spin_lock_init(&drvdata->lock);
+
+ ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
+ if (ret) {
+ dev_err(dev, "failed to create sysfs groups: %d\n", ret);
+ return ret;
+ }
+
+ drvdata->enabled = false;
+
+ pm_runtime_put(&adev->dev);
+
+ return 0;
+}
+
+static void tgu_remove(struct amba_device *adev)
+{
+ struct device *dev = &adev->dev;
+
+ sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
+
+ tgu_disable(dev);
+}
+
+static const struct amba_id tgu_ids[] = {
+ {
+ .id = 0x000f0e00,
+ .mask = 0x000fffff,
+ },
+ { 0, 0, NULL },
+};
+
+MODULE_DEVICE_TABLE(amba, tgu_ids);
+
+static struct amba_driver tgu_driver = {
+ .drv = {
+ .name = "qcom-tgu",
+ .suppress_bind_attrs = true,
+ },
+ .probe = tgu_probe,
+ .remove = tgu_remove,
+ .id_table = tgu_ids,
+};
+
+module_amba_driver(tgu_driver);
+
+MODULE_AUTHOR("Songwei Chai <songwei.chai@oss.qualcomm.com>");
+MODULE_AUTHOR("Jinlong Mao <jinlong.mao@oss.qualcomm.com>");
+MODULE_DESCRIPTION("Qualcomm Trigger Generation Unit driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
new file mode 100644
index 000000000000..dd7533b9d735
--- /dev/null
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _QCOM_TGU_H
+#define _QCOM_TGU_H
+
+/* Register addresses */
+#define TGU_CONTROL 0x0000
+#define TGU_LAR 0xfb0
+#define TGU_UNLOCK_OFFSET 0xc5acce55
+
+static inline void TGU_LOCK(void __iomem *addr)
+{
+ do {
+ /* Wait for things to settle */
+ mb();
+ writel_relaxed(0x0, addr + TGU_LAR);
+ } while (0);
+}
+
+static inline void TGU_UNLOCK(void __iomem *addr)
+{
+ do {
+ writel_relaxed(TGU_UNLOCK_OFFSET, addr + TGU_LAR);
+ /* Make sure everyone has seen this */
+ mb();
+ } while (0);
+}
+
+/**
+ * struct tgu_drvdata - Data structure for a TGU (Trigger Generator Unit)
+ * @base: Memory-mapped base address of the TGU device
+ * @dev: Pointer to the associated device structure
+ * @lock: Spinlock for handling concurrent access to private data
+ * @enabled: Flag indicating whether the TGU device is enabled
+ *
+ * This structure defines the data associated with a TGU device,
+ * including its base address, device pointers, clock, spinlock for
+ * synchronization, trigger data pointers, maximum limits for various
+ * trigger-related parameters, and enable status.
+ */
+struct tgu_drvdata {
+ void __iomem *base;
+ struct device *dev;
+ spinlock_t lock;
+ bool enabled;
+};
+
+#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 2/7] qcom-tgu: Add TGU driver
2026-07-01 5:55 ` [PATCH v15 2/7] qcom-tgu: Add TGU driver Songwei Chai
@ 2026-07-01 6:17 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:17 UTC (permalink / raw)
To: Songwei Chai; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Time-of-Check to Time-of-Use (TOCTOU) race condition in `enable_tgu_store` causes unbalanced PM runtime usage counter.
- [High] Missing `pm_runtime_get_sync()` and unbalanced sysfs PM references in `tgu_remove()` lead to PM usage counter underflow or leaks.
--
commit e9711e322428fa7e4360329e125f890c5dceb36f
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add TGU driver
Add driver to support device TGU (Trigger Generation Unit).
TGU is a Data Engine which can be utilized to sense a plurality of
signals and create a trigger into the CTI or generate interrupts to
processors. Add probe/enable/disable functions for tgu.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> new file mode 100644
> index 0000000000000..49c8f710b9318
> --- /dev/null
> +++ b/drivers/hwtracing/qcom/tgu.c
[ ... ]
> +/* enable_tgu_store - Configure Trace and Gating Unit (TGU) triggers. */
> +static ssize_t enable_tgu_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf,
> + size_t size)
> +{
> + struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
> + unsigned long val;
> + int ret;
> +
> + ret = kstrtoul(buf, 0, &val);
> + if (ret || val > 1)
> + return -EINVAL;
> +
> + if (val) {
> + scoped_guard(spinlock, &drvdata->lock) {
> + if (drvdata->enabled)
> + return -EBUSY;
> + }
> +
> + ret = pm_runtime_resume_and_get(dev);
[Severity: High]
Is there a time-of-check to time-of-use (TOCTOU) race condition here?
Because scoped_guard releases the lock immediately after checking
drvdata->enabled, two threads could concurrently write '1' to this sysfs
attribute and both pass the check.
This would cause pm_runtime_resume_and_get() to be called twice, leaking a PM
runtime usage counter since the internal boolean state only changes once.
[ ... ]
> + } else {
> + scoped_guard(spinlock, &drvdata->lock) {
> + if (!drvdata->enabled)
> + return -EINVAL;
> + }
> +
> + tgu_disable(dev);
> + pm_runtime_put(dev);
> + }
> +
> + return size;
> +}
Similarly, if two threads concurrently write '0' to the sysfs attribute, could
the lock being released early allow both threads to bypass the state check,
causing the PM usage counter to decrement multiple times?
[ ... ]
> +static void tgu_remove(struct amba_device *adev)
> +{
> + struct device *dev = &adev->dev;
> +
> + sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
> +
> + tgu_disable(dev);
> +}
[Severity: High]
Since tgu_probe() called pm_runtime_put(), doesn't tgu_remove() need to call
pm_runtime_get_sync() to balance the counter?
Without this, the AMBA bus framework might underflow the PM usage counter to -1
during remove.
Additionally, if the device is removed while it was enabled via sysfs,
tgu_disable(dev) is called to turn off the hardware, but it doesn't seem to
call pm_runtime_put(). Does this leak the PM runtime reference that was
acquired in enable_tgu_store()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 3/7] qcom-tgu: Add signal priority support
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
2026-07-01 5:55 ` [PATCH v15 1/7] dt-bindings: arm: Add support for Qualcomm TGU trace Songwei Chai
2026-07-01 5:55 ` [PATCH v15 2/7] qcom-tgu: Add TGU driver Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:25 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 4/7] qcom-tgu: Add TGU decode support Songwei Chai
` (3 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Like circuit of a Logic analyzer, in TGU, the requirement could be
configured in each step and the trigger will be created once the
requirements are met. Add priority functionality here to sort the
signals into different priorities. The signal which is wanted could
be configured in each step's priority node, the larger number means
the higher priority and the signal with higher priority will be sensed
more preferentially.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 +
drivers/hwtracing/qcom/tgu.c | 161 ++++++++++++++++++
drivers/hwtracing/qcom/tgu.h | 114 +++++++++++++
3 files changed, 282 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 8eddcf3bb5fe..2f1d4ecaf76b 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -7,3 +7,10 @@ Description:
Accepts only one of the 2 values - 0 or 1.
0 : disable TGU.
1 : enable TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_priority[0:3]/reg[0:17]
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the sensed signal with specific step and priority for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 49c8f710b931..7d69986c3e3d 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -14,14 +14,123 @@
#include "tgu.h"
+static int calculate_array_location(struct tgu_drvdata *drvdata,
+ int step_index, int operation_index,
+ int reg_index)
+{
+ return operation_index * (drvdata->num_step) * (drvdata->num_reg) +
+ step_index * (drvdata->num_reg) + reg_index;
+}
+
+static ssize_t tgu_dataset_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct tgu_attribute *tgu_attr =
+ container_of(attr, struct tgu_attribute, attr);
+ int index;
+
+ index = calculate_array_location(drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index,
+ tgu_attr->reg_num);
+
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->priority[index]);
+}
+
+static ssize_t tgu_dataset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct tgu_drvdata *tgu_drvdata = dev_get_drvdata(dev);
+ struct tgu_attribute *tgu_attr =
+ container_of(attr, struct tgu_attribute, attr);
+ unsigned long val;
+ int index;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ guard(spinlock)(&tgu_drvdata->lock);
+ index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index,
+ tgu_attr->reg_num);
+
+ tgu_drvdata->value_table->priority[index] = val;
+
+ return size;
+}
+
+static umode_t tgu_node_visible(struct kobject *kobject,
+ struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobject);
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct device_attribute *dev_attr =
+ container_of(attr, struct device_attribute, attr);
+ struct tgu_attribute *tgu_attr =
+ container_of(dev_attr, struct tgu_attribute, attr);
+
+ if (tgu_attr->step_index >= drvdata->num_step)
+ return SYSFS_GROUP_INVISIBLE;
+
+ if (tgu_attr->reg_num >= drvdata->num_reg)
+ return 0;
+
+ return attr->mode;
+}
+
static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
{
+ int i, j, k, index;
+
TGU_UNLOCK(drvdata->base);
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < MAX_PRIORITY; j++) {
+ for (k = 0; k < drvdata->num_reg; k++) {
+ index = calculate_array_location(
+ drvdata, i, j, k);
+
+ writel(drvdata->value_table->priority[index],
+ drvdata->base +
+ PRIORITY_REG_STEP(i, j, k));
+ }
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
TGU_LOCK(drvdata->base);
}
+static void tgu_set_reg_number(struct tgu_drvdata *drvdata)
+{
+ int num_sense_input;
+ int num_reg;
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_DEVID);
+
+ num_sense_input = TGU_DEVID_SENSE_INPUT(devid);
+ num_reg = (num_sense_input * TGU_BITS_PER_SIGNAL) / LENGTH_REGISTER;
+
+ if ((num_sense_input * TGU_BITS_PER_SIGNAL) % LENGTH_REGISTER)
+ num_reg++;
+
+ drvdata->num_reg = num_reg;
+}
+
+static void tgu_set_steps(struct tgu_drvdata *drvdata)
+{
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_DEVID);
+
+ drvdata->num_step = TGU_DEVID_STEPS(devid);
+}
+
static int tgu_enable(struct device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
@@ -121,6 +230,38 @@ static const struct attribute_group tgu_common_grp = {
static const struct attribute_group *tgu_attr_groups[] = {
&tgu_common_grp,
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(0, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(1, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(2, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(3, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(4, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(5, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(6, 3),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 0),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 1),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 2),
+ PRIORITY_ATTRIBUTE_GROUP_INIT(7, 3),
NULL,
};
@@ -128,6 +269,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
+ unsigned int *priority;
+ size_t priority_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -143,12 +286,30 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
spin_lock_init(&drvdata->lock);
+ tgu_set_reg_number(drvdata);
+ tgu_set_steps(drvdata);
+
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
dev_err(dev, "failed to create sysfs groups: %d\n", ret);
return ret;
}
+ drvdata->value_table =
+ devm_kzalloc(dev, sizeof(*drvdata->value_table), GFP_KERNEL);
+ if (!drvdata->value_table)
+ return -ENOMEM;
+
+ priority_size = MAX_PRIORITY * drvdata->num_reg * drvdata->num_step;
+
+ priority = devm_kcalloc(dev, priority_size,
+ sizeof(*drvdata->value_table->priority),
+ GFP_KERNEL);
+ if (!priority)
+ return -ENOMEM;
+
+ drvdata->value_table->priority = priority;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index dd7533b9d735..f994d83acb1d 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -10,6 +10,114 @@
#define TGU_CONTROL 0x0000
#define TGU_LAR 0xfb0
#define TGU_UNLOCK_OFFSET 0xc5acce55
+#define TGU_DEVID 0xfc8
+
+#define TGU_DEVID_SENSE_INPUT(devid_val) \
+ ((int)FIELD_GET(GENMASK(17, 10), devid_val))
+#define TGU_DEVID_STEPS(devid_val) \
+ ((int)FIELD_GET(GENMASK(6, 3), devid_val))
+#define TGU_BITS_PER_SIGNAL 4
+#define LENGTH_REGISTER 32
+
+/*
+ * TGU configuration space Step configuration
+ * offset table space layout
+ * x-------------------------x$ x-------------x$
+ * | |$ | |$
+ * | | | reserve |$
+ * | | | |$
+ * |coresight management | |-------------|base+n*0x1D8+0x1F4$
+ * | registers | |---> |priority[3] |$
+ * | | | |-------------|base+n*0x1D8+0x194$
+ * | | | |priority[2] |$
+ * |-------------------------| | |-------------|base+n*0x1D8+0x134$
+ * | | | |priority[1] |$
+ * | step[7] | | |-------------|base+n*0x1D8+0xD4$
+ * |-------------------------|->base+0x40+7*0x1D8 | |priority[0] |$
+ * | | | |-------------|base+n*0x1D8+0x74$
+ * | ... | | | condition |$
+ * | | | | select |$
+ * |-------------------------|->base+0x40+1*0x1D8 | |-------------|base+n*0x1D8+0x60$
+ * | | | | condition |$
+ * | step[0] |--------------------> | decode |$
+ * |-------------------------|-> base+0x40 |-------------|base+n*0x1D8+0x50$
+ * | | | |$
+ * | Control and status space| |Timer/Counter|$
+ * | space | | |$
+ * x-------------------------x->base x-------------x base+n*0x1D8+0x40$
+ *
+ */
+#define STEP_OFFSET 0x1D8
+#define PRIORITY_START_OFFSET 0x0074
+#define PRIORITY_OFFSET 0x60
+#define REG_OFFSET 0x4
+
+/* Calculate compare step addresses */
+#define PRIORITY_REG_STEP(step, priority, reg)\
+ (PRIORITY_START_OFFSET + PRIORITY_OFFSET * priority +\
+ REG_OFFSET * reg + STEP_OFFSET * step)
+
+#define tgu_dataset_rw(name, step_index, type, reg_num) \
+ (&((struct tgu_attribute[]){ { \
+ __ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
+ step_index, \
+ type, \
+ reg_num, \
+ } })[0].attr.attr)
+
+#define STEP_PRIORITY(step_index, reg_num, priority) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_PRIORITY##priority, \
+ reg_num)
+
+#define STEP_PRIORITY_LIST(step_index, priority) \
+ {STEP_PRIORITY(step_index, 0, priority), \
+ STEP_PRIORITY(step_index, 1, priority), \
+ STEP_PRIORITY(step_index, 2, priority), \
+ STEP_PRIORITY(step_index, 3, priority), \
+ STEP_PRIORITY(step_index, 4, priority), \
+ STEP_PRIORITY(step_index, 5, priority), \
+ STEP_PRIORITY(step_index, 6, priority), \
+ STEP_PRIORITY(step_index, 7, priority), \
+ STEP_PRIORITY(step_index, 8, priority), \
+ STEP_PRIORITY(step_index, 9, priority), \
+ STEP_PRIORITY(step_index, 10, priority), \
+ STEP_PRIORITY(step_index, 11, priority), \
+ STEP_PRIORITY(step_index, 12, priority), \
+ STEP_PRIORITY(step_index, 13, priority), \
+ STEP_PRIORITY(step_index, 14, priority), \
+ STEP_PRIORITY(step_index, 15, priority), \
+ STEP_PRIORITY(step_index, 16, priority), \
+ STEP_PRIORITY(step_index, 17, priority), \
+ NULL \
+ }
+
+#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_priority" #priority \
+ })
+
+enum operation_index {
+ TGU_PRIORITY0,
+ TGU_PRIORITY1,
+ TGU_PRIORITY2,
+ TGU_PRIORITY3,
+};
+
+/* Maximum priority that TGU supports */
+#define MAX_PRIORITY 4
+
+struct tgu_attribute {
+ struct device_attribute attr;
+ u32 step_index;
+ enum operation_index operation_index;
+ u32 reg_num;
+};
+
+struct value_table {
+ unsigned int *priority;
+};
static inline void TGU_LOCK(void __iomem *addr)
{
@@ -35,6 +143,9 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @dev: Pointer to the associated device structure
* @lock: Spinlock for handling concurrent access to private data
* @enabled: Flag indicating whether the TGU device is enabled
+ * @value_table: Store given value based on relevant parameters
+ * @num_reg: Maximum number of registers
+ * @num_step: Maximum step size
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -46,6 +157,9 @@ struct tgu_drvdata {
struct device *dev;
spinlock_t lock;
bool enabled;
+ struct value_table *value_table;
+ int num_reg;
+ int num_step;
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 3/7] qcom-tgu: Add signal priority support
2026-07-01 5:55 ` [PATCH v15 3/7] qcom-tgu: Add signal priority support Songwei Chai
@ 2026-07-01 6:25 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:25 UTC (permalink / raw)
To: Songwei Chai; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Sysfs files are exposed to userspace before their backing memory is allocated, creating a race condition that leads to a NULL pointer dereference.
- [High] Missing cleanup of explicitly created sysfs groups on probe error paths leads to dangling sysfs nodes and potential Use-After-Free.
- [Low] Missing spinlock acquisition or `READ_ONCE` in `tgu_dataset_show` causes a data race with concurrent sysfs writes.
--
commit bffbcf9cda751c587a916ac6240800447250831c
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add signal priority support
Like circuit of a Logic analyzer, in TGU, the requirement could be
configured in each step and the trigger will be created once the
requirements are met. Add priority functionality here to sort the
signals into different priorities. The signal which is wanted could
be configured in each step's priority node, the larger number means
the higher priority and the signal with higher priority will be sensed
more preferentially.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> index 49c8f710b9318..7d69986c3e3d2 100644
> --- a/drivers/hwtracing/qcom/tgu.c
> +++ b/drivers/hwtracing/qcom/tgu.c
> @@ -14,14 +14,123 @@
>
> #include "tgu.h"
>
[ ... ]
> +static ssize_t tgu_dataset_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
> + struct tgu_attribute *tgu_attr =
> + container_of(attr, struct tgu_attribute, attr);
> + int index;
> +
> + index = calculate_array_location(drvdata, tgu_attr->step_index,
> + tgu_attr->operation_index,
> + tgu_attr->reg_num);
> +
> + return sysfs_emit(buf, "0x%x\n",
> + drvdata->value_table->priority[index]);
[Severity: Low]
Does this read need to be protected by drvdata->lock or use READ_ONCE()?
tgu_dataset_store() acquires drvdata->lock before writing to this array,
but this read accesses it locklessly, which could cause a data race.
> +}
[ ... ]
> @@ -128,6 +269,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
> {
> struct device *dev = &adev->dev;
> struct tgu_drvdata *drvdata;
> + unsigned int *priority;
> + size_t priority_size;
> int ret;
>
> drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> @@ -143,12 +286,30 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
>
> spin_lock_init(&drvdata->lock);
>
> + tgu_set_reg_number(drvdata);
> + tgu_set_steps(drvdata);
> +
> ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
> if (ret) {
> dev_err(dev, "failed to create sysfs groups: %d\n", ret);
> return ret;
> }
>
> + drvdata->value_table =
> + devm_kzalloc(dev, sizeof(*drvdata->value_table), GFP_KERNEL);
[Severity: High]
Does this expose sysfs files to userspace before their backing memory is
allocated?
If userspace reads a sysfs file immediately after sysfs_create_groups() returns,
but before the memory allocations below complete, tgu_dataset_show() will
dereference the uninitialized drvdata->value_table pointer. Should these
allocations happen before the sysfs groups are created?
> + if (!drvdata->value_table)
> + return -ENOMEM;
[Severity: High]
Does this error path leak the sysfs groups created earlier?
If this allocation fails, the function returns -ENOMEM without calling
sysfs_remove_groups(). The devres cleanup will eventually clear the drvdata
pointer, leaving dangling sysfs nodes. Any subsequent access to these files
would then trigger a NULL pointer dereference or use-after-free.
> +
> + priority_size = MAX_PRIORITY * drvdata->num_reg * drvdata->num_step;
> +
> + priority = devm_kcalloc(dev, priority_size,
> + sizeof(*drvdata->value_table->priority),
> + GFP_KERNEL);
> + if (!priority)
> + return -ENOMEM;
> +
> + drvdata->value_table->priority = priority;
>
> drvdata->enabled = false;
>
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 4/7] qcom-tgu: Add TGU decode support
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
` (2 preceding siblings ...)
2026-07-01 5:55 ` [PATCH v15 3/7] qcom-tgu: Add signal priority support Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:37 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 5/7] qcom-tgu: Add support to configure next action Songwei Chai
` (2 subsequent siblings)
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Decoding is when all the potential pieces for creating a trigger
are brought together for a given step. Example - there may be a
counter keeping track of some occurrences and a priority-group that
is being used to detect a pattern on the sense inputs. These 2
inputs to condition_decode must be programmed, for a given step,
to establish the condition for the trigger, or movement to another
steps.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 +
drivers/hwtracing/qcom/tgu.c | 157 +++++++++++++++---
drivers/hwtracing/qcom/tgu.h | 27 +++
3 files changed, 170 insertions(+), 21 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 2f1d4ecaf76b..81a4e6036b72 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -14,3 +14,10 @@ KernelVersion: 7.2
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the sensed signal with specific step and priority for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_decode/reg[0:3]
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the decode mode with specific step for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 7d69986c3e3d..937211923d93 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -18,8 +18,33 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
int step_index, int operation_index,
int reg_index)
{
- return operation_index * (drvdata->num_step) * (drvdata->num_reg) +
- step_index * (drvdata->num_reg) + reg_index;
+ switch (operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ return operation_index * (drvdata->num_step) *
+ (drvdata->num_reg) +
+ step_index * (drvdata->num_reg) + reg_index;
+ case TGU_CONDITION_DECODE:
+ return step_index * (drvdata->num_condition_decode) +
+ reg_index;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int check_array_location(struct tgu_drvdata *drvdata, int step,
+ int ops, int reg)
+{
+ int result = calculate_array_location(drvdata, step, ops, reg);
+
+ if (result == -EINVAL)
+ dev_err(drvdata->dev, "check array location - Fail\n");
+
+ return result;
}
static ssize_t tgu_dataset_show(struct device *dev,
@@ -30,12 +55,26 @@ static ssize_t tgu_dataset_show(struct device *dev,
container_of(attr, struct tgu_attribute, attr);
int index;
- index = calculate_array_location(drvdata, tgu_attr->step_index,
- tgu_attr->operation_index,
- tgu_attr->reg_num);
-
- return sysfs_emit(buf, "0x%x\n",
- drvdata->value_table->priority[index]);
+ index = check_array_location(drvdata, tgu_attr->step_index,
+ tgu_attr->operation_index, tgu_attr->reg_num);
+
+ if (index == -EINVAL)
+ return index;
+
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->priority[index]);
+ case TGU_CONDITION_DECODE:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->condition_decode[index]);
+ default:
+ break;
+ }
+ return -EINVAL;
}
static ssize_t tgu_dataset_store(struct device *dev,
@@ -54,13 +93,31 @@ static ssize_t tgu_dataset_store(struct device *dev,
return ret;
guard(spinlock)(&tgu_drvdata->lock);
- index = calculate_array_location(tgu_drvdata, tgu_attr->step_index,
+ index = check_array_location(tgu_drvdata, tgu_attr->step_index,
tgu_attr->operation_index,
tgu_attr->reg_num);
- tgu_drvdata->value_table->priority[index] = val;
+ if (index == -EINVAL)
+ return index;
+
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ tgu_drvdata->value_table->priority[index] = val;
+ ret = size;
+ break;
+ case TGU_CONDITION_DECODE:
+ tgu_drvdata->value_table->condition_decode[index] = val;
+ ret = size;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
- return size;
+ return ret;
}
static umode_t tgu_node_visible(struct kobject *kobject,
@@ -77,13 +134,26 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->step_index >= drvdata->num_step)
return SYSFS_GROUP_INVISIBLE;
- if (tgu_attr->reg_num >= drvdata->num_reg)
- return 0;
+ switch (tgu_attr->operation_index) {
+ case TGU_PRIORITY0:
+ case TGU_PRIORITY1:
+ case TGU_PRIORITY2:
+ case TGU_PRIORITY3:
+ if (tgu_attr->reg_num < drvdata->num_reg)
+ return attr->mode;
+ break;
+ case TGU_CONDITION_DECODE:
+ if (tgu_attr->reg_num < drvdata->num_condition_decode)
+ return attr->mode;
+ break;
+ default:
+ break;
+ }
- return attr->mode;
+ return 0;
}
-static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
+static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
{
int i, j, k, index;
@@ -91,8 +161,10 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
for (i = 0; i < drvdata->num_step; i++) {
for (j = 0; j < MAX_PRIORITY; j++) {
for (k = 0; k < drvdata->num_reg; k++) {
- index = calculate_array_location(
+ index = check_array_location(
drvdata, i, j, k);
+ if (index == -EINVAL)
+ goto exit;
writel(drvdata->value_table->priority[index],
drvdata->base +
@@ -100,9 +172,23 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
}
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_condition_decode; j++) {
+ index = check_array_location(drvdata, i,
+ TGU_CONDITION_DECODE, j);
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->value_table->condition_decode[index],
+ drvdata->base + CONDITION_DECODE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
+exit:
TGU_LOCK(drvdata->base);
+ return index >= 0 ? 0 : -EINVAL;
}
static void tgu_set_reg_number(struct tgu_drvdata *drvdata)
@@ -131,16 +217,26 @@ static void tgu_set_steps(struct tgu_drvdata *drvdata)
drvdata->num_step = TGU_DEVID_STEPS(devid);
}
+static void tgu_set_conditions(struct tgu_drvdata *drvdata)
+{
+ u32 devid;
+
+ devid = readl(drvdata->base + TGU_DEVID);
+ drvdata->num_condition_decode = TGU_DEVID_CONDITIONS(devid);
+}
+
static int tgu_enable(struct device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ int ret;
guard(spinlock)(&drvdata->lock);
- drvdata->enabled = true;
- tgu_write_all_hw_regs(drvdata);
+ ret = tgu_write_all_hw_regs(drvdata);
+ if (!ret)
+ drvdata->enabled = true;
- return 0;
+ return ret;
}
static void tgu_do_disable(struct tgu_drvdata *drvdata)
@@ -262,6 +358,14 @@ static const struct attribute_group *tgu_attr_groups[] = {
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 1),
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 2),
PRIORITY_ATTRIBUTE_GROUP_INIT(7, 3),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(0),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(1),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(2),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(3),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(4),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(5),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(6),
+ CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
@@ -269,8 +373,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority;
- size_t priority_size;
+ unsigned int *priority, *condition;
+ size_t priority_size, condition_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -288,6 +392,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
tgu_set_reg_number(drvdata);
tgu_set_steps(drvdata);
+ tgu_set_conditions(drvdata);
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
@@ -310,6 +415,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->priority = priority;
+ condition_size = drvdata->num_condition_decode * drvdata->num_step;
+
+ condition = devm_kcalloc(dev, condition_size,
+ sizeof(*(drvdata->value_table->condition_decode)),
+ GFP_KERNEL);
+ if (!condition)
+ return -ENOMEM;
+
+ drvdata->value_table->condition_decode = condition;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index f994d83acb1d..56e4161a8bc2 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -16,6 +16,8 @@
((int)FIELD_GET(GENMASK(17, 10), devid_val))
#define TGU_DEVID_STEPS(devid_val) \
((int)FIELD_GET(GENMASK(6, 3), devid_val))
+#define TGU_DEVID_CONDITIONS(devid_val) \
+ ((int)FIELD_GET(GENMASK(2, 0), devid_val))
#define TGU_BITS_PER_SIGNAL 4
#define LENGTH_REGISTER 32
@@ -49,6 +51,7 @@
*/
#define STEP_OFFSET 0x1D8
#define PRIORITY_START_OFFSET 0x0074
+#define CONDITION_DECODE_OFFSET 0x0050
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -57,6 +60,9 @@
(PRIORITY_START_OFFSET + PRIORITY_OFFSET * priority +\
REG_OFFSET * reg + STEP_OFFSET * step)
+#define CONDITION_DECODE_STEP(step, decode) \
+ (CONDITION_DECODE_OFFSET + REG_OFFSET * decode + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -68,6 +74,8 @@
#define STEP_PRIORITY(step_index, reg_num, priority) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_PRIORITY##priority, \
reg_num)
+#define STEP_DECODE(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -91,6 +99,14 @@
NULL \
}
+#define STEP_DECODE_LIST(n) \
+ {STEP_DECODE(n, 0), \
+ STEP_DECODE(n, 1), \
+ STEP_DECODE(n, 2), \
+ STEP_DECODE(n, 3), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -98,11 +114,19 @@
.name = "step" #step "_priority" #priority \
})
+#define CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_DECODE_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_condition_decode" \
+ })
+
enum operation_index {
TGU_PRIORITY0,
TGU_PRIORITY1,
TGU_PRIORITY2,
TGU_PRIORITY3,
+ TGU_CONDITION_DECODE,
};
/* Maximum priority that TGU supports */
@@ -117,6 +141,7 @@ struct tgu_attribute {
struct value_table {
unsigned int *priority;
+ unsigned int *condition_decode;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -146,6 +171,7 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @value_table: Store given value based on relevant parameters
* @num_reg: Maximum number of registers
* @num_step: Maximum step size
+ * @num_condition_decode: Maximum number of condition_decode
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -160,6 +186,7 @@ struct tgu_drvdata {
struct value_table *value_table;
int num_reg;
int num_step;
+ int num_condition_decode;
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 4/7] qcom-tgu: Add TGU decode support
2026-07-01 5:55 ` [PATCH v15 4/7] qcom-tgu: Add TGU decode support Songwei Chai
@ 2026-07-01 6:37 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:37 UTC (permalink / raw)
To: Songwei Chai; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] The local variable `index` is used uninitialized in `tgu_write_all_hw_regs()`, leading to unpredictable return values.
Pre-existing issues:
- [High] Probe error paths fail to remove manually created sysfs groups, creating a Use-After-Free vulnerability.
- [High] Sysfs attributes are exposed to userspace before their internal data structures are allocated, exposing a window for a NULL pointer dereference.
- [High] TOCTOU race conditions in `enable_tgu_store` and `reset_tgu_store` allow concurrent executions to permanently leak PM reference counts.
--
commit 02dfd241f8e959109566cea8502a03ef77cfcf4f
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add TGU decode support
This patch adds condition decode support to the Qualcomm Trace and Gating Unit
(TGU). It introduces registers to establish conditions for triggers or step
movements.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> index 7d69986c3e3d2..937211923d937 100644
> --- a/drivers/hwtracing/qcom/tgu.c
> +++ b/drivers/hwtracing/qcom/tgu.c
[ ... ]
> @@ -91,8 +161,10 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
> for (i = 0; i < drvdata->num_step; i++) {
> for (j = 0; j < MAX_PRIORITY; j++) {
> for (k = 0; k < drvdata->num_reg; k++) {
> - index = calculate_array_location(
> + index = check_array_location(
> drvdata, i, j, k);
> + if (index == -EINVAL)
> + goto exit;
>
> writel(drvdata->value_table->priority[index],
> drvdata->base +
> @@ -100,9 +172,23 @@ static void tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
> }
> }
> }
> +
> + for (i = 0; i < drvdata->num_step; i++) {
> + for (j = 0; j < drvdata->num_condition_decode; j++) {
> + index = check_array_location(drvdata, i,
> + TGU_CONDITION_DECODE, j);
> + if (index == -EINVAL)
> + goto exit;
> +
> + writel(drvdata->value_table->condition_decode[index],
> + drvdata->base + CONDITION_DECODE_STEP(i, j));
> + }
> + }
> /* Enable TGU to program the triggers */
> writel(1, drvdata->base + TGU_CONTROL);
> +exit:
> TGU_LOCK(drvdata->base);
> + return index >= 0 ? 0 : -EINVAL;
[Severity: High]
Could index be used uninitialized here?
If the hardware reports 0 for drvdata->num_step (e.g., via TGU_DEVID_STEPS),
the nested loops in tgu_write_all_hw_regs() are bypassed entirely.
The function then evaluates index >= 0 ? 0 : -EINVAL using the uninitialized
stack value of index, leading to unpredictable initialization failures or
spurious successes.
> }
[ ... ]
> @@ -131,16 +217,26 @@ static void tgu_set_steps(struct tgu_drvdata *drvdata)
> drvdata->num_step = TGU_DEVID_STEPS(devid);
> }
>
> +static void tgu_set_conditions(struct tgu_drvdata *drvdata)
> +{
> + u32 devid;
> +
> + devid = readl(drvdata->base + TGU_DEVID);
> + drvdata->num_condition_decode = TGU_DEVID_CONDITIONS(devid);
> +}
> +
> static int tgu_enable(struct device *dev)
> {
> struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
> + int ret;
>
> guard(spinlock)(&drvdata->lock);
> - drvdata->enabled = true;
>
> - tgu_write_all_hw_regs(drvdata);
> + ret = tgu_write_all_hw_regs(drvdata);
> + if (!ret)
> + drvdata->enabled = true;
[Severity: High]
This isn't a bug introduced by this patch, but is there a TOCTOU race
condition in enable_tgu_store() and reset_tgu_store() that allows
concurrent executions to permanently leak PM reference counts?
enable_tgu_store() checks drvdata->enabled under a spinlock, drops the lock,
and acquires a PM reference via pm_runtime_resume_and_get() before calling
tgu_enable().
Two concurrent writers can both bypass the check and acquire references, but
a subsequent disable will only drop one reference, preventing the device and
its hierarchy from entering low-power states.
>
> - return 0;
> + return ret;
> }
[ ... ]
> @@ -288,6 +392,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
>
> tgu_set_reg_number(drvdata);
> tgu_set_steps(drvdata);
> + tgu_set_conditions(drvdata);
>
> ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
[Severity: High]
This is a pre-existing issue, but does this early sysfs creation expose a
window for a NULL pointer dereference?
In tgu_probe(), sysfs_create_groups() exposes the device attributes to user
space before drvdata->value_table and its nested arrays are allocated.
If a user process accesses the sysfs files immediately, the sysfs handlers
(like tgu_dataset_show) will dereference a NULL value_table pointer, causing
a kernel panic. Any unprivileged user or udev rule can read the 0644 sysfs
files during this window.
> if (ret) {
> @@ -310,6 +415,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
>
> drvdata->value_table->priority = priority;
>
> + condition_size = drvdata->num_condition_decode * drvdata->num_step;
> +
> + condition = devm_kcalloc(dev, condition_size,
> + sizeof(*(drvdata->value_table->condition_decode)),
> + GFP_KERNEL);
> + if (!condition)
> + return -ENOMEM;
[Severity: High]
This isn't a bug introduced by this patch, but doesn't this error path fail
to remove manually created sysfs groups, creating a Use-After-Free
vulnerability?
If memory allocations fail after sysfs_create_groups() is called in
tgu_probe(), the function returns -ENOMEM without calling
sysfs_remove_groups().
The driver core then frees the devm-managed drvdata, leaving dangling sysfs
files on the device's kobject. Any local unprivileged user can read the
0644 sysfs files, triggering a UAF on the freed drvdata pointer.
> +
> + drvdata->value_table->condition_decode = condition;
> +
> drvdata->enabled = false;
>
> pm_runtime_put(&adev->dev);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=4
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 5/7] qcom-tgu: Add support to configure next action
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
` (3 preceding siblings ...)
2026-07-01 5:55 ` [PATCH v15 4/7] qcom-tgu: Add TGU decode support Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:45 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 6/7] qcom-tgu: Add timer/counter functionality for TGU Songwei Chai
2026-07-01 5:55 ` [PATCH v15 7/7] qcom-tgu: Add reset node to initialize Songwei Chai
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Add "select" node for each step to determine if another step is taken,
trigger(s) are generated, counters/timers incremented/decremented, etc.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 +++
drivers/hwtracing/qcom/tgu.c | 53 ++++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 26 +++++++++
3 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 81a4e6036b72..f8b4ea199faf 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -21,3 +21,10 @@ KernelVersion: 7.2
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the decode mode with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_select/reg[0:3]
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the next action with specific step for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 937211923d93..fefe932059cb 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -29,6 +29,9 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
case TGU_CONDITION_DECODE:
return step_index * (drvdata->num_condition_decode) +
reg_index;
+ case TGU_CONDITION_SELECT:
+ return step_index * (drvdata->num_condition_select) +
+ reg_index;
default:
break;
}
@@ -71,6 +74,9 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_DECODE:
return sysfs_emit(buf, "0x%x\n",
drvdata->value_table->condition_decode[index]);
+ case TGU_CONDITION_SELECT:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->condition_select[index]);
default:
break;
}
@@ -112,6 +118,10 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->value_table->condition_decode[index] = val;
ret = size;
break;
+ case TGU_CONDITION_SELECT:
+ tgu_drvdata->value_table->condition_select[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -146,6 +156,13 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->reg_num < drvdata->num_condition_decode)
return attr->mode;
break;
+ case TGU_CONDITION_SELECT:
+ /* 'default' register is at the end of 'select' region */
+ if (tgu_attr->reg_num == drvdata->num_condition_select - 1)
+ attr->name = "default";
+ if (tgu_attr->reg_num < drvdata->num_condition_select)
+ return attr->mode;
+ break;
default:
break;
}
@@ -184,6 +201,18 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
drvdata->base + CONDITION_DECODE_STEP(i, j));
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_condition_select; j++) {
+ index = check_array_location(drvdata, i,
+ TGU_CONDITION_SELECT, j);
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->value_table->condition_select[index],
+ drvdata->base + CONDITION_SELECT_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -223,6 +252,8 @@ static void tgu_set_conditions(struct tgu_drvdata *drvdata)
devid = readl(drvdata->base + TGU_DEVID);
drvdata->num_condition_decode = TGU_DEVID_CONDITIONS(devid);
+ /* select region has an additional 'default' register */
+ drvdata->num_condition_select = TGU_DEVID_CONDITIONS(devid) + 1;
}
static int tgu_enable(struct device *dev)
@@ -366,6 +397,14 @@ static const struct attribute_group *tgu_attr_groups[] = {
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(5),
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(6),
CONDITION_DECODE_ATTRIBUTE_GROUP_INIT(7),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(0),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(1),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(2),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(3),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(4),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(5),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(6),
+ CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
@@ -373,8 +412,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority, *condition;
- size_t priority_size, condition_size;
+ unsigned int *priority, *condition, *select;
+ size_t priority_size, condition_size, select_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -425,6 +464,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->condition_decode = condition;
+ select_size = drvdata->num_condition_select * drvdata->num_step;
+
+ select = devm_kcalloc(dev, select_size,
+ sizeof(*(drvdata->value_table->condition_select)),
+ GFP_KERNEL);
+ if (!select)
+ return -ENOMEM;
+
+ drvdata->value_table->condition_select = select;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index 56e4161a8bc2..c61aa8dc51b0 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -52,6 +52,7 @@
#define STEP_OFFSET 0x1D8
#define PRIORITY_START_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
+#define CONDITION_SELECT_OFFSET 0x0060
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -63,6 +64,9 @@
#define CONDITION_DECODE_STEP(step, decode) \
(CONDITION_DECODE_OFFSET + REG_OFFSET * decode + STEP_OFFSET * step)
+#define CONDITION_SELECT_STEP(step, select) \
+ (CONDITION_SELECT_OFFSET + REG_OFFSET * select + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -76,6 +80,8 @@
reg_num)
#define STEP_DECODE(step_index, reg_num) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
+#define STEP_SELECT(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_SELECT, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -107,6 +113,15 @@
NULL \
}
+#define STEP_SELECT_LIST(n) \
+ {STEP_SELECT(n, 0), \
+ STEP_SELECT(n, 1), \
+ STEP_SELECT(n, 2), \
+ STEP_SELECT(n, 3), \
+ STEP_SELECT(n, 4), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -121,12 +136,20 @@
.name = "step" #step "_condition_decode" \
})
+#define CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_SELECT_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_condition_select" \
+ })
+
enum operation_index {
TGU_PRIORITY0,
TGU_PRIORITY1,
TGU_PRIORITY2,
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
+ TGU_CONDITION_SELECT,
};
/* Maximum priority that TGU supports */
@@ -142,6 +165,7 @@ struct tgu_attribute {
struct value_table {
unsigned int *priority;
unsigned int *condition_decode;
+ unsigned int *condition_select;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -172,6 +196,7 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @num_reg: Maximum number of registers
* @num_step: Maximum step size
* @num_condition_decode: Maximum number of condition_decode
+ * @num_condition_select: Maximum number of condition_select
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -187,6 +212,7 @@ struct tgu_drvdata {
int num_reg;
int num_step;
int num_condition_decode;
+ int num_condition_select;
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 5/7] qcom-tgu: Add support to configure next action
2026-07-01 5:55 ` [PATCH v15 5/7] qcom-tgu: Add support to configure next action Songwei Chai
@ 2026-07-01 6:45 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:45 UTC (permalink / raw)
To: Songwei Chai; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
New issues:
- [High] Modifying `attr->name` dynamically inside the `is_visible` callback permanently mutates a statically allocated and globally shared sysfs attribute.
- [Low] The dynamically renamed `default` sysfs node is missing from the ABI documentation.
Pre-existing issues:
- [High] Sysfs groups are exposed to userspace before internal data structures are fully allocated, creating a race window for a NULL pointer dereference panic.
- [High] Missing cleanup of sysfs groups on probe error paths leads to orphaned interfaces and Use-After-Free.
- [High] TOCTOU race condition in `enable_tgu_store` causes `pm_runtime_put` underflow.
- [High] Runtime PM usage counter leak on module removal.
- [Medium] Uninitialized variable `index` is used in the return statement when hardware configuration loops are skipped.
- [Medium] `tgu_dataset_show` accesses array elements without holding `drvdata->lock`, leading to data races.
--
commit 321ab56e90d93dd3077807f4f183c67363243eb4
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add support to configure next action
This commit adds support to configure the next action for each step by
introducing a select node. It allows configuring triggers, counters, and timers.
> diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> --- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
> +++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
[ ... ]
> @@ -21,3 +21,10 @@ KernelVersion: 7.2
> Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
> Description:
> (RW) Set/Get the decode mode with specific step for TGU.
> +
> +What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_condition_select/reg[0:3]
> +Date: July 2026
> +KernelVersion: 7.2
> +Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
> +Description:
> + (RW) Set/Get the next action with specific step for TGU.
[Severity: Low]
Is the default node missing from the ABI documentation?
The logic in tgu_node_visible() renames the final register in the
condition_select region to default, making it available to userspace, but it
does not appear to be documented here.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> --- a/drivers/hwtracing/qcom/tgu.c
> +++ b/drivers/hwtracing/qcom/tgu.c
[ ... ]
> @@ -71,6 +74,9 @@ static ssize_t tgu_dataset_show(struct device *dev,
> case TGU_CONDITION_DECODE:
> return sysfs_emit(buf, "0x%x\n",
> drvdata->value_table->condition_decode[index]);
> + case TGU_CONDITION_SELECT:
> + return sysfs_emit(buf, "0x%x\n",
> + drvdata->value_table->condition_select[index]);
[Severity: Medium]
This is a pre-existing issue, but does tgu_dataset_show() need to hold
drvdata->lock when reading these arrays?
Arrays like condition_select are zeroed and updated under the lock in
reset_tgu_store() and tgu_dataset_store(), so reading them locklessly here
might lead to torn reads or intermediate states being observed.
[ ... ]
> @@ -146,6 +156,13 @@ static umode_t tgu_node_visible(struct kobject *kobject,
> if (tgu_attr->reg_num < drvdata->num_condition_decode)
> return attr->mode;
> break;
> + case TGU_CONDITION_SELECT:
> + /* 'default' register is at the end of 'select' region */
> + if (tgu_attr->reg_num == drvdata->num_condition_select - 1)
> + attr->name = "default";
[Severity: High]
Does dynamically renaming attr->name here permanently mutate the globally
shared static sysfs attribute?
The struct attribute instances are defined via statically allocated compound
literals at the file scope. Modifying attr->name here changes it for all TGU
instances.
If a system has multiple TGU devices with varying num_condition_select
values, or if the driver is unbound and rebound, the name remains default.
Could this cause a sysfs collision (-EEXIST) when registering subsequent
devices?
[ ... ]
> @@ -184,6 +201,18 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
> drvdata->base + CONDITION_DECODE_STEP(i, j));
> }
> }
> +
> + for (i = 0; i < drvdata->num_step; i++) {
> + for (j = 0; j < drvdata->num_condition_select; j++) {
> + index = check_array_location(drvdata, i,
> + TGU_CONDITION_SELECT, j);
> + if (index == -EINVAL)
> + goto exit;
> +
> + writel(drvdata->value_table->condition_select[index],
> + drvdata->base + CONDITION_SELECT_STEP(i, j));
> + }
> + }
[Severity: Medium]
This is a pre-existing issue, but is the index variable uninitialized if all
the num_step loops in tgu_write_all_hw_regs() are bypassed?
If drvdata->num_step is 0, the function jumps to the exit label and returns
index >= 0 ? 0 : -EINVAL. This evaluates uninitialized stack garbage and
could result in a non-deterministic return value.
[ ... ]
> @@ -425,6 +464,16 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
[ ... ]
> drvdata->value_table->condition_decode = condition;
>
> + select_size = drvdata->num_condition_select * drvdata->num_step;
> +
> + select = devm_kcalloc(dev, select_size,
> + sizeof(*(drvdata->value_table->condition_select)),
> + GFP_KERNEL);
> + if (!select)
> + return -ENOMEM;
[Severity: High]
This is a pre-existing issue, but does sysfs_create_groups() happen earlier
in tgu_probe() before these internal structures are allocated?
If sysfs nodes are exposed to userspace before memory like condition_select
is fully allocated, could an early concurrent sysfs read or write trigger a
NULL pointer dereference?
[Severity: High]
This is a pre-existing issue, but if devm_kcalloc() fails and we return
-ENOMEM here, are the sysfs groups orphaned?
Because sysfs_create_groups() is used instead of a devres managed
alternative, failing probe here without calling sysfs_remove_groups() could
leave sysfs files active while freeing the underlying driver memory,
potentially causing a Use-After-Free if accessed later.
Additionally, there are a couple of pre-existing regressions in the driver
PM handling:
[Severity: High]
This is a pre-existing issue, but is there a race condition in
enable_tgu_store() that causes a pm_runtime_put() underflow?
If two threads concurrently write "0" to enable_tgu, they sequentially pass
the lock and both observe drvdata->enabled == true:
scoped_guard(spinlock, &drvdata->lock) {
if (!drvdata->enabled)
return -EINVAL;
}
Both threads then proceed locklessly to call tgu_disable(dev) and
pm_runtime_put(dev). Since there was only one matching
pm_runtime_resume_and_get(), this would result in a PM usage counter
underflow.
[Severity: High]
This is a pre-existing issue, but does tgu_remove() neglect to release the
runtime PM reference if the device is currently enabled?
tgu_remove() calls tgu_disable(dev) to stop the hardware, but does not seem
to call pm_runtime_put() to balance the PM reference acquired during
enablement, which might leak the PM reference and keep the parent bus awake.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 6/7] qcom-tgu: Add timer/counter functionality for TGU
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
` (4 preceding siblings ...)
2026-07-01 5:55 ` [PATCH v15 5/7] qcom-tgu: Add support to configure next action Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 6:54 ` sashiko-bot
2026-07-01 5:55 ` [PATCH v15 7/7] qcom-tgu: Add reset node to initialize Songwei Chai
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Add counter and timer node for each step which could be
programed if they are to be utilized in trigger event/sequence.
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 14 +++
drivers/hwtracing/qcom/tgu.c | 116 +++++++++++++++++-
drivers/hwtracing/qcom/tgu.h | 57 +++++++++
3 files changed, 185 insertions(+), 2 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index f8b4ea199faf..7fad11eb759c 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -28,3 +28,17 @@ KernelVersion: 7.2
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the next action with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_timer/reg[0:1]
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the timer value with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/step[0:7]_counter/reg[0:1]
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (RW) Set/Get the counter value with specific step for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index fefe932059cb..6d5bf2621cb0 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -32,6 +32,10 @@ static int calculate_array_location(struct tgu_drvdata *drvdata,
case TGU_CONDITION_SELECT:
return step_index * (drvdata->num_condition_select) +
reg_index;
+ case TGU_COUNTER:
+ return step_index * (drvdata->num_counter) + reg_index;
+ case TGU_TIMER:
+ return step_index * (drvdata->num_timer) + reg_index;
default:
break;
}
@@ -77,6 +81,12 @@ static ssize_t tgu_dataset_show(struct device *dev,
case TGU_CONDITION_SELECT:
return sysfs_emit(buf, "0x%x\n",
drvdata->value_table->condition_select[index]);
+ case TGU_TIMER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->timer[index]);
+ case TGU_COUNTER:
+ return sysfs_emit(buf, "0x%x\n",
+ drvdata->value_table->counter[index]);
default:
break;
}
@@ -122,6 +132,14 @@ static ssize_t tgu_dataset_store(struct device *dev,
tgu_drvdata->value_table->condition_select[index] = val;
ret = size;
break;
+ case TGU_TIMER:
+ tgu_drvdata->value_table->timer[index] = val;
+ ret = size;
+ break;
+ case TGU_COUNTER:
+ tgu_drvdata->value_table->counter[index] = val;
+ ret = size;
+ break;
default:
ret = -EINVAL;
break;
@@ -163,6 +181,18 @@ static umode_t tgu_node_visible(struct kobject *kobject,
if (tgu_attr->reg_num < drvdata->num_condition_select)
return attr->mode;
break;
+ case TGU_COUNTER:
+ if (!drvdata->num_counter)
+ break;
+ if (tgu_attr->reg_num < drvdata->num_counter)
+ return attr->mode;
+ break;
+ case TGU_TIMER:
+ if (!drvdata->num_timer)
+ break;
+ if (tgu_attr->reg_num < drvdata->num_timer)
+ return attr->mode;
+ break;
default:
break;
}
@@ -213,6 +243,30 @@ static ssize_t tgu_write_all_hw_regs(struct tgu_drvdata *drvdata)
drvdata->base + CONDITION_SELECT_STEP(i, j));
}
}
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_timer; j++) {
+ index = check_array_location(drvdata, i, TGU_TIMER, j);
+
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->value_table->timer[index],
+ drvdata->base + TIMER_COMPARE_STEP(i, j));
+ }
+ }
+
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_counter; j++) {
+ index = check_array_location(drvdata, i, TGU_COUNTER, j);
+
+ if (index == -EINVAL)
+ goto exit;
+
+ writel(drvdata->value_table->counter[index],
+ drvdata->base + COUNTER_COMPARE_STEP(i, j));
+ }
+ }
/* Enable TGU to program the triggers */
writel(1, drvdata->base + TGU_CONTROL);
exit:
@@ -256,6 +310,27 @@ static void tgu_set_conditions(struct tgu_drvdata *drvdata)
drvdata->num_condition_select = TGU_DEVID_CONDITIONS(devid) + 1;
}
+static void tgu_set_timer_counter(struct tgu_drvdata *drvdata)
+{
+ int num_timers = 0, num_counters = 0;
+ u32 devid2;
+
+ devid2 = readl(drvdata->base + CORESIGHT_DEVID2);
+
+ if (TGU_DEVID2_TIMER0(devid2))
+ num_timers++;
+ if (TGU_DEVID2_TIMER1(devid2))
+ num_timers++;
+
+ if (TGU_DEVID2_COUNTER0(devid2))
+ num_counters++;
+ if (TGU_DEVID2_COUNTER1(devid2))
+ num_counters++;
+
+ drvdata->num_timer = num_timers;
+ drvdata->num_counter = num_counters;
+}
+
static int tgu_enable(struct device *dev)
{
struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
@@ -405,6 +480,22 @@ static const struct attribute_group *tgu_attr_groups[] = {
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(5),
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(6),
CONDITION_SELECT_ATTRIBUTE_GROUP_INIT(7),
+ TIMER_ATTRIBUTE_GROUP_INIT(0),
+ TIMER_ATTRIBUTE_GROUP_INIT(1),
+ TIMER_ATTRIBUTE_GROUP_INIT(2),
+ TIMER_ATTRIBUTE_GROUP_INIT(3),
+ TIMER_ATTRIBUTE_GROUP_INIT(4),
+ TIMER_ATTRIBUTE_GROUP_INIT(5),
+ TIMER_ATTRIBUTE_GROUP_INIT(6),
+ TIMER_ATTRIBUTE_GROUP_INIT(7),
+ COUNTER_ATTRIBUTE_GROUP_INIT(0),
+ COUNTER_ATTRIBUTE_GROUP_INIT(1),
+ COUNTER_ATTRIBUTE_GROUP_INIT(2),
+ COUNTER_ATTRIBUTE_GROUP_INIT(3),
+ COUNTER_ATTRIBUTE_GROUP_INIT(4),
+ COUNTER_ATTRIBUTE_GROUP_INIT(5),
+ COUNTER_ATTRIBUTE_GROUP_INIT(6),
+ COUNTER_ATTRIBUTE_GROUP_INIT(7),
NULL,
};
@@ -412,8 +503,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
struct tgu_drvdata *drvdata;
- unsigned int *priority, *condition, *select;
- size_t priority_size, condition_size, select_size;
+ unsigned int *priority, *condition, *select, *timer, *counter;
+ size_t priority_size, condition_size, select_size, timer_size, counter_size;
int ret;
drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
@@ -432,6 +523,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
tgu_set_reg_number(drvdata);
tgu_set_steps(drvdata);
tgu_set_conditions(drvdata);
+ tgu_set_timer_counter(drvdata);
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
@@ -474,6 +566,26 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
drvdata->value_table->condition_select = select;
+ timer_size = drvdata->num_step * drvdata->num_timer;
+
+ timer = devm_kcalloc(dev, timer_size,
+ sizeof(*(drvdata->value_table->timer)),
+ GFP_KERNEL);
+ if (!timer)
+ return -ENOMEM;
+
+ drvdata->value_table->timer = timer;
+
+ counter_size = drvdata->num_step * drvdata->num_counter;
+
+ counter = devm_kcalloc(dev, counter_size,
+ sizeof(*(drvdata->value_table->counter)),
+ GFP_KERNEL);
+ if (!counter)
+ return -ENOMEM;
+
+ drvdata->value_table->counter = counter;
+
drvdata->enabled = false;
pm_runtime_put(&adev->dev);
diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
index c61aa8dc51b0..1bcbc99169de 100644
--- a/drivers/hwtracing/qcom/tgu.h
+++ b/drivers/hwtracing/qcom/tgu.h
@@ -11,6 +11,7 @@
#define TGU_LAR 0xfb0
#define TGU_UNLOCK_OFFSET 0xc5acce55
#define TGU_DEVID 0xfc8
+#define CORESIGHT_DEVID2 0xfc0
#define TGU_DEVID_SENSE_INPUT(devid_val) \
((int)FIELD_GET(GENMASK(17, 10), devid_val))
@@ -18,6 +19,16 @@
((int)FIELD_GET(GENMASK(6, 3), devid_val))
#define TGU_DEVID_CONDITIONS(devid_val) \
((int)FIELD_GET(GENMASK(2, 0), devid_val))
+#define TGU_DEVID2_TIMER0(devid_val) \
+ ((int)FIELD_GET(GENMASK(23, 18), devid_val))
+#define TGU_DEVID2_TIMER1(devid_val) \
+ ((int)FIELD_GET(GENMASK(17, 13), devid_val))
+#define TGU_DEVID2_COUNTER0(devid_val) \
+ ((int)FIELD_GET(GENMASK(11, 6), devid_val))
+#define TGU_DEVID2_COUNTER1(devid_val) \
+ ((int)FIELD_GET(GENMASK(5, 0), devid_val))
+
+
#define TGU_BITS_PER_SIGNAL 4
#define LENGTH_REGISTER 32
@@ -53,6 +64,8 @@
#define PRIORITY_START_OFFSET 0x0074
#define CONDITION_DECODE_OFFSET 0x0050
#define CONDITION_SELECT_OFFSET 0x0060
+#define TIMER_START_OFFSET 0x0040
+#define COUNTER_START_OFFSET 0x0048
#define PRIORITY_OFFSET 0x60
#define REG_OFFSET 0x4
@@ -67,6 +80,12 @@
#define CONDITION_SELECT_STEP(step, select) \
(CONDITION_SELECT_OFFSET + REG_OFFSET * select + STEP_OFFSET * step)
+#define TIMER_COMPARE_STEP(step, timer) \
+ (TIMER_START_OFFSET + REG_OFFSET * timer + STEP_OFFSET * step)
+
+#define COUNTER_COMPARE_STEP(step, counter) \
+ (COUNTER_START_OFFSET + REG_OFFSET * counter + STEP_OFFSET * step)
+
#define tgu_dataset_rw(name, step_index, type, reg_num) \
(&((struct tgu_attribute[]){ { \
__ATTR(name, 0644, tgu_dataset_show, tgu_dataset_store), \
@@ -82,6 +101,10 @@
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_DECODE, reg_num)
#define STEP_SELECT(step_index, reg_num) \
tgu_dataset_rw(reg##reg_num, step_index, TGU_CONDITION_SELECT, reg_num)
+#define STEP_TIMER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_TIMER, reg_num)
+#define STEP_COUNTER(step_index, reg_num) \
+ tgu_dataset_rw(reg##reg_num, step_index, TGU_COUNTER, reg_num)
#define STEP_PRIORITY_LIST(step_index, priority) \
{STEP_PRIORITY(step_index, 0, priority), \
@@ -122,6 +145,18 @@
NULL \
}
+#define STEP_TIMER_LIST(n) \
+ {STEP_TIMER(n, 0), \
+ STEP_TIMER(n, 1), \
+ NULL \
+ }
+
+#define STEP_COUNTER_LIST(n) \
+ {STEP_COUNTER(n, 0), \
+ STEP_COUNTER(n, 1), \
+ NULL \
+ }
+
#define PRIORITY_ATTRIBUTE_GROUP_INIT(step, priority)\
(&(const struct attribute_group){\
.attrs = (struct attribute*[])STEP_PRIORITY_LIST(step, priority),\
@@ -143,6 +178,20 @@
.name = "step" #step "_condition_select" \
})
+#define TIMER_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_TIMER_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_timer" \
+ })
+
+#define COUNTER_ATTRIBUTE_GROUP_INIT(step)\
+ (&(const struct attribute_group){\
+ .attrs = (struct attribute*[])STEP_COUNTER_LIST(step),\
+ .is_visible = tgu_node_visible,\
+ .name = "step" #step "_counter" \
+ })
+
enum operation_index {
TGU_PRIORITY0,
TGU_PRIORITY1,
@@ -150,6 +199,8 @@ enum operation_index {
TGU_PRIORITY3,
TGU_CONDITION_DECODE,
TGU_CONDITION_SELECT,
+ TGU_TIMER,
+ TGU_COUNTER,
};
/* Maximum priority that TGU supports */
@@ -166,6 +217,8 @@ struct value_table {
unsigned int *priority;
unsigned int *condition_decode;
unsigned int *condition_select;
+ unsigned int *timer;
+ unsigned int *counter;
};
static inline void TGU_LOCK(void __iomem *addr)
@@ -197,6 +250,8 @@ static inline void TGU_UNLOCK(void __iomem *addr)
* @num_step: Maximum step size
* @num_condition_decode: Maximum number of condition_decode
* @num_condition_select: Maximum number of condition_select
+ * @num_timer: Maximum number of timers
+ * @num_counter: Maximum number of counters
*
* This structure defines the data associated with a TGU device,
* including its base address, device pointers, clock, spinlock for
@@ -213,6 +268,8 @@ struct tgu_drvdata {
int num_step;
int num_condition_decode;
int num_condition_select;
+ int num_timer;
+ int num_counter;
};
#endif
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 6/7] qcom-tgu: Add timer/counter functionality for TGU
2026-07-01 5:55 ` [PATCH v15 6/7] qcom-tgu: Add timer/counter functionality for TGU Songwei Chai
@ 2026-07-01 6:54 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 6:54 UTC (permalink / raw)
To: Songwei Chai; +Cc: conor+dt, robh, devicetree
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
New issues:
- [Medium] Incorrect bitmask definition for TGU_DEVID2_TIMER1 skips bit 12.
- [Low] Empty sysfs directories for timers and counters are created when the hardware does not support them.
Pre-existing issues:
- [High] Sysfs attributes are exposed to userspace before their underlying data structures are fully allocated, leading to a possible NULL pointer dereference.
- [High] Probe error paths leak sysfs groups and leave dangling pointers, causing a Use-After-Free if accessed.
- [High] Unsynchronized PM runtime operations in enable_tgu_store() cause PM usage counter leaks.
- [High] Driver unbind leaks a PM runtime reference if the TGU device was left enabled.
--
commit 358a24e20604be5f3d933b0c130bc3468d07b832
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add timer/counter functionality for TGU
This commit adds counter and timer nodes for each TGU step. It provides sysfs
interfaces to program these components for use in trigger events or sequences.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> index fefe932059cbb..6d5bf2621cb05 100644
> --- a/drivers/hwtracing/qcom/tgu.c
> +++ b/drivers/hwtracing/qcom/tgu.c
[ ... ]
> @@ -163,6 +181,18 @@ static umode_t tgu_node_visible(struct kobject *kobject,
> if (tgu_attr->reg_num < drvdata->num_condition_select)
> return attr->mode;
> break;
> + case TGU_COUNTER:
> + if (!drvdata->num_counter)
> + break;
[Severity: Low]
When drvdata->num_counter is 0, this breaks out and returns 0 instead of
SYSFS_GROUP_INVISIBLE. Could this leave empty sysfs directories behind when
the hardware doesn't support counters? Returning SYSFS_GROUP_INVISIBLE would
properly suppress the creation of the named group directory.
> + if (tgu_attr->reg_num < drvdata->num_counter)
> + return attr->mode;
> + break;
> + case TGU_TIMER:
> + if (!drvdata->num_timer)
> + break;
[Severity: Low]
Similarly, should this return SYSFS_GROUP_INVISIBLE when drvdata->num_timer
is 0 to prevent creating empty directories?
> + if (tgu_attr->reg_num < drvdata->num_timer)
> + return attr->mode;
> + break;
> default:
> break;
> }
[ ... ]
> @@ -412,8 +503,8 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
> {
> struct device *dev = &adev->dev;
> struct tgu_drvdata *drvdata;
> - unsigned int *priority, *condition, *select;
> - size_t priority_size, condition_size, select_size;
> + unsigned int *priority, *condition, *select, *timer, *counter;
> + size_t priority_size, condition_size, select_size, timer_size, counter_size;
> int ret;
>
> drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
> @@ -432,6 +523,7 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
> tgu_set_reg_number(drvdata);
> tgu_set_steps(drvdata);
> tgu_set_conditions(drvdata);
> + tgu_set_timer_counter(drvdata);
>
> ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
> if (ret) {
> @@ -474,6 +566,26 @@ static int tgu_probe(struct amba_device *adev, const struct amba_id *id)
[Severity: High]
This is a pre-existing issue, but sysfs_create_groups() is called before the
value_table and its arrays are allocated. If a concurrent userspace process
reads a sysfs file, could tgu_dataset_show() dereference an unallocated
drvdata->value_table, resulting in a NULL pointer dereference?
>
> drvdata->value_table->condition_select = select;
>
> + timer_size = drvdata->num_step * drvdata->num_timer;
> +
> + timer = devm_kcalloc(dev, timer_size,
> + sizeof(*(drvdata->value_table->timer)),
> + GFP_KERNEL);
> + if (!timer)
> + return -ENOMEM;
[Severity: High]
This isn't a bug introduced by this patch, but if devm_kcalloc() fails here,
we return -ENOMEM without cleaning up the sysfs groups created earlier.
The driver core will free devres-managed memory, but the active sysfs files
will remain. If userspace reads these files afterward, could it trigger a
use-after-free or NULL pointer dereference when accessing drvdata?
> +
> + drvdata->value_table->timer = timer;
> +
> + counter_size = drvdata->num_step * drvdata->num_counter;
> +
> + counter = devm_kcalloc(dev, counter_size,
> + sizeof(*(drvdata->value_table->counter)),
> + GFP_KERNEL);
> + if (!counter)
> + return -ENOMEM;
> +
> + drvdata->value_table->counter = counter;
> +
> drvdata->enabled = false;
>
> pm_runtime_put(&adev->dev);
> diff --git a/drivers/hwtracing/qcom/tgu.h b/drivers/hwtracing/qcom/tgu.h
> index c61aa8dc51b0d..1bcbc99169def 100644
> --- a/drivers/hwtracing/qcom/tgu.h
> +++ b/drivers/hwtracing/qcom/tgu.h
[ ... ]
> @@ -18,6 +19,16 @@
> ((int)FIELD_GET(GENMASK(6, 3), devid_val))
> #define TGU_DEVID_CONDITIONS(devid_val) \
> ((int)FIELD_GET(GENMASK(2, 0), devid_val))
> +#define TGU_DEVID2_TIMER0(devid_val) \
> + ((int)FIELD_GET(GENMASK(23, 18), devid_val))
> +#define TGU_DEVID2_TIMER1(devid_val) \
> + ((int)FIELD_GET(GENMASK(17, 13), devid_val))
[Severity: Medium]
Does GENMASK(17, 13) correctly represent the TIMER1 mask? TIMER0, COUNTER0,
and COUNTER1 are all defined as 6 bits wide, but GENMASK(17, 13) is only 5
bits wide, skipping bit 12. Should this be GENMASK(17, 12) instead?
> +#define TGU_DEVID2_COUNTER0(devid_val) \
> + ((int)FIELD_GET(GENMASK(11, 6), devid_val))
> +#define TGU_DEVID2_COUNTER1(devid_val) \
> + ((int)FIELD_GET(GENMASK(5, 0), devid_val))
> +
> +
I also noticed a couple of pre-existing issues in other parts of the driver
unrelated to this specific patch:
[Severity: High]
This is a pre-existing issue, but in enable_tgu_store():
scoped_guard(spinlock, &drvdata->lock) {
if (drvdata->enabled)
return -EBUSY;
}
ret = pm_runtime_resume_and_get(dev);
The lock is dropped before the PM runtime reference is acquired. Could two
concurrent writers both bypass the !drvdata->enabled check, allowing both
to acquire PM references, but only one to be released during disable, leaking
the PM usage counter?
[Severity: High]
This is a pre-existing issue, but in tgu_remove():
sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
tgu_disable(dev);
The tgu_disable() function disables the hardware but doesn't call
pm_runtime_put(). If the device was left enabled via sysfs, does this leak
the PM runtime reference upon driver unbind?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v15 7/7] qcom-tgu: Add reset node to initialize
2026-07-01 5:55 [PATCH v15 0/7] Provide support for Trigger Generation Unit Songwei Chai
` (5 preceding siblings ...)
2026-07-01 5:55 ` [PATCH v15 6/7] qcom-tgu: Add timer/counter functionality for TGU Songwei Chai
@ 2026-07-01 5:55 ` Songwei Chai
2026-07-01 7:06 ` sashiko-bot
6 siblings, 1 reply; 15+ messages in thread
From: Songwei Chai @ 2026-07-01 5:55 UTC (permalink / raw)
To: andersson, alexander.shishkin, mike.leach, konrad.dybcio,
suzuki.poulose, james.clark, krzk+dt, conor+dt
Cc: Songwei Chai, linux-kernel, linux-arm-kernel, linux-arm-msm,
coresight, devicetree, gregkh, Jie Gan
Add reset node to initialize the value of
priority/condition_decode/condition_select/timer/counter nodes.
Signed-off-by: Songwei Chai <songwei.chai@oss.qualcomm.com>
Reviewed-by: Jie Gan <jie.gan@oss.qualcomm.com>
---
.../ABI/testing/sysfs-bus-amba-devices-tgu | 7 ++
drivers/hwtracing/qcom/tgu.c | 74 +++++++++++++++++++
2 files changed, 81 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
index 7fad11eb759c..cb0710fea9c7 100644
--- a/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
+++ b/Documentation/ABI/testing/sysfs-bus-amba-devices-tgu
@@ -42,3 +42,10 @@ KernelVersion: 7.2
Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
Description:
(RW) Set/Get the counter value with specific step for TGU.
+
+What: /sys/bus/amba/devices/<tgu-name>/reset_tgu
+Date: July 2026
+KernelVersion: 7.2
+Contact: Jinlong Mao <jinlong.mao@oss.qualcomm.com>, Songwei Chai <songwei.chai@oss.qualcomm.com>
+Description:
+ (Write) Write 1 to reset the dataset for TGU.
diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
index 6d5bf2621cb0..9fb51f2a912f 100644
--- a/drivers/hwtracing/qcom/tgu.c
+++ b/drivers/hwtracing/qcom/tgu.c
@@ -420,8 +420,82 @@ static ssize_t enable_tgu_store(struct device *dev,
}
static DEVICE_ATTR_RW(enable_tgu);
+/* reset_tgu_store - Reset Trace and Gating Unit (TGU) configuration. */
+static ssize_t reset_tgu_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
+ struct value_table *vt = drvdata->value_table;
+ u32 *cond_decode = drvdata->value_table->condition_decode;
+ unsigned long value;
+ int i, j, ret;
+
+ if (kstrtoul(buf, 0, &value) || value != 1)
+ return -EINVAL;
+
+ spin_lock(&drvdata->lock);
+ if (!drvdata->enabled) {
+ spin_unlock(&drvdata->lock);
+ ret = pm_runtime_resume_and_get(drvdata->dev);
+ if (ret)
+ return ret;
+ spin_lock(&drvdata->lock);
+ }
+
+ tgu_do_disable(drvdata);
+
+ if (vt->priority) {
+ size_t size = MAX_PRIORITY * drvdata->num_step *
+ drvdata->num_reg * sizeof(unsigned int);
+ memset(vt->priority, 0, size);
+ }
+
+ if (vt->condition_decode) {
+ size_t size = drvdata->num_condition_decode *
+ drvdata->num_step * sizeof(unsigned int);
+ memset(vt->condition_decode, 0, size);
+ }
+
+ /* Initialize all condition registers to NOT(value=0x1000000) */
+ for (i = 0; i < drvdata->num_step; i++) {
+ for (j = 0; j < drvdata->num_condition_decode; j++) {
+ cond_decode[calculate_array_location(drvdata, i,
+ TGU_CONDITION_DECODE, j)] = 0x1000000;
+ }
+ }
+
+ if (vt->condition_select) {
+ size_t size = drvdata->num_condition_select *
+ drvdata->num_step * sizeof(unsigned int);
+ memset(vt->condition_select, 0, size);
+ }
+
+ if (vt->timer) {
+ size_t size = (drvdata->num_step) * (drvdata->num_timer) *
+ sizeof(unsigned int);
+ memset(vt->timer, 0, size);
+ }
+
+ if (vt->counter) {
+ size_t size = (drvdata->num_step) * (drvdata->num_counter) *
+ sizeof(unsigned int);
+ memset(vt->counter, 0, size);
+ }
+
+ spin_unlock(&drvdata->lock);
+
+ dev_dbg(dev, "Qualcomm-TGU reset complete\n");
+
+ pm_runtime_put(drvdata->dev);
+
+ return size;
+}
+static DEVICE_ATTR_WO(reset_tgu);
+
static struct attribute *tgu_common_attrs[] = {
&dev_attr_enable_tgu.attr,
+ &dev_attr_reset_tgu.attr,
NULL,
};
--
2.34.1
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v15 7/7] qcom-tgu: Add reset node to initialize
2026-07-01 5:55 ` [PATCH v15 7/7] qcom-tgu: Add reset node to initialize Songwei Chai
@ 2026-07-01 7:06 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-07-01 7:06 UTC (permalink / raw)
To: Songwei Chai; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] A TOCTOU race condition in `reset_tgu_store` leads to a permanent PM reference leak.
Pre-existing issues:
- [High] A TOCTOU race condition in `enable_tgu_store(0)` causes a double `pm_runtime_put()`, leading to PM usage counter underflow.
- [High] A TOCTOU race condition in `enable_tgu_store(1)` allows multiple threads to enable the device concurrently, leaking PM references.
- [High] Missing `sysfs_remove_groups()` on the `tgu_probe()` error path leaves sysfs files accessible, leading to a NULL pointer dereference.
- [High] The `tgu_node_visible` function incorrectly modifies a shared global attribute name, corrupting the sysfs structure.
- [High] The PM runtime usage count is leaked if the device is unbound while enabled.
- [Medium] The function `tgu_dataset_show` reads shared array values without holding the required lock.
--
commit 609c4b01711040b0d5156593e5d5c7a06ebc7c31
Author: Songwei Chai <songwei.chai@oss.qualcomm.com>
qcom-tgu: Add reset node to initialize
Add reset node to initialize the value of
priority/condition_decode/condition_select/timer/counter nodes.
> diff --git a/drivers/hwtracing/qcom/tgu.c b/drivers/hwtracing/qcom/tgu.c
> index 6d5bf2621cb05..9fb51f2a912f9 100644
> --- a/drivers/hwtracing/qcom/tgu.c
> +++ b/drivers/hwtracing/qcom/tgu.c
[ ... ]
> @@ -420,8 +420,82 @@ static ssize_t enable_tgu_store(struct device *dev,
> }
> static DEVICE_ATTR_RW(enable_tgu);
>
> +/* reset_tgu_store - Reset Trace and Gating Unit (TGU) configuration. */
> +static ssize_t reset_tgu_store(struct device *dev,
> + struct device_attribute *attr, const char *buf,
> + size_t size)
> +{
> + struct tgu_drvdata *drvdata = dev_get_drvdata(dev);
> + struct value_table *vt = drvdata->value_table;
> + u32 *cond_decode = drvdata->value_table->condition_decode;
> + unsigned long value;
> + int i, j, ret;
> +
> + if (kstrtoul(buf, 0, &value) || value != 1)
> + return -EINVAL;
> +
> + spin_lock(&drvdata->lock);
> + if (!drvdata->enabled) {
> + spin_unlock(&drvdata->lock);
> + ret = pm_runtime_resume_and_get(drvdata->dev);
[Severity: High]
Does unlocking to get the PM reference introduce a TOCTOU race condition?
If a concurrent enable_tgu_store(1) enables the device and increments the
PM usage count before this thread reacquires the lock, reset_tgu_store()
will unconditionally disable the hardware and clear the enabled flag.
Since the device state is now disabled, would the PM reference acquired by
the concurrent enable_tgu_store(1) be permanently leaked?
> + if (ret)
> + return ret;
> + spin_lock(&drvdata->lock);
> + }
[ ... ]
This is a pre-existing issue, but does enable_tgu_store() have TOCTOU race
conditions?
drivers/hwtracing/qcom/tgu.c:enable_tgu_store() {
...
if (val) {
scoped_guard(spinlock, &drvdata->lock) {
if (drvdata->enabled)
return -EBUSY;
}
ret = pm_runtime_resume_and_get(dev);
...
} else {
scoped_guard(spinlock, &drvdata->lock) {
if (!drvdata->enabled)
return -EINVAL;
}
tgu_disable(dev);
pm_runtime_put(dev);
}
...
}
[Severity: High]
For enable_tgu_store(1), if two threads concurrently observe !drvdata->enabled
within the scoped guard, both will drop the lock and acquire PM references.
Both would then independently call tgu_enable(dev), setting the flag to true.
Would this increment the PM usage count multiple times, while disabling the
device only decrements it once?
[Severity: High]
For enable_tgu_store(0), if two threads concurrently verify
drvdata->enabled == true inside the scoped lock guard and then drop the lock,
the first thread calls tgu_disable(dev), sets the flag to false, and calls
pm_runtime_put(dev). The second thread calls tgu_disable(dev), which returns
early because the flag is now false, but then unconditionally calls
pm_runtime_put(dev). Would this double-put cause a PM usage counter underflow?
This is a pre-existing issue, but does tgu_probe() leave sysfs files
accessible if subsequent allocations fail?
drivers/hwtracing/qcom/tgu.c:tgu_probe() {
...
ret = sysfs_create_groups(&dev->kobj, tgu_attr_groups);
if (ret) {
...
}
drvdata->value_table =
devm_kzalloc(dev, sizeof(*drvdata->value_table), GFP_KERNEL);
if (!drvdata->value_table)
return -ENOMEM;
...
}
[Severity: High]
If devm_kzalloc() fails, the probe returns an error, and the driver core sets
drvdata to NULL. Because sysfs_remove_groups() is not called on this error
path, the sysfs attributes remain. If a user subsequently accesses these
dangling attributes, would dev_get_drvdata(dev) return NULL, causing a panic
when dereferencing drvdata?
This is a pre-existing issue, but does tgu_node_visible() incorrectly modify
a shared global attribute name?
drivers/hwtracing/qcom/tgu.c:tgu_node_visible() {
...
case TGU_CONDITION_SELECT:
/* 'default' register is at the end of 'select' region */
if (tgu_attr->reg_num == drvdata->num_condition_select - 1)
attr->name = "default";
...
}
[Severity: High]
The attributes in tgu_attr_groups appear to be statically allocated. Since
this dynamically executes attr->name = "default", does it destructively
overwrite the shared global attribute structure, permanently altering the name
for all TGU devices?
This is a pre-existing issue, but is the PM runtime usage count leaked if the
device is unbound while enabled?
drivers/hwtracing/qcom/tgu.c:tgu_remove() {
...
static void tgu_remove(struct amba_device *adev)
{
struct device *dev = &adev->dev;
sysfs_remove_groups(&dev->kobj, tgu_attr_groups);
tgu_disable(dev);
}
...
}
[Severity: High]
If the user enables the TGU via enable_tgu_store(1), a PM reference is
acquired. When the device is later unbound, tgu_remove() calls tgu_disable(),
which turns off the hardware and sets the enabled flag to false. However, it
fails to call pm_runtime_put() to balance the PM reference. Does this
permanently leak the PM reference?
This is a pre-existing issue, but does tgu_dataset_show() read shared array
values without holding the required lock?
drivers/hwtracing/qcom/tgu.c:tgu_dataset_show() {
...
case TGU_PRIORITY0:
case TGU_PRIORITY1:
case TGU_PRIORITY2:
case TGU_PRIORITY3:
return sysfs_emit(buf, "0x%x\n",
drvdata->value_table->priority[index]);
...
}
[Severity: Medium]
This function reads values from drvdata->value_table without acquiring
drvdata->lock. Since sysfs store operations can concurrently modify or
memset these arrays while holding the lock, does this expose a data race
where sysfs reads might observe partially written values?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260701055529.445819-1-songwei.chai@oss.qualcomm.com?part=7
^ permalink raw reply [flat|nested] 15+ messages in thread