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, buffer: &value, space: TB_CFG_SWITCH, offset, length: 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, buffer: &value, space: TB_CFG_SWITCH, offset, length: 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(sw: port->sw)) { |
56 | u32 dummy; |
57 | |
58 | tb_port_read(port, buffer: &dummy, space: TB_CFG_PORT, offset: 0, length: 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 | * Returns dword offset of the next capability in port config space |
68 | * capability list and returns it. Passing %0 returns the first entry in |
69 | * the capability list. If no next capability is found returns %0. In case |
70 | * of failure returns negative errno. |
71 | */ |
72 | int tb_port_next_cap(struct tb_port *port, unsigned int offset) |
73 | { |
74 | struct tb_cap_any ; |
75 | int ret; |
76 | |
77 | if (!offset) |
78 | return port->config.first_cap_offset; |
79 | |
80 | ret = tb_port_read(port, buffer: &header, space: TB_CFG_PORT, offset, length: 1); |
81 | if (ret) |
82 | return ret; |
83 | |
84 | return header.basic.next; |
85 | } |
86 | |
87 | static int __tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) |
88 | { |
89 | int offset = 0; |
90 | |
91 | do { |
92 | struct tb_cap_any ; |
93 | int ret; |
94 | |
95 | offset = tb_port_next_cap(port, offset); |
96 | if (offset < 0) |
97 | return offset; |
98 | |
99 | ret = tb_port_read(port, buffer: &header, space: TB_CFG_PORT, offset, length: 1); |
100 | if (ret) |
101 | return ret; |
102 | |
103 | if (header.basic.cap == cap) |
104 | return offset; |
105 | } while (offset > 0); |
106 | |
107 | return -ENOENT; |
108 | } |
109 | |
110 | /** |
111 | * tb_port_find_cap() - Find port capability |
112 | * @port: Port to find the capability for |
113 | * @cap: Capability to look |
114 | * |
115 | * Returns offset to start of capability or %-ENOENT if no such |
116 | * capability was found. Negative errno is returned if there was an |
117 | * error. |
118 | */ |
119 | int tb_port_find_cap(struct tb_port *port, enum tb_port_cap cap) |
120 | { |
121 | int ret; |
122 | |
123 | ret = tb_port_enable_tmu(port, enable: true); |
124 | if (ret) |
125 | return ret; |
126 | |
127 | ret = __tb_port_find_cap(port, cap); |
128 | |
129 | tb_port_dummy_read(port); |
130 | tb_port_enable_tmu(port, enable: false); |
131 | |
132 | return ret; |
133 | } |
134 | |
135 | /** |
136 | * tb_switch_next_cap() - Return next capability in the linked list |
137 | * @sw: Switch to find the capability for |
138 | * @offset: Previous capability offset (%0 for start) |
139 | * |
140 | * Finds dword offset of the next capability in router config space |
141 | * capability list and returns it. Passing %0 returns the first entry in |
142 | * the capability list. If no next capability is found returns %0. In case |
143 | * of failure returns negative errno. |
144 | */ |
145 | int tb_switch_next_cap(struct tb_switch *sw, unsigned int offset) |
146 | { |
147 | struct tb_cap_any ; |
148 | int ret; |
149 | |
150 | if (!offset) |
151 | return sw->config.first_cap_offset; |
152 | |
153 | ret = tb_sw_read(sw, buffer: &header, space: TB_CFG_SWITCH, offset, length: 2); |
154 | if (ret) |
155 | return ret; |
156 | |
157 | switch (header.basic.cap) { |
158 | case TB_SWITCH_CAP_TMU: |
159 | ret = header.basic.next; |
160 | break; |
161 | |
162 | case TB_SWITCH_CAP_VSE: |
163 | if (!header.extended_short.length) |
164 | ret = header.extended_long.next; |
165 | else |
166 | ret = header.extended_short.next; |
167 | break; |
168 | |
169 | default: |
170 | tb_sw_dbg(sw, "unknown capability %#x at %#x\n" , |
171 | header.basic.cap, offset); |
172 | ret = -EINVAL; |
173 | break; |
174 | } |
175 | |
176 | return ret >= VSE_CAP_OFFSET_MAX ? 0 : ret; |
177 | } |
178 | |
179 | /** |
180 | * tb_switch_find_cap() - Find switch capability |
181 | * @sw: Switch to find the capability for |
182 | * @cap: Capability to look |
183 | * |
184 | * Returns offset to start of capability or %-ENOENT if no such |
185 | * capability was found. Negative errno is returned if there was an |
186 | * error. |
187 | */ |
188 | int tb_switch_find_cap(struct tb_switch *sw, enum tb_switch_cap cap) |
189 | { |
190 | int offset = 0; |
191 | |
192 | do { |
193 | struct tb_cap_any ; |
194 | int ret; |
195 | |
196 | offset = tb_switch_next_cap(sw, offset); |
197 | if (offset < 0) |
198 | return offset; |
199 | |
200 | ret = tb_sw_read(sw, buffer: &header, space: TB_CFG_SWITCH, offset, length: 1); |
201 | if (ret) |
202 | return ret; |
203 | |
204 | if (header.basic.cap == cap) |
205 | return offset; |
206 | } while (offset); |
207 | |
208 | return -ENOENT; |
209 | } |
210 | |
211 | /** |
212 | * tb_switch_find_vse_cap() - Find switch vendor specific capability |
213 | * @sw: Switch to find the capability for |
214 | * @vsec: Vendor specific capability to look |
215 | * |
216 | * Functions enumerates vendor specific capabilities (VSEC) of a switch |
217 | * and returns offset when capability matching @vsec is found. If no |
218 | * such capability is found returns %-ENOENT. In case of error returns |
219 | * negative errno. |
220 | */ |
221 | int tb_switch_find_vse_cap(struct tb_switch *sw, enum tb_switch_vse_cap vsec) |
222 | { |
223 | int offset = 0; |
224 | |
225 | do { |
226 | struct tb_cap_any ; |
227 | int ret; |
228 | |
229 | offset = tb_switch_next_cap(sw, offset); |
230 | if (offset < 0) |
231 | return offset; |
232 | |
233 | ret = tb_sw_read(sw, buffer: &header, space: TB_CFG_SWITCH, offset, length: 1); |
234 | if (ret) |
235 | return ret; |
236 | |
237 | if (header.extended_short.cap == TB_SWITCH_CAP_VSE && |
238 | header.extended_short.vsec_id == vsec) |
239 | return offset; |
240 | } while (offset); |
241 | |
242 | return -ENOENT; |
243 | } |
244 | |