#include <linux/device.h>
#include <linux/idr.h>
#include <linux/kernel.h>
#include <linux/property.h>
#include "coresight-priv.h"
#include "coresight-trace-id.h"
static DEFINE_IDR(path_idr);
static DEFINE_PER_CPU(struct coresight_path *, 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,
struct coresight_path *path)
{
int ret;
lockdep_assert_held(&coresight_mutex);
if (coresight_get_mode(csdev) != CS_MODE_SYSFS) {
ret = source_ops(csdev)->enable(csdev, NULL, mode, path);
if (ret)
return ret;
}
csdev->refcnt++;
return 0;
}
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;
}
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;
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;
}
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 coresight_path *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;
if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {
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;
}
coresight_path_assign_trace_id(path, CS_MODE_SYSFS);
if (!IS_VALID_CS_TRACE_ID(path->trace_id))
goto err_path;
ret = coresight_enable_path(path, CS_MODE_SYSFS);
if (ret)
goto err_path;
ret = coresight_enable_source_sysfs(csdev, CS_MODE_SYSFS, path);
if (ret)
goto err_source;
switch (subtype) {
case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:
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:
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:
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 coresight_path *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)));
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:
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 ssize_t label_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char *str;
int ret;
ret = fwnode_property_read_string(dev_fwnode(dev), "label", &str);
if (ret == 0)
return sysfs_emit(buf, "%s\n", str);
else
return ret;
}
static DEVICE_ATTR_RO(label);
static umode_t label_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
if (attr == &dev_attr_label.attr) {
if (fwnode_property_present(dev_fwnode(dev), "label"))
return attr->mode;
else
return 0;
}
return attr->mode;
}
static struct attribute *coresight_sink_attrs[] = {
&dev_attr_enable_sink.attr,
&dev_attr_label.attr,
NULL,
};
static struct attribute_group coresight_sink_group = {
.attrs = coresight_sink_attrs,
.is_visible = label_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_sink);
static struct attribute *coresight_source_attrs[] = {
&dev_attr_enable_source.attr,
&dev_attr_label.attr,
NULL,
};
static struct attribute_group coresight_source_group = {
.attrs = coresight_source_attrs,
.is_visible = label_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_source);
static struct attribute *coresight_link_attrs[] = {
&dev_attr_label.attr,
NULL,
};
static struct attribute_group coresight_link_group = {
.attrs = coresight_link_attrs,
.is_visible = label_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_link);
static struct attribute *coresight_helper_attrs[] = {
&dev_attr_label.attr,
NULL,
};
static struct attribute_group coresight_helper_group = {
.attrs = coresight_helper_attrs,
.is_visible = label_is_visible,
};
__ATTRIBUTE_GROUPS(coresight_helper);
const struct device_type coresight_dev_type[] = {
[CORESIGHT_DEV_TYPE_SINK] = {
.name = "sink",
.groups = coresight_sink_groups,
},
[CORESIGHT_DEV_TYPE_LINK] = {
.name = "link",
.groups = coresight_link_groups,
},
[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",
.groups = coresight_helper_groups,
}
};
static_assert(ARRAY_SIZE(coresight_dev_type) == CORESIGHT_DEV_TYPE_MAX);
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",
};
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;
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;
ret = sysfs_add_link_to_group(&info->target->dev.kobj,
coresight_conns_group.name,
&info->orig->dev.kobj,
info->target_name);
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);
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;
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;
}
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;
}