Re: [PATCH v5 11/14] gtrace: Add perf driver for tracing using perf tool
From: Zane Leung
Date: Wed Sep 23 2026 - 21:36:50 EST
Hi Mayuresh,
On 8/10/2026 11:22 PM, Mayuresh Chitale wrote:
> Add perf driver for the gtrace similar to ARM Coresight and Hisilicon
> PTT drivers. The driver adds 'gtrace' event descriptor which can be used
> by the perf tool to record the trace data. The actual data format depends
> on type of encoder device and optionally the PMU specific trace format
> type.
>
> Co-developed-by: Anup Patel <anup.patel@xxxxxxxxxxxxxxxx>
> Signed-off-by: Anup Patel <anup.patel@xxxxxxxxxxxxxxxx>
> Signed-off-by: Mayuresh Chitale <mayuresh.chitale@xxxxxxxxxxxxxxxx>
> ---
> drivers/hwtracing/gtrace/Kconfig | 1 +
> drivers/hwtracing/gtrace/Makefile | 2 +-
> drivers/hwtracing/gtrace/gtrace-core.c | 15 +-
> drivers/hwtracing/gtrace/gtrace-perf.c | 347 +++++++++++++++++++++++++
> include/linux/gtrace.h | 3 +
> 5 files changed, 366 insertions(+), 2 deletions(-)
> create mode 100644 drivers/hwtracing/gtrace/gtrace-perf.c
>
> diff --git a/drivers/hwtracing/gtrace/Kconfig b/drivers/hwtracing/gtrace/Kconfig
> index de4a3b533591..e5b62d65e2fd 100644
> --- a/drivers/hwtracing/gtrace/Kconfig
> +++ b/drivers/hwtracing/gtrace/Kconfig
> @@ -3,6 +3,7 @@
> menuconfig GTRACE
> tristate "Generic Hardware Trace Support"
> depends on OF
> + select PERF_EVENTS
> help
> This framework provides an architecture-neutral kernel interface
> for hardware trace drivers. It builds a topological view of the
> diff --git a/drivers/hwtracing/gtrace/Makefile b/drivers/hwtracing/gtrace/Makefile
> index 3e0c68d1b632..aa044fdc787b 100644
> --- a/drivers/hwtracing/gtrace/Makefile
> +++ b/drivers/hwtracing/gtrace/Makefile
> @@ -1,7 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
>
> obj-$(CONFIG_GTRACE) += gtrace.o
> -gtrace-y := gtrace-core.o
> +gtrace-y := gtrace-core.o gtrace-perf.o
>
> obj-$(CONFIG_RVTRACE) += rvtrace-platform.o
> obj-$(CONFIG_RVTRACE_ENCODER) += rvtrace-encoder.o
> diff --git a/drivers/hwtracing/gtrace/gtrace-core.c b/drivers/hwtracing/gtrace/gtrace-core.c
> index 53a3679ba69f..102251f6a5a7 100644
> --- a/drivers/hwtracing/gtrace/gtrace-core.c
> +++ b/drivers/hwtracing/gtrace/gtrace-core.c
> @@ -714,13 +714,26 @@ EXPORT_SYMBOL_GPL(__gtrace_register_driver);
>
> static int __init gtrace_init(void)
> {
> + int ret;
> +
> gtrace_init_type_idx();
>
> - return bus_register(>race_bustype);
> + ret = bus_register(>race_bustype);
> + if (ret)
> + return ret;
> +
> + ret = gtrace_perf_init();
> + if (ret) {
> + bus_unregister(>race_bustype);
> + return ret;
> + }
> +
> + return 0;
> }
>
> static void __exit gtrace_exit(void)
> {
> + gtrace_perf_exit();
> bus_unregister(>race_bustype);
> }
>
> diff --git a/drivers/hwtracing/gtrace/gtrace-perf.c b/drivers/hwtracing/gtrace/gtrace-perf.c
> new file mode 100644
> index 000000000000..ed9155bbbdb7
> --- /dev/null
> +++ b/drivers/hwtracing/gtrace/gtrace-perf.c
> @@ -0,0 +1,347 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2026 Qualcomm Technologies, Inc.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/cpumask.h>
> +#include <linux/device.h>
> +#include <linux/list.h>
> +#include <linux/mm.h>
> +#include <linux/init.h>
> +#include <linux/perf_event.h>
> +#include <linux/vmalloc.h>
> +#include <linux/percpu-defs.h>
> +#include <linux/slab.h>
> +#include <linux/stringhash.h>
> +#include <linux/types.h>
> +#include <linux/workqueue.h>
> +#include <linux/gtrace.h>
> +
> +#define GTRACE_PMU_NAME "gtrace"
> +static struct pmu gtrace_pmu;
> +static DEFINE_SPINLOCK(perf_buf_lock);
> +
> +/**
> + * struct gtrace_event_data - generic hardware trace perf event data
> + * @work: Handle to free allocated memory outside IRQ context.
> + * @mask: Hold the CPU(s) this event was set for.
> + * @aux_hwid_done: Whether a CPU has emitted the TraceID packet or not.
> + * @path: An array of path, each slot for one CPU.
> + * @buf: Aux buffer / pages allocated by perf framework.
> + */
> +struct gtrace_event_data {
> + struct work_struct work;
> + cpumask_t mask;
> + cpumask_t aux_hwid_done;
> + struct gtrace_path * __percpu *path;
> + struct gtrace_perf_auxbuf buf;
> +};
> +
> +struct gtrace_ctxt {
> + struct perf_output_handle handle;
> + struct gtrace_event_data *event_data;
> +};
> +
> +static DEFINE_PER_CPU(struct gtrace_ctxt, gtrace_ctxt);
> +
> +static void *alloc_event_data(int cpu)
> +{
> + struct gtrace_event_data *event_data;
> + cpumask_t *mask;
> +
> + event_data = kzalloc_obj(*event_data);
> + if (!event_data)
> + return NULL;
> +
> + /* Update mask as per selected CPUs */
> + mask = &event_data->mask;
> + if (cpu != -1)
> + cpumask_set_cpu(cpu, mask);
> + else
> + cpumask_copy(mask, cpu_present_mask);
> +
> + event_data->path = alloc_percpu(struct gtrace_path *);
> + return event_data;
> +}
> +
> +static void gtrace_free_aux(void *data)
> +{
> + struct gtrace_event_data *event_data = data;
> +
> + schedule_work(&event_data->work);
> +}
> +
> +static struct gtrace_path **gtrace_event_cpu_path_ptr(struct gtrace_event_data *data,
> + int cpu)
> +{
> + return per_cpu_ptr(data->path, cpu);
> +}
> +
> +static void free_event_data(struct work_struct *work)
> +{
> + struct gtrace_event_data *event_data;
> + struct gtrace_path *path;
> + cpumask_t *mask;
> + int cpu;
> +
> + event_data = container_of(work, struct gtrace_event_data, work);
> + mask = &event_data->mask;
> + for_each_cpu(cpu, mask) {
> + path = *gtrace_event_cpu_path_ptr(event_data, cpu);
> + gtrace_destroy_path(path);
> + }
> + free_percpu(event_data->path);
> + kfree(event_data);
> +}
> +
> +static void *gtrace_setup_aux(struct perf_event *event, void **pages,
> + int nr_pages, bool overwrite)
> +{
> + struct gtrace_event_data *event_data = NULL;
> + struct page **pagelist;
> + int cpu = event->cpu, i;
> + cpumask_t *mask;
> +
> + event_data = alloc_event_data(cpu);
> + if (!event_data)
> + return NULL;
> +
> + INIT_WORK(&event_data->work, free_event_data);
> + mask = &event_data->mask;
> + /*
> + * Create the path for each CPU in the mask. In case of any failure skip the CPU
> + */
> + for_each_cpu(cpu, mask) {
> + struct gtrace_component *src;
> + struct gtrace_path *path;
> +
> + src = gtrace_cpu_source(cpu);
> + if (!src)
> + continue;
> +
> + path = gtrace_create_path(src, NULL, GTRACE_COMPONENT_MODE_PERF);
> + if (!path)
> + continue;
> +
> + *gtrace_event_cpu_path_ptr(event_data, cpu) = path;
> + }
> +
> + /* If we don't have any CPUs ready for tracing, abort */
> + cpu = cpumask_first(&event_data->mask);
> + if (cpu >= nr_cpu_ids)
> + goto err;
> +
> + pagelist = kcalloc(nr_pages, sizeof(*pagelist), GFP_KERNEL);
> + if (!pagelist)
> + goto err;
> +
> + for (i = 0; i < nr_pages; i++)
> + pagelist[i] = virt_to_page(pages[i]);
> +
> + event_data->buf.base = vmap(pagelist, nr_pages, VM_MAP, PAGE_KERNEL);
> + if (!event_data->buf.base) {
> + kfree(pagelist);
> + goto err;
> + }
> +
> + event_data->buf.nr_pages = nr_pages;
> + event_data->buf.length = nr_pages * PAGE_SIZE;
> + event_data->buf.pos = 0;
> + return event_data;
> +err:
> + gtrace_free_aux(event_data);
> + return NULL;
> +}
> +
> +static void gtrace_event_read(struct perf_event *event)
> +{
> +}
> +
> +static void gtrace_event_destroy(struct perf_event *event)
> +{
> +}
> +
> +static int gtrace_event_init(struct perf_event *event)
> +{
> + if (event->attr.type != gtrace_pmu.type)
> + return -EINVAL;
> +
> + event->destroy = gtrace_event_destroy;
> + return 0;
> +}
> +
> +static void gtrace_event_start(struct perf_event *event, int flags)
> +{
> + struct gtrace_ctxt *ctxt = this_cpu_ptr(>race_ctxt);
> + struct perf_output_handle *handle = &ctxt->handle;
> + struct gtrace_event_data *event_data;
> + int cpu = smp_processor_id();
> + struct gtrace_path *path;
> +
> + if (WARN_ON(ctxt->event_data))
> + goto fail;
> +
> + /*
> + * Deal with the ring buffer API and get a handle on the
> + * session's information.
> + */
> + event_data = perf_aux_output_begin(handle, event);
> + if (!event_data)
> + goto fail;
> +
> + if (!cpumask_test_cpu(cpu, &event_data->mask))
> + goto out;
> +
> + event_data->buf.pos = handle->head % event_data->buf.length;
> + path = *gtrace_event_cpu_path_ptr(event_data, cpu);
> + if (!path) {
> + pr_err("Error. Path not found\n");
> + return;
> + }
> +
> + if (gtrace_path_start(path)) {
> + pr_err("Error. Tracing not started\n");
> + return;
> + }
> +
> + /*
> + * output cpu / trace ID in perf record, once for the lifetime
> + * of the event.
> + */
> + if (!cpumask_test_cpu(cpu, &event_data->aux_hwid_done)) {
> + cpumask_set_cpu(cpu, &event_data->aux_hwid_done);
> + perf_report_aux_output_id(event, cpu);
> + }
> +
> +out:
> + /* Tell the perf core the event is alive */
> + event->hw.state = 0;
> + ctxt->event_data = event_data;
> + return;
> +fail:
> + event->hw.state = PERF_HES_STOPPED;
> +}
> +
> +static void gtrace_event_stop(struct perf_event *event, int mode)
> +{
> + struct gtrace_ctxt *ctxt = this_cpu_ptr(>race_ctxt);
> + struct perf_output_handle *handle = &ctxt->handle;
> + struct gtrace_event_data *event_data;
> + int ret, cpu = smp_processor_id();
> + struct gtrace_path *path;
> + size_t size;
> + u64 format;
> +
> + if (event->hw.state == PERF_HES_STOPPED)
> + return;
> +
> + if (handle->event &&
> + WARN_ON(perf_get_aux(handle) != ctxt->event_data))
> + return;
> +
> + event_data = ctxt->event_data;
> + ctxt->event_data = NULL;
> +
> + if (WARN_ON(!event_data))
> + return;
> +
> + if (handle->event && (mode & PERF_EF_UPDATE) && !cpumask_test_cpu(cpu, &event_data->mask)) {
> + event->hw.state = PERF_HES_STOPPED;
> + perf_aux_output_end(handle, 0);
> + return;
> + }
> +
> + /* stop tracing */
> + path = *gtrace_event_cpu_path_ptr(event_data, cpu);
> + if (!path) {
> + pr_err("Error. Path not found\n");
> + return;
> + }
> +
> + if (gtrace_path_stop(path)) {
> + pr_err("Error. Tracing not stopped\n");
> + return;
> + }
> +
> + event->hw.state = PERF_HES_STOPPED;
> + if (handle->event && (mode & PERF_EF_UPDATE)) {
> + if (WARN_ON_ONCE(handle->event != event))
> + return;
> + spin_lock(&perf_buf_lock);
> + ret = gtrace_path_copyto_auxbuf(path, &event_data->buf, &size, &format);
> + spin_unlock(&perf_buf_lock);
> + WARN_ON_ONCE(ret);
In order to be compatible with Coresight Sink, I would like to change it like:
```
struct gtrace_component *sink = gtrace_path_sink(path);
spin_lock(&perf_buf_lock);
size = sink->copyto_auxbuf(sink, handle, &event_data->buf);
spin_unlock(&perf_buf_lock);
```
> + if (READ_ONCE(handle->event)) {
> + /* Tag the AUX data with the format reported by the sink. */
> + perf_aux_output_flag(handle, format);
> + if (size > handle->size) {
> + size = handle->size;
> + perf_aux_output_flag(handle, PERF_AUX_FLAG_TRUNCATED);
> + }
This part should be moved to sink->copyto_ouxbuf() internally.
> + perf_aux_output_end(handle, size);
> + } else
> + WARN_ON(size);
> + }
> +}
> +
> +static int gtrace_event_add(struct perf_event *event, int mode)
> +{
> + struct hw_perf_event *hwc = &event->hw;
> + int ret = 0;
> +
> + if (mode & PERF_EF_START) {
> + gtrace_event_start(event, 0);
> + if (hwc->state & PERF_HES_STOPPED)
> + ret = -EINVAL;
> + } else {
> + hwc->state = PERF_HES_STOPPED;
> + }
> +
> + return ret;
> +}
> +
> +static void gtrace_event_del(struct perf_event *event, int mode)
> +{
> + gtrace_event_stop(event, PERF_EF_UPDATE);
> +}
> +
> +PMU_FORMAT_ATTR(event, "config:0-0");
> +
> +static struct attribute *gtrace_pmu_formats_attr[] = {
> + &format_attr_event.attr,
> + NULL,
> +};
> +
> +static struct attribute_group gtrace_pmu_format_group = {
> + .name = "format",
> + .attrs = gtrace_pmu_formats_attr,
> +};
> +
> +static const struct attribute_group *gtrace_pmu_attr_groups[] = {
> + >race_pmu_format_group,
> + NULL,
> +};
> +
> +int __init gtrace_perf_init(void)
> +{
> + gtrace_pmu.capabilities = (PERF_PMU_CAP_EXCLUSIVE | PERF_PMU_CAP_ITRACE);
> + gtrace_pmu.attr_groups = gtrace_pmu_attr_groups;
> + gtrace_pmu.task_ctx_nr = perf_sw_context;
> + gtrace_pmu.read = gtrace_event_read;
> + gtrace_pmu.event_init = gtrace_event_init;
> + gtrace_pmu.setup_aux = gtrace_setup_aux;
> + gtrace_pmu.free_aux = gtrace_free_aux;
> + gtrace_pmu.start = gtrace_event_start;
> + gtrace_pmu.stop = gtrace_event_stop;
> + gtrace_pmu.add = gtrace_event_add;
> + gtrace_pmu.del = gtrace_event_del;
> + gtrace_pmu.module = THIS_MODULE;
> +
> + return perf_pmu_register(>race_pmu, GTRACE_PMU_NAME, -1);
> +}
> +
> +void __exit gtrace_perf_exit(void)
> +{
> + perf_pmu_unregister(>race_pmu);
> +}
> diff --git a/include/linux/gtrace.h b/include/linux/gtrace.h
> index bc82814e2738..439e2f091d4d 100644
> --- a/include/linux/gtrace.h
> +++ b/include/linux/gtrace.h
> @@ -317,4 +317,7 @@ static inline void gtrace_unregister_driver(struct gtrace_driver *gtdrv)
> driver_unregister(>drv->driver);
> }
>
> +int gtrace_perf_init(void);
> +void gtrace_perf_exit(void);
> +
> #endif /* __LINUX_GTRACE_H__ */