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