1576 lines · 37.8 KB · C
RawDownload
// 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.