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 static int tb_port_enable_tmu(struct tb_port *port, bool enable) 19 { 20 struct tb_switch *sw = port->sw; 21 u32 value, offset; 22 int ret; 23 24 /* 25 * Legacy devices need to have TMU access enabled before port 26 * space can be fully accessed. 27 */ 28 if (tb_switch_is_light_ridge(sw)) 29 offset = 0x26; 30 else if (tb_switch_is_eagle_ridge(sw)) 31 offset = 0x2a; 32 else 33 return 0; 34 35 ret = tb_sw_read(sw, &value, TB_CFG_SWITCH, offset, 1); 36 if (ret) 37 return ret; 38 39 if (enable) 40 value |= TMU_ACCESS_EN; 41 else 42 value &= ~TMU_ACCESS_EN; 43 44 return tb_sw_write(sw, &value, TB_CFG_SWITCH, offset, 1); 45 } 46 47 static void tb_port_dummy_read(struct tb_port *port) 48 { 49 /* 50 * When reading from next capability pointer location in port 51 * config space the read data is not cleared on LR. To avoid 52 * reading stale data on next read perform one dummy read after 53 * port capabilities are walked. 54 */ 55 if (tb_switch_is_light_ridge(port->sw)) { 56 u32 dummy; 57 58 tb_port_read(port, &dummy, TB_CFG_PORT, 0, 1); 59 } 60 } 61 62 /** 63 * tb_port_next_cap() - Return next capability in the linked list 64 * @port: Port to find the capability for 65 * @offset: Previous capability offset (%0 for start) 66 * 67 * Finds dword offset of the next capability in port config space 68 * capability list. When passed %0 in @offset parameter, first entry 69 * will be returned, if it exists. 70 * 71 * Return: 72 * * Double word offset of the first or next capability - On success. 73 * * %0 - If no next capability is found. 74 * * Negative errno - Another error occurred. 75 */ 76 int tb_port_next_cap(struct tb_port *port, unsigned int offset) 77 { 78 struct tb_cap_any header; 79 int ret; 80 81 if (!offset) 82 return port->config.first_cap_offset; 83 84 ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); 85 if (ret) 86 return ret; 87 88 return header.basic.next; 89 } 90 91 static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 92 { 93 int offset = 0; 94 95 do { 96 struct tb_cap_any header; 97 int ret; 98 99 offset = tb_port_next_cap(port, offset); 100 if (offset < 0) 101 return offset; 102 103 ret = tb_port_read(port, &header, TB_CFG_PORT, offset, 1); 104 if (ret) 105 return ret; 106 107 if (header.basic.cap == cap) 108 return offset; 109 } while (offset > 0); 110 111 return -ENOENT; 112 } 113 114 /** 115 * tb_port_find_cap() - Find port capability 116 * @port: Port to find the capability for 117 * @cap: Capability to look 118 * 119 * Return: 120 * * Offset to the start of capability - On success. 121 * * %-ENOENT - If no such capability was found. 122 * * Negative errno - Another error occurred. 123 */ 124 int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) 125 { 126 int ret; 127 128 ret = tb_port_enable_tmu(port, true); 129 if (ret) 130 return ret; 131 132 ret = __tb_port_find_cap(port, cap); 133 134 tb_port_dummy_read(port); 135 tb_port_enable_tmu(port, false); 136 137 return ret; 138 } 139 140 /** 141 * tb_switch_next_cap() - Return next capability in the linked list 142 * @sw: Switch to find the capability for 143 * @offset: Previous capability offset (%0 for start) 144 * 145 * Finds dword offset of the next capability in port config space 146 * capability list. When passed %0 in @offset parameter, first entry 147 * will be returned, if it exists. 148 * 149 * Return: 150 * * Double word offset of the first or next capability - On success. 151 * * %0 - If no next capability is found. 152 * * Negative errno - Another error occurred. 153 */ 154 int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) 155 { 156 struct tb_cap_any header; 157 int ret; 158 159 if (!offset) 160 return sw->config.first_cap_offset; 161 162 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 2); 163 if (ret) 164 return ret; 165 166 switch (header.basic.cap) { 167 case TB_SWITCH_CAP_TMU: 168 ret = header.basic.next; 169 break; 170 171 case TB_SWITCH_CAP_VSE: 172 if (!header.extended_short.length) 173 ret = header.extended_long.next; 174 else 175 ret = header.extended_short.next; 176 break; 177 178 default: 179 tb_sw_dbg(sw, "unknown capability %#x at %#x\n", 180 header.basic.cap, offset); 181 ret = -EINVAL; 182 break; 183 } 184 185 return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret; 186 } 187 188 /** 189 * tb_switch_find_cap() - Find switch capability 190 * @sw: Switch to find the capability for 191 * @cap: Capability to look 192 * 193 * Return: 194 * * Offset to the start of capability - On success. 195 * * %-ENOENT - If no such capability was found. 196 * * Negative errno - Another error occurred. 197 */ 198 int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) 199 { 200 int offset = 0; 201 202 do { 203 struct tb_cap_any header; 204 int ret; 205 206 offset = tb_switch_next_cap(sw, offset); 207 if (offset < 0) 208 return offset; 209 210 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); 211 if (ret) 212 return ret; 213 214 if (header.basic.cap == cap) 215 return offset; 216 } while (offset); 217 218 return -ENOENT; 219 } 220 221 /** 222 * tb_switch_find_vse_cap() - Find switch vendor specific capability 223 * @sw: Switch to find the capability for 224 * @vsec: Vendor specific capability to look 225 * 226 * This function enumerates vendor specific capabilities (VSEC) of a 227 * switch and returns offset when capability matching @vsec is found. 228 * 229 * Return: 230 * * Offset of capability - On success. 231 * * %-ENOENT - If capability was not found. 232 * * Negative errno - Another error occurred. 233 */ 234 int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) 235 { 236 int offset = 0; 237 238 do { 239 struct tb_cap_any header; 240 int ret; 241 242 offset = tb_switch_next_cap(sw, offset); 243 if (offset < 0) 244 return offset; 245 246 ret = tb_sw_read(sw, &header, TB_CFG_SWITCH, offset, 1); 247 if (ret) 248 return ret; 249 250 if (header.extended_short.cap == TB_SWITCH_CAP_VSE && 251 header.extended_short.vsec_id == vsec) 252 return offset; 253 } while (offset); 254 255 return -ENOENT; 256 } 257