// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019, Linaro Limited, All rights reserved. * Author: Mike Leach */ #include #include #include #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; }