]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
thunderbolt: Add sideband register access to debugfs
authorMika Westerberg <mika.westerberg@linux.intel.com>
Mon, 20 Mar 2023 11:50:44 +0000 (13:50 +0200)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Mon, 17 Jun 2024 09:47:11 +0000 (12:47 +0300)
This makes it possible to read and write USB4 port and retimer sideband
registers through debugfs which is useful for debugging and manufacturing
purposes. We add "sb_regs" debugfs attribute under each USB4 port and
retimer that is used to access the sideband.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
drivers/thunderbolt/debugfs.c
drivers/thunderbolt/retimer.c
drivers/thunderbolt/sb_regs.h
drivers/thunderbolt/tb.h

index 70b52aac3d97d49025bac9f6b041d3e4daf93f86..5196ece2ded732cd583caac3c3a78cc96ca8826e 100644 (file)
 
 #define COUNTER_SET_LEN                3
 
+/* Sideband registers and their sizes as defined in the USB4 spec */
+struct sb_reg {
+       unsigned int reg;
+       unsigned int size;
+};
+
+#define SB_MAX_SIZE            64
+
+/* Sideband registers for router */
+static const struct sb_reg port_sb_regs[] = {
+       { USB4_SB_VENDOR_ID, 4 },
+       { USB4_SB_PRODUCT_ID, 4 },
+       { USB4_SB_DEBUG_CONF, 4 },
+       { USB4_SB_DEBUG, 54 },
+       { USB4_SB_LRD_TUNING, 4 },
+       { USB4_SB_OPCODE, 4 },
+       { USB4_SB_METADATA, 4 },
+       { USB4_SB_LINK_CONF, 3 },
+       { USB4_SB_GEN23_TXFFE, 4 },
+       { USB4_SB_GEN4_TXFFE, 4 },
+       { USB4_SB_VERSION, 4 },
+       { USB4_SB_DATA, 64 },
+};
+
+/* Sideband registers for retimer */
+static const struct sb_reg retimer_sb_regs[] = {
+       { USB4_SB_VENDOR_ID, 4 },
+       { USB4_SB_PRODUCT_ID, 4 },
+       { USB4_SB_FW_VERSION, 4 },
+       { USB4_SB_LRD_TUNING, 4 },
+       { USB4_SB_OPCODE, 4 },
+       { USB4_SB_METADATA, 4 },
+       { USB4_SB_GEN23_TXFFE, 4 },
+       { USB4_SB_GEN4_TXFFE, 4 },
+       { USB4_SB_VERSION, 4 },
+       { USB4_SB_DATA, 64 },
+};
+
 #define DEBUGFS_ATTR(__space, __write)                                 \
 static int __space ## _open(struct inode *inode, struct file *file)    \
 {                                                                      \
@@ -184,10 +222,157 @@ static ssize_t switch_regs_write(struct file *file, const char __user *user_buf,
 
        return regs_write(sw, NULL, user_buf, count, ppos);
 }
+
+static bool parse_sb_line(char **line, u8 *reg, u8 *data, size_t data_size,
+                         size_t *bytes_read)
+{
+       char *field, *token;
+       int i;
+
+       token = strsep(line, "\n");
+       if (!token)
+               return false;
+
+       /* Parse the register first */
+       field = strsep(&token, " ");
+       if (!field)
+               return false;
+       if (kstrtou8(field, 0, reg))
+               return false;
+
+       /* Then the values for the register, up to data_size */
+       for (i = 0; i < data_size; i++) {
+               field = strsep(&token, " ");
+               if (!field)
+                       break;
+               if (kstrtou8(field, 0, &data[i]))
+                       return false;
+       }
+
+       *bytes_read = i;
+       return true;
+}
+
+static ssize_t sb_regs_write(struct tb_port *port, const struct sb_reg *sb_regs,
+                            size_t size, enum usb4_sb_target target, u8 index,
+                            char *buf, size_t count, loff_t *ppos)
+{
+       u8 reg, data[SB_MAX_SIZE];
+       size_t bytes_read;
+       char *line = buf;
+
+       /* User did hardware changes behind the driver's back */
+       add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+
+       /*
+        * For sideband registers we accept:
+        * reg b0 b1 b2...\n
+        *
+        * Here "reg" is the byte offset of the sideband register and "b0"..
+        * are the byte values. There can be less byte values than the register
+        * size. The leftovers will not be overwritten.
+        */
+       while (parse_sb_line(&line, &reg, data, ARRAY_SIZE(data), &bytes_read)) {
+               const struct sb_reg *sb_reg;
+               int ret;
+
+               /* At least one byte must be passed */
+               if (bytes_read < 1)
+                       return -EINVAL;
+
+               /* Find the register */
+               sb_reg = NULL;
+               for (int i = 0; i < size; i++) {
+                       if (sb_regs[i].reg == reg) {
+                               sb_reg = &sb_regs[i];
+                               break;
+                       }
+               }
+
+               if (!sb_reg)
+                       return -EINVAL;
+
+               if (bytes_read > sb_regs->size)
+                       return -E2BIG;
+
+               ret = usb4_port_sb_write(port, target, index, sb_reg->reg, data,
+                                        bytes_read);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+static ssize_t port_sb_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;
+       struct tb_switch *sw = port->sw;
+       struct tb *tb = sw->tb;
+       char *buf;
+       int ret;
+
+       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_rpm_put;
+       }
+
+       ret = sb_regs_write(port, port_sb_regs, ARRAY_SIZE(port_sb_regs),
+                           USB4_SB_TARGET_ROUTER, 0, buf, count, ppos);
+
+       mutex_unlock(&tb->lock);
+out_rpm_put:
+       pm_runtime_mark_last_busy(&sw->dev);
+       pm_runtime_put_autosuspend(&sw->dev);
+
+       return ret < 0 ? ret : count;
+}
+
+static ssize_t retimer_sb_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_retimer *rt = s->private;
+       struct tb *tb = rt->tb;
+       char *buf;
+       int ret;
+
+       buf = validate_and_copy_from_user(user_buf, &count);
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       pm_runtime_get_sync(&rt->dev);
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_rpm_put;
+       }
+
+       ret = sb_regs_write(rt->port, retimer_sb_regs, ARRAY_SIZE(retimer_sb_regs),
+                           USB4_SB_TARGET_RETIMER, rt->index, buf, count, ppos);
+
+       mutex_unlock(&tb->lock);
+out_rpm_put:
+       pm_runtime_mark_last_busy(&rt->dev);
+       pm_runtime_put_autosuspend(&rt->dev);
+
+       return ret < 0 ? ret : count;
+}
 #define DEBUGFS_MODE           0600
 #else
 #define port_regs_write                NULL
 #define switch_regs_write      NULL
+#define port_sb_regs_write     NULL
+#define retimer_sb_regs_write  NULL
 #define DEBUGFS_MODE           0400
 #endif
 
@@ -1505,6 +1690,60 @@ out:
 }
 DEBUGFS_ATTR_RW(counters);
 
+static int sb_regs_show(struct tb_port *port, const struct sb_reg *sb_regs,
+                       size_t size, enum usb4_sb_target target, u8 index,
+                       struct seq_file *s)
+{
+       int ret, i;
+
+       seq_puts(s, "# register value\n");
+
+       for (i = 0; i < size; i++) {
+               const struct sb_reg *regs = &sb_regs[i];
+               u8 data[64];
+               int j;
+
+               memset(data, 0, sizeof(data));
+               ret = usb4_port_sb_read(port, target, index, regs->reg, data,
+                                       regs->size);
+               if (ret)
+                       return ret;
+
+               seq_printf(s, "0x%02x", regs->reg);
+               for (j = 0; j < regs->size; j++)
+                       seq_printf(s, " 0x%02x", data[j]);
+               seq_puts(s, "\n");
+       }
+
+       return 0;
+}
+
+static int port_sb_regs_show(struct seq_file *s, void *not_used)
+{
+       struct tb_port *port = s->private;
+       struct tb_switch *sw = port->sw;
+       struct tb *tb = sw->tb;
+       int ret;
+
+       pm_runtime_get_sync(&sw->dev);
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_rpm_put;
+       }
+
+       ret = sb_regs_show(port, port_sb_regs, ARRAY_SIZE(port_sb_regs),
+                          USB4_SB_TARGET_ROUTER, 0, s);
+
+       mutex_unlock(&tb->lock);
+out_rpm_put:
+       pm_runtime_mark_last_busy(&sw->dev);
+       pm_runtime_put_autosuspend(&sw->dev);
+
+       return ret;
+}
+DEBUGFS_ATTR_RW(port_sb_regs);
+
 /**
  * tb_switch_debugfs_init() - Add debugfs entries for router
  * @sw: Pointer to the router
@@ -1539,6 +1778,9 @@ void tb_switch_debugfs_init(struct tb_switch *sw)
                if (port->config.counters_support)
                        debugfs_create_file("counters", 0600, debugfs_dir, port,
                                            &counters_fops);
+               if (port->usb4)
+                       debugfs_create_file("sb_regs", DEBUGFS_MODE, debugfs_dir,
+                                           port, &port_sb_regs_fops);
        }
 
        margining_switch_init(sw);
@@ -1590,6 +1832,57 @@ void tb_service_debugfs_remove(struct tb_service *svc)
        svc->debugfs_dir = NULL;
 }
 
+static int retimer_sb_regs_show(struct seq_file *s, void *not_used)
+{
+       struct tb_retimer *rt = s->private;
+       struct tb *tb = rt->tb;
+       int ret;
+
+       pm_runtime_get_sync(&rt->dev);
+
+       if (mutex_lock_interruptible(&tb->lock)) {
+               ret = -ERESTARTSYS;
+               goto out_rpm_put;
+       }
+
+       ret = sb_regs_show(rt->port, retimer_sb_regs, ARRAY_SIZE(retimer_sb_regs),
+                          USB4_SB_TARGET_RETIMER, rt->index, s);
+
+       mutex_unlock(&tb->lock);
+out_rpm_put:
+       pm_runtime_mark_last_busy(&rt->dev);
+       pm_runtime_put_autosuspend(&rt->dev);
+
+       return ret;
+}
+DEBUGFS_ATTR_RW(retimer_sb_regs);
+
+/**
+ * tb_retimer_debugfs_init() - Add debugfs directory for retimer
+ * @rt: Pointer to retimer structure
+ *
+ * Adds and populates retimer debugfs directory.
+ */
+void tb_retimer_debugfs_init(struct tb_retimer *rt)
+{
+       struct dentry *debugfs_dir;
+
+       debugfs_dir = debugfs_create_dir(dev_name(&rt->dev), tb_debugfs_root);
+       debugfs_create_file("sb_regs", DEBUGFS_MODE, debugfs_dir, rt,
+                           &retimer_sb_regs_fops);
+}
+
+/**
+ * tb_retimer_debugfs_remove() - Remove retimer debugfs directory
+ * @rt: Pointer to retimer structure
+ *
+ * Removes the retimer debugfs directory along with its contents.
+ */
+void tb_retimer_debugfs_remove(struct tb_retimer *rt)
+{
+       debugfs_lookup_and_remove(dev_name(&rt->dev), tb_debugfs_root);
+}
+
 void tb_debugfs_init(void)
 {
        tb_debugfs_root = debugfs_create_dir("thunderbolt", NULL);
index 9b66bff98f7eca69d37492586eda27620d3e86fe..8ce1dc7bbfad9d1ee34896db9c5ff52f84aa055c 100644 (file)
@@ -437,12 +437,14 @@ static int tb_retimer_add(struct tb_port *port, u8 index, u32 auth_status)
        pm_runtime_mark_last_busy(&rt->dev);
        pm_runtime_use_autosuspend(&rt->dev);
 
+       tb_retimer_debugfs_init(rt);
        return 0;
 }
 
 static void tb_retimer_remove(struct tb_retimer *rt)
 {
        dev_info(&rt->dev, "retimer disconnected\n");
+       tb_retimer_debugfs_remove(rt);
        tb_nvm_free(rt->nvm);
        device_unregister(&rt->dev);
 }
index f37a4320f10a528393b8a10f320103b8565b124d..b2a6add82161ea5ec0cd4d3011a975da8d85d8e8 100644 (file)
 
 #define USB4_SB_VENDOR_ID                      0x00
 #define USB4_SB_PRODUCT_ID                     0x01
+#define USB4_SB_FW_VERSION                     0x02
+#define USB4_SB_DEBUG_CONF                     0x05
+#define USB4_SB_DEBUG                          0x06
+#define USB4_SB_LRD_TUNING                     0x07
 #define USB4_SB_OPCODE                         0x08
 
 enum usb4_sb_opcode {
@@ -35,6 +39,10 @@ enum usb4_sb_opcode {
 
 #define USB4_SB_METADATA                       0x09
 #define USB4_SB_METADATA_NVM_AUTH_WRITE_MASK   GENMASK(5, 0)
+#define USB4_SB_LINK_CONF                      0x0c
+#define USB4_SB_GEN23_TXFFE                    0x0d
+#define USB4_SB_GEN4_TXFFE                     0x0e
+#define USB4_SB_VERSION                                0x0f
 #define USB4_SB_DATA                           0x12
 
 /* USB4_SB_OPCODE_READ_LANE_MARGINING_CAP */
index ab3366fcb0a35fccc7ced640f132b62437016c99..1a53d18223d9494c77eab9cdaed5c698bdfc6b17 100644 (file)
@@ -1458,6 +1458,8 @@ void tb_xdomain_debugfs_init(struct tb_xdomain *xd);
 void tb_xdomain_debugfs_remove(struct tb_xdomain *xd);
 void tb_service_debugfs_init(struct tb_service *svc);
 void tb_service_debugfs_remove(struct tb_service *svc);
+void tb_retimer_debugfs_init(struct tb_retimer *rt);
+void tb_retimer_debugfs_remove(struct tb_retimer *rt);
 #else
 static inline void tb_debugfs_init(void) { }
 static inline void tb_debugfs_exit(void) { }
@@ -1467,6 +1469,8 @@ static inline void tb_xdomain_debugfs_init(struct tb_xdomain *xd) { }
 static inline void tb_xdomain_debugfs_remove(struct tb_xdomain *xd) { }
 static inline void tb_service_debugfs_init(struct tb_service *svc) { }
 static inline void tb_service_debugfs_remove(struct tb_service *svc) { }
+static inline void tb_retimer_debugfs_init(struct tb_retimer *rt) { }
+static inline void tb_retimer_debugfs_remove(struct tb_retimer *rt) { }
 #endif
 
 #endif