// 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_V1_PCIE_LEN 1
#define PORT_CAP_V2_PCIE_LEN 2
#define PORT_CAP_POWER_LEN 2
#define PORT_CAP_LANE_LEN 3
#define PORT_CAP_USB3_LEN 5
#define PORT_CAP_DP_V1_LEN 9
#define PORT_CAP_DP_V2_LEN 14
#define PORT_CAP_TMU_V1_LEN 8
#define PORT_CAP_TMU_V2_LEN 10
#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: %
|