Skip to content

Commit

Permalink
thunderbolt: Add support for USB 3.x tunnels
Browse files Browse the repository at this point in the history
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>
Link: https://lore.kernel.org/r/20191217123345.31850-9-mika.westerberg@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Rajmohan Mani authored and Greg Kroah-Hartman committed Dec 18, 2019
1 parent cf29b9a commit e6f8185
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 27 deletions.
35 changes: 35 additions & 0 deletions drivers/thunderbolt/switch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
154 changes: 130 additions & 24 deletions drivers/thunderbolt/tb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);

/**
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 */
Expand Down
15 changes: 15 additions & 0 deletions drivers/thunderbolt/tb.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
9 changes: 8 additions & 1 deletion drivers/thunderbolt/tb_regs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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 */
Expand Down
Loading

0 comments on commit e6f8185

Please sign in to comment.