linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/9] thunderbolt: Add support for USB4
@ 2019-12-17 12:33 Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 1/9] thunderbolt: Make tb_find_port() available to other files Mika Westerberg
                   ` (9 more replies)
  0 siblings, 10 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

Hi all,

USB4 is the public specification of Thunderbolt 3 protocol and can be
downloaded here:

  https://www.usb.org/sites/default/files/USB4%20Specification_1.zip

USB4 is about tunneling different protocols over a single cable (in the
same way as Thunderbolt). The current USB4 spec supports PCIe, Display Port
and USB 3.x, and also software based protocols such as networking between
domains (hosts).

So far PCs have been using firmware based Connection Manager (FW CM, ICM)
and Apple systems have been using software based one (SW CM, ECM). A
Connection Manager is the entity that handles creation of different tunnel
types through the USB4 (and Thunderbolt) fabric. With USB4 the plan is to
have software based Connection Manager everywhere but some early systems
will come with firmware based connection manager.

Current Linux Thunderbolt driver supports both "modes" and can detect which
one to use dynamically.

This series extends the Linux Thunderbolt driver to support USB4 compliant
hosts and devices (this applies to both firmware and software based
connection managers). USB4 Features enabled by this series include:

  - PCIe tunneling
  - Display Port tunneling
  - USB 3.x tunneling
  - P2P networking (implemented in drivers/net/thunderbolt.c)
  - Host and device NVM firmware upgrade

Power management support is still work in progress. It will be submitted
later on once properly tested.

The previous versions of the series can be seen here:

  v1: https://lore.kernel.org/linux-usb/20191023112154.64235-1-mika.westerberg@linux.intel.com/
  RFC: https://lore.kernel.org/lkml/20191001113830.13028-1-mika.westerberg@linux.intel.com/

Changes from v1:

  * Rebased on top of v5.5-rc2.
  * Add a new patch to populate PG field in hotplug ack packet.
  * Rename the networking driver Kconfig symbol to CONFIG_USB4_NET to
    follow the driver itself (CONFIG_USB4).

Changes from the RFC version:

  * Spelled out what are ICM, and SW CM (and ECM)
  * Log warning in tb_switch_add() instead of the caller
  * Use Lukas' suggestion in port walk helper macro and also drop
    tb_switch_for_each_remote_port() and tb_switch_for_each_connected_port()
  * Rework icm.c::add_switch() so that we don't need to pass huge amount of
    parameters to it
  * Add rx/tx versions of link width/speed attributes following convention
    used in USB bus (with the exception that we provide rx_speed and
    tx_speed as well).
  * Spell out DROM and try to clarify what linking in patch [11/25] means.
  * Add a new patch that expands controller name in existing tb_switch_is_xy()
    functions and do the same for tb_switch_is_ar()/tr().
  * Move register name conversion pathes up in the series so that we can
    apply them for v5.5 already.
  * Update changelog of patch [14/25] so that it only mentions Titan Ridge.
  * Rename CONFIG_THUNDERBOLT to CONFIG_USB4, this should be more future
    proof.
  * Check if TMU is enabled in tb_switch_tmu_enable().
  * Use "usb3" and "USB3" in USB 3.x tunneling functionality instead of
    plain "usb".
  * Reword documentation patch [25/25] according to received comments.
  * Introduce icm_firmware_running().

Mika Westerberg (6):
  thunderbolt: Make tb_find_port() available to other files
  thunderbolt: Call tb_eeprom_get_drom_offset() from tb_eeprom_read_n()
  thunderbolt: Populate PG field in hot plug acknowledgment packet
  thunderbolt: Add initial support for USB4
  thunderbolt: Update Kconfig entries to USB4
  thunderbolt: Update documentation with the USB4 information

Rajmohan Mani (3):
  thunderbolt: Make tb_switch_find_cap() available to other files
  thunderbolt: Add support for Time Management Unit
  thunderbolt: Add support for USB 3.x tunnels

 Documentation/admin-guide/thunderbolt.rst |  30 +-
 drivers/Makefile                          |   2 +-
 drivers/net/Kconfig                       |  10 +-
 drivers/net/Makefile                      |   2 +-
 drivers/thunderbolt/Kconfig               |  11 +-
 drivers/thunderbolt/Makefile              |   4 +-
 drivers/thunderbolt/cap.c                 |  11 +-
 drivers/thunderbolt/ctl.c                 |  19 +-
 drivers/thunderbolt/ctl.h                 |   3 +-
 drivers/thunderbolt/eeprom.c              | 137 ++--
 drivers/thunderbolt/nhi.c                 |   3 +
 drivers/thunderbolt/nhi.h                 |   2 +
 drivers/thunderbolt/switch.c              | 439 ++++++++++---
 drivers/thunderbolt/tb.c                  | 227 +++++--
 drivers/thunderbolt/tb.h                  | 101 +++
 drivers/thunderbolt/tb_msgs.h             |   6 +-
 drivers/thunderbolt/tb_regs.h             |  65 +-
 drivers/thunderbolt/tmu.c                 | 383 +++++++++++
 drivers/thunderbolt/tunnel.c              | 169 ++++-
 drivers/thunderbolt/tunnel.h              |   9 +
 drivers/thunderbolt/usb4.c                | 764 ++++++++++++++++++++++
 drivers/thunderbolt/xdomain.c             |   6 +
 22 files changed, 2167 insertions(+), 236 deletions(-)
 create mode 100644 drivers/thunderbolt/tmu.c
 create mode 100644 drivers/thunderbolt/usb4.c

-- 
2.24.0


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v2 1/9] thunderbolt: Make tb_find_port() available to other files
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 2/9] thunderbolt: Call tb_eeprom_get_drom_offset() from tb_eeprom_read_n() Mika Westerberg
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

We will be needing this when adding initial USB4 support so make it
available to other files in the driver as well. We also rename it to
tb_switch_find_port() to follow conventions used in switch.c.

No functional changes.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/switch.c | 18 ++++++++++++++++++
 drivers/thunderbolt/tb.c     | 22 ++--------------------
 drivers/thunderbolt/tb.h     |  2 ++
 3 files changed, 22 insertions(+), 20 deletions(-)

diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index ca86a8e09c77..9c72521cb298 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -2517,6 +2517,24 @@ struct tb_switch *tb_switch_find_by_route(struct tb *tb, u64 route)
 	return NULL;
 }
 
+/**
+ * tb_switch_find_port() - return the first port of @type on @sw or NULL
+ * @sw: Switch to find the port from
+ * @type: Port type to look for
+ */
+struct tb_port *tb_switch_find_port(struct tb_switch *sw,
+				    enum tb_port_type type)
+{
+	struct tb_port *port;
+
+	tb_switch_for_each_port(sw, port) {
+		if (port->config.type == type)
+			return port;
+	}
+
+	return NULL;
+}
+
 void tb_switch_exit(void)
 {
 	ida_destroy(&nvm_ida);
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index ea8727f769d6..54085f67810a 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -338,24 +338,6 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
 	}
 }
 
-/**
- * tb_find_port() - return the first port of @type on @sw or NULL
- * @sw: Switch to find the port from
- * @type: Port type to look for
- */
-static struct tb_port *tb_find_port(struct tb_switch *sw,
-				    enum tb_port_type type)
-{
-	struct tb_port *port;
-
-	tb_switch_for_each_port(sw, port) {
-		if (port->config.type == type)
-			return port;
-	}
-
-	return NULL;
-}
-
 /**
  * tb_find_unused_port() - return the first inactive port on @sw
  * @sw: Switch to find the port on
@@ -586,7 +568,7 @@ static int tb_tunnel_pci(struct tb *tb, struct tb_switch *sw)
 	struct tb_switch *parent_sw;
 	struct tb_tunnel *tunnel;
 
-	up = tb_find_port(sw, TB_TYPE_PCIE_UP);
+	up = tb_switch_find_port(sw, TB_TYPE_PCIE_UP);
 	if (!up)
 		return 0;
 
@@ -624,7 +606,7 @@ static int tb_approve_xdomain_paths(struct tb *tb, struct tb_xdomain *xd)
 
 	sw = tb_to_switch(xd->dev.parent);
 	dst_port = tb_port_at(xd->route, sw);
-	nhi_port = tb_find_port(tb->root_switch, TB_TYPE_NHI);
+	nhi_port = tb_switch_find_port(tb->root_switch, TB_TYPE_NHI);
 
 	mutex_lock(&tb->lock);
 	tunnel = tb_tunnel_alloc_dma(tb, nhi_port, dst_port, xd->transmit_ring,
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index ec851f20c571..ade1c7c77db1 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -533,6 +533,8 @@ void tb_switch_suspend(struct tb_switch *sw);
 int tb_switch_resume(struct tb_switch *sw);
 int tb_switch_reset(struct tb *tb, u64 route);
 void tb_sw_set_unplugged(struct tb_switch *sw);
+struct tb_port *tb_switch_find_port(struct tb_switch *sw,
+				    enum tb_port_type type);
 struct tb_switch *tb_switch_find_by_link_depth(struct tb *tb, u8 link,
 					       u8 depth);
 struct tb_switch *tb_switch_find_by_uuid(struct tb *tb, const uuid_t *uuid);
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 2/9] thunderbolt: Call tb_eeprom_get_drom_offset() from tb_eeprom_read_n()
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 1/9] thunderbolt: Make tb_find_port() available to other files Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet Mika Westerberg
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

We are going to re-use tb_drom_read() for USB4 DROM reading as well.
USB4 has separate router operations for this which does not need the
drom_offset. Therefore we move call to tb_eeprom_get_drom_offset() into
tb_eeprom_read_n() where it is needed.

While there change return -ENOSYS to -ENODEV because the former is only
supposed to be used with system calls (invalid syscall nr).

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/eeprom.c | 88 ++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 45 deletions(-)

diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 8dd7de0cc826..540e0105bcc0 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -130,13 +130,52 @@ static int tb_eeprom_in(struct tb_switch *sw, u8 *val)
 	return 0;
 }
 
+/**
+ * tb_eeprom_get_drom_offset - get drom offset within eeprom
+ */
+static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
+{
+	struct tb_cap_plug_events cap;
+	int res;
+
+	if (!sw->cap_plug_events) {
+		tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n");
+		return -ENODEV;
+	}
+	res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events,
+			     sizeof(cap) / 4);
+	if (res)
+		return res;
+
+	if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
+		tb_sw_warn(sw, "no NVM\n");
+		return -ENODEV;
+	}
+
+	if (cap.drom_offset > 0xffff) {
+		tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n",
+				cap.drom_offset);
+		return -ENXIO;
+	}
+	*offset = cap.drom_offset;
+	return 0;
+}
+
 /**
  * tb_eeprom_read_n - read count bytes from offset into val
  */
 static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
 		size_t count)
 {
+	u16 drom_offset;
 	int i, res;
+
+	res = tb_eeprom_get_drom_offset(sw, &drom_offset);
+	if (res)
+		return res;
+
+	offset += drom_offset;
+
 	res = tb_eeprom_active(sw, true);
 	if (res)
 		return res;
@@ -238,36 +277,6 @@ struct tb_drom_entry_port {
 } __packed;
 
 
-/**
- * tb_eeprom_get_drom_offset - get drom offset within eeprom
- */
-static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
-{
-	struct tb_cap_plug_events cap;
-	int res;
-	if (!sw->cap_plug_events) {
-		tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n");
-		return -ENOSYS;
-	}
-	res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events,
-			     sizeof(cap) / 4);
-	if (res)
-		return res;
-
-	if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) {
-		tb_sw_warn(sw, "no NVM\n");
-		return -ENOSYS;
-	}
-
-	if (cap.drom_offset > 0xffff) {
-		tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n",
-				cap.drom_offset);
-		return -ENXIO;
-	}
-	*offset = cap.drom_offset;
-	return 0;
-}
-
 /**
  * tb_drom_read_uid_only - read uid directly from drom
  *
@@ -277,17 +286,11 @@ static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset)
 int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid)
 {
 	u8 data[9];
-	u16 drom_offset;
 	u8 crc;
-	int res = tb_eeprom_get_drom_offset(sw, &drom_offset);
-	if (res)
-		return res;
-
-	if (drom_offset == 0)
-		return -ENODEV;
+	int res;
 
 	/* read uid */
-	res = tb_eeprom_read_n(sw, drom_offset, data, 9);
+	res = tb_eeprom_read_n(sw, 0, data, 9);
 	if (res)
 		return res;
 
@@ -489,7 +492,6 @@ static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
  */
 int tb_drom_read(struct tb_switch *sw)
 {
-	u16 drom_offset;
 	u16 size;
 	u32 crc;
 	struct tb_drom_header *header;
@@ -517,11 +519,7 @@ int tb_drom_read(struct tb_switch *sw)
 		return 0;
 	}
 
-	res = tb_eeprom_get_drom_offset(sw, &drom_offset);
-	if (res)
-		return res;
-
-	res = tb_eeprom_read_n(sw, drom_offset + 14, (u8 *) &size, 2);
+	res = tb_eeprom_read_n(sw, 14, (u8 *) &size, 2);
 	if (res)
 		return res;
 	size &= 0x3ff;
@@ -535,7 +533,7 @@ int tb_drom_read(struct tb_switch *sw)
 	sw->drom = kzalloc(size, GFP_KERNEL);
 	if (!sw->drom)
 		return -ENOMEM;
-	res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size);
+	res = tb_eeprom_read_n(sw, 0, sw->drom, size);
 	if (res)
 		goto err;
 
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 1/9] thunderbolt: Make tb_find_port() available to other files Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 2/9] thunderbolt: Call tb_eeprom_get_drom_offset() from tb_eeprom_read_n() Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-17 12:46   ` Greg Kroah-Hartman
  2019-12-17 12:47   ` Greg Kroah-Hartman
  2019-12-17 12:33 ` [PATCH v2 4/9] thunderbolt: Add initial support for USB4 Mika Westerberg
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
packet that is sent as response of hot plug/unplug events. This field
tells whether the acknowledgment is for plug or unplug event. This needs
to be set accordingly in order the router to send further hot plug
notifications.

To make it simpler we fill the field unconditionally. Legacy devices do
not look at this field so there should be no problems with them.

While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
log message accordingly. The function is only used to ack plug/unplug
events.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/ctl.c     | 19 +++++++++++++------
 drivers/thunderbolt/ctl.h     |  3 +--
 drivers/thunderbolt/tb.c      |  3 +--
 drivers/thunderbolt/tb_msgs.h |  6 +++++-
 4 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
index d97813e80e5f..f77ceae5c7d7 100644
--- a/drivers/thunderbolt/ctl.c
+++ b/drivers/thunderbolt/ctl.c
@@ -708,19 +708,26 @@ void tb_ctl_stop(struct tb_ctl *ctl)
 /* public interface, commands */
 
 /**
- * tb_cfg_error() - send error packet
+ * tb_cfg_ack_plug() - Ack hot plug/unplug event
+ * @ctl: Control channel to use
+ * @route: Router that originated the event
+ * @port: Port where the hot plug/unplug happened
+ * @unplug: Ack hot plug or unplug
  *
- * Return: Returns 0 on success or an error code on failure.
+ * Call this as response for hot plug/unplug event to ack it.
+ * Returns %0 on success or an error code on failure.
  */
-int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
-		 enum tb_cfg_error error)
+int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug)
 {
 	struct cfg_error_pkg pkg = {
 		.header = tb_cfg_make_header(route),
 		.port = port,
-		.error = error,
+		.error = TB_CFG_ERROR_ACK_PLUG_EVENT,
+		.pg = unplug ? TB_CFG_ERROR_PG_HOT_UNPLUG
+			     : TB_CFG_ERROR_PG_HOT_PLUG,
 	};
-	tb_ctl_dbg(ctl, "resetting error on %llx:%x.\n", route, port);
+	tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%x\n",
+		   unplug ? "un" : "", route, port);
 	return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
 }
 
diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
index 2f1a1e111110..97cb03b38953 100644
--- a/drivers/thunderbolt/ctl.h
+++ b/drivers/thunderbolt/ctl.h
@@ -123,8 +123,7 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
 	return header;
 }
 
-int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
-		 enum tb_cfg_error error);
+int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
 struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
 				  int timeout_msec);
 struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 54085f67810a..e54d0d89a32d 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -768,8 +768,7 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
 
 	route = tb_cfg_get_route(&pkg->header);
 
-	if (tb_cfg_error(tb->ctl, route, pkg->port,
-			 TB_CFG_ERROR_ACK_PLUG_EVENT)) {
+	if (tb_cfg_ack_plug(tb->ctl, route, pkg->port, pkg->unplug)) {
 		tb_warn(tb, "could not ack plug event on %llx:%x\n", route,
 			pkg->port);
 	}
diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h
index 3705057723b6..fc208c567953 100644
--- a/drivers/thunderbolt/tb_msgs.h
+++ b/drivers/thunderbolt/tb_msgs.h
@@ -67,9 +67,13 @@ struct cfg_error_pkg {
 	u32 zero1:4;
 	u32 port:6;
 	u32 zero2:2; /* Both should be zero, still they are different fields. */
-	u32 zero3:16;
+	u32 zero3:14;
+	u32 pg:2;
 } __packed;
 
+#define TB_CFG_ERROR_PG_HOT_PLUG	0x2
+#define TB_CFG_ERROR_PG_HOT_UNPLUG	0x3
+
 /* TB_CFG_PKG_EVENT */
 struct cfg_event_pkg {
 	struct tb_cfg_header header;
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 4/9] thunderbolt: Add initial support for USB4
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (2 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-18  9:34   ` Nicholas Johnson
  2019-12-17 12:33 ` [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4 Mika Westerberg
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

USB4 is the public specification based on Thunderbolt 3 protocol. There
are some differences in register layouts and flows. In addition to PCIe
and DP tunneling, USB4 supports tunneling of USB 3.x. USB4 is also
backward compatible with Thunderbolt 3 (and older generations but the
spec only talks about 3rd generation). USB4 compliant devices can be
identified by checking USB4 version field in router configuration space.

This patch adds initial support for USB4 compliant hosts and devices
which enables following features provided by the existing functionality
in the driver:

  - PCIe tunneling
  - Display Port tunneling
  - Host and device NVM firmware upgrade
  - P2P networking

This brings the USB4 support to the same level that we already have for
Thunderbolt 1, 2 and 3 devices.

Note the spec talks about host and device "routers" but in the driver we
still use term "switch" in most places. Both can be used interchangeably.

Co-developed-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/Makefile  |   2 +-
 drivers/thunderbolt/eeprom.c  |  53 ++-
 drivers/thunderbolt/nhi.c     |   3 +
 drivers/thunderbolt/nhi.h     |   2 +
 drivers/thunderbolt/switch.c  | 382 +++++++++++++-----
 drivers/thunderbolt/tb.c      |  20 +-
 drivers/thunderbolt/tb.h      |  36 ++
 drivers/thunderbolt/tb_regs.h |  36 +-
 drivers/thunderbolt/tunnel.c  |  11 +-
 drivers/thunderbolt/usb4.c    | 724 ++++++++++++++++++++++++++++++++++
 drivers/thunderbolt/xdomain.c |   6 +
 11 files changed, 1158 insertions(+), 117 deletions(-)
 create mode 100644 drivers/thunderbolt/usb4.c

diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 001187c577bf..c0b2fd73dfbd 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 obj-${CONFIG_THUNDERBOLT} := 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
+thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o usb4.o
diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
index 540e0105bcc0..921d164b3f35 100644
--- a/drivers/thunderbolt/eeprom.c
+++ b/drivers/thunderbolt/eeprom.c
@@ -487,6 +487,37 @@ static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
 	return ret;
 }
 
+static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size)
+{
+	int ret;
+
+	ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size));
+	if (ret)
+		return ret;
+
+	/* Size includes CRC8 + UID + CRC32 */
+	*size += 1 + 8 + 4;
+	sw->drom = kzalloc(*size, GFP_KERNEL);
+	if (!sw->drom)
+		return -ENOMEM;
+
+	ret = usb4_switch_drom_read(sw, 0, sw->drom, *size);
+	if (ret) {
+		kfree(sw->drom);
+		sw->drom = NULL;
+	}
+
+	return ret;
+}
+
+static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
+			  size_t count)
+{
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_drom_read(sw, offset, val, count);
+	return tb_eeprom_read_n(sw, offset, val, count);
+}
+
 /**
  * tb_drom_read - copy drom to sw->drom and parse it
  */
@@ -512,14 +543,26 @@ int tb_drom_read(struct tb_switch *sw)
 			goto parse;
 
 		/*
-		 * The root switch contains only a dummy drom (header only,
-		 * no entries). Hardcode the configuration here.
+		 * USB4 hosts may support reading DROM through router
+		 * operations.
 		 */
-		tb_drom_read_uid_only(sw, &sw->uid);
+		if (tb_switch_is_usb4(sw)) {
+			usb4_switch_read_uid(sw, &sw->uid);
+			if (!usb4_copy_host_drom(sw, &size))
+				goto parse;
+		} else {
+			/*
+			 * The root switch contains only a dummy drom
+			 * (header only, no entries). Hardcode the
+			 * configuration here.
+			 */
+			tb_drom_read_uid_only(sw, &sw->uid);
+		}
+
 		return 0;
 	}
 
-	res = tb_eeprom_read_n(sw, 14, (u8 *) &size, 2);
+	res = tb_drom_read_n(sw, 14, (u8 *) &size, 2);
 	if (res)
 		return res;
 	size &= 0x3ff;
@@ -533,7 +576,7 @@ int tb_drom_read(struct tb_switch *sw)
 	sw->drom = kzalloc(size, GFP_KERNEL);
 	if (!sw->drom)
 		return -ENOMEM;
-	res = tb_eeprom_read_n(sw, 0, sw->drom, size);
+	res = tb_drom_read_n(sw, 0, sw->drom, size);
 	if (res)
 		goto err;
 
diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
index 641b21b54460..1be491ecbb45 100644
--- a/drivers/thunderbolt/nhi.c
+++ b/drivers/thunderbolt/nhi.c
@@ -1271,6 +1271,9 @@ static struct pci_device_id nhi_ids[] = {
 	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
 	  .driver_data = (kernel_ulong_t)&icl_nhi_ops },
 
+	/* Any USB4 compliant host */
+	{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
+
 	{ 0,}
 };
 
diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
index b7b973949f8e..5d276ee9b38e 100644
--- a/drivers/thunderbolt/nhi.h
+++ b/drivers/thunderbolt/nhi.h
@@ -74,4 +74,6 @@ extern const struct tb_nhi_ops icl_nhi_ops;
 #define PCI_DEVICE_ID_INTEL_ICL_NHI1			0x8a0d
 #define PCI_DEVICE_ID_INTEL_ICL_NHI0			0x8a17
 
+#define PCI_CLASS_SERIAL_USB_USB4			0x0c0340
+
 #endif
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 9c72521cb298..c1d5cd7e0631 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -163,10 +163,12 @@ static int nvm_validate_and_write(struct tb_switch *sw)
 		image_size -= hdr_size;
 	}
 
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_nvm_write(sw, 0, buf, image_size);
 	return dma_port_flash_write(sw->dma_port, 0, buf, image_size);
 }
 
-static int nvm_authenticate_host(struct tb_switch *sw)
+static int nvm_authenticate_host_dma_port(struct tb_switch *sw)
 {
 	int ret = 0;
 
@@ -206,7 +208,7 @@ static int nvm_authenticate_host(struct tb_switch *sw)
 	return ret;
 }
 
-static int nvm_authenticate_device(struct tb_switch *sw)
+static int nvm_authenticate_device_dma_port(struct tb_switch *sw)
 {
 	int ret, retries = 10;
 
@@ -251,6 +253,78 @@ static int nvm_authenticate_device(struct tb_switch *sw)
 	return -ETIMEDOUT;
 }
 
+static void nvm_authenticate_start_dma_port(struct tb_switch *sw)
+{
+	struct pci_dev *root_port;
+
+	/*
+	 * During host router NVM upgrade we should not allow root port to
+	 * go into D3cold because some root ports cannot trigger PME
+	 * itself. To be on the safe side keep the root port in D0 during
+	 * the whole upgrade process.
+	 */
+	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
+	if (root_port)
+		pm_runtime_get_noresume(&root_port->dev);
+}
+
+static void nvm_authenticate_complete_dma_port(struct tb_switch *sw)
+{
+	struct pci_dev *root_port;
+
+	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
+	if (root_port)
+		pm_runtime_put(&root_port->dev);
+}
+
+static inline bool nvm_readable(struct tb_switch *sw)
+{
+	if (tb_switch_is_usb4(sw)) {
+		/*
+		 * USB4 devices must support NVM operations but it is
+		 * optional for hosts. Therefore we query the NVM sector
+		 * size here and if it is supported assume NVM
+		 * operations are implemented.
+		 */
+		return usb4_switch_nvm_sector_size(sw) > 0;
+	}
+
+	/* Thunderbolt 2 and 3 devices support NVM through DMA port */
+	return !!sw->dma_port;
+}
+
+static inline bool nvm_upgradeable(struct tb_switch *sw)
+{
+	if (sw->no_nvm_upgrade)
+		return false;
+	return nvm_readable(sw);
+}
+
+static inline int nvm_read(struct tb_switch *sw, unsigned int address,
+			   void *buf, size_t size)
+{
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_nvm_read(sw, address, buf, size);
+	return dma_port_flash_read(sw->dma_port, address, buf, size);
+}
+
+static int nvm_authenticate(struct tb_switch *sw)
+{
+	int ret;
+
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_nvm_authenticate(sw);
+
+	if (!tb_route(sw)) {
+		nvm_authenticate_start_dma_port(sw);
+		ret = nvm_authenticate_host_dma_port(sw);
+	} else {
+		ret = nvm_authenticate_device_dma_port(sw);
+	}
+
+	return ret;
+}
+
 static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val,
 			      size_t bytes)
 {
@@ -264,7 +338,7 @@ static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val,
 		goto out;
 	}
 
-	ret = dma_port_flash_read(sw->dma_port, offset, val, bytes);
+	ret = nvm_read(sw, offset, val, bytes);
 	mutex_unlock(&sw->tb->lock);
 
 out:
@@ -341,9 +415,21 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
 	u32 val;
 	int ret;
 
-	if (!sw->dma_port)
+	if (!nvm_readable(sw))
 		return 0;
 
+	/*
+	 * The NVM format of non-Intel hardware is not known so
+	 * currently restrict NVM upgrade for Intel hardware. We may
+	 * relax this in the future when we learn other NVM formats.
+	 */
+	if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) {
+		dev_info(&sw->dev,
+			 "NVM format of vendor %#x is not known, disabling NVM upgrade\n",
+			 sw->config.vendor_id);
+		return 0;
+	}
+
 	nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
 	if (!nvm)
 		return -ENOMEM;
@@ -358,8 +444,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
 	if (!sw->safe_mode) {
 		u32 nvm_size, hdr_size;
 
-		ret = dma_port_flash_read(sw->dma_port, NVM_FLASH_SIZE, &val,
-					  sizeof(val));
+		ret = nvm_read(sw, NVM_FLASH_SIZE, &val, sizeof(val));
 		if (ret)
 			goto err_ida;
 
@@ -367,8 +452,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
 		nvm_size = (SZ_1M << (val & 7)) / 8;
 		nvm_size = (nvm_size - hdr_size) / 2;
 
-		ret = dma_port_flash_read(sw->dma_port, NVM_VERSION, &val,
-					  sizeof(val));
+		ret = nvm_read(sw, NVM_VERSION, &val, sizeof(val));
 		if (ret)
 			goto err_ida;
 
@@ -619,6 +703,24 @@ int tb_port_clear_counter(struct tb_port *port, int counter)
 	return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3);
 }
 
+/**
+ * tb_port_unlock() - Unlock downstream port
+ * @port: Port to unlock
+ *
+ * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the
+ * downstream router accessible for CM.
+ */
+int tb_port_unlock(struct tb_port *port)
+{
+	if (tb_switch_is_icm(port->sw))
+		return 0;
+	if (!tb_port_is_null(port))
+		return -EINVAL;
+	if (tb_switch_is_usb4(port->sw))
+		return usb4_port_unlock(port);
+	return 0;
+}
+
 /**
  * tb_init_port() - initialize a port
  *
@@ -650,6 +752,10 @@ static int tb_init_port(struct tb_port *port)
 			port->cap_phy = cap;
 		else
 			tb_port_WARN(port, "non switch port without a PHY\n");
+
+		cap = tb_port_find_cap(port, TB_PORT_CAP_USB4);
+		if (cap > 0)
+			port->cap_usb4 = cap;
 	} else if (port->port != 0) {
 		cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP);
 		if (cap > 0)
@@ -1088,20 +1194,38 @@ int tb_dp_port_enable(struct tb_port *port, bool enable)
 
 /* switch utility functions */
 
-static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
+static const char *tb_switch_generation_name(const struct tb_switch *sw)
+{
+	switch (sw->generation) {
+	case 1:
+		return "Thunderbolt 1";
+	case 2:
+		return "Thunderbolt 2";
+	case 3:
+		return "Thunderbolt 3";
+	case 4:
+		return "USB4";
+	default:
+		return "Unknown";
+	}
+}
+
+static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
 {
-	tb_dbg(tb, " Switch: %x:%x (Revision: %d, TB Version: %d)\n",
-	       sw->vendor_id, sw->device_id, sw->revision,
-	       sw->thunderbolt_version);
-	tb_dbg(tb, "  Max Port Number: %d\n", sw->max_port_number);
+	const struct tb_regs_switch_header *regs = &sw->config;
+
+	tb_dbg(tb, " %s Switch: %x:%x (Revision: %d, TB Version: %d)\n",
+	       tb_switch_generation_name(sw), regs->vendor_id, regs->device_id,
+	       regs->revision, regs->thunderbolt_version);
+	tb_dbg(tb, "  Max Port Number: %d\n", regs->max_port_number);
 	tb_dbg(tb, "  Config:\n");
 	tb_dbg(tb,
 		"   Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n",
-	       sw->upstream_port_number, sw->depth,
-	       (((u64) sw->route_hi) << 32) | sw->route_lo,
-	       sw->enabled, sw->plug_events_delay);
+	       regs->upstream_port_number, regs->depth,
+	       (((u64) regs->route_hi) << 32) | regs->route_lo,
+	       regs->enabled, regs->plug_events_delay);
 	tb_dbg(tb, "   unknown1: %#x unknown4: %#x\n",
-	       sw->__unknown1, sw->__unknown4);
+	       regs->__unknown1, regs->__unknown4);
 }
 
 /**
@@ -1148,6 +1272,10 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
 	if (res)
 		return res;
 
+	/* Plug events are always enabled in USB4 */
+	if (tb_switch_is_usb4(sw))
+		return 0;
+
 	res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
 	if (res)
 		return res;
@@ -1359,30 +1487,6 @@ static ssize_t lanes_show(struct device *dev, struct device_attribute *attr,
 static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL);
 static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL);
 
-static void nvm_authenticate_start(struct tb_switch *sw)
-{
-	struct pci_dev *root_port;
-
-	/*
-	 * During host router NVM upgrade we should not allow root port to
-	 * go into D3cold because some root ports cannot trigger PME
-	 * itself. To be on the safe side keep the root port in D0 during
-	 * the whole upgrade process.
-	 */
-	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
-	if (root_port)
-		pm_runtime_get_noresume(&root_port->dev);
-}
-
-static void nvm_authenticate_complete(struct tb_switch *sw)
-{
-	struct pci_dev *root_port;
-
-	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
-	if (root_port)
-		pm_runtime_put(&root_port->dev);
-}
-
 static ssize_t nvm_authenticate_show(struct device *dev,
 	struct device_attribute *attr, char *buf)
 {
@@ -1431,17 +1535,7 @@ static ssize_t nvm_authenticate_store(struct device *dev,
 			goto exit_unlock;
 
 		sw->nvm->authenticating = true;
-
-		if (!tb_route(sw)) {
-			/*
-			 * Keep root port from suspending as long as the
-			 * NVM upgrade process is running.
-			 */
-			nvm_authenticate_start(sw);
-			ret = nvm_authenticate_host(sw);
-		} else {
-			ret = nvm_authenticate_device(sw);
-		}
+		ret = nvm_authenticate(sw);
 	}
 
 exit_unlock:
@@ -1556,11 +1650,11 @@ static umode_t switch_attr_is_visible(struct kobject *kobj,
 			return attr->mode;
 		return 0;
 	} else if (attr == &dev_attr_nvm_authenticate.attr) {
-		if (sw->dma_port && !sw->no_nvm_upgrade)
+		if (nvm_upgradeable(sw))
 			return attr->mode;
 		return 0;
 	} else if (attr == &dev_attr_nvm_version.attr) {
-		if (sw->dma_port)
+		if (nvm_readable(sw))
 			return attr->mode;
 		return 0;
 	} else if (attr == &dev_attr_boot.attr) {
@@ -1672,6 +1766,9 @@ static int tb_switch_get_generation(struct tb_switch *sw)
 		return 3;
 
 	default:
+		if (tb_switch_is_usb4(sw))
+			return 4;
+
 		/*
 		 * For unknown switches assume generation to be 1 to be
 		 * on the safe side.
@@ -1682,6 +1779,19 @@ static int tb_switch_get_generation(struct tb_switch *sw)
 	}
 }
 
+static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth)
+{
+	int max_depth;
+
+	if (tb_switch_is_usb4(sw) ||
+	    (sw->tb->root_switch && tb_switch_is_usb4(sw->tb->root_switch)))
+		max_depth = USB4_SWITCH_MAX_DEPTH;
+	else
+		max_depth = TB_SWITCH_MAX_DEPTH;
+
+	return depth > max_depth;
+}
+
 /**
  * tb_switch_alloc() - allocate a switch
  * @tb: Pointer to the owning domain
@@ -1703,10 +1813,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
 	int upstream_port;
 	int i, ret, depth;
 
-	/* Make sure we do not exceed maximum topology limit */
+	/* Unlock the downstream port so we can access the switch below */
+	if (route) {
+		struct tb_switch *parent_sw = tb_to_switch(parent);
+		struct tb_port *down;
+
+		down = tb_port_at(route, parent_sw);
+		tb_port_unlock(down);
+	}
+
 	depth = tb_route_length(route);
-	if (depth > TB_SWITCH_MAX_DEPTH)
-		return ERR_PTR(-EADDRNOTAVAIL);
 
 	upstream_port = tb_cfg_get_upstream_port(tb->ctl, route);
 	if (upstream_port < 0)
@@ -1721,8 +1837,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
 	if (ret)
 		goto err_free_sw_ports;
 
+	sw->generation = tb_switch_get_generation(sw);
+
 	tb_dbg(tb, "current switch config:\n");
-	tb_dump_switch(tb, &sw->config);
+	tb_dump_switch(tb, sw);
 
 	/* configure switch */
 	sw->config.upstream_port_number = upstream_port;
@@ -1731,6 +1849,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
 	sw->config.route_lo = lower_32_bits(route);
 	sw->config.enabled = 0;
 
+	/* Make sure we do not exceed maximum topology limit */
+	if (tb_switch_exceeds_max_depth(sw, depth))
+		return ERR_PTR(-EADDRNOTAVAIL);
+
 	/* initialize ports */
 	sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
 				GFP_KERNEL);
@@ -1745,14 +1867,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
 		sw->ports[i].port = i;
 	}
 
-	sw->generation = tb_switch_get_generation(sw);
-
 	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
-	if (ret < 0) {
-		tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
-		goto err_free_sw_ports;
-	}
-	sw->cap_plug_events = ret;
+	if (ret > 0)
+		sw->cap_plug_events = ret;
 
 	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER);
 	if (ret > 0)
@@ -1823,7 +1940,8 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
  *
  * Call this function before the switch is added to the system. It will
  * upload configuration to the switch and makes it available for the
- * connection manager to use.
+ * connection manager to use. Can be called to the switch again after
+ * resume from low power states to re-initialize it.
  *
  * Return: %0 in case of success and negative errno in case of failure
  */
@@ -1834,21 +1952,50 @@ int tb_switch_configure(struct tb_switch *sw)
 	int ret;
 
 	route = tb_route(sw);
-	tb_dbg(tb, "initializing Switch at %#llx (depth: %d, up port: %d)\n",
-	       route, tb_route_length(route), sw->config.upstream_port_number);
 
-	if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
-		tb_sw_warn(sw, "unknown switch vendor id %#x\n",
-			   sw->config.vendor_id);
+	tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n",
+	       sw->config.enabled ? "restoring " : "initializing", route,
+	       tb_route_length(route), sw->config.upstream_port_number);
 
 	sw->config.enabled = 1;
 
-	/* upload configuration */
-	ret = tb_sw_write(sw, 1 + (u32 *)&sw->config, TB_CFG_SWITCH, 1, 3);
-	if (ret)
-		return ret;
+	if (tb_switch_is_usb4(sw)) {
+		/*
+		 * For USB4 devices, we need to program the CM version
+		 * accordingly so that it knows to expose all the
+		 * additional capabilities.
+		 */
+		sw->config.cmuv = USB4_VERSION_1_0;
+
+		/* Enumerate the switch */
+		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
+				  ROUTER_CS_1, 4);
+		if (ret)
+			return ret;
 
-	ret = tb_lc_configure_link(sw);
+		ret = usb4_switch_setup(sw);
+		if (ret)
+			return ret;
+
+		ret = usb4_switch_configure_link(sw);
+	} else {
+		if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
+			tb_sw_warn(sw, "unknown switch vendor id %#x\n",
+				   sw->config.vendor_id);
+
+		if (!sw->cap_plug_events) {
+			tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
+			return -ENODEV;
+		}
+
+		/* Enumerate the switch */
+		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
+				  ROUTER_CS_1, 3);
+		if (ret)
+			return ret;
+
+		ret = tb_lc_configure_link(sw);
+	}
 	if (ret)
 		return ret;
 
@@ -1857,18 +2004,32 @@ int tb_switch_configure(struct tb_switch *sw)
 
 static int tb_switch_set_uuid(struct tb_switch *sw)
 {
+	bool uid = false;
 	u32 uuid[4];
 	int ret;
 
 	if (sw->uuid)
 		return 0;
 
-	/*
-	 * The newer controllers include fused UUID as part of link
-	 * controller specific registers
-	 */
-	ret = tb_lc_read_uuid(sw, uuid);
-	if (ret) {
+	if (tb_switch_is_usb4(sw)) {
+		ret = usb4_switch_read_uid(sw, &sw->uid);
+		if (ret)
+			return ret;
+		uid = true;
+	} else {
+		/*
+		 * The newer controllers include fused UUID as part of
+		 * link controller specific registers
+		 */
+		ret = tb_lc_read_uuid(sw, uuid);
+		if (ret) {
+			if (ret != -EINVAL)
+				return ret;
+			uid = true;
+		}
+	}
+
+	if (uid) {
 		/*
 		 * ICM generates UUID based on UID and fills the upper
 		 * two words with ones. This is not strictly following
@@ -1935,7 +2096,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
 	nvm_get_auth_status(sw, &status);
 	if (status) {
 		if (!tb_route(sw))
-			nvm_authenticate_complete(sw);
+			nvm_authenticate_complete_dma_port(sw);
 		return 0;
 	}
 
@@ -1950,7 +2111,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
 
 	/* Now we can allow root port to suspend again */
 	if (!tb_route(sw))
-		nvm_authenticate_complete(sw);
+		nvm_authenticate_complete_dma_port(sw);
 
 	if (status) {
 		tb_sw_info(sw, "switch flash authentication failed\n");
@@ -2004,6 +2165,8 @@ static bool tb_switch_lane_bonding_possible(struct tb_switch *sw)
 	if (!up->dual_link_port || !up->dual_link_port->remote)
 		return false;
 
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_lane_bonding_possible(sw);
 	return tb_lc_lane_bonding_possible(sw);
 }
 
@@ -2240,7 +2403,11 @@ void tb_switch_remove(struct tb_switch *sw)
 
 	if (!sw->is_unplugged)
 		tb_plug_events_active(sw, false);
-	tb_lc_unconfigure_link(sw);
+
+	if (tb_switch_is_usb4(sw))
+		usb4_switch_unconfigure_link(sw);
+	else
+		tb_lc_unconfigure_link(sw);
 
 	tb_switch_nvm_remove(sw);
 
@@ -2298,7 +2465,10 @@ int tb_switch_resume(struct tb_switch *sw)
 			return err;
 		}
 
-		err = tb_drom_read_uid_only(sw, &uid);
+		if (tb_switch_is_usb4(sw))
+			err = usb4_switch_read_uid(sw, &uid);
+		else
+			err = tb_drom_read_uid_only(sw, &uid);
 		if (err) {
 			tb_sw_warn(sw, "uid read failed\n");
 			return err;
@@ -2311,16 +2481,7 @@ int tb_switch_resume(struct tb_switch *sw)
 		}
 	}
 
-	/* upload configuration */
-	err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3);
-	if (err)
-		return err;
-
-	err = tb_lc_configure_link(sw);
-	if (err)
-		return err;
-
-	err = tb_plug_events_active(sw, true);
+	err = tb_switch_configure(sw);
 	if (err)
 		return err;
 
@@ -2336,8 +2497,14 @@ int tb_switch_resume(struct tb_switch *sw)
 				tb_sw_set_unplugged(port->remote->sw);
 			else if (port->xdomain)
 				port->xdomain->is_unplugged = true;
-		} else if (tb_port_has_remote(port)) {
-			if (tb_switch_resume(port->remote->sw)) {
+		} else if (tb_port_has_remote(port) || port->xdomain) {
+			/*
+			 * Always unlock the port so the downstream
+			 * switch/domain is accessible.
+			 */
+			if (tb_port_unlock(port))
+				tb_port_warn(port, "failed to unlock port\n");
+			if (port->remote && tb_switch_resume(port->remote->sw)) {
 				tb_port_warn(port,
 					     "lost during suspend, disconnecting\n");
 				tb_sw_set_unplugged(port->remote->sw);
@@ -2361,7 +2528,10 @@ void tb_switch_suspend(struct tb_switch *sw)
 			tb_switch_suspend(port->remote->sw);
 	}
 
-	tb_lc_set_sleep(sw);
+	if (tb_switch_is_usb4(sw))
+		usb4_switch_set_sleep(sw);
+	else
+		tb_lc_set_sleep(sw);
 }
 
 /**
@@ -2374,6 +2544,8 @@ void tb_switch_suspend(struct tb_switch *sw)
  */
 bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
 {
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_query_dp_resource(sw, in);
 	return tb_lc_dp_sink_query(sw, in);
 }
 
@@ -2388,6 +2560,8 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
  */
 int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
 {
+	if (tb_switch_is_usb4(sw))
+		return usb4_switch_alloc_dp_resource(sw, in);
 	return tb_lc_dp_sink_alloc(sw, in);
 }
 
@@ -2401,10 +2575,16 @@ int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
  */
 void tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
 {
-	if (tb_lc_dp_sink_dealloc(sw, in)) {
+	int ret;
+
+	if (tb_switch_is_usb4(sw))
+		ret = usb4_switch_dealloc_dp_resource(sw, in);
+	else
+		ret = tb_lc_dp_sink_dealloc(sw, in);
+
+	if (ret)
 		tb_sw_warn(sw, "failed to de-allocate DP resource for port %d\n",
 			   in->port);
-	}
 }
 
 struct tb_sw_lookup {
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index e54d0d89a32d..6b99dcd1790c 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -365,12 +365,15 @@ static struct tb_port *tb_find_unused_port(struct tb_switch *sw,
 static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
 					 const struct tb_port *port)
 {
+	struct tb_port *down = NULL;
+
 	/*
 	 * To keep plugging devices consistently in the same PCIe
-	 * hierarchy, do mapping here for root switch downstream PCIe
-	 * ports.
+	 * hierarchy, do mapping here for switch downstream PCIe ports.
 	 */
-	if (!tb_route(sw)) {
+	if (tb_switch_is_usb4(sw)) {
+		down = usb4_switch_map_pcie_down(sw, port);
+	} else if (!tb_route(sw)) {
 		int phy_port = tb_phy_port_from_link(port->port);
 		int index;
 
@@ -391,12 +394,17 @@ static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
 		/* Validate the hard-coding */
 		if (WARN_ON(index > sw->config.max_port_number))
 			goto out;
-		if (WARN_ON(!tb_port_is_pcie_down(&sw->ports[index])))
+
+		down = &sw->ports[index];
+	}
+
+	if (down) {
+		if (WARN_ON(!tb_port_is_pcie_down(down)))
 			goto out;
-		if (WARN_ON(tb_pci_port_is_enabled(&sw->ports[index])))
+		if (WARN_ON(tb_pci_port_is_enabled(down)))
 			goto out;
 
-		return &sw->ports[index];
+		return down;
 	}
 
 out:
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index ade1c7c77db1..0158f0e9858c 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -44,6 +44,7 @@ struct tb_switch_nvm {
 
 #define TB_SWITCH_KEY_SIZE		32
 #define TB_SWITCH_MAX_DEPTH		6
+#define USB4_SWITCH_MAX_DEPTH		5
 
 /**
  * struct tb_switch - a thunderbolt switch
@@ -129,6 +130,7 @@ struct tb_switch {
  * @xdomain: Remote host (%NULL if not connected)
  * @cap_phy: Offset, zero if not found
  * @cap_adap: Offset of the adapter specific capability (%0 if not present)
+ * @cap_usb4: Offset to the USB4 port capability (%0 if not present)
  * @port: Port number on switch
  * @disabled: Disabled by eeprom
  * @bonded: true if the port is bonded (two lanes combined as one)
@@ -146,6 +148,7 @@ struct tb_port {
 	struct tb_xdomain *xdomain;
 	int cap_phy;
 	int cap_adap;
+	int cap_usb4;
 	u8 port;
 	bool disabled;
 	bool bonded;
@@ -637,6 +640,17 @@ static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw)
 	}
 }
 
+/**
+ * tb_switch_is_usb4() - Is the switch USB4 compliant
+ * @sw: Switch to check
+ *
+ * Returns true if the @sw is USB4 compliant router, false otherwise.
+ */
+static inline bool tb_switch_is_usb4(const struct tb_switch *sw)
+{
+	return sw->config.thunderbolt_version == USB4_VERSION_1_0;
+}
+
 /**
  * tb_switch_is_icm() - Is the switch handled by ICM firmware
  * @sw: Switch to check
@@ -662,6 +676,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
 int tb_port_add_nfc_credits(struct tb_port *port, int credits);
 int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
 int tb_port_clear_counter(struct tb_port *port, int counter);
+int tb_port_unlock(struct tb_port *port);
 int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
 void tb_port_release_in_hopid(struct tb_port *port, int hopid);
 int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
@@ -736,4 +751,25 @@ void tb_xdomain_remove(struct tb_xdomain *xd);
 struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
 						 u8 depth);
 
+int usb4_switch_setup(struct tb_switch *sw);
+int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid);
+int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
+			  size_t size);
+int usb4_switch_configure_link(struct tb_switch *sw);
+void usb4_switch_unconfigure_link(struct tb_switch *sw);
+bool usb4_switch_lane_bonding_possible(struct tb_switch *sw);
+int usb4_switch_set_sleep(struct tb_switch *sw);
+int usb4_switch_nvm_sector_size(struct tb_switch *sw);
+int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
+			 size_t size);
+int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
+			  const void *buf, size_t size);
+int usb4_switch_nvm_authenticate(struct tb_switch *sw);
+bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
+int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
+int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
+struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
+					  const struct tb_port *port);
+
+int usb4_port_unlock(struct tb_port *port);
 #endif
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index 7ee45b73c7f7..47f73f992412 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -41,6 +41,7 @@ enum tb_port_cap {
 	TB_PORT_CAP_TIME1		= 0x03,
 	TB_PORT_CAP_ADAP		= 0x04,
 	TB_PORT_CAP_VSE			= 0x05,
+	TB_PORT_CAP_USB4		= 0x06,
 };
 
 enum tb_port_state {
@@ -164,10 +165,36 @@ struct tb_regs_switch_header {
 				  * milliseconds. Writing 0x00 is interpreted
 				  * as 255ms.
 				  */
-	u32 __unknown4:16;
+	u32 cmuv:8;
+	u32 __unknown4:8;
 	u32 thunderbolt_version:8;
 } __packed;
 
+/* USB4 version 1.0 */
+#define USB4_VERSION_1_0			0x20
+
+#define ROUTER_CS_1				0x01
+#define ROUTER_CS_4				0x04
+#define ROUTER_CS_5				0x05
+#define ROUTER_CS_5_SLP				BIT(0)
+#define ROUTER_CS_5_C3S				BIT(23)
+#define ROUTER_CS_5_PTO				BIT(24)
+#define ROUTER_CS_5_HCO				BIT(26)
+#define ROUTER_CS_5_CV				BIT(31)
+#define ROUTER_CS_6				0x06
+#define ROUTER_CS_6_SLPR			BIT(0)
+#define ROUTER_CS_6_TNS				BIT(1)
+#define ROUTER_CS_6_HCI				BIT(18)
+#define ROUTER_CS_6_CR				BIT(25)
+#define ROUTER_CS_7				0x07
+#define ROUTER_CS_9				0x09
+#define ROUTER_CS_25				0x19
+#define ROUTER_CS_26				0x1a
+#define ROUTER_CS_26_STATUS_MASK		GENMASK(29, 24)
+#define ROUTER_CS_26_STATUS_SHIFT		24
+#define ROUTER_CS_26_ONS			BIT(30)
+#define ROUTER_CS_26_OV				BIT(31)
+
 enum tb_port_type {
 	TB_TYPE_INACTIVE	= 0x000000,
 	TB_TYPE_PORT		= 0x000001,
@@ -216,6 +243,7 @@ struct tb_regs_port_header {
 #define ADP_CS_4_NFC_BUFFERS_MASK		GENMASK(9, 0)
 #define ADP_CS_4_TOTAL_BUFFERS_MASK		GENMASK(29, 20)
 #define ADP_CS_4_TOTAL_BUFFERS_SHIFT		20
+#define ADP_CS_4_LCK				BIT(31)
 #define ADP_CS_5				0x05
 #define ADP_CS_5_LCA_MASK			GENMASK(28, 22)
 #define ADP_CS_5_LCA_SHIFT			22
@@ -237,6 +265,12 @@ struct tb_regs_port_header {
 #define LANE_ADP_CS_1_CURRENT_WIDTH_MASK	GENMASK(25, 20)
 #define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT	20
 
+/* USB4 port registers */
+#define PORT_CS_18				0x12
+#define PORT_CS_18_BE				BIT(8)
+#define PORT_CS_19				0x13
+#define PORT_CS_19_PC				BIT(3)
+
 /* Display Port adapter registers */
 #define ADP_DP_CS_0				0x00
 #define ADP_DP_CS_0_VIDEO_HOPID_MASK		GENMASK(26, 16)
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 0d3463c4e24a..21d266a76b7d 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -243,6 +243,12 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
 	return tunnel;
 }
 
+static bool tb_dp_is_usb4(const struct tb_switch *sw)
+{
+	/* Titan Ridge DP adapters need the same treatment as USB4 */
+	return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
+}
+
 static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
 {
 	int timeout = 10;
@@ -250,8 +256,7 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
 	int ret;
 
 	/* Both ends need to support this */
-	if (!tb_switch_is_titan_ridge(in->sw) ||
-	    !tb_switch_is_titan_ridge(out->sw))
+	if (!tb_dp_is_usb4(in->sw) || !tb_dp_is_usb4(out->sw))
 		return 0;
 
 	ret = tb_port_read(out, &val, TB_CFG_PORT,
@@ -531,7 +536,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel)
 	u32 val, rate = 0, lanes = 0;
 	int ret;
 
-	if (tb_switch_is_titan_ridge(sw)) {
+	if (tb_dp_is_usb4(sw)) {
 		int timeout = 10;
 
 		/*
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
new file mode 100644
index 000000000000..b84c74346d2b
--- /dev/null
+++ b/drivers/thunderbolt/usb4.c
@@ -0,0 +1,724 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB4 specific functionality
+ *
+ * Copyright (C) 2019, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Rajmohan Mani <rajmohan.mani@intel.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/ktime.h>
+
+#include "tb.h"
+
+#define USB4_DATA_DWORDS		16
+#define USB4_DATA_RETRIES		3
+
+enum usb4_switch_op {
+	USB4_SWITCH_OP_QUERY_DP_RESOURCE = 0x10,
+	USB4_SWITCH_OP_ALLOC_DP_RESOURCE = 0x11,
+	USB4_SWITCH_OP_DEALLOC_DP_RESOURCE = 0x12,
+	USB4_SWITCH_OP_NVM_WRITE = 0x20,
+	USB4_SWITCH_OP_NVM_AUTH = 0x21,
+	USB4_SWITCH_OP_NVM_READ = 0x22,
+	USB4_SWITCH_OP_NVM_SET_OFFSET = 0x23,
+	USB4_SWITCH_OP_DROM_READ = 0x24,
+	USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25,
+};
+
+#define USB4_NVM_READ_OFFSET_MASK	GENMASK(23, 2)
+#define USB4_NVM_READ_OFFSET_SHIFT	2
+#define USB4_NVM_READ_LENGTH_MASK	GENMASK(27, 24)
+#define USB4_NVM_READ_LENGTH_SHIFT	24
+
+#define USB4_NVM_SET_OFFSET_MASK	USB4_NVM_READ_OFFSET_MASK
+#define USB4_NVM_SET_OFFSET_SHIFT	USB4_NVM_READ_OFFSET_SHIFT
+
+#define USB4_DROM_ADDRESS_MASK		GENMASK(14, 2)
+#define USB4_DROM_ADDRESS_SHIFT		2
+#define USB4_DROM_SIZE_MASK		GENMASK(19, 15)
+#define USB4_DROM_SIZE_SHIFT		15
+
+#define USB4_NVM_SECTOR_SIZE_MASK	GENMASK(23, 0)
+
+typedef int (*read_block_fn)(struct tb_switch *, unsigned int, void *, size_t);
+typedef int (*write_block_fn)(struct tb_switch *, const void *, size_t);
+
+static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
+				    u32 value, int timeout_msec)
+{
+	ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
+
+	do {
+		u32 val;
+		int ret;
+
+		ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, offset, 1);
+		if (ret)
+			return ret;
+
+		if ((val & bit) == value)
+			return 0;
+
+		usleep_range(50, 100);
+	} while (ktime_before(ktime_get(), timeout));
+
+	return -ETIMEDOUT;
+}
+
+static int usb4_switch_op_read_data(struct tb_switch *sw, void *data,
+				    size_t dwords)
+{
+	if (dwords > USB4_DATA_DWORDS)
+		return -EINVAL;
+
+	return tb_sw_read(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords);
+}
+
+static int usb4_switch_op_write_data(struct tb_switch *sw, const void *data,
+				     size_t dwords)
+{
+	if (dwords > USB4_DATA_DWORDS)
+		return -EINVAL;
+
+	return tb_sw_write(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords);
+}
+
+static int usb4_switch_op_read_metadata(struct tb_switch *sw, u32 *metadata)
+{
+	return tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
+}
+
+static int usb4_switch_op_write_metadata(struct tb_switch *sw, u32 metadata)
+{
+	return tb_sw_write(sw, &metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
+}
+
+static int usb4_switch_do_read_data(struct tb_switch *sw, u16 address,
+	void *buf, size_t size, read_block_fn read_block)
+{
+	unsigned int retries = USB4_DATA_RETRIES;
+	unsigned int offset;
+
+	offset = address & 3;
+	address = address & ~3;
+
+	do {
+		size_t nbytes = min_t(size_t, size, USB4_DATA_DWORDS * 4);
+		unsigned int dwaddress, dwords;
+		u8 data[USB4_DATA_DWORDS * 4];
+		int ret;
+
+		dwaddress = address / 4;
+		dwords = ALIGN(nbytes, 4) / 4;
+
+		ret = read_block(sw, dwaddress, data, dwords);
+		if (ret) {
+			if (ret == -ETIMEDOUT) {
+				if (retries--)
+					continue;
+				ret = -EIO;
+			}
+			return ret;
+		}
+
+		memcpy(buf, data + offset, nbytes);
+
+		size -= nbytes;
+		address += nbytes;
+		buf += nbytes;
+	} while (size > 0);
+
+	return 0;
+}
+
+static int usb4_switch_do_write_data(struct tb_switch *sw, u16 address,
+	const void *buf, size_t size, write_block_fn write_next_block)
+{
+	unsigned int retries = USB4_DATA_RETRIES;
+	unsigned int offset;
+
+	offset = address & 3;
+	address = address & ~3;
+
+	do {
+		u32 nbytes = min_t(u32, size, USB4_DATA_DWORDS * 4);
+		u8 data[USB4_DATA_DWORDS * 4];
+		int ret;
+
+		memcpy(data + offset, buf, nbytes);
+
+		ret = write_next_block(sw, data, nbytes / 4);
+		if (ret) {
+			if (ret == -ETIMEDOUT) {
+				if (retries--)
+					continue;
+				ret = -EIO;
+			}
+			return ret;
+		}
+
+		size -= nbytes;
+		address += nbytes;
+		buf += nbytes;
+	} while (size > 0);
+
+	return 0;
+}
+
+static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
+{
+	u32 val;
+	int ret;
+
+	val = opcode | ROUTER_CS_26_OV;
+	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_wait_for_bit(sw, ROUTER_CS_26, ROUTER_CS_26_OV, 0, 500);
+	if (ret)
+		return ret;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
+	if (val & ROUTER_CS_26_ONS)
+		return -EOPNOTSUPP;
+
+	*status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT;
+	return 0;
+}
+
+/**
+ * usb4_switch_setup() - Additional setup for USB4 device
+ * @sw: USB4 router to setup
+ *
+ * USB4 routers need additional settings in order to enable all the
+ * tunneling. This function enables USB and PCIe tunneling if it can be
+ * enabled (e.g the parent switch also supports them). If USB tunneling
+ * is not available for some reason (like that there is Thunderbolt 3
+ * switch upstream) then the internal xHCI controller is enabled
+ * instead.
+ */
+int usb4_switch_setup(struct tb_switch *sw)
+{
+	struct tb_switch *parent;
+	bool tbt3, xhci;
+	u32 val = 0;
+	int ret;
+
+	if (!tb_route(sw))
+		return 0;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1);
+	if (ret)
+		return ret;
+
+	xhci = val & ROUTER_CS_6_HCI;
+	tbt3 = !(val & ROUTER_CS_6_TNS);
+
+	tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n",
+		  tbt3 ? "yes" : "no", xhci ? "yes" : "no");
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+	if (ret)
+		return ret;
+
+	parent = tb_switch_parent(sw);
+
+	/* Only enable PCIe tunneling if the parent router supports it */
+	if (tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) {
+		val |= ROUTER_CS_5_PTO;
+		/* xHCI can be enabled if PCIe tunneling is supported */
+		if (xhci & ROUTER_CS_6_HCI)
+			val |= ROUTER_CS_5_HCO;
+	}
+
+	/* TBT3 supported by the CM */
+	val |= ROUTER_CS_5_C3S;
+	/* Tunneling configuration is ready now */
+	val |= ROUTER_CS_5_CV;
+
+	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+	if (ret)
+		return ret;
+
+	return usb4_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR,
+					ROUTER_CS_6_CR, 50);
+}
+
+/**
+ * usb4_switch_read_uid() - Read UID from USB4 router
+ * @sw: USB4 router
+ *
+ * Reads 64-bit UID from USB4 router config space.
+ */
+int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid)
+{
+	return tb_sw_read(sw, uid, TB_CFG_SWITCH, ROUTER_CS_7, 2);
+}
+
+static int usb4_switch_drom_read_block(struct tb_switch *sw,
+				       unsigned int dwaddress, void *buf,
+				       size_t dwords)
+{
+	u8 status = 0;
+	u32 metadata;
+	int ret;
+
+	metadata = (dwords << USB4_DROM_SIZE_SHIFT) & USB4_DROM_SIZE_MASK;
+	metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) &
+		USB4_DROM_ADDRESS_MASK;
+
+	ret = usb4_switch_op_write_metadata(sw, metadata);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_DROM_READ, &status);
+	if (ret)
+		return ret;
+
+	if (status)
+		return -EIO;
+
+	return usb4_switch_op_read_data(sw, buf, dwords);
+}
+
+/**
+ * usb4_switch_drom_read() - Read arbitrary bytes from USB4 router DROM
+ * @sw: USB4 router
+ *
+ * Uses USB4 router operations to read router DROM. For devices this
+ * should always work but for hosts it may return %-EOPNOTSUPP in which
+ * case the host router does not have DROM.
+ */
+int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
+			  size_t size)
+{
+	return usb4_switch_do_read_data(sw, address, buf, size,
+					usb4_switch_drom_read_block);
+}
+
+static int usb4_set_port_configured(struct tb_port *port, bool configured)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_port_read(port, &val, TB_CFG_PORT,
+			   port->cap_usb4 + PORT_CS_19, 1);
+	if (ret)
+		return ret;
+
+	if (configured)
+		val |= PORT_CS_19_PC;
+	else
+		val &= ~PORT_CS_19_PC;
+
+	return tb_port_write(port, &val, TB_CFG_PORT,
+			     port->cap_usb4 + PORT_CS_19, 1);
+}
+
+/**
+ * usb4_switch_configure_link() - Set upstream USB4 link configured
+ * @sw: USB4 router
+ *
+ * Sets the upstream USB4 link to be configured for power management
+ * purposes.
+ */
+int usb4_switch_configure_link(struct tb_switch *sw)
+{
+	struct tb_port *up;
+
+	if (!tb_route(sw))
+		return 0;
+
+	up = tb_upstream_port(sw);
+	return usb4_set_port_configured(up, true);
+}
+
+/**
+ * usb4_switch_unconfigure_link() - Un-set upstream USB4 link configuration
+ * @sw: USB4 router
+ *
+ * Reverse of usb4_switch_configure_link().
+ */
+void usb4_switch_unconfigure_link(struct tb_switch *sw)
+{
+	struct tb_port *up;
+
+	if (sw->is_unplugged || !tb_route(sw))
+		return;
+
+	up = tb_upstream_port(sw);
+	usb4_set_port_configured(up, false);
+}
+
+/**
+ * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding
+ * @sw: USB4 router
+ *
+ * Checks whether conditions are met so that lane bonding can be
+ * established with the upstream router. Call only for device routers.
+ */
+bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
+{
+	struct tb_port *up;
+	int ret;
+	u32 val;
+
+	up = tb_upstream_port(sw);
+	ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1);
+	if (ret)
+		return false;
+
+	return !!(val & PORT_CS_18_BE);
+}
+
+/**
+ * usb4_switch_set_sleep() - Prepare the router to enter sleep
+ * @sw: USB4 router
+ *
+ * Enables wakes and sets sleep bit for the router. Returns when the
+ * router sleep ready bit has been asserted.
+ */
+int usb4_switch_set_sleep(struct tb_switch *sw)
+{
+	int ret;
+	u32 val;
+
+	/* Set sleep bit and wait for sleep ready to be asserted */
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+	if (ret)
+		return ret;
+
+	val |= ROUTER_CS_5_SLP;
+
+	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
+	if (ret)
+		return ret;
+
+	return usb4_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_SLPR,
+					ROUTER_CS_6_SLPR, 500);
+}
+
+/**
+ * usb4_switch_nvm_sector_size() - Return router NVM sector size
+ * @sw: USB4 router
+ *
+ * If the router supports NVM operations this function returns the NVM
+ * sector size in bytes. If NVM operations are not supported returns
+ * %-EOPNOTSUPP.
+ */
+int usb4_switch_nvm_sector_size(struct tb_switch *sw)
+{
+	u32 metadata;
+	u8 status;
+	int ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &status);
+	if (ret)
+		return ret;
+
+	if (status)
+		return status == 0x2 ? -EOPNOTSUPP : -EIO;
+
+	ret = usb4_switch_op_read_metadata(sw, &metadata);
+	if (ret)
+		return ret;
+
+	return metadata & USB4_NVM_SECTOR_SIZE_MASK;
+}
+
+static int usb4_switch_nvm_read_block(struct tb_switch *sw,
+	unsigned int dwaddress, void *buf, size_t dwords)
+{
+	u8 status = 0;
+	u32 metadata;
+	int ret;
+
+	metadata = (dwords << USB4_NVM_READ_LENGTH_SHIFT) &
+		   USB4_NVM_READ_LENGTH_MASK;
+	metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) &
+		   USB4_NVM_READ_OFFSET_MASK;
+
+	ret = usb4_switch_op_write_metadata(sw, metadata);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_READ, &status);
+	if (ret)
+		return ret;
+
+	if (status)
+		return -EIO;
+
+	return usb4_switch_op_read_data(sw, buf, dwords);
+}
+
+/**
+ * usb4_switch_nvm_read() - Read arbitrary bytes from router NVM
+ * @sw: USB4 router
+ * @address: Starting address in bytes
+ * @buf: Read data is placed here
+ * @size: How many bytes to read
+ *
+ * Reads NVM contents of the router. If NVM is not supported returns
+ * %-EOPNOTSUPP.
+ */
+int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
+			 size_t size)
+{
+	return usb4_switch_do_read_data(sw, address, buf, size,
+					usb4_switch_nvm_read_block);
+}
+
+static int usb4_switch_nvm_set_offset(struct tb_switch *sw,
+				      unsigned int address)
+{
+	u32 metadata, dwaddress;
+	u8 status = 0;
+	int ret;
+
+	dwaddress = address / 4;
+	metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) &
+		   USB4_NVM_SET_OFFSET_MASK;
+
+	ret = usb4_switch_op_write_metadata(sw, metadata);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &status);
+	if (ret)
+		return ret;
+
+	return status ? -EIO : 0;
+}
+
+static int usb4_switch_nvm_write_next_block(struct tb_switch *sw,
+					    const void *buf, size_t dwords)
+{
+	u8 status;
+	int ret;
+
+	ret = usb4_switch_op_write_data(sw, buf, dwords);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_WRITE, &status);
+	if (ret)
+		return ret;
+
+	return status ? -EIO : 0;
+}
+
+/**
+ * usb4_switch_nvm_write() - Write to the router NVM
+ * @sw: USB4 router
+ * @address: Start address where to write in bytes
+ * @buf: Pointer to the data to write
+ * @size: Size of @buf in bytes
+ *
+ * Writes @buf to the router NVM using USB4 router operations. If NVM
+ * write is not supported returns %-EOPNOTSUPP.
+ */
+int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
+			  const void *buf, size_t size)
+{
+	int ret;
+
+	ret = usb4_switch_nvm_set_offset(sw, address);
+	if (ret)
+		return ret;
+
+	return usb4_switch_do_write_data(sw, address, buf, size,
+					 usb4_switch_nvm_write_next_block);
+}
+
+/**
+ * usb4_switch_nvm_authenticate() - Authenticate new NVM
+ * @sw: USB4 router
+ *
+ * After the new NVM has been written via usb4_switch_nvm_write(), this
+ * function triggers NVM authentication process. If the authentication
+ * is successful the router is power cycled and the new NVM starts
+ * running. In case of failure returns negative errno.
+ */
+int usb4_switch_nvm_authenticate(struct tb_switch *sw)
+{
+	u8 status = 0;
+	int ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, &status);
+	if (ret)
+		return ret;
+
+	switch (status) {
+	case 0x0:
+		tb_sw_dbg(sw, "NVM authentication successful\n");
+		return 0;
+	case 0x1:
+		return -EINVAL;
+	case 0x2:
+		return -EAGAIN;
+	case 0x3:
+		return -EOPNOTSUPP;
+	default:
+		return -EIO;
+	}
+}
+
+/**
+ * usb4_switch_query_dp_resource() - Query availability of DP IN resource
+ * @sw: USB4 router
+ * @in: DP IN adapter
+ *
+ * For DP tunneling this function can be used to query availability of
+ * DP IN resource. Returns true if the resource is available for DP
+ * tunneling, false otherwise.
+ */
+bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
+{
+	u8 status;
+	int ret;
+
+	ret = usb4_switch_op_write_metadata(sw, in->port);
+	if (ret)
+		return false;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &status);
+	/*
+	 * If DP resource allocation is not supported assume it is
+	 * always available.
+	 */
+	if (ret == -EOPNOTSUPP)
+		return true;
+	else if (ret)
+		return false;
+
+	return !status;
+}
+
+/**
+ * usb4_switch_alloc_dp_resource() - Allocate DP IN resource
+ * @sw: USB4 router
+ * @in: DP IN adapter
+ *
+ * Allocates DP IN resource for DP tunneling using USB4 router
+ * operations. If the resource was allocated returns %0. Otherwise
+ * returns negative errno, in particular %-EBUSY if the resource is
+ * already allocated.
+ */
+int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
+{
+	u8 status;
+	int ret;
+
+	ret = usb4_switch_op_write_metadata(sw, in->port);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &status);
+	if (ret == -EOPNOTSUPP)
+		return 0;
+	else if (ret)
+		return ret;
+
+	return status ? -EBUSY : 0;
+}
+
+/**
+ * usb4_switch_dealloc_dp_resource() - Releases allocated DP IN resource
+ * @sw: USB4 router
+ * @in: DP IN adapter
+ *
+ * Releases the previously allocated DP IN resource.
+ */
+int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
+{
+	u8 status;
+	int ret;
+
+	ret = usb4_switch_op_write_metadata(sw, in->port);
+	if (ret)
+		return ret;
+
+	ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &status);
+	if (ret == -EOPNOTSUPP)
+		return 0;
+	else if (ret)
+		return ret;
+
+	return status ? -EIO : 0;
+}
+
+static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port)
+{
+	struct tb_port *p;
+	int usb4_idx = 0;
+
+	/* Assume port is primary */
+	tb_switch_for_each_port(sw, p) {
+		if (!tb_port_is_null(p))
+			continue;
+		if (tb_is_upstream_port(p))
+			continue;
+		if (!p->link_nr) {
+			if (p == port)
+				break;
+			usb4_idx++;
+		}
+	}
+
+	return usb4_idx;
+}
+
+/**
+ * usb4_switch_map_pcie_down() - Map USB4 port to a PCIe downstream adapter
+ * @sw: USB4 router
+ * @port: USB4 port
+ *
+ * USB4 routers have direct mapping between USB4 ports and PCIe
+ * downstream adapters where the PCIe topology is extended. This
+ * function returns the corresponding downstream PCIe adapter or %NULL
+ * if no such mapping was possible.
+ */
+struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
+					  const struct tb_port *port)
+{
+	int usb4_idx = usb4_port_idx(sw, port);
+	struct tb_port *p;
+	int pcie_idx = 0;
+
+	/* Find PCIe down port matching usb4_port */
+	tb_switch_for_each_port(sw, p) {
+		if (!tb_port_is_pcie_down(p))
+			continue;
+
+		if (pcie_idx == usb4_idx && !tb_pci_port_is_enabled(p))
+			return p;
+
+		pcie_idx++;
+	}
+
+	return NULL;
+}
+
+/**
+ * usb4_port_unlock() - Unlock USB4 downstream port
+ * @port: USB4 port to unlock
+ *
+ * Unlocks USB4 downstream port so that the connection manager can
+ * access the router below this port.
+ */
+int usb4_port_unlock(struct tb_port *port)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
+	if (ret)
+		return ret;
+
+	val &= ~ADP_CS_4_LCK;
+	return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
+}
diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
index 880d784398a3..053f918e00e8 100644
--- a/drivers/thunderbolt/xdomain.c
+++ b/drivers/thunderbolt/xdomain.c
@@ -1220,7 +1220,13 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
 				    u64 route, const uuid_t *local_uuid,
 				    const uuid_t *remote_uuid)
 {
+	struct tb_switch *parent_sw = tb_to_switch(parent);
 	struct tb_xdomain *xd;
+	struct tb_port *down;
+
+	/* Make sure the downstream domain is accessible */
+	down = tb_port_at(route, parent_sw);
+	tb_port_unlock(down);
 
 	xd = kzalloc(sizeof(*xd), GFP_KERNEL);
 	if (!xd)
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (3 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 4/9] thunderbolt: Add initial support for USB4 Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-18  9:36   ` Nicholas Johnson
  2019-12-17 12:33 ` [PATCH v2 6/9] thunderbolt: Make tb_switch_find_cap() available to other files Mika Westerberg
                   ` (4 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

Since the driver now supports USB4 which is the standard going forward,
update the Kconfig entry to mention this and rename the entry from
CONFIG_THUNDERBOLT to CONFIG_USB4 instead to help people to find the
correct option if they want to enable USB4.

Also do the same for Thunderbolt network driver.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Cc: David S. Miller <davem@davemloft.net>
---
 drivers/Makefile             |  2 +-
 drivers/net/Kconfig          | 10 +++++-----
 drivers/net/Makefile         |  2 +-
 drivers/thunderbolt/Kconfig  | 11 ++++++-----
 drivers/thunderbolt/Makefile |  2 +-
 5 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/drivers/Makefile b/drivers/Makefile
index aaef17cc6512..31cf17dee252 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -171,7 +171,7 @@ obj-$(CONFIG_POWERCAP)		+= powercap/
 obj-$(CONFIG_MCB)		+= mcb/
 obj-$(CONFIG_PERF_EVENTS)	+= perf/
 obj-$(CONFIG_RAS)		+= ras/
-obj-$(CONFIG_THUNDERBOLT)	+= thunderbolt/
+obj-$(CONFIG_USB4)		+= thunderbolt/
 obj-$(CONFIG_CORESIGHT)		+= hwtracing/coresight/
 obj-y				+= hwtracing/intel_th/
 obj-$(CONFIG_STM)		+= hwtracing/stm/
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index d02f12a5254e..d1c84d47779d 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -489,12 +489,12 @@ config FUJITSU_ES
 	  This driver provides support for Extended Socket network device
 	  on Extended Partitioning of FUJITSU PRIMEQUEST 2000 E2 series.
 
-config THUNDERBOLT_NET
-	tristate "Networking over Thunderbolt cable"
-	depends on THUNDERBOLT && INET
+config USB4_NET
+	tristate "Networking over USB4 and Thunderbolt cables"
+	depends on USB4 && INET
 	help
-	  Select this if you want to create network between two
-	  computers over a Thunderbolt cable. The driver supports Apple
+	  Select this if you want to create network between two computers
+	  over a USB4 and Thunderbolt cables. The driver supports Apple
 	  ThunderboltIP protocol and allows communication with any host
 	  supporting the same protocol including Windows and macOS.
 
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 0d3ba056cda3..29e83e9f545e 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -76,6 +76,6 @@ obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
 obj-$(CONFIG_FUJITSU_ES) += fjes/
 
 thunderbolt-net-y += thunderbolt.o
-obj-$(CONFIG_THUNDERBOLT_NET) += thunderbolt-net.o
+obj-$(CONFIG_USB4_NET) += thunderbolt-net.o
 obj-$(CONFIG_NETDEVSIM) += netdevsim/
 obj-$(CONFIG_NET_FAILOVER) += net_failover.o
diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
index fd9adca898ff..1eb757e8df3b 100644
--- a/drivers/thunderbolt/Kconfig
+++ b/drivers/thunderbolt/Kconfig
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0-only
-menuconfig THUNDERBOLT
-	tristate "Thunderbolt support"
+menuconfig USB4
+	tristate "Unified support for USB4 and Thunderbolt"
 	depends on PCI
 	depends on X86 || COMPILE_TEST
 	select APPLE_PROPERTIES if EFI_STUB && X86
@@ -9,9 +9,10 @@ menuconfig THUNDERBOLT
 	select CRYPTO_HASH
 	select NVMEM
 	help
-	  Thunderbolt Controller driver. This driver is required if you
-	  want to hotplug Thunderbolt devices on Apple hardware or on PCs
-	  with Intel Falcon Ridge or newer.
+	  USB4 and Thunderbolt driver. USB4 is the public speficiation
+	  based on Thunderbolt 3 protocol. This driver is required if
+	  you want to hotplug Thunderbolt and USB4 compliant devices on
+	  Apple hardware or on PCs with Intel Falcon Ridge or newer.
 
 	  To compile this driver a module, choose M here. The module will be
 	  called thunderbolt.
diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index c0b2fd73dfbd..102e9529ee66 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
-obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
+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 usb4.o
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 6/9] thunderbolt: Make tb_switch_find_cap() available to other files
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (4 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4 Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit Mika Westerberg
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

From: Rajmohan Mani <rajmohan.mani@intel.com>

We need to find switch capabilities in order to implement TMU support so
make it available to other files as well.

Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/cap.c | 11 ++++++++++-
 drivers/thunderbolt/tb.h  |  1 +
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c
index fdd77bb4628d..19db6cdc5b70 100644
--- a/drivers/thunderbolt/cap.c
+++ b/drivers/thunderbolt/cap.c
@@ -113,7 +113,16 @@ int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
 	return ret;
 }
 
-static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
+/**
+ * tb_switch_find_cap() - Find switch capability
+ * @sw Switch to find the capability for
+ * @cap: Capability to look
+ *
+ * Returns offset to start of capability or %-ENOENT if no such
+ * capability was found. Negative errno is returned if there was an
+ * error.
+ */
+int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
 {
 	int offset = sw->config.first_cap_offset;
 
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 0158f0e9858c..28dd0e0b1579 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -685,6 +685,7 @@ struct tb_port *tb_next_port_on_path(struct tb_port *start, struct tb_port *end,
 				     struct tb_port *prev);
 
 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
+int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap);
 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
 bool tb_port_is_enabled(struct tb_port *port);
 
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (5 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 6/9] thunderbolt: Make tb_switch_find_cap() available to other files Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-18  9:38   ` Nicholas Johnson
  2019-12-17 12:33 ` [PATCH v2 8/9] thunderbolt: Add support for USB 3.x tunnels Mika Westerberg
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

From: Rajmohan Mani <rajmohan.mani@intel.com>

Time Management Unit (TMU) is included in each USB4 router. It is used
to synchronize time across the USB4 fabric. By default when USB4 router
is plugged to the domain, its TMU is turned off. This differs from
Thunderbolt (1, 2 and 3) devices whose TMU is by default configured to
bi-directional HiFi mode. Since time synchronization is needed for
proper Display Port tunneling this means we need to configure the TMU on
USB4 compliant devices.

The USB4 spec allows some flexibility on how the TMU can be configured.
This makes it possible to enable link power management states (CLx) in
certain topologies, where for example DP tunneling is not used. TMU can
also be re-configured dynamicaly depending on types of tunnels created
over the USB4 fabric.

In this patch we simply configure the TMU to be in bi-directional HiFi
mode. This way we can tunnel any kind of traffic without need to perform
complex steps to re-configure the domain dynamically. We can add more
fine-grained TMU configuration later on when we start enabling CLx
states.

Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Co-developed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/Makefile  |   2 +-
 drivers/thunderbolt/switch.c  |   4 +
 drivers/thunderbolt/tb.c      |  28 +++
 drivers/thunderbolt/tb.h      |  47 +++++
 drivers/thunderbolt/tb_regs.h |  20 ++
 drivers/thunderbolt/tmu.c     | 383 ++++++++++++++++++++++++++++++++++
 6 files changed, 483 insertions(+), 1 deletion(-)
 create mode 100644 drivers/thunderbolt/tmu.c

diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
index 102e9529ee66..eae28dd45250 100644
--- a/drivers/thunderbolt/Makefile
+++ b/drivers/thunderbolt/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 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 usb4.o
+thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index c1d5cd7e0631..82f45780dc81 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -2338,6 +2338,10 @@ int tb_switch_add(struct tb_switch *sw)
 		ret = tb_switch_update_link_attributes(sw);
 		if (ret)
 			return ret;
+
+		ret = tb_switch_tmu_init(sw);
+		if (ret)
+			return ret;
 	}
 
 	ret = device_add(&sw->dev);
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index 6b99dcd1790c..e446624dd3e7 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -158,6 +158,25 @@ static void tb_scan_xdomain(struct tb_port *port)
 	}
 }
 
+static int tb_enable_tmu(struct tb_switch *sw)
+{
+	int ret;
+
+	/* If it is already enabled in correct mode, don't touch it */
+	if (tb_switch_tmu_is_enabled(sw))
+		return 0;
+
+	ret = tb_switch_tmu_disable(sw);
+	if (ret)
+		return ret;
+
+	ret = tb_switch_tmu_post_time(sw);
+	if (ret)
+		return ret;
+
+	return tb_switch_tmu_enable(sw);
+}
+
 static void tb_scan_port(struct tb_port *port);
 
 /**
@@ -257,6 +276,9 @@ static void tb_scan_port(struct tb_port *port)
 	if (tb_switch_lane_bonding_enable(sw))
 		tb_sw_warn(sw, "failed to enable lane bonding\n");
 
+	if (tb_enable_tmu(sw))
+		tb_sw_warn(sw, "failed to enable TMU\n");
+
 	tb_scan_switch(sw);
 }
 
@@ -709,6 +731,7 @@ static void tb_handle_hotplug(struct work_struct *work)
 			tb_sw_set_unplugged(port->remote->sw);
 			tb_free_invalid_tunnels(tb);
 			tb_remove_dp_resources(port->remote->sw);
+			tb_switch_tmu_disable(port->remote->sw);
 			tb_switch_lane_bonding_disable(port->remote->sw);
 			tb_switch_remove(port->remote->sw);
 			port->remote = NULL;
@@ -855,6 +878,8 @@ static int tb_start(struct tb *tb)
 		return ret;
 	}
 
+	/* Enable TMU if it is off */
+	tb_switch_tmu_enable(tb->root_switch);
 	/* Full scan to discover devices added before the driver was loaded. */
 	tb_scan_switch(tb->root_switch);
 	/* Find out tunnels created by the boot firmware */
@@ -886,6 +911,9 @@ static void tb_restore_children(struct tb_switch *sw)
 {
 	struct tb_port *port;
 
+	if (tb_enable_tmu(sw))
+		tb_sw_warn(sw, "failed to restore TMU configuration\n");
+
 	tb_switch_for_each_port(sw, port) {
 		if (!tb_port_has_remote(port))
 			continue;
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 28dd0e0b1579..63ffb3cbdefe 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -46,6 +46,38 @@ struct tb_switch_nvm {
 #define TB_SWITCH_MAX_DEPTH		6
 #define USB4_SWITCH_MAX_DEPTH		5
 
+/**
+ * enum tb_switch_tmu_rate - TMU refresh rate
+ * @TB_SWITCH_TMU_RATE_OFF: %0 (Disable Time Sync handshake)
+ * @TB_SWITCH_TMU_RATE_HIFI: %16 us time interval between successive
+ *			     transmission of the Delay Request TSNOS
+ *			     (Time Sync Notification Ordered Set) on a Link
+ * @TB_SWITCH_TMU_RATE_NORMAL: %1 ms time interval between successive
+ *			       transmission of the Delay Request TSNOS on
+ *			       a Link
+ */
+enum tb_switch_tmu_rate {
+	TB_SWITCH_TMU_RATE_OFF = 0,
+	TB_SWITCH_TMU_RATE_HIFI = 16,
+	TB_SWITCH_TMU_RATE_NORMAL = 1000,
+};
+
+/**
+ * struct tb_switch_tmu - Structure holding switch TMU configuration
+ * @cap: Offset to the TMU capability (%0 if not found)
+ * @has_ucap: Does the switch support uni-directional mode
+ * @rate: TMU refresh rate related to upstream switch. In case of root
+ *	  switch this holds the domain rate.
+ * @unidirectional: Is the TMU in uni-directional or bi-directional mode
+ *		    related to upstream switch. Don't case for root switch.
+ */
+struct tb_switch_tmu {
+	int cap;
+	bool has_ucap;
+	enum tb_switch_tmu_rate rate;
+	bool unidirectional;
+};
+
 /**
  * struct tb_switch - a thunderbolt switch
  * @dev: Device for the switch
@@ -55,6 +87,7 @@ struct tb_switch_nvm {
  *	      mailbox this will hold the pointer to that (%NULL
  *	      otherwise). If set it also means the switch has
  *	      upgradeable NVM.
+ * @tmu: The switch TMU configuration
  * @tb: Pointer to the domain the switch belongs to
  * @uid: Unique ID of the switch
  * @uuid: UUID of the switch (or %NULL if not supported)
@@ -93,6 +126,7 @@ struct tb_switch {
 	struct tb_regs_switch_header config;
 	struct tb_port *ports;
 	struct tb_dma_port *dma_port;
+	struct tb_switch_tmu tmu;
 	struct tb *tb;
 	u64 uid;
 	uuid_t *uuid;
@@ -129,6 +163,7 @@ struct tb_switch {
  * @remote: Remote port (%NULL if not connected)
  * @xdomain: Remote host (%NULL if not connected)
  * @cap_phy: Offset, zero if not found
+ * @cap_tmu: Offset of the adapter specific TMU capability (%0 if not present)
  * @cap_adap: Offset of the adapter specific capability (%0 if not present)
  * @cap_usb4: Offset to the USB4 port capability (%0 if not present)
  * @port: Port number on switch
@@ -147,6 +182,7 @@ struct tb_port {
 	struct tb_port *remote;
 	struct tb_xdomain *xdomain;
 	int cap_phy;
+	int cap_tmu;
 	int cap_adap;
 	int cap_usb4;
 	u8 port;
@@ -672,6 +708,17 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
 int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
 void tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
 
+int tb_switch_tmu_init(struct tb_switch *sw);
+int tb_switch_tmu_post_time(struct tb_switch *sw);
+int tb_switch_tmu_disable(struct tb_switch *sw);
+int tb_switch_tmu_enable(struct tb_switch *sw);
+
+static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
+{
+	return sw->tmu.rate == TB_SWITCH_TMU_RATE_HIFI &&
+	       !sw->tmu.unidirectional;
+}
+
 int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
 int tb_port_add_nfc_credits(struct tb_port *port, int credits);
 int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index 47f73f992412..ec1a5d1f7c94 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -26,6 +26,7 @@
 #define TB_MAX_CONFIG_RW_LENGTH 60
 
 enum tb_switch_cap {
+	TB_SWITCH_CAP_TMU		= 0x03,
 	TB_SWITCH_CAP_VSE		= 0x05,
 };
 
@@ -195,6 +196,21 @@ struct tb_regs_switch_header {
 #define ROUTER_CS_26_ONS			BIT(30)
 #define ROUTER_CS_26_OV				BIT(31)
 
+/* Router TMU configuration */
+#define TMU_RTR_CS_0				0x00
+#define TMU_RTR_CS_0_TD				BIT(27)
+#define TMU_RTR_CS_0_UCAP			BIT(30)
+#define TMU_RTR_CS_1				0x01
+#define TMU_RTR_CS_1_LOCAL_TIME_NS_MASK		GENMASK(31, 16)
+#define TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT	16
+#define TMU_RTR_CS_2				0x02
+#define TMU_RTR_CS_3				0x03
+#define TMU_RTR_CS_3_LOCAL_TIME_NS_MASK		GENMASK(15, 0)
+#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK	GENMASK(31, 16)
+#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT	16
+#define TMU_RTR_CS_22				0x16
+#define TMU_RTR_CS_24				0x18
+
 enum tb_port_type {
 	TB_TYPE_INACTIVE	= 0x000000,
 	TB_TYPE_PORT		= 0x000001,
@@ -248,6 +264,10 @@ struct tb_regs_port_header {
 #define ADP_CS_5_LCA_MASK			GENMASK(28, 22)
 #define ADP_CS_5_LCA_SHIFT			22
 
+/* TMU adapter registers */
+#define TMU_ADP_CS_3				0x03
+#define TMU_ADP_CS_3_UDM			BIT(29)
+
 /* Lane adapter registers */
 #define LANE_ADP_CS_0				0x00
 #define LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK	GENMASK(25, 20)
diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c
new file mode 100644
index 000000000000..039c42a06000
--- /dev/null
+++ b/drivers/thunderbolt/tmu.c
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Thunderbolt Time Management Unit (TMU) support
+ *
+ * Copyright (C) 2019, Intel Corporation
+ * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
+ *	    Rajmohan Mani <rajmohan.mani@intel.com>
+ */
+
+#include <linux/delay.h>
+
+#include "tb.h"
+
+static const char *tb_switch_tmu_mode_name(const struct tb_switch *sw)
+{
+	bool root_switch = !tb_route(sw);
+
+	switch (sw->tmu.rate) {
+	case TB_SWITCH_TMU_RATE_OFF:
+		return "off";
+
+	case TB_SWITCH_TMU_RATE_HIFI:
+		/* Root switch does not have upstream directionality */
+		if (root_switch)
+			return "HiFi";
+		if (sw->tmu.unidirectional)
+			return "uni-directional, HiFi";
+		return "bi-directional, HiFi";
+
+	case TB_SWITCH_TMU_RATE_NORMAL:
+		if (root_switch)
+			return "normal";
+		return "uni-directional, normal";
+
+	default:
+		return "unknown";
+	}
+}
+
+static bool tb_switch_tmu_ucap_supported(struct tb_switch *sw)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
+			 sw->tmu.cap + TMU_RTR_CS_0, 1);
+	if (ret)
+		return false;
+
+	return !!(val & TMU_RTR_CS_0_UCAP);
+}
+
+static int tb_switch_tmu_rate_read(struct tb_switch *sw)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
+			 sw->tmu.cap + TMU_RTR_CS_3, 1);
+	if (ret)
+		return ret;
+
+	val >>= TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT;
+	return val;
+}
+
+static int tb_switch_tmu_rate_write(struct tb_switch *sw, int rate)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
+			 sw->tmu.cap + TMU_RTR_CS_3, 1);
+	if (ret)
+		return ret;
+
+	val &= ~TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK;
+	val |= rate << TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT;
+
+	return tb_sw_write(sw, &val, TB_CFG_SWITCH,
+			   sw->tmu.cap + TMU_RTR_CS_3, 1);
+}
+
+static int tb_port_tmu_write(struct tb_port *port, u8 offset, u32 mask,
+			     u32 value)
+{
+	u32 data;
+	int ret;
+
+	ret = tb_port_read(port, &data, TB_CFG_PORT, port->cap_tmu + offset, 1);
+	if (ret)
+		return ret;
+
+	data &= ~mask;
+	data |= value;
+
+	return tb_port_write(port, &data, TB_CFG_PORT,
+			     port->cap_tmu + offset, 1);
+}
+
+static int tb_port_tmu_set_unidirectional(struct tb_port *port,
+					  bool unidirectional)
+{
+	u32 val;
+
+	if (!port->sw->tmu.has_ucap)
+		return 0;
+
+	val = unidirectional ? TMU_ADP_CS_3_UDM : 0;
+	return tb_port_tmu_write(port, TMU_ADP_CS_3, TMU_ADP_CS_3_UDM, val);
+}
+
+static inline int tb_port_tmu_unidirectional_disable(struct tb_port *port)
+{
+	return tb_port_tmu_set_unidirectional(port, false);
+}
+
+static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_port_read(port, &val, TB_CFG_PORT,
+			   port->cap_tmu + TMU_ADP_CS_3, 1);
+	if (ret)
+		return false;
+
+	return val & TMU_ADP_CS_3_UDM;
+}
+
+static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set)
+{
+	int ret;
+	u32 val;
+
+	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
+			 sw->tmu.cap + TMU_RTR_CS_0, 1);
+	if (ret)
+		return ret;
+
+	if (set)
+		val |= TMU_RTR_CS_0_TD;
+	else
+		val &= ~TMU_RTR_CS_0_TD;
+
+	return tb_sw_write(sw, &val, TB_CFG_SWITCH,
+			   sw->tmu.cap + TMU_RTR_CS_0, 1);
+}
+
+/**
+ * tb_switch_tmu_init() - Initialize switch TMU structures
+ * @sw: Switch to initialized
+ *
+ * This function must be called before other TMU related functions to
+ * makes the internal structures are filled in correctly. Does not
+ * change any hardware configuration.
+ */
+int tb_switch_tmu_init(struct tb_switch *sw)
+{
+	struct tb_port *port;
+	int ret;
+
+	if (tb_switch_is_icm(sw))
+		return 0;
+
+	ret = tb_switch_find_cap(sw, TB_SWITCH_CAP_TMU);
+	if (ret > 0)
+		sw->tmu.cap = ret;
+
+	tb_switch_for_each_port(sw, port) {
+		int cap;
+
+		cap = tb_port_find_cap(port, TB_PORT_CAP_TIME1);
+		if (cap > 0)
+			port->cap_tmu = cap;
+	}
+
+	ret = tb_switch_tmu_rate_read(sw);
+	if (ret < 0)
+		return ret;
+
+	sw->tmu.rate = ret;
+
+	sw->tmu.has_ucap = tb_switch_tmu_ucap_supported(sw);
+	if (sw->tmu.has_ucap) {
+		tb_sw_dbg(sw, "TMU: supports uni-directional mode\n");
+
+		if (tb_route(sw)) {
+			struct tb_port *up = tb_upstream_port(sw);
+
+			sw->tmu.unidirectional =
+				tb_port_tmu_is_unidirectional(up);
+		}
+	} else {
+		sw->tmu.unidirectional = false;
+	}
+
+	tb_sw_dbg(sw, "TMU: current mode: %s\n", tb_switch_tmu_mode_name(sw));
+	return 0;
+}
+
+/**
+ * tb_switch_tmu_post_time() - Update switch local time
+ * @sw: Switch whose time to update
+ *
+ * Updates switch local time using time posting procedure.
+ */
+int tb_switch_tmu_post_time(struct tb_switch *sw)
+{
+	unsigned int  post_local_time_offset, post_time_offset;
+	struct tb_switch *root_switch = sw->tb->root_switch;
+	u64 hi, mid, lo, local_time, post_time;
+	int i, ret, retries = 100;
+	u32 gm_local_time[3];
+
+	if (!tb_route(sw))
+		return 0;
+
+	if (!tb_switch_is_usb4(sw))
+		return 0;
+
+	/* Need to be able to read the grand master time */
+	if (!root_switch->tmu.cap)
+		return 0;
+
+	ret = tb_sw_read(root_switch, gm_local_time, TB_CFG_SWITCH,
+			 root_switch->tmu.cap + TMU_RTR_CS_1,
+			 ARRAY_SIZE(gm_local_time));
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(gm_local_time); i++)
+		tb_sw_dbg(root_switch, "local_time[%d]=0x%08x\n", i,
+			  gm_local_time[i]);
+
+	/* Convert to nanoseconds (drop fractional part) */
+	hi = gm_local_time[2] & TMU_RTR_CS_3_LOCAL_TIME_NS_MASK;
+	mid = gm_local_time[1];
+	lo = (gm_local_time[0] & TMU_RTR_CS_1_LOCAL_TIME_NS_MASK) >>
+		TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT;
+	local_time = hi << 48 | mid << 16 | lo;
+
+	/* Tell the switch that time sync is disrupted for a while */
+	ret = tb_switch_tmu_set_time_disruption(sw, true);
+	if (ret)
+		return ret;
+
+	post_local_time_offset = sw->tmu.cap + TMU_RTR_CS_22;
+	post_time_offset = sw->tmu.cap + TMU_RTR_CS_24;
+
+	/*
+	 * Write the Grandmaster time to the Post Local Time registers
+	 * of the new switch.
+	 */
+	ret = tb_sw_write(sw, &local_time, TB_CFG_SWITCH,
+			  post_local_time_offset, 2);
+	if (ret)
+		goto out;
+
+	/*
+	 * Have the new switch update its local time (by writing 1 to
+	 * the post_time registers) and wait for the completion of the
+	 * same (post_time register becomes 0). This means the time has
+	 * been converged properly.
+	 */
+	post_time = 1;
+
+	ret = tb_sw_write(sw, &post_time, TB_CFG_SWITCH, post_time_offset, 2);
+	if (ret)
+		goto out;
+
+	do {
+		usleep_range(5, 10);
+		ret = tb_sw_read(sw, &post_time, TB_CFG_SWITCH,
+				 post_time_offset, 2);
+		if (ret)
+			goto out;
+	} while (--retries && post_time);
+
+	if (!retries) {
+		ret = -ETIMEDOUT;
+		goto out;
+	}
+
+	tb_sw_dbg(sw, "TMU: updated local time to %#llx\n", local_time);
+
+out:
+	tb_switch_tmu_set_time_disruption(sw, false);
+	return ret;
+}
+
+/**
+ * tb_switch_tmu_disable() - Disable TMU of a switch
+ * @sw: Switch whose TMU to disable
+ *
+ * Turns off TMU of @sw if it is enabled. If not enabled does nothing.
+ */
+int tb_switch_tmu_disable(struct tb_switch *sw)
+{
+	int ret;
+
+	if (!tb_switch_is_usb4(sw))
+		return 0;
+
+	/* Already disabled? */
+	if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF)
+		return 0;
+
+	if (sw->tmu.unidirectional) {
+		struct tb_switch *parent = tb_switch_parent(sw);
+		struct tb_port *up, *down;
+
+		up = tb_upstream_port(sw);
+		down = tb_port_at(tb_route(sw), parent);
+
+		/* The switch may be unplugged so ignore any errors */
+		tb_port_tmu_unidirectional_disable(up);
+		ret = tb_port_tmu_unidirectional_disable(down);
+		if (ret)
+			return ret;
+	}
+
+	tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
+
+	sw->tmu.unidirectional = false;
+	sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF;
+
+	tb_sw_dbg(sw, "TMU: disabled\n");
+	return 0;
+}
+
+/**
+ * tb_switch_tmu_enable() - Enable TMU on a switch
+ * @sw: Switch whose TMU to enable
+ *
+ * Enables TMU of a switch to be in bi-directional, HiFi mode. In this mode
+ * all tunneling should work.
+ */
+int tb_switch_tmu_enable(struct tb_switch *sw)
+{
+	int ret;
+
+	if (!tb_switch_is_usb4(sw))
+		return 0;
+
+	if (tb_switch_tmu_is_enabled(sw))
+		return 0;
+
+	ret = tb_switch_tmu_set_time_disruption(sw, true);
+	if (ret)
+		return ret;
+
+	/* Change mode to bi-directional */
+	if (tb_route(sw) && sw->tmu.unidirectional) {
+		struct tb_switch *parent = tb_switch_parent(sw);
+		struct tb_port *up, *down;
+
+		up = tb_upstream_port(sw);
+		down = tb_port_at(tb_route(sw), parent);
+
+		ret = tb_port_tmu_unidirectional_disable(down);
+		if (ret)
+			return ret;
+
+		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
+		if (ret)
+			return ret;
+
+		ret = tb_port_tmu_unidirectional_disable(up);
+		if (ret)
+			return ret;
+	} else {
+		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
+		if (ret)
+			return ret;
+	}
+
+	sw->tmu.unidirectional = false;
+	sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI;
+	tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));
+
+	return tb_switch_tmu_set_time_disruption(sw, false);
+}
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 8/9] thunderbolt: Add support for USB 3.x tunnels
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (6 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-17 12:33 ` [PATCH v2 9/9] thunderbolt: Update documentation with the USB4 information Mika Westerberg
  2019-12-18 14:43 ` [PATCH v2 0/9] thunderbolt: Add support for USB4 Greg Kroah-Hartman
  9 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

From: Rajmohan Mani <rajmohan.mani@intel.com>

USB4 added a capability to tunnel USB 3.x protocol over the USB4
fabric. USB4 device routers may include integrated SuperSpeed HUB or a
function or both. USB tunneling follows PCIe so that the tunnel is
created between the parent and the child router from USB3 downstream
adapter port to USB3 upstream adapter port over a single USB4 link.

This adds support for USB 3.x tunneling and also capability to discover
existing USB 3.x tunnels (for example created by connection manager in
boot firmware).

Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
Co-developed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 drivers/thunderbolt/switch.c  |  35 ++++++++
 drivers/thunderbolt/tb.c      | 154 +++++++++++++++++++++++++++------
 drivers/thunderbolt/tb.h      |  15 ++++
 drivers/thunderbolt/tb_regs.h |   9 +-
 drivers/thunderbolt/tunnel.c  | 158 +++++++++++++++++++++++++++++++++-
 drivers/thunderbolt/tunnel.h  |   9 ++
 drivers/thunderbolt/usb4.c    |  42 ++++++++-
 7 files changed, 395 insertions(+), 27 deletions(-)

diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
index 82f45780dc81..3454e6154958 100644
--- a/drivers/thunderbolt/switch.c
+++ b/drivers/thunderbolt/switch.c
@@ -1042,11 +1042,46 @@ bool tb_port_is_enabled(struct tb_port *port)
 	case TB_TYPE_DP_HDMI_OUT:
 		return tb_dp_port_is_enabled(port);
 
+	case TB_TYPE_USB3_UP:
+	case TB_TYPE_USB3_DOWN:
+		return tb_usb3_port_is_enabled(port);
+
 	default:
 		return false;
 	}
 }
 
+/**
+ * tb_usb3_port_is_enabled() - Is the USB3 adapter port enabled
+ * @port: USB3 adapter port to check
+ */
+bool tb_usb3_port_is_enabled(struct tb_port *port)
+{
+	u32 data;
+
+	if (tb_port_read(port, &data, TB_CFG_PORT,
+			 port->cap_adap + ADP_USB3_CS_0, 1))
+		return false;
+
+	return !!(data & ADP_USB3_CS_0_PE);
+}
+
+/**
+ * tb_usb3_port_enable() - Enable USB3 adapter port
+ * @port: USB3 adapter port to enable
+ * @enable: Enable/disable the USB3 adapter
+ */
+int tb_usb3_port_enable(struct tb_port *port, bool enable)
+{
+	u32 word = enable ? (ADP_USB3_CS_0_PE | ADP_USB3_CS_0_V)
+			  : ADP_USB3_CS_0_V;
+
+	if (!port->cap_adap)
+		return -ENXIO;
+	return tb_port_write(port, &word, TB_CFG_PORT,
+			     port->cap_adap + ADP_USB3_CS_0, 1);
+}
+
 /**
  * tb_pci_port_is_enabled() - Is the PCIe adapter port enabled
  * @port: PCIe port to check
diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
index e446624dd3e7..107cd232f486 100644
--- a/drivers/thunderbolt/tb.c
+++ b/drivers/thunderbolt/tb.c
@@ -111,6 +111,10 @@ static void tb_discover_tunnels(struct tb_switch *sw)
 			tunnel = tb_tunnel_discover_pci(tb, port);
 			break;
 
+		case TB_TYPE_USB3_DOWN:
+			tunnel = tb_tunnel_discover_usb3(tb, port);
+			break;
+
 		default:
 			break;
 		}
@@ -177,6 +181,118 @@ static int tb_enable_tmu(struct tb_switch *sw)
 	return tb_switch_tmu_enable(sw);
 }
 
+/**
+ * tb_find_unused_port() - return the first inactive port on @sw
+ * @sw: Switch to find the port on
+ * @type: Port type to look for
+ */
+static struct tb_port *tb_find_unused_port(struct tb_switch *sw,
+					   enum tb_port_type type)
+{
+	struct tb_port *port;
+
+	tb_switch_for_each_port(sw, port) {
+		if (tb_is_upstream_port(port))
+			continue;
+		if (port->config.type != type)
+			continue;
+		if (!port->cap_adap)
+			continue;
+		if (tb_port_is_enabled(port))
+			continue;
+		return port;
+	}
+	return NULL;
+}
+
+static struct tb_port *tb_find_usb3_down(struct tb_switch *sw,
+					const struct tb_port *port)
+{
+	struct tb_port *down;
+
+	down = usb4_switch_map_usb3_down(sw, port);
+	if (down) {
+		if (WARN_ON(!tb_port_is_usb3_down(down)))
+			goto out;
+		if (WARN_ON(tb_usb3_port_is_enabled(down)))
+			goto out;
+
+		return down;
+	}
+
+out:
+	return tb_find_unused_port(sw, TB_TYPE_USB3_DOWN);
+}
+
+static int tb_tunnel_usb3(struct tb *tb, struct tb_switch *sw)
+{
+	struct tb_switch *parent = tb_switch_parent(sw);
+	struct tb_port *up, *down, *port;
+	struct tb_cm *tcm = tb_priv(tb);
+	struct tb_tunnel *tunnel;
+
+	up = tb_switch_find_port(sw, TB_TYPE_USB3_UP);
+	if (!up)
+		return 0;
+
+	/*
+	 * Look up available down port. Since we are chaining it should
+	 * be found right above this switch.
+	 */
+	port = tb_port_at(tb_route(sw), parent);
+	down = tb_find_usb3_down(parent, port);
+	if (!down)
+		return 0;
+
+	if (tb_route(parent)) {
+		struct tb_port *parent_up;
+		/*
+		 * Check first that the parent switch has its upstream USB3
+		 * port enabled. Otherwise the chain is not complete and
+		 * there is no point setting up a new tunnel.
+		 */
+		parent_up = tb_switch_find_port(parent, TB_TYPE_USB3_UP);
+		if (!parent_up || !tb_port_is_enabled(parent_up))
+			return 0;
+	}
+
+	tunnel = tb_tunnel_alloc_usb3(tb, up, down);
+	if (!tunnel)
+		return -ENOMEM;
+
+	if (tb_tunnel_activate(tunnel)) {
+		tb_port_info(up,
+			     "USB3 tunnel activation failed, aborting\n");
+		tb_tunnel_free(tunnel);
+		return -EIO;
+	}
+
+	list_add_tail(&tunnel->list, &tcm->tunnel_list);
+	return 0;
+}
+
+static int tb_create_usb3_tunnels(struct tb_switch *sw)
+{
+	struct tb_port *port;
+	int ret;
+
+	if (tb_route(sw)) {
+		ret = tb_tunnel_usb3(sw->tb, sw);
+		if (ret)
+			return ret;
+	}
+
+	tb_switch_for_each_port(sw, port) {
+		if (!tb_port_has_remote(port))
+			continue;
+		ret = tb_create_usb3_tunnels(port->remote->sw);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
 static void tb_scan_port(struct tb_port *port);
 
 /**
@@ -279,6 +395,15 @@ static void tb_scan_port(struct tb_port *port)
 	if (tb_enable_tmu(sw))
 		tb_sw_warn(sw, "failed to enable TMU\n");
 
+	/*
+	 * Create USB 3.x tunnels only when the switch is plugged to the
+	 * domain. This is because we scan the domain also during discovery
+	 * and want to discover existing USB 3.x tunnels before we create
+	 * any new.
+	 */
+	if (tcm->hotplug_active && tb_tunnel_usb3(sw->tb, sw))
+		tb_sw_warn(sw, "USB3 tunnel creation failed\n");
+
 	tb_scan_switch(sw);
 }
 
@@ -360,30 +485,6 @@ static void tb_free_unplugged_children(struct tb_switch *sw)
 	}
 }
 
-/**
- * tb_find_unused_port() - return the first inactive port on @sw
- * @sw: Switch to find the port on
- * @type: Port type to look for
- */
-static struct tb_port *tb_find_unused_port(struct tb_switch *sw,
-					   enum tb_port_type type)
-{
-	struct tb_port *port;
-
-	tb_switch_for_each_port(sw, port) {
-		if (tb_is_upstream_port(port))
-			continue;
-		if (port->config.type != type)
-			continue;
-		if (port->cap_adap)
-			continue;
-		if (tb_port_is_enabled(port))
-			continue;
-		return port;
-	}
-	return NULL;
-}
-
 static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
 					 const struct tb_port *port)
 {
@@ -884,6 +985,11 @@ static int tb_start(struct tb *tb)
 	tb_scan_switch(tb->root_switch);
 	/* Find out tunnels created by the boot firmware */
 	tb_discover_tunnels(tb->root_switch);
+	/*
+	 * If the boot firmware did not create USB 3.x tunnels create them
+	 * now for the whole topology.
+	 */
+	tb_create_usb3_tunnels(tb->root_switch);
 	/* Add DP IN resources for the root switch */
 	tb_add_dp_resources(tb->root_switch);
 	/* Make the discovered switches available to the userspace */
diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
index 63ffb3cbdefe..2eb2bcd3cca3 100644
--- a/drivers/thunderbolt/tb.h
+++ b/drivers/thunderbolt/tb.h
@@ -432,6 +432,16 @@ static inline bool tb_port_is_dpout(const struct tb_port *port)
 	return port && port->config.type == TB_TYPE_DP_HDMI_OUT;
 }
 
+static inline bool tb_port_is_usb3_down(const struct tb_port *port)
+{
+	return port && port->config.type == TB_TYPE_USB3_DOWN;
+}
+
+static inline bool tb_port_is_usb3_up(const struct tb_port *port)
+{
+	return port && port->config.type == TB_TYPE_USB3_UP;
+}
+
 static inline int tb_sw_read(struct tb_switch *sw, void *buffer,
 			     enum tb_cfg_space space, u32 offset, u32 length)
 {
@@ -736,6 +746,9 @@ int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap);
 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
 bool tb_port_is_enabled(struct tb_port *port);
 
+bool tb_usb3_port_is_enabled(struct tb_port *port);
+int tb_usb3_port_enable(struct tb_port *port, bool enable);
+
 bool tb_pci_port_is_enabled(struct tb_port *port);
 int tb_pci_port_enable(struct tb_port *port, bool enable);
 
@@ -818,6 +831,8 @@ int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
 int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
 struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
 					  const struct tb_port *port);
+struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
+					  const struct tb_port *port);
 
 int usb4_port_unlock(struct tb_port *port);
 #endif
diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
index ec1a5d1f7c94..c29c5075525a 100644
--- a/drivers/thunderbolt/tb_regs.h
+++ b/drivers/thunderbolt/tb_regs.h
@@ -180,6 +180,7 @@ struct tb_regs_switch_header {
 #define ROUTER_CS_5_SLP				BIT(0)
 #define ROUTER_CS_5_C3S				BIT(23)
 #define ROUTER_CS_5_PTO				BIT(24)
+#define ROUTER_CS_5_UTO				BIT(25)
 #define ROUTER_CS_5_HCO				BIT(26)
 #define ROUTER_CS_5_CV				BIT(31)
 #define ROUTER_CS_6				0x06
@@ -221,7 +222,8 @@ enum tb_port_type {
 	TB_TYPE_DP_HDMI_OUT	= 0x0e0102,
 	TB_TYPE_PCIE_DOWN	= 0x100101,
 	TB_TYPE_PCIE_UP		= 0x100102,
-	/* TB_TYPE_USB		= 0x200000, lower order bits are not known */
+	TB_TYPE_USB3_DOWN	= 0x200101,
+	TB_TYPE_USB3_UP		= 0x200102,
 };
 
 /* Present on every port in TB_CF_PORT at address zero. */
@@ -331,6 +333,11 @@ struct tb_regs_port_header {
 #define ADP_PCIE_CS_0				0x00
 #define ADP_PCIE_CS_0_PE			BIT(31)
 
+/* USB adapter registers */
+#define ADP_USB3_CS_0				0x00
+#define ADP_USB3_CS_0_V				BIT(30)
+#define ADP_USB3_CS_0_PE			BIT(31)
+
 /* Hop register from TB_CFG_HOPS. 8 byte per entry. */
 struct tb_regs_hop {
 	/* DWORD 0 */
diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
index 21d266a76b7d..dbe90bcf4ad4 100644
--- a/drivers/thunderbolt/tunnel.c
+++ b/drivers/thunderbolt/tunnel.c
@@ -19,6 +19,12 @@
 #define TB_PCI_PATH_DOWN		0
 #define TB_PCI_PATH_UP			1
 
+/* USB3 adapters use always HopID of 8 for both directions */
+#define TB_USB3_HOPID			8
+
+#define TB_USB3_PATH_DOWN		0
+#define TB_USB3_PATH_UP			1
+
 /* DP adapters use HopID 8 for AUX and 9 for Video */
 #define TB_DP_AUX_TX_HOPID		8
 #define TB_DP_AUX_RX_HOPID		8
@@ -31,7 +37,7 @@
 #define TB_DMA_PATH_OUT			0
 #define TB_DMA_PATH_IN			1
 
-static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA" };
+static const char * const tb_tunnel_names[] = { "PCI", "DP", "DMA", "USB3" };
 
 #define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...)                   \
 	do {                                                            \
@@ -848,6 +854,156 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
 	return tunnel;
 }
 
+static int tb_usb3_activate(struct tb_tunnel *tunnel, bool activate)
+{
+	int res;
+
+	res = tb_usb3_port_enable(tunnel->src_port, activate);
+	if (res)
+		return res;
+
+	if (tb_port_is_usb3_up(tunnel->dst_port))
+		return tb_usb3_port_enable(tunnel->dst_port, activate);
+
+	return 0;
+}
+
+static void tb_usb3_init_path(struct tb_path *path)
+{
+	path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL;
+	path->egress_shared_buffer = TB_PATH_NONE;
+	path->ingress_fc_enable = TB_PATH_ALL;
+	path->ingress_shared_buffer = TB_PATH_NONE;
+	path->priority = 3;
+	path->weight = 3;
+	path->drop_packages = 0;
+	path->nfc_credits = 0;
+	path->hops[0].initial_credits = 7;
+	path->hops[1].initial_credits =
+		tb_initial_credits(path->hops[1].in_port->sw);
+}
+
+/**
+ * tb_tunnel_discover_usb3() - Discover existing USB3 tunnels
+ * @tb: Pointer to the domain structure
+ * @down: USB3 downstream adapter
+ *
+ * If @down adapter is active, follows the tunnel to the USB3 upstream
+ * adapter and back. Returns the discovered tunnel or %NULL if there was
+ * no tunnel.
+ */
+struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down)
+{
+	struct tb_tunnel *tunnel;
+	struct tb_path *path;
+
+	if (!tb_usb3_port_is_enabled(down))
+		return NULL;
+
+	tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_USB3);
+	if (!tunnel)
+		return NULL;
+
+	tunnel->activate = tb_usb3_activate;
+	tunnel->src_port = down;
+
+	/*
+	 * Discover both paths even if they are not complete. We will
+	 * clean them up by calling tb_tunnel_deactivate() below in that
+	 * case.
+	 */
+	path = tb_path_discover(down, TB_USB3_HOPID, NULL, -1,
+				&tunnel->dst_port, "USB3 Up");
+	if (!path) {
+		/* Just disable the downstream port */
+		tb_usb3_port_enable(down, false);
+		goto err_free;
+	}
+	tunnel->paths[TB_USB3_PATH_UP] = path;
+	tb_usb3_init_path(tunnel->paths[TB_USB3_PATH_UP]);
+
+	path = tb_path_discover(tunnel->dst_port, -1, down, TB_USB3_HOPID, NULL,
+				"USB3 Down");
+	if (!path)
+		goto err_deactivate;
+	tunnel->paths[TB_USB3_PATH_DOWN] = path;
+	tb_usb3_init_path(tunnel->paths[TB_USB3_PATH_DOWN]);
+
+	/* Validate that the tunnel is complete */
+	if (!tb_port_is_usb3_up(tunnel->dst_port)) {
+		tb_port_warn(tunnel->dst_port,
+			     "path does not end on an USB3 adapter, cleaning up\n");
+		goto err_deactivate;
+	}
+
+	if (down != tunnel->src_port) {
+		tb_tunnel_warn(tunnel, "path is not complete, cleaning up\n");
+		goto err_deactivate;
+	}
+
+	if (!tb_usb3_port_is_enabled(tunnel->dst_port)) {
+		tb_tunnel_warn(tunnel,
+			       "tunnel is not fully activated, cleaning up\n");
+		goto err_deactivate;
+	}
+
+	tb_tunnel_dbg(tunnel, "discovered\n");
+	return tunnel;
+
+err_deactivate:
+	tb_tunnel_deactivate(tunnel);
+err_free:
+	tb_tunnel_free(tunnel);
+
+	return NULL;
+}
+
+/**
+ * tb_tunnel_alloc_usb3() - allocate a USB3 tunnel
+ * @tb: Pointer to the domain structure
+ * @up: USB3 upstream adapter port
+ * @down: USB3 downstream adapter port
+ *
+ * Allocate an USB3 tunnel. The ports must be of type @TB_TYPE_USB3_UP and
+ * @TB_TYPE_USB3_DOWN.
+ *
+ * Return: Returns a tb_tunnel on success or %NULL on failure.
+ */
+struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
+				       struct tb_port *down)
+{
+	struct tb_tunnel *tunnel;
+	struct tb_path *path;
+
+	tunnel = tb_tunnel_alloc(tb, 2, TB_TUNNEL_USB3);
+	if (!tunnel)
+		return NULL;
+
+	tunnel->activate = tb_usb3_activate;
+	tunnel->src_port = down;
+	tunnel->dst_port = up;
+
+	path = tb_path_alloc(tb, down, TB_USB3_HOPID, up, TB_USB3_HOPID, 0,
+			     "USB3 Down");
+	if (!path) {
+		tb_tunnel_free(tunnel);
+		return NULL;
+	}
+	tb_usb3_init_path(path);
+	tunnel->paths[TB_USB3_PATH_DOWN] = path;
+
+	path = tb_path_alloc(tb, up, TB_USB3_HOPID, down, TB_USB3_HOPID, 0,
+			     "USB3 Up");
+	if (!path) {
+		tb_tunnel_free(tunnel);
+		return NULL;
+	}
+	tb_usb3_init_path(path);
+	tunnel->paths[TB_USB3_PATH_UP] = path;
+
+	return tunnel;
+}
+
 /**
  * tb_tunnel_free() - free a tunnel
  * @tunnel: Tunnel to be freed
diff --git a/drivers/thunderbolt/tunnel.h b/drivers/thunderbolt/tunnel.h
index ba888da005f5..3f5ba93225e7 100644
--- a/drivers/thunderbolt/tunnel.h
+++ b/drivers/thunderbolt/tunnel.h
@@ -15,6 +15,7 @@ enum tb_tunnel_type {
 	TB_TUNNEL_PCI,
 	TB_TUNNEL_DP,
 	TB_TUNNEL_DMA,
+	TB_TUNNEL_USB3,
 };
 
 /**
@@ -57,6 +58,9 @@ struct tb_tunnel *tb_tunnel_alloc_dma(struct tb *tb, struct tb_port *nhi,
 				      struct tb_port *dst, int transmit_ring,
 				      int transmit_path, int receive_ring,
 				      int receive_path);
+struct tb_tunnel *tb_tunnel_discover_usb3(struct tb *tb, struct tb_port *down);
+struct tb_tunnel *tb_tunnel_alloc_usb3(struct tb *tb, struct tb_port *up,
+				       struct tb_port *down);
 
 void tb_tunnel_free(struct tb_tunnel *tunnel);
 int tb_tunnel_activate(struct tb_tunnel *tunnel);
@@ -82,5 +86,10 @@ static inline bool tb_tunnel_is_dma(const struct tb_tunnel *tunnel)
 	return tunnel->type == TB_TUNNEL_DMA;
 }
 
+static inline bool tb_tunnel_is_usb3(const struct tb_tunnel *tunnel)
+{
+	return tunnel->type == TB_TUNNEL_USB3;
+}
+
 #endif
 
diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
index b84c74346d2b..dbe7ecce4505 100644
--- a/drivers/thunderbolt/usb4.c
+++ b/drivers/thunderbolt/usb4.c
@@ -226,10 +226,19 @@ int usb4_switch_setup(struct tb_switch *sw)
 
 	parent = tb_switch_parent(sw);
 
+	if (tb_switch_find_port(parent, TB_TYPE_USB3_DOWN)) {
+		val |= ROUTER_CS_5_UTO;
+		xhci = false;
+	}
+
 	/* Only enable PCIe tunneling if the parent router supports it */
 	if (tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) {
 		val |= ROUTER_CS_5_PTO;
-		/* xHCI can be enabled if PCIe tunneling is supported */
+		/*
+		 * xHCI can be enabled if PCIe tunneling is supported
+		 * and the parent does not have any USB3 dowstream
+		 * adapters (so we cannot do USB 3.x tunneling).
+		 */
 		if (xhci & ROUTER_CS_6_HCI)
 			val |= ROUTER_CS_5_HCO;
 	}
@@ -703,6 +712,37 @@ struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
 	return NULL;
 }
 
+/**
+ * usb4_switch_map_usb3_down() - Map USB4 port to a USB3 downstream adapter
+ * @sw: USB4 router
+ * @port: USB4 port
+ *
+ * USB4 routers have direct mapping between USB4 ports and USB 3.x
+ * downstream adapters where the USB 3.x topology is extended. This
+ * function returns the corresponding downstream USB 3.x adapter or
+ * %NULL if no such mapping was possible.
+ */
+struct tb_port *usb4_switch_map_usb3_down(struct tb_switch *sw,
+					  const struct tb_port *port)
+{
+	int usb4_idx = usb4_port_idx(sw, port);
+	struct tb_port *p;
+	int usb_idx = 0;
+
+	/* Find USB3 down port matching usb4_port */
+	tb_switch_for_each_port(sw, p) {
+		if (!tb_port_is_usb3_down(p))
+			continue;
+
+		if (usb_idx == usb4_idx && !tb_usb3_port_is_enabled(p))
+			return p;
+
+		usb_idx++;
+	}
+
+	return NULL;
+}
+
 /**
  * usb4_port_unlock() - Unlock USB4 downstream port
  * @port: USB4 port to unlock
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v2 9/9] thunderbolt: Update documentation with the USB4 information
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (7 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 8/9] thunderbolt: Add support for USB 3.x tunnels Mika Westerberg
@ 2019-12-17 12:33 ` Mika Westerberg
  2019-12-18 14:43 ` [PATCH v2 0/9] thunderbolt: Add support for USB4 Greg Kroah-Hartman
  9 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 12:33 UTC (permalink / raw)
  To: linux-usb
  Cc: Andreas Noever, Michael Jamet, Mika Westerberg, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner,
	Greg Kroah-Hartman, Alan Stern, Mario.Limonciello, Anthony Wong,
	Oliver Neukum, Christian Kellner, David S . Miller, netdev,
	linux-kernel

Update user's and administrator's guide to mention USB4, how it relates
to Thunderbolt and and how it is supported in Linux.

While there add the missing SPDX identifier to the document.

Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
---
 Documentation/admin-guide/thunderbolt.rst | 30 +++++++++++++++++++----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/Documentation/admin-guide/thunderbolt.rst b/Documentation/admin-guide/thunderbolt.rst
index 898ad78f3cc7..10c4f0ce2ad0 100644
--- a/Documentation/admin-guide/thunderbolt.rst
+++ b/Documentation/admin-guide/thunderbolt.rst
@@ -1,6 +1,28 @@
-=============
- Thunderbolt
-=============
+.. SPDX-License-Identifier: GPL-2.0
+
+======================
+ USB4 and Thunderbolt
+======================
+USB4 is the public specification based on Thunderbolt 3 protocol with
+some differences at the register level among other things. Connection
+manager is an entity running on the host router (host controller)
+responsible for enumerating routers and establishing tunnels. A
+connection manager can be implemented either in firmware or software.
+Typically PCs come with a firmware connection manager for Thunderbolt 3
+and early USB4 capable systems. Apple systems on the other hand use
+software connection manager and the later USB4 compliant devices follow
+the suit.
+
+The Linux Thunderbolt driver supports both and can detect at runtime which
+connection manager implementation is to be used. To be on the safe side the
+software connection manager in Linux also advertises security level
+``user`` which means PCIe tunneling is disabled by default. The
+documentation below applies to both implementations with the exception that
+the software connection manager only supports ``user`` security level and
+is expected to be accompanied with an IOMMU based DMA protection.
+
+Security levels and how to use them
+-----------------------------------
 The interface presented here is not meant for end users. Instead there
 should be a userspace tool that handles all the low-level details, keeps
 a database of the authorized devices and prompts users for new connections.
@@ -18,8 +40,6 @@ This will authorize all devices automatically when they appear. However,
 keep in mind that this bypasses the security levels and makes the system
 vulnerable to DMA attacks.
 
-Security levels and how to use them
------------------------------------
 Starting with Intel Falcon Ridge Thunderbolt controller there are 4
 security levels available. Intel Titan Ridge added one more security level
 (usbonly). The reason for these is the fact that the connected devices can
-- 
2.24.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 12:33 ` [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet Mika Westerberg
@ 2019-12-17 12:46   ` Greg Kroah-Hartman
  2019-12-17 14:55     ` Mika Westerberg
  2019-12-17 12:47   ` Greg Kroah-Hartman
  1 sibling, 1 reply; 21+ messages in thread
From: Greg Kroah-Hartman @ 2019-12-17 12:46 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:39PM +0300, Mika Westerberg wrote:
> USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
> packet that is sent as response of hot plug/unplug events. This field
> tells whether the acknowledgment is for plug or unplug event. This needs
> to be set accordingly in order the router to send further hot plug
> notifications.
> 
> To make it simpler we fill the field unconditionally. Legacy devices do
> not look at this field so there should be no problems with them.
> 
> While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
> log message accordingly. The function is only used to ack plug/unplug
> events.
> 
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> ---
>  drivers/thunderbolt/ctl.c     | 19 +++++++++++++------
>  drivers/thunderbolt/ctl.h     |  3 +--
>  drivers/thunderbolt/tb.c      |  3 +--
>  drivers/thunderbolt/tb_msgs.h |  6 +++++-
>  4 files changed, 20 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
> index d97813e80e5f..f77ceae5c7d7 100644
> --- a/drivers/thunderbolt/ctl.c
> +++ b/drivers/thunderbolt/ctl.c
> @@ -708,19 +708,26 @@ void tb_ctl_stop(struct tb_ctl *ctl)
>  /* public interface, commands */
>  
>  /**
> - * tb_cfg_error() - send error packet
> + * tb_cfg_ack_plug() - Ack hot plug/unplug event
> + * @ctl: Control channel to use
> + * @route: Router that originated the event
> + * @port: Port where the hot plug/unplug happened
> + * @unplug: Ack hot plug or unplug
>   *
> - * Return: Returns 0 on success or an error code on failure.
> + * Call this as response for hot plug/unplug event to ack it.
> + * Returns %0 on success or an error code on failure.
>   */
> -int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
> -		 enum tb_cfg_error error)
> +int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug)
>  {
>  	struct cfg_error_pkg pkg = {
>  		.header = tb_cfg_make_header(route),
>  		.port = port,
> -		.error = error,
> +		.error = TB_CFG_ERROR_ACK_PLUG_EVENT,
> +		.pg = unplug ? TB_CFG_ERROR_PG_HOT_UNPLUG
> +			     : TB_CFG_ERROR_PG_HOT_PLUG,
>  	};
> -	tb_ctl_dbg(ctl, "resetting error on %llx:%x.\n", route, port);
> +	tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%x\n",
> +		   unplug ? "un" : "", route, port);
>  	return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
>  }
>  
> diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
> index 2f1a1e111110..97cb03b38953 100644
> --- a/drivers/thunderbolt/ctl.h
> +++ b/drivers/thunderbolt/ctl.h
> @@ -123,8 +123,7 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
>  	return header;
>  }
>  
> -int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
> -		 enum tb_cfg_error error);
> +int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
>  struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
>  				  int timeout_msec);
>  struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index 54085f67810a..e54d0d89a32d 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -768,8 +768,7 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
>  
>  	route = tb_cfg_get_route(&pkg->header);
>  
> -	if (tb_cfg_error(tb->ctl, route, pkg->port,
> -			 TB_CFG_ERROR_ACK_PLUG_EVENT)) {
> +	if (tb_cfg_ack_plug(tb->ctl, route, pkg->port, pkg->unplug)) {
>  		tb_warn(tb, "could not ack plug event on %llx:%x\n", route,
>  			pkg->port);
>  	}
> diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h
> index 3705057723b6..fc208c567953 100644
> --- a/drivers/thunderbolt/tb_msgs.h
> +++ b/drivers/thunderbolt/tb_msgs.h
> @@ -67,9 +67,13 @@ struct cfg_error_pkg {
>  	u32 zero1:4;
>  	u32 port:6;
>  	u32 zero2:2; /* Both should be zero, still they are different fields. */
> -	u32 zero3:16;
> +	u32 zero3:14;
> +	u32 pg:2;
>  } __packed;

Meta-comment, how does this work for endian issues?  gcc will "always"
pack these in the correct way such that they match up to the bits on the
wire?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 12:33 ` [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet Mika Westerberg
  2019-12-17 12:46   ` Greg Kroah-Hartman
@ 2019-12-17 12:47   ` Greg Kroah-Hartman
  2019-12-17 14:56     ` Mika Westerberg
  1 sibling, 1 reply; 21+ messages in thread
From: Greg Kroah-Hartman @ 2019-12-17 12:47 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:39PM +0300, Mika Westerberg wrote:
> USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
> packet that is sent as response of hot plug/unplug events. This field
> tells whether the acknowledgment is for plug or unplug event. This needs
> to be set accordingly in order the router to send further hot plug
> notifications.
> 
> To make it simpler we fill the field unconditionally. Legacy devices do
> not look at this field so there should be no problems with them.
> 
> While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
> log message accordingly. The function is only used to ack plug/unplug
> events.
> 
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>

First 3 patches look "trivial" enough for me to take right now, any
objection to that?

Should I be using my usb tree for this?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 12:46   ` Greg Kroah-Hartman
@ 2019-12-17 14:55     ` Mika Westerberg
  0 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 14:55 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 01:46:23PM +0100, Greg Kroah-Hartman wrote:
> On Tue, Dec 17, 2019 at 03:33:39PM +0300, Mika Westerberg wrote:
> > USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
> > packet that is sent as response of hot plug/unplug events. This field
> > tells whether the acknowledgment is for plug or unplug event. This needs
> > to be set accordingly in order the router to send further hot plug
> > notifications.
> > 
> > To make it simpler we fill the field unconditionally. Legacy devices do
> > not look at this field so there should be no problems with them.
> > 
> > While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
> > log message accordingly. The function is only used to ack plug/unplug
> > events.
> > 
> > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> > ---
> >  drivers/thunderbolt/ctl.c     | 19 +++++++++++++------
> >  drivers/thunderbolt/ctl.h     |  3 +--
> >  drivers/thunderbolt/tb.c      |  3 +--
> >  drivers/thunderbolt/tb_msgs.h |  6 +++++-
> >  4 files changed, 20 insertions(+), 11 deletions(-)
> > 
> > diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c
> > index d97813e80e5f..f77ceae5c7d7 100644
> > --- a/drivers/thunderbolt/ctl.c
> > +++ b/drivers/thunderbolt/ctl.c
> > @@ -708,19 +708,26 @@ void tb_ctl_stop(struct tb_ctl *ctl)
> >  /* public interface, commands */
> >  
> >  /**
> > - * tb_cfg_error() - send error packet
> > + * tb_cfg_ack_plug() - Ack hot plug/unplug event
> > + * @ctl: Control channel to use
> > + * @route: Router that originated the event
> > + * @port: Port where the hot plug/unplug happened
> > + * @unplug: Ack hot plug or unplug
> >   *
> > - * Return: Returns 0 on success or an error code on failure.
> > + * Call this as response for hot plug/unplug event to ack it.
> > + * Returns %0 on success or an error code on failure.
> >   */
> > -int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
> > -		 enum tb_cfg_error error)
> > +int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug)
> >  {
> >  	struct cfg_error_pkg pkg = {
> >  		.header = tb_cfg_make_header(route),
> >  		.port = port,
> > -		.error = error,
> > +		.error = TB_CFG_ERROR_ACK_PLUG_EVENT,
> > +		.pg = unplug ? TB_CFG_ERROR_PG_HOT_UNPLUG
> > +			     : TB_CFG_ERROR_PG_HOT_PLUG,
> >  	};
> > -	tb_ctl_dbg(ctl, "resetting error on %llx:%x.\n", route, port);
> > +	tb_ctl_dbg(ctl, "acking hot %splug event on %llx:%x\n",
> > +		   unplug ? "un" : "", route, port);
> >  	return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR);
> >  }
> >  
> > diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h
> > index 2f1a1e111110..97cb03b38953 100644
> > --- a/drivers/thunderbolt/ctl.h
> > +++ b/drivers/thunderbolt/ctl.h
> > @@ -123,8 +123,7 @@ static inline struct tb_cfg_header tb_cfg_make_header(u64 route)
> >  	return header;
> >  }
> >  
> > -int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port,
> > -		 enum tb_cfg_error error);
> > +int tb_cfg_ack_plug(struct tb_ctl *ctl, u64 route, u32 port, bool unplug);
> >  struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route,
> >  				  int timeout_msec);
> >  struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer,
> > diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> > index 54085f67810a..e54d0d89a32d 100644
> > --- a/drivers/thunderbolt/tb.c
> > +++ b/drivers/thunderbolt/tb.c
> > @@ -768,8 +768,7 @@ static void tb_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
> >  
> >  	route = tb_cfg_get_route(&pkg->header);
> >  
> > -	if (tb_cfg_error(tb->ctl, route, pkg->port,
> > -			 TB_CFG_ERROR_ACK_PLUG_EVENT)) {
> > +	if (tb_cfg_ack_plug(tb->ctl, route, pkg->port, pkg->unplug)) {
> >  		tb_warn(tb, "could not ack plug event on %llx:%x\n", route,
> >  			pkg->port);
> >  	}
> > diff --git a/drivers/thunderbolt/tb_msgs.h b/drivers/thunderbolt/tb_msgs.h
> > index 3705057723b6..fc208c567953 100644
> > --- a/drivers/thunderbolt/tb_msgs.h
> > +++ b/drivers/thunderbolt/tb_msgs.h
> > @@ -67,9 +67,13 @@ struct cfg_error_pkg {
> >  	u32 zero1:4;
> >  	u32 port:6;
> >  	u32 zero2:2; /* Both should be zero, still they are different fields. */
> > -	u32 zero3:16;
> > +	u32 zero3:14;
> > +	u32 pg:2;
> >  } __packed;
> 
> Meta-comment, how does this work for endian issues?  gcc will "always"
> pack these in the correct way such that they match up to the bits on the
> wire?

Good question. I'm not entirely sure. My guess is that this simply does
not work properly on a big endian system (judging from what is done in
struct iphdr for example).

It is on my todo list to eventually get rid of all bit fields that are
used to deal with the hardware registers/protocol in this driver. New
stuff is not supposed to use bit fields with some exceptions like this
one.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 12:47   ` Greg Kroah-Hartman
@ 2019-12-17 14:56     ` Mika Westerberg
  2019-12-18 14:35       ` Greg Kroah-Hartman
  0 siblings, 1 reply; 21+ messages in thread
From: Mika Westerberg @ 2019-12-17 14:56 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 01:47:45PM +0100, Greg Kroah-Hartman wrote:
> On Tue, Dec 17, 2019 at 03:33:39PM +0300, Mika Westerberg wrote:
> > USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
> > packet that is sent as response of hot plug/unplug events. This field
> > tells whether the acknowledgment is for plug or unplug event. This needs
> > to be set accordingly in order the router to send further hot plug
> > notifications.
> > 
> > To make it simpler we fill the field unconditionally. Legacy devices do
> > not look at this field so there should be no problems with them.
> > 
> > While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
> > log message accordingly. The function is only used to ack plug/unplug
> > events.
> > 
> > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> 
> First 3 patches look "trivial" enough for me to take right now, any
> objection to that?

No objections from my side :)

> Should I be using my usb tree for this?

Yes, I think it makes sense now that this is also under USB IF umbrella.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 4/9] thunderbolt: Add initial support for USB4
  2019-12-17 12:33 ` [PATCH v2 4/9] thunderbolt: Add initial support for USB4 Mika Westerberg
@ 2019-12-18  9:34   ` Nicholas Johnson
  2019-12-18 14:36     ` Greg Kroah-Hartman
  0 siblings, 1 reply; 21+ messages in thread
From: Nicholas Johnson @ 2019-12-18  9:34 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Lukas Wunner, Greg Kroah-Hartman, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:40PM +0300, Mika Westerberg wrote:
> USB4 is the public specification based on Thunderbolt 3 protocol. There
> are some differences in register layouts and flows. In addition to PCIe
> and DP tunneling, USB4 supports tunneling of USB 3.x. USB4 is also
> backward compatible with Thunderbolt 3 (and older generations but the
> spec only talks about 3rd generation). USB4 compliant devices can be
> identified by checking USB4 version field in router configuration space.
> 
> This patch adds initial support for USB4 compliant hosts and devices
> which enables following features provided by the existing functionality
> in the driver:
> 
>   - PCIe tunneling
>   - Display Port tunneling
Nitpick: DisplayPort is a single word.

>   - Host and device NVM firmware upgrade
>   - P2P networking
> 
> This brings the USB4 support to the same level that we already have for
> Thunderbolt 1, 2 and 3 devices.
> 
> Note the spec talks about host and device "routers" but in the driver we
> still use term "switch" in most places. Both can be used interchangeably.
> 
> Co-developed-by: Rajmohan Mani <rajmohan.mani@intel.com>
> Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> ---
>  drivers/thunderbolt/Makefile  |   2 +-
>  drivers/thunderbolt/eeprom.c  |  53 ++-
>  drivers/thunderbolt/nhi.c     |   3 +
>  drivers/thunderbolt/nhi.h     |   2 +
>  drivers/thunderbolt/switch.c  | 382 +++++++++++++-----
>  drivers/thunderbolt/tb.c      |  20 +-
>  drivers/thunderbolt/tb.h      |  36 ++
>  drivers/thunderbolt/tb_regs.h |  36 +-
>  drivers/thunderbolt/tunnel.c  |  11 +-
>  drivers/thunderbolt/usb4.c    | 724 ++++++++++++++++++++++++++++++++++
>  drivers/thunderbolt/xdomain.c |   6 +
>  11 files changed, 1158 insertions(+), 117 deletions(-)
>  create mode 100644 drivers/thunderbolt/usb4.c
> 
> diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
> index 001187c577bf..c0b2fd73dfbd 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  obj-${CONFIG_THUNDERBOLT} := 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
> +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o usb4.o
> diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c
> index 540e0105bcc0..921d164b3f35 100644
> --- a/drivers/thunderbolt/eeprom.c
> +++ b/drivers/thunderbolt/eeprom.c
> @@ -487,6 +487,37 @@ static int tb_drom_copy_nvm(struct tb_switch *sw, u16 *size)
>  	return ret;
>  }
>  
> +static int usb4_copy_host_drom(struct tb_switch *sw, u16 *size)
> +{
> +	int ret;
> +
> +	ret = usb4_switch_drom_read(sw, 14, size, sizeof(*size));
> +	if (ret)
> +		return ret;
> +
> +	/* Size includes CRC8 + UID + CRC32 */
> +	*size += 1 + 8 + 4;
> +	sw->drom = kzalloc(*size, GFP_KERNEL);
> +	if (!sw->drom)
> +		return -ENOMEM;
> +
> +	ret = usb4_switch_drom_read(sw, 0, sw->drom, *size);
> +	if (ret) {
> +		kfree(sw->drom);
> +		sw->drom = NULL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int tb_drom_read_n(struct tb_switch *sw, u16 offset, u8 *val,
> +			  size_t count)
> +{
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_drom_read(sw, offset, val, count);
> +	return tb_eeprom_read_n(sw, offset, val, count);
> +}
> +
>  /**
>   * tb_drom_read - copy drom to sw->drom and parse it
>   */
> @@ -512,14 +543,26 @@ int tb_drom_read(struct tb_switch *sw)
>  			goto parse;
>  
>  		/*
> -		 * The root switch contains only a dummy drom (header only,
> -		 * no entries). Hardcode the configuration here.
> +		 * USB4 hosts may support reading DROM through router
> +		 * operations.
>  		 */
> -		tb_drom_read_uid_only(sw, &sw->uid);
> +		if (tb_switch_is_usb4(sw)) {
> +			usb4_switch_read_uid(sw, &sw->uid);
> +			if (!usb4_copy_host_drom(sw, &size))
> +				goto parse;
> +		} else {
> +			/*
> +			 * The root switch contains only a dummy drom
> +			 * (header only, no entries). Hardcode the
> +			 * configuration here.
> +			 */
> +			tb_drom_read_uid_only(sw, &sw->uid);
> +		}
> +
>  		return 0;
>  	}
>  
> -	res = tb_eeprom_read_n(sw, 14, (u8 *) &size, 2);
> +	res = tb_drom_read_n(sw, 14, (u8 *) &size, 2);
>  	if (res)
>  		return res;
>  	size &= 0x3ff;
> @@ -533,7 +576,7 @@ int tb_drom_read(struct tb_switch *sw)
>  	sw->drom = kzalloc(size, GFP_KERNEL);
>  	if (!sw->drom)
>  		return -ENOMEM;
> -	res = tb_eeprom_read_n(sw, 0, sw->drom, size);
> +	res = tb_drom_read_n(sw, 0, sw->drom, size);
>  	if (res)
>  		goto err;
>  
> diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c
> index 641b21b54460..1be491ecbb45 100644
> --- a/drivers/thunderbolt/nhi.c
> +++ b/drivers/thunderbolt/nhi.c
> @@ -1271,6 +1271,9 @@ static struct pci_device_id nhi_ids[] = {
>  	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ICL_NHI1),
>  	  .driver_data = (kernel_ulong_t)&icl_nhi_ops },
>  
> +	/* Any USB4 compliant host */
> +	{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_USB4, ~0) },
> +
>  	{ 0,}
>  };
>  
> diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h
> index b7b973949f8e..5d276ee9b38e 100644
> --- a/drivers/thunderbolt/nhi.h
> +++ b/drivers/thunderbolt/nhi.h
> @@ -74,4 +74,6 @@ extern const struct tb_nhi_ops icl_nhi_ops;
>  #define PCI_DEVICE_ID_INTEL_ICL_NHI1			0x8a0d
>  #define PCI_DEVICE_ID_INTEL_ICL_NHI0			0x8a17
>  
> +#define PCI_CLASS_SERIAL_USB_USB4			0x0c0340
> +
>  #endif
> diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
> index 9c72521cb298..c1d5cd7e0631 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -163,10 +163,12 @@ static int nvm_validate_and_write(struct tb_switch *sw)
>  		image_size -= hdr_size;
>  	}
>  
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_nvm_write(sw, 0, buf, image_size);
>  	return dma_port_flash_write(sw->dma_port, 0, buf, image_size);
>  }
>  
> -static int nvm_authenticate_host(struct tb_switch *sw)
> +static int nvm_authenticate_host_dma_port(struct tb_switch *sw)
>  {
>  	int ret = 0;
>  
> @@ -206,7 +208,7 @@ static int nvm_authenticate_host(struct tb_switch *sw)
>  	return ret;
>  }
>  
> -static int nvm_authenticate_device(struct tb_switch *sw)
> +static int nvm_authenticate_device_dma_port(struct tb_switch *sw)
>  {
>  	int ret, retries = 10;
>  
> @@ -251,6 +253,78 @@ static int nvm_authenticate_device(struct tb_switch *sw)
>  	return -ETIMEDOUT;
>  }
>  
> +static void nvm_authenticate_start_dma_port(struct tb_switch *sw)
> +{
> +	struct pci_dev *root_port;
> +
> +	/*
> +	 * During host router NVM upgrade we should not allow root port to
> +	 * go into D3cold because some root ports cannot trigger PME
> +	 * itself. To be on the safe side keep the root port in D0 during
> +	 * the whole upgrade process.
> +	 */
> +	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
> +	if (root_port)
> +		pm_runtime_get_noresume(&root_port->dev);
> +}
> +
> +static void nvm_authenticate_complete_dma_port(struct tb_switch *sw)
> +{
> +	struct pci_dev *root_port;
> +
> +	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
> +	if (root_port)
> +		pm_runtime_put(&root_port->dev);
> +}
> +
> +static inline bool nvm_readable(struct tb_switch *sw)
> +{
> +	if (tb_switch_is_usb4(sw)) {
> +		/*
> +		 * USB4 devices must support NVM operations but it is
> +		 * optional for hosts. Therefore we query the NVM sector
> +		 * size here and if it is supported assume NVM
> +		 * operations are implemented.
> +		 */
> +		return usb4_switch_nvm_sector_size(sw) > 0;
> +	}
> +
> +	/* Thunderbolt 2 and 3 devices support NVM through DMA port */
> +	return !!sw->dma_port;
> +}
> +
> +static inline bool nvm_upgradeable(struct tb_switch *sw)
> +{
> +	if (sw->no_nvm_upgrade)
> +		return false;
> +	return nvm_readable(sw);
> +}
> +
> +static inline int nvm_read(struct tb_switch *sw, unsigned int address,
> +			   void *buf, size_t size)
> +{
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_nvm_read(sw, address, buf, size);
> +	return dma_port_flash_read(sw->dma_port, address, buf, size);
> +}
> +
> +static int nvm_authenticate(struct tb_switch *sw)
> +{
> +	int ret;
> +
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_nvm_authenticate(sw);
> +
> +	if (!tb_route(sw)) {
> +		nvm_authenticate_start_dma_port(sw);
> +		ret = nvm_authenticate_host_dma_port(sw);
> +	} else {
> +		ret = nvm_authenticate_device_dma_port(sw);
> +	}
> +
> +	return ret;
> +}
> +
>  static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val,
>  			      size_t bytes)
>  {
> @@ -264,7 +338,7 @@ static int tb_switch_nvm_read(void *priv, unsigned int offset, void *val,
>  		goto out;
>  	}
>  
> -	ret = dma_port_flash_read(sw->dma_port, offset, val, bytes);
> +	ret = nvm_read(sw, offset, val, bytes);
>  	mutex_unlock(&sw->tb->lock);
>  
>  out:
> @@ -341,9 +415,21 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
>  	u32 val;
>  	int ret;
>  
> -	if (!sw->dma_port)
> +	if (!nvm_readable(sw))
>  		return 0;
>  
> +	/*
> +	 * The NVM format of non-Intel hardware is not known so
> +	 * currently restrict NVM upgrade for Intel hardware. We may
> +	 * relax this in the future when we learn other NVM formats.
> +	 */
> +	if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL) {
> +		dev_info(&sw->dev,
> +			 "NVM format of vendor %#x is not known, disabling NVM upgrade\n",
> +			 sw->config.vendor_id);
> +		return 0;
> +	}
> +
>  	nvm = kzalloc(sizeof(*nvm), GFP_KERNEL);
>  	if (!nvm)
>  		return -ENOMEM;
> @@ -358,8 +444,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
>  	if (!sw->safe_mode) {
>  		u32 nvm_size, hdr_size;
>  
> -		ret = dma_port_flash_read(sw->dma_port, NVM_FLASH_SIZE, &val,
> -					  sizeof(val));
> +		ret = nvm_read(sw, NVM_FLASH_SIZE, &val, sizeof(val));
>  		if (ret)
>  			goto err_ida;
>  
> @@ -367,8 +452,7 @@ static int tb_switch_nvm_add(struct tb_switch *sw)
>  		nvm_size = (SZ_1M << (val & 7)) / 8;
>  		nvm_size = (nvm_size - hdr_size) / 2;
>  
> -		ret = dma_port_flash_read(sw->dma_port, NVM_VERSION, &val,
> -					  sizeof(val));
> +		ret = nvm_read(sw, NVM_VERSION, &val, sizeof(val));
>  		if (ret)
>  			goto err_ida;
>  
> @@ -619,6 +703,24 @@ int tb_port_clear_counter(struct tb_port *port, int counter)
>  	return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3);
>  }
>  
> +/**
> + * tb_port_unlock() - Unlock downstream port
> + * @port: Port to unlock
> + *
> + * Needed for USB4 but can be called for any CIO/USB4 ports. Makes the
> + * downstream router accessible for CM.
> + */
> +int tb_port_unlock(struct tb_port *port)
> +{
> +	if (tb_switch_is_icm(port->sw))
> +		return 0;
> +	if (!tb_port_is_null(port))
> +		return -EINVAL;
> +	if (tb_switch_is_usb4(port->sw))
> +		return usb4_port_unlock(port);
> +	return 0;
> +}
> +
>  /**
>   * tb_init_port() - initialize a port
>   *
> @@ -650,6 +752,10 @@ static int tb_init_port(struct tb_port *port)
>  			port->cap_phy = cap;
>  		else
>  			tb_port_WARN(port, "non switch port without a PHY\n");
> +
> +		cap = tb_port_find_cap(port, TB_PORT_CAP_USB4);
> +		if (cap > 0)
> +			port->cap_usb4 = cap;
>  	} else if (port->port != 0) {
>  		cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP);
>  		if (cap > 0)
> @@ -1088,20 +1194,38 @@ int tb_dp_port_enable(struct tb_port *port, bool enable)
>  
>  /* switch utility functions */
>  
> -static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw)
> +static const char *tb_switch_generation_name(const struct tb_switch *sw)
> +{
> +	switch (sw->generation) {
> +	case 1:
> +		return "Thunderbolt 1";
> +	case 2:
> +		return "Thunderbolt 2";
> +	case 3:
> +		return "Thunderbolt 3";
> +	case 4:
> +		return "USB4";
> +	default:
> +		return "Unknown";
> +	}
> +}
> +
> +static void tb_dump_switch(const struct tb *tb, const struct tb_switch *sw)
>  {
> -	tb_dbg(tb, " Switch: %x:%x (Revision: %d, TB Version: %d)\n",
> -	       sw->vendor_id, sw->device_id, sw->revision,
> -	       sw->thunderbolt_version);
> -	tb_dbg(tb, "  Max Port Number: %d\n", sw->max_port_number);
> +	const struct tb_regs_switch_header *regs = &sw->config;
> +
> +	tb_dbg(tb, " %s Switch: %x:%x (Revision: %d, TB Version: %d)\n",
> +	       tb_switch_generation_name(sw), regs->vendor_id, regs->device_id,
> +	       regs->revision, regs->thunderbolt_version);
> +	tb_dbg(tb, "  Max Port Number: %d\n", regs->max_port_number);
>  	tb_dbg(tb, "  Config:\n");
>  	tb_dbg(tb,
>  		"   Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n",
> -	       sw->upstream_port_number, sw->depth,
> -	       (((u64) sw->route_hi) << 32) | sw->route_lo,
> -	       sw->enabled, sw->plug_events_delay);
> +	       regs->upstream_port_number, regs->depth,
> +	       (((u64) regs->route_hi) << 32) | regs->route_lo,
> +	       regs->enabled, regs->plug_events_delay);
>  	tb_dbg(tb, "   unknown1: %#x unknown4: %#x\n",
> -	       sw->__unknown1, sw->__unknown4);
> +	       regs->__unknown1, regs->__unknown4);
>  }
>  
>  /**
> @@ -1148,6 +1272,10 @@ static int tb_plug_events_active(struct tb_switch *sw, bool active)
>  	if (res)
>  		return res;
>  
> +	/* Plug events are always enabled in USB4 */
> +	if (tb_switch_is_usb4(sw))
> +		return 0;
> +
>  	res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1);
>  	if (res)
>  		return res;
> @@ -1359,30 +1487,6 @@ static ssize_t lanes_show(struct device *dev, struct device_attribute *attr,
>  static DEVICE_ATTR(rx_lanes, 0444, lanes_show, NULL);
>  static DEVICE_ATTR(tx_lanes, 0444, lanes_show, NULL);
>  
> -static void nvm_authenticate_start(struct tb_switch *sw)
> -{
> -	struct pci_dev *root_port;
> -
> -	/*
> -	 * During host router NVM upgrade we should not allow root port to
> -	 * go into D3cold because some root ports cannot trigger PME
> -	 * itself. To be on the safe side keep the root port in D0 during
> -	 * the whole upgrade process.
> -	 */
> -	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
> -	if (root_port)
> -		pm_runtime_get_noresume(&root_port->dev);
> -}
> -
> -static void nvm_authenticate_complete(struct tb_switch *sw)
> -{
> -	struct pci_dev *root_port;
> -
> -	root_port = pci_find_pcie_root_port(sw->tb->nhi->pdev);
> -	if (root_port)
> -		pm_runtime_put(&root_port->dev);
> -}
> -
>  static ssize_t nvm_authenticate_show(struct device *dev,
>  	struct device_attribute *attr, char *buf)
>  {
> @@ -1431,17 +1535,7 @@ static ssize_t nvm_authenticate_store(struct device *dev,
>  			goto exit_unlock;
>  
>  		sw->nvm->authenticating = true;
> -
> -		if (!tb_route(sw)) {
> -			/*
> -			 * Keep root port from suspending as long as the
> -			 * NVM upgrade process is running.
> -			 */
> -			nvm_authenticate_start(sw);
> -			ret = nvm_authenticate_host(sw);
> -		} else {
> -			ret = nvm_authenticate_device(sw);
> -		}
> +		ret = nvm_authenticate(sw);
>  	}
>  
>  exit_unlock:
> @@ -1556,11 +1650,11 @@ static umode_t switch_attr_is_visible(struct kobject *kobj,
>  			return attr->mode;
>  		return 0;
>  	} else if (attr == &dev_attr_nvm_authenticate.attr) {
> -		if (sw->dma_port && !sw->no_nvm_upgrade)
> +		if (nvm_upgradeable(sw))
>  			return attr->mode;
>  		return 0;
>  	} else if (attr == &dev_attr_nvm_version.attr) {
> -		if (sw->dma_port)
> +		if (nvm_readable(sw))
>  			return attr->mode;
>  		return 0;
>  	} else if (attr == &dev_attr_boot.attr) {
> @@ -1672,6 +1766,9 @@ static int tb_switch_get_generation(struct tb_switch *sw)
>  		return 3;
>  
>  	default:
> +		if (tb_switch_is_usb4(sw))
> +			return 4;
> +
>  		/*
>  		 * For unknown switches assume generation to be 1 to be
>  		 * on the safe side.
> @@ -1682,6 +1779,19 @@ static int tb_switch_get_generation(struct tb_switch *sw)
>  	}
>  }
>  
> +static bool tb_switch_exceeds_max_depth(const struct tb_switch *sw, int depth)
> +{
> +	int max_depth;
> +
> +	if (tb_switch_is_usb4(sw) ||
> +	    (sw->tb->root_switch && tb_switch_is_usb4(sw->tb->root_switch)))
> +		max_depth = USB4_SWITCH_MAX_DEPTH;
> +	else
> +		max_depth = TB_SWITCH_MAX_DEPTH;
> +
> +	return depth > max_depth;
> +}
> +
>  /**
>   * tb_switch_alloc() - allocate a switch
>   * @tb: Pointer to the owning domain
> @@ -1703,10 +1813,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
>  	int upstream_port;
>  	int i, ret, depth;
>  
> -	/* Make sure we do not exceed maximum topology limit */
> +	/* Unlock the downstream port so we can access the switch below */
> +	if (route) {
> +		struct tb_switch *parent_sw = tb_to_switch(parent);
> +		struct tb_port *down;
> +
> +		down = tb_port_at(route, parent_sw);
> +		tb_port_unlock(down);
> +	}
> +
>  	depth = tb_route_length(route);
> -	if (depth > TB_SWITCH_MAX_DEPTH)
> -		return ERR_PTR(-EADDRNOTAVAIL);
>  
>  	upstream_port = tb_cfg_get_upstream_port(tb->ctl, route);
>  	if (upstream_port < 0)
> @@ -1721,8 +1837,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
>  	if (ret)
>  		goto err_free_sw_ports;
>  
> +	sw->generation = tb_switch_get_generation(sw);
> +
>  	tb_dbg(tb, "current switch config:\n");
> -	tb_dump_switch(tb, &sw->config);
> +	tb_dump_switch(tb, sw);
>  
>  	/* configure switch */
>  	sw->config.upstream_port_number = upstream_port;
> @@ -1731,6 +1849,10 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
>  	sw->config.route_lo = lower_32_bits(route);
>  	sw->config.enabled = 0;
>  
> +	/* Make sure we do not exceed maximum topology limit */
> +	if (tb_switch_exceeds_max_depth(sw, depth))
> +		return ERR_PTR(-EADDRNOTAVAIL);
> +
>  	/* initialize ports */
>  	sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports),
>  				GFP_KERNEL);
> @@ -1745,14 +1867,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, struct device *parent,
>  		sw->ports[i].port = i;
>  	}
>  
> -	sw->generation = tb_switch_get_generation(sw);
> -
>  	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
> -	if (ret < 0) {
> -		tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
> -		goto err_free_sw_ports;
> -	}
> -	sw->cap_plug_events = ret;
> +	if (ret > 0)
> +		sw->cap_plug_events = ret;
>  
>  	ret = tb_switch_find_vse_cap(sw, TB_VSE_CAP_LINK_CONTROLLER);
>  	if (ret > 0)
> @@ -1823,7 +1940,8 @@ tb_switch_alloc_safe_mode(struct tb *tb, struct device *parent, u64 route)
>   *
>   * Call this function before the switch is added to the system. It will
>   * upload configuration to the switch and makes it available for the
> - * connection manager to use.
> + * connection manager to use. Can be called to the switch again after
> + * resume from low power states to re-initialize it.
>   *
>   * Return: %0 in case of success and negative errno in case of failure
>   */
> @@ -1834,21 +1952,50 @@ int tb_switch_configure(struct tb_switch *sw)
>  	int ret;
>  
>  	route = tb_route(sw);
> -	tb_dbg(tb, "initializing Switch at %#llx (depth: %d, up port: %d)\n",
> -	       route, tb_route_length(route), sw->config.upstream_port_number);
>  
> -	if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
> -		tb_sw_warn(sw, "unknown switch vendor id %#x\n",
> -			   sw->config.vendor_id);
> +	tb_dbg(tb, "%s Switch at %#llx (depth: %d, up port: %d)\n",
> +	       sw->config.enabled ? "restoring " : "initializing", route,
> +	       tb_route_length(route), sw->config.upstream_port_number);
>  
>  	sw->config.enabled = 1;
>  
> -	/* upload configuration */
> -	ret = tb_sw_write(sw, 1 + (u32 *)&sw->config, TB_CFG_SWITCH, 1, 3);
> -	if (ret)
> -		return ret;
> +	if (tb_switch_is_usb4(sw)) {
> +		/*
> +		 * For USB4 devices, we need to program the CM version
> +		 * accordingly so that it knows to expose all the
> +		 * additional capabilities.
> +		 */
> +		sw->config.cmuv = USB4_VERSION_1_0;
> +
> +		/* Enumerate the switch */
> +		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
> +				  ROUTER_CS_1, 4);
> +		if (ret)
> +			return ret;
>  
> -	ret = tb_lc_configure_link(sw);
> +		ret = usb4_switch_setup(sw);
> +		if (ret)
> +			return ret;
> +
> +		ret = usb4_switch_configure_link(sw);
> +	} else {
> +		if (sw->config.vendor_id != PCI_VENDOR_ID_INTEL)
> +			tb_sw_warn(sw, "unknown switch vendor id %#x\n",
> +				   sw->config.vendor_id);
> +
> +		if (!sw->cap_plug_events) {
> +			tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
> +			return -ENODEV;
> +		}
> +
> +		/* Enumerate the switch */
> +		ret = tb_sw_write(sw, (u32 *)&sw->config + 1, TB_CFG_SWITCH,
> +				  ROUTER_CS_1, 3);
> +		if (ret)
> +			return ret;
> +
> +		ret = tb_lc_configure_link(sw);
> +	}
>  	if (ret)
>  		return ret;
>  
> @@ -1857,18 +2004,32 @@ int tb_switch_configure(struct tb_switch *sw)
>  
>  static int tb_switch_set_uuid(struct tb_switch *sw)
>  {
> +	bool uid = false;
>  	u32 uuid[4];
>  	int ret;
>  
>  	if (sw->uuid)
>  		return 0;
>  
> -	/*
> -	 * The newer controllers include fused UUID as part of link
> -	 * controller specific registers
> -	 */
> -	ret = tb_lc_read_uuid(sw, uuid);
> -	if (ret) {
> +	if (tb_switch_is_usb4(sw)) {
> +		ret = usb4_switch_read_uid(sw, &sw->uid);
> +		if (ret)
> +			return ret;
> +		uid = true;
> +	} else {
> +		/*
> +		 * The newer controllers include fused UUID as part of
> +		 * link controller specific registers
> +		 */
> +		ret = tb_lc_read_uuid(sw, uuid);
> +		if (ret) {
> +			if (ret != -EINVAL)
> +				return ret;
> +			uid = true;
> +		}
> +	}
> +
> +	if (uid) {
>  		/*
>  		 * ICM generates UUID based on UID and fills the upper
>  		 * two words with ones. This is not strictly following
> @@ -1935,7 +2096,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
>  	nvm_get_auth_status(sw, &status);
>  	if (status) {
>  		if (!tb_route(sw))
> -			nvm_authenticate_complete(sw);
> +			nvm_authenticate_complete_dma_port(sw);
>  		return 0;
>  	}
>  
> @@ -1950,7 +2111,7 @@ static int tb_switch_add_dma_port(struct tb_switch *sw)
>  
>  	/* Now we can allow root port to suspend again */
>  	if (!tb_route(sw))
> -		nvm_authenticate_complete(sw);
> +		nvm_authenticate_complete_dma_port(sw);
>  
>  	if (status) {
>  		tb_sw_info(sw, "switch flash authentication failed\n");
> @@ -2004,6 +2165,8 @@ static bool tb_switch_lane_bonding_possible(struct tb_switch *sw)
>  	if (!up->dual_link_port || !up->dual_link_port->remote)
>  		return false;
>  
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_lane_bonding_possible(sw);
>  	return tb_lc_lane_bonding_possible(sw);
>  }
>  
> @@ -2240,7 +2403,11 @@ void tb_switch_remove(struct tb_switch *sw)
>  
>  	if (!sw->is_unplugged)
>  		tb_plug_events_active(sw, false);
> -	tb_lc_unconfigure_link(sw);
> +
> +	if (tb_switch_is_usb4(sw))
> +		usb4_switch_unconfigure_link(sw);
> +	else
> +		tb_lc_unconfigure_link(sw);
>  
>  	tb_switch_nvm_remove(sw);
>  
> @@ -2298,7 +2465,10 @@ int tb_switch_resume(struct tb_switch *sw)
>  			return err;
>  		}
>  
> -		err = tb_drom_read_uid_only(sw, &uid);
> +		if (tb_switch_is_usb4(sw))
> +			err = usb4_switch_read_uid(sw, &uid);
> +		else
> +			err = tb_drom_read_uid_only(sw, &uid);
>  		if (err) {
>  			tb_sw_warn(sw, "uid read failed\n");
>  			return err;
> @@ -2311,16 +2481,7 @@ int tb_switch_resume(struct tb_switch *sw)
>  		}
>  	}
>  
> -	/* upload configuration */
> -	err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3);
> -	if (err)
> -		return err;
> -
> -	err = tb_lc_configure_link(sw);
> -	if (err)
> -		return err;
> -
> -	err = tb_plug_events_active(sw, true);
> +	err = tb_switch_configure(sw);
>  	if (err)
>  		return err;
>  
> @@ -2336,8 +2497,14 @@ int tb_switch_resume(struct tb_switch *sw)
>  				tb_sw_set_unplugged(port->remote->sw);
>  			else if (port->xdomain)
>  				port->xdomain->is_unplugged = true;
> -		} else if (tb_port_has_remote(port)) {
> -			if (tb_switch_resume(port->remote->sw)) {
> +		} else if (tb_port_has_remote(port) || port->xdomain) {
> +			/*
> +			 * Always unlock the port so the downstream
> +			 * switch/domain is accessible.
> +			 */
> +			if (tb_port_unlock(port))
> +				tb_port_warn(port, "failed to unlock port\n");
> +			if (port->remote && tb_switch_resume(port->remote->sw)) {
>  				tb_port_warn(port,
>  					     "lost during suspend, disconnecting\n");
>  				tb_sw_set_unplugged(port->remote->sw);
> @@ -2361,7 +2528,10 @@ void tb_switch_suspend(struct tb_switch *sw)
>  			tb_switch_suspend(port->remote->sw);
>  	}
>  
> -	tb_lc_set_sleep(sw);
> +	if (tb_switch_is_usb4(sw))
> +		usb4_switch_set_sleep(sw);
> +	else
> +		tb_lc_set_sleep(sw);
>  }
>  
>  /**
> @@ -2374,6 +2544,8 @@ void tb_switch_suspend(struct tb_switch *sw)
>   */
>  bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
>  {
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_query_dp_resource(sw, in);
>  	return tb_lc_dp_sink_query(sw, in);
>  }
>  
> @@ -2388,6 +2560,8 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
>   */
>  int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
>  {
> +	if (tb_switch_is_usb4(sw))
> +		return usb4_switch_alloc_dp_resource(sw, in);
>  	return tb_lc_dp_sink_alloc(sw, in);
>  }
>  
> @@ -2401,10 +2575,16 @@ int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
>   */
>  void tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
>  {
> -	if (tb_lc_dp_sink_dealloc(sw, in)) {
> +	int ret;
> +
> +	if (tb_switch_is_usb4(sw))
> +		ret = usb4_switch_dealloc_dp_resource(sw, in);
> +	else
> +		ret = tb_lc_dp_sink_dealloc(sw, in);
> +
> +	if (ret)
>  		tb_sw_warn(sw, "failed to de-allocate DP resource for port %d\n",
>  			   in->port);
> -	}
>  }
>  
>  struct tb_sw_lookup {
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index e54d0d89a32d..6b99dcd1790c 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -365,12 +365,15 @@ static struct tb_port *tb_find_unused_port(struct tb_switch *sw,
>  static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
>  					 const struct tb_port *port)
>  {
> +	struct tb_port *down = NULL;
> +
>  	/*
>  	 * To keep plugging devices consistently in the same PCIe
> -	 * hierarchy, do mapping here for root switch downstream PCIe
> -	 * ports.
> +	 * hierarchy, do mapping here for switch downstream PCIe ports.
>  	 */
> -	if (!tb_route(sw)) {
> +	if (tb_switch_is_usb4(sw)) {
> +		down = usb4_switch_map_pcie_down(sw, port);
> +	} else if (!tb_route(sw)) {
>  		int phy_port = tb_phy_port_from_link(port->port);
>  		int index;
>  
> @@ -391,12 +394,17 @@ static struct tb_port *tb_find_pcie_down(struct tb_switch *sw,
>  		/* Validate the hard-coding */
>  		if (WARN_ON(index > sw->config.max_port_number))
>  			goto out;
> -		if (WARN_ON(!tb_port_is_pcie_down(&sw->ports[index])))
> +
> +		down = &sw->ports[index];
> +	}
> +
> +	if (down) {
> +		if (WARN_ON(!tb_port_is_pcie_down(down)))
>  			goto out;
> -		if (WARN_ON(tb_pci_port_is_enabled(&sw->ports[index])))
> +		if (WARN_ON(tb_pci_port_is_enabled(down)))
>  			goto out;
>  
> -		return &sw->ports[index];
> +		return down;
>  	}
>  
>  out:
> diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
> index ade1c7c77db1..0158f0e9858c 100644
> --- a/drivers/thunderbolt/tb.h
> +++ b/drivers/thunderbolt/tb.h
> @@ -44,6 +44,7 @@ struct tb_switch_nvm {
>  
>  #define TB_SWITCH_KEY_SIZE		32
>  #define TB_SWITCH_MAX_DEPTH		6
> +#define USB4_SWITCH_MAX_DEPTH		5
>  
>  /**
>   * struct tb_switch - a thunderbolt switch
> @@ -129,6 +130,7 @@ struct tb_switch {
>   * @xdomain: Remote host (%NULL if not connected)
>   * @cap_phy: Offset, zero if not found
>   * @cap_adap: Offset of the adapter specific capability (%0 if not present)
> + * @cap_usb4: Offset to the USB4 port capability (%0 if not present)
>   * @port: Port number on switch
>   * @disabled: Disabled by eeprom
>   * @bonded: true if the port is bonded (two lanes combined as one)
> @@ -146,6 +148,7 @@ struct tb_port {
>  	struct tb_xdomain *xdomain;
>  	int cap_phy;
>  	int cap_adap;
> +	int cap_usb4;
>  	u8 port;
>  	bool disabled;
>  	bool bonded;
> @@ -637,6 +640,17 @@ static inline bool tb_switch_is_titan_ridge(const struct tb_switch *sw)
>  	}
>  }
>  
> +/**
> + * tb_switch_is_usb4() - Is the switch USB4 compliant
> + * @sw: Switch to check
> + *
> + * Returns true if the @sw is USB4 compliant router, false otherwise.
> + */
> +static inline bool tb_switch_is_usb4(const struct tb_switch *sw)
> +{
> +	return sw->config.thunderbolt_version == USB4_VERSION_1_0;
> +}
> +
>  /**
>   * tb_switch_is_icm() - Is the switch handled by ICM firmware
>   * @sw: Switch to check
> @@ -662,6 +676,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
>  int tb_port_add_nfc_credits(struct tb_port *port, int credits);
>  int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
>  int tb_port_clear_counter(struct tb_port *port, int counter);
> +int tb_port_unlock(struct tb_port *port);
>  int tb_port_alloc_in_hopid(struct tb_port *port, int hopid, int max_hopid);
>  void tb_port_release_in_hopid(struct tb_port *port, int hopid);
>  int tb_port_alloc_out_hopid(struct tb_port *port, int hopid, int max_hopid);
> @@ -736,4 +751,25 @@ void tb_xdomain_remove(struct tb_xdomain *xd);
>  struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
>  						 u8 depth);
>  
> +int usb4_switch_setup(struct tb_switch *sw);
> +int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid);
> +int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
> +			  size_t size);
> +int usb4_switch_configure_link(struct tb_switch *sw);
> +void usb4_switch_unconfigure_link(struct tb_switch *sw);
> +bool usb4_switch_lane_bonding_possible(struct tb_switch *sw);
> +int usb4_switch_set_sleep(struct tb_switch *sw);
> +int usb4_switch_nvm_sector_size(struct tb_switch *sw);
> +int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
> +			 size_t size);
> +int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
> +			  const void *buf, size_t size);
> +int usb4_switch_nvm_authenticate(struct tb_switch *sw);
> +bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
> +int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
> +int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
> +struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
> +					  const struct tb_port *port);
> +
> +int usb4_port_unlock(struct tb_port *port);
>  #endif
> diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
> index 7ee45b73c7f7..47f73f992412 100644
> --- a/drivers/thunderbolt/tb_regs.h
> +++ b/drivers/thunderbolt/tb_regs.h
> @@ -41,6 +41,7 @@ enum tb_port_cap {
>  	TB_PORT_CAP_TIME1		= 0x03,
>  	TB_PORT_CAP_ADAP		= 0x04,
>  	TB_PORT_CAP_VSE			= 0x05,
> +	TB_PORT_CAP_USB4		= 0x06,
>  };
>  
>  enum tb_port_state {
> @@ -164,10 +165,36 @@ struct tb_regs_switch_header {
>  				  * milliseconds. Writing 0x00 is interpreted
>  				  * as 255ms.
>  				  */
> -	u32 __unknown4:16;
> +	u32 cmuv:8;
> +	u32 __unknown4:8;
>  	u32 thunderbolt_version:8;
>  } __packed;
>  
> +/* USB4 version 1.0 */
> +#define USB4_VERSION_1_0			0x20
> +
> +#define ROUTER_CS_1				0x01
> +#define ROUTER_CS_4				0x04
> +#define ROUTER_CS_5				0x05
> +#define ROUTER_CS_5_SLP				BIT(0)
> +#define ROUTER_CS_5_C3S				BIT(23)
> +#define ROUTER_CS_5_PTO				BIT(24)
> +#define ROUTER_CS_5_HCO				BIT(26)
> +#define ROUTER_CS_5_CV				BIT(31)
> +#define ROUTER_CS_6				0x06
> +#define ROUTER_CS_6_SLPR			BIT(0)
> +#define ROUTER_CS_6_TNS				BIT(1)
> +#define ROUTER_CS_6_HCI				BIT(18)
> +#define ROUTER_CS_6_CR				BIT(25)
> +#define ROUTER_CS_7				0x07
> +#define ROUTER_CS_9				0x09
> +#define ROUTER_CS_25				0x19
> +#define ROUTER_CS_26				0x1a
> +#define ROUTER_CS_26_STATUS_MASK		GENMASK(29, 24)
> +#define ROUTER_CS_26_STATUS_SHIFT		24
> +#define ROUTER_CS_26_ONS			BIT(30)
> +#define ROUTER_CS_26_OV				BIT(31)
> +
>  enum tb_port_type {
>  	TB_TYPE_INACTIVE	= 0x000000,
>  	TB_TYPE_PORT		= 0x000001,
> @@ -216,6 +243,7 @@ struct tb_regs_port_header {
>  #define ADP_CS_4_NFC_BUFFERS_MASK		GENMASK(9, 0)
>  #define ADP_CS_4_TOTAL_BUFFERS_MASK		GENMASK(29, 20)
>  #define ADP_CS_4_TOTAL_BUFFERS_SHIFT		20
> +#define ADP_CS_4_LCK				BIT(31)
>  #define ADP_CS_5				0x05
>  #define ADP_CS_5_LCA_MASK			GENMASK(28, 22)
>  #define ADP_CS_5_LCA_SHIFT			22
> @@ -237,6 +265,12 @@ struct tb_regs_port_header {
>  #define LANE_ADP_CS_1_CURRENT_WIDTH_MASK	GENMASK(25, 20)
>  #define LANE_ADP_CS_1_CURRENT_WIDTH_SHIFT	20
>  
> +/* USB4 port registers */
> +#define PORT_CS_18				0x12
> +#define PORT_CS_18_BE				BIT(8)
> +#define PORT_CS_19				0x13
> +#define PORT_CS_19_PC				BIT(3)
> +
>  /* Display Port adapter registers */
Nitpick: DisplayPort is a single word.

>  #define ADP_DP_CS_0				0x00
>  #define ADP_DP_CS_0_VIDEO_HOPID_MASK		GENMASK(26, 16)
> diff --git a/drivers/thunderbolt/tunnel.c b/drivers/thunderbolt/tunnel.c
> index 0d3463c4e24a..21d266a76b7d 100644
> --- a/drivers/thunderbolt/tunnel.c
> +++ b/drivers/thunderbolt/tunnel.c
> @@ -243,6 +243,12 @@ struct tb_tunnel *tb_tunnel_alloc_pci(struct tb *tb, struct tb_port *up,
>  	return tunnel;
>  }
>  
> +static bool tb_dp_is_usb4(const struct tb_switch *sw)
> +{
> +	/* Titan Ridge DP adapters need the same treatment as USB4 */
> +	return tb_switch_is_usb4(sw) || tb_switch_is_titan_ridge(sw);
> +}
> +
>  static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
>  {
>  	int timeout = 10;
> @@ -250,8 +256,7 @@ static int tb_dp_cm_handshake(struct tb_port *in, struct tb_port *out)
>  	int ret;
>  
>  	/* Both ends need to support this */
> -	if (!tb_switch_is_titan_ridge(in->sw) ||
> -	    !tb_switch_is_titan_ridge(out->sw))
> +	if (!tb_dp_is_usb4(in->sw) || !tb_dp_is_usb4(out->sw))
>  		return 0;
>  
>  	ret = tb_port_read(out, &val, TB_CFG_PORT,
> @@ -531,7 +536,7 @@ static int tb_dp_consumed_bandwidth(struct tb_tunnel *tunnel)
>  	u32 val, rate = 0, lanes = 0;
>  	int ret;
>  
> -	if (tb_switch_is_titan_ridge(sw)) {
> +	if (tb_dp_is_usb4(sw)) {
>  		int timeout = 10;
>  
>  		/*
> diff --git a/drivers/thunderbolt/usb4.c b/drivers/thunderbolt/usb4.c
> new file mode 100644
> index 000000000000..b84c74346d2b
> --- /dev/null
> +++ b/drivers/thunderbolt/usb4.c
> @@ -0,0 +1,724 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * USB4 specific functionality
> + *
> + * Copyright (C) 2019, Intel Corporation
> + * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
> + *	    Rajmohan Mani <rajmohan.mani@intel.com>
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/ktime.h>
> +
> +#include "tb.h"
> +
> +#define USB4_DATA_DWORDS		16
> +#define USB4_DATA_RETRIES		3
> +
> +enum usb4_switch_op {
> +	USB4_SWITCH_OP_QUERY_DP_RESOURCE = 0x10,
> +	USB4_SWITCH_OP_ALLOC_DP_RESOURCE = 0x11,
> +	USB4_SWITCH_OP_DEALLOC_DP_RESOURCE = 0x12,
> +	USB4_SWITCH_OP_NVM_WRITE = 0x20,
> +	USB4_SWITCH_OP_NVM_AUTH = 0x21,
> +	USB4_SWITCH_OP_NVM_READ = 0x22,
> +	USB4_SWITCH_OP_NVM_SET_OFFSET = 0x23,
> +	USB4_SWITCH_OP_DROM_READ = 0x24,
> +	USB4_SWITCH_OP_NVM_SECTOR_SIZE = 0x25,
> +};
> +
> +#define USB4_NVM_READ_OFFSET_MASK	GENMASK(23, 2)
> +#define USB4_NVM_READ_OFFSET_SHIFT	2
> +#define USB4_NVM_READ_LENGTH_MASK	GENMASK(27, 24)
> +#define USB4_NVM_READ_LENGTH_SHIFT	24
> +
> +#define USB4_NVM_SET_OFFSET_MASK	USB4_NVM_READ_OFFSET_MASK
> +#define USB4_NVM_SET_OFFSET_SHIFT	USB4_NVM_READ_OFFSET_SHIFT
> +
> +#define USB4_DROM_ADDRESS_MASK		GENMASK(14, 2)
> +#define USB4_DROM_ADDRESS_SHIFT		2
> +#define USB4_DROM_SIZE_MASK		GENMASK(19, 15)
> +#define USB4_DROM_SIZE_SHIFT		15
> +
> +#define USB4_NVM_SECTOR_SIZE_MASK	GENMASK(23, 0)
> +
> +typedef int (*read_block_fn)(struct tb_switch *, unsigned int, void *, size_t);
> +typedef int (*write_block_fn)(struct tb_switch *, const void *, size_t);
> +
> +static int usb4_switch_wait_for_bit(struct tb_switch *sw, u32 offset, u32 bit,
> +				    u32 value, int timeout_msec)
> +{
> +	ktime_t timeout = ktime_add_ms(ktime_get(), timeout_msec);
> +
> +	do {
> +		u32 val;
> +		int ret;
> +
> +		ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, offset, 1);
> +		if (ret)
> +			return ret;
> +
> +		if ((val & bit) == value)
> +			return 0;
> +
> +		usleep_range(50, 100);
> +	} while (ktime_before(ktime_get(), timeout));
> +
> +	return -ETIMEDOUT;
> +}
> +
> +static int usb4_switch_op_read_data(struct tb_switch *sw, void *data,
> +				    size_t dwords)
> +{
> +	if (dwords > USB4_DATA_DWORDS)
> +		return -EINVAL;
> +
> +	return tb_sw_read(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords);
> +}
> +
> +static int usb4_switch_op_write_data(struct tb_switch *sw, const void *data,
> +				     size_t dwords)
> +{
> +	if (dwords > USB4_DATA_DWORDS)
> +		return -EINVAL;
> +
> +	return tb_sw_write(sw, data, TB_CFG_SWITCH, ROUTER_CS_9, dwords);
> +}
> +
> +static int usb4_switch_op_read_metadata(struct tb_switch *sw, u32 *metadata)
> +{
> +	return tb_sw_read(sw, metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
> +}
> +
> +static int usb4_switch_op_write_metadata(struct tb_switch *sw, u32 metadata)
> +{
> +	return tb_sw_write(sw, &metadata, TB_CFG_SWITCH, ROUTER_CS_25, 1);
> +}
> +
> +static int usb4_switch_do_read_data(struct tb_switch *sw, u16 address,
> +	void *buf, size_t size, read_block_fn read_block)
> +{
> +	unsigned int retries = USB4_DATA_RETRIES;
> +	unsigned int offset;
> +
> +	offset = address & 3;
> +	address = address & ~3;
> +
> +	do {
> +		size_t nbytes = min_t(size_t, size, USB4_DATA_DWORDS * 4);
> +		unsigned int dwaddress, dwords;
> +		u8 data[USB4_DATA_DWORDS * 4];
> +		int ret;
> +
> +		dwaddress = address / 4;
> +		dwords = ALIGN(nbytes, 4) / 4;
> +
> +		ret = read_block(sw, dwaddress, data, dwords);
> +		if (ret) {
> +			if (ret == -ETIMEDOUT) {
> +				if (retries--)
> +					continue;
> +				ret = -EIO;
> +			}
> +			return ret;
> +		}
> +
> +		memcpy(buf, data + offset, nbytes);
> +
> +		size -= nbytes;
> +		address += nbytes;
> +		buf += nbytes;
> +	} while (size > 0);
> +
> +	return 0;
> +}
> +
> +static int usb4_switch_do_write_data(struct tb_switch *sw, u16 address,
> +	const void *buf, size_t size, write_block_fn write_next_block)
> +{
> +	unsigned int retries = USB4_DATA_RETRIES;
> +	unsigned int offset;
> +
> +	offset = address & 3;
> +	address = address & ~3;
> +
> +	do {
> +		u32 nbytes = min_t(u32, size, USB4_DATA_DWORDS * 4);
> +		u8 data[USB4_DATA_DWORDS * 4];
> +		int ret;
> +
> +		memcpy(data + offset, buf, nbytes);
> +
> +		ret = write_next_block(sw, data, nbytes / 4);
> +		if (ret) {
> +			if (ret == -ETIMEDOUT) {
> +				if (retries--)
> +					continue;
> +				ret = -EIO;
> +			}
> +			return ret;
> +		}
> +
> +		size -= nbytes;
> +		address += nbytes;
> +		buf += nbytes;
> +	} while (size > 0);
> +
> +	return 0;
> +}
> +
> +static int usb4_switch_op(struct tb_switch *sw, u16 opcode, u8 *status)
> +{
> +	u32 val;
> +	int ret;
> +
> +	val = opcode | ROUTER_CS_26_OV;
> +	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_wait_for_bit(sw, ROUTER_CS_26, ROUTER_CS_26_OV, 0, 500);
> +	if (ret)
> +		return ret;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_26, 1);
> +	if (val & ROUTER_CS_26_ONS)
> +		return -EOPNOTSUPP;
> +
> +	*status = (val & ROUTER_CS_26_STATUS_MASK) >> ROUTER_CS_26_STATUS_SHIFT;
> +	return 0;
> +}
> +
> +/**
> + * usb4_switch_setup() - Additional setup for USB4 device
> + * @sw: USB4 router to setup
> + *
> + * USB4 routers need additional settings in order to enable all the
> + * tunneling. This function enables USB and PCIe tunneling if it can be
> + * enabled (e.g the parent switch also supports them). If USB tunneling
> + * is not available for some reason (like that there is Thunderbolt 3
> + * switch upstream) then the internal xHCI controller is enabled
> + * instead.
> + */
> +int usb4_switch_setup(struct tb_switch *sw)
> +{
> +	struct tb_switch *parent;
> +	bool tbt3, xhci;
> +	u32 val = 0;
> +	int ret;
> +
> +	if (!tb_route(sw))
> +		return 0;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_6, 1);
> +	if (ret)
> +		return ret;
> +
> +	xhci = val & ROUTER_CS_6_HCI;
> +	tbt3 = !(val & ROUTER_CS_6_TNS);
> +
> +	tb_sw_dbg(sw, "TBT3 support: %s, xHCI: %s\n",
> +		  tbt3 ? "yes" : "no", xhci ? "yes" : "no");
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
> +	if (ret)
> +		return ret;
> +
> +	parent = tb_switch_parent(sw);
> +
> +	/* Only enable PCIe tunneling if the parent router supports it */
> +	if (tb_switch_find_port(parent, TB_TYPE_PCIE_DOWN)) {
> +		val |= ROUTER_CS_5_PTO;
> +		/* xHCI can be enabled if PCIe tunneling is supported */
> +		if (xhci & ROUTER_CS_6_HCI)
> +			val |= ROUTER_CS_5_HCO;
> +	}
> +
> +	/* TBT3 supported by the CM */
> +	val |= ROUTER_CS_5_C3S;
> +	/* Tunneling configuration is ready now */
> +	val |= ROUTER_CS_5_CV;
> +
> +	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
> +	if (ret)
> +		return ret;
> +
> +	return usb4_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_CR,
> +					ROUTER_CS_6_CR, 50);
> +}
> +
> +/**
> + * usb4_switch_read_uid() - Read UID from USB4 router
> + * @sw: USB4 router
> + *
> + * Reads 64-bit UID from USB4 router config space.
> + */
> +int usb4_switch_read_uid(struct tb_switch *sw, u64 *uid)
> +{
> +	return tb_sw_read(sw, uid, TB_CFG_SWITCH, ROUTER_CS_7, 2);
> +}
> +
> +static int usb4_switch_drom_read_block(struct tb_switch *sw,
> +				       unsigned int dwaddress, void *buf,
> +				       size_t dwords)
> +{
> +	u8 status = 0;
> +	u32 metadata;
> +	int ret;
> +
> +	metadata = (dwords << USB4_DROM_SIZE_SHIFT) & USB4_DROM_SIZE_MASK;
> +	metadata |= (dwaddress << USB4_DROM_ADDRESS_SHIFT) &
> +		USB4_DROM_ADDRESS_MASK;
> +
> +	ret = usb4_switch_op_write_metadata(sw, metadata);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_DROM_READ, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status)
> +		return -EIO;
> +
> +	return usb4_switch_op_read_data(sw, buf, dwords);
> +}
> +
> +/**
> + * usb4_switch_drom_read() - Read arbitrary bytes from USB4 router DROM
> + * @sw: USB4 router
> + *
> + * Uses USB4 router operations to read router DROM. For devices this
> + * should always work but for hosts it may return %-EOPNOTSUPP in which
> + * case the host router does not have DROM.
> + */
> +int usb4_switch_drom_read(struct tb_switch *sw, unsigned int address, void *buf,
> +			  size_t size)
> +{
> +	return usb4_switch_do_read_data(sw, address, buf, size,
> +					usb4_switch_drom_read_block);
> +}
> +
> +static int usb4_set_port_configured(struct tb_port *port, bool configured)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_port_read(port, &val, TB_CFG_PORT,
> +			   port->cap_usb4 + PORT_CS_19, 1);
> +	if (ret)
> +		return ret;
> +
> +	if (configured)
> +		val |= PORT_CS_19_PC;
> +	else
> +		val &= ~PORT_CS_19_PC;
> +
> +	return tb_port_write(port, &val, TB_CFG_PORT,
> +			     port->cap_usb4 + PORT_CS_19, 1);
> +}
> +
> +/**
> + * usb4_switch_configure_link() - Set upstream USB4 link configured
> + * @sw: USB4 router
> + *
> + * Sets the upstream USB4 link to be configured for power management
> + * purposes.
> + */
> +int usb4_switch_configure_link(struct tb_switch *sw)
> +{
> +	struct tb_port *up;
> +
> +	if (!tb_route(sw))
> +		return 0;
> +
> +	up = tb_upstream_port(sw);
> +	return usb4_set_port_configured(up, true);
> +}
> +
> +/**
> + * usb4_switch_unconfigure_link() - Un-set upstream USB4 link configuration
> + * @sw: USB4 router
> + *
> + * Reverse of usb4_switch_configure_link().
> + */
> +void usb4_switch_unconfigure_link(struct tb_switch *sw)
> +{
> +	struct tb_port *up;
> +
> +	if (sw->is_unplugged || !tb_route(sw))
> +		return;
> +
> +	up = tb_upstream_port(sw);
> +	usb4_set_port_configured(up, false);
> +}
> +
> +/**
> + * usb4_switch_lane_bonding_possible() - Are conditions met for lane bonding
> + * @sw: USB4 router
> + *
> + * Checks whether conditions are met so that lane bonding can be
> + * established with the upstream router. Call only for device routers.
> + */
> +bool usb4_switch_lane_bonding_possible(struct tb_switch *sw)
> +{
> +	struct tb_port *up;
> +	int ret;
> +	u32 val;
> +
> +	up = tb_upstream_port(sw);
> +	ret = tb_port_read(up, &val, TB_CFG_PORT, up->cap_usb4 + PORT_CS_18, 1);
> +	if (ret)
> +		return false;
> +
> +	return !!(val & PORT_CS_18_BE);
> +}
> +
> +/**
> + * usb4_switch_set_sleep() - Prepare the router to enter sleep
> + * @sw: USB4 router
> + *
> + * Enables wakes and sets sleep bit for the router. Returns when the
> + * router sleep ready bit has been asserted.
> + */
> +int usb4_switch_set_sleep(struct tb_switch *sw)
> +{
> +	int ret;
> +	u32 val;
> +
> +	/* Set sleep bit and wait for sleep ready to be asserted */
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
> +	if (ret)
> +		return ret;
> +
> +	val |= ROUTER_CS_5_SLP;
> +
> +	ret = tb_sw_write(sw, &val, TB_CFG_SWITCH, ROUTER_CS_5, 1);
> +	if (ret)
> +		return ret;
> +
> +	return usb4_switch_wait_for_bit(sw, ROUTER_CS_6, ROUTER_CS_6_SLPR,
> +					ROUTER_CS_6_SLPR, 500);
> +}
> +
> +/**
> + * usb4_switch_nvm_sector_size() - Return router NVM sector size
> + * @sw: USB4 router
> + *
> + * If the router supports NVM operations this function returns the NVM
> + * sector size in bytes. If NVM operations are not supported returns
> + * %-EOPNOTSUPP.
> + */
> +int usb4_switch_nvm_sector_size(struct tb_switch *sw)
> +{
> +	u32 metadata;
> +	u8 status;
> +	int ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SECTOR_SIZE, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status)
> +		return status == 0x2 ? -EOPNOTSUPP : -EIO;
> +
> +	ret = usb4_switch_op_read_metadata(sw, &metadata);
> +	if (ret)
> +		return ret;
> +
> +	return metadata & USB4_NVM_SECTOR_SIZE_MASK;
> +}
> +
> +static int usb4_switch_nvm_read_block(struct tb_switch *sw,
> +	unsigned int dwaddress, void *buf, size_t dwords)
> +{
> +	u8 status = 0;
> +	u32 metadata;
> +	int ret;
> +
> +	metadata = (dwords << USB4_NVM_READ_LENGTH_SHIFT) &
> +		   USB4_NVM_READ_LENGTH_MASK;
> +	metadata |= (dwaddress << USB4_NVM_READ_OFFSET_SHIFT) &
> +		   USB4_NVM_READ_OFFSET_MASK;
> +
> +	ret = usb4_switch_op_write_metadata(sw, metadata);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_READ, &status);
> +	if (ret)
> +		return ret;
> +
> +	if (status)
> +		return -EIO;
> +
> +	return usb4_switch_op_read_data(sw, buf, dwords);
> +}
> +
> +/**
> + * usb4_switch_nvm_read() - Read arbitrary bytes from router NVM
> + * @sw: USB4 router
> + * @address: Starting address in bytes
> + * @buf: Read data is placed here
> + * @size: How many bytes to read
> + *
> + * Reads NVM contents of the router. If NVM is not supported returns
> + * %-EOPNOTSUPP.
> + */
> +int usb4_switch_nvm_read(struct tb_switch *sw, unsigned int address, void *buf,
> +			 size_t size)
> +{
> +	return usb4_switch_do_read_data(sw, address, buf, size,
> +					usb4_switch_nvm_read_block);
> +}
> +
> +static int usb4_switch_nvm_set_offset(struct tb_switch *sw,
> +				      unsigned int address)
> +{
> +	u32 metadata, dwaddress;
> +	u8 status = 0;
> +	int ret;
> +
> +	dwaddress = address / 4;
> +	metadata = (dwaddress << USB4_NVM_SET_OFFSET_SHIFT) &
> +		   USB4_NVM_SET_OFFSET_MASK;
> +
> +	ret = usb4_switch_op_write_metadata(sw, metadata);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_SET_OFFSET, &status);
> +	if (ret)
> +		return ret;
> +
> +	return status ? -EIO : 0;
> +}
> +
> +static int usb4_switch_nvm_write_next_block(struct tb_switch *sw,
> +					    const void *buf, size_t dwords)
> +{
> +	u8 status;
> +	int ret;
> +
> +	ret = usb4_switch_op_write_data(sw, buf, dwords);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_WRITE, &status);
> +	if (ret)
> +		return ret;
> +
> +	return status ? -EIO : 0;
> +}
> +
> +/**
> + * usb4_switch_nvm_write() - Write to the router NVM
> + * @sw: USB4 router
> + * @address: Start address where to write in bytes
> + * @buf: Pointer to the data to write
> + * @size: Size of @buf in bytes
> + *
> + * Writes @buf to the router NVM using USB4 router operations. If NVM
> + * write is not supported returns %-EOPNOTSUPP.
> + */
> +int usb4_switch_nvm_write(struct tb_switch *sw, unsigned int address,
> +			  const void *buf, size_t size)
> +{
> +	int ret;
> +
> +	ret = usb4_switch_nvm_set_offset(sw, address);
> +	if (ret)
> +		return ret;
> +
> +	return usb4_switch_do_write_data(sw, address, buf, size,
> +					 usb4_switch_nvm_write_next_block);
> +}
> +
> +/**
> + * usb4_switch_nvm_authenticate() - Authenticate new NVM
> + * @sw: USB4 router
> + *
> + * After the new NVM has been written via usb4_switch_nvm_write(), this
> + * function triggers NVM authentication process. If the authentication
> + * is successful the router is power cycled and the new NVM starts
> + * running. In case of failure returns negative errno.
> + */
> +int usb4_switch_nvm_authenticate(struct tb_switch *sw)
> +{
> +	u8 status = 0;
> +	int ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_NVM_AUTH, &status);
> +	if (ret)
> +		return ret;
> +
> +	switch (status) {
> +	case 0x0:
> +		tb_sw_dbg(sw, "NVM authentication successful\n");
> +		return 0;
> +	case 0x1:
> +		return -EINVAL;
> +	case 0x2:
> +		return -EAGAIN;
> +	case 0x3:
> +		return -EOPNOTSUPP;
> +	default:
> +		return -EIO;
> +	}
> +}
> +
> +/**
> + * usb4_switch_query_dp_resource() - Query availability of DP IN resource
> + * @sw: USB4 router
> + * @in: DP IN adapter
> + *
> + * For DP tunneling this function can be used to query availability of
> + * DP IN resource. Returns true if the resource is available for DP
> + * tunneling, false otherwise.
> + */
> +bool usb4_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in)
> +{
> +	u8 status;
> +	int ret;
> +
> +	ret = usb4_switch_op_write_metadata(sw, in->port);
> +	if (ret)
> +		return false;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_QUERY_DP_RESOURCE, &status);
> +	/*
> +	 * If DP resource allocation is not supported assume it is
> +	 * always available.
> +	 */
> +	if (ret == -EOPNOTSUPP)
> +		return true;
> +	else if (ret)
> +		return false;
> +
> +	return !status;
> +}
> +
> +/**
> + * usb4_switch_alloc_dp_resource() - Allocate DP IN resource
> + * @sw: USB4 router
> + * @in: DP IN adapter
> + *
> + * Allocates DP IN resource for DP tunneling using USB4 router
> + * operations. If the resource was allocated returns %0. Otherwise
> + * returns negative errno, in particular %-EBUSY if the resource is
> + * already allocated.
> + */
> +int usb4_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
> +{
> +	u8 status;
> +	int ret;
> +
> +	ret = usb4_switch_op_write_metadata(sw, in->port);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_ALLOC_DP_RESOURCE, &status);
> +	if (ret == -EOPNOTSUPP)
> +		return 0;
> +	else if (ret)
> +		return ret;
> +
> +	return status ? -EBUSY : 0;
> +}
> +
> +/**
> + * usb4_switch_dealloc_dp_resource() - Releases allocated DP IN resource
> + * @sw: USB4 router
> + * @in: DP IN adapter
> + *
> + * Releases the previously allocated DP IN resource.
> + */
> +int usb4_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in)
> +{
> +	u8 status;
> +	int ret;
> +
> +	ret = usb4_switch_op_write_metadata(sw, in->port);
> +	if (ret)
> +		return ret;
> +
> +	ret = usb4_switch_op(sw, USB4_SWITCH_OP_DEALLOC_DP_RESOURCE, &status);
> +	if (ret == -EOPNOTSUPP)
> +		return 0;
> +	else if (ret)
> +		return ret;
> +
> +	return status ? -EIO : 0;
> +}
> +
> +static int usb4_port_idx(const struct tb_switch *sw, const struct tb_port *port)
> +{
> +	struct tb_port *p;
> +	int usb4_idx = 0;
> +
> +	/* Assume port is primary */
> +	tb_switch_for_each_port(sw, p) {
> +		if (!tb_port_is_null(p))
> +			continue;
> +		if (tb_is_upstream_port(p))
> +			continue;
> +		if (!p->link_nr) {
> +			if (p == port)
> +				break;
> +			usb4_idx++;
> +		}
> +	}
> +
> +	return usb4_idx;
> +}
> +
> +/**
> + * usb4_switch_map_pcie_down() - Map USB4 port to a PCIe downstream adapter
> + * @sw: USB4 router
> + * @port: USB4 port
> + *
> + * USB4 routers have direct mapping between USB4 ports and PCIe
> + * downstream adapters where the PCIe topology is extended. This
> + * function returns the corresponding downstream PCIe adapter or %NULL
> + * if no such mapping was possible.
> + */
> +struct tb_port *usb4_switch_map_pcie_down(struct tb_switch *sw,
> +					  const struct tb_port *port)
> +{
> +	int usb4_idx = usb4_port_idx(sw, port);
> +	struct tb_port *p;
> +	int pcie_idx = 0;
> +
> +	/* Find PCIe down port matching usb4_port */
> +	tb_switch_for_each_port(sw, p) {
> +		if (!tb_port_is_pcie_down(p))
> +			continue;
> +
> +		if (pcie_idx == usb4_idx && !tb_pci_port_is_enabled(p))
> +			return p;
> +
> +		pcie_idx++;
> +	}
> +
> +	return NULL;
> +}
> +
> +/**
> + * usb4_port_unlock() - Unlock USB4 downstream port
> + * @port: USB4 port to unlock
> + *
> + * Unlocks USB4 downstream port so that the connection manager can
> + * access the router below this port.
> + */
> +int usb4_port_unlock(struct tb_port *port)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_port_read(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
> +	if (ret)
> +		return ret;
> +
> +	val &= ~ADP_CS_4_LCK;
> +	return tb_port_write(port, &val, TB_CFG_PORT, ADP_CS_4, 1);
> +}
> diff --git a/drivers/thunderbolt/xdomain.c b/drivers/thunderbolt/xdomain.c
> index 880d784398a3..053f918e00e8 100644
> --- a/drivers/thunderbolt/xdomain.c
> +++ b/drivers/thunderbolt/xdomain.c
> @@ -1220,7 +1220,13 @@ struct tb_xdomain *tb_xdomain_alloc(struct tb *tb, struct device *parent,
>  				    u64 route, const uuid_t *local_uuid,
>  				    const uuid_t *remote_uuid)
>  {
> +	struct tb_switch *parent_sw = tb_to_switch(parent);
>  	struct tb_xdomain *xd;
> +	struct tb_port *down;
> +
> +	/* Make sure the downstream domain is accessible */
> +	down = tb_port_at(route, parent_sw);
> +	tb_port_unlock(down);
>  
>  	xd = kzalloc(sizeof(*xd), GFP_KERNEL);
>  	if (!xd)
> -- 
> 2.24.0
> 

Kind regards,
Nicholas

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4
  2019-12-17 12:33 ` [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4 Mika Westerberg
@ 2019-12-18  9:36   ` Nicholas Johnson
  0 siblings, 0 replies; 21+ messages in thread
From: Nicholas Johnson @ 2019-12-18  9:36 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Lukas Wunner, Greg Kroah-Hartman, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:41PM +0300, Mika Westerberg wrote:
> Since the driver now supports USB4 which is the standard going forward,
> update the Kconfig entry to mention this and rename the entry from
> CONFIG_THUNDERBOLT to CONFIG_USB4 instead to help people to find the
> correct option if they want to enable USB4.
> 
> Also do the same for Thunderbolt network driver.
> 
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> Cc: David S. Miller <davem@davemloft.net>
> ---
>  drivers/Makefile             |  2 +-
>  drivers/net/Kconfig          | 10 +++++-----
>  drivers/net/Makefile         |  2 +-
>  drivers/thunderbolt/Kconfig  | 11 ++++++-----
>  drivers/thunderbolt/Makefile |  2 +-
>  5 files changed, 14 insertions(+), 13 deletions(-)
> 
> diff --git a/drivers/Makefile b/drivers/Makefile
> index aaef17cc6512..31cf17dee252 100644
> --- a/drivers/Makefile
> +++ b/drivers/Makefile
> @@ -171,7 +171,7 @@ obj-$(CONFIG_POWERCAP)		+= powercap/
>  obj-$(CONFIG_MCB)		+= mcb/
>  obj-$(CONFIG_PERF_EVENTS)	+= perf/
>  obj-$(CONFIG_RAS)		+= ras/
> -obj-$(CONFIG_THUNDERBOLT)	+= thunderbolt/
> +obj-$(CONFIG_USB4)		+= thunderbolt/
>  obj-$(CONFIG_CORESIGHT)		+= hwtracing/coresight/
>  obj-y				+= hwtracing/intel_th/
>  obj-$(CONFIG_STM)		+= hwtracing/stm/
> diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
> index d02f12a5254e..d1c84d47779d 100644
> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
> @@ -489,12 +489,12 @@ config FUJITSU_ES
>  	  This driver provides support for Extended Socket network device
>  	  on Extended Partitioning of FUJITSU PRIMEQUEST 2000 E2 series.
>  
> -config THUNDERBOLT_NET
> -	tristate "Networking over Thunderbolt cable"
> -	depends on THUNDERBOLT && INET
> +config USB4_NET
> +	tristate "Networking over USB4 and Thunderbolt cables"
> +	depends on USB4 && INET
>  	help
> -	  Select this if you want to create network between two
> -	  computers over a Thunderbolt cable. The driver supports Apple
> +	  Select this if you want to create network between two computers
> +	  over a USB4 and Thunderbolt cables. The driver supports Apple
Nitpick: Perhaps should be "over USB4 and Thunderbolt cables".

>  	  ThunderboltIP protocol and allows communication with any host
>  	  supporting the same protocol including Windows and macOS.
>  
> diff --git a/drivers/net/Makefile b/drivers/net/Makefile
> index 0d3ba056cda3..29e83e9f545e 100644
> --- a/drivers/net/Makefile
> +++ b/drivers/net/Makefile
> @@ -76,6 +76,6 @@ obj-$(CONFIG_NTB_NETDEV) += ntb_netdev.o
>  obj-$(CONFIG_FUJITSU_ES) += fjes/
>  
>  thunderbolt-net-y += thunderbolt.o
> -obj-$(CONFIG_THUNDERBOLT_NET) += thunderbolt-net.o
> +obj-$(CONFIG_USB4_NET) += thunderbolt-net.o
>  obj-$(CONFIG_NETDEVSIM) += netdevsim/
>  obj-$(CONFIG_NET_FAILOVER) += net_failover.o
> diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig
> index fd9adca898ff..1eb757e8df3b 100644
> --- a/drivers/thunderbolt/Kconfig
> +++ b/drivers/thunderbolt/Kconfig
> @@ -1,6 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> -menuconfig THUNDERBOLT
> -	tristate "Thunderbolt support"
> +menuconfig USB4
> +	tristate "Unified support for USB4 and Thunderbolt"
>  	depends on PCI
>  	depends on X86 || COMPILE_TEST
>  	select APPLE_PROPERTIES if EFI_STUB && X86
> @@ -9,9 +9,10 @@ menuconfig THUNDERBOLT
>  	select CRYPTO_HASH
>  	select NVMEM
>  	help
> -	  Thunderbolt Controller driver. This driver is required if you
> -	  want to hotplug Thunderbolt devices on Apple hardware or on PCs
> -	  with Intel Falcon Ridge or newer.
> +	  USB4 and Thunderbolt driver. USB4 is the public speficiation
> +	  based on Thunderbolt 3 protocol. This driver is required if
> +	  you want to hotplug Thunderbolt and USB4 compliant devices on
> +	  Apple hardware or on PCs with Intel Falcon Ridge or newer.
>  
>  	  To compile this driver a module, choose M here. The module will be
>  	  called thunderbolt.
> diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
> index c0b2fd73dfbd..102e9529ee66 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
> -obj-${CONFIG_THUNDERBOLT} := thunderbolt.o
> +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 usb4.o
> -- 
> 2.24.0
> 

Kind regards,
Nicholas

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit
  2019-12-17 12:33 ` [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit Mika Westerberg
@ 2019-12-18  9:38   ` Nicholas Johnson
  0 siblings, 0 replies; 21+ messages in thread
From: Nicholas Johnson @ 2019-12-18  9:38 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Lukas Wunner, Greg Kroah-Hartman, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:43PM +0300, Mika Westerberg wrote:
> From: Rajmohan Mani <rajmohan.mani@intel.com>
> 
> Time Management Unit (TMU) is included in each USB4 router. It is used
> to synchronize time across the USB4 fabric. By default when USB4 router
> is plugged to the domain, its TMU is turned off. This differs from
> Thunderbolt (1, 2 and 3) devices whose TMU is by default configured to
> bi-directional HiFi mode. Since time synchronization is needed for
> proper Display Port tunneling this means we need to configure the TMU on
Nitpick: DisplayPort is a single word.

> USB4 compliant devices.
> 
> The USB4 spec allows some flexibility on how the TMU can be configured.
> This makes it possible to enable link power management states (CLx) in
> certain topologies, where for example DP tunneling is not used. TMU can
> also be re-configured dynamicaly depending on types of tunnels created
> over the USB4 fabric.
> 
> In this patch we simply configure the TMU to be in bi-directional HiFi
> mode. This way we can tunnel any kind of traffic without need to perform
> complex steps to re-configure the domain dynamically. We can add more
> fine-grained TMU configuration later on when we start enabling CLx
> states.
> 
> Signed-off-by: Rajmohan Mani <rajmohan.mani@intel.com>
> Co-developed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> ---
>  drivers/thunderbolt/Makefile  |   2 +-
>  drivers/thunderbolt/switch.c  |   4 +
>  drivers/thunderbolt/tb.c      |  28 +++
>  drivers/thunderbolt/tb.h      |  47 +++++
>  drivers/thunderbolt/tb_regs.h |  20 ++
>  drivers/thunderbolt/tmu.c     | 383 ++++++++++++++++++++++++++++++++++
>  6 files changed, 483 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/thunderbolt/tmu.c
> 
> diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile
> index 102e9529ee66..eae28dd45250 100644
> --- a/drivers/thunderbolt/Makefile
> +++ b/drivers/thunderbolt/Makefile
> @@ -1,4 +1,4 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  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 usb4.o
> +thunderbolt-objs += domain.o dma_port.o icm.o property.o xdomain.o lc.o tmu.o usb4.o
> diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c
> index c1d5cd7e0631..82f45780dc81 100644
> --- a/drivers/thunderbolt/switch.c
> +++ b/drivers/thunderbolt/switch.c
> @@ -2338,6 +2338,10 @@ int tb_switch_add(struct tb_switch *sw)
>  		ret = tb_switch_update_link_attributes(sw);
>  		if (ret)
>  			return ret;
> +
> +		ret = tb_switch_tmu_init(sw);
> +		if (ret)
> +			return ret;
>  	}
>  
>  	ret = device_add(&sw->dev);
> diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c
> index 6b99dcd1790c..e446624dd3e7 100644
> --- a/drivers/thunderbolt/tb.c
> +++ b/drivers/thunderbolt/tb.c
> @@ -158,6 +158,25 @@ static void tb_scan_xdomain(struct tb_port *port)
>  	}
>  }
>  
> +static int tb_enable_tmu(struct tb_switch *sw)
> +{
> +	int ret;
> +
> +	/* If it is already enabled in correct mode, don't touch it */
> +	if (tb_switch_tmu_is_enabled(sw))
> +		return 0;
> +
> +	ret = tb_switch_tmu_disable(sw);
> +	if (ret)
> +		return ret;
> +
> +	ret = tb_switch_tmu_post_time(sw);
> +	if (ret)
> +		return ret;
> +
> +	return tb_switch_tmu_enable(sw);
> +}
> +
>  static void tb_scan_port(struct tb_port *port);
>  
>  /**
> @@ -257,6 +276,9 @@ static void tb_scan_port(struct tb_port *port)
>  	if (tb_switch_lane_bonding_enable(sw))
>  		tb_sw_warn(sw, "failed to enable lane bonding\n");
>  
> +	if (tb_enable_tmu(sw))
> +		tb_sw_warn(sw, "failed to enable TMU\n");
> +
>  	tb_scan_switch(sw);
>  }
>  
> @@ -709,6 +731,7 @@ static void tb_handle_hotplug(struct work_struct *work)
>  			tb_sw_set_unplugged(port->remote->sw);
>  			tb_free_invalid_tunnels(tb);
>  			tb_remove_dp_resources(port->remote->sw);
> +			tb_switch_tmu_disable(port->remote->sw);
>  			tb_switch_lane_bonding_disable(port->remote->sw);
>  			tb_switch_remove(port->remote->sw);
>  			port->remote = NULL;
> @@ -855,6 +878,8 @@ static int tb_start(struct tb *tb)
>  		return ret;
>  	}
>  
> +	/* Enable TMU if it is off */
> +	tb_switch_tmu_enable(tb->root_switch);
>  	/* Full scan to discover devices added before the driver was loaded. */
>  	tb_scan_switch(tb->root_switch);
>  	/* Find out tunnels created by the boot firmware */
> @@ -886,6 +911,9 @@ static void tb_restore_children(struct tb_switch *sw)
>  {
>  	struct tb_port *port;
>  
> +	if (tb_enable_tmu(sw))
> +		tb_sw_warn(sw, "failed to restore TMU configuration\n");
> +
>  	tb_switch_for_each_port(sw, port) {
>  		if (!tb_port_has_remote(port))
>  			continue;
> diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h
> index 28dd0e0b1579..63ffb3cbdefe 100644
> --- a/drivers/thunderbolt/tb.h
> +++ b/drivers/thunderbolt/tb.h
> @@ -46,6 +46,38 @@ struct tb_switch_nvm {
>  #define TB_SWITCH_MAX_DEPTH		6
>  #define USB4_SWITCH_MAX_DEPTH		5
>  
> +/**
> + * enum tb_switch_tmu_rate - TMU refresh rate
> + * @TB_SWITCH_TMU_RATE_OFF: %0 (Disable Time Sync handshake)
> + * @TB_SWITCH_TMU_RATE_HIFI: %16 us time interval between successive
> + *			     transmission of the Delay Request TSNOS
> + *			     (Time Sync Notification Ordered Set) on a Link
> + * @TB_SWITCH_TMU_RATE_NORMAL: %1 ms time interval between successive
> + *			       transmission of the Delay Request TSNOS on
> + *			       a Link
> + */
> +enum tb_switch_tmu_rate {
> +	TB_SWITCH_TMU_RATE_OFF = 0,
> +	TB_SWITCH_TMU_RATE_HIFI = 16,
> +	TB_SWITCH_TMU_RATE_NORMAL = 1000,
> +};
> +
> +/**
> + * struct tb_switch_tmu - Structure holding switch TMU configuration
> + * @cap: Offset to the TMU capability (%0 if not found)
> + * @has_ucap: Does the switch support uni-directional mode
> + * @rate: TMU refresh rate related to upstream switch. In case of root
> + *	  switch this holds the domain rate.
> + * @unidirectional: Is the TMU in uni-directional or bi-directional mode
> + *		    related to upstream switch. Don't case for root switch.
> + */
> +struct tb_switch_tmu {
> +	int cap;
> +	bool has_ucap;
> +	enum tb_switch_tmu_rate rate;
> +	bool unidirectional;
> +};
> +
>  /**
>   * struct tb_switch - a thunderbolt switch
>   * @dev: Device for the switch
> @@ -55,6 +87,7 @@ struct tb_switch_nvm {
>   *	      mailbox this will hold the pointer to that (%NULL
>   *	      otherwise). If set it also means the switch has
>   *	      upgradeable NVM.
> + * @tmu: The switch TMU configuration
>   * @tb: Pointer to the domain the switch belongs to
>   * @uid: Unique ID of the switch
>   * @uuid: UUID of the switch (or %NULL if not supported)
> @@ -93,6 +126,7 @@ struct tb_switch {
>  	struct tb_regs_switch_header config;
>  	struct tb_port *ports;
>  	struct tb_dma_port *dma_port;
> +	struct tb_switch_tmu tmu;
>  	struct tb *tb;
>  	u64 uid;
>  	uuid_t *uuid;
> @@ -129,6 +163,7 @@ struct tb_switch {
>   * @remote: Remote port (%NULL if not connected)
>   * @xdomain: Remote host (%NULL if not connected)
>   * @cap_phy: Offset, zero if not found
> + * @cap_tmu: Offset of the adapter specific TMU capability (%0 if not present)
>   * @cap_adap: Offset of the adapter specific capability (%0 if not present)
>   * @cap_usb4: Offset to the USB4 port capability (%0 if not present)
>   * @port: Port number on switch
> @@ -147,6 +182,7 @@ struct tb_port {
>  	struct tb_port *remote;
>  	struct tb_xdomain *xdomain;
>  	int cap_phy;
> +	int cap_tmu;
>  	int cap_adap;
>  	int cap_usb4;
>  	u8 port;
> @@ -672,6 +708,17 @@ bool tb_switch_query_dp_resource(struct tb_switch *sw, struct tb_port *in);
>  int tb_switch_alloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
>  void tb_switch_dealloc_dp_resource(struct tb_switch *sw, struct tb_port *in);
>  
> +int tb_switch_tmu_init(struct tb_switch *sw);
> +int tb_switch_tmu_post_time(struct tb_switch *sw);
> +int tb_switch_tmu_disable(struct tb_switch *sw);
> +int tb_switch_tmu_enable(struct tb_switch *sw);
> +
> +static inline bool tb_switch_tmu_is_enabled(const struct tb_switch *sw)
> +{
> +	return sw->tmu.rate == TB_SWITCH_TMU_RATE_HIFI &&
> +	       !sw->tmu.unidirectional;
> +}
> +
>  int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged);
>  int tb_port_add_nfc_credits(struct tb_port *port, int credits);
>  int tb_port_set_initial_credits(struct tb_port *port, u32 credits);
> diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h
> index 47f73f992412..ec1a5d1f7c94 100644
> --- a/drivers/thunderbolt/tb_regs.h
> +++ b/drivers/thunderbolt/tb_regs.h
> @@ -26,6 +26,7 @@
>  #define TB_MAX_CONFIG_RW_LENGTH 60
>  
>  enum tb_switch_cap {
> +	TB_SWITCH_CAP_TMU		= 0x03,
>  	TB_SWITCH_CAP_VSE		= 0x05,
>  };
>  
> @@ -195,6 +196,21 @@ struct tb_regs_switch_header {
>  #define ROUTER_CS_26_ONS			BIT(30)
>  #define ROUTER_CS_26_OV				BIT(31)
>  
> +/* Router TMU configuration */
> +#define TMU_RTR_CS_0				0x00
> +#define TMU_RTR_CS_0_TD				BIT(27)
> +#define TMU_RTR_CS_0_UCAP			BIT(30)
> +#define TMU_RTR_CS_1				0x01
> +#define TMU_RTR_CS_1_LOCAL_TIME_NS_MASK		GENMASK(31, 16)
> +#define TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT	16
> +#define TMU_RTR_CS_2				0x02
> +#define TMU_RTR_CS_3				0x03
> +#define TMU_RTR_CS_3_LOCAL_TIME_NS_MASK		GENMASK(15, 0)
> +#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK	GENMASK(31, 16)
> +#define TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT	16
> +#define TMU_RTR_CS_22				0x16
> +#define TMU_RTR_CS_24				0x18
> +
>  enum tb_port_type {
>  	TB_TYPE_INACTIVE	= 0x000000,
>  	TB_TYPE_PORT		= 0x000001,
> @@ -248,6 +264,10 @@ struct tb_regs_port_header {
>  #define ADP_CS_5_LCA_MASK			GENMASK(28, 22)
>  #define ADP_CS_5_LCA_SHIFT			22
>  
> +/* TMU adapter registers */
> +#define TMU_ADP_CS_3				0x03
> +#define TMU_ADP_CS_3_UDM			BIT(29)
> +
>  /* Lane adapter registers */
>  #define LANE_ADP_CS_0				0x00
>  #define LANE_ADP_CS_0_SUPPORTED_WIDTH_MASK	GENMASK(25, 20)
> diff --git a/drivers/thunderbolt/tmu.c b/drivers/thunderbolt/tmu.c
> new file mode 100644
> index 000000000000..039c42a06000
> --- /dev/null
> +++ b/drivers/thunderbolt/tmu.c
> @@ -0,0 +1,383 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Thunderbolt Time Management Unit (TMU) support
> + *
> + * Copyright (C) 2019, Intel Corporation
> + * Authors: Mika Westerberg <mika.westerberg@linux.intel.com>
> + *	    Rajmohan Mani <rajmohan.mani@intel.com>
> + */
> +
> +#include <linux/delay.h>
> +
> +#include "tb.h"
> +
> +static const char *tb_switch_tmu_mode_name(const struct tb_switch *sw)
> +{
> +	bool root_switch = !tb_route(sw);
> +
> +	switch (sw->tmu.rate) {
> +	case TB_SWITCH_TMU_RATE_OFF:
> +		return "off";
> +
> +	case TB_SWITCH_TMU_RATE_HIFI:
> +		/* Root switch does not have upstream directionality */
> +		if (root_switch)
> +			return "HiFi";
> +		if (sw->tmu.unidirectional)
> +			return "uni-directional, HiFi";
> +		return "bi-directional, HiFi";
> +
> +	case TB_SWITCH_TMU_RATE_NORMAL:
> +		if (root_switch)
> +			return "normal";
> +		return "uni-directional, normal";
> +
> +	default:
> +		return "unknown";
> +	}
> +}
> +
> +static bool tb_switch_tmu_ucap_supported(struct tb_switch *sw)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
> +			 sw->tmu.cap + TMU_RTR_CS_0, 1);
> +	if (ret)
> +		return false;
> +
> +	return !!(val & TMU_RTR_CS_0_UCAP);
> +}
> +
> +static int tb_switch_tmu_rate_read(struct tb_switch *sw)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
> +			 sw->tmu.cap + TMU_RTR_CS_3, 1);
> +	if (ret)
> +		return ret;
> +
> +	val >>= TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT;
> +	return val;
> +}
> +
> +static int tb_switch_tmu_rate_write(struct tb_switch *sw, int rate)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
> +			 sw->tmu.cap + TMU_RTR_CS_3, 1);
> +	if (ret)
> +		return ret;
> +
> +	val &= ~TMU_RTR_CS_3_TS_PACKET_INTERVAL_MASK;
> +	val |= rate << TMU_RTR_CS_3_TS_PACKET_INTERVAL_SHIFT;
> +
> +	return tb_sw_write(sw, &val, TB_CFG_SWITCH,
> +			   sw->tmu.cap + TMU_RTR_CS_3, 1);
> +}
> +
> +static int tb_port_tmu_write(struct tb_port *port, u8 offset, u32 mask,
> +			     u32 value)
> +{
> +	u32 data;
> +	int ret;
> +
> +	ret = tb_port_read(port, &data, TB_CFG_PORT, port->cap_tmu + offset, 1);
> +	if (ret)
> +		return ret;
> +
> +	data &= ~mask;
> +	data |= value;
> +
> +	return tb_port_write(port, &data, TB_CFG_PORT,
> +			     port->cap_tmu + offset, 1);
> +}
> +
> +static int tb_port_tmu_set_unidirectional(struct tb_port *port,
> +					  bool unidirectional)
> +{
> +	u32 val;
> +
> +	if (!port->sw->tmu.has_ucap)
> +		return 0;
> +
> +	val = unidirectional ? TMU_ADP_CS_3_UDM : 0;
> +	return tb_port_tmu_write(port, TMU_ADP_CS_3, TMU_ADP_CS_3_UDM, val);
> +}
> +
> +static inline int tb_port_tmu_unidirectional_disable(struct tb_port *port)
> +{
> +	return tb_port_tmu_set_unidirectional(port, false);
> +}
> +
> +static bool tb_port_tmu_is_unidirectional(struct tb_port *port)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_port_read(port, &val, TB_CFG_PORT,
> +			   port->cap_tmu + TMU_ADP_CS_3, 1);
> +	if (ret)
> +		return false;
> +
> +	return val & TMU_ADP_CS_3_UDM;
> +}
> +
> +static int tb_switch_tmu_set_time_disruption(struct tb_switch *sw, bool set)
> +{
> +	int ret;
> +	u32 val;
> +
> +	ret = tb_sw_read(sw, &val, TB_CFG_SWITCH,
> +			 sw->tmu.cap + TMU_RTR_CS_0, 1);
> +	if (ret)
> +		return ret;
> +
> +	if (set)
> +		val |= TMU_RTR_CS_0_TD;
> +	else
> +		val &= ~TMU_RTR_CS_0_TD;
> +
> +	return tb_sw_write(sw, &val, TB_CFG_SWITCH,
> +			   sw->tmu.cap + TMU_RTR_CS_0, 1);
> +}
> +
> +/**
> + * tb_switch_tmu_init() - Initialize switch TMU structures
> + * @sw: Switch to initialized
> + *
> + * This function must be called before other TMU related functions to
> + * makes the internal structures are filled in correctly. Does not
> + * change any hardware configuration.
> + */
> +int tb_switch_tmu_init(struct tb_switch *sw)
> +{
> +	struct tb_port *port;
> +	int ret;
> +
> +	if (tb_switch_is_icm(sw))
> +		return 0;
> +
> +	ret = tb_switch_find_cap(sw, TB_SWITCH_CAP_TMU);
> +	if (ret > 0)
> +		sw->tmu.cap = ret;
> +
> +	tb_switch_for_each_port(sw, port) {
> +		int cap;
> +
> +		cap = tb_port_find_cap(port, TB_PORT_CAP_TIME1);
> +		if (cap > 0)
> +			port->cap_tmu = cap;
> +	}
> +
> +	ret = tb_switch_tmu_rate_read(sw);
> +	if (ret < 0)
> +		return ret;
> +
> +	sw->tmu.rate = ret;
> +
> +	sw->tmu.has_ucap = tb_switch_tmu_ucap_supported(sw);
> +	if (sw->tmu.has_ucap) {
> +		tb_sw_dbg(sw, "TMU: supports uni-directional mode\n");
> +
> +		if (tb_route(sw)) {
> +			struct tb_port *up = tb_upstream_port(sw);
> +
> +			sw->tmu.unidirectional =
> +				tb_port_tmu_is_unidirectional(up);
> +		}
> +	} else {
> +		sw->tmu.unidirectional = false;
> +	}
> +
> +	tb_sw_dbg(sw, "TMU: current mode: %s\n", tb_switch_tmu_mode_name(sw));
> +	return 0;
> +}
> +
> +/**
> + * tb_switch_tmu_post_time() - Update switch local time
> + * @sw: Switch whose time to update
> + *
> + * Updates switch local time using time posting procedure.
> + */
> +int tb_switch_tmu_post_time(struct tb_switch *sw)
> +{
> +	unsigned int  post_local_time_offset, post_time_offset;
> +	struct tb_switch *root_switch = sw->tb->root_switch;
> +	u64 hi, mid, lo, local_time, post_time;
> +	int i, ret, retries = 100;
> +	u32 gm_local_time[3];
> +
> +	if (!tb_route(sw))
> +		return 0;
> +
> +	if (!tb_switch_is_usb4(sw))
> +		return 0;
> +
> +	/* Need to be able to read the grand master time */
> +	if (!root_switch->tmu.cap)
> +		return 0;
> +
> +	ret = tb_sw_read(root_switch, gm_local_time, TB_CFG_SWITCH,
> +			 root_switch->tmu.cap + TMU_RTR_CS_1,
> +			 ARRAY_SIZE(gm_local_time));
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < ARRAY_SIZE(gm_local_time); i++)
> +		tb_sw_dbg(root_switch, "local_time[%d]=0x%08x\n", i,
> +			  gm_local_time[i]);
> +
> +	/* Convert to nanoseconds (drop fractional part) */
> +	hi = gm_local_time[2] & TMU_RTR_CS_3_LOCAL_TIME_NS_MASK;
> +	mid = gm_local_time[1];
> +	lo = (gm_local_time[0] & TMU_RTR_CS_1_LOCAL_TIME_NS_MASK) >>
> +		TMU_RTR_CS_1_LOCAL_TIME_NS_SHIFT;
> +	local_time = hi << 48 | mid << 16 | lo;
> +
> +	/* Tell the switch that time sync is disrupted for a while */
> +	ret = tb_switch_tmu_set_time_disruption(sw, true);
> +	if (ret)
> +		return ret;
> +
> +	post_local_time_offset = sw->tmu.cap + TMU_RTR_CS_22;
> +	post_time_offset = sw->tmu.cap + TMU_RTR_CS_24;
> +
> +	/*
> +	 * Write the Grandmaster time to the Post Local Time registers
> +	 * of the new switch.
> +	 */
> +	ret = tb_sw_write(sw, &local_time, TB_CFG_SWITCH,
> +			  post_local_time_offset, 2);
> +	if (ret)
> +		goto out;
> +
> +	/*
> +	 * Have the new switch update its local time (by writing 1 to
> +	 * the post_time registers) and wait for the completion of the
> +	 * same (post_time register becomes 0). This means the time has
> +	 * been converged properly.
> +	 */
> +	post_time = 1;
> +
> +	ret = tb_sw_write(sw, &post_time, TB_CFG_SWITCH, post_time_offset, 2);
> +	if (ret)
> +		goto out;
> +
> +	do {
> +		usleep_range(5, 10);
> +		ret = tb_sw_read(sw, &post_time, TB_CFG_SWITCH,
> +				 post_time_offset, 2);
> +		if (ret)
> +			goto out;
> +	} while (--retries && post_time);
> +
> +	if (!retries) {
> +		ret = -ETIMEDOUT;
> +		goto out;
> +	}
> +
> +	tb_sw_dbg(sw, "TMU: updated local time to %#llx\n", local_time);
> +
> +out:
> +	tb_switch_tmu_set_time_disruption(sw, false);
> +	return ret;
> +}
> +
> +/**
> + * tb_switch_tmu_disable() - Disable TMU of a switch
> + * @sw: Switch whose TMU to disable
> + *
> + * Turns off TMU of @sw if it is enabled. If not enabled does nothing.
> + */
> +int tb_switch_tmu_disable(struct tb_switch *sw)
> +{
> +	int ret;
> +
> +	if (!tb_switch_is_usb4(sw))
> +		return 0;
> +
> +	/* Already disabled? */
> +	if (sw->tmu.rate == TB_SWITCH_TMU_RATE_OFF)
> +		return 0;
> +
> +	if (sw->tmu.unidirectional) {
> +		struct tb_switch *parent = tb_switch_parent(sw);
> +		struct tb_port *up, *down;
> +
> +		up = tb_upstream_port(sw);
> +		down = tb_port_at(tb_route(sw), parent);
> +
> +		/* The switch may be unplugged so ignore any errors */
> +		tb_port_tmu_unidirectional_disable(up);
> +		ret = tb_port_tmu_unidirectional_disable(down);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_OFF);
> +
> +	sw->tmu.unidirectional = false;
> +	sw->tmu.rate = TB_SWITCH_TMU_RATE_OFF;
> +
> +	tb_sw_dbg(sw, "TMU: disabled\n");
> +	return 0;
> +}
> +
> +/**
> + * tb_switch_tmu_enable() - Enable TMU on a switch
> + * @sw: Switch whose TMU to enable
> + *
> + * Enables TMU of a switch to be in bi-directional, HiFi mode. In this mode
> + * all tunneling should work.
> + */
> +int tb_switch_tmu_enable(struct tb_switch *sw)
> +{
> +	int ret;
> +
> +	if (!tb_switch_is_usb4(sw))
> +		return 0;
> +
> +	if (tb_switch_tmu_is_enabled(sw))
> +		return 0;
> +
> +	ret = tb_switch_tmu_set_time_disruption(sw, true);
> +	if (ret)
> +		return ret;
> +
> +	/* Change mode to bi-directional */
> +	if (tb_route(sw) && sw->tmu.unidirectional) {
> +		struct tb_switch *parent = tb_switch_parent(sw);
> +		struct tb_port *up, *down;
> +
> +		up = tb_upstream_port(sw);
> +		down = tb_port_at(tb_route(sw), parent);
> +
> +		ret = tb_port_tmu_unidirectional_disable(down);
> +		if (ret)
> +			return ret;
> +
> +		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
> +		if (ret)
> +			return ret;
> +
> +		ret = tb_port_tmu_unidirectional_disable(up);
> +		if (ret)
> +			return ret;
> +	} else {
> +		ret = tb_switch_tmu_rate_write(sw, TB_SWITCH_TMU_RATE_HIFI);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	sw->tmu.unidirectional = false;
> +	sw->tmu.rate = TB_SWITCH_TMU_RATE_HIFI;
> +	tb_sw_dbg(sw, "TMU: mode set to: %s\n", tb_switch_tmu_mode_name(sw));
> +
> +	return tb_switch_tmu_set_time_disruption(sw, false);
> +}
> -- 
> 2.24.0
> 

Kind regards,
Nicholas

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet
  2019-12-17 14:56     ` Mika Westerberg
@ 2019-12-18 14:35       ` Greg Kroah-Hartman
  0 siblings, 0 replies; 21+ messages in thread
From: Greg Kroah-Hartman @ 2019-12-18 14:35 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 04:56:32PM +0200, Mika Westerberg wrote:
> On Tue, Dec 17, 2019 at 01:47:45PM +0100, Greg Kroah-Hartman wrote:
> > On Tue, Dec 17, 2019 at 03:33:39PM +0300, Mika Westerberg wrote:
> > > USB4 1.0 section 6.4.2.7 specifies a new field (PG) in notification
> > > packet that is sent as response of hot plug/unplug events. This field
> > > tells whether the acknowledgment is for plug or unplug event. This needs
> > > to be set accordingly in order the router to send further hot plug
> > > notifications.
> > > 
> > > To make it simpler we fill the field unconditionally. Legacy devices do
> > > not look at this field so there should be no problems with them.
> > > 
> > > While there rename tb_cfg_error() to tb_cfg_ack_plug() and update the
> > > log message accordingly. The function is only used to ack plug/unplug
> > > events.
> > > 
> > > Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
> > 
> > First 3 patches look "trivial" enough for me to take right now, any
> > objection to that?
> 
> No objections from my side :)
> 
> > Should I be using my usb tree for this?
> 
> Yes, I think it makes sense now that this is also under USB IF umbrella.

Ok, now done, thanks.

greg k-h

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 4/9] thunderbolt: Add initial support for USB4
  2019-12-18  9:34   ` Nicholas Johnson
@ 2019-12-18 14:36     ` Greg Kroah-Hartman
  0 siblings, 0 replies; 21+ messages in thread
From: Greg Kroah-Hartman @ 2019-12-18 14:36 UTC (permalink / raw)
  To: Nicholas Johnson
  Cc: Mika Westerberg, linux-usb, Andreas Noever, Michael Jamet,
	Yehezkel Bernat, Rajmohan Mani, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Wed, Dec 18, 2019 at 09:34:45AM +0000, Nicholas Johnson wrote:
> On Tue, Dec 17, 2019 at 03:33:40PM +0300, Mika Westerberg wrote:
> > USB4 is the public specification based on Thunderbolt 3 protocol. There
> > are some differences in register layouts and flows. In addition to PCIe
> > and DP tunneling, USB4 supports tunneling of USB 3.x. USB4 is also
> > backward compatible with Thunderbolt 3 (and older generations but the
> > spec only talks about 3rd generation). USB4 compliant devices can be
> > identified by checking USB4 version field in router configuration space.
> > 
> > This patch adds initial support for USB4 compliant hosts and devices
> > which enables following features provided by the existing functionality
> > in the driver:
> > 
> >   - PCIe tunneling
> >   - Display Port tunneling
> Nitpick: DisplayPort is a single word.

Please learn to trim replies, it was a pain to read this message :(


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 0/9] thunderbolt: Add support for USB4
  2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
                   ` (8 preceding siblings ...)
  2019-12-17 12:33 ` [PATCH v2 9/9] thunderbolt: Update documentation with the USB4 information Mika Westerberg
@ 2019-12-18 14:43 ` Greg Kroah-Hartman
  2019-12-18 15:30   ` Mika Westerberg
  9 siblings, 1 reply; 21+ messages in thread
From: Greg Kroah-Hartman @ 2019-12-18 14:43 UTC (permalink / raw)
  To: Mika Westerberg
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Tue, Dec 17, 2019 at 03:33:36PM +0300, Mika Westerberg wrote:
> Hi all,
> 
> USB4 is the public specification of Thunderbolt 3 protocol and can be
> downloaded here:
> 
>   https://www.usb.org/sites/default/files/USB4%20Specification_1.zip
> 
> USB4 is about tunneling different protocols over a single cable (in the
> same way as Thunderbolt). The current USB4 spec supports PCIe, Display Port
> and USB 3.x, and also software based protocols such as networking between
> domains (hosts).
> 
> So far PCs have been using firmware based Connection Manager (FW CM, ICM)
> and Apple systems have been using software based one (SW CM, ECM). A
> Connection Manager is the entity that handles creation of different tunnel
> types through the USB4 (and Thunderbolt) fabric. With USB4 the plan is to
> have software based Connection Manager everywhere but some early systems
> will come with firmware based connection manager.
> 
> Current Linux Thunderbolt driver supports both "modes" and can detect which
> one to use dynamically.
> 
> This series extends the Linux Thunderbolt driver to support USB4 compliant
> hosts and devices (this applies to both firmware and software based
> connection managers). USB4 Features enabled by this series include:
> 
>   - PCIe tunneling
>   - Display Port tunneling
>   - USB 3.x tunneling
>   - P2P networking (implemented in drivers/net/thunderbolt.c)
>   - Host and device NVM firmware upgrade
> 
> Power management support is still work in progress. It will be submitted
> later on once properly tested.
> 
> The previous versions of the series can be seen here:
> 
>   v1: https://lore.kernel.org/linux-usb/20191023112154.64235-1-mika.westerberg@linux.intel.com/
>   RFC: https://lore.kernel.org/lkml/20191001113830.13028-1-mika.westerberg@linux.intel.com/
> 
> Changes from v1:
> 
>   * Rebased on top of v5.5-rc2.
>   * Add a new patch to populate PG field in hotplug ack packet.
>   * Rename the networking driver Kconfig symbol to CONFIG_USB4_NET to
>     follow the driver itself (CONFIG_USB4).

At a quick glance, this looks nice and sane, good job.  I've taken all
of these into my tree, let's see if 0-day has any problems with it :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v2 0/9] thunderbolt: Add support for USB4
  2019-12-18 14:43 ` [PATCH v2 0/9] thunderbolt: Add support for USB4 Greg Kroah-Hartman
@ 2019-12-18 15:30   ` Mika Westerberg
  0 siblings, 0 replies; 21+ messages in thread
From: Mika Westerberg @ 2019-12-18 15:30 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: linux-usb, Andreas Noever, Michael Jamet, Yehezkel Bernat,
	Rajmohan Mani, Nicholas Johnson, Lukas Wunner, Alan Stern,
	Mario.Limonciello, Anthony Wong, Oliver Neukum,
	Christian Kellner, David S . Miller, netdev, linux-kernel

On Wed, Dec 18, 2019 at 03:43:16PM +0100, Greg Kroah-Hartman wrote:
> On Tue, Dec 17, 2019 at 03:33:36PM +0300, Mika Westerberg wrote:
> > Hi all,
> > 
> > USB4 is the public specification of Thunderbolt 3 protocol and can be
> > downloaded here:
> > 
> >   https://www.usb.org/sites/default/files/USB4%20Specification_1.zip
> > 
> > USB4 is about tunneling different protocols over a single cable (in the
> > same way as Thunderbolt). The current USB4 spec supports PCIe, Display Port
> > and USB 3.x, and also software based protocols such as networking between
> > domains (hosts).
> > 
> > So far PCs have been using firmware based Connection Manager (FW CM, ICM)
> > and Apple systems have been using software based one (SW CM, ECM). A
> > Connection Manager is the entity that handles creation of different tunnel
> > types through the USB4 (and Thunderbolt) fabric. With USB4 the plan is to
> > have software based Connection Manager everywhere but some early systems
> > will come with firmware based connection manager.
> > 
> > Current Linux Thunderbolt driver supports both "modes" and can detect which
> > one to use dynamically.
> > 
> > This series extends the Linux Thunderbolt driver to support USB4 compliant
> > hosts and devices (this applies to both firmware and software based
> > connection managers). USB4 Features enabled by this series include:
> > 
> >   - PCIe tunneling
> >   - Display Port tunneling
> >   - USB 3.x tunneling
> >   - P2P networking (implemented in drivers/net/thunderbolt.c)
> >   - Host and device NVM firmware upgrade
> > 
> > Power management support is still work in progress. It will be submitted
> > later on once properly tested.
> > 
> > The previous versions of the series can be seen here:
> > 
> >   v1: https://lore.kernel.org/linux-usb/20191023112154.64235-1-mika.westerberg@linux.intel.com/
> >   RFC: https://lore.kernel.org/lkml/20191001113830.13028-1-mika.westerberg@linux.intel.com/
> > 
> > Changes from v1:
> > 
> >   * Rebased on top of v5.5-rc2.
> >   * Add a new patch to populate PG field in hotplug ack packet.
> >   * Rename the networking driver Kconfig symbol to CONFIG_USB4_NET to
> >     follow the driver itself (CONFIG_USB4).
> 
> At a quick glance, this looks nice and sane, good job.  I've taken all
> of these into my tree, let's see if 0-day has any problems with it :)

Thanks :)

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2019-12-18 15:30 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-17 12:33 [PATCH v2 0/9] thunderbolt: Add support for USB4 Mika Westerberg
2019-12-17 12:33 ` [PATCH v2 1/9] thunderbolt: Make tb_find_port() available to other files Mika Westerberg
2019-12-17 12:33 ` [PATCH v2 2/9] thunderbolt: Call tb_eeprom_get_drom_offset() from tb_eeprom_read_n() Mika Westerberg
2019-12-17 12:33 ` [PATCH v2 3/9] thunderbolt: Populate PG field in hot plug acknowledgment packet Mika Westerberg
2019-12-17 12:46   ` Greg Kroah-Hartman
2019-12-17 14:55     ` Mika Westerberg
2019-12-17 12:47   ` Greg Kroah-Hartman
2019-12-17 14:56     ` Mika Westerberg
2019-12-18 14:35       ` Greg Kroah-Hartman
2019-12-17 12:33 ` [PATCH v2 4/9] thunderbolt: Add initial support for USB4 Mika Westerberg
2019-12-18  9:34   ` Nicholas Johnson
2019-12-18 14:36     ` Greg Kroah-Hartman
2019-12-17 12:33 ` [PATCH v2 5/9] thunderbolt: Update Kconfig entries to USB4 Mika Westerberg
2019-12-18  9:36   ` Nicholas Johnson
2019-12-17 12:33 ` [PATCH v2 6/9] thunderbolt: Make tb_switch_find_cap() available to other files Mika Westerberg
2019-12-17 12:33 ` [PATCH v2 7/9] thunderbolt: Add support for Time Management Unit Mika Westerberg
2019-12-18  9:38   ` Nicholas Johnson
2019-12-17 12:33 ` [PATCH v2 8/9] thunderbolt: Add support for USB 3.x tunnels Mika Westerberg
2019-12-17 12:33 ` [PATCH v2 9/9] thunderbolt: Update documentation with the USB4 information Mika Westerberg
2019-12-18 14:43 ` [PATCH v2 0/9] thunderbolt: Add support for USB4 Greg Kroah-Hartman
2019-12-18 15:30   ` Mika Westerberg

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).