596 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2019, Linaro Limited, All rights reserved.4 * Author: Mike Leach <mike.leach@linaro.org>5 */6 7#include <linux/device.h>8#include <linux/idr.h>9#include <linux/kernel.h>10 11#include "coresight-priv.h"12#include "coresight-trace-id.h"13 14/*15 * Use IDR to map the hash of the source's device name16 * to the pointer of path for the source. The idr is for17 * the sources which aren't associated with CPU.18 */19static DEFINE_IDR(path_idr);20 21/*22 * When operating Coresight drivers from the sysFS interface, only a single23 * path can exist from a tracer (associated to a CPU) to a sink.24 */25static DEFINE_PER_CPU(struct list_head *, tracer_path);26 27ssize_t coresight_simple_show_pair(struct device *_dev,28 struct device_attribute *attr, char *buf)29{30 struct coresight_device *csdev = container_of(_dev, struct coresight_device, dev);31 struct cs_pair_attribute *cs_attr = container_of(attr, struct cs_pair_attribute, attr);32 u64 val;33 34 pm_runtime_get_sync(_dev->parent);35 val = csdev_access_relaxed_read_pair(&csdev->access, cs_attr->lo_off, cs_attr->hi_off);36 pm_runtime_put_sync(_dev->parent);37 return sysfs_emit(buf, "0x%llx\n", val);38}39EXPORT_SYMBOL_GPL(coresight_simple_show_pair);40 41ssize_t coresight_simple_show32(struct device *_dev,42 struct device_attribute *attr, char *buf)43{44 struct coresight_device *csdev = container_of(_dev, struct coresight_device, dev);45 struct cs_off_attribute *cs_attr = container_of(attr, struct cs_off_attribute, attr);46 u64 val;47 48 pm_runtime_get_sync(_dev->parent);49 val = csdev_access_relaxed_read32(&csdev->access, cs_attr->off);50 pm_runtime_put_sync(_dev->parent);51 return sysfs_emit(buf, "0x%llx\n", val);52}53EXPORT_SYMBOL_GPL(coresight_simple_show32);54 55static int coresight_enable_source_sysfs(struct coresight_device *csdev,56 enum cs_mode mode, void *data)57{58 int ret;59 60 /*61 * Comparison with CS_MODE_SYSFS works without taking any device62 * specific spinlock because the truthyness of that comparison can only63 * change with coresight_mutex held, which we already have here.64 */65 lockdep_assert_held(&coresight_mutex);66 if (coresight_get_mode(csdev) != CS_MODE_SYSFS) {67 ret = source_ops(csdev)->enable(csdev, data, mode, NULL);68 if (ret)69 return ret;70 }71 72 csdev->refcnt++;73 74 return 0;75}76 77/**78 * coresight_disable_source_sysfs - Drop the reference count by 1 and disable79 * the device if there are no users left.80 *81 * @csdev: The coresight device to disable82 * @data: Opaque data to pass on to the disable function of the source device.83 * For example in perf mode this is a pointer to the struct perf_event.84 *85 * Returns true if the device has been disabled.86 */87static bool coresight_disable_source_sysfs(struct coresight_device *csdev,88 void *data)89{90 lockdep_assert_held(&coresight_mutex);91 if (coresight_get_mode(csdev) != CS_MODE_SYSFS)92 return false;93 94 csdev->refcnt--;95 if (csdev->refcnt == 0) {96 coresight_disable_source(csdev, data);97 return true;98 }99 return false;100}101 102/**103 * coresight_find_activated_sysfs_sink - returns the first sink activated via104 * sysfs using connection based search starting from the source reference.105 *106 * @csdev: Coresight source device reference107 */108static struct coresight_device *109coresight_find_activated_sysfs_sink(struct coresight_device *csdev)110{111 int i;112 struct coresight_device *sink = NULL;113 114 if ((csdev->type == CORESIGHT_DEV_TYPE_SINK ||115 csdev->type == CORESIGHT_DEV_TYPE_LINKSINK) &&116 csdev->sysfs_sink_activated)117 return csdev;118 119 /*120 * Recursively explore each port found on this element.121 */122 for (i = 0; i < csdev->pdata->nr_outconns; i++) {123 struct coresight_device *child_dev;124 125 child_dev = csdev->pdata->out_conns[i]->dest_dev;126 if (child_dev)127 sink = coresight_find_activated_sysfs_sink(child_dev);128 if (sink)129 return sink;130 }131 132 return NULL;133}134 135/** coresight_validate_source - make sure a source has the right credentials to136 * be used via sysfs.137 * @csdev: the device structure for a source.138 * @function: the function this was called from.139 *140 * Assumes the coresight_mutex is held.141 */142static int coresight_validate_source_sysfs(struct coresight_device *csdev,143 const char *function)144{145 u32 type, subtype;146 147 type = csdev->type;148 subtype = csdev->subtype.source_subtype;149 150 if (type != CORESIGHT_DEV_TYPE_SOURCE) {151 dev_err(&csdev->dev, "wrong device type in %s\n", function);152 return -EINVAL;153 }154 155 if (subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_PROC &&156 subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE &&157 subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM &&158 subtype != CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS) {159 dev_err(&csdev->dev, "wrong device subtype in %s\n", function);160 return -EINVAL;161 }162 163 return 0;164}165 166int coresight_enable_sysfs(struct coresight_device *csdev)167{168 int cpu, ret = 0;169 struct coresight_device *sink;170 struct list_head *path;171 enum coresight_dev_subtype_source subtype;172 u32 hash;173 174 subtype = csdev->subtype.source_subtype;175 176 mutex_lock(&coresight_mutex);177 178 ret = coresight_validate_source_sysfs(csdev, __func__);179 if (ret)180 goto out;181 182 /*183 * mode == SYSFS implies that it's already enabled. Don't look at the184 * refcount to determine this because we don't claim the source until185 * coresight_enable_source() so can still race with Perf mode which186 * doesn't hold coresight_mutex.187 */188 if (coresight_get_mode(csdev) == CS_MODE_SYSFS) {189 /*190 * There could be multiple applications driving the software191 * source. So keep the refcount for each such user when the192 * source is already enabled.193 */194 if (subtype == CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE)195 csdev->refcnt++;196 goto out;197 }198 199 sink = coresight_find_activated_sysfs_sink(csdev);200 if (!sink) {201 ret = -EINVAL;202 goto out;203 }204 205 path = coresight_build_path(csdev, sink);206 if (IS_ERR(path)) {207 pr_err("building path(s) failed\n");208 ret = PTR_ERR(path);209 goto out;210 }211 212 ret = coresight_enable_path(path, CS_MODE_SYSFS, NULL);213 if (ret)214 goto err_path;215 216 ret = coresight_enable_source_sysfs(csdev, CS_MODE_SYSFS, NULL);217 if (ret)218 goto err_source;219 220 switch (subtype) {221 case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:222 /*223 * When working from sysFS it is important to keep track224 * of the paths that were created so that they can be225 * undone in 'coresight_disable()'. Since there can only226 * be a single session per tracer (when working from sysFS)227 * a per-cpu variable will do just fine.228 */229 cpu = source_ops(csdev)->cpu_id(csdev);230 per_cpu(tracer_path, cpu) = path;231 break;232 case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:233 case CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM:234 case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:235 /*236 * Use the hash of source's device name as ID237 * and map the ID to the pointer of the path.238 */239 hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));240 ret = idr_alloc_u32(&path_idr, path, &hash, hash, GFP_KERNEL);241 if (ret)242 goto err_source;243 break;244 default:245 /* We can't be here */246 break;247 }248 249out:250 mutex_unlock(&coresight_mutex);251 return ret;252 253err_source:254 coresight_disable_path(path);255 256err_path:257 coresight_release_path(path);258 goto out;259}260EXPORT_SYMBOL_GPL(coresight_enable_sysfs);261 262void coresight_disable_sysfs(struct coresight_device *csdev)263{264 int cpu, ret;265 struct list_head *path = NULL;266 u32 hash;267 268 mutex_lock(&coresight_mutex);269 270 ret = coresight_validate_source_sysfs(csdev, __func__);271 if (ret)272 goto out;273 274 if (!coresight_disable_source_sysfs(csdev, NULL))275 goto out;276 277 switch (csdev->subtype.source_subtype) {278 case CORESIGHT_DEV_SUBTYPE_SOURCE_PROC:279 cpu = source_ops(csdev)->cpu_id(csdev);280 path = per_cpu(tracer_path, cpu);281 per_cpu(tracer_path, cpu) = NULL;282 break;283 case CORESIGHT_DEV_SUBTYPE_SOURCE_SOFTWARE:284 case CORESIGHT_DEV_SUBTYPE_SOURCE_TPDM:285 case CORESIGHT_DEV_SUBTYPE_SOURCE_OTHERS:286 hash = hashlen_hash(hashlen_string(NULL, dev_name(&csdev->dev)));287 /* Find the path by the hash. */288 path = idr_find(&path_idr, hash);289 if (path == NULL) {290 pr_err("Path is not found for %s\n", dev_name(&csdev->dev));291 goto out;292 }293 idr_remove(&path_idr, hash);294 break;295 default:296 /* We can't be here */297 break;298 }299 300 coresight_disable_path(path);301 coresight_release_path(path);302 303out:304 mutex_unlock(&coresight_mutex);305}306EXPORT_SYMBOL_GPL(coresight_disable_sysfs);307 308static ssize_t enable_sink_show(struct device *dev,309 struct device_attribute *attr, char *buf)310{311 struct coresight_device *csdev = to_coresight_device(dev);312 313 return scnprintf(buf, PAGE_SIZE, "%u\n", csdev->sysfs_sink_activated);314}315 316static ssize_t enable_sink_store(struct device *dev,317 struct device_attribute *attr,318 const char *buf, size_t size)319{320 int ret;321 unsigned long val;322 struct coresight_device *csdev = to_coresight_device(dev);323 324 ret = kstrtoul(buf, 10, &val);325 if (ret)326 return ret;327 328 csdev->sysfs_sink_activated = !!val;329 330 return size;331 332}333static DEVICE_ATTR_RW(enable_sink);334 335static ssize_t enable_source_show(struct device *dev,336 struct device_attribute *attr, char *buf)337{338 struct coresight_device *csdev = to_coresight_device(dev);339 340 guard(mutex)(&coresight_mutex);341 return scnprintf(buf, PAGE_SIZE, "%u\n",342 coresight_get_mode(csdev) == CS_MODE_SYSFS);343}344 345static ssize_t enable_source_store(struct device *dev,346 struct device_attribute *attr,347 const char *buf, size_t size)348{349 int ret = 0;350 unsigned long val;351 struct coresight_device *csdev = to_coresight_device(dev);352 353 ret = kstrtoul(buf, 10, &val);354 if (ret)355 return ret;356 357 if (val) {358 ret = coresight_enable_sysfs(csdev);359 if (ret)360 return ret;361 } else {362 coresight_disable_sysfs(csdev);363 }364 365 return size;366}367static DEVICE_ATTR_RW(enable_source);368 369static struct attribute *coresight_sink_attrs[] = {370 &dev_attr_enable_sink.attr,371 NULL,372};373ATTRIBUTE_GROUPS(coresight_sink);374 375static struct attribute *coresight_source_attrs[] = {376 &dev_attr_enable_source.attr,377 NULL,378};379ATTRIBUTE_GROUPS(coresight_source);380 381const struct device_type coresight_dev_type[] = {382 [CORESIGHT_DEV_TYPE_SINK] = {383 .name = "sink",384 .groups = coresight_sink_groups,385 },386 [CORESIGHT_DEV_TYPE_LINK] = {387 .name = "link",388 },389 [CORESIGHT_DEV_TYPE_LINKSINK] = {390 .name = "linksink",391 .groups = coresight_sink_groups,392 },393 [CORESIGHT_DEV_TYPE_SOURCE] = {394 .name = "source",395 .groups = coresight_source_groups,396 },397 [CORESIGHT_DEV_TYPE_HELPER] = {398 .name = "helper",399 }400};401/* Ensure the enum matches the names and groups */402static_assert(ARRAY_SIZE(coresight_dev_type) == CORESIGHT_DEV_TYPE_MAX);403 404/*405 * Connections group - links attribute.406 * Count of created links between coresight components in the group.407 */408static ssize_t nr_links_show(struct device *dev,409 struct device_attribute *attr,410 char *buf)411{412 struct coresight_device *csdev = to_coresight_device(dev);413 414 return sprintf(buf, "%d\n", csdev->nr_links);415}416static DEVICE_ATTR_RO(nr_links);417 418static struct attribute *coresight_conns_attrs[] = {419 &dev_attr_nr_links.attr,420 NULL,421};422 423static struct attribute_group coresight_conns_group = {424 .attrs = coresight_conns_attrs,425 .name = "connections",426};427 428/*429 * Create connections group for CoreSight devices.430 * This group will then be used to collate the sysfs links between431 * devices.432 */433int coresight_create_conns_sysfs_group(struct coresight_device *csdev)434{435 int ret = 0;436 437 if (!csdev)438 return -EINVAL;439 440 ret = sysfs_create_group(&csdev->dev.kobj, &coresight_conns_group);441 if (ret)442 return ret;443 444 csdev->has_conns_grp = true;445 return ret;446}447 448void coresight_remove_conns_sysfs_group(struct coresight_device *csdev)449{450 if (!csdev)451 return;452 453 if (csdev->has_conns_grp) {454 sysfs_remove_group(&csdev->dev.kobj, &coresight_conns_group);455 csdev->has_conns_grp = false;456 }457}458 459int coresight_add_sysfs_link(struct coresight_sysfs_link *info)460{461 int ret = 0;462 463 if (!info)464 return -EINVAL;465 if (!info->orig || !info->target ||466 !info->orig_name || !info->target_name)467 return -EINVAL;468 if (!info->orig->has_conns_grp || !info->target->has_conns_grp)469 return -EINVAL;470 471 /* first link orig->target */472 ret = sysfs_add_link_to_group(&info->orig->dev.kobj,473 coresight_conns_group.name,474 &info->target->dev.kobj,475 info->orig_name);476 if (ret)477 return ret;478 479 /* second link target->orig */480 ret = sysfs_add_link_to_group(&info->target->dev.kobj,481 coresight_conns_group.name,482 &info->orig->dev.kobj,483 info->target_name);484 485 /* error in second link - remove first - otherwise inc counts */486 if (ret) {487 sysfs_remove_link_from_group(&info->orig->dev.kobj,488 coresight_conns_group.name,489 info->orig_name);490 } else {491 info->orig->nr_links++;492 info->target->nr_links++;493 }494 495 return ret;496}497EXPORT_SYMBOL_GPL(coresight_add_sysfs_link);498 499void coresight_remove_sysfs_link(struct coresight_sysfs_link *info)500{501 if (!info)502 return;503 if (!info->orig || !info->target ||504 !info->orig_name || !info->target_name)505 return;506 507 sysfs_remove_link_from_group(&info->orig->dev.kobj,508 coresight_conns_group.name,509 info->orig_name);510 511 sysfs_remove_link_from_group(&info->target->dev.kobj,512 coresight_conns_group.name,513 info->target_name);514 515 info->orig->nr_links--;516 info->target->nr_links--;517}518EXPORT_SYMBOL_GPL(coresight_remove_sysfs_link);519 520/*521 * coresight_make_links: Make a link for a connection from a @orig522 * device to @target, represented by @conn.523 *524 * e.g, for devOrig[output_X] -> devTarget[input_Y] is represented525 * as two symbolic links :526 *527 * /sys/.../devOrig/out:X -> /sys/.../devTarget/528 * /sys/.../devTarget/in:Y -> /sys/.../devOrig/529 *530 * The link names are allocated for a device where it appears. i.e, the531 * "out" link on the master and "in" link on the slave device.532 * The link info is stored in the connection record for avoiding533 * the reconstruction of names for removal.534 */535int coresight_make_links(struct coresight_device *orig,536 struct coresight_connection *conn,537 struct coresight_device *target)538{539 int ret = -ENOMEM;540 char *outs = NULL, *ins = NULL;541 struct coresight_sysfs_link *link = NULL;542 543 /* Helper devices aren't shown in sysfs */544 if (conn->dest_port == -1 && conn->src_port == -1)545 return 0;546 547 do {548 outs = devm_kasprintf(&orig->dev, GFP_KERNEL,549 "out:%d", conn->src_port);550 if (!outs)551 break;552 ins = devm_kasprintf(&target->dev, GFP_KERNEL,553 "in:%d", conn->dest_port);554 if (!ins)555 break;556 link = devm_kzalloc(&orig->dev,557 sizeof(struct coresight_sysfs_link),558 GFP_KERNEL);559 if (!link)560 break;561 562 link->orig = orig;563 link->target = target;564 link->orig_name = outs;565 link->target_name = ins;566 567 ret = coresight_add_sysfs_link(link);568 if (ret)569 break;570 571 conn->link = link;572 return 0;573 } while (0);574 575 return ret;576}577 578/*579 * coresight_remove_links: Remove the sysfs links for a given connection @conn,580 * from @orig device to @target device. See coresight_make_links() for more581 * details.582 */583void coresight_remove_links(struct coresight_device *orig,584 struct coresight_connection *conn)585{586 if (!orig || !conn->link)587 return;588 589 coresight_remove_sysfs_link(conn->link);590 591 devm_kfree(&conn->dest_dev->dev, conn->link->target_name);592 devm_kfree(&orig->dev, conn->link->orig_name);593 devm_kfree(&orig->dev, conn->link);594 conn->link = NULL;595}596