From: Mike Bazov mike@perception-point.io
This patch introduces a new kernel-mode ETM tracers API for the use and control of kernel clients.
Kernel clients are interacting with coresight via 'coresight session'. To create a coresight session, first, you must call coresight_etm_create_session(). A session descriptor is returned. Before going futher and actually using the session to play trace data, a coresight buffer __must__ be allocated to be used by the session.
A coresight buffer is an opaque structure that is maintined internally by the implementation. The user can only control the size of the buffer, but can allocate as many buffers as it want using coresight_etm_alloc_buffer().
To make a session use this buffer, you can set it via coresight_etm_set_buffer(). Finally, to start/stop playing trace data, call coresight_etm_play()/coresight_etm_pause(). This will cause trace data to be generated on the __current__ CPU. This can be called from multiple CPUs concurrently, each call will cause the corrct ETM tracer to play/stop trace data.
Finally, trace data can be harvested by coresight_get_trace(), which returns a pointer that the trace data can be copied from.
To destroy a coresight session, call coresight_destroy_session(). You must free the coresight buffers allocated before destroying a session.
Signed-off-by: Mike Bazov mike@perception-point.io --- drivers/hwtracing/coresight/Makefile | 2 +- drivers/hwtracing/coresight/coresight-etm-api.c | 284 ++++++++++++++++++++++++ drivers/hwtracing/coresight/coresight-etm4x.c | 2 + drivers/hwtracing/coresight/coresight-priv.h | 1 + include/linux/coresight.h | 87 ++++++++ 5 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 drivers/hwtracing/coresight/coresight-etm-api.c
diff --git a/drivers/hwtracing/coresight/Makefile b/drivers/hwtracing/coresight/Makefile index 41870ded51a3..8f0af5e7cabb 100644 --- a/drivers/hwtracing/coresight/Makefile +++ b/drivers/hwtracing/coresight/Makefile @@ -2,7 +2,7 @@ # # Makefile for CoreSight drivers. # -obj-$(CONFIG_CORESIGHT) += coresight.o coresight-etm-perf.o +obj-$(CONFIG_CORESIGHT) += coresight.o coresight-etm-perf.o coresight-etm-api.o obj-$(CONFIG_OF) += of_coresight.o obj-$(CONFIG_CORESIGHT_LINK_AND_SINK_TMC) += coresight-tmc.o \ coresight-tmc-etf.o \ diff --git a/drivers/hwtracing/coresight/coresight-etm-api.c b/drivers/hwtracing/coresight/coresight-etm-api.c new file mode 100644 index 000000000000..a2d877077a20 --- /dev/null +++ b/drivers/hwtracing/coresight/coresight-etm-api.c @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ETM kernel interface driver + * + * Author: Mike Bazov mike@perception-point.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/. + */ + +#include <linux/atomic.h> +#include <linux/coresight.h> +#include <linux/cpumask.h> +#include <linux/slab.h> + +#include "coresight-priv.h" + +/** + * struct coresight_session - Coresight struct describing an API session. + * @path: An array of path, each slot for each CPU. + * @ncpus: Number of cpus used in the array of paths. + */ +struct coresight_session { + struct list_head __percpu **path; + size_t ncpus; +}; + +static DEFINE_PER_CPU(struct coresight_device *, csdev_src); + +void coresight_etm_register(struct coresight_device *csdev) +{ + int cpu = source_ops(csdev)->cpu_id(csdev); + + per_cpu(csdev_src, cpu) = csdev; +} + +void *coresight_etm_alloc_buffer(struct coresight_session *session, + size_t buffer_size) +{ + size_t nr_pages = 0; + struct coresight_device *sink = NULL; + + if (!session) + return ERR_PTR(-EINVAL); + + sink = coresight_get_sink(session->path[0]); + if (!sink) + return ERR_PTR(-ENOENT); + + if (!sink_ops(sink)->alloc_buffer) + return ERR_PTR(-EINVAL); + + nr_pages = PAGE_ALIGN(buffer_size) >> PAGE_SHIFT; + + return sink_ops(sink)->alloc_buffer(sink, CS_MODE_API, 0, + NULL, nr_pages, false); +} +EXPORT_SYMBOL_GPL(coresight_etm_alloc_buffer); + +void coresight_etm_free_buffer(struct coresight_session *session, + void *buffer) +{ + struct coresight_session *cs_session = session; + struct coresight_device *sink = NULL; + + if (!session || !buffer) + return; + + sink = coresight_get_sink(cs_session->path[0]); + if (!sink) + return; + + if (!sink_ops(sink)->free_buffer) + return; + + sink_ops(sink)->free_buffer(CS_MODE_API, buffer); +} +EXPORT_SYMBOL_GPL(coresight_etm_free_buffer); + +int coresight_etm_set_buffer(struct coresight_session *session, + void *buffer) +{ + struct coresight_device *sink = NULL; + + sink = coresight_get_sink(session->path[0]); + if (!sink) + return -ENOENT; + + return sink_ops(sink)->set_buffer(sink, CS_MODE_API, NULL, buffer); +} +EXPORT_SYMBOL_GPL(coresight_etm_set_buffer); + +struct coresight_session *coresight_etm_create_session(void) +{ + int ret = 0, cpu = 0; + void *err = NULL; + struct coresight_session *session = NULL; + struct coresight_device *sink = NULL; + struct list_head **path = NULL; + size_t ncpus = 0; + + /* Currently, the only way of activating a sink is via sysfs, since + * there is no comfortable way of exporting the sink/link types + * to the user, as the devtypes are extremely generic, where the user + * might want to be more specific. Also, don't deactivate the sink, + * the user will disable it as when wishes via sysfs. + */ + sink = coresight_get_enabled_sink(false); + if (!sink) + return ERR_PTR(-ENODEV); + + /* Every coresight path uses the same sink. This implies a + * coresight session uses a single sink for now. + * Mark the session as the sink owner. Prevent other concurrent API + * sessions from using that sink. + */ + if (atomic_cmpxchg(&sink->used, 0, 1)) + return ERR_PTR(-EBUSY); + + session = kzalloc(sizeof(*session), GFP_KERNEL); + if (!session) { + err = ERR_PTR(-ENOMEM); + goto failed; + } + + get_online_cpus(); + + ncpus = num_online_cpus(); + + /* Build a path for each CPU */ + path = kcalloc(ncpus, sizeof(struct list_head *), GFP_KERNEL); + if (!path) { + err = ERR_PTR(-ENOMEM); + goto failed_put_cpus; + } + + for (cpu = 0; cpu < ncpus; ++cpu) { + struct coresight_device *source = NULL; + + source = per_cpu(csdev_src, cpu); + if (!source) { + err = ERR_PTR(-ENODEV); + goto failed_put_cpus; + } + + path[cpu] = coresight_build_path(source, sink); + if (IS_ERR(path[cpu])) { + err = path[cpu]; + path[cpu] = NULL; + goto failed_put_cpus; + } + + ret = coresight_enable_path(path[cpu], CS_MODE_API, NULL); + if (ret) { + coresight_release_path(path[cpu]); + path[cpu] = NULL; + err = ERR_PTR(ret); + goto failed_put_cpus; + } + } + + put_online_cpus(); + + session->ncpus = ncpus; + session->path = path; + + return session; + +failed_put_cpus: + put_online_cpus(); +failed: + if (path) { + for (cpu--; cpu >= 0; cpu--) { + if (path[cpu]) { + coresight_disable_path(path[cpu]); + coresight_release_path(path[cpu]); + } + } + + kfree(path); + } + + kfree(session); + + /* Allow other API sessions to use the sink. */ + atomic_set(&sink->used, 0); + + return err; +} +EXPORT_SYMBOL_GPL(coresight_etm_create_session); + +void coresight_etm_destroy_session(struct coresight_session *session) +{ + int cpu; + struct coresight_device *sink = NULL; + + if (!session) + return; + + sink = coresight_get_sink(session->path[0]); + if (sink) + /* Make sure the trace buffer is NULL. Disabling the sink + * possibly also means syncing the buffer, we don't know + * whether the user freed the used buffer or not, it is his + * responsibilty to sync the buffer when he needs to read trace + * data by calling coresight_etm_get_trace(). + */ + sink_ops(sink)->set_buffer(sink, CS_MODE_API, NULL, NULL); + + for (cpu = 0; cpu < session->ncpus; ++cpu) { + struct coresight_device *source = NULL; + + source = per_cpu(csdev_src, cpu); + if (source) + source_ops(source)->disable(source, NULL); + + coresight_disable_path(session->path[cpu]); + coresight_release_path(session->path[cpu]); + } + + /* Allow other API sessions to use this sink. */ + if (sink) + atomic_set(&sink->used, 0); + + kfree(session->path); + kfree(session); +} +EXPORT_SYMBOL_GPL(coresight_etm_destroy_session); + +int coresight_etm_play(struct coresight_session *session, + struct etm_config *config) +{ + struct coresight_device *source = NULL; + + if (!session) + return -EINVAL; + + source = per_cpu(csdev_src, smp_processor_id()); + if (!source) + return -ENXIO; + + return source_ops(source)->enable(source, config, CS_MODE_API); +} +EXPORT_SYMBOL_GPL(coresight_etm_play); + +void coresight_etm_pause(struct coresight_session *session) +{ + struct coresight_device *source = NULL; + + if (!session) + return; + + source = per_cpu(csdev_src, smp_processor_id()); + if (!source) + return; + + source_ops(source)->disable(source, NULL); +} +EXPORT_SYMBOL_GPL(coresight_etm_pause); + +ssize_t coresight_etm_get_trace(struct coresight_session *session, + loff_t poss, size_t len, char **buf) +{ + struct coresight_device *sink = NULL; + + if (!session) + return -EINVAL; + + sink = coresight_get_sink(session->path[0]); + if (!sink) + return -ENXIO; + + return sink_ops(sink)->get_trace(sink, CS_MODE_API, poss, len, buf); +} +EXPORT_SYMBOL_GPL(coresight_etm_get_trace); diff --git a/drivers/hwtracing/coresight/coresight-etm4x.c b/drivers/hwtracing/coresight/coresight-etm4x.c index f102b16617d0..4237aab60b96 100644 --- a/drivers/hwtracing/coresight/coresight-etm4x.c +++ b/drivers/hwtracing/coresight/coresight-etm4x.c @@ -1082,6 +1082,8 @@ static int etm4_probe(struct amba_device *adev, const struct amba_id *id) goto err_arch_supported; }
+ coresight_etm_register(drvdata->csdev); + pm_runtime_put(&adev->dev); dev_info(dev, "CPU%d: ETM v%d.%d initialized\n", drvdata->cpu, drvdata->arch >> 4, drvdata->arch & 0xf); diff --git a/drivers/hwtracing/coresight/coresight-priv.h b/drivers/hwtracing/coresight/coresight-priv.h index 174e6f1fab3e..803c7242864b 100644 --- a/drivers/hwtracing/coresight/coresight-priv.h +++ b/drivers/hwtracing/coresight/coresight-priv.h @@ -144,6 +144,7 @@ struct coresight_device *coresight_get_enabled_sink(bool reset); struct list_head *coresight_build_path(struct coresight_device *csdev, struct coresight_device *sink); void coresight_release_path(struct list_head *path); +void coresight_etm_register(struct coresight_device *csdev);
#ifdef CONFIG_CORESIGHT_SOURCE_ETM3X extern int etm_readl_cp14(u32 off, unsigned int *val); diff --git a/include/linux/coresight.h b/include/linux/coresight.h index 0a57a2ce8d4d..09b992381645 100644 --- a/include/linux/coresight.h +++ b/include/linux/coresight.h @@ -156,6 +156,7 @@ struct coresight_connection { * @activated: 'true' only if a _sink_ has been activated. A sink can be activated but not yet enabled. Enabling for a _sink_ happens when a source has been selected for that it. + * @used: used only in a _sink_, 1 if the sink is used by an API session. */ struct coresight_device { struct coresight_connection *conns; @@ -169,6 +170,7 @@ struct coresight_device { bool orphan; bool enable; /* true only if configured as part of a path */ bool activated; /* true only if a sink is part of a path */ + atomic_t used; /* 1 if used by a coresight api trace session */ };
#define to_coresight_device(d) container_of(d, struct coresight_device, dev) @@ -256,6 +258,8 @@ struct coresight_ops { const struct coresight_ops_helper *helper_ops; };
+struct coresight_session; + /** * struct etm_config - Generic etm configuration. * @retstack: enable retstack. @@ -282,6 +286,89 @@ extern int coresight_enable(struct coresight_device *csdev); extern void coresight_disable(struct coresight_device *csdev); extern int coresight_timeout(void __iomem *addr, u32 offset, int position, int value); + +/** + * coresight_etm_alloc_buffer - allocate a buffer with fixed size for the + * provided session. + * + * @session: the coresight session + * @buffer_size: the size of the coresight buffer + * + * Returns a coresight buffer object or a negative errno value in case + * of a failure. + */ +extern void *coresight_etm_alloc_buffer( + struct coresight_session *session, + size_t buffer_size); +/** + * coresight_etm_free_buffer: free a coresight buffer + * + * @session: the coresight session. + * @buffer: a coresight buffer previously allocated by + * coresight_etm_alloc_buffer() + */ +extern void coresight_etm_free_buffer(struct coresight_session *session, + void *buffer); + +/** + * coresight_etm_set_buffer - Set a coresight trace buffer. + * + * @session: the coresight session. + * @buffer: a coresight buffer previosuly allocated by + * coresight_etm_alloc_buffer() + * + * Returns 0 on success, <0 on failure. + */ +extern int coresight_etm_set_buffer(struct coresight_session *session, + void *buffer); + +/** + * coresight_etm_create_session - create a coresight session. + * + * Returns a coresight session object or a negative errno value in case of + * a failure. + */ +extern struct coresight_session *coresight_etm_create_session(void); + +/** + * coresight_etm_destroy_session: destroy a coresight session. + * + * @session: the coresight session. + */ +extern void coresight_etm_destroy_session(struct coresight_session *session); + +/** + * coresight_etm_enable: make the source start playing trace data. the source + * is configured according to the config argument that is provided. + * + * @session: the coresight session. + * @config: the etm configuration + * + * Returns 0 on success, <0 on failure. + */ +extern int coresight_etm_play(struct coresight_session *session, + struct etm_config *config); + +/** + * coresight_etm_disable: pause the source from playing trace data. + * + * @session: the coresight session. + */ +extern void coresight_etm_pause(struct coresight_session *session); + +/** + * coresight_etm_get_trace: get the coresight trace from the current buffer. + * + * @session: the coresight session. + * @poss: position to get the trace from. + * @len: maximum length of trace data. + * @buff: a pointer to a pointer that will receive the trace data. + * + * returns the actual length of trace data, or a negative errno value + * in case of a failure. + */ +extern ssize_t coresight_etm_get_trace(struct coresight_session *session, + loff_t poss, size_t len, char **buf); #else static inline struct coresight_device * coresight_register(struct coresight_desc *desc) { return NULL; }