mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-23 08:35:19 -05:00
c95c2733e5
Now that mode is in struct coresight_device accesses can be wrapped. Signed-off-by: James Clark <james.clark@arm.com> Link: https://lore.kernel.org/r/20240129154050.569566-12-james.clark@arm.com Signed-off-by: Suzuki K Poulose <suzuki.poulose@arm.com>
594 lines
15 KiB
C
594 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"
|
|
|
|
/*
|
|
* 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);
|
|
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);
|
|
|
|
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;
|
|
}
|