1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Thunderbolt driver - capabilities lookup 4 * 5 * Copyright (c) 2014 Andreas Noever <andreas.noever@gmail.com> 6 * Copyright (C) 2018, Intel Corporation 7 */ 8 9 #include <linux/slab.h> 10 #include <linux/errno.h> 11 12 #include "tb.h" 13 14 #define CAP_OFFSET_MAX 0xff 15 #define VSE_CAP_OFFSET_MAX 0xffff 16 #define TMU_ACCESS_EN BIT(20) 17 18 struct tb_cap_any { 19 union { 20 struct tb_cap_basic basic; 21 struct tb_cap_extended_short extended_short; 22 struct tb_cap_extended_long extended_long; 23 }; 24 } __packed; 25 26 static int tb_port_enable_tmu(struct tb_port *port, bool enable) 27 { 28 struct tb_switch *sw = port->sw; 29 u32 value, offset; 30 int ret; 31 32 /* 33 * Legacy devices need to have TMU access enabled before port 34 * space can be fully accessed. 35 */ 36 if (tb_switch_is_lr(sw)) 37 offset = 0x26; 38 else if (tb_switch_is_er(sw)) 39 offset = 0x2a; 40 else 41 return 0; 42 43 ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1); 44 if (ret) 45 return ret; 46 47 if (enable) 48 value |= TMU_ACCESS_EN; 49 else 50 value &= ~TMU_ACCESS_EN; 51 52 return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1); 53 } 54 55 static void tb_port_dummy_read(struct tb_port *port) 56 { 57 /* 58 * When reading from next capability pointer location in port 59 * config space the read data is not cleared on LR. To avoid 60 * reading stale data on next read perform one dummy read after 61 * port capabilities are walked. 62 */ 63 if (tb_switch_is_lr(port->sw)) { 64 u32 dummy; 65 66 tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1); 67 } 68 } 69 70 static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 71 { 72 u32 offset = 1; 73 74 do { 75 struct tb_cap_any header; 76 int ret; 77 78 ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); 79 if (ret) 80 return ret; 81 82 if (header.basic.cap == cap) 83 return offset; 84 85 offset = header.basic.next; 86 } while (offset); 87 88 return -ENOENT; 89 } 90 91 /** 92 * tb_port_find_cap() - Find port capability 93 * @port: Port to find the capability for 94 * @cap: Capability to look 95 * 96 * Returns offset to start of capability or %-ENOENT if no such 97 * capability was found. Negative errno is returned if there was an 98 * error. 99 */ 100 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 101 { 102 int ret; 103 104 ret = tb_port_enable_tmu(port, true); 105 if (ret) 106 return ret; 107 108 ret = __tb_port_find_cap(port, cap); 109 110 tb_port_dummy_read(port); 111 tb_port_enable_tmu(port, false); 112 113 return ret; 114 } 115 116 static int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) 117 { 118 int offset = sw->config.first_cap_offset; 119 120 while (offset > 0 && offset < CAP_OFFSET_MAX) { 121 struct tb_cap_any header; 122 int ret; 123 124 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); 125 if (ret) 126 return ret; 127 128 if (header.basic.cap == cap) 129 return offset; 130 131 offset = header.basic.next; 132 } 133 134 return -ENOENT; 135 } 136 137 /** 138 * tb_switch_find_vse_cap() - Find switch vendor specific capability 139 * @sw: Switch to find the capability for 140 * @vsec: Vendor specific capability to look 141 * 142 * Functions enumerates vendor specific capabilities (VSEC) of a switch 143 * and returns offset when capability matching @vsec is found. If no 144 * such capability is found returns %-ENOENT. In case of error returns 145 * negative errno. 146 */ 147 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) 148 { 149 struct tb_cap_any header; 150 int offset; 151 152 offset = tb_switch_find_cap(sw, TB_SWITCH_CAP_VSE); 153 if (offset < 0) 154 return offset; 155 156 while (offset > 0 && offset < VSE_CAP_OFFSET_MAX) { 157 int ret; 158 159 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); 160 if (ret) 161 return ret; 162 163 /* 164 * Extended vendor specific capabilities come in two 165 * flavors: short and long. The latter is used when 166 * offset is over 0xff. 167 */ 168 if (offset >= CAP_OFFSET_MAX) { 169 if (header.extended_long.vsec_id == vsec) 170 return offset; 171 offset = header.extended_long.next; 172 } else { 173 if (header.extended_short.vsec_id == vsec) 174 return offset; 175 if (!header.extended_short.length) 176 return -ENOENT; 177 offset = header.extended_short.next; 178 } 179 } 180 181 return -ENOENT; 182 } 183