]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
thunderbolt: Add support for authenticate on disconnect
authorMario Limonciello <mario.limonciello@dell.com>
Tue, 23 Jun 2020 16:14:29 +0000 (11:14 -0500)
committerMika Westerberg <mika.westerberg@linux.intel.com>
Wed, 1 Jul 2020 10:51:49 +0000 (13:51 +0300)
Some external devices can support completing thunderbolt authentication
when they are unplugged. For this to work though, the link controller must
remain operational.

The only device known to support this right now is the Dell WD19TB, so add
a quirk for this.

Signed-off-by: Mario Limonciello <mario.limonciello@dell.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Documentation/ABI/testing/sysfs-bus-thunderbolt
drivers/thunderbolt/Makefile
drivers/thunderbolt/eeprom.c
drivers/thunderbolt/lc.c
drivers/thunderbolt/quirks.c [new file with mode: 0644]
drivers/thunderbolt/switch.c
drivers/thunderbolt/tb.h
drivers/thunderbolt/tb_regs.h

index 7d0500b4d58a95e0c02bd73be5b914192653211f..dd565c378b4032f18b1e528f6b3c0bdc6e5e0939 100644 (file)
@@ -276,3 +276,16 @@ Date:              Oct 2020
 KernelVersion: v5.9
 Contact:       Mika Westerberg <mika.westerberg@linux.intel.com>
 Description:   Retimer vendor identifier read from the hardware.
+
+What:          /sys/bus/thunderbolt/devices/.../nvm_authenticate_on_disconnect
+Date:          Oct 2020
+KernelVersion: v5.9
+Contact:       Mario Limonciello <mario.limonciello@dell.com>
+Description:   For supported devices, automatically authenticate the new Thunderbolt
+               image when the device is disconnected from the host system.
+
+               This file will accept writing values "1" or "2"
+               - Writing "1" will flush the image to the storage
+               area and prepare the device for authentication on disconnect.
+               - Writing "2" will run some basic validation on the image
+               and flush it to the storage area.
index cf7e1b42f4ad59a2603c1af2324773d48a908937..4ab5bfad7bfd69f1b81a9655707569f848eb2fbd 100644 (file)
@@ -2,6 +2,6 @@
 obj-${CONFIG_USB4} := thunderbolt.o
 thunderbolt-objs := nhi.o nhi_ops.o ctl.o tb.o switch.o cap.o path.o tunnel.o eeprom.o
 thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
-thunderbolt-objs += nvm.o retimer.o
+thunderbolt-objs += nvm.o retimer.o quirks.o
 
 obj-${CONFIG_USB4_KUNIT_TEST} += test.o
index b451a5aa90b5003256a956186d0867ddf04759db..3ebca44ab3faa38de775099a2fc135711f1ec855 100644 (file)
@@ -599,6 +599,7 @@ parse:
                sw->uid = header->uid;
        sw->vendor = header->vendor_id;
        sw->device = header->model_id;
+       tb_check_quirks(sw);
 
        crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len);
        if (crc != header->data_crc32) {
index bd44d50246d2c8171ba94698925cd40f55b71a62..19be627d090fd0decb5c534c6ed02850f6cee9b7 100644 (file)
@@ -366,3 +366,17 @@ int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in)
        tb_port_dbg(in, "sink %d de-allocated\n", sink);
        return 0;
 }
+
+/**
+ * tb_lc_force_power() - Forces LC to be powered on
+ * @sw: Thunderbolt switch
+ *
+ * This is useful to let authentication cycle pass even without
+ * a Thunderbolt link present.
+ */
+int tb_lc_force_power(struct tb_switch *sw)
+{
+       u32 in = 0xffff;
+
+       return tb_sw_write(sw, &in, TB_CFG_SWITCH, TB_LC_POWER, 1);
+}
diff --git a/drivers/thunderbolt/quirks.c b/drivers/thunderbolt/quirks.c
new file mode 100644 (file)
index 0000000..0525f59
--- /dev/null
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thunderbolt driver - quirks
+ *
+ * Copyright (c) 2020 Mario Limonciello <mario.limonciello@dell.com>
+ */
+
+#include "tb.h"
+
+static void quirk_force_power_link(struct tb_switch *sw)
+{
+       sw->quirks |= QUIRK_FORCE_POWER_LINK_CONTROLLER;
+}
+
+struct tb_quirk {
+       u16 vendor;
+       u16 device;
+       void (*hook)(struct tb_switch *sw);
+};
+
+const static struct tb_quirk tb_quirks[] = {
+       /* Dell WD19TB supports self-authentication on unplug */
+       { 0x00d4, 0xb070, quirk_force_power_link },
+};
+
+/**
+ * tb_check_quirks() - Check for quirks to apply
+ * @sw: Thunderbolt switch
+ *
+ *  Apply any quirks for the Thunderbolt controller
+ */
+void tb_check_quirks(struct tb_switch *sw)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(tb_quirks); i++) {
+               const struct tb_quirk *q = &tb_quirks[i];
+
+               if (sw->device == q->device && sw->vendor == q->vendor)
+                       q->hook(sw);
+       }
+}
index bbfbfebeee7f51de7bf32f0f090f06b93d19122f..712395f518b82225e06f58699f23c2fca470cb4d 100644 (file)
@@ -1493,8 +1493,8 @@ static ssize_t nvm_authenticate_show(struct device *dev,
        return sprintf(buf, "%#x\n", status);
 }
 
-static ssize_t nvm_authenticate_store(struct device *dev,
-       struct device_attribute *attr, const char *buf, size_t count)
+static ssize_t nvm_authenticate_sysfs(struct device *dev, const char *buf,
+                                     bool disconnect)
 {
        struct tb_switch *sw = tb_to_switch(dev);
        int val;
@@ -1532,8 +1532,12 @@ static ssize_t nvm_authenticate_store(struct device *dev,
                                goto exit_unlock;
                }
                if (val == WRITE_AND_AUTHENTICATE) {
-                       sw->nvm->authenticating = true;
-                       ret = nvm_authenticate(sw);
+                       if (disconnect) {
+                               ret = tb_lc_force_power(sw);
+                       } else {
+                               sw->nvm->authenticating = true;
+                               ret = nvm_authenticate(sw);
+                       }
                }
        }
 
@@ -1543,12 +1547,35 @@ exit_rpm:
        pm_runtime_mark_last_busy(&sw->dev);
        pm_runtime_put_autosuspend(&sw->dev);
 
+       return ret;
+}
+
+static ssize_t nvm_authenticate_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       int ret = nvm_authenticate_sysfs(dev, buf, false);
        if (ret)
                return ret;
        return count;
 }
 static DEVICE_ATTR_RW(nvm_authenticate);
 
+static ssize_t nvm_authenticate_on_disconnect_show(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       return nvm_authenticate_show(dev, attr, buf);
+}
+
+static ssize_t nvm_authenticate_on_disconnect_store(struct device *dev,
+       struct device_attribute *attr, const char *buf, size_t count)
+{
+       int ret;
+
+       ret = nvm_authenticate_sysfs(dev, buf, true);
+       return ret ? ret : count;
+}
+static DEVICE_ATTR_RW(nvm_authenticate_on_disconnect);
+
 static ssize_t nvm_version_show(struct device *dev,
                                struct device_attribute *attr, char *buf)
 {
@@ -1606,6 +1633,7 @@ static struct attribute *switch_attrs[] = {
        &dev_attr_generation.attr,
        &dev_attr_key.attr,
        &dev_attr_nvm_authenticate.attr,
+       &dev_attr_nvm_authenticate_on_disconnect.attr,
        &dev_attr_nvm_version.attr,
        &dev_attr_rx_speed.attr,
        &dev_attr_rx_lanes.attr,
@@ -1660,6 +1688,10 @@ static umode_t switch_attr_is_visible(struct kobject *kobj,
                if (tb_route(sw))
                        return attr->mode;
                return 0;
+       } else if (attr == &dev_attr_nvm_authenticate_on_disconnect.attr) {
+               if (sw->quirks & QUIRK_FORCE_POWER_LINK_CONTROLLER)
+                       return attr->mode;
+               return 0;
        }
 
        return sw->safe_mode ? 0 : attr->mode;
index b04a2da9128bccffc6788e26d340b4a86da7c9b7..a413d55b5f8b3d40aff34f0db6fab9dd98a63c9e 100644 (file)
@@ -133,6 +133,7 @@ struct tb_switch_tmu {
  * @depth: Depth in the chain this switch is connected (ICM only)
  * @rpm_complete: Completion used to wait for runtime resume to
  *               complete (ICM only)
+ * @quirks: Quirks used for this Thunderbolt switch
  *
  * When the switch is being added or removed to the domain (other
  * switches) you need to have domain lock held.
@@ -171,6 +172,7 @@ struct tb_switch {
        u8 link;
        u8 depth;
        struct completion rpm_complete;
+       unsigned long quirks;
 };
 
 /**
@@ -849,6 +851,7 @@ bool tb_lc_lane_bonding_possible(struct tb_switch *sw);
 bool tb_lc_dp_sink_query(struct tb_switch *sw, struct tb_port *in);
 int tb_lc_dp_sink_alloc(struct tb_switch *sw, struct tb_port *in);
 int tb_lc_dp_sink_dealloc(struct tb_switch *sw, struct tb_port *in);
+int tb_lc_force_power(struct tb_switch *sw);
 
 static inline int tb_route_length(u64 route)
 {
@@ -941,4 +944,10 @@ int usb4_usb3_port_allocate_bandwidth(struct tb_port *port, int *upstream_bw,
                                      int *downstream_bw);
 int usb4_usb3_port_release_bandwidth(struct tb_port *port, int *upstream_bw,
                                     int *downstream_bw);
+
+/* keep link controller awake during update */
+#define QUIRK_FORCE_POWER_LINK_CONTROLLER              BIT(0)
+
+void tb_check_quirks(struct tb_switch *sw);
+
 #endif
index 2ac6af8e0c133c891f92207a5104368c0fd0721b..fd4fc144d17f654d1187c48ef914231e5514aace 100644 (file)
@@ -409,6 +409,7 @@ struct tb_regs_hop {
 #define TB_LC_SNK_ALLOCATION_SNK1_SHIFT        4
 #define TB_LC_SNK_ALLOCATION_SNK1_MASK GENMASK(7, 4)
 #define TB_LC_SNK_ALLOCATION_SNK1_CM   0x1
+#define TB_LC_POWER                    0x740
 
 /* Link controller registers */
 #define TB_LC_PORT_ATTR                        0x8d