Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
c781c06d KH |
2 | /* |
3 | * Incremental bus scan, based on bus topology | |
3038e353 KH |
4 | * |
5 | * Copyright (C) 2004-2006 Kristian Hoegsberg <krh@bitplanet.net> | |
3038e353 KH |
6 | */ |
7 | ||
e8ca9702 | 8 | #include <linux/bug.h> |
3038e353 | 9 | #include <linux/errno.h> |
77c9a5da SR |
10 | #include <linux/firewire.h> |
11 | #include <linux/firewire-constants.h> | |
e8ca9702 SR |
12 | #include <linux/jiffies.h> |
13 | #include <linux/kernel.h> | |
14 | #include <linux/list.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/slab.h> | |
17 | #include <linux/spinlock.h> | |
e8ca9702 | 18 | |
60063497 | 19 | #include <linux/atomic.h> |
cb7c96da | 20 | #include <asm/byteorder.h> |
e8ca9702 | 21 | |
77c9a5da | 22 | #include "core.h" |
24b7f8e5 | 23 | #include "phy-packet-definitions.h" |
6b0b708f | 24 | #include <trace/events/firewire.h> |
3038e353 | 25 | |
95688e97 | 26 | static struct fw_node *fw_node_create(u32 sid, int port_count, int color) |
3038e353 KH |
27 | { |
28 | struct fw_node *node; | |
29 | ||
3c70de9b | 30 | node = kzalloc(struct_size(node, ports, port_count), GFP_ATOMIC); |
3038e353 KH |
31 | if (node == NULL) |
32 | return NULL; | |
33 | ||
34 | node->color = color; | |
8f035147 TS |
35 | node->node_id = LOCAL_BUS | phy_packet_self_id_get_phy_id(sid); |
36 | node->link_on = phy_packet_self_id_zero_get_link_active(sid); | |
37 | // NOTE: Only two bits, thus only for SCODE_100, SCODE_200, SCODE_400, and SCODE_BETA. | |
38 | node->phy_speed = phy_packet_self_id_zero_get_scode(sid); | |
39 | node->initiated_reset = phy_packet_self_id_zero_get_initiated_reset(sid); | |
3038e353 KH |
40 | node->port_count = port_count; |
41 | ||
392910cf | 42 | refcount_set(&node->ref_count, 1); |
3038e353 KH |
43 | INIT_LIST_HEAD(&node->link); |
44 | ||
45 | return node; | |
46 | } | |
47 | ||
c781c06d KH |
48 | /* |
49 | * Compute the maximum hop count for this node and it's children. The | |
83db801c KH |
50 | * maximum hop count is the maximum number of connections between any |
51 | * two nodes in the subtree rooted at this node. We need this for | |
52 | * setting the gap count. As we build the tree bottom up in | |
53 | * build_tree() below, this is fairly easy to do: for each node we | |
54 | * maintain the max hop count and the max depth, ie the number of hops | |
55 | * to the furthest leaf. Computing the max hop count breaks down into | |
56 | * two cases: either the path goes through this node, in which case | |
57 | * the hop count is the sum of the two biggest child depths plus 2. | |
58 | * Or it could be the case that the max hop path is entirely | |
59 | * containted in a child tree, in which case the max hop count is just | |
60 | * the max hop count of this child. | |
61 | */ | |
62 | static void update_hop_count(struct fw_node *node) | |
63 | { | |
64 | int depths[2] = { -1, -1 }; | |
65 | int max_child_hops = 0; | |
66 | int i; | |
67 | ||
68 | for (i = 0; i < node->port_count; i++) { | |
dae1a3aa | 69 | if (node->ports[i] == NULL) |
83db801c KH |
70 | continue; |
71 | ||
dae1a3aa SR |
72 | if (node->ports[i]->max_hops > max_child_hops) |
73 | max_child_hops = node->ports[i]->max_hops; | |
83db801c | 74 | |
dae1a3aa | 75 | if (node->ports[i]->max_depth > depths[0]) { |
83db801c | 76 | depths[1] = depths[0]; |
dae1a3aa SR |
77 | depths[0] = node->ports[i]->max_depth; |
78 | } else if (node->ports[i]->max_depth > depths[1]) | |
79 | depths[1] = node->ports[i]->max_depth; | |
83db801c KH |
80 | } |
81 | ||
82 | node->max_depth = depths[0] + 1; | |
83 | node->max_hops = max(max_child_hops, depths[0] + depths[1] + 2); | |
84 | } | |
85 | ||
e5f84f82 SR |
86 | static inline struct fw_node *fw_node(struct list_head *l) |
87 | { | |
88 | return list_entry(l, struct fw_node, link); | |
89 | } | |
83db801c | 90 | |
656b7afd | 91 | /* |
3038e353 KH |
92 | * This function builds the tree representation of the topology given |
93 | * by the self IDs from the latest bus reset. During the construction | |
94 | * of the tree, the function checks that the self IDs are valid and | |
af901ca1 | 95 | * internally consistent. On success this function returns the |
3038e353 KH |
96 | * fw_node corresponding to the local card otherwise NULL. |
97 | */ | |
677ceae1 TS |
98 | static struct fw_node *build_tree(struct fw_card *card, const u32 *sid, int self_id_count, |
99 | unsigned int generation) | |
3038e353 | 100 | { |
24b7f8e5 TS |
101 | struct self_id_sequence_enumerator enumerator = { |
102 | .cursor = sid, | |
103 | .quadlet_count = self_id_count, | |
104 | }; | |
93e4fd45 | 105 | struct fw_node *node, *child, *local_node, *irm_node; |
10f5aee0 | 106 | struct list_head stack; |
10f5aee0 | 107 | int phy_id, stack_depth; |
24d40125 SR |
108 | int gap_count; |
109 | bool beta_repeaters_present; | |
3038e353 KH |
110 | |
111 | local_node = NULL; | |
112 | node = NULL; | |
113 | INIT_LIST_HEAD(&stack); | |
114 | stack_depth = 0; | |
3038e353 | 115 | phy_id = 0; |
93e4fd45 | 116 | irm_node = NULL; |
8f035147 | 117 | gap_count = phy_packet_self_id_zero_get_gap_count(*sid); |
24d40125 | 118 | beta_repeaters_present = false; |
3038e353 | 119 | |
24b7f8e5 TS |
120 | while (enumerator.quadlet_count > 0) { |
121 | unsigned int child_port_count = 0; | |
122 | unsigned int total_port_count = 0; | |
123 | unsigned int parent_count = 0; | |
124 | unsigned int quadlet_count; | |
125 | const u32 *self_id_sequence; | |
126 | unsigned int port_capacity; | |
127 | enum phy_packet_self_id_port_status port_status; | |
128 | unsigned int port_index; | |
10f5aee0 TS |
129 | struct list_head *h; |
130 | int i; | |
3038e353 | 131 | |
24b7f8e5 TS |
132 | self_id_sequence = self_id_sequence_enumerator_next(&enumerator, &quadlet_count); |
133 | if (IS_ERR(self_id_sequence)) { | |
134 | if (PTR_ERR(self_id_sequence) != -ENODATA) { | |
135 | fw_err(card, "inconsistent extended self IDs: %ld\n", | |
136 | PTR_ERR(self_id_sequence)); | |
137 | return NULL; | |
138 | } | |
139 | break; | |
140 | } | |
141 | ||
142 | port_capacity = self_id_sequence_get_port_capacity(quadlet_count); | |
2fd22faf | 143 | trace_self_id_sequence(card->index, self_id_sequence, quadlet_count, generation); |
677ceae1 | 144 | |
24b7f8e5 TS |
145 | for (port_index = 0; port_index < port_capacity; ++port_index) { |
146 | port_status = self_id_sequence_get_port_status(self_id_sequence, quadlet_count, | |
147 | port_index); | |
148 | switch (port_status) { | |
149 | case PHY_PACKET_SELF_ID_PORT_STATUS_CHILD: | |
150 | ++child_port_count; | |
151 | fallthrough; | |
152 | case PHY_PACKET_SELF_ID_PORT_STATUS_PARENT: | |
153 | case PHY_PACKET_SELF_ID_PORT_STATUS_NCONN: | |
154 | ++total_port_count; | |
155 | fallthrough; | |
156 | case PHY_PACKET_SELF_ID_PORT_STATUS_NONE: | |
157 | default: | |
158 | break; | |
159 | } | |
3038e353 KH |
160 | } |
161 | ||
8f035147 | 162 | if (phy_id != phy_packet_self_id_get_phy_id(self_id_sequence[0])) { |
26b4950d | 163 | fw_err(card, "PHY ID mismatch in self ID: %d != %d\n", |
8f035147 | 164 | phy_id, phy_packet_self_id_get_phy_id(self_id_sequence[0])); |
3038e353 KH |
165 | return NULL; |
166 | } | |
167 | ||
168 | if (child_port_count > stack_depth) { | |
26b4950d | 169 | fw_err(card, "topology stack underflow\n"); |
3038e353 KH |
170 | return NULL; |
171 | } | |
172 | ||
c781c06d KH |
173 | /* |
174 | * Seek back from the top of our stack to find the | |
175 | * start of the child nodes for this node. | |
176 | */ | |
3038e353 KH |
177 | for (i = 0, h = &stack; i < child_port_count; i++) |
178 | h = h->prev; | |
c1b91ce4 YD |
179 | /* |
180 | * When the stack is empty, this yields an invalid value, | |
181 | * but that pointer will never be dereferenced. | |
182 | */ | |
3038e353 KH |
183 | child = fw_node(h); |
184 | ||
24b7f8e5 | 185 | node = fw_node_create(self_id_sequence[0], total_port_count, card->color); |
3038e353 | 186 | if (node == NULL) { |
26b4950d | 187 | fw_err(card, "out of memory while building topology\n"); |
3038e353 KH |
188 | return NULL; |
189 | } | |
190 | ||
191 | if (phy_id == (card->node_id & 0x3f)) | |
192 | local_node = node; | |
193 | ||
8f035147 | 194 | if (phy_packet_self_id_zero_get_contender(self_id_sequence[0])) |
93e4fd45 | 195 | irm_node = node; |
3038e353 | 196 | |
24b7f8e5 TS |
197 | for (port_index = 0; port_index < total_port_count; ++port_index) { |
198 | port_status = self_id_sequence_get_port_status(self_id_sequence, quadlet_count, | |
199 | port_index); | |
200 | switch (port_status) { | |
201 | case PHY_PACKET_SELF_ID_PORT_STATUS_PARENT: | |
202 | // Who's your daddy? We dont know the parent node at this time, so | |
203 | // we temporarily abuse node->color for remembering the entry in | |
204 | // the node->ports array where the parent node should be. Later, | |
205 | // when we handle the parent node, we fix up the reference. | |
206 | ++parent_count; | |
3038e353 KH |
207 | node->color = i; |
208 | break; | |
209 | ||
24b7f8e5 TS |
210 | case PHY_PACKET_SELF_ID_PORT_STATUS_CHILD: |
211 | node->ports[port_index] = child; | |
212 | // Fix up parent reference for this child node. | |
dae1a3aa | 213 | child->ports[child->color] = node; |
3038e353 KH |
214 | child->color = card->color; |
215 | child = fw_node(child->link.next); | |
216 | break; | |
24b7f8e5 TS |
217 | case PHY_PACKET_SELF_ID_PORT_STATUS_NCONN: |
218 | case PHY_PACKET_SELF_ID_PORT_STATUS_NONE: | |
219 | default: | |
220 | break; | |
3038e353 KH |
221 | } |
222 | } | |
223 | ||
24b7f8e5 TS |
224 | // Check that the node reports exactly one parent port, except for the root, which |
225 | // of course should have no parents. | |
226 | if ((enumerator.quadlet_count == 0 && parent_count != 0) || | |
227 | (enumerator.quadlet_count > 0 && parent_count != 1)) { | |
26b4950d SR |
228 | fw_err(card, "parent port inconsistency for node %d: " |
229 | "parent_count=%d\n", phy_id, parent_count); | |
3038e353 KH |
230 | return NULL; |
231 | } | |
232 | ||
233 | /* Pop the child nodes off the stack and push the new node. */ | |
234 | __list_del(h->prev, &stack); | |
235 | list_add_tail(&node->link, &stack); | |
236 | stack_depth += 1 - child_port_count; | |
237 | ||
24b7f8e5 | 238 | if (node->phy_speed == SCODE_BETA && parent_count + child_port_count > 1) |
24d40125 SR |
239 | beta_repeaters_present = true; |
240 | ||
24b7f8e5 TS |
241 | // If PHYs report different gap counts, set an invalid count which will force a gap |
242 | // count reconfiguration and a reset. | |
8f035147 | 243 | if (phy_packet_self_id_zero_get_gap_count(self_id_sequence[0]) != gap_count) |
25b1c3d8 | 244 | gap_count = 0; |
83db801c KH |
245 | |
246 | update_hop_count(node); | |
247 | ||
3038e353 KH |
248 | phy_id++; |
249 | } | |
250 | ||
251 | card->root_node = node; | |
93e4fd45 | 252 | card->irm_node = irm_node; |
83db801c | 253 | card->gap_count = gap_count; |
24d40125 | 254 | card->beta_repeaters_present = beta_repeaters_present; |
3038e353 KH |
255 | |
256 | return local_node; | |
257 | } | |
258 | ||
a98e2719 KH |
259 | typedef void (*fw_node_callback_t)(struct fw_card * card, |
260 | struct fw_node * node, | |
261 | struct fw_node * parent); | |
3038e353 | 262 | |
53dca511 SR |
263 | static void for_each_fw_node(struct fw_card *card, struct fw_node *root, |
264 | fw_node_callback_t callback) | |
3038e353 KH |
265 | { |
266 | struct list_head list; | |
267 | struct fw_node *node, *next, *child, *parent; | |
268 | int i; | |
269 | ||
270 | INIT_LIST_HEAD(&list); | |
271 | ||
272 | fw_node_get(root); | |
273 | list_add_tail(&root->link, &list); | |
274 | parent = NULL; | |
275 | list_for_each_entry(node, &list, link) { | |
276 | node->color = card->color; | |
277 | ||
278 | for (i = 0; i < node->port_count; i++) { | |
dae1a3aa | 279 | child = node->ports[i]; |
3038e353 KH |
280 | if (!child) |
281 | continue; | |
282 | if (child->color == card->color) | |
283 | parent = child; | |
284 | else { | |
285 | fw_node_get(child); | |
286 | list_add_tail(&child->link, &list); | |
287 | } | |
288 | } | |
289 | ||
290 | callback(card, node, parent); | |
291 | } | |
292 | ||
293 | list_for_each_entry_safe(node, next, &list, link) | |
294 | fw_node_put(node); | |
295 | } | |
296 | ||
53dca511 SR |
297 | static void report_lost_node(struct fw_card *card, |
298 | struct fw_node *node, struct fw_node *parent) | |
3038e353 KH |
299 | { |
300 | fw_node_event(card, node, FW_NODE_DESTROYED); | |
301 | fw_node_put(node); | |
d6f95a3d SR |
302 | |
303 | /* Topology has changed - reset bus manager retry counter */ | |
304 | card->bm_retries = 0; | |
3038e353 KH |
305 | } |
306 | ||
53dca511 SR |
307 | static void report_found_node(struct fw_card *card, |
308 | struct fw_node *node, struct fw_node *parent) | |
3038e353 KH |
309 | { |
310 | int b_path = (node->phy_speed == SCODE_BETA); | |
311 | ||
312 | if (parent != NULL) { | |
748086eb SR |
313 | /* min() macro doesn't work here with gcc 3.4 */ |
314 | node->max_speed = parent->max_speed < node->phy_speed ? | |
315 | parent->max_speed : node->phy_speed; | |
3038e353 KH |
316 | node->b_path = parent->b_path && b_path; |
317 | } else { | |
318 | node->max_speed = node->phy_speed; | |
319 | node->b_path = b_path; | |
320 | } | |
321 | ||
322 | fw_node_event(card, node, FW_NODE_CREATED); | |
d6f95a3d SR |
323 | |
324 | /* Topology has changed - reset bus manager retry counter */ | |
325 | card->bm_retries = 0; | |
3038e353 KH |
326 | } |
327 | ||
a7ecbe92 | 328 | /* Must be called with card->lock held */ |
3038e353 KH |
329 | void fw_destroy_nodes(struct fw_card *card) |
330 | { | |
3038e353 KH |
331 | card->color++; |
332 | if (card->local_node != NULL) | |
333 | for_each_fw_node(card, card->local_node, report_lost_node); | |
15803478 | 334 | card->local_node = NULL; |
3038e353 KH |
335 | } |
336 | ||
337 | static void move_tree(struct fw_node *node0, struct fw_node *node1, int port) | |
338 | { | |
339 | struct fw_node *tree; | |
340 | int i; | |
341 | ||
dae1a3aa SR |
342 | tree = node1->ports[port]; |
343 | node0->ports[port] = tree; | |
3038e353 | 344 | for (i = 0; i < tree->port_count; i++) { |
dae1a3aa SR |
345 | if (tree->ports[i] == node1) { |
346 | tree->ports[i] = node0; | |
3038e353 KH |
347 | break; |
348 | } | |
349 | } | |
350 | } | |
351 | ||
656b7afd SR |
352 | /* |
353 | * Compare the old topology tree for card with the new one specified by root. | |
354 | * Queue the nodes and mark them as either found, lost or updated. | |
355 | * Update the nodes in the card topology tree as we go. | |
3038e353 | 356 | */ |
53dca511 | 357 | static void update_tree(struct fw_card *card, struct fw_node *root) |
3038e353 KH |
358 | { |
359 | struct list_head list0, list1; | |
77e55719 | 360 | struct fw_node *node0, *node1, *next1; |
3038e353 KH |
361 | int i, event; |
362 | ||
363 | INIT_LIST_HEAD(&list0); | |
364 | list_add_tail(&card->local_node->link, &list0); | |
365 | INIT_LIST_HEAD(&list1); | |
366 | list_add_tail(&root->link, &list1); | |
367 | ||
368 | node0 = fw_node(list0.next); | |
369 | node1 = fw_node(list1.next); | |
3038e353 KH |
370 | |
371 | while (&node0->link != &list0) { | |
a2cdebe3 | 372 | WARN_ON(node0->port_count != node1->port_count); |
3038e353 | 373 | |
3038e353 KH |
374 | if (node0->link_on && !node1->link_on) |
375 | event = FW_NODE_LINK_OFF; | |
376 | else if (!node0->link_on && node1->link_on) | |
377 | event = FW_NODE_LINK_ON; | |
c9755e14 SR |
378 | else if (node1->initiated_reset && node1->link_on) |
379 | event = FW_NODE_INITIATED_RESET; | |
3038e353 KH |
380 | else |
381 | event = FW_NODE_UPDATED; | |
382 | ||
383 | node0->node_id = node1->node_id; | |
384 | node0->color = card->color; | |
385 | node0->link_on = node1->link_on; | |
386 | node0->initiated_reset = node1->initiated_reset; | |
83db801c | 387 | node0->max_hops = node1->max_hops; |
3038e353 KH |
388 | node1->color = card->color; |
389 | fw_node_event(card, node0, event); | |
390 | ||
391 | if (card->root_node == node1) | |
392 | card->root_node = node0; | |
393 | if (card->irm_node == node1) | |
394 | card->irm_node = node0; | |
395 | ||
396 | for (i = 0; i < node0->port_count; i++) { | |
dae1a3aa | 397 | if (node0->ports[i] && node1->ports[i]) { |
c781c06d KH |
398 | /* |
399 | * This port didn't change, queue the | |
3038e353 | 400 | * connected node for further |
c781c06d KH |
401 | * investigation. |
402 | */ | |
dae1a3aa | 403 | if (node0->ports[i]->color == card->color) |
3038e353 | 404 | continue; |
dae1a3aa SR |
405 | list_add_tail(&node0->ports[i]->link, &list0); |
406 | list_add_tail(&node1->ports[i]->link, &list1); | |
407 | } else if (node0->ports[i]) { | |
c781c06d KH |
408 | /* |
409 | * The nodes connected here were | |
3038e353 KH |
410 | * unplugged; unref the lost nodes and |
411 | * queue FW_NODE_LOST callbacks for | |
c781c06d KH |
412 | * them. |
413 | */ | |
3038e353 | 414 | |
dae1a3aa | 415 | for_each_fw_node(card, node0->ports[i], |
3038e353 | 416 | report_lost_node); |
dae1a3aa SR |
417 | node0->ports[i] = NULL; |
418 | } else if (node1->ports[i]) { | |
c781c06d KH |
419 | /* |
420 | * One or more node were connected to | |
3038e353 KH |
421 | * this port. Move the new nodes into |
422 | * the tree and queue FW_NODE_CREATED | |
c781c06d KH |
423 | * callbacks for them. |
424 | */ | |
3038e353 | 425 | move_tree(node0, node1, i); |
dae1a3aa | 426 | for_each_fw_node(card, node0->ports[i], |
3038e353 | 427 | report_found_node); |
3038e353 KH |
428 | } |
429 | } | |
430 | ||
431 | node0 = fw_node(node0->link.next); | |
77e55719 JF |
432 | next1 = fw_node(node1->link.next); |
433 | fw_node_put(node1); | |
434 | node1 = next1; | |
3038e353 KH |
435 | } |
436 | } | |
437 | ||
53dca511 SR |
438 | static void update_topology_map(struct fw_card *card, |
439 | u32 *self_ids, int self_id_count) | |
473d28c7 | 440 | { |
cb7c96da SR |
441 | int node_count = (card->root_node->node_id & 0x3f) + 1; |
442 | __be32 *map = card->topology_map; | |
443 | ||
444 | *map++ = cpu_to_be32((self_id_count + 2) << 16); | |
445 | *map++ = cpu_to_be32(be32_to_cpu(card->topology_map[1]) + 1); | |
446 | *map++ = cpu_to_be32((node_count << 16) | self_id_count); | |
447 | ||
448 | while (self_id_count--) | |
449 | *map++ = cpu_to_be32p(self_ids++); | |
473d28c7 | 450 | |
e175569c | 451 | fw_compute_block_crc(card->topology_map); |
473d28c7 KH |
452 | } |
453 | ||
53dca511 | 454 | void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, |
c8a94ded | 455 | int self_id_count, u32 *self_ids, bool bm_abdicate) |
3038e353 KH |
456 | { |
457 | struct fw_node *local_node; | |
458 | unsigned long flags; | |
3038e353 | 459 | |
893098b2 | 460 | trace_bus_reset_handle(card->index, generation, node_id, bm_abdicate, self_ids, self_id_count); |
6b0b708f | 461 | |
a7ecbe92 ND |
462 | spin_lock_irqsave(&card->lock, flags); |
463 | ||
a5c7f471 SR |
464 | /* |
465 | * If the selfID buffer is not the immediate successor of the | |
466 | * previously processed one, we cannot reliably compare the | |
467 | * old and new topologies. | |
468 | */ | |
8cd0bbbd | 469 | if (!is_next_generation(generation, card->generation) && |
a5c7f471 | 470 | card->local_node != NULL) { |
a5c7f471 SR |
471 | fw_destroy_nodes(card); |
472 | card->bm_retries = 0; | |
473 | } | |
474 | ||
db3c9cc1 | 475 | card->broadcast_channel_allocated = card->broadcast_channel_auto_allocated; |
3038e353 | 476 | card->node_id = node_id; |
b5d2a5e0 SR |
477 | /* |
478 | * Update node_id before generation to prevent anybody from using | |
479 | * a stale node_id together with a current generation. | |
480 | */ | |
481 | smp_wmb(); | |
3038e353 | 482 | card->generation = generation; |
e71084af | 483 | card->reset_jiffies = get_jiffies_64(); |
250b2b6d | 484 | card->bm_node_id = 0xffff; |
c8a94ded | 485 | card->bm_abdicate = bm_abdicate; |
0fa1986f | 486 | fw_schedule_bm_work(card, 0); |
3038e353 | 487 | |
677ceae1 | 488 | local_node = build_tree(card, self_ids, self_id_count, generation); |
473d28c7 KH |
489 | |
490 | update_topology_map(card, self_ids, self_id_count); | |
3038e353 KH |
491 | |
492 | card->color++; | |
493 | ||
494 | if (local_node == NULL) { | |
26b4950d | 495 | fw_err(card, "topology build failed\n"); |
3038e353 KH |
496 | /* FIXME: We need to issue a bus reset in this case. */ |
497 | } else if (card->local_node == NULL) { | |
498 | card->local_node = local_node; | |
499 | for_each_fw_node(card, local_node, report_found_node); | |
500 | } else { | |
83db801c | 501 | update_tree(card, local_node); |
3038e353 KH |
502 | } |
503 | ||
504 | spin_unlock_irqrestore(&card->lock, flags); | |
505 | } | |
3038e353 | 506 | EXPORT_SYMBOL(fw_core_handle_bus_reset); |