diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /net/irda/irlmp_frame.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'net/irda/irlmp_frame.c')
-rw-r--r-- | net/irda/irlmp_frame.c | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/net/irda/irlmp_frame.c b/net/irda/irlmp_frame.c new file mode 100644 index 000000000000..91cd268172fa --- /dev/null +++ b/net/irda/irlmp_frame.c | |||
@@ -0,0 +1,491 @@ | |||
1 | /********************************************************************* | ||
2 | * | ||
3 | * Filename: irlmp_frame.c | ||
4 | * Version: 0.9 | ||
5 | * Description: IrLMP frame implementation | ||
6 | * Status: Experimental. | ||
7 | * Author: Dag Brattli <dagb@cs.uit.no> | ||
8 | * Created at: Tue Aug 19 02:09:59 1997 | ||
9 | * Modified at: Mon Dec 13 13:41:12 1999 | ||
10 | * Modified by: Dag Brattli <dagb@cs.uit.no> | ||
11 | * | ||
12 | * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no> | ||
13 | * All Rights Reserved. | ||
14 | * Copyright (c) 2000-2003 Jean Tourrilhes <jt@hpl.hp.com> | ||
15 | * | ||
16 | * This program is free software; you can redistribute it and/or | ||
17 | * modify it under the terms of the GNU General Public License as | ||
18 | * published by the Free Software Foundation; either version 2 of | ||
19 | * the License, or (at your option) any later version. | ||
20 | * | ||
21 | * Neither Dag Brattli nor University of Tromsų admit liability nor | ||
22 | * provide warranty for any of this software. This material is | ||
23 | * provided "AS-IS" and at no charge. | ||
24 | * | ||
25 | ********************************************************************/ | ||
26 | |||
27 | #include <linux/config.h> | ||
28 | #include <linux/skbuff.h> | ||
29 | #include <linux/kernel.h> | ||
30 | |||
31 | #include <net/irda/irda.h> | ||
32 | #include <net/irda/irlap.h> | ||
33 | #include <net/irda/timer.h> | ||
34 | #include <net/irda/irlmp.h> | ||
35 | #include <net/irda/irlmp_frame.h> | ||
36 | #include <net/irda/discovery.h> | ||
37 | |||
38 | static struct lsap_cb *irlmp_find_lsap(struct lap_cb *self, __u8 dlsap, | ||
39 | __u8 slsap, int status, hashbin_t *); | ||
40 | |||
41 | inline void irlmp_send_data_pdu(struct lap_cb *self, __u8 dlsap, __u8 slsap, | ||
42 | int expedited, struct sk_buff *skb) | ||
43 | { | ||
44 | skb->data[0] = dlsap; | ||
45 | skb->data[1] = slsap; | ||
46 | |||
47 | if (expedited) { | ||
48 | IRDA_DEBUG(4, "%s(), sending expedited data\n", __FUNCTION__); | ||
49 | irlap_data_request(self->irlap, skb, TRUE); | ||
50 | } else | ||
51 | irlap_data_request(self->irlap, skb, FALSE); | ||
52 | } | ||
53 | |||
54 | /* | ||
55 | * Function irlmp_send_lcf_pdu (dlsap, slsap, opcode,skb) | ||
56 | * | ||
57 | * Send Link Control Frame to IrLAP | ||
58 | */ | ||
59 | void irlmp_send_lcf_pdu(struct lap_cb *self, __u8 dlsap, __u8 slsap, | ||
60 | __u8 opcode, struct sk_buff *skb) | ||
61 | { | ||
62 | __u8 *frame; | ||
63 | |||
64 | IRDA_DEBUG(2, "%s()\n", __FUNCTION__); | ||
65 | |||
66 | IRDA_ASSERT(self != NULL, return;); | ||
67 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
68 | IRDA_ASSERT(skb != NULL, return;); | ||
69 | |||
70 | frame = skb->data; | ||
71 | |||
72 | frame[0] = dlsap | CONTROL_BIT; | ||
73 | frame[1] = slsap; | ||
74 | |||
75 | frame[2] = opcode; | ||
76 | |||
77 | if (opcode == DISCONNECT) | ||
78 | frame[3] = 0x01; /* Service user request */ | ||
79 | else | ||
80 | frame[3] = 0x00; /* rsvd */ | ||
81 | |||
82 | irlap_data_request(self->irlap, skb, FALSE); | ||
83 | } | ||
84 | |||
85 | /* | ||
86 | * Function irlmp_input (skb) | ||
87 | * | ||
88 | * Used by IrLAP to pass received data frames to IrLMP layer | ||
89 | * | ||
90 | */ | ||
91 | void irlmp_link_data_indication(struct lap_cb *self, struct sk_buff *skb, | ||
92 | int unreliable) | ||
93 | { | ||
94 | struct lsap_cb *lsap; | ||
95 | __u8 slsap_sel; /* Source (this) LSAP address */ | ||
96 | __u8 dlsap_sel; /* Destination LSAP address */ | ||
97 | __u8 *fp; | ||
98 | |||
99 | IRDA_DEBUG(4, "%s()\n", __FUNCTION__); | ||
100 | |||
101 | IRDA_ASSERT(self != NULL, return;); | ||
102 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
103 | IRDA_ASSERT(skb->len > 2, return;); | ||
104 | |||
105 | fp = skb->data; | ||
106 | |||
107 | /* | ||
108 | * The next statements may be confusing, but we do this so that | ||
109 | * destination LSAP of received frame is source LSAP in our view | ||
110 | */ | ||
111 | slsap_sel = fp[0] & LSAP_MASK; | ||
112 | dlsap_sel = fp[1]; | ||
113 | |||
114 | /* | ||
115 | * Check if this is an incoming connection, since we must deal with | ||
116 | * it in a different way than other established connections. | ||
117 | */ | ||
118 | if ((fp[0] & CONTROL_BIT) && (fp[2] == CONNECT_CMD)) { | ||
119 | IRDA_DEBUG(3, "%s(), incoming connection, " | ||
120 | "source LSAP=%d, dest LSAP=%d\n", | ||
121 | __FUNCTION__, slsap_sel, dlsap_sel); | ||
122 | |||
123 | /* Try to find LSAP among the unconnected LSAPs */ | ||
124 | lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, CONNECT_CMD, | ||
125 | irlmp->unconnected_lsaps); | ||
126 | |||
127 | /* Maybe LSAP was already connected, so try one more time */ | ||
128 | if (!lsap) { | ||
129 | IRDA_DEBUG(1, "%s(), incoming connection for LSAP already connected\n", __FUNCTION__); | ||
130 | lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, 0, | ||
131 | self->lsaps); | ||
132 | } | ||
133 | } else | ||
134 | lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, 0, | ||
135 | self->lsaps); | ||
136 | |||
137 | if (lsap == NULL) { | ||
138 | IRDA_DEBUG(2, "IrLMP, Sorry, no LSAP for received frame!\n"); | ||
139 | IRDA_DEBUG(2, "%s(), slsap_sel = %02x, dlsap_sel = %02x\n", | ||
140 | __FUNCTION__, slsap_sel, dlsap_sel); | ||
141 | if (fp[0] & CONTROL_BIT) { | ||
142 | IRDA_DEBUG(2, "%s(), received control frame %02x\n", | ||
143 | __FUNCTION__, fp[2]); | ||
144 | } else { | ||
145 | IRDA_DEBUG(2, "%s(), received data frame\n", __FUNCTION__); | ||
146 | } | ||
147 | return; | ||
148 | } | ||
149 | |||
150 | /* | ||
151 | * Check if we received a control frame? | ||
152 | */ | ||
153 | if (fp[0] & CONTROL_BIT) { | ||
154 | switch (fp[2]) { | ||
155 | case CONNECT_CMD: | ||
156 | lsap->lap = self; | ||
157 | irlmp_do_lsap_event(lsap, LM_CONNECT_INDICATION, skb); | ||
158 | break; | ||
159 | case CONNECT_CNF: | ||
160 | irlmp_do_lsap_event(lsap, LM_CONNECT_CONFIRM, skb); | ||
161 | break; | ||
162 | case DISCONNECT: | ||
163 | IRDA_DEBUG(4, "%s(), Disconnect indication!\n", | ||
164 | __FUNCTION__); | ||
165 | irlmp_do_lsap_event(lsap, LM_DISCONNECT_INDICATION, | ||
166 | skb); | ||
167 | break; | ||
168 | case ACCESSMODE_CMD: | ||
169 | IRDA_DEBUG(0, "Access mode cmd not implemented!\n"); | ||
170 | break; | ||
171 | case ACCESSMODE_CNF: | ||
172 | IRDA_DEBUG(0, "Access mode cnf not implemented!\n"); | ||
173 | break; | ||
174 | default: | ||
175 | IRDA_DEBUG(0, "%s(), Unknown control frame %02x\n", | ||
176 | __FUNCTION__, fp[2]); | ||
177 | break; | ||
178 | } | ||
179 | } else if (unreliable) { | ||
180 | /* Optimize and bypass the state machine if possible */ | ||
181 | if (lsap->lsap_state == LSAP_DATA_TRANSFER_READY) | ||
182 | irlmp_udata_indication(lsap, skb); | ||
183 | else | ||
184 | irlmp_do_lsap_event(lsap, LM_UDATA_INDICATION, skb); | ||
185 | } else { | ||
186 | /* Optimize and bypass the state machine if possible */ | ||
187 | if (lsap->lsap_state == LSAP_DATA_TRANSFER_READY) | ||
188 | irlmp_data_indication(lsap, skb); | ||
189 | else | ||
190 | irlmp_do_lsap_event(lsap, LM_DATA_INDICATION, skb); | ||
191 | } | ||
192 | } | ||
193 | |||
194 | /* | ||
195 | * Function irlmp_link_unitdata_indication (self, skb) | ||
196 | * | ||
197 | * | ||
198 | * | ||
199 | */ | ||
200 | #ifdef CONFIG_IRDA_ULTRA | ||
201 | void irlmp_link_unitdata_indication(struct lap_cb *self, struct sk_buff *skb) | ||
202 | { | ||
203 | struct lsap_cb *lsap; | ||
204 | __u8 slsap_sel; /* Source (this) LSAP address */ | ||
205 | __u8 dlsap_sel; /* Destination LSAP address */ | ||
206 | __u8 pid; /* Protocol identifier */ | ||
207 | __u8 *fp; | ||
208 | unsigned long flags; | ||
209 | |||
210 | IRDA_DEBUG(4, "%s()\n", __FUNCTION__); | ||
211 | |||
212 | IRDA_ASSERT(self != NULL, return;); | ||
213 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
214 | IRDA_ASSERT(skb->len > 2, return;); | ||
215 | |||
216 | fp = skb->data; | ||
217 | |||
218 | /* | ||
219 | * The next statements may be confusing, but we do this so that | ||
220 | * destination LSAP of received frame is source LSAP in our view | ||
221 | */ | ||
222 | slsap_sel = fp[0] & LSAP_MASK; | ||
223 | dlsap_sel = fp[1]; | ||
224 | pid = fp[2]; | ||
225 | |||
226 | if (pid & 0x80) { | ||
227 | IRDA_DEBUG(0, "%s(), extension in PID not supp!\n", | ||
228 | __FUNCTION__); | ||
229 | return; | ||
230 | } | ||
231 | |||
232 | /* Check if frame is addressed to the connectionless LSAP */ | ||
233 | if ((slsap_sel != LSAP_CONNLESS) || (dlsap_sel != LSAP_CONNLESS)) { | ||
234 | IRDA_DEBUG(0, "%s(), dropping frame!\n", __FUNCTION__); | ||
235 | return; | ||
236 | } | ||
237 | |||
238 | /* Search the connectionless LSAP */ | ||
239 | spin_lock_irqsave(&irlmp->unconnected_lsaps->hb_spinlock, flags); | ||
240 | lsap = (struct lsap_cb *) hashbin_get_first(irlmp->unconnected_lsaps); | ||
241 | while (lsap != NULL) { | ||
242 | /* | ||
243 | * Check if source LSAP and dest LSAP selectors and PID match. | ||
244 | */ | ||
245 | if ((lsap->slsap_sel == slsap_sel) && | ||
246 | (lsap->dlsap_sel == dlsap_sel) && | ||
247 | (lsap->pid == pid)) | ||
248 | { | ||
249 | break; | ||
250 | } | ||
251 | lsap = (struct lsap_cb *) hashbin_get_next(irlmp->unconnected_lsaps); | ||
252 | } | ||
253 | spin_unlock_irqrestore(&irlmp->unconnected_lsaps->hb_spinlock, flags); | ||
254 | |||
255 | if (lsap) | ||
256 | irlmp_connless_data_indication(lsap, skb); | ||
257 | else { | ||
258 | IRDA_DEBUG(0, "%s(), found no matching LSAP!\n", __FUNCTION__); | ||
259 | } | ||
260 | } | ||
261 | #endif /* CONFIG_IRDA_ULTRA */ | ||
262 | |||
263 | /* | ||
264 | * Function irlmp_link_disconnect_indication (reason, userdata) | ||
265 | * | ||
266 | * IrLAP has disconnected | ||
267 | * | ||
268 | */ | ||
269 | void irlmp_link_disconnect_indication(struct lap_cb *lap, | ||
270 | struct irlap_cb *irlap, | ||
271 | LAP_REASON reason, | ||
272 | struct sk_buff *skb) | ||
273 | { | ||
274 | IRDA_DEBUG(2, "%s()\n", __FUNCTION__); | ||
275 | |||
276 | IRDA_ASSERT(lap != NULL, return;); | ||
277 | IRDA_ASSERT(lap->magic == LMP_LAP_MAGIC, return;); | ||
278 | |||
279 | lap->reason = reason; | ||
280 | lap->daddr = DEV_ADDR_ANY; | ||
281 | |||
282 | /* FIXME: must do something with the skb if any */ | ||
283 | |||
284 | /* | ||
285 | * Inform station state machine | ||
286 | */ | ||
287 | irlmp_do_lap_event(lap, LM_LAP_DISCONNECT_INDICATION, NULL); | ||
288 | } | ||
289 | |||
290 | /* | ||
291 | * Function irlmp_link_connect_indication (qos) | ||
292 | * | ||
293 | * Incoming LAP connection! | ||
294 | * | ||
295 | */ | ||
296 | void irlmp_link_connect_indication(struct lap_cb *self, __u32 saddr, | ||
297 | __u32 daddr, struct qos_info *qos, | ||
298 | struct sk_buff *skb) | ||
299 | { | ||
300 | IRDA_DEBUG(4, "%s()\n", __FUNCTION__); | ||
301 | |||
302 | /* Copy QoS settings for this session */ | ||
303 | self->qos = qos; | ||
304 | |||
305 | /* Update destination device address */ | ||
306 | self->daddr = daddr; | ||
307 | IRDA_ASSERT(self->saddr == saddr, return;); | ||
308 | |||
309 | irlmp_do_lap_event(self, LM_LAP_CONNECT_INDICATION, skb); | ||
310 | } | ||
311 | |||
312 | /* | ||
313 | * Function irlmp_link_connect_confirm (qos) | ||
314 | * | ||
315 | * LAP connection confirmed! | ||
316 | * | ||
317 | */ | ||
318 | void irlmp_link_connect_confirm(struct lap_cb *self, struct qos_info *qos, | ||
319 | struct sk_buff *skb) | ||
320 | { | ||
321 | IRDA_DEBUG(4, "%s()\n", __FUNCTION__); | ||
322 | |||
323 | IRDA_ASSERT(self != NULL, return;); | ||
324 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
325 | IRDA_ASSERT(qos != NULL, return;); | ||
326 | |||
327 | /* Don't need use the skb for now */ | ||
328 | |||
329 | /* Copy QoS settings for this session */ | ||
330 | self->qos = qos; | ||
331 | |||
332 | irlmp_do_lap_event(self, LM_LAP_CONNECT_CONFIRM, NULL); | ||
333 | } | ||
334 | |||
335 | /* | ||
336 | * Function irlmp_link_discovery_indication (self, log) | ||
337 | * | ||
338 | * Device is discovering us | ||
339 | * | ||
340 | * It's not an answer to our own discoveries, just another device trying | ||
341 | * to perform discovery, but we don't want to miss the opportunity | ||
342 | * to exploit this information, because : | ||
343 | * o We may not actively perform discovery (just passive discovery) | ||
344 | * o This type of discovery is much more reliable. In some cases, it | ||
345 | * seem that less than 50% of our discoveries get an answer, while | ||
346 | * we always get ~100% of these. | ||
347 | * o Make faster discovery, statistically divide time of discovery | ||
348 | * events by 2 (important for the latency aspect and user feel) | ||
349 | * o Even is we do active discovery, the other node might not | ||
350 | * answer our discoveries (ex: Palm). The Palm will just perform | ||
351 | * one active discovery and connect directly to us. | ||
352 | * | ||
353 | * However, when both devices discover each other, they might attempt to | ||
354 | * connect to each other following the discovery event, and it would create | ||
355 | * collisions on the medium (SNRM battle). | ||
356 | * The "fix" for that is to disable all connection requests in IrLAP | ||
357 | * for 100ms after a discovery indication by setting the media_busy flag. | ||
358 | * Previously, we used to postpone the event which was quite ugly. Now | ||
359 | * that IrLAP takes care of this problem, just pass the event up... | ||
360 | * | ||
361 | * Jean II | ||
362 | */ | ||
363 | void irlmp_link_discovery_indication(struct lap_cb *self, | ||
364 | discovery_t *discovery) | ||
365 | { | ||
366 | IRDA_ASSERT(self != NULL, return;); | ||
367 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
368 | |||
369 | /* Add to main log, cleanup */ | ||
370 | irlmp_add_discovery(irlmp->cachelog, discovery); | ||
371 | |||
372 | /* Just handle it the same way as a discovery confirm, | ||
373 | * bypass the LM_LAP state machine (see below) */ | ||
374 | irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_PASSIVE); | ||
375 | } | ||
376 | |||
377 | /* | ||
378 | * Function irlmp_link_discovery_confirm (self, log) | ||
379 | * | ||
380 | * Called by IrLAP with a list of discoveries after the discovery | ||
381 | * request has been carried out. A NULL log is received if IrLAP | ||
382 | * was unable to carry out the discovery request | ||
383 | * | ||
384 | */ | ||
385 | void irlmp_link_discovery_confirm(struct lap_cb *self, hashbin_t *log) | ||
386 | { | ||
387 | IRDA_DEBUG(4, "%s()\n", __FUNCTION__); | ||
388 | |||
389 | IRDA_ASSERT(self != NULL, return;); | ||
390 | IRDA_ASSERT(self->magic == LMP_LAP_MAGIC, return;); | ||
391 | |||
392 | /* Add to main log, cleanup */ | ||
393 | irlmp_add_discovery_log(irlmp->cachelog, log); | ||
394 | |||
395 | /* Propagate event to various LSAPs registered for it. | ||
396 | * We bypass the LM_LAP state machine because | ||
397 | * 1) We do it regardless of the LM_LAP state | ||
398 | * 2) It doesn't affect the LM_LAP state | ||
399 | * 3) Faster, slimer, simpler, ... | ||
400 | * Jean II */ | ||
401 | irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_ACTIVE); | ||
402 | } | ||
403 | |||
404 | #ifdef CONFIG_IRDA_CACHE_LAST_LSAP | ||
405 | static inline void irlmp_update_cache(struct lap_cb *lap, | ||
406 | struct lsap_cb *lsap) | ||
407 | { | ||
408 | /* Prevent concurrent read to get garbage */ | ||
409 | lap->cache.valid = FALSE; | ||
410 | /* Update cache entry */ | ||
411 | lap->cache.dlsap_sel = lsap->dlsap_sel; | ||
412 | lap->cache.slsap_sel = lsap->slsap_sel; | ||
413 | lap->cache.lsap = lsap; | ||
414 | lap->cache.valid = TRUE; | ||
415 | } | ||
416 | #endif | ||
417 | |||
418 | /* | ||
419 | * Function irlmp_find_handle (self, dlsap_sel, slsap_sel, status, queue) | ||
420 | * | ||
421 | * Find handle associated with destination and source LSAP | ||
422 | * | ||
423 | * Any IrDA connection (LSAP/TSAP) is uniquely identified by | ||
424 | * 3 parameters, the local lsap, the remote lsap and the remote address. | ||
425 | * We may initiate multiple connections to the same remote service | ||
426 | * (they will have different local lsap), a remote device may initiate | ||
427 | * multiple connections to the same local service (they will have | ||
428 | * different remote lsap), or multiple devices may connect to the same | ||
429 | * service and may use the same remote lsap (and they will have | ||
430 | * different remote address). | ||
431 | * So, where is the remote address ? Each LAP connection is made with | ||
432 | * a single remote device, so imply a specific remote address. | ||
433 | * Jean II | ||
434 | */ | ||
435 | static struct lsap_cb *irlmp_find_lsap(struct lap_cb *self, __u8 dlsap_sel, | ||
436 | __u8 slsap_sel, int status, | ||
437 | hashbin_t *queue) | ||
438 | { | ||
439 | struct lsap_cb *lsap; | ||
440 | unsigned long flags; | ||
441 | |||
442 | /* | ||
443 | * Optimize for the common case. We assume that the last frame | ||
444 | * received is in the same connection as the last one, so check in | ||
445 | * cache first to avoid the linear search | ||
446 | */ | ||
447 | #ifdef CONFIG_IRDA_CACHE_LAST_LSAP | ||
448 | if ((self->cache.valid) && | ||
449 | (self->cache.slsap_sel == slsap_sel) && | ||
450 | (self->cache.dlsap_sel == dlsap_sel)) | ||
451 | { | ||
452 | return (self->cache.lsap); | ||
453 | } | ||
454 | #endif | ||
455 | |||
456 | spin_lock_irqsave(&queue->hb_spinlock, flags); | ||
457 | |||
458 | lsap = (struct lsap_cb *) hashbin_get_first(queue); | ||
459 | while (lsap != NULL) { | ||
460 | /* | ||
461 | * If this is an incoming connection, then the destination | ||
462 | * LSAP selector may have been specified as LM_ANY so that | ||
463 | * any client can connect. In that case we only need to check | ||
464 | * if the source LSAP (in our view!) match! | ||
465 | */ | ||
466 | if ((status == CONNECT_CMD) && | ||
467 | (lsap->slsap_sel == slsap_sel) && | ||
468 | (lsap->dlsap_sel == LSAP_ANY)) { | ||
469 | /* This is where the dest lsap sel is set on incoming | ||
470 | * lsaps */ | ||
471 | lsap->dlsap_sel = dlsap_sel; | ||
472 | break; | ||
473 | } | ||
474 | /* | ||
475 | * Check if source LSAP and dest LSAP selectors match. | ||
476 | */ | ||
477 | if ((lsap->slsap_sel == slsap_sel) && | ||
478 | (lsap->dlsap_sel == dlsap_sel)) | ||
479 | break; | ||
480 | |||
481 | lsap = (struct lsap_cb *) hashbin_get_next(queue); | ||
482 | } | ||
483 | #ifdef CONFIG_IRDA_CACHE_LAST_LSAP | ||
484 | if(lsap) | ||
485 | irlmp_update_cache(self, lsap); | ||
486 | #endif | ||
487 | spin_unlock_irqrestore(&queue->hb_spinlock, flags); | ||
488 | |||
489 | /* Return what we've found or NULL */ | ||
490 | return lsap; | ||
491 | } | ||