]>
Commit | Line | Data |
---|---|---|
31ef9134 CL |
1 | /* |
2 | * Connection Management Procedures (IEC 61883-1) helper functions | |
3 | * | |
4 | * Copyright (c) Clemens Ladisch <[email protected]> | |
5 | * Licensed under the terms of the GNU General Public License, version 2. | |
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 | #define IMPR_SPEED_MASK 0xc0000000 | |
18 | #define IMPR_SPEED_SHIFT 30 | |
19 | #define IMPR_XSPEED_MASK 0x00000060 | |
20 | #define IMPR_XSPEED_SHIFT 5 | |
21 | #define IMPR_PLUGS_MASK 0x0000001f | |
22 | ||
23 | #define IPCR_ONLINE 0x80000000 | |
24 | #define IPCR_BCAST_CONN 0x40000000 | |
25 | #define IPCR_P2P_CONN_MASK 0x3f000000 | |
26 | #define IPCR_P2P_CONN_SHIFT 24 | |
27 | #define IPCR_CHANNEL_MASK 0x003f0000 | |
28 | #define IPCR_CHANNEL_SHIFT 16 | |
29 | ||
30 | enum bus_reset_handling { | |
31 | ABORT_ON_BUS_RESET, | |
32 | SUCCEED_ON_BUS_RESET, | |
33 | }; | |
34 | ||
b9075fa9 | 35 | static __printf(2, 3) |
31ef9134 CL |
36 | void cmp_error(struct cmp_connection *c, const char *fmt, ...) |
37 | { | |
38 | va_list va; | |
39 | ||
40 | va_start(va, fmt); | |
41 | dev_err(&c->resources.unit->device, "%cPCR%u: %pV", | |
42 | 'i', c->pcr_index, &(struct va_format){ fmt, &va }); | |
43 | va_end(va); | |
44 | } | |
45 | ||
46 | static int pcr_modify(struct cmp_connection *c, | |
47 | __be32 (*modify)(struct cmp_connection *c, __be32 old), | |
48 | int (*check)(struct cmp_connection *c, __be32 pcr), | |
49 | enum bus_reset_handling bus_reset_handling) | |
50 | { | |
51 | struct fw_device *device = fw_parent_device(c->resources.unit); | |
31ef9134 CL |
52 | int generation = c->resources.generation; |
53 | int rcode, errors = 0; | |
f30e6d3e | 54 | __be32 old_arg, buffer[2]; |
31ef9134 CL |
55 | int err; |
56 | ||
57 | buffer[0] = c->last_pcr_value; | |
58 | for (;;) { | |
59 | old_arg = buffer[0]; | |
60 | buffer[1] = modify(c, buffer[0]); | |
61 | ||
62 | rcode = fw_run_transaction( | |
63 | device->card, TCODE_LOCK_COMPARE_SWAP, | |
64 | device->node_id, generation, device->max_speed, | |
65 | CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), | |
66 | buffer, 8); | |
67 | ||
68 | if (rcode == RCODE_COMPLETE) { | |
69 | if (buffer[0] == old_arg) /* success? */ | |
70 | break; | |
71 | ||
72 | if (check) { | |
73 | err = check(c, buffer[0]); | |
74 | if (err < 0) | |
75 | return err; | |
76 | } | |
77 | } else if (rcode == RCODE_GENERATION) | |
78 | goto bus_reset; | |
79 | else if (rcode_is_permanent_error(rcode) || ++errors >= 3) | |
80 | goto io_error; | |
81 | } | |
82 | c->last_pcr_value = buffer[1]; | |
83 | ||
84 | return 0; | |
85 | ||
86 | io_error: | |
87 | cmp_error(c, "transaction failed: %s\n", rcode_string(rcode)); | |
88 | return -EIO; | |
89 | ||
90 | bus_reset: | |
91 | return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0; | |
92 | } | |
93 | ||
94 | ||
95 | /** | |
96 | * cmp_connection_init - initializes a connection manager | |
97 | * @c: the connection manager to initialize | |
98 | * @unit: a unit of the target device | |
99 | * @ipcr_index: the index of the iPCR on the target device | |
100 | */ | |
101 | int cmp_connection_init(struct cmp_connection *c, | |
102 | struct fw_unit *unit, | |
103 | unsigned int ipcr_index) | |
104 | { | |
105 | __be32 impr_be; | |
106 | u32 impr; | |
107 | int err; | |
108 | ||
109 | err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, | |
110 | CSR_REGISTER_BASE + CSR_IMPR, | |
111 | &impr_be, 4); | |
112 | if (err < 0) | |
113 | return err; | |
114 | impr = be32_to_cpu(impr_be); | |
115 | ||
116 | if (ipcr_index >= (impr & IMPR_PLUGS_MASK)) | |
117 | return -EINVAL; | |
118 | ||
5b2599a0 CL |
119 | err = fw_iso_resources_init(&c->resources, unit); |
120 | if (err < 0) | |
121 | return err; | |
122 | ||
31ef9134 CL |
123 | c->connected = false; |
124 | mutex_init(&c->mutex); | |
31ef9134 CL |
125 | c->last_pcr_value = cpu_to_be32(0x80000000); |
126 | c->pcr_index = ipcr_index; | |
127 | c->max_speed = (impr & IMPR_SPEED_MASK) >> IMPR_SPEED_SHIFT; | |
128 | if (c->max_speed == SCODE_BETA) | |
129 | c->max_speed += (impr & IMPR_XSPEED_MASK) >> IMPR_XSPEED_SHIFT; | |
130 | ||
131 | return 0; | |
132 | } | |
133 | EXPORT_SYMBOL(cmp_connection_init); | |
134 | ||
135 | /** | |
136 | * cmp_connection_destroy - free connection manager resources | |
137 | * @c: the connection manager | |
138 | */ | |
139 | void cmp_connection_destroy(struct cmp_connection *c) | |
140 | { | |
141 | WARN_ON(c->connected); | |
142 | mutex_destroy(&c->mutex); | |
143 | fw_iso_resources_destroy(&c->resources); | |
144 | } | |
145 | EXPORT_SYMBOL(cmp_connection_destroy); | |
146 | ||
147 | ||
148 | static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) | |
149 | { | |
150 | ipcr &= ~cpu_to_be32(IPCR_BCAST_CONN | | |
151 | IPCR_P2P_CONN_MASK | | |
152 | IPCR_CHANNEL_MASK); | |
153 | ipcr |= cpu_to_be32(1 << IPCR_P2P_CONN_SHIFT); | |
154 | ipcr |= cpu_to_be32(c->resources.channel << IPCR_CHANNEL_SHIFT); | |
155 | ||
156 | return ipcr; | |
157 | } | |
158 | ||
159 | static int ipcr_set_check(struct cmp_connection *c, __be32 ipcr) | |
160 | { | |
161 | if (ipcr & cpu_to_be32(IPCR_BCAST_CONN | | |
162 | IPCR_P2P_CONN_MASK)) { | |
163 | cmp_error(c, "plug is already in use\n"); | |
164 | return -EBUSY; | |
165 | } | |
166 | if (!(ipcr & cpu_to_be32(IPCR_ONLINE))) { | |
167 | cmp_error(c, "plug is not on-line\n"); | |
168 | return -ECONNREFUSED; | |
169 | } | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
174 | /** | |
175 | * cmp_connection_establish - establish a connection to the target | |
176 | * @c: the connection manager | |
177 | * @max_payload_bytes: the amount of data (including CIP headers) per packet | |
178 | * | |
179 | * This function establishes a point-to-point connection from the local | |
180 | * computer to the target by allocating isochronous resources (channel and | |
181 | * bandwidth) and setting the target's input plug control register. When this | |
182 | * function succeeds, the caller is responsible for starting transmitting | |
183 | * packets. | |
184 | */ | |
185 | int cmp_connection_establish(struct cmp_connection *c, | |
186 | unsigned int max_payload_bytes) | |
187 | { | |
188 | int err; | |
189 | ||
190 | if (WARN_ON(c->connected)) | |
191 | return -EISCONN; | |
192 | ||
193 | c->speed = min(c->max_speed, | |
194 | fw_parent_device(c->resources.unit)->max_speed); | |
195 | ||
196 | mutex_lock(&c->mutex); | |
197 | ||
198 | retry_after_bus_reset: | |
199 | err = fw_iso_resources_allocate(&c->resources, | |
200 | max_payload_bytes, c->speed); | |
201 | if (err < 0) | |
202 | goto err_mutex; | |
203 | ||
204 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | |
205 | ABORT_ON_BUS_RESET); | |
206 | if (err == -EAGAIN) { | |
207 | fw_iso_resources_free(&c->resources); | |
208 | goto retry_after_bus_reset; | |
209 | } | |
210 | if (err < 0) | |
211 | goto err_resources; | |
212 | ||
213 | c->connected = true; | |
214 | ||
215 | mutex_unlock(&c->mutex); | |
216 | ||
217 | return 0; | |
218 | ||
219 | err_resources: | |
220 | fw_iso_resources_free(&c->resources); | |
221 | err_mutex: | |
222 | mutex_unlock(&c->mutex); | |
223 | ||
224 | return err; | |
225 | } | |
226 | EXPORT_SYMBOL(cmp_connection_establish); | |
227 | ||
228 | /** | |
229 | * cmp_connection_update - update the connection after a bus reset | |
230 | * @c: the connection manager | |
231 | * | |
232 | * This function must be called from the driver's .update handler to reestablish | |
233 | * any connection that might have been active. | |
234 | * | |
235 | * Returns zero on success, or a negative error code. On an error, the | |
236 | * connection is broken and the caller must stop transmitting iso packets. | |
237 | */ | |
238 | int cmp_connection_update(struct cmp_connection *c) | |
239 | { | |
240 | int err; | |
241 | ||
242 | mutex_lock(&c->mutex); | |
243 | ||
244 | if (!c->connected) { | |
245 | mutex_unlock(&c->mutex); | |
246 | return 0; | |
247 | } | |
248 | ||
249 | err = fw_iso_resources_update(&c->resources); | |
250 | if (err < 0) | |
251 | goto err_unconnect; | |
252 | ||
253 | err = pcr_modify(c, ipcr_set_modify, ipcr_set_check, | |
254 | SUCCEED_ON_BUS_RESET); | |
255 | if (err < 0) | |
256 | goto err_resources; | |
257 | ||
258 | mutex_unlock(&c->mutex); | |
259 | ||
260 | return 0; | |
261 | ||
262 | err_resources: | |
263 | fw_iso_resources_free(&c->resources); | |
264 | err_unconnect: | |
265 | c->connected = false; | |
266 | mutex_unlock(&c->mutex); | |
267 | ||
268 | return err; | |
269 | } | |
270 | EXPORT_SYMBOL(cmp_connection_update); | |
271 | ||
272 | ||
273 | static __be32 ipcr_break_modify(struct cmp_connection *c, __be32 ipcr) | |
274 | { | |
275 | return ipcr & ~cpu_to_be32(IPCR_BCAST_CONN | IPCR_P2P_CONN_MASK); | |
276 | } | |
277 | ||
278 | /** | |
279 | * cmp_connection_break - break the connection to the target | |
280 | * @c: the connection manager | |
281 | * | |
282 | * This function deactives the connection in the target's input plug control | |
283 | * register, and frees the isochronous resources of the connection. Before | |
284 | * calling this function, the caller should cease transmitting packets. | |
285 | */ | |
286 | void cmp_connection_break(struct cmp_connection *c) | |
287 | { | |
288 | int err; | |
289 | ||
290 | mutex_lock(&c->mutex); | |
291 | ||
292 | if (!c->connected) { | |
293 | mutex_unlock(&c->mutex); | |
294 | return; | |
295 | } | |
296 | ||
297 | err = pcr_modify(c, ipcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); | |
298 | if (err < 0) | |
299 | cmp_error(c, "plug is still connected\n"); | |
300 | ||
301 | fw_iso_resources_free(&c->resources); | |
302 | ||
303 | c->connected = false; | |
304 | ||
305 | mutex_unlock(&c->mutex); | |
306 | } | |
307 | EXPORT_SYMBOL(cmp_connection_break); |