Commit da2da04b authored by Mika Westerberg's avatar Mika Westerberg Committed by Greg Kroah-Hartman

thunderbolt: Rework capability handling

Organization of the capabilities in switches and ports is not so random
after all. Rework the capability handling functionality so that it
follows how capabilities are organized and provide two new functions
(tb_switch_find_vse_cap() and tb_port_find_cap()) which can be used to
extract capabilities for ports and switches. Then convert the current
users over these.
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarYehezkel Bernat <yehezkel.bernat@intel.com>
Reviewed-by: default avatarMichael Jamet <michael.jamet@intel.com>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: default avatarAndreas Noever <andreas.noever@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 046bee1f
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
#include "tb.h" #include "tb.h"
#define CAP_OFFSET_MAX 0xff
#define VSE_CAP_OFFSET_MAX 0xffff
struct tb_cap_any { struct tb_cap_any {
union { union {
...@@ -18,99 +20,110 @@ struct tb_cap_any { ...@@ -18,99 +20,110 @@ struct tb_cap_any {
}; };
} __packed; } __packed;
static bool tb_cap_is_basic(struct tb_cap_any *cap) /**
{ * tb_port_find_cap() - Find port capability
/* basic.cap is u8. This checks only the lower 8 bit of cap. */ * @port: Port to find the capability for
return cap->basic.cap != 5; * @cap: Capability to look
} *
* Returns offset to start of capability or %-ENOENT if no such
static bool tb_cap_is_long(struct tb_cap_any *cap) * capability was found. Negative errno is returned if there was an
* error.
*/
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap)
{ {
return !tb_cap_is_basic(cap) u32 offset;
&& cap->extended_short.next == 0
&& cap->extended_short.length == 0;
}
static enum tb_cap tb_cap(struct tb_cap_any *cap) /*
{ * DP out adapters claim to implement TMU capability but in
if (tb_cap_is_basic(cap)) * reality they do not so we hard code the adapter specific
return cap->basic.cap; * capability offset here.
*/
if (port->config.type == TB_TYPE_DP_HDMI_OUT)
offset = 0x39;
else else
/* extended_short/long have cap at the same offset. */ offset = 0x1;
return cap->extended_short.cap;
do {
struct tb_cap_any header;
int ret;
ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1);
if (ret)
return ret;
if (header.basic.cap == cap)
return offset;
offset = header.basic.next;
} while (offset);
return -ENOENT;
} }
static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset) static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap)
{ {
int next; int offset = sw->config.first_cap_offset;
if (offset == 1) {
/* while (offset > 0 && offset < CAP_OFFSET_MAX) {
* The first pointer is part of the switch header and always struct tb_cap_any header;
* a simple pointer. int ret;
*/
next = cap->basic.next; ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1);
} else { if (ret)
/* return ret;
* Somehow Intel decided to use 3 different types of capability
* headers. It is not like anyone could have predicted that if (header.basic.cap == cap)
* single byte offsets are not enough... return offset;
*/
if (tb_cap_is_basic(cap)) offset = header.basic.next;
next = cap->basic.next;
else if (!tb_cap_is_long(cap))
next = cap->extended_short.next;
else
next = cap->extended_long.next;
} }
/*
* "Hey, we could terminate some capability lists with a null offset return -ENOENT;
* and others with a pointer to the last element." - "Great idea!"
*/
if (next == offset)
return 0;
return next;
} }
/** /**
* tb_find_cap() - find a capability * tb_switch_find_vse_cap() - Find switch vendor specific capability
* @sw: Switch to find the capability for
* @vsec: Vendor specific capability to look
* *
* Return: Returns a positive offset if the capability was found and 0 if not. * Functions enumerates vendor specific capabilities (VSEC) of a switch
* Returns an error code on failure. * and returns offset when capability matching @vsec is found. If no
* such capability is found returns %-ENOENT. In case of error returns
* negative errno.
*/ */
int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap) int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec)
{ {
u32 offset = 1;
struct tb_cap_any header; struct tb_cap_any header;
int res; int offset;
int retries = 10;
while (retries--) { offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE);
res = tb_port_read(port, &header, space, offset, 1); if (offset < 0)
if (res) {
/* Intel needs some help with linked lists. */
if (space == TB_CFG_PORT && offset == 0xa
&& port->config.type == TB_TYPE_DP_HDMI_OUT) {
offset = 0x39;
continue;
}
return res;
}
if (offset != 1) {
if (tb_cap(&header) == cap)
return offset; return offset;
if (tb_cap_is_long(&header)) {
/* tb_cap_extended_long is 2 dwords */ while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) {
res = tb_port_read(port, &header, space, int ret;
offset, 2);
if (res) ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2);
return res; if (ret)
} return ret;
/*
* Extended vendor specific capabilities come in two
* flavors: short and long. The latter is used when
* offset is over 0xff.
*/
if (offset >= CAP_OFFSET_MAX) {
if (header.extended_long.vsec_id == vsec)
return offset;
offset = header.extended_long.next;
} else {
if (header.extended_short.vsec_id == vsec)
return offset;
if (!header.extended_short.length)
return -ENOENT;
offset = header.extended_short.next;
} }
offset = tb_cap_next(&header, offset);
if (!offset)
return 0;
} }
tb_port_WARN(port,
"run out of retries while looking for cap %#x in config space %d, last offset: %#x\n", return -ENOENT;
cap, space, offset);
return -EIO;
} }
...@@ -192,7 +192,7 @@ static int tb_init_port(struct tb_port *port) ...@@ -192,7 +192,7 @@ static int tb_init_port(struct tb_port *port)
/* Port 0 is the switch itself and has no PHY. */ /* Port 0 is the switch itself and has no PHY. */
if (port->config.type == TB_TYPE_PORT && port->port != 0) { if (port->config.type == TB_TYPE_PORT && port->port != 0) {
cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); cap = tb_port_find_cap(port, TB_PORT_CAP_PHY);
if (cap > 0) if (cap > 0)
port->cap_phy = cap; port->cap_phy = cap;
...@@ -394,9 +394,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) ...@@ -394,9 +394,9 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route)
sw->ports[i].port = i; sw->ports[i].port = i;
} }
cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS); cap = tb_switch_find_vse_cap(sw, TB_VSE_CAP_PLUG_EVENTS);
if (cap < 0) { if (cap < 0) {
tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n"); tb_sw_warn(sw, "cannot find TB_VSE_CAP_PLUG_EVENTS aborting\n");
goto err; goto err;
} }
sw->cap_plug_events = cap; sw->cap_plug_events = cap;
......
...@@ -121,8 +121,8 @@ static struct tb_port *tb_find_unused_down_port(struct tb_switch *sw) ...@@ -121,8 +121,8 @@ static struct tb_port *tb_find_unused_down_port(struct tb_switch *sw)
continue; continue;
if (sw->ports[i].config.type != TB_TYPE_PCIE_DOWN) if (sw->ports[i].config.type != TB_TYPE_PCIE_DOWN)
continue; continue;
cap = tb_find_cap(&sw->ports[i], TB_CFG_PORT, TB_CAP_PCIE); cap = tb_port_find_cap(&sw->ports[i], TB_PORT_CAP_ADAP);
if (cap <= 0) if (cap < 0)
continue; continue;
res = tb_port_read(&sw->ports[i], &data, TB_CFG_PORT, cap, 1); res = tb_port_read(&sw->ports[i], &data, TB_CFG_PORT, cap, 1);
if (res < 0) if (res < 0)
...@@ -165,8 +165,8 @@ static void tb_activate_pcie_devices(struct tb *tb) ...@@ -165,8 +165,8 @@ static void tb_activate_pcie_devices(struct tb *tb)
} }
/* check whether port is already activated */ /* check whether port is already activated */
cap = tb_find_cap(up_port, TB_CFG_PORT, TB_CAP_PCIE); cap = tb_port_find_cap(up_port, TB_PORT_CAP_ADAP);
if (cap <= 0) if (cap < 0)
continue; continue;
if (tb_port_read(up_port, &data, TB_CFG_PORT, cap, 1)) if (tb_port_read(up_port, &data, TB_CFG_PORT, cap, 1))
continue; continue;
......
...@@ -233,7 +233,8 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); ...@@ -233,7 +233,8 @@ 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_add_nfc_credits(struct tb_port *port, int credits);
int tb_port_clear_counter(struct tb_port *port, int counter); int tb_port_clear_counter(struct tb_port *port, int counter);
int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap); int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec);
int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap);
struct tb_path *tb_path_alloc(struct tb *tb, int num_hops); struct tb_path *tb_path_alloc(struct tb *tb, int num_hops);
void tb_path_free(struct tb_path *path); void tb_path_free(struct tb_path *path);
......
...@@ -23,15 +23,22 @@ ...@@ -23,15 +23,22 @@
*/ */
#define TB_MAX_CONFIG_RW_LENGTH 60 #define TB_MAX_CONFIG_RW_LENGTH 60
enum tb_cap { enum tb_switch_cap {
TB_CAP_PHY = 0x0001, TB_SWITCH_CAP_VSE = 0x05,
TB_CAP_TIME1 = 0x0003, };
TB_CAP_PCIE = 0x0004,
TB_CAP_I2C = 0x0005, enum tb_switch_vse_cap {
TB_CAP_PLUG_EVENTS = 0x0105, /* also EEPROM */ TB_VSE_CAP_PLUG_EVENTS = 0x01, /* also EEPROM */
TB_CAP_TIME2 = 0x0305, TB_VSE_CAP_TIME2 = 0x03,
TB_CAP_IECS = 0x0405, TB_VSE_CAP_IECS = 0x04,
TB_CAP_LINK_CONTROLLER = 0x0605, /* also IECS */ TB_VSE_CAP_LINK_CONTROLLER = 0x06, /* also IECS */
};
enum tb_port_cap {
TB_PORT_CAP_PHY = 0x01,
TB_PORT_CAP_TIME1 = 0x03,
TB_PORT_CAP_ADAP = 0x04,
TB_PORT_CAP_VSE = 0x05,
}; };
enum tb_port_state { enum tb_port_state {
...@@ -49,15 +56,34 @@ struct tb_cap_basic { ...@@ -49,15 +56,34 @@ struct tb_cap_basic {
u8 cap; /* if cap == 0x05 then we have a extended capability */ u8 cap; /* if cap == 0x05 then we have a extended capability */
} __packed; } __packed;
/**
* struct tb_cap_extended_short - Switch extended short capability
* @next: Pointer to the next capability. If @next and @length are zero
* then we have a long cap.
* @cap: Base capability ID (see &enum tb_switch_cap)
* @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
* @length: Length of this capability
*/
struct tb_cap_extended_short { struct tb_cap_extended_short {
u8 next; /* if next and length are zero then we have a long cap */ u8 next;
enum tb_cap cap:16; u8 cap;
u8 vsec_id;
u8 length; u8 length;
} __packed; } __packed;
/**
* struct tb_cap_extended_long - Switch extended long capability
* @zero1: This field should be zero
* @cap: Base capability ID (see &enum tb_switch_cap)
* @vsec_id: Vendor specific capability ID (see &enum switch_vse_cap)
* @zero2: This field should be zero
* @next: Pointer to the next capability
* @length: Length of this capability
*/
struct tb_cap_extended_long { struct tb_cap_extended_long {
u8 zero1; u8 zero1;
enum tb_cap cap:16; u8 cap;
u8 vsec_id;
u8 zero2; u8 zero2;
u16 next; u16 next;
u16 length; u16 length;
......
...@@ -147,10 +147,10 @@ bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel) ...@@ -147,10 +147,10 @@ bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel)
static int tb_pci_port_active(struct tb_port *port, bool active) static int tb_pci_port_active(struct tb_port *port, bool active)
{ {
u32 word = active ? 0x80000000 : 0x0; u32 word = active ? 0x80000000 : 0x0;
int cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PCIE); int cap = tb_port_find_cap(port, TB_PORT_CAP_ADAP);
if (cap <= 0) { if (cap < 0) {
tb_port_warn(port, "TB_CAP_PCIE not found: %d\n", cap); tb_port_warn(port, "TB_PORT_CAP_ADAP not found: %d\n", cap);
return cap ? cap : -ENXIO; return cap;
} }
return tb_port_write(port, &word, TB_CFG_PORT, cap, 1); return tb_port_write(port, &word, TB_CFG_PORT, cap, 1);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment