From da2da04b8d4476a411feb2a12b47792aebbc142f Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Tue, 6 Jun 2017 15:24:58 +0300 Subject: 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: Mika Westerberg Reviewed-by: Yehezkel Bernat Reviewed-by: Michael Jamet Reviewed-by: Andy Shevchenko Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/cap.c | 169 +++++++++++++++++++++++++--------------------- 1 file changed, 91 insertions(+), 78 deletions(-) (limited to 'drivers/thunderbolt/cap.c') diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c index a7b47e7cddbd..38bc27a5ce4f 100644 --- a/drivers/thunderbolt/cap.c +++ b/drivers/thunderbolt/cap.c @@ -9,6 +9,8 @@ #include "tb.h" +#define CAP_OFFSET_MAX 0xff +#define VSE_CAP_OFFSET_MAX 0xffff struct tb_cap_any { union { @@ -18,99 +20,110 @@ struct tb_cap_any { }; } __packed; -static bool tb_cap_is_basic(struct tb_cap_any *cap) -{ - /* basic.cap is u8. This checks only the lower 8 bit of cap. */ - return cap->basic.cap != 5; -} - -static bool tb_cap_is_long(struct tb_cap_any *cap) +/** + * tb_port_find_cap() - Find port capability + * @port: Port 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_port_find_cap(struct tb_port *port, enum tb_port_cap cap) { - return !tb_cap_is_basic(cap) - && cap->extended_short.next == 0 - && cap->extended_short.length == 0; -} + u32 offset; -static enum tb_cap tb_cap(struct tb_cap_any *cap) -{ - if (tb_cap_is_basic(cap)) - return cap->basic.cap; + /* + * DP out adapters claim to implement TMU capability but in + * reality they do not so we hard code the adapter specific + * capability offset here. + */ + if (port->config.type == TB_TYPE_DP_HDMI_OUT) + offset = 0x39; else - /* extended_short/long have cap at the same offset. */ - return cap->extended_short.cap; + offset = 0x1; + + 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; - if (offset == 1) { - /* - * The first pointer is part of the switch header and always - * a simple pointer. - */ - next = cap->basic.next; - } else { - /* - * Somehow Intel decided to use 3 different types of capability - * headers. It is not like anyone could have predicted that - * single byte offsets are not enough... - */ - if (tb_cap_is_basic(cap)) - next = cap->basic.next; - else if (!tb_cap_is_long(cap)) - next = cap->extended_short.next; - else - next = cap->extended_long.next; + int offset = sw->config.first_cap_offset; + + while (offset > 0 && offset < CAP_OFFSET_MAX) { + struct tb_cap_any header; + int ret; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); + if (ret) + return ret; + + if (header.basic.cap == cap) + return offset; + + offset = header.basic.next; } - /* - * "Hey, we could terminate some capability lists with a null offset - * and others with a pointer to the last element." - "Great idea!" - */ - if (next == offset) - return 0; - return next; + + return -ENOENT; } /** - * 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. - * Returns an error code on failure. + * Functions enumerates vendor specific capabilities (VSEC) of a switch + * 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; - int res; - int retries = 10; - while (retries--) { - res = tb_port_read(port, &header, space, offset, 1); - 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) + int offset; + + offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); + if (offset < 0) + return offset; + + while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { + int ret; + + ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); + 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; - if (tb_cap_is_long(&header)) { - /* tb_cap_extended_long is 2 dwords */ - res = tb_port_read(port, &header, space, - offset, 2); - if (res) - return res; - } + 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", - cap, space, offset); - return -EIO; + + return -ENOENT; } -- cgit v1.2.1