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