1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Connection Management Procedures (IEC 61883-1) helper functions |
4 | * |
5 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/firewire.h> |
10 | #include <linux/firewire-constants.h> |
11 | #include <linux/module.h> |
12 | #include <linux/sched.h> |
13 | #include "lib.h" |
14 | #include "iso-resources.h" |
15 | #include "cmp.h" |
16 | |
17 | /* MPR common fields */ |
18 | #define MPR_SPEED_MASK 0xc0000000 |
19 | #define MPR_SPEED_SHIFT 30 |
20 | #define MPR_XSPEED_MASK 0x00000060 |
21 | #define MPR_XSPEED_SHIFT 5 |
22 | #define MPR_PLUGS_MASK 0x0000001f |
23 | |
24 | /* PCR common fields */ |
25 | #define PCR_ONLINE 0x80000000 |
26 | #define PCR_BCAST_CONN 0x40000000 |
27 | #define PCR_P2P_CONN_MASK 0x3f000000 |
28 | #define PCR_P2P_CONN_SHIFT 24 |
29 | #define PCR_CHANNEL_MASK 0x003f0000 |
30 | #define PCR_CHANNEL_SHIFT 16 |
31 | |
32 | /* oPCR specific fields */ |
33 | #define OPCR_XSPEED_MASK 0x00C00000 |
34 | #define OPCR_XSPEED_SHIFT 22 |
35 | #define OPCR_SPEED_MASK 0x0000C000 |
36 | #define OPCR_SPEED_SHIFT 14 |
37 | #define OPCR_OVERHEAD_ID_MASK 0x00003C00 |
38 | #define OPCR_OVERHEAD_ID_SHIFT 10 |
39 | |
40 | enum bus_reset_handling { |
41 | ABORT_ON_BUS_RESET, |
42 | SUCCEED_ON_BUS_RESET, |
43 | }; |
44 | |
45 | static __printf(2, 3) |
46 | void cmp_error(struct cmp_connection *c, const char *fmt, ...) |
47 | { |
48 | va_list va; |
49 | |
50 | va_start(va, fmt); |
51 | dev_err(&c->resources.unit->device, "%cPCR%u: %pV" , |
52 | (c->direction == CMP_INPUT) ? 'i' : 'o', |
53 | c->pcr_index, &(struct va_format){ fmt, &va }); |
54 | va_end(va); |
55 | } |
56 | |
57 | static u64 mpr_address(struct cmp_connection *c) |
58 | { |
59 | if (c->direction == CMP_INPUT) |
60 | return CSR_REGISTER_BASE + CSR_IMPR; |
61 | else |
62 | return CSR_REGISTER_BASE + CSR_OMPR; |
63 | } |
64 | |
65 | static u64 pcr_address(struct cmp_connection *c) |
66 | { |
67 | if (c->direction == CMP_INPUT) |
68 | return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index); |
69 | else |
70 | return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index); |
71 | } |
72 | |
73 | static int pcr_modify(struct cmp_connection *c, |
74 | __be32 (*modify)(struct cmp_connection *c, __be32 old), |
75 | int (*check)(struct cmp_connection *c, __be32 pcr), |
76 | enum bus_reset_handling bus_reset_handling) |
77 | { |
78 | __be32 old_arg, buffer[2]; |
79 | int err; |
80 | |
81 | buffer[0] = c->last_pcr_value; |
82 | for (;;) { |
83 | old_arg = buffer[0]; |
84 | buffer[1] = modify(c, buffer[0]); |
85 | |
86 | err = snd_fw_transaction( |
87 | unit: c->resources.unit, TCODE_LOCK_COMPARE_SWAP, |
88 | offset: pcr_address(c), buffer, length: 8, |
89 | FW_FIXED_GENERATION | c->resources.generation); |
90 | |
91 | if (err < 0) { |
92 | if (err == -EAGAIN && |
93 | bus_reset_handling == SUCCEED_ON_BUS_RESET) |
94 | err = 0; |
95 | return err; |
96 | } |
97 | |
98 | if (buffer[0] == old_arg) /* success? */ |
99 | break; |
100 | |
101 | if (check) { |
102 | err = check(c, buffer[0]); |
103 | if (err < 0) |
104 | return err; |
105 | } |
106 | } |
107 | c->last_pcr_value = buffer[1]; |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | |
113 | /** |
114 | * cmp_connection_init - initializes a connection manager |
115 | * @c: the connection manager to initialize |
116 | * @unit: a unit of the target device |
117 | * @direction: input or output |
118 | * @pcr_index: the index of the iPCR/oPCR on the target device |
119 | */ |
120 | int cmp_connection_init(struct cmp_connection *c, |
121 | struct fw_unit *unit, |
122 | enum cmp_direction direction, |
123 | unsigned int pcr_index) |
124 | { |
125 | __be32 mpr_be; |
126 | u32 mpr; |
127 | int err; |
128 | |
129 | c->direction = direction; |
130 | err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, |
131 | offset: mpr_address(c), buffer: &mpr_be, length: 4, flags: 0); |
132 | if (err < 0) |
133 | return err; |
134 | mpr = be32_to_cpu(mpr_be); |
135 | |
136 | if (pcr_index >= (mpr & MPR_PLUGS_MASK)) |
137 | return -EINVAL; |
138 | |
139 | err = fw_iso_resources_init(r: &c->resources, unit); |
140 | if (err < 0) |
141 | return err; |
142 | |
143 | c->connected = false; |
144 | mutex_init(&c->mutex); |
145 | c->last_pcr_value = cpu_to_be32(0x80000000); |
146 | c->pcr_index = pcr_index; |
147 | c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT; |
148 | if (c->max_speed == SCODE_BETA) |
149 | c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT; |
150 | |
151 | return 0; |
152 | } |
153 | EXPORT_SYMBOL(cmp_connection_init); |
154 | |
155 | /** |
156 | * cmp_connection_check_used - check connection is already esablished or not |
157 | * @c: the connection manager to be checked |
158 | * @used: the pointer to store the result of checking the connection |
159 | */ |
160 | int cmp_connection_check_used(struct cmp_connection *c, bool *used) |
161 | { |
162 | __be32 pcr; |
163 | int err; |
164 | |
165 | err = snd_fw_transaction( |
166 | unit: c->resources.unit, TCODE_READ_QUADLET_REQUEST, |
167 | offset: pcr_address(c), buffer: &pcr, length: 4, flags: 0); |
168 | if (err >= 0) |
169 | *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN | |
170 | PCR_P2P_CONN_MASK)); |
171 | |
172 | return err; |
173 | } |
174 | EXPORT_SYMBOL(cmp_connection_check_used); |
175 | |
176 | /** |
177 | * cmp_connection_destroy - free connection manager resources |
178 | * @c: the connection manager |
179 | */ |
180 | void cmp_connection_destroy(struct cmp_connection *c) |
181 | { |
182 | WARN_ON(c->connected); |
183 | mutex_destroy(lock: &c->mutex); |
184 | fw_iso_resources_destroy(r: &c->resources); |
185 | } |
186 | EXPORT_SYMBOL(cmp_connection_destroy); |
187 | |
188 | int cmp_connection_reserve(struct cmp_connection *c, |
189 | unsigned int max_payload_bytes) |
190 | { |
191 | int err; |
192 | |
193 | mutex_lock(&c->mutex); |
194 | |
195 | if (WARN_ON(c->resources.allocated)) { |
196 | err = -EBUSY; |
197 | goto end; |
198 | } |
199 | |
200 | c->speed = min(c->max_speed, |
201 | fw_parent_device(c->resources.unit)->max_speed); |
202 | |
203 | err = fw_iso_resources_allocate(r: &c->resources, max_payload_bytes, |
204 | speed: c->speed); |
205 | end: |
206 | mutex_unlock(lock: &c->mutex); |
207 | |
208 | return err; |
209 | } |
210 | EXPORT_SYMBOL(cmp_connection_reserve); |
211 | |
212 | void cmp_connection_release(struct cmp_connection *c) |
213 | { |
214 | mutex_lock(&c->mutex); |
215 | fw_iso_resources_free(r: &c->resources); |
216 | mutex_unlock(lock: &c->mutex); |
217 | } |
218 | EXPORT_SYMBOL(cmp_connection_release); |
219 | |
220 | static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) |
221 | { |
222 | ipcr &= ~cpu_to_be32(PCR_BCAST_CONN | |
223 | PCR_P2P_CONN_MASK | |
224 | PCR_CHANNEL_MASK); |
225 | ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); |
226 | ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); |
227 | |
228 | return ipcr; |
229 | } |
230 | |
231 | static int get_overhead_id(struct cmp_connection *c) |
232 | { |
233 | int id; |
234 | |
235 | /* |
236 | * apply "oPCR overhead ID encoding" |
237 | * the encoding table can convert up to 512. |
238 | * here the value over 512 is converted as the same way as 512. |
239 | */ |
240 | for (id = 1; id < 16; id++) { |
241 | if (c->resources.bandwidth_overhead < (id << 5)) |
242 | break; |
243 | } |
244 | if (id == 16) |
245 | id = 0; |
246 | |
247 | return id; |
248 | } |
249 | |
250 | static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr) |
251 | { |
252 | unsigned int spd, xspd; |
253 | |
254 | /* generate speed and extended speed field value */ |
255 | if (c->speed > SCODE_400) { |
256 | spd = SCODE_800; |
257 | xspd = c->speed - SCODE_800; |
258 | } else { |
259 | spd = c->speed; |
260 | xspd = 0; |
261 | } |
262 | |
263 | opcr &= ~cpu_to_be32(PCR_BCAST_CONN | |
264 | PCR_P2P_CONN_MASK | |
265 | OPCR_XSPEED_MASK | |
266 | PCR_CHANNEL_MASK | |
267 | OPCR_SPEED_MASK | |
268 | OPCR_OVERHEAD_ID_MASK); |
269 | opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); |
270 | opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT); |
271 | opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); |
272 | opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT); |
273 | opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT); |
274 | |
275 | return opcr; |
276 | } |
277 | |
278 | static int pcr_set_check(struct cmp_connection *c, __be32 pcr) |
279 | { |
280 | if (pcr & cpu_to_be32(PCR_BCAST_CONN | |
281 | PCR_P2P_CONN_MASK)) { |
282 | cmp_error(c, fmt: "plug is already in use\n" ); |
283 | return -EBUSY; |
284 | } |
285 | if (!(pcr & cpu_to_be32(PCR_ONLINE))) { |
286 | cmp_error(c, fmt: "plug is not on-line\n" ); |
287 | return -ECONNREFUSED; |
288 | } |
289 | |
290 | return 0; |
291 | } |
292 | |
293 | /** |
294 | * cmp_connection_establish - establish a connection to the target |
295 | * @c: the connection manager |
296 | * |
297 | * This function establishes a point-to-point connection from the local |
298 | * computer to the target by allocating isochronous resources (channel and |
299 | * bandwidth) and setting the target's input/output plug control register. |
300 | * When this function succeeds, the caller is responsible for starting |
301 | * transmitting packets. |
302 | */ |
303 | int cmp_connection_establish(struct cmp_connection *c) |
304 | { |
305 | int err; |
306 | |
307 | mutex_lock(&c->mutex); |
308 | |
309 | if (WARN_ON(c->connected)) { |
310 | mutex_unlock(lock: &c->mutex); |
311 | return -EISCONN; |
312 | } |
313 | |
314 | retry_after_bus_reset: |
315 | if (c->direction == CMP_OUTPUT) |
316 | err = pcr_modify(c, modify: opcr_set_modify, check: pcr_set_check, |
317 | bus_reset_handling: ABORT_ON_BUS_RESET); |
318 | else |
319 | err = pcr_modify(c, modify: ipcr_set_modify, check: pcr_set_check, |
320 | bus_reset_handling: ABORT_ON_BUS_RESET); |
321 | |
322 | if (err == -EAGAIN) { |
323 | err = fw_iso_resources_update(r: &c->resources); |
324 | if (err >= 0) |
325 | goto retry_after_bus_reset; |
326 | } |
327 | if (err >= 0) |
328 | c->connected = true; |
329 | |
330 | mutex_unlock(lock: &c->mutex); |
331 | |
332 | return err; |
333 | } |
334 | EXPORT_SYMBOL(cmp_connection_establish); |
335 | |
336 | /** |
337 | * cmp_connection_update - update the connection after a bus reset |
338 | * @c: the connection manager |
339 | * |
340 | * This function must be called from the driver's .update handler to |
341 | * reestablish any connection that might have been active. |
342 | * |
343 | * Returns zero on success, or a negative error code. On an error, the |
344 | * connection is broken and the caller must stop transmitting iso packets. |
345 | */ |
346 | int cmp_connection_update(struct cmp_connection *c) |
347 | { |
348 | int err; |
349 | |
350 | mutex_lock(&c->mutex); |
351 | |
352 | if (!c->connected) { |
353 | mutex_unlock(lock: &c->mutex); |
354 | return 0; |
355 | } |
356 | |
357 | err = fw_iso_resources_update(r: &c->resources); |
358 | if (err < 0) |
359 | goto err_unconnect; |
360 | |
361 | if (c->direction == CMP_OUTPUT) |
362 | err = pcr_modify(c, modify: opcr_set_modify, check: pcr_set_check, |
363 | bus_reset_handling: SUCCEED_ON_BUS_RESET); |
364 | else |
365 | err = pcr_modify(c, modify: ipcr_set_modify, check: pcr_set_check, |
366 | bus_reset_handling: SUCCEED_ON_BUS_RESET); |
367 | |
368 | if (err < 0) |
369 | goto err_unconnect; |
370 | |
371 | mutex_unlock(lock: &c->mutex); |
372 | |
373 | return 0; |
374 | |
375 | err_unconnect: |
376 | c->connected = false; |
377 | mutex_unlock(lock: &c->mutex); |
378 | |
379 | return err; |
380 | } |
381 | EXPORT_SYMBOL(cmp_connection_update); |
382 | |
383 | static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr) |
384 | { |
385 | return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK); |
386 | } |
387 | |
388 | /** |
389 | * cmp_connection_break - break the connection to the target |
390 | * @c: the connection manager |
391 | * |
392 | * This function deactives the connection in the target's input/output plug |
393 | * control register, and frees the isochronous resources of the connection. |
394 | * Before calling this function, the caller should cease transmitting packets. |
395 | */ |
396 | void cmp_connection_break(struct cmp_connection *c) |
397 | { |
398 | int err; |
399 | |
400 | mutex_lock(&c->mutex); |
401 | |
402 | if (!c->connected) { |
403 | mutex_unlock(lock: &c->mutex); |
404 | return; |
405 | } |
406 | |
407 | err = pcr_modify(c, modify: pcr_break_modify, NULL, bus_reset_handling: SUCCEED_ON_BUS_RESET); |
408 | if (err < 0) |
409 | cmp_error(c, fmt: "plug is still connected\n" ); |
410 | |
411 | c->connected = false; |
412 | |
413 | mutex_unlock(lock: &c->mutex); |
414 | } |
415 | EXPORT_SYMBOL(cmp_connection_break); |
416 | |