mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-16 02:14:58 +00:00
5ad628a761
This will allow sessions with more than CORESIGHT_TRACE_IDS_MAX ETMs as long as there are fewer than that many ETMs connected to each sink. Each sink owns its own trace ID map, and any Perf session connecting to that sink will allocate from it, even if the sink is currently in use by other users. This is similar to the existing behavior where the dynamic trace IDs are constant as long as there is any concurrent Perf session active. It's not completely optimal because slightly more IDs will be used than necessary, but the optimal solution involves tracking the PIDs of each session and allocating ID maps based on the session owner. This is difficult to do with the combination of per-thread and per-cpu modes and some scheduling issues. The complexity of this isn't likely to worth it because even with multiple users they'd just see a difference in the ordering of ID allocations rather than hitting any limits (unless the hardware does have too many ETMs connected to one sink). Signed-off-by: James Clark <james.clark@arm.com> Reviewed-by: Mike Leach <mike.leach@linaro.org> Signed-off-by: James Clark <james.clark@linaro.org> Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com> Link: https://lore.kernel.org/r/20240722101202.26915-15-james.clark@linaro.org
596 lines
15 KiB
C
596 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019, Linaro Limited, All rights reserved.
|
|
* Author: Mike Leach <mike.leach@linaro.org>
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include "coresight-priv.h"
|
|
#include "coresight-trace-id.h"
|
|
|
|
/*
|
|
* Use IDR to map the hash of the source's device name
|
|
* to the pointer of path for the source. The idr is for
|
|
* the sources which aren't associated with CPU.
|
|
*/
|
|
static DEFINE_IDR(path_idr);
|
|
|
|
/*
|
|
* When operating Coresight drivers from the sysFS interface, only a single
|
|
* path can exist from a tracer (associated to a CPU) to a sink.
|
|
*/
|
|
static DEFINE_PER_CPU(struct list_head *, tracer_path);
|
|
|
|
ssize_t coresight_simple_show_pair(struct device *_dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct coresight_device *csdev = container_of(_dev, struct coresight_device, dev);
|
|
struct cs_pair_attribute *cs_attr = container_of(attr, struct cs_pair_attribute, attr);
|
|
u64 val;
|
|
|
|
pm_runtime_get_sync(_dev->parent);
|
|
val = csdev_access_relaxed_read_pair(&csdev->access, cs_attr->lo_off, cs_attr->hi_off);
|
|
pm_runtime_put_sync(_dev->parent);
|
|
return sysfs_emit(buf, "0x%llx\n", val);
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_simple_show_pair);
|
|
|
|
ssize_t coresight_simple_show32(struct device *_dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct coresight_device *csdev = container_of(_dev, struct coresight_device, dev);
|
|
struct cs_off_attribute *cs_attr = container_of(attr, struct cs_off_attribute, attr);
|
|
u64 val;
|
|
|
|
pm_runtime_get_sync(_dev->parent);
|
|
val = csdev_access_relaxed_read32(&csdev->access, cs_attr->off);
|
|
pm_runtime_put_sync(_dev->parent);
|
|
return sysfs_emit(buf, "0x%llx\n", val);
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_simple_show32);
|
|
|
|
static int coresight_enable_source_sysfs(struct coresight_device *csdev,
|
|
enum cs_mode mode, void *data)
|
|
{
|
|
int ret;
|
|
|
|
/*
|
|
* Comparison with CS_MODE_SYSFS works without taking any device
|
|
* specific spinlock because the truthyness of that comparison can only
|
|
* change with coresight_mutex held, which we already have here.
|
|
*/
|
|
lockdep_assert_held(&coresight_mutex);
|
|
if (coresight_get_mode(csdev) != CS_MODE_SYSFS) {
|
|
ret = source_ops(csdev)->enable(csdev, data, mode, NULL);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
csdev->refcnt++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* coresight_disable_source_sysfs - Drop the reference count by 1 and disable
|
|
* the device if there are no users left.
|
|
*
|
|
* @csdev: The coresight device to disable
|
|
* @data: Opaque data to pass on to the disable function of the source device.
|
|
* For example in perf mode this is a pointer to the struct perf_event.
|
|
*
|
|
* Returns true if the device has been disabled.
|
|
*/
|
|
static bool coresight_disable_source_sysfs(struct coresight_device *csdev,
|
|
void *data)
|
|
{
|
|
lockdep_assert_held(&coresight_mutex);
|
|
if (coresight_get_mode(csdev) != CS_MODE_SYSFS)
|
|
return false;
|
|
|
|
csdev->refcnt--;
|
|
if (csdev->refcnt == 0) {
|
|
coresight_disable_source(csdev, data);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* coresight_find_activated_sysfs_sink - returns the first sink activated via
|
|
* sysfs using connection based search starting from the source reference.
|
|
*
|
|
* @csdev: Coresight source device reference
|
|
*/
|
|
static struct coresight_device *
|
|
coresight_find_activated_sysfs_sink(struct coresight_device *csdev)
|
|
{
|
|
int i;
|
|
struct coresight_device *sink = NULL;
|
|
|
|
if ((csdev->type == CORESIGHT_DEV_TYPE_SINK ||
|
|
csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) &&
|
|
csdev->sysfs_sink_activated)
|
|
return csdev;
|
|
|
|
/*
|
|
* Recursively explore each port found on this element.
|
|
*/
|
|
for (i = 0; i < csdev->pdata->nr_outconns; i++) {
|
|
struct coresight_device *child_dev;
|
|
|
|
child_dev = csdev->pdata->out_conns[i]->dest_dev;
|
|
if (child_dev)
|
|
sink = coresight_find_activated_sysfs_sink(child_dev);
|
|
if (sink)
|
|
return sink;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/** coresight_validate_source - make sure a source has the right credentials to
|
|
* be used via sysfs.
|
|
* @csdev: the device structure for a source.
|
|
* @function: the function this was called from.
|
|
*
|
|
* Assumes the coresight_mutex is held.
|
|
*/
|
|
static int coresight_validate_source_sysfs(struct coresight_device *csdev,
|
|
const char *function)
|
|
{
|
|
u32 type, subtype;
|
|
|
|
type = csdev->type;
|
|
subtype = csdev->subtype.source_subtype;
|
|
|
|
if (type != CORESIGHT_DEV_TYPE_SOURCE) {
|
|
dev_err(&csdev->dev, "wrong device type in %s\n", function);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_PROC &&
|
|
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE &&
|
|
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM &&
|
|
subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS) {
|
|
dev_err(&csdev->dev, "wrong device subtype in %s\n", function);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int coresight_enable_sysfs(struct coresight_device *csdev)
|
|
{
|
|
int cpu, ret = 0;
|
|
struct coresight_device *sink;
|
|
struct list_head *path;
|
|
enum coresight_dev_subtype_source subtype;
|
|
u32 hash;
|
|
|
|
subtype = csdev->subtype.source_subtype;
|
|
|
|
mutex_lock(&coresight_mutex);
|
|
|
|
ret = coresight_validate_source_sysfs(csdev, __func__);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/*
|
|
* mode == SYSFS implies that it's already enabled. Don't look at the
|
|
* refcount to determine this because we don't claim the source until
|
|
* coresight_enable_source() so can still race with Perf mode which
|
|
* doesn't hold coresight_mutex.
|
|
*/
|
|
if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
|
|
/*
|
|
* There could be multiple applications driving the software
|
|
* source. So keep the refcount for each such user when the
|
|
* source is already enabled.
|
|
*/
|
|
if (subtype == CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE)
|
|
csdev->refcnt++;
|
|
goto out;
|
|
}
|
|
|
|
sink = coresight_find_activated_sysfs_sink(csdev);
|
|
if (!sink) {
|
|
ret = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
path = coresight_build_path(csdev, sink);
|
|
if (IS_ERR(path)) {
|
|
pr_err("building path(s) failed\n");
|
|
ret = PTR_ERR(path);
|
|
goto out;
|
|
}
|
|
|
|
ret = coresight_enable_path(path, CS_MODE_SYSFS, NULL);
|
|
if (ret)
|
|
goto err_path;
|
|
|
|
ret = coresight_enable_source_sysfs(csdev, CS_MODE_SYSFS, NULL);
|
|
if (ret)
|
|
goto err_source;
|
|
|
|
switch (subtype) {
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
|
|
/*
|
|
* When working from sysFS it is important to keep track
|
|
* of the paths that were created so that they can be
|
|
* undone in 'coresight_disable()'. Since there can only
|
|
* be a single session per tracer (when working from sysFS)
|
|
* a per-cpu variable will do just fine.
|
|
*/
|
|
cpu = source_ops(csdev)->cpu_id(csdev);
|
|
per_cpu(tracer_path, cpu) = path;
|
|
break;
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM:
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:
|
|
/*
|
|
* Use the hash of source's device name as ID
|
|
* and map the ID to the pointer of the path.
|
|
*/
|
|
hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));
|
|
ret = idr_alloc_u32(&path_idr, path, &hash, hash, GFP_KERNEL);
|
|
if (ret)
|
|
goto err_source;
|
|
break;
|
|
default:
|
|
/* We can't be here */
|
|
break;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&coresight_mutex);
|
|
return ret;
|
|
|
|
err_source:
|
|
coresight_disable_path(path);
|
|
|
|
err_path:
|
|
coresight_release_path(path);
|
|
goto out;
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_enable_sysfs);
|
|
|
|
void coresight_disable_sysfs(struct coresight_device *csdev)
|
|
{
|
|
int cpu, ret;
|
|
struct list_head *path = NULL;
|
|
u32 hash;
|
|
|
|
mutex_lock(&coresight_mutex);
|
|
|
|
ret = coresight_validate_source_sysfs(csdev, __func__);
|
|
if (ret)
|
|
goto out;
|
|
|
|
if (!coresight_disable_source_sysfs(csdev, NULL))
|
|
goto out;
|
|
|
|
switch (csdev->subtype.source_subtype) {
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
|
|
cpu = source_ops(csdev)->cpu_id(csdev);
|
|
path = per_cpu(tracer_path, cpu);
|
|
per_cpu(tracer_path, cpu) = NULL;
|
|
break;
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM:
|
|
case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:
|
|
hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));
|
|
/* Find the path by the hash. */
|
|
path = idr_find(&path_idr, hash);
|
|
if (path == NULL) {
|
|
pr_err("Path is not found for %s\n", dev_name(&csdev->dev));
|
|
goto out;
|
|
}
|
|
idr_remove(&path_idr, hash);
|
|
break;
|
|
default:
|
|
/* We can't be here */
|
|
break;
|
|
}
|
|
|
|
coresight_disable_path(path);
|
|
coresight_release_path(path);
|
|
|
|
out:
|
|
mutex_unlock(&coresight_mutex);
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_disable_sysfs);
|
|
|
|
static ssize_t enable_sink_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct coresight_device *csdev = to_coresight_device(dev);
|
|
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n", csdev->sysfs_sink_activated);
|
|
}
|
|
|
|
static ssize_t enable_sink_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int ret;
|
|
unsigned long val;
|
|
struct coresight_device *csdev = to_coresight_device(dev);
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
csdev->sysfs_sink_activated = !!val;
|
|
|
|
return size;
|
|
|
|
}
|
|
static DEVICE_ATTR_RW(enable_sink);
|
|
|
|
static ssize_t enable_source_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct coresight_device *csdev = to_coresight_device(dev);
|
|
|
|
guard(mutex)(&coresight_mutex);
|
|
return scnprintf(buf, PAGE_SIZE, "%u\n",
|
|
coresight_get_mode(csdev) == CS_MODE_SYSFS);
|
|
}
|
|
|
|
static ssize_t enable_source_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t size)
|
|
{
|
|
int ret = 0;
|
|
unsigned long val;
|
|
struct coresight_device *csdev = to_coresight_device(dev);
|
|
|
|
ret = kstrtoul(buf, 10, &val);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (val) {
|
|
ret = coresight_enable_sysfs(csdev);
|
|
if (ret)
|
|
return ret;
|
|
} else {
|
|
coresight_disable_sysfs(csdev);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
static DEVICE_ATTR_RW(enable_source);
|
|
|
|
static struct attribute *coresight_sink_attrs[] = {
|
|
&dev_attr_enable_sink.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(coresight_sink);
|
|
|
|
static struct attribute *coresight_source_attrs[] = {
|
|
&dev_attr_enable_source.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(coresight_source);
|
|
|
|
const struct device_type coresight_dev_type[] = {
|
|
[CORESIGHT_DEV_TYPE_SINK] = {
|
|
.name = "sink",
|
|
.groups = coresight_sink_groups,
|
|
},
|
|
[CORESIGHT_DEV_TYPE_LINK] = {
|
|
.name = "link",
|
|
},
|
|
[CORESIGHT_DEV_TYPE_LINKSINK] = {
|
|
.name = "linksink",
|
|
.groups = coresight_sink_groups,
|
|
},
|
|
[CORESIGHT_DEV_TYPE_SOURCE] = {
|
|
.name = "source",
|
|
.groups = coresight_source_groups,
|
|
},
|
|
[CORESIGHT_DEV_TYPE_HELPER] = {
|
|
.name = "helper",
|
|
}
|
|
};
|
|
/* Ensure the enum matches the names and groups */
|
|
static_assert(ARRAY_SIZE(coresight_dev_type) == CORESIGHT_DEV_TYPE_MAX);
|
|
|
|
/*
|
|
* Connections group - links attribute.
|
|
* Count of created links between coresight components in the group.
|
|
*/
|
|
static ssize_t nr_links_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct coresight_device *csdev = to_coresight_device(dev);
|
|
|
|
return sprintf(buf, "%d\n", csdev->nr_links);
|
|
}
|
|
static DEVICE_ATTR_RO(nr_links);
|
|
|
|
static struct attribute *coresight_conns_attrs[] = {
|
|
&dev_attr_nr_links.attr,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group coresight_conns_group = {
|
|
.attrs = coresight_conns_attrs,
|
|
.name = "connections",
|
|
};
|
|
|
|
/*
|
|
* Create connections group for CoreSight devices.
|
|
* This group will then be used to collate the sysfs links between
|
|
* devices.
|
|
*/
|
|
int coresight_create_conns_sysfs_group(struct coresight_device *csdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!csdev)
|
|
return -EINVAL;
|
|
|
|
ret = sysfs_create_group(&csdev->dev.kobj, &coresight_conns_group);
|
|
if (ret)
|
|
return ret;
|
|
|
|
csdev->has_conns_grp = true;
|
|
return ret;
|
|
}
|
|
|
|
void coresight_remove_conns_sysfs_group(struct coresight_device *csdev)
|
|
{
|
|
if (!csdev)
|
|
return;
|
|
|
|
if (csdev->has_conns_grp) {
|
|
sysfs_remove_group(&csdev->dev.kobj, &coresight_conns_group);
|
|
csdev->has_conns_grp = false;
|
|
}
|
|
}
|
|
|
|
int coresight_add_sysfs_link(struct coresight_sysfs_link *info)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!info)
|
|
return -EINVAL;
|
|
if (!info->orig || !info->target ||
|
|
!info->orig_name || !info->target_name)
|
|
return -EINVAL;
|
|
if (!info->orig->has_conns_grp || !info->target->has_conns_grp)
|
|
return -EINVAL;
|
|
|
|
/* first link orig->target */
|
|
ret = sysfs_add_link_to_group(&info->orig->dev.kobj,
|
|
coresight_conns_group.name,
|
|
&info->target->dev.kobj,
|
|
info->orig_name);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* second link target->orig */
|
|
ret = sysfs_add_link_to_group(&info->target->dev.kobj,
|
|
coresight_conns_group.name,
|
|
&info->orig->dev.kobj,
|
|
info->target_name);
|
|
|
|
/* error in second link - remove first - otherwise inc counts */
|
|
if (ret) {
|
|
sysfs_remove_link_from_group(&info->orig->dev.kobj,
|
|
coresight_conns_group.name,
|
|
info->orig_name);
|
|
} else {
|
|
info->orig->nr_links++;
|
|
info->target->nr_links++;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_add_sysfs_link);
|
|
|
|
void coresight_remove_sysfs_link(struct coresight_sysfs_link *info)
|
|
{
|
|
if (!info)
|
|
return;
|
|
if (!info->orig || !info->target ||
|
|
!info->orig_name || !info->target_name)
|
|
return;
|
|
|
|
sysfs_remove_link_from_group(&info->orig->dev.kobj,
|
|
coresight_conns_group.name,
|
|
info->orig_name);
|
|
|
|
sysfs_remove_link_from_group(&info->target->dev.kobj,
|
|
coresight_conns_group.name,
|
|
info->target_name);
|
|
|
|
info->orig->nr_links--;
|
|
info->target->nr_links--;
|
|
}
|
|
EXPORT_SYMBOL_GPL(coresight_remove_sysfs_link);
|
|
|
|
/*
|
|
* coresight_make_links: Make a link for a connection from a @orig
|
|
* device to @target, represented by @conn.
|
|
*
|
|
* e.g, for devOrig[output_X] -> devTarget[input_Y] is represented
|
|
* as two symbolic links :
|
|
*
|
|
* /sys/.../devOrig/out:X -> /sys/.../devTarget/
|
|
* /sys/.../devTarget/in:Y -> /sys/.../devOrig/
|
|
*
|
|
* The link names are allocated for a device where it appears. i.e, the
|
|
* "out" link on the master and "in" link on the slave device.
|
|
* The link info is stored in the connection record for avoiding
|
|
* the reconstruction of names for removal.
|
|
*/
|
|
int coresight_make_links(struct coresight_device *orig,
|
|
struct coresight_connection *conn,
|
|
struct coresight_device *target)
|
|
{
|
|
int ret = -ENOMEM;
|
|
char *outs = NULL, *ins = NULL;
|
|
struct coresight_sysfs_link *link = NULL;
|
|
|
|
/* Helper devices aren't shown in sysfs */
|
|
if (conn->dest_port == -1 && conn->src_port == -1)
|
|
return 0;
|
|
|
|
do {
|
|
outs = devm_kasprintf(&orig->dev, GFP_KERNEL,
|
|
"out:%d", conn->src_port);
|
|
if (!outs)
|
|
break;
|
|
ins = devm_kasprintf(&target->dev, GFP_KERNEL,
|
|
"in:%d", conn->dest_port);
|
|
if (!ins)
|
|
break;
|
|
link = devm_kzalloc(&orig->dev,
|
|
sizeof(struct coresight_sysfs_link),
|
|
GFP_KERNEL);
|
|
if (!link)
|
|
break;
|
|
|
|
link->orig = orig;
|
|
link->target = target;
|
|
link->orig_name = outs;
|
|
link->target_name = ins;
|
|
|
|
ret = coresight_add_sysfs_link(link);
|
|
if (ret)
|
|
break;
|
|
|
|
conn->link = link;
|
|
return 0;
|
|
} while (0);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* coresight_remove_links: Remove the sysfs links for a given connection @conn,
|
|
* from @orig device to @target device. See coresight_make_links() for more
|
|
* details.
|
|
*/
|
|
void coresight_remove_links(struct coresight_device *orig,
|
|
struct coresight_connection *conn)
|
|
{
|
|
if (!orig || !conn->link)
|
|
return;
|
|
|
|
coresight_remove_sysfs_link(conn->link);
|
|
|
|
devm_kfree(&conn->dest_dev->dev, conn->link->target_name);
|
|
devm_kfree(&orig->dev, conn->link->orig_name);
|
|
devm_kfree(&orig->dev, conn->link);
|
|
conn->link = NULL;
|
|
}
|