// SPDX-License-Identifier: GPL-2.0/* * Debugfs interface * * Copyright (C) 2020, Intel Corporation * Authors: Gil Fine <gil.fine@intel.com> * Mika Westerberg <mika.westerberg@linux.intel.com> */ #include <linux/debugfs.h>#include <linux/pm_runtime.h>#include <linux/uaccess.h> #include "tb.h"#include "sb_regs.h" #define PORT_CAP_PCIE_LEN 1#define PORT_CAP_POWER_LEN 2#define PORT_CAP_LANE_LEN 3#define PORT_CAP_USB3_LEN 5#define PORT_CAP_DP_LEN 8#define PORT_CAP_TMU_LEN 8#define PORT_CAP_BASIC_LEN 9#define PORT_CAP_USB4_LEN 20 #define SWITCH_CAP_TMU_LEN 26#define SWITCH_CAP_BASIC_LEN 27 #define PATH_LEN 2 #define COUNTER_SET_LEN 3 #define DEBUGFS_ATTR(__space, __write) \static int __space ## _open(struct inode *inode, struct file *file) \{ \ return single_open(file, __space ## _show, inode->i_private); \} \ \static const struct file_operations __space ## _fops = { \ .owner = THIS_MODULE, \ .open = __space ## _open, \ .release = single_release, \ .read = seq_read, \ .write = __write, \ .llseek = seq_lseek, \} #define DEBUGFS_ATTR_RO(__space) \ DEBUGFS_ATTR(__space, NULL) #define DEBUGFS_ATTR_RW(__space) \ DEBUGFS_ATTR(__space, __space ## _write) static struct dentry *tb_debugfs_root; static void *validate_and_copy_from_user(const void __user *user_buf, size_t *count){ size_t nbytes; void *buf; if (!*count) return ERR_PTR(-EINVAL); if (!access_ok(user_buf, *count)) return ERR_PTR(-EFAULT); buf = (void *)get_zeroed_page(GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); nbytes = min_t(size_t, *count, PAGE_SIZE); if (copy_from_user(buf, user_buf, nbytes)) { free_page((unsigned long)buf); return ERR_PTR(-EFAULT); } *count = nbytes; return buf;} static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len, int long_fmt_len){ char *token; u32 v[5]; int ret; token = strsep(line, "\n"); if (!token) return false; /* * For Adapter/Router configuration space: * Short format is: offset value\n * v[0] v[1] * Long format as produced from the read side: * offset relative_offset cap_id vs_cap_id value\n * v[0] v[1] v[2] v[3] v[4] * * For Counter configuration space: * Short format is: offset\n * v[0] * Long format as produced from the read side: * offset relative_offset counter_id value\n * v[0] v[1] v[2] v[3] */ ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]); /* In case of Counters, clear counter, "val" content is NA */ if (ret == short_fmt_len) { *offs = v[0]; *val = v[short_fmt_len - 1]; return true; } else if (ret == long_fmt_len) { *offs = v[0]; *val = v[long_fmt_len - 1]; return true; } return false;} #if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE)static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, const char __user *user_buf, size_t count, loff_t *ppos){ struct tb *tb = sw->tb; char *line, *buf; u32 val, offset; int ret = 0; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); pm_runtime_get_sync(&sw->dev); if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out; } /* User did hardware changes behind the driver's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); line = buf; while (parse_line(&line, &offset, &val, 2, 5)) { if (port) ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1); else ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1); if (ret) break; } mutex_unlock(&tb->lock); out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); free_page((unsigned long)buf); return ret < 0 ? ret : count;} static ssize_t port_regs_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){ struct seq_file *s = file->private_data; struct tb_port *port = s->private; return regs_write(port->sw, port, user_buf, count, ppos);} static ssize_t switch_regs_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){ struct seq_file *s = file->private_data; struct tb_switch *sw = s->private; return regs_write(sw, NULL, user_buf, count, ppos);}#define DEBUGFS_MODE 0600#else#define port_regs_write NULL#define switch_regs_write NULL#define DEBUGFS_MODE 0400#endif #if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING)/** * struct tb_margining - Lane margining support * @caps: Port lane margining capabilities * @results: Last lane margining results * @lanes: %0, %1 or %7 (all) * @min_ber_level: Minimum supported BER level contour value * @max_ber_level: Maximum supported BER level contour value * @ber_level: Current BER level contour value * @voltage_steps: Number of mandatory voltage steps * @max_voltage_offset: Maximum mandatory voltage offset (in mV) * @time_steps: Number of time margin steps * @max_time_offset: Maximum time margin offset (in mUI) * @software: %true if software margining is used instead of hardware * @time: %true if time margining is used instead of voltage * @right_high: %false if left/low margin test is performed, %true if * right/high */struct tb_margining { u32 caps[2]; u32 results[2]; unsigned int lanes; unsigned int min_ber_level; unsigned int max_ber_level; unsigned int ber_level; unsigned int voltage_steps; unsigned int max_voltage_offset; unsigned int time_steps; unsigned int max_time_offset; bool software; bool time; bool right_high;}; static bool supports_software(const struct usb4_port *usb4){ return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW;} static bool supports_hardware(const struct usb4_port *usb4){ return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW;} static bool both_lanes(const struct usb4_port *usb4){ return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES;} static unsigned int independent_voltage_margins(const struct usb4_port *usb4){ return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >> USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT;} static bool supports_time(const struct usb4_port *usb4){ return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME;} /* Only applicable if supports_time() returns true */static unsigned int independent_time_margins(const struct usb4_port *usb4){ return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >> USB4_MARGIN_CAP_1_TIME_INDP_SHIFT;} static ssize_tmargining_ber_level_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){ struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; unsigned int val; int ret = 0; char *buf; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; if (usb4->margining->software) { ret = -EINVAL; goto out_unlock; } buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) { ret = PTR_ERR(buf); goto out_unlock; } buf[count - 1] = '\0'; ret = kstrtouint(buf, 10, &val); if (ret) goto out_free; if (val < usb4->margining->min_ber_level || val > usb4->margining->max_ber_level) { ret = -EINVAL; goto out_free; } usb4->margining->ber_level = val; out_free: free_page((unsigned long)buf);out_unlock: mutex_unlock(&tb->lock); return ret < 0 ? ret : count;} static void ber_level_show(struct seq_file *s, unsigned int val){ if (val % 2) seq_printf(s, "3 * 1e%d (%u)\n", -12 + (val + 1) / 2, val); else seq_printf(s, "1e%d (%u)\n", -12 + val / 2, val);} static int margining_ber_level_show(struct seq_file *s, void *not_used){ struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; if (usb4->margining->software) return -EINVAL; ber_level_show(s, usb4->margining->ber_level); return 0;}DEBUGFS_ATTR_RW(margining_ber_level); static int margining_caps_show(struct seq_file *s, void *not_used){ struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; u32 cap0, cap1; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; /* Dump the raw caps first */ cap0 = usb4->margining->caps[0]; seq_printf(s, "0x%08x\n", cap0); cap1 = usb4->margining->caps[1]; seq_printf(s, "0x%08x\n", cap1); seq_printf(s, "# software margining: %s\n", supports_software(usb4) ? "yes" : "no"); if (supports_hardware(usb4)) { seq_puts(s, "# hardware margining: yes\n"); seq_puts(s, "# minimum BER level contour: "); ber_level_show(s, usb4->margining->min_ber_level); seq_puts(s, "# maximum BER level contour: "); ber_level_show(s, usb4->margining->max_ber_level); } else { seq_puts(s, "# hardware margining: no\n"); } seq_printf(s, "# both lanes simultaneously: %s\n", both_lanes(usb4) ? "yes" : "no"); seq_printf(s, "# voltage margin steps: %u\n", usb4->margining->voltage_steps); seq_printf(s, "# maximum voltage offset: %u mV\n", usb4->margining->max_voltage_offset); switch (independent_voltage_margins(usb4)) { case USB4_MARGIN_CAP_0_VOLTAGE_MIN: seq_puts(s, "# returns minimum between high and low voltage margins\n"); break; case USB4_MARGIN_CAP_0_VOLTAGE_HL: seq_puts(s, "# returns high or low voltage margin\n"); break; case USB4_MARGIN_CAP_0_VOLTAGE_BOTH: seq_puts(s, "# returns both high and low margins\n"); break; } if (supports_time(usb4)) { seq_puts(s, "# time margining: yes\n"); seq_printf(s, "# time margining is destructive: %s\n", cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no"); switch (independent_time_margins(usb4)) { case USB4_MARGIN_CAP_1_TIME_MIN: seq_puts(s, "# returns minimum between left and right time margins\n"); break; case USB4_MARGIN_CAP_1_TIME_LR: seq_puts(s, "# returns left or right margin\n"); break; case USB4_MARGIN_CAP_1_TIME_BOTH: seq_puts(s, "# returns both left and right margins\n"); break; } seq_printf(s, "# time margin steps: %u\n", usb4->margining->time_steps); seq_printf(s, "# maximum time offset: %u mUI\n", usb4->margining->max_time_offset); } else { seq_puts(s, "# time margining: no\n"); } mutex_unlock(&tb->lock); return 0;}DEBUGFS_ATTR_RO(margining_caps); static ssize_tmargining_lanes_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){ struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; int ret = 0; char *buf; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); buf[count - 1] = '\0'; if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out_free; } if (!strcmp(buf, "0")) { usb4->margining->lanes = 0; } else if (!strcmp(buf, "1")) { usb4->margining->lanes = 1; } else if (!strcmp(buf, "all")) { /* Needs to be supported */ if (both_lanes(usb4)) usb4->margining->lanes = 7; else ret = -EINVAL; } else { ret = -EINVAL; } mutex_unlock(&tb->lock); out_free: free_page((unsigned long)buf); return ret < 0 ? ret : count;} static int margining_lanes_show(struct seq_file *s, void *not_used){ struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; unsigned int lanes; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; lanes = usb4->margining->lanes; if (both_lanes(usb4)) { if (!lanes) seq_puts(s, "[0] 1 all\n"); else if (lanes == 1) seq_puts(s, "0 [1] all\n"); else seq_puts(s, "0 1 [all]\n"); } else { if (!lanes) seq_puts(s, "[0] 1\n"); else seq_puts(s, "0 [1]\n"); } mutex_unlock(&tb->lock); return 0;}DEBUGFS_ATTR_RW(margining_lanes); static ssize_t margining_mode_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos){ struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; int ret = 0; char *buf; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); buf[count - 1] = '\0'; if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out_free; } if (!strcmp(buf, "software")) { if (supports_software(usb4)) usb4->margining->software = true;Lines 1–500 of 1576. Load more
// SPDX-License-Identifier: GPL-2.0 /* * Debugfs interface * * Copyright (C) 2020, Intel Corporation * Authors: Gil Fine <gil.fine@intel.com> * Mika Westerberg <mika.westerberg@linux.intel.com> */ #include <linux/debugfs.h> #include <linux/pm_runtime.h> #include <linux/uaccess.h> #include "tb.h" #include "sb_regs.h" #define PORT_CAP_PCIE_LEN 1 #define PORT_CAP_POWER_LEN 2 #define PORT_CAP_LANE_LEN 3 #define PORT_CAP_USB3_LEN 5 #define PORT_CAP_DP_LEN 8 #define PORT_CAP_TMU_LEN 8 #define PORT_CAP_BASIC_LEN 9 #define PORT_CAP_USB4_LEN 20 #define SWITCH_CAP_TMU_LEN 26 #define SWITCH_CAP_BASIC_LEN 27 #define PATH_LEN 2 #define COUNTER_SET_LEN 3 #define DEBUGFS_ATTR(__space, __write) \ static int __space ## _open(struct inode *inode, struct file *file) \ { \ return single_open(file, __space ## _show, inode->i_private); \ } \ \ static const struct file_operations __space ## _fops = { \ .owner = THIS_MODULE, \ .open = __space ## _open, \ .release = single_release, \ .read = seq_read, \ .write = __write, \ .llseek = seq_lseek, \ } #define DEBUGFS_ATTR_RO(__space) \ DEBUGFS_ATTR(__space, NULL) #define DEBUGFS_ATTR_RW(__space) \ DEBUGFS_ATTR(__space, __space ## _write) static struct dentry *tb_debugfs_root; static void *validate_and_copy_from_user(const void __user *user_buf, size_t *count) { size_t nbytes; void *buf; if (!*count) return ERR_PTR(-EINVAL); if (!access_ok(user_buf, *count)) return ERR_PTR(-EFAULT); buf = (void *)get_zeroed_page(GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); nbytes = min_t(size_t, *count, PAGE_SIZE); if (copy_from_user(buf, user_buf, nbytes)) { free_page((unsigned long)buf); return ERR_PTR(-EFAULT); } *count = nbytes; return buf; } static bool parse_line(char **line, u32 *offs, u32 *val, int short_fmt_len, int long_fmt_len) { char *token; u32 v[5]; int ret; token = strsep(line, "\n"); if (!token) return false; /* * For Adapter/Router configuration space: * Short format is: offset value\n * v[0] v[1] * Long format as produced from the read side: * offset relative_offset cap_id vs_cap_id value\n * v[0] v[1] v[2] v[3] v[4] * * For Counter configuration space: * Short format is: offset\n * v[0] * Long format as produced from the read side: * offset relative_offset counter_id value\n * v[0] v[1] v[2] v[3] */ ret = sscanf(token, "%i %i %i %i %i", &v[0], &v[1], &v[2], &v[3], &v[4]); /* In case of Counters, clear counter, "val" content is NA */ if (ret == short_fmt_len) { *offs = v[0]; *val = v[short_fmt_len - 1]; return true; } else if (ret == long_fmt_len) { *offs = v[0]; *val = v[long_fmt_len - 1]; return true; } return false; } #if IS_ENABLED(CONFIG_USB4_DEBUGFS_WRITE) static ssize_t regs_write(struct tb_switch *sw, struct tb_port *port, const char __user *user_buf, size_t count, loff_t *ppos) { struct tb *tb = sw->tb; char *line, *buf; u32 val, offset; int ret = 0; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); pm_runtime_get_sync(&sw->dev); if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out; } /* User did hardware changes behind the driver's back */ add_taint(TAINT_USER, LOCKDEP_STILL_OK); line = buf; while (parse_line(&line, &offset, &val, 2, 5)) { if (port) ret = tb_port_write(port, &val, TB_CFG_PORT, offset, 1); else ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, offset, 1); if (ret) break; } mutex_unlock(&tb->lock); out: pm_runtime_mark_last_busy(&sw->dev); pm_runtime_put_autosuspend(&sw->dev); free_page((unsigned long)buf); return ret < 0 ? ret : count; } static ssize_t port_regs_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct tb_port *port = s->private; return regs_write(port->sw, port, user_buf, count, ppos); } static ssize_t switch_regs_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct tb_switch *sw = s->private; return regs_write(sw, NULL, user_buf, count, ppos); } #define DEBUGFS_MODE 0600 #else #define port_regs_write NULL #define switch_regs_write NULL #define DEBUGFS_MODE 0400 #endif #if IS_ENABLED(CONFIG_USB4_DEBUGFS_MARGINING) /** * struct tb_margining - Lane margining support * @caps: Port lane margining capabilities * @results: Last lane margining results * @lanes: %0, %1 or %7 (all) * @min_ber_level: Minimum supported BER level contour value * @max_ber_level: Maximum supported BER level contour value * @ber_level: Current BER level contour value * @voltage_steps: Number of mandatory voltage steps * @max_voltage_offset: Maximum mandatory voltage offset (in mV) * @time_steps: Number of time margin steps * @max_time_offset: Maximum time margin offset (in mUI) * @software: %true if software margining is used instead of hardware * @time: %true if time margining is used instead of voltage * @right_high: %false if left/low margin test is performed, %true if * right/high */ struct tb_margining { u32 caps[2]; u32 results[2]; unsigned int lanes; unsigned int min_ber_level; unsigned int max_ber_level; unsigned int ber_level; unsigned int voltage_steps; unsigned int max_voltage_offset; unsigned int time_steps; unsigned int max_time_offset; bool software; bool time; bool right_high; }; static bool supports_software(const struct usb4_port *usb4) { return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_SW; } static bool supports_hardware(const struct usb4_port *usb4) { return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_MODES_HW; } static bool both_lanes(const struct usb4_port *usb4) { return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_2_LANES; } static unsigned int independent_voltage_margins(const struct usb4_port *usb4) { return (usb4->margining->caps[0] & USB4_MARGIN_CAP_0_VOLTAGE_INDP_MASK) >> USB4_MARGIN_CAP_0_VOLTAGE_INDP_SHIFT; } static bool supports_time(const struct usb4_port *usb4) { return usb4->margining->caps[0] & USB4_MARGIN_CAP_0_TIME; } /* Only applicable if supports_time() returns true */ static unsigned int independent_time_margins(const struct usb4_port *usb4) { return (usb4->margining->caps[1] & USB4_MARGIN_CAP_1_TIME_INDP_MASK) >> USB4_MARGIN_CAP_1_TIME_INDP_SHIFT; } static ssize_t margining_ber_level_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; unsigned int val; int ret = 0; char *buf; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; if (usb4->margining->software) { ret = -EINVAL; goto out_unlock; } buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) { ret = PTR_ERR(buf); goto out_unlock; } buf[count - 1] = '\0'; ret = kstrtouint(buf, 10, &val); if (ret) goto out_free; if (val < usb4->margining->min_ber_level || val > usb4->margining->max_ber_level) { ret = -EINVAL; goto out_free; } usb4->margining->ber_level = val; out_free: free_page((unsigned long)buf); out_unlock: mutex_unlock(&tb->lock); return ret < 0 ? ret : count; } static void ber_level_show(struct seq_file *s, unsigned int val) { if (val % 2) seq_printf(s, "3 * 1e%d (%u)\n", -12 + (val + 1) / 2, val); else seq_printf(s, "1e%d (%u)\n", -12 + val / 2, val); } static int margining_ber_level_show(struct seq_file *s, void *not_used) { struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; if (usb4->margining->software) return -EINVAL; ber_level_show(s, usb4->margining->ber_level); return 0; } DEBUGFS_ATTR_RW(margining_ber_level); static int margining_caps_show(struct seq_file *s, void *not_used) { struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; u32 cap0, cap1; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; /* Dump the raw caps first */ cap0 = usb4->margining->caps[0]; seq_printf(s, "0x%08x\n", cap0); cap1 = usb4->margining->caps[1]; seq_printf(s, "0x%08x\n", cap1); seq_printf(s, "# software margining: %s\n", supports_software(usb4) ? "yes" : "no"); if (supports_hardware(usb4)) { seq_puts(s, "# hardware margining: yes\n"); seq_puts(s, "# minimum BER level contour: "); ber_level_show(s, usb4->margining->min_ber_level); seq_puts(s, "# maximum BER level contour: "); ber_level_show(s, usb4->margining->max_ber_level); } else { seq_puts(s, "# hardware margining: no\n"); } seq_printf(s, "# both lanes simultaneously: %s\n", both_lanes(usb4) ? "yes" : "no"); seq_printf(s, "# voltage margin steps: %u\n", usb4->margining->voltage_steps); seq_printf(s, "# maximum voltage offset: %u mV\n", usb4->margining->max_voltage_offset); switch (independent_voltage_margins(usb4)) { case USB4_MARGIN_CAP_0_VOLTAGE_MIN: seq_puts(s, "# returns minimum between high and low voltage margins\n"); break; case USB4_MARGIN_CAP_0_VOLTAGE_HL: seq_puts(s, "# returns high or low voltage margin\n"); break; case USB4_MARGIN_CAP_0_VOLTAGE_BOTH: seq_puts(s, "# returns both high and low margins\n"); break; } if (supports_time(usb4)) { seq_puts(s, "# time margining: yes\n"); seq_printf(s, "# time margining is destructive: %s\n", cap1 & USB4_MARGIN_CAP_1_TIME_DESTR ? "yes" : "no"); switch (independent_time_margins(usb4)) { case USB4_MARGIN_CAP_1_TIME_MIN: seq_puts(s, "# returns minimum between left and right time margins\n"); break; case USB4_MARGIN_CAP_1_TIME_LR: seq_puts(s, "# returns left or right margin\n"); break; case USB4_MARGIN_CAP_1_TIME_BOTH: seq_puts(s, "# returns both left and right margins\n"); break; } seq_printf(s, "# time margin steps: %u\n", usb4->margining->time_steps); seq_printf(s, "# maximum time offset: %u mUI\n", usb4->margining->max_time_offset); } else { seq_puts(s, "# time margining: no\n"); } mutex_unlock(&tb->lock); return 0; } DEBUGFS_ATTR_RO(margining_caps); static ssize_t margining_lanes_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; int ret = 0; char *buf; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); buf[count - 1] = '\0'; if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out_free; } if (!strcmp(buf, "0")) { usb4->margining->lanes = 0; } else if (!strcmp(buf, "1")) { usb4->margining->lanes = 1; } else if (!strcmp(buf, "all")) { /* Needs to be supported */ if (both_lanes(usb4)) usb4->margining->lanes = 7; else ret = -EINVAL; } else { ret = -EINVAL; } mutex_unlock(&tb->lock); out_free: free_page((unsigned long)buf); return ret < 0 ? ret : count; } static int margining_lanes_show(struct seq_file *s, void *not_used) { struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; unsigned int lanes; if (mutex_lock_interruptible(&tb->lock)) return -ERESTARTSYS; lanes = usb4->margining->lanes; if (both_lanes(usb4)) { if (!lanes) seq_puts(s, "[0] 1 all\n"); else if (lanes == 1) seq_puts(s, "0 [1] all\n"); else seq_puts(s, "0 1 [all]\n"); } else { if (!lanes) seq_puts(s, "[0] 1\n"); else seq_puts(s, "0 [1]\n"); } mutex_unlock(&tb->lock); return 0; } DEBUGFS_ATTR_RW(margining_lanes); static ssize_t margining_mode_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct tb_port *port = s->private; struct usb4_port *usb4 = port->usb4; struct tb *tb = port->sw->tb; int ret = 0; char *buf; buf = validate_and_copy_from_user(user_buf, &count); if (IS_ERR(buf)) return PTR_ERR(buf); buf[count - 1] = '\0'; if (mutex_lock_interruptible(&tb->lock)) { ret = -ERESTARTSYS; goto out_free; } if (!strcmp(buf, "software")) { if (supports_software(usb4)) usb4->margining->software = true;