1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Debugfs interface |
4 | * |
5 | * Copyright (C) 2020, Intel Corporation |
6 | * Authors: Gil Fine <gil.fine@intel.com> |
7 | * Mika Westerberg <mika.westerberg@linux.intel.com> |
8 | */ |
9 | |
10 | #include <linux/debugfs.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/uaccess.h> |
13 | |
14 | #include "tb.h" |
15 | #include "sb_regs.h" |
16 | |
17 | #define PORT_CAP_V1_PCIE_LEN 1 |
18 | #define PORT_CAP_V2_PCIE_LEN 2 |
19 | #define PORT_CAP_POWER_LEN 2 |
20 | #define PORT_CAP_LANE_LEN 3 |
21 | #define PORT_CAP_USB3_LEN 5 |
22 | #define PORT_CAP_DP_V1_LEN 9 |
23 | #define PORT_CAP_DP_V2_LEN 14 |
24 | #define PORT_CAP_TMU_V1_LEN 8 |
25 | #define PORT_CAP_TMU_V2_LEN 10 |
26 | #define PORT_CAP_BASIC_LEN 9 |
27 | #define PORT_CAP_USB4_LEN 20 |
28 | |
29 | #define SWITCH_CAP_TMU_LEN 26 |
30 | #define SWITCH_CAP_BASIC_LEN 27 |
31 | |
32 | #define PATH_LEN 2 |
33 | |
34 | #define COUNTER_SET_LEN 3 |
35 | |
36 | #define DEBUGFS_ATTR(__space, __write) \ |
37 | static int __space ## _open(struct inode *inode, struct file *file) \ |
38 | { \ |
39 | return single_open(file, __space ## _show, inode->i_private); \ |
40 | } \ |
41 | \ |
42 | static const struct file_operations __space ## _fops = { \ |
43 | .owner = THIS_MODULE, \ |
44 | .open = __space ## _open, \ |
45 | .release = single_release, \ |
46 | .read = seq_read, \ |
47 | .write = __write, \ |
48 | .llseek = seq_lseek, \ |
49 | } |
50 | |
51 | #define DEBUGFS_ATTR_RO(__space) \ |
52 | DEBUGFS_ATTR(__space, NULL) |
53 | |
54 | #define DEBUGFS_ATTR_RW(__space) \ |
55 | DEBUGFS_ATTR(__space, __space ## _write) |
56 | |
57 | static struct dentry *tb_debugfs_root; |
58 | |
59 | static void *validate_and_copy_from_user(const void __user *user_buf, |
60 | size_t *count) |
61 | { |
62 | size_t nbytes; |
63 | void *buf; |
64 | |
65 | if (!*count) |
66 | return ERR_PTR(error: -EINVAL); |
67 | |
68 | if (!access_ok(user_buf, *count)) |
69 | return ERR_PTR(error: -EFAULT); |
70 | |
71 | buf = (void *)get_zeroed_page(GFP_KERNEL); |
72 | if (!buf) |
73 | return ERR_PTR(error: -ENOMEM); |
74 | |
75 | nbytes = min_t(size_t, *count, PAGE_SIZE); |
76 | if (copy_from_user(to: buf, from: user_buf, n: nbytes)) { |
77 | free_page((unsigned long)buf); |
78 | return ERR_PTR(error: -EFAULT); |
79 | } |
80 | |
81 | *count = nbytes; |
82 | return buf; |
83 | } |
84 | |
85 | static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len, |
86 | int long_fmt_len) |
87 | { |
88 | char *token; |
89 | u32 v[5]; |
90 | int ret; |
91 | |
92 | token = strsep(line, "\n" ); |
93 | if (!token) |
94 | return false; |
95 | |
96 | /* |
97 | * For Adapter/Router configuration space: |
98 | * Short format is: offset value\n |
99 | * v[0] v[1] |
100 | * Long format as produced from the read side: |
101 | * offset relative_offset cap_id vs_cap_id value\n |
102 | * v[0] v[1] v[2] v[3] v[4] |
103 | * |
104 | * For Counter configuration space: |
105 | * Short format is: offset\n |
106 | * v[0] |
107 | * Long format as produced from the read side: |
108 | * offset relative_offset counter_id value\n |
109 | * v[0] v[1] v[2] v[3] |
110 | */ |
111 | ret = sscanf(token, "%i %i %i %i %i" , &v[0], &v[1], &v[2], &v[3], &v[4]); |
112 | /* In case of Counters, clear counter, "val" content is NA */ |
113 | if (ret == short_fmt_len) { |
114 | *offs = v[0]; |
115 | *val = v[short_fmt_len - 1]; |
116 | return true; |
117 | } else if (ret == long_fmt_len) { |
118 | *offs = v[0]; |
119 | *val = v[long_fmt_len - 1]; |
120 | return true; |
121 | } |
122 | |
123 | return false; |
124 | } |
125 | |
126 | #if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE) |
127 | static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, |
128 | const char __user *user_buf, size_t count, |
129 | loff_t *ppos) |
130 | { |
131 | struct tb *tb = sw->tb; |
132 | char *line, *buf; |
133 | u32 val, offset; |
134 | int ret = 0; |
135 | |
136 | buf = validate_and_copy_from_user(user_buf, count: &count); |
137 | if (IS_ERR(ptr: buf)) |
138 | return PTR_ERR(ptr: buf); |
139 | |
140 | pm_runtime_get_sync(dev: &sw->dev); |
141 | |
142 | if (mutex_lock_interruptible(&tb->lock)) { |
143 | ret = -ERESTARTSYS; |
144 | goto out; |
145 | } |
146 | |
147 | /* User did hardware changes behind the driver's back */ |
148 | add_taint(TAINT_USER, LOCKDEP_STILL_OK); |
149 | |
150 | line = buf; |
151 | while (parse_line(line: &line, offs: &offset, val: &val, short_fmt_len: 2, long_fmt_len: 5)) { |
152 | if (port) |
153 | ret = tb_port_write(port, buffer: &val, space: TB_CFG_PORT, offset, length: 1); |
154 | else |
155 | ret = tb_sw_write(sw, buffer: &val, space: TB_CFG_SWITCH, offset, length: 1); |
156 | if (ret) |
157 | break; |
158 | } |
159 | |
160 | mutex_unlock(lock: &tb->lock); |
161 | |
162 | out: |
163 | pm_runtime_mark_last_busy(dev: &sw->dev); |
164 | pm_runtime_put_autosuspend(dev: &sw->dev); |
165 | free_page((unsigned long)buf); |
166 | |
167 | return ret < 0 ? ret : count; |
168 | } |
169 | |
170 | static ssize_t port_regs_write(struct file *file, const char __user *user_buf, |
171 | size_t count, loff_t *ppos) |
172 | { |
173 | struct seq_file *s = file->private_data; |
174 | struct tb_port *port = s->private; |
175 | |
176 | return regs_write(sw: port->sw, port, user_buf, count, ppos); |
177 | } |
178 | |
179 | static ssize_t switch_regs_write(struct file *file, const char __user *user_buf, |
180 | size_t count, loff_t *ppos) |
181 | { |
182 | struct seq_file *s = file->private_data; |
183 | struct tb_switch *sw = s->private; |
184 | |
185 | return regs_write(sw, NULL, user_buf, count, ppos); |
186 | } |
187 | #define DEBUGFS_MODE 0600 |
188 | #else |
189 | #define port_regs_write NULL |
190 | #define switch_regs_write NULL |
191 | #define DEBUGFS_MODE 0400 |
192 | #endif |
193 | |
194 | #if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING) |
195 | /** |
196 | * struct tb_margining - Lane margining support |
197 | * @caps: Port lane margining capabilities |
198 | * @results: Last lane margining results |
199 | * @lanes: %0, %1 or %7 (all) |
200 | * @min_ber_level: Minimum supported BER level contour value |
201 | * @max_ber_level: Maximum supported BER level contour value |
202 | * @ber_level: Current BER level contour value |
203 | * @voltage_steps: Number of mandatory voltage steps |
204 | * @max_voltage_offset: Maximum mandatory voltage offset (in mV) |
205 | * @time_steps: Number of time margin steps |
206 | * @max_time_offset: Maximum time margin offset (in mUI) |
207 | * @software: %true if software margining is used instead of hardware |
208 | * @time: %true if time margining is used instead of voltage |
209 | * @right_high: %false if left/low margin test is performed, %true if |
210 | * right/high |
211 | */ |
212 | struct tb_margining { |
213 | u32 caps[2]; |
214 | u32 results[2]; |
215 | unsigned int lanes; |
216 | unsigned int min_ber_level; |
217 | unsigned int max_ber_level; |
218 | unsigned int ber_level; |
219 | unsigned int voltage_steps; |
220 | unsigned int max_voltage_offset; |
221 | unsigned int time_steps; |
222 | unsigned int max_time_offset; |
223 | bool software; |
224 | bool time; |
225 | bool right_high; |
226 | }; |
227 | |
228 | static bool supports_software(const struct usb4_port *usb4) |
229 | { |
230 | return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW; |
231 | } |
232 | |
233 | static bool supports_hardware(const struct usb4_port *usb4) |
234 | { |
235 | return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW; |
236 | } |
237 | |
238 | static bool both_lanes(const struct usb4_port *usb4) |
239 | { |
240 | return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES; |
241 | } |
242 | |
243 | static unsigned int independent_voltage_margins(const struct usb4_port *usb4) |
244 | { |
245 | return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >> |
246 | USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT; |
247 | } |
248 | |
249 | static bool supports_time(const struct usb4_port *usb4) |
250 | { |
251 | return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME; |
252 | } |
253 | |
254 | /* Only applicable if supports_time() returns true */ |
255 | static unsigned int independent_time_margins(const struct usb4_port *usb4) |
256 | { |
257 | return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >> |
258 | USB4_MARGIN_CAP_1_TIME_INDP_SHIFT; |
259 | } |
260 | |
261 | static ssize_t |
262 | margining_ber_level_write(struct file *file, const char __user *user_buf, |
263 | size_t count, loff_t *ppos) |
264 | { |
265 | struct seq_file *s = file->private_data; |
266 | struct tb_port *port = s->private; |
267 | struct usb4_port *usb4 = port->usb4; |
268 | struct tb *tb = port->sw->tb; |
269 | unsigned int val; |
270 | int ret = 0; |
271 | char *buf; |
272 | |
273 | if (mutex_lock_interruptible(&tb->lock)) |
274 | return -ERESTARTSYS; |
275 | |
276 | if (usb4->margining->software) { |
277 | ret = -EINVAL; |
278 | goto out_unlock; |
279 | } |
280 | |
281 | buf = validate_and_copy_from_user(user_buf, count: &count); |
282 | if (IS_ERR(ptr: buf)) { |
283 | ret = PTR_ERR(ptr: buf); |
284 | goto out_unlock; |
285 | } |
286 | |
287 | buf[count - 1] = '\0'; |
288 | |
289 | ret = kstrtouint(s: buf, base: 10, res: &val); |
290 | if (ret) |
291 | goto out_free; |
292 | |
293 | if (val < usb4->margining->min_ber_level || |
294 | val > usb4->margining->max_ber_level) { |
295 | ret = -EINVAL; |
296 | goto out_free; |
297 | } |
298 | |
299 | usb4->margining->ber_level = val; |
300 | |
301 | out_free: |
302 | free_page((unsigned long)buf); |
303 | out_unlock: |
304 | mutex_unlock(lock: &tb->lock); |
305 | |
306 | return ret < 0 ? ret : count; |
307 | } |
308 | |
309 | static void ber_level_show(struct seq_file *s, unsigned int val) |
310 | { |
311 | if (val % 2) |
312 | seq_printf(m: s, fmt: "3 * 1e%d (%u)\n" , -12 + (val + 1) / 2, val); |
313 | else |
314 | seq_printf(m: s, fmt: "1e%d (%u)\n" , -12 + val / 2, val); |
315 | } |
316 | |
317 | static int margining_ber_level_show(struct seq_file *s, void *not_used) |
318 | { |
319 | struct tb_port *port = s->private; |
320 | struct usb4_port *usb4 = port->usb4; |
321 | |
322 | if (usb4->margining->software) |
323 | return -EINVAL; |
324 | ber_level_show(s, val: usb4->margining->ber_level); |
325 | return 0; |
326 | } |
327 | DEBUGFS_ATTR_RW(margining_ber_level); |
328 | |
329 | static int margining_caps_show(struct seq_file *s, void *not_used) |
330 | { |
331 | struct tb_port *port = s->private; |
332 | struct usb4_port *usb4 = port->usb4; |
333 | struct tb *tb = port->sw->tb; |
334 | u32 cap0, cap1; |
335 | |
336 | if (mutex_lock_interruptible(&tb->lock)) |
337 | return -ERESTARTSYS; |
338 | |
339 | /* Dump the raw caps first */ |
340 | cap0 = usb4->margining->caps[0]; |
341 | seq_printf(m: s, fmt: "0x%08x\n" , cap0); |
342 | cap1 = usb4->margining->caps[1]; |
343 | seq_printf(m: s, fmt: "0x%08x\n" , cap1); |
344 | |
345 | seq_printf(m: s, fmt: "# software margining: %s\n" , |
346 | supports_software(usb4) ? "yes" : "no" ); |
347 | if (supports_hardware(usb4)) { |
348 | seq_puts(m: s, s: "# hardware margining: yes\n" ); |
349 | seq_puts(m: s, s: "# minimum BER level contour: " ); |
350 | ber_level_show(s, val: usb4->margining->min_ber_level); |
351 | seq_puts(m: s, s: "# maximum BER level contour: " ); |
352 | ber_level_show(s, val: usb4->margining->max_ber_level); |
353 | } else { |
354 | seq_puts(m: s, s: "# hardware margining: no\n" ); |
355 | } |
356 | |
357 | seq_printf(m: s, fmt: "# both lanes simultaneously: %s\n" , |
358 | both_lanes(usb4) ? "yes" : "no" ); |
359 | seq_printf(m: s, fmt: "# voltage margin steps: %u\n" , |
360 | usb4->margining->voltage_steps); |
361 | seq_printf(m: s, fmt: "# maximum voltage offset: %u mV\n" , |
362 | usb4->margining->max_voltage_offset); |
363 | |
364 | switch (independent_voltage_margins(usb4)) { |
365 | case USB4_MARGIN_CAP_0_VOLTAGE_MIN: |
366 | seq_puts(m: s, s: "# returns minimum between high and low voltage margins\n" ); |
367 | break; |
368 | case USB4_MARGIN_CAP_0_VOLTAGE_HL: |
369 | seq_puts(m: s, s: "# returns high or low voltage margin\n" ); |
370 | break; |
371 | case USB4_MARGIN_CAP_0_VOLTAGE_BOTH: |
372 | seq_puts(m: s, s: "# returns both high and low margins\n" ); |
373 | break; |
374 | } |
375 | |
376 | if (supports_time(usb4)) { |
377 | seq_puts(m: s, s: "# time margining: yes\n" ); |
378 | seq_printf(m: s, fmt: "# time margining is destructive: %s\n" , |
379 | cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no" ); |
380 | |
381 | switch (independent_time_margins(usb4)) { |
382 | case USB4_MARGIN_CAP_1_TIME_MIN: |
383 | seq_puts(m: s, s: "# returns minimum between left and right time margins\n" ); |
384 | break; |
385 | case USB4_MARGIN_CAP_1_TIME_LR: |
386 | seq_puts(m: s, s: "# returns left or right margin\n" ); |
387 | break; |
388 | case USB4_MARGIN_CAP_1_TIME_BOTH: |
389 | seq_puts(m: s, s: "# returns both left and right margins\n" ); |
390 | break; |
391 | } |
392 | |
393 | seq_printf(m: s, fmt: "# time margin steps: %u\n" , |
394 | usb4->margining->time_steps); |
395 | seq_printf(m: s, fmt: "# maximum time offset: %u mUI\n" , |
396 | usb4->margining->max_time_offset); |
397 | } else { |
398 | seq_puts(m: s, s: "# time margining: no\n" ); |
399 | } |
400 | |
401 | mutex_unlock(lock: &tb->lock); |
402 | return 0; |
403 | } |
404 | DEBUGFS_ATTR_RO(margining_caps); |
405 | |
406 | static ssize_t |
407 | margining_lanes_write(struct file *file, const char __user *user_buf, |
408 | size_t count, loff_t *ppos) |
409 | { |
410 | struct seq_file *s = file->private_data; |
411 | struct tb_port *port = s->private; |
412 | struct usb4_port *usb4 = port->usb4; |
413 | struct tb *tb = port->sw->tb; |
414 | int ret = 0; |
415 | char *buf; |
416 | |
417 | buf = validate_and_copy_from_user(user_buf, count: &count); |
418 | if (IS_ERR(ptr: buf)) |
419 | return PTR_ERR(ptr: buf); |
420 | |
421 | buf[count - 1] = '\0'; |
422 | |
423 | if (mutex_lock_interruptible(&tb->lock)) { |
424 | ret = -ERESTARTSYS; |
425 | goto out_free; |
426 | } |
427 | |
428 | if (!strcmp(buf, "0" )) { |
429 | usb4->margining->lanes = 0; |
430 | } else if (!strcmp(buf, "1" )) { |
431 | usb4->margining->lanes = 1; |
432 | } else if (!strcmp(buf, "all" )) { |
433 | /* Needs to be supported */ |
434 | if (both_lanes(usb4)) |
435 | usb4->margining->lanes = 7; |
436 | else |
437 | ret = -EINVAL; |
438 | } else { |
439 | ret = -EINVAL; |
440 | } |
441 | |
442 | mutex_unlock(lock: &tb->lock); |
443 | |
444 | out_free: |
445 | free_page((unsigned long)buf); |
446 | return ret < 0 ? ret : count; |
447 | } |
448 | |
449 | static int margining_lanes_show(struct seq_file *s, void *not_used) |
450 | { |
451 | struct tb_port *port = s->private; |
452 | struct usb4_port *usb4 = port->usb4; |
453 | struct tb *tb = port->sw->tb; |
454 | unsigned int lanes; |
455 | |
456 | if (mutex_lock_interruptible(&tb->lock)) |
457 | return -ERESTARTSYS; |
458 | |
459 | lanes = usb4->margining->lanes; |
460 | if (both_lanes(usb4)) { |
461 | if (!lanes) |
462 | seq_puts(m: s, s: "[0] 1 all\n" ); |
463 | else if (lanes == 1) |
464 | seq_puts(m: s, s: "0 [1] all\n" ); |
465 | else |
466 | seq_puts(m: s, s: "0 1 [all]\n" ); |
467 | } else { |
468 | if (!lanes) |
469 | seq_puts(m: s, s: "[0] 1\n" ); |
470 | else |
471 | seq_puts(m: s, s: "0 [1]\n" ); |
472 | } |
473 | |
474 | mutex_unlock(lock: &tb->lock); |
475 | return 0; |
476 | } |
477 | DEBUGFS_ATTR_RW(margining_lanes); |
478 | |
479 | static ssize_t margining_mode_write(struct file *file, |
480 | const char __user *user_buf, |
481 | size_t count, loff_t *ppos) |
482 | { |
483 | struct seq_file *s = file->private_data; |
484 | struct tb_port *port = s->private; |
485 | struct usb4_port *usb4 = port->usb4; |
486 | struct tb *tb = port->sw->tb; |
487 | int ret = 0; |
488 | char *buf; |
489 | |
490 | buf = validate_and_copy_from_user(user_buf, count: &count); |
491 | if (IS_ERR(ptr: buf)) |
492 | return PTR_ERR(ptr: buf); |
493 | |
494 | buf[count - 1] = '\0'; |
495 | |
496 | if (mutex_lock_interruptible(&tb->lock)) { |
497 | ret = -ERESTARTSYS; |
498 | goto out_free; |
499 | } |
500 | |
501 | if (!strcmp(buf, "software" )) { |
502 | if (supports_software(usb4)) |
503 | usb4->margining->software = true; |
504 | else |
505 | ret = -EINVAL; |
506 | } else if (!strcmp(buf, "hardware" )) { |
507 | if (supports_hardware(usb4)) |
508 | usb4->margining->software = false; |
509 | else |
510 | ret = -EINVAL; |
511 | } else { |
512 | ret = -EINVAL; |
513 | } |
514 | |
515 | mutex_unlock(lock: &tb->lock); |
516 | |
517 | out_free: |
518 | free_page((unsigned long)buf); |
519 | return ret ? ret : count; |
520 | } |
521 | |
522 | static int margining_mode_show(struct seq_file *s, void *not_used) |
523 | { |
524 | const struct tb_port *port = s->private; |
525 | const struct usb4_port *usb4 = port->usb4; |
526 | struct tb *tb = port->sw->tb; |
527 | const char *space = "" ; |
528 | |
529 | if (mutex_lock_interruptible(&tb->lock)) |
530 | return -ERESTARTSYS; |
531 | |
532 | if (supports_software(usb4)) { |
533 | if (usb4->margining->software) |
534 | seq_puts(m: s, s: "[software]" ); |
535 | else |
536 | seq_puts(m: s, s: "software" ); |
537 | space = " " ; |
538 | } |
539 | if (supports_hardware(usb4)) { |
540 | if (usb4->margining->software) |
541 | seq_printf(m: s, fmt: "%shardware" , space); |
542 | else |
543 | seq_printf(m: s, fmt: "%s[hardware]" , space); |
544 | } |
545 | |
546 | mutex_unlock(lock: &tb->lock); |
547 | |
548 | seq_puts(m: s, s: "\n" ); |
549 | return 0; |
550 | } |
551 | DEBUGFS_ATTR_RW(margining_mode); |
552 | |
553 | static int margining_run_write(void *data, u64 val) |
554 | { |
555 | struct tb_port *port = data; |
556 | struct usb4_port *usb4 = port->usb4; |
557 | struct tb_switch *sw = port->sw; |
558 | struct tb_margining *margining; |
559 | struct tb_switch *down_sw; |
560 | struct tb *tb = sw->tb; |
561 | int ret, clx; |
562 | |
563 | if (val != 1) |
564 | return -EINVAL; |
565 | |
566 | pm_runtime_get_sync(dev: &sw->dev); |
567 | |
568 | if (mutex_lock_interruptible(&tb->lock)) { |
569 | ret = -ERESTARTSYS; |
570 | goto out_rpm_put; |
571 | } |
572 | |
573 | if (tb_is_upstream_port(port)) |
574 | down_sw = sw; |
575 | else if (port->remote) |
576 | down_sw = port->remote->sw; |
577 | else |
578 | down_sw = NULL; |
579 | |
580 | if (down_sw) { |
581 | /* |
582 | * CL states may interfere with lane margining so |
583 | * disable them temporarily now. |
584 | */ |
585 | ret = tb_switch_clx_disable(sw: down_sw); |
586 | if (ret < 0) { |
587 | tb_sw_warn(down_sw, "failed to disable CL states\n" ); |
588 | goto out_unlock; |
589 | } |
590 | clx = ret; |
591 | } |
592 | |
593 | margining = usb4->margining; |
594 | |
595 | if (margining->software) { |
596 | tb_port_dbg(port, "running software %s lane margining for lanes %u\n" , |
597 | margining->time ? "time" : "voltage" , margining->lanes); |
598 | ret = usb4_port_sw_margin(port, lanes: margining->lanes, timing: margining->time, |
599 | right_high: margining->right_high, |
600 | USB4_MARGIN_SW_COUNTER_CLEAR); |
601 | if (ret) |
602 | goto out_clx; |
603 | |
604 | ret = usb4_port_sw_margin_errors(port, errors: &margining->results[0]); |
605 | } else { |
606 | tb_port_dbg(port, "running hardware %s lane margining for lanes %u\n" , |
607 | margining->time ? "time" : "voltage" , margining->lanes); |
608 | /* Clear the results */ |
609 | margining->results[0] = 0; |
610 | margining->results[1] = 0; |
611 | ret = usb4_port_hw_margin(port, lanes: margining->lanes, |
612 | ber_level: margining->ber_level, timing: margining->time, |
613 | right_high: margining->right_high, results: margining->results); |
614 | } |
615 | |
616 | out_clx: |
617 | if (down_sw) |
618 | tb_switch_clx_enable(sw: down_sw, clx); |
619 | out_unlock: |
620 | mutex_unlock(lock: &tb->lock); |
621 | out_rpm_put: |
622 | pm_runtime_mark_last_busy(dev: &sw->dev); |
623 | pm_runtime_put_autosuspend(dev: &sw->dev); |
624 | |
625 | return ret; |
626 | } |
627 | DEFINE_DEBUGFS_ATTRIBUTE(margining_run_fops, NULL, margining_run_write, |
628 | "%llu\n" ); |
629 | |
630 | static ssize_t margining_results_write(struct file *file, |
631 | const char __user *user_buf, |
632 | size_t count, loff_t *ppos) |
633 | { |
634 | struct seq_file *s = file->private_data; |
635 | struct tb_port *port = s->private; |
636 | struct usb4_port *usb4 = port->usb4; |
637 | struct tb *tb = port->sw->tb; |
638 | |
639 | if (mutex_lock_interruptible(&tb->lock)) |
640 | return -ERESTARTSYS; |
641 | |
642 | /* Just clear the results */ |
643 | usb4->margining->results[0] = 0; |
644 | usb4->margining->results[1] = 0; |
645 | |
646 | mutex_unlock(lock: &tb->lock); |
647 | return count; |
648 | } |
649 | |
650 | static void voltage_margin_show(struct seq_file *s, |
651 | const struct tb_margining *margining, u8 val) |
652 | { |
653 | unsigned int tmp, voltage; |
654 | |
655 | tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK; |
656 | voltage = tmp * margining->max_voltage_offset / margining->voltage_steps; |
657 | seq_printf(m: s, fmt: "%u mV (%u)" , voltage, tmp); |
658 | if (val & USB4_MARGIN_HW_RES_1_EXCEEDS) |
659 | seq_puts(m: s, s: " exceeds maximum" ); |
660 | seq_puts(m: s, s: "\n" ); |
661 | } |
662 | |
663 | static void time_margin_show(struct seq_file *s, |
664 | const struct tb_margining *margining, u8 val) |
665 | { |
666 | unsigned int tmp, interval; |
667 | |
668 | tmp = val & USB4_MARGIN_HW_RES_1_MARGIN_MASK; |
669 | interval = tmp * margining->max_time_offset / margining->time_steps; |
670 | seq_printf(m: s, fmt: "%u mUI (%u)" , interval, tmp); |
671 | if (val & USB4_MARGIN_HW_RES_1_EXCEEDS) |
672 | seq_puts(m: s, s: " exceeds maximum" ); |
673 | seq_puts(m: s, s: "\n" ); |
674 | } |
675 | |
676 | static int margining_results_show(struct seq_file *s, void *not_used) |
677 | { |
678 | struct tb_port *port = s->private; |
679 | struct usb4_port *usb4 = port->usb4; |
680 | struct tb_margining *margining; |
681 | struct tb *tb = port->sw->tb; |
682 | |
683 | if (mutex_lock_interruptible(&tb->lock)) |
684 | return -ERESTARTSYS; |
685 | |
686 | margining = usb4->margining; |
687 | /* Dump the raw results first */ |
688 | seq_printf(m: s, fmt: "0x%08x\n" , margining->results[0]); |
689 | /* Only the hardware margining has two result dwords */ |
690 | if (!margining->software) { |
691 | unsigned int val; |
692 | |
693 | seq_printf(m: s, fmt: "0x%08x\n" , margining->results[1]); |
694 | |
695 | if (margining->time) { |
696 | if (!margining->lanes || margining->lanes == 7) { |
697 | val = margining->results[1]; |
698 | seq_puts(m: s, s: "# lane 0 right time margin: " ); |
699 | time_margin_show(s, margining, val); |
700 | val = margining->results[1] >> |
701 | USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT; |
702 | seq_puts(m: s, s: "# lane 0 left time margin: " ); |
703 | time_margin_show(s, margining, val); |
704 | } |
705 | if (margining->lanes == 1 || margining->lanes == 7) { |
706 | val = margining->results[1] >> |
707 | USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT; |
708 | seq_puts(m: s, s: "# lane 1 right time margin: " ); |
709 | time_margin_show(s, margining, val); |
710 | val = margining->results[1] >> |
711 | USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT; |
712 | seq_puts(m: s, s: "# lane 1 left time margin: " ); |
713 | time_margin_show(s, margining, val); |
714 | } |
715 | } else { |
716 | if (!margining->lanes || margining->lanes == 7) { |
717 | val = margining->results[1]; |
718 | seq_puts(m: s, s: "# lane 0 high voltage margin: " ); |
719 | voltage_margin_show(s, margining, val); |
720 | val = margining->results[1] >> |
721 | USB4_MARGIN_HW_RES_1_L0_LL_MARGIN_SHIFT; |
722 | seq_puts(m: s, s: "# lane 0 low voltage margin: " ); |
723 | voltage_margin_show(s, margining, val); |
724 | } |
725 | if (margining->lanes == 1 || margining->lanes == 7) { |
726 | val = margining->results[1] >> |
727 | USB4_MARGIN_HW_RES_1_L1_RH_MARGIN_SHIFT; |
728 | seq_puts(m: s, s: "# lane 1 high voltage margin: " ); |
729 | voltage_margin_show(s, margining, val); |
730 | val = margining->results[1] >> |
731 | USB4_MARGIN_HW_RES_1_L1_LL_MARGIN_SHIFT; |
732 | seq_puts(m: s, s: "# lane 1 low voltage margin: " ); |
733 | voltage_margin_show(s, margining, val); |
734 | } |
735 | } |
736 | } |
737 | |
738 | mutex_unlock(lock: &tb->lock); |
739 | return 0; |
740 | } |
741 | DEBUGFS_ATTR_RW(margining_results); |
742 | |
743 | static ssize_t margining_test_write(struct file *file, |
744 | const char __user *user_buf, |
745 | size_t count, loff_t *ppos) |
746 | { |
747 | struct seq_file *s = file->private_data; |
748 | struct tb_port *port = s->private; |
749 | struct usb4_port *usb4 = port->usb4; |
750 | struct tb *tb = port->sw->tb; |
751 | int ret = 0; |
752 | char *buf; |
753 | |
754 | buf = validate_and_copy_from_user(user_buf, count: &count); |
755 | if (IS_ERR(ptr: buf)) |
756 | return PTR_ERR(ptr: buf); |
757 | |
758 | buf[count - 1] = '\0'; |
759 | |
760 | if (mutex_lock_interruptible(&tb->lock)) { |
761 | ret = -ERESTARTSYS; |
762 | goto out_free; |
763 | } |
764 | |
765 | if (!strcmp(buf, "time" ) && supports_time(usb4)) |
766 | usb4->margining->time = true; |
767 | else if (!strcmp(buf, "voltage" )) |
768 | usb4->margining->time = false; |
769 | else |
770 | ret = -EINVAL; |
771 | |
772 | mutex_unlock(lock: &tb->lock); |
773 | |
774 | out_free: |
775 | free_page((unsigned long)buf); |
776 | return ret ? ret : count; |
777 | } |
778 | |
779 | static int margining_test_show(struct seq_file *s, void *not_used) |
780 | { |
781 | struct tb_port *port = s->private; |
782 | struct usb4_port *usb4 = port->usb4; |
783 | struct tb *tb = port->sw->tb; |
784 | |
785 | if (mutex_lock_interruptible(&tb->lock)) |
786 | return -ERESTARTSYS; |
787 | |
788 | if (supports_time(usb4)) { |
789 | if (usb4->margining->time) |
790 | seq_puts(m: s, s: "voltage [time]\n" ); |
791 | else |
792 | seq_puts(m: s, s: "[voltage] time\n" ); |
793 | } else { |
794 | seq_puts(m: s, s: "[voltage]\n" ); |
795 | } |
796 | |
797 | mutex_unlock(lock: &tb->lock); |
798 | return 0; |
799 | } |
800 | DEBUGFS_ATTR_RW(margining_test); |
801 | |
802 | static ssize_t margining_margin_write(struct file *file, |
803 | const char __user *user_buf, |
804 | size_t count, loff_t *ppos) |
805 | { |
806 | struct seq_file *s = file->private_data; |
807 | struct tb_port *port = s->private; |
808 | struct usb4_port *usb4 = port->usb4; |
809 | struct tb *tb = port->sw->tb; |
810 | int ret = 0; |
811 | char *buf; |
812 | |
813 | buf = validate_and_copy_from_user(user_buf, count: &count); |
814 | if (IS_ERR(ptr: buf)) |
815 | return PTR_ERR(ptr: buf); |
816 | |
817 | buf[count - 1] = '\0'; |
818 | |
819 | if (mutex_lock_interruptible(&tb->lock)) { |
820 | ret = -ERESTARTSYS; |
821 | goto out_free; |
822 | } |
823 | |
824 | if (usb4->margining->time) { |
825 | if (!strcmp(buf, "left" )) |
826 | usb4->margining->right_high = false; |
827 | else if (!strcmp(buf, "right" )) |
828 | usb4->margining->right_high = true; |
829 | else |
830 | ret = -EINVAL; |
831 | } else { |
832 | if (!strcmp(buf, "low" )) |
833 | usb4->margining->right_high = false; |
834 | else if (!strcmp(buf, "high" )) |
835 | usb4->margining->right_high = true; |
836 | else |
837 | ret = -EINVAL; |
838 | } |
839 | |
840 | mutex_unlock(lock: &tb->lock); |
841 | |
842 | out_free: |
843 | free_page((unsigned long)buf); |
844 | return ret ? ret : count; |
845 | } |
846 | |
847 | static int margining_margin_show(struct seq_file *s, void *not_used) |
848 | { |
849 | struct tb_port *port = s->private; |
850 | struct usb4_port *usb4 = port->usb4; |
851 | struct tb *tb = port->sw->tb; |
852 | |
853 | if (mutex_lock_interruptible(&tb->lock)) |
854 | return -ERESTARTSYS; |
855 | |
856 | if (usb4->margining->time) { |
857 | if (usb4->margining->right_high) |
858 | seq_puts(m: s, s: "left [right]\n" ); |
859 | else |
860 | seq_puts(m: s, s: "[left] right\n" ); |
861 | } else { |
862 | if (usb4->margining->right_high) |
863 | seq_puts(m: s, s: "low [high]\n" ); |
864 | else |
865 | seq_puts(m: s, s: "[low] high\n" ); |
866 | } |
867 | |
868 | mutex_unlock(lock: &tb->lock); |
869 | return 0; |
870 | } |
871 | DEBUGFS_ATTR_RW(margining_margin); |
872 | |
873 | static void margining_port_init(struct tb_port *port) |
874 | { |
875 | struct tb_margining *margining; |
876 | struct dentry *dir, *parent; |
877 | struct usb4_port *usb4; |
878 | char dir_name[10]; |
879 | unsigned int val; |
880 | int ret; |
881 | |
882 | usb4 = port->usb4; |
883 | if (!usb4) |
884 | return; |
885 | |
886 | snprintf(buf: dir_name, size: sizeof(dir_name), fmt: "port%d" , port->port); |
887 | parent = debugfs_lookup(name: dir_name, parent: port->sw->debugfs_dir); |
888 | |
889 | margining = kzalloc(size: sizeof(*margining), GFP_KERNEL); |
890 | if (!margining) |
891 | return; |
892 | |
893 | ret = usb4_port_margining_caps(port, caps: margining->caps); |
894 | if (ret) { |
895 | kfree(objp: margining); |
896 | return; |
897 | } |
898 | |
899 | usb4->margining = margining; |
900 | |
901 | /* Set the initial mode */ |
902 | if (supports_software(usb4)) |
903 | margining->software = true; |
904 | |
905 | val = (margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_STEPS_MASK) >> |
906 | USB4_MARGIN_CAP_0_VOLTAGE_STEPS_SHIFT; |
907 | margining->voltage_steps = val; |
908 | val = (margining->caps[0] & USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_MASK) >> |
909 | USB4_MARGIN_CAP_0_MAX_VOLTAGE_OFFSET_SHIFT; |
910 | margining->max_voltage_offset = 74 + val * 2; |
911 | |
912 | if (supports_time(usb4)) { |
913 | val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_STEPS_MASK) >> |
914 | USB4_MARGIN_CAP_1_TIME_STEPS_SHIFT; |
915 | margining->time_steps = val; |
916 | val = (margining->caps[1] & USB4_MARGIN_CAP_1_TIME_OFFSET_MASK) >> |
917 | USB4_MARGIN_CAP_1_TIME_OFFSET_SHIFT; |
918 | /* |
919 | * Store it as mUI (milli Unit Interval) because we want |
920 | * to keep it as integer. |
921 | */ |
922 | margining->max_time_offset = 200 + 10 * val; |
923 | } |
924 | |
925 | dir = debugfs_create_dir(name: "margining" , parent); |
926 | if (supports_hardware(usb4)) { |
927 | val = (margining->caps[1] & USB4_MARGIN_CAP_1_MIN_BER_MASK) >> |
928 | USB4_MARGIN_CAP_1_MIN_BER_SHIFT; |
929 | margining->min_ber_level = val; |
930 | val = (margining->caps[1] & USB4_MARGIN_CAP_1_MAX_BER_MASK) >> |
931 | USB4_MARGIN_CAP_1_MAX_BER_SHIFT; |
932 | margining->max_ber_level = val; |
933 | |
934 | /* Set the default to minimum */ |
935 | margining->ber_level = margining->min_ber_level; |
936 | |
937 | debugfs_create_file(name: "ber_level_contour" , mode: 0400, parent: dir, data: port, |
938 | fops: &margining_ber_level_fops); |
939 | } |
940 | debugfs_create_file(name: "caps" , mode: 0400, parent: dir, data: port, fops: &margining_caps_fops); |
941 | debugfs_create_file(name: "lanes" , mode: 0600, parent: dir, data: port, fops: &margining_lanes_fops); |
942 | debugfs_create_file(name: "mode" , mode: 0600, parent: dir, data: port, fops: &margining_mode_fops); |
943 | debugfs_create_file(name: "run" , mode: 0600, parent: dir, data: port, fops: &margining_run_fops); |
944 | debugfs_create_file(name: "results" , mode: 0600, parent: dir, data: port, fops: &margining_results_fops); |
945 | debugfs_create_file(name: "test" , mode: 0600, parent: dir, data: port, fops: &margining_test_fops); |
946 | if (independent_voltage_margins(usb4) || |
947 | (supports_time(usb4) && independent_time_margins(usb4))) |
948 | debugfs_create_file(name: "margin" , mode: 0600, parent: dir, data: port, fops: &margining_margin_fops); |
949 | } |
950 | |
951 | static void margining_port_remove(struct tb_port *port) |
952 | { |
953 | struct dentry *parent; |
954 | char dir_name[10]; |
955 | |
956 | if (!port->usb4) |
957 | return; |
958 | |
959 | snprintf(buf: dir_name, size: sizeof(dir_name), fmt: "port%d" , port->port); |
960 | parent = debugfs_lookup(name: dir_name, parent: port->sw->debugfs_dir); |
961 | if (parent) |
962 | debugfs_lookup_and_remove(name: "margining" , parent); |
963 | |
964 | kfree(objp: port->usb4->margining); |
965 | port->usb4->margining = NULL; |
966 | } |
967 | |
968 | static void margining_switch_init(struct tb_switch *sw) |
969 | { |
970 | struct tb_port *upstream, *downstream; |
971 | struct tb_switch *parent_sw; |
972 | u64 route = tb_route(sw); |
973 | |
974 | if (!route) |
975 | return; |
976 | |
977 | upstream = tb_upstream_port(sw); |
978 | parent_sw = tb_switch_parent(sw); |
979 | downstream = tb_port_at(route, sw: parent_sw); |
980 | |
981 | margining_port_init(port: downstream); |
982 | margining_port_init(port: upstream); |
983 | } |
984 | |
985 | static void margining_switch_remove(struct tb_switch *sw) |
986 | { |
987 | struct tb_port *upstream, *downstream; |
988 | struct tb_switch *parent_sw; |
989 | u64 route = tb_route(sw); |
990 | |
991 | if (!route) |
992 | return; |
993 | |
994 | upstream = tb_upstream_port(sw); |
995 | parent_sw = tb_switch_parent(sw); |
996 | downstream = tb_port_at(route, sw: parent_sw); |
997 | |
998 | margining_port_remove(port: upstream); |
999 | margining_port_remove(port: downstream); |
1000 | } |
1001 | |
1002 | static void margining_xdomain_init(struct tb_xdomain *xd) |
1003 | { |
1004 | struct tb_switch *parent_sw; |
1005 | struct tb_port *downstream; |
1006 | |
1007 | parent_sw = tb_xdomain_parent(xd); |
1008 | downstream = tb_port_at(route: xd->route, sw: parent_sw); |
1009 | |
1010 | margining_port_init(port: downstream); |
1011 | } |
1012 | |
1013 | static void margining_xdomain_remove(struct tb_xdomain *xd) |
1014 | { |
1015 | struct tb_switch *parent_sw; |
1016 | struct tb_port *downstream; |
1017 | |
1018 | parent_sw = tb_xdomain_parent(xd); |
1019 | downstream = tb_port_at(route: xd->route, sw: parent_sw); |
1020 | margining_port_remove(port: downstream); |
1021 | } |
1022 | #else |
1023 | static inline void margining_switch_init(struct tb_switch *sw) { } |
1024 | static inline void margining_switch_remove(struct tb_switch *sw) { } |
1025 | static inline void margining_xdomain_init(struct tb_xdomain *xd) { } |
1026 | static inline void margining_xdomain_remove(struct tb_xdomain *xd) { } |
1027 | #endif |
1028 | |
1029 | static int port_clear_all_counters(struct tb_port *port) |
1030 | { |
1031 | u32 *buf; |
1032 | int ret; |
1033 | |
1034 | buf = kcalloc(COUNTER_SET_LEN * port->config.max_counters, size: sizeof(u32), |
1035 | GFP_KERNEL); |
1036 | if (!buf) |
1037 | return -ENOMEM; |
1038 | |
1039 | ret = tb_port_write(port, buffer: buf, space: TB_CFG_COUNTERS, offset: 0, |
1040 | COUNTER_SET_LEN * port->config.max_counters); |
1041 | kfree(objp: buf); |
1042 | |
1043 | return ret; |
1044 | } |
1045 | |
1046 | static ssize_t counters_write(struct file *file, const char __user *user_buf, |
1047 | size_t count, loff_t *ppos) |
1048 | { |
1049 | struct seq_file *s = file->private_data; |
1050 | struct tb_port *port = s->private; |
1051 | struct tb_switch *sw = port->sw; |
1052 | struct tb *tb = port->sw->tb; |
1053 | char *buf; |
1054 | int ret; |
1055 | |
1056 | buf = validate_and_copy_from_user(user_buf, count: &count); |
1057 | if (IS_ERR(ptr: buf)) |
1058 | return PTR_ERR(ptr: buf); |
1059 | |
1060 | pm_runtime_get_sync(dev: &sw->dev); |
1061 | |
1062 | if (mutex_lock_interruptible(&tb->lock)) { |
1063 | ret = -ERESTARTSYS; |
1064 | goto out; |
1065 | } |
1066 | |
1067 | /* If written delimiter only, clear all counters in one shot */ |
1068 | if (buf[0] == '\n') { |
1069 | ret = port_clear_all_counters(port); |
1070 | } else { |
1071 | char *line = buf; |
1072 | u32 val, offset; |
1073 | |
1074 | ret = -EINVAL; |
1075 | while (parse_line(line: &line, offs: &offset, val: &val, short_fmt_len: 1, long_fmt_len: 4)) { |
1076 | ret = tb_port_write(port, buffer: &val, space: TB_CFG_COUNTERS, |
1077 | offset, length: 1); |
1078 | if (ret) |
1079 | break; |
1080 | } |
1081 | } |
1082 | |
1083 | mutex_unlock(lock: &tb->lock); |
1084 | |
1085 | out: |
1086 | pm_runtime_mark_last_busy(dev: &sw->dev); |
1087 | pm_runtime_put_autosuspend(dev: &sw->dev); |
1088 | free_page((unsigned long)buf); |
1089 | |
1090 | return ret < 0 ? ret : count; |
1091 | } |
1092 | |
1093 | static void cap_show_by_dw(struct seq_file *s, struct tb_switch *sw, |
1094 | struct tb_port *port, unsigned int cap, |
1095 | unsigned int offset, u8 cap_id, u8 vsec_id, |
1096 | int dwords) |
1097 | { |
1098 | int i, ret; |
1099 | u32 data; |
1100 | |
1101 | for (i = 0; i < dwords; i++) { |
1102 | if (port) |
1103 | ret = tb_port_read(port, buffer: &data, space: TB_CFG_PORT, offset: cap + offset + i, length: 1); |
1104 | else |
1105 | ret = tb_sw_read(sw, buffer: &data, space: TB_CFG_SWITCH, offset: cap + offset + i, length: 1); |
1106 | if (ret) { |
1107 | seq_printf(m: s, fmt: "0x%04x <not accessible>\n" , cap + offset + i); |
1108 | continue; |
1109 | } |
1110 | |
1111 | seq_printf(m: s, fmt: "0x%04x %4d 0x%02x 0x%02x 0x%08x\n" , cap + offset + i, |
1112 | offset + i, cap_id, vsec_id, data); |
1113 | } |
1114 | } |
1115 | |
1116 | static void cap_show(struct seq_file *s, struct tb_switch *sw, |
1117 | struct tb_port *port, unsigned int cap, u8 cap_id, |
1118 | u8 vsec_id, int length) |
1119 | { |
1120 | int ret, offset = 0; |
1121 | |
1122 | while (length > 0) { |
1123 | int i, dwords = min(length, TB_MAX_CONFIG_RW_LENGTH); |
1124 | u32 data[TB_MAX_CONFIG_RW_LENGTH]; |
1125 | |
1126 | if (port) |
1127 | ret = tb_port_read(port, buffer: data, space: TB_CFG_PORT, offset: cap + offset, |
1128 | length: dwords); |
1129 | else |
1130 | ret = tb_sw_read(sw, buffer: data, space: TB_CFG_SWITCH, offset: cap + offset, length: dwords); |
1131 | if (ret) { |
1132 | cap_show_by_dw(s, sw, port, cap, offset, cap_id, vsec_id, dwords: length); |
1133 | return; |
1134 | } |
1135 | |
1136 | for (i = 0; i < dwords; i++) { |
1137 | seq_printf(m: s, fmt: "0x%04x %4d 0x%02x 0x%02x 0x%08x\n" , |
1138 | cap + offset + i, offset + i, |
1139 | cap_id, vsec_id, data[i]); |
1140 | } |
1141 | |
1142 | length -= dwords; |
1143 | offset += dwords; |
1144 | } |
1145 | } |
1146 | |
1147 | static void port_cap_show(struct tb_port *port, struct seq_file *s, |
1148 | unsigned int cap) |
1149 | { |
1150 | struct tb_cap_any ; |
1151 | u8 vsec_id = 0; |
1152 | size_t length; |
1153 | int ret; |
1154 | |
1155 | ret = tb_port_read(port, buffer: &header, space: TB_CFG_PORT, offset: cap, length: 1); |
1156 | if (ret) { |
1157 | seq_printf(m: s, fmt: "0x%04x <capability read failed>\n" , cap); |
1158 | return; |
1159 | } |
1160 | |
1161 | switch (header.basic.cap) { |
1162 | case TB_PORT_CAP_PHY: |
1163 | length = PORT_CAP_LANE_LEN; |
1164 | break; |
1165 | |
1166 | case TB_PORT_CAP_TIME1: |
1167 | if (usb4_switch_version(sw: port->sw) < 2) |
1168 | length = PORT_CAP_TMU_V1_LEN; |
1169 | else |
1170 | length = PORT_CAP_TMU_V2_LEN; |
1171 | break; |
1172 | |
1173 | case TB_PORT_CAP_POWER: |
1174 | length = PORT_CAP_POWER_LEN; |
1175 | break; |
1176 | |
1177 | case TB_PORT_CAP_ADAP: |
1178 | if (tb_port_is_pcie_down(port) || tb_port_is_pcie_up(port)) { |
1179 | if (usb4_switch_version(sw: port->sw) < 2) |
1180 | length = PORT_CAP_V1_PCIE_LEN; |
1181 | else |
1182 | length = PORT_CAP_V2_PCIE_LEN; |
1183 | } else if (tb_port_is_dpin(port)) { |
1184 | if (usb4_switch_version(sw: port->sw) < 2) |
1185 | length = PORT_CAP_DP_V1_LEN; |
1186 | else |
1187 | length = PORT_CAP_DP_V2_LEN; |
1188 | } else if (tb_port_is_dpout(port)) { |
1189 | length = PORT_CAP_DP_V1_LEN; |
1190 | } else if (tb_port_is_usb3_down(port) || |
1191 | tb_port_is_usb3_up(port)) { |
1192 | length = PORT_CAP_USB3_LEN; |
1193 | } else { |
1194 | seq_printf(m: s, fmt: "0x%04x <unsupported capability 0x%02x>\n" , |
1195 | cap, header.basic.cap); |
1196 | return; |
1197 | } |
1198 | break; |
1199 | |
1200 | case TB_PORT_CAP_VSE: |
1201 | if (!header.extended_short.length) { |
1202 | ret = tb_port_read(port, buffer: (u32 *)&header + 1, space: TB_CFG_PORT, |
1203 | offset: cap + 1, length: 1); |
1204 | if (ret) { |
1205 | seq_printf(m: s, fmt: "0x%04x <capability read failed>\n" , |
1206 | cap + 1); |
1207 | return; |
1208 | } |
1209 | length = header.extended_long.length; |
1210 | vsec_id = header.extended_short.vsec_id; |
1211 | } else { |
1212 | length = header.extended_short.length; |
1213 | vsec_id = header.extended_short.vsec_id; |
1214 | } |
1215 | break; |
1216 | |
1217 | case TB_PORT_CAP_USB4: |
1218 | length = PORT_CAP_USB4_LEN; |
1219 | break; |
1220 | |
1221 | default: |
1222 | seq_printf(m: s, fmt: "0x%04x <unsupported capability 0x%02x>\n" , |
1223 | cap, header.basic.cap); |
1224 | return; |
1225 | } |
1226 | |
1227 | cap_show(s, NULL, port, cap, cap_id: header.basic.cap, vsec_id, length); |
1228 | } |
1229 | |
1230 | static void port_caps_show(struct tb_port *port, struct seq_file *s) |
1231 | { |
1232 | int cap; |
1233 | |
1234 | cap = tb_port_next_cap(port, offset: 0); |
1235 | while (cap > 0) { |
1236 | port_cap_show(port, s, cap); |
1237 | cap = tb_port_next_cap(port, offset: cap); |
1238 | } |
1239 | } |
1240 | |
1241 | static int port_basic_regs_show(struct tb_port *port, struct seq_file *s) |
1242 | { |
1243 | u32 data[PORT_CAP_BASIC_LEN]; |
1244 | int ret, i; |
1245 | |
1246 | ret = tb_port_read(port, buffer: data, space: TB_CFG_PORT, offset: 0, ARRAY_SIZE(data)); |
1247 | if (ret) |
1248 | return ret; |
1249 | |
1250 | for (i = 0; i < ARRAY_SIZE(data); i++) |
1251 | seq_printf(m: s, fmt: "0x%04x %4d 0x00 0x00 0x%08x\n" , i, i, data[i]); |
1252 | |
1253 | return 0; |
1254 | } |
1255 | |
1256 | static int port_regs_show(struct seq_file *s, void *not_used) |
1257 | { |
1258 | struct tb_port *port = s->private; |
1259 | struct tb_switch *sw = port->sw; |
1260 | struct tb *tb = sw->tb; |
1261 | int ret; |
1262 | |
1263 | pm_runtime_get_sync(dev: &sw->dev); |
1264 | |
1265 | if (mutex_lock_interruptible(&tb->lock)) { |
1266 | ret = -ERESTARTSYS; |
1267 | goto out_rpm_put; |
1268 | } |
1269 | |
1270 | seq_puts(m: s, s: "# offset relative_offset cap_id vs_cap_id value\n" ); |
1271 | |
1272 | ret = port_basic_regs_show(port, s); |
1273 | if (ret) |
1274 | goto out_unlock; |
1275 | |
1276 | port_caps_show(port, s); |
1277 | |
1278 | out_unlock: |
1279 | mutex_unlock(lock: &tb->lock); |
1280 | out_rpm_put: |
1281 | pm_runtime_mark_last_busy(dev: &sw->dev); |
1282 | pm_runtime_put_autosuspend(dev: &sw->dev); |
1283 | |
1284 | return ret; |
1285 | } |
1286 | DEBUGFS_ATTR_RW(port_regs); |
1287 | |
1288 | static void switch_cap_show(struct tb_switch *sw, struct seq_file *s, |
1289 | unsigned int cap) |
1290 | { |
1291 | struct tb_cap_any ; |
1292 | int ret, length; |
1293 | u8 vsec_id = 0; |
1294 | |
1295 | ret = tb_sw_read(sw, buffer: &header, space: TB_CFG_SWITCH, offset: cap, length: 1); |
1296 | if (ret) { |
1297 | seq_printf(m: s, fmt: "0x%04x <capability read failed>\n" , cap); |
1298 | return; |
1299 | } |
1300 | |
1301 | if (header.basic.cap == TB_SWITCH_CAP_VSE) { |
1302 | if (!header.extended_short.length) { |
1303 | ret = tb_sw_read(sw, buffer: (u32 *)&header + 1, space: TB_CFG_SWITCH, |
1304 | offset: cap + 1, length: 1); |
1305 | if (ret) { |
1306 | seq_printf(m: s, fmt: "0x%04x <capability read failed>\n" , |
1307 | cap + 1); |
1308 | return; |
1309 | } |
1310 | length = header.extended_long.length; |
1311 | } else { |
1312 | length = header.extended_short.length; |
1313 | } |
1314 | vsec_id = header.extended_short.vsec_id; |
1315 | } else { |
1316 | if (header.basic.cap == TB_SWITCH_CAP_TMU) { |
1317 | length = SWITCH_CAP_TMU_LEN; |
1318 | } else { |
1319 | seq_printf(m: s, fmt: "0x%04x <unknown capability 0x%02x>\n" , |
1320 | cap, header.basic.cap); |
1321 | return; |
1322 | } |
1323 | } |
1324 | |
1325 | cap_show(s, sw, NULL, cap, cap_id: header.basic.cap, vsec_id, length); |
1326 | } |
1327 | |
1328 | static void switch_caps_show(struct tb_switch *sw, struct seq_file *s) |
1329 | { |
1330 | int cap; |
1331 | |
1332 | cap = tb_switch_next_cap(sw, offset: 0); |
1333 | while (cap > 0) { |
1334 | switch_cap_show(sw, s, cap); |
1335 | cap = tb_switch_next_cap(sw, offset: cap); |
1336 | } |
1337 | } |
1338 | |
1339 | static int switch_basic_regs_show(struct tb_switch *sw, struct seq_file *s) |
1340 | { |
1341 | u32 data[SWITCH_CAP_BASIC_LEN]; |
1342 | size_t dwords; |
1343 | int ret, i; |
1344 | |
1345 | /* Only USB4 has the additional registers */ |
1346 | if (tb_switch_is_usb4(sw)) |
1347 | dwords = ARRAY_SIZE(data); |
1348 | else |
1349 | dwords = 7; |
1350 | |
1351 | ret = tb_sw_read(sw, buffer: data, space: TB_CFG_SWITCH, offset: 0, length: dwords); |
1352 | if (ret) |
1353 | return ret; |
1354 | |
1355 | for (i = 0; i < dwords; i++) |
1356 | seq_printf(m: s, fmt: "0x%04x %4d 0x00 0x00 0x%08x\n" , i, i, data[i]); |
1357 | |
1358 | return 0; |
1359 | } |
1360 | |
1361 | static int switch_regs_show(struct seq_file *s, void *not_used) |
1362 | { |
1363 | struct tb_switch *sw = s->private; |
1364 | struct tb *tb = sw->tb; |
1365 | int ret; |
1366 | |
1367 | pm_runtime_get_sync(dev: &sw->dev); |
1368 | |
1369 | if (mutex_lock_interruptible(&tb->lock)) { |
1370 | ret = -ERESTARTSYS; |
1371 | goto out_rpm_put; |
1372 | } |
1373 | |
1374 | seq_puts(m: s, s: "# offset relative_offset cap_id vs_cap_id value\n" ); |
1375 | |
1376 | ret = switch_basic_regs_show(sw, s); |
1377 | if (ret) |
1378 | goto out_unlock; |
1379 | |
1380 | switch_caps_show(sw, s); |
1381 | |
1382 | out_unlock: |
1383 | mutex_unlock(lock: &tb->lock); |
1384 | out_rpm_put: |
1385 | pm_runtime_mark_last_busy(dev: &sw->dev); |
1386 | pm_runtime_put_autosuspend(dev: &sw->dev); |
1387 | |
1388 | return ret; |
1389 | } |
1390 | DEBUGFS_ATTR_RW(switch_regs); |
1391 | |
1392 | static int path_show_one(struct tb_port *port, struct seq_file *s, int hopid) |
1393 | { |
1394 | u32 data[PATH_LEN]; |
1395 | int ret, i; |
1396 | |
1397 | ret = tb_port_read(port, buffer: data, space: TB_CFG_HOPS, offset: hopid * PATH_LEN, |
1398 | ARRAY_SIZE(data)); |
1399 | if (ret) { |
1400 | seq_printf(m: s, fmt: "0x%04x <not accessible>\n" , hopid * PATH_LEN); |
1401 | return ret; |
1402 | } |
1403 | |
1404 | for (i = 0; i < ARRAY_SIZE(data); i++) { |
1405 | seq_printf(m: s, fmt: "0x%04x %4d 0x%02x 0x%08x\n" , |
1406 | hopid * PATH_LEN + i, i, hopid, data[i]); |
1407 | } |
1408 | |
1409 | return 0; |
1410 | } |
1411 | |
1412 | static int path_show(struct seq_file *s, void *not_used) |
1413 | { |
1414 | struct tb_port *port = s->private; |
1415 | struct tb_switch *sw = port->sw; |
1416 | struct tb *tb = sw->tb; |
1417 | int start, i, ret = 0; |
1418 | |
1419 | pm_runtime_get_sync(dev: &sw->dev); |
1420 | |
1421 | if (mutex_lock_interruptible(&tb->lock)) { |
1422 | ret = -ERESTARTSYS; |
1423 | goto out_rpm_put; |
1424 | } |
1425 | |
1426 | seq_puts(m: s, s: "# offset relative_offset in_hop_id value\n" ); |
1427 | |
1428 | /* NHI and lane adapters have entry for path 0 */ |
1429 | if (tb_port_is_null(port) || tb_port_is_nhi(port)) { |
1430 | ret = path_show_one(port, s, hopid: 0); |
1431 | if (ret) |
1432 | goto out_unlock; |
1433 | } |
1434 | |
1435 | start = tb_port_is_nhi(port) ? 1 : TB_PATH_MIN_HOPID; |
1436 | |
1437 | for (i = start; i <= port->config.max_in_hop_id; i++) { |
1438 | ret = path_show_one(port, s, hopid: i); |
1439 | if (ret) |
1440 | break; |
1441 | } |
1442 | |
1443 | out_unlock: |
1444 | mutex_unlock(lock: &tb->lock); |
1445 | out_rpm_put: |
1446 | pm_runtime_mark_last_busy(dev: &sw->dev); |
1447 | pm_runtime_put_autosuspend(dev: &sw->dev); |
1448 | |
1449 | return ret; |
1450 | } |
1451 | DEBUGFS_ATTR_RO(path); |
1452 | |
1453 | static int counter_set_regs_show(struct tb_port *port, struct seq_file *s, |
1454 | int counter) |
1455 | { |
1456 | u32 data[COUNTER_SET_LEN]; |
1457 | int ret, i; |
1458 | |
1459 | ret = tb_port_read(port, buffer: data, space: TB_CFG_COUNTERS, |
1460 | offset: counter * COUNTER_SET_LEN, ARRAY_SIZE(data)); |
1461 | if (ret) { |
1462 | seq_printf(m: s, fmt: "0x%04x <not accessible>\n" , |
1463 | counter * COUNTER_SET_LEN); |
1464 | return ret; |
1465 | } |
1466 | |
1467 | for (i = 0; i < ARRAY_SIZE(data); i++) { |
1468 | seq_printf(m: s, fmt: "0x%04x %4d 0x%02x 0x%08x\n" , |
1469 | counter * COUNTER_SET_LEN + i, i, counter, data[i]); |
1470 | } |
1471 | |
1472 | return 0; |
1473 | } |
1474 | |
1475 | static int counters_show(struct seq_file *s, void *not_used) |
1476 | { |
1477 | struct tb_port *port = s->private; |
1478 | struct tb_switch *sw = port->sw; |
1479 | struct tb *tb = sw->tb; |
1480 | int i, ret = 0; |
1481 | |
1482 | pm_runtime_get_sync(dev: &sw->dev); |
1483 | |
1484 | if (mutex_lock_interruptible(&tb->lock)) { |
1485 | ret = -ERESTARTSYS; |
1486 | goto out; |
1487 | } |
1488 | |
1489 | seq_puts(m: s, s: "# offset relative_offset counter_id value\n" ); |
1490 | |
1491 | for (i = 0; i < port->config.max_counters; i++) { |
1492 | ret = counter_set_regs_show(port, s, counter: i); |
1493 | if (ret) |
1494 | break; |
1495 | } |
1496 | |
1497 | mutex_unlock(lock: &tb->lock); |
1498 | |
1499 | out: |
1500 | pm_runtime_mark_last_busy(dev: &sw->dev); |
1501 | pm_runtime_put_autosuspend(dev: &sw->dev); |
1502 | |
1503 | return ret; |
1504 | } |
1505 | DEBUGFS_ATTR_RW(counters); |
1506 | |
1507 | /** |
1508 | * tb_switch_debugfs_init() - Add debugfs entries for router |
1509 | * @sw: Pointer to the router |
1510 | * |
1511 | * Adds debugfs directories and files for given router. |
1512 | */ |
1513 | void tb_switch_debugfs_init(struct tb_switch *sw) |
1514 | { |
1515 | struct dentry *debugfs_dir; |
1516 | struct tb_port *port; |
1517 | |
1518 | debugfs_dir = debugfs_create_dir(name: dev_name(dev: &sw->dev), parent: tb_debugfs_root); |
1519 | sw->debugfs_dir = debugfs_dir; |
1520 | debugfs_create_file(name: "regs" , DEBUGFS_MODE, parent: debugfs_dir, data: sw, |
1521 | fops: &switch_regs_fops); |
1522 | |
1523 | tb_switch_for_each_port(sw, port) { |
1524 | struct dentry *debugfs_dir; |
1525 | char dir_name[10]; |
1526 | |
1527 | if (port->disabled) |
1528 | continue; |
1529 | if (port->config.type == TB_TYPE_INACTIVE) |
1530 | continue; |
1531 | |
1532 | snprintf(buf: dir_name, size: sizeof(dir_name), fmt: "port%d" , port->port); |
1533 | debugfs_dir = debugfs_create_dir(name: dir_name, parent: sw->debugfs_dir); |
1534 | debugfs_create_file(name: "regs" , DEBUGFS_MODE, parent: debugfs_dir, |
1535 | data: port, fops: &port_regs_fops); |
1536 | debugfs_create_file(name: "path" , mode: 0400, parent: debugfs_dir, data: port, |
1537 | fops: &path_fops); |
1538 | if (port->config.counters_support) |
1539 | debugfs_create_file(name: "counters" , mode: 0600, parent: debugfs_dir, data: port, |
1540 | fops: &counters_fops); |
1541 | } |
1542 | |
1543 | margining_switch_init(sw); |
1544 | } |
1545 | |
1546 | /** |
1547 | * tb_switch_debugfs_remove() - Remove all router debugfs entries |
1548 | * @sw: Pointer to the router |
1549 | * |
1550 | * Removes all previously added debugfs entries under this router. |
1551 | */ |
1552 | void tb_switch_debugfs_remove(struct tb_switch *sw) |
1553 | { |
1554 | margining_switch_remove(sw); |
1555 | debugfs_remove_recursive(dentry: sw->debugfs_dir); |
1556 | } |
1557 | |
1558 | void tb_xdomain_debugfs_init(struct tb_xdomain *xd) |
1559 | { |
1560 | margining_xdomain_init(xd); |
1561 | } |
1562 | |
1563 | void tb_xdomain_debugfs_remove(struct tb_xdomain *xd) |
1564 | { |
1565 | margining_xdomain_remove(xd); |
1566 | } |
1567 | |
1568 | /** |
1569 | * tb_service_debugfs_init() - Add debugfs directory for service |
1570 | * @svc: Thunderbolt service pointer |
1571 | * |
1572 | * Adds debugfs directory for service. |
1573 | */ |
1574 | void tb_service_debugfs_init(struct tb_service *svc) |
1575 | { |
1576 | svc->debugfs_dir = debugfs_create_dir(name: dev_name(dev: &svc->dev), |
1577 | parent: tb_debugfs_root); |
1578 | } |
1579 | |
1580 | /** |
1581 | * tb_service_debugfs_remove() - Remove service debugfs directory |
1582 | * @svc: Thunderbolt service pointer |
1583 | * |
1584 | * Removes the previously created debugfs directory for @svc. |
1585 | */ |
1586 | void tb_service_debugfs_remove(struct tb_service *svc) |
1587 | { |
1588 | debugfs_remove_recursive(dentry: svc->debugfs_dir); |
1589 | svc->debugfs_dir = NULL; |
1590 | } |
1591 | |
1592 | void tb_debugfs_init(void) |
1593 | { |
1594 | tb_debugfs_root = debugfs_create_dir(name: "thunderbolt" , NULL); |
1595 | } |
1596 | |
1597 | void tb_debugfs_exit(void) |
1598 | { |
1599 | debugfs_remove_recursive(dentry: tb_debugfs_root); |
1600 | } |
1601 | |