xref: /freebsd/sys/netgraph/bluetooth/l2cap/ng_l2cap_misc.c (revision 7660b554bc59a07be0431c17e0e33815818baa69)
1 /*
2  * ng_l2cap_misc.c
3  *
4  * Copyright (c) Maksim Yevmenkin <m_evmenkin@yahoo.com>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  *
28  * $Id: ng_l2cap_misc.c,v 1.4 2003/04/28 21:44:59 max Exp $
29  * $FreeBSD$
30  */
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/kernel.h>
35 #include <sys/malloc.h>
36 #include <sys/mbuf.h>
37 #include <sys/queue.h>
38 #include <netgraph/ng_message.h>
39 #include <netgraph/netgraph.h>
40 #include "ng_bluetooth.h"
41 #include "ng_hci.h"
42 #include "ng_l2cap.h"
43 #include "ng_l2cap_var.h"
44 #include "ng_l2cap_cmds.h"
45 #include "ng_l2cap_evnt.h"
46 #include "ng_l2cap_llpi.h"
47 #include "ng_l2cap_ulpi.h"
48 #include "ng_l2cap_misc.h"
49 
50 static u_int16_t	ng_l2cap_get_cid		(ng_l2cap_p);
51 static void		ng_l2cap_queue_discon_timeout	(void *);
52 static void		ng_l2cap_queue_lp_timeout	(void *);
53 static void		ng_l2cap_queue_command_timeout	(void *);
54 
55 /******************************************************************************
56  ******************************************************************************
57  **                              Utility routines
58  ******************************************************************************
59  ******************************************************************************/
60 
61 /*
62  * Send hook information to the upper layer
63  */
64 
65 void
66 ng_l2cap_send_hook_info(node_p node, hook_p hook, void *arg1, int arg2)
67 {
68 	ng_l2cap_p	 l2cap = NULL;
69 	struct ng_mesg	*msg = NULL;
70 	int		 error = 0;
71 
72 	if (node == NULL || NG_NODE_NOT_VALID(node) ||
73 	    hook == NULL || NG_HOOK_NOT_VALID(hook))
74 		return;
75 
76 	l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node);
77 	if (l2cap->hci == NULL || NG_HOOK_NOT_VALID(l2cap->hci) ||
78 	    bcmp(&l2cap->bdaddr, NG_HCI_BDADDR_ANY, sizeof(l2cap->bdaddr)) == 0)
79 		return;
80 
81 	NG_MKMESSAGE(msg, NGM_L2CAP_COOKIE, NGM_L2CAP_NODE_HOOK_INFO,
82 		sizeof(bdaddr_t), M_NOWAIT);
83 	if (msg != NULL) {
84 		bcopy(&l2cap->bdaddr, msg->data, sizeof(bdaddr_t));
85 		NG_SEND_MSG_HOOK(error, node, msg, hook, NULL);
86 	} else
87 		error = ENOMEM;
88 
89 	if (error != 0)
90 		NG_L2CAP_INFO(
91 "%s: %s - failed to send HOOK_INFO message to hook \"%s\", error=%d\n",
92 			__func__, NG_NODE_NAME(l2cap->node), NG_HOOK_NAME(hook),
93 			error);
94 } /* ng_l2cap_send_hook_info */
95 
96 /*
97  * Create new connection descriptor for the "remote" unit.
98  * Will link connection descriptor to the l2cap node.
99  */
100 
101 ng_l2cap_con_p
102 ng_l2cap_new_con(ng_l2cap_p l2cap, bdaddr_p bdaddr)
103 {
104 	ng_l2cap_con_p	con = NULL;
105 
106 	/* Create new connection descriptor */
107 	MALLOC(con, ng_l2cap_con_p, sizeof(*con), M_NETGRAPH_L2CAP,
108 		M_NOWAIT|M_ZERO);
109 	if (con == NULL)
110 		return (NULL);
111 
112 	con->l2cap = l2cap;
113 	con->state = NG_L2CAP_CON_CLOSED;
114 
115 	bcopy(bdaddr, &con->remote, sizeof(con->remote));
116 	callout_handle_init(&con->con_timo);
117 
118 	con->ident = NG_L2CAP_FIRST_IDENT - 1;
119 	TAILQ_INIT(&con->cmd_list);
120 
121 	/* Link connection */
122 	LIST_INSERT_HEAD(&l2cap->con_list, con, next);
123 
124 	return (con);
125 } /* ng_l2cap_new_con */
126 
127 /*
128  * Add reference to the connection descriptor
129  */
130 
131 void
132 ng_l2cap_con_ref(ng_l2cap_con_p con)
133 {
134 	con->refcnt ++;
135 
136 	if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO) {
137 		if ((con->state != NG_L2CAP_CON_OPEN) ||
138 		    (con->flags & NG_L2CAP_CON_OUTGOING) == 0)
139 			panic("%s: %s - bad auto disconnect timeout\n",
140 				__func__, NG_NODE_NAME(con->l2cap->node));
141 
142 		ng_l2cap_discon_untimeout(con);
143 	}
144 } /* ng_l2cap_con_ref */
145 
146 /*
147  * Remove reference from the connection descriptor
148  */
149 
150 void
151 ng_l2cap_con_unref(ng_l2cap_con_p con)
152 {
153 	con->refcnt --;
154 
155 	if (con->refcnt < 0)
156 		panic("%s: %s - con->refcnt < 0\n",
157 			__func__, NG_NODE_NAME(con->l2cap->node));
158 
159 	/*
160 	 * Set auto disconnect timer only if the following conditions are met:
161 	 * 1) we have no reference on the connection
162 	 * 2) connection is in OPEN state
163 	 * 3) it is an outgoing connection
164 	 * 4) disconnect timeout > 0
165 	 */
166 
167 	if ((con->refcnt == 0) &&
168 	    (con->state == NG_L2CAP_CON_OPEN) &&
169 	    (con->flags & NG_L2CAP_CON_OUTGOING) &&
170 	    (con->l2cap->discon_timo > 0)) {
171 		if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)
172 			panic("%s: %s - duplicated auto disconnect timeout\n",
173 				__func__, NG_NODE_NAME(con->l2cap->node));
174 
175 		ng_l2cap_discon_timeout(con);
176 	}
177 } /* ng_l2cap_con_unref */
178 
179 /*
180  * Set auto disconnect timeout
181  */
182 
183 void
184 ng_l2cap_discon_timeout(ng_l2cap_con_p con)
185 {
186 	if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO))
187 		panic("%s: %s - invalid timeout, state=%d, flags=%#x\n",
188 			__func__, NG_NODE_NAME(con->l2cap->node),
189 			con->state, con->flags);
190 
191 	NG_NODE_REF(con->l2cap->node);
192 	con->flags |= NG_L2CAP_CON_AUTO_DISCON_TIMO;
193 	con->con_timo = timeout(ng_l2cap_queue_discon_timeout, con,
194 					con->l2cap->discon_timo * hz);
195 } /* ng_l2cap_discon_timeout */
196 
197 /*
198  * Unset auto disconnect timeout
199  */
200 
201 void
202 ng_l2cap_discon_untimeout(ng_l2cap_con_p con)
203 {
204 	untimeout(ng_l2cap_queue_discon_timeout, con, con->con_timo);
205 	con->flags &= ~NG_L2CAP_CON_AUTO_DISCON_TIMO;
206 	NG_NODE_UNREF(con->l2cap->node);
207 } /* ng_l2cap_discon_untimeout */
208 
209 /*
210  *  Queue auto disconnect timeout
211  */
212 
213 static void
214 ng_l2cap_queue_discon_timeout(void *context)
215 {
216 	ng_l2cap_con_p	con = (ng_l2cap_con_p) context;
217 	node_p		node = con->l2cap->node;
218 
219 	if (NG_NODE_IS_VALID(node))
220 		ng_send_fn(node,NULL,&ng_l2cap_process_discon_timeout,con,0);
221 
222 	NG_NODE_UNREF(node);
223 } /* ng_l2cap_queue_discon_timeout */
224 
225 /*
226  * Free connection descriptor. Will unlink connection and free everything.
227  */
228 
229 void
230 ng_l2cap_free_con(ng_l2cap_con_p con)
231 {
232 	ng_l2cap_chan_p f = NULL, n = NULL;
233 
234 	if (con->flags & NG_L2CAP_CON_LP_TIMO)
235 		ng_l2cap_lp_untimeout(con);
236 	else if (con->flags & NG_L2CAP_CON_AUTO_DISCON_TIMO)
237 		ng_l2cap_discon_untimeout(con);
238 
239 	con->state = NG_L2CAP_CON_CLOSED;
240 
241 	if (con->tx_pkt != NULL) {
242 		while (con->tx_pkt != NULL) {
243 			struct mbuf	*m = con->tx_pkt->m_nextpkt;
244 
245 			m_freem(con->tx_pkt);
246 			con->tx_pkt = m;
247 		}
248 	}
249 
250 	NG_FREE_M(con->rx_pkt);
251 
252 	for (f = LIST_FIRST(&con->l2cap->chan_list); f != NULL; ) {
253 		n = LIST_NEXT(f, next);
254 
255 		if (f->con == con)
256 			ng_l2cap_free_chan(f);
257 
258 		f = n;
259 	}
260 
261 	while (!TAILQ_EMPTY(&con->cmd_list)) {
262 		ng_l2cap_cmd_p	cmd = TAILQ_FIRST(&con->cmd_list);
263 
264 		ng_l2cap_unlink_cmd(cmd);
265 		ng_l2cap_free_cmd(cmd);
266 	}
267 
268 	LIST_REMOVE(con, next);
269 	bzero(con, sizeof(*con));
270 	FREE(con, M_NETGRAPH_L2CAP);
271 } /* ng_l2cap_free_con */
272 
273 /*
274  * Get connection by "remote" address
275  */
276 
277 ng_l2cap_con_p
278 ng_l2cap_con_by_addr(ng_l2cap_p l2cap, bdaddr_p bdaddr)
279 {
280 	ng_l2cap_con_p	con = NULL;
281 
282 	LIST_FOREACH(con, &l2cap->con_list, next)
283 		if (bcmp(bdaddr, &con->remote, sizeof(con->remote)) == 0)
284 			break;
285 
286 	return (con);
287 } /* ng_l2cap_con_by_addr */
288 
289 /*
290  * Get connection by "handle"
291  */
292 
293 ng_l2cap_con_p
294 ng_l2cap_con_by_handle(ng_l2cap_p l2cap, u_int16_t con_handle)
295 {
296 	ng_l2cap_con_p	con = NULL;
297 
298 	LIST_FOREACH(con, &l2cap->con_list, next)
299 		if (con->con_handle == con_handle)
300 			break;
301 
302 	return (con);
303 } /* ng_l2cap_con_by_handle */
304 
305 /*
306  * Allocate new L2CAP channel descriptor on "con" conection with "psm".
307  * Will link the channel to the l2cap node
308  */
309 
310 ng_l2cap_chan_p
311 ng_l2cap_new_chan(ng_l2cap_p l2cap, ng_l2cap_con_p con, u_int16_t psm)
312 {
313 	ng_l2cap_chan_p	ch = NULL;
314 
315 	MALLOC(ch, ng_l2cap_chan_p, sizeof(*ch), M_NETGRAPH_L2CAP,
316 		M_NOWAIT|M_ZERO);
317 	if (ch == NULL)
318 		return (NULL);
319 
320 	ch->scid = ng_l2cap_get_cid(l2cap);
321 
322 	if (ch->scid != NG_L2CAP_NULL_CID) {
323 		/* Initialize channel */
324 		ch->psm = psm;
325 		ch->con = con;
326 		ch->state = NG_L2CAP_CLOSED;
327 
328 		/* Set MTU and flow control settings to defaults */
329 		ch->imtu = NG_L2CAP_MTU_DEFAULT;
330 		bcopy(ng_l2cap_default_flow(), &ch->iflow, sizeof(ch->iflow));
331 
332 		ch->omtu = NG_L2CAP_MTU_DEFAULT;
333 		bcopy(ng_l2cap_default_flow(), &ch->oflow, sizeof(ch->oflow));
334 
335 		ch->flush_timo = NG_L2CAP_FLUSH_TIMO_DEFAULT;
336 		ch->link_timo = NG_L2CAP_LINK_TIMO_DEFAULT;
337 
338 		LIST_INSERT_HEAD(&l2cap->chan_list, ch, next);
339 
340 		ng_l2cap_con_ref(con);
341 	} else {
342 		bzero(ch, sizeof(*ch));
343 		FREE(ch, M_NETGRAPH_L2CAP);
344 		ch = NULL;
345 	}
346 
347 	return (ch);
348 } /* ng_l2cap_new_chan */
349 
350 /*
351  * Get channel by source (local) channel ID
352  */
353 
354 ng_l2cap_chan_p
355 ng_l2cap_chan_by_scid(ng_l2cap_p l2cap, u_int16_t scid)
356 {
357 	ng_l2cap_chan_p	ch = NULL;
358 
359 	LIST_FOREACH(ch, &l2cap->chan_list, next)
360 		if (ch->scid == scid)
361 			break;
362 
363 	return (ch);
364 } /* ng_l2cap_chan_by_scid */
365 
366 /*
367  * Free channel descriptor.
368  */
369 
370 void
371 ng_l2cap_free_chan(ng_l2cap_chan_p ch)
372 {
373 	ng_l2cap_cmd_p	f = NULL, n = NULL;
374 
375 	f = TAILQ_FIRST(&ch->con->cmd_list);
376 	while (f != NULL) {
377 		n = TAILQ_NEXT(f, next);
378 
379 		if (f->ch == ch) {
380 			ng_l2cap_unlink_cmd(f);
381 			ng_l2cap_free_cmd(f);
382 		}
383 
384 		f = n;
385 	}
386 
387 	LIST_REMOVE(ch, next);
388 
389 	ng_l2cap_con_unref(ch->con);
390 
391 	bzero(ch, sizeof(*ch));
392 	FREE(ch, M_NETGRAPH_L2CAP);
393 } /* ng_l2cap_free_chan */
394 
395 /*
396  * Create new L2CAP command descriptor. WILL NOT add command to the queue.
397  */
398 
399 ng_l2cap_cmd_p
400 ng_l2cap_new_cmd(ng_l2cap_con_p con, ng_l2cap_chan_p ch, u_int8_t ident,
401 		u_int8_t code, u_int32_t token)
402 {
403 	ng_l2cap_cmd_p	cmd = NULL;
404 
405 	KASSERT((ch == NULL || ch->con == con),
406 ("%s: %s - invalid channel pointer!\n",
407 		__func__, NG_NODE_NAME(con->l2cap->node)));
408 
409 	MALLOC(cmd, ng_l2cap_cmd_p, sizeof(*cmd), M_NETGRAPH_L2CAP,
410 		M_NOWAIT|M_ZERO);
411 	if (cmd == NULL)
412 		return (NULL);
413 
414 	cmd->con = con;
415 	cmd->ch = ch;
416 	cmd->ident = ident;
417 	cmd->code = code;
418 	cmd->token = token;
419 	callout_handle_init(&cmd->timo);
420 
421 	return (cmd);
422 } /* ng_l2cap_new_cmd */
423 
424 /*
425  * Get L2CAP command descriptor by ident
426  */
427 
428 ng_l2cap_cmd_p
429 ng_l2cap_cmd_by_ident(ng_l2cap_con_p con, u_int8_t ident)
430 {
431 	ng_l2cap_cmd_p	cmd = NULL;
432 
433 	TAILQ_FOREACH(cmd, &con->cmd_list, next)
434 		if (cmd->ident == ident)
435 			break;
436 
437 	return (cmd);
438 } /* ng_l2cap_cmd_by_ident */
439 
440 /*
441  * Set LP timeout
442  */
443 
444 void
445 ng_l2cap_lp_timeout(ng_l2cap_con_p con)
446 {
447 	if (con->flags & (NG_L2CAP_CON_LP_TIMO|NG_L2CAP_CON_AUTO_DISCON_TIMO))
448 		panic("%s: %s - invalid timeout, state=%d, flags=%#x\n",
449 			__func__, NG_NODE_NAME(con->l2cap->node),
450 			con->state, con->flags);
451 
452 	NG_NODE_REF(con->l2cap->node);
453 	con->flags |= NG_L2CAP_CON_LP_TIMO;
454 	con->con_timo = timeout(ng_l2cap_queue_lp_timeout, con,
455 				bluetooth_hci_connect_timeout());
456 } /* ng_l2cap_lp_timeout */
457 
458 /*
459  * Unset LP timeout
460  */
461 
462 void
463 ng_l2cap_lp_untimeout(ng_l2cap_con_p con)
464 {
465 	untimeout(ng_l2cap_queue_lp_timeout, con, con->con_timo);
466 	con->flags &= ~NG_L2CAP_CON_LP_TIMO;
467 	NG_NODE_UNREF(con->l2cap->node);
468 } /* ng_l2cap_lp_untimeout */
469 
470 /*
471  * OK, timeout has happend so queue LP timeout processing function
472  */
473 
474 static void
475 ng_l2cap_queue_lp_timeout(void *context)
476 {
477 	ng_l2cap_con_p	con = (ng_l2cap_con_p) context;
478 	node_p		node = con->l2cap->node;
479 
480 	/*
481 	 * We need to save node pointer here, because ng_send_fn()
482 	 * can execute ng_l2cap_process_lp_timeout() without putting
483 	 * item into node's queue (if node can be locked). Once
484 	 * ng_l2cap_process_lp_timeout() executed the con pointer
485 	 * is no longer valid.
486 	 */
487 
488 	if (NG_NODE_IS_VALID(node))
489 		ng_send_fn(node, NULL, &ng_l2cap_process_lp_timeout, con, 0);
490 
491 	NG_NODE_UNREF(node);
492 } /* ng_l2cap_queue_lp_timeout */
493 
494 /*
495  * Set L2CAP command timeout
496  */
497 
498 void
499 ng_l2cap_command_timeout(ng_l2cap_cmd_p cmd, int timo)
500 {
501 	NG_NODE_REF(cmd->con->l2cap->node);
502 	cmd->flags |= NG_L2CAP_CMD_PENDING;
503 	cmd->timo = timeout(ng_l2cap_queue_command_timeout, cmd, timo);
504 } /* ng_l2cap_command_timeout */
505 
506 /*
507  * Unset L2CAP command timeout
508  */
509 
510 void
511 ng_l2cap_command_untimeout(ng_l2cap_cmd_p cmd)
512 {
513 	cmd->flags &= ~NG_L2CAP_CMD_PENDING;
514 	untimeout(ng_l2cap_queue_command_timeout, cmd, cmd->timo);
515 	NG_NODE_UNREF(cmd->con->l2cap->node);
516 } /* ng_l2cap_command_untimeout */
517 
518 /*
519  * OK, timeout has happend so queue L2CAP command timeout processing function
520  */
521 
522 static void
523 ng_l2cap_queue_command_timeout(void *context)
524 {
525 	ng_l2cap_cmd_p	cmd = (ng_l2cap_cmd_p) context;
526 	node_p		node = cmd->con->l2cap->node;
527 
528 	/*
529 	 * We need to save node pointer here, because ng_send_fn()
530 	 * can execute ng_l2cap_process_command_timeout() without
531 	 * putting item into node's queue (if node can be locked).
532 	 * Once ng_l2cap_process_command_timeout() executed the
533 	 * cmd pointer is no longer valid.
534 	 */
535 
536 	if (NG_NODE_IS_VALID(node))
537 		ng_send_fn(node,NULL,&ng_l2cap_process_command_timeout,cmd,0);
538 
539 	NG_NODE_UNREF(node);
540 } /* ng_l2cap_queue_command_timeout */
541 
542 /*
543  * Prepend "m"buf with "size" bytes
544  */
545 
546 struct mbuf *
547 ng_l2cap_prepend(struct mbuf *m, int size)
548 {
549 	M_PREPEND(m, size, M_DONTWAIT);
550 	if (m == NULL || (m->m_len < size && (m = m_pullup(m, size)) == NULL))
551 		return (NULL);
552 
553 	return (m);
554 } /* ng_l2cap_prepend */
555 
556 /*
557  * Default flow settings
558  */
559 
560 ng_l2cap_flow_p
561 ng_l2cap_default_flow(void)
562 {
563 	static ng_l2cap_flow_t	default_flow = {
564 		/* flags */		0x0,
565 		/* service_type */	NG_HCI_SERVICE_TYPE_BEST_EFFORT,
566 		/* token_rate */	0xffffffff, /* maximum */
567 		/* token_bucket_size */	0xffffffff, /* maximum */
568 		/* peak_bandwidth */	0x00000000, /* maximum */
569 		/* latency */		0xffffffff, /* don't care */
570 		/* delay_variation */	0xffffffff  /* don't care */
571 	};
572 
573 	return (&default_flow);
574 } /* ng_l2cap_default_flow */
575 
576 /*
577  * Get next available channel ID
578  * XXX FIXME this is *UGLY* but will do for now
579  */
580 
581 static u_int16_t
582 ng_l2cap_get_cid(ng_l2cap_p l2cap)
583 {
584 	u_int16_t	cid = l2cap->cid + 1;
585 
586 	if (cid < NG_L2CAP_FIRST_CID)
587 		cid = NG_L2CAP_FIRST_CID;
588 
589 	while (cid != l2cap->cid) {
590 		if (ng_l2cap_chan_by_scid(l2cap, cid) == NULL) {
591 			l2cap->cid = cid;
592 
593 			return (cid);
594 		}
595 
596 		cid ++;
597 		if (cid < NG_L2CAP_FIRST_CID)
598 			cid = NG_L2CAP_FIRST_CID;
599 	}
600 
601 	return (NG_L2CAP_NULL_CID);
602 } /* ng_l2cap_get_cid */
603 
604 /*
605  * Get next available command ident
606  * XXX FIXME this is *UGLY* but will do for now
607  */
608 
609 u_int8_t
610 ng_l2cap_get_ident(ng_l2cap_con_p con)
611 {
612 	u_int8_t	ident = con->ident + 1;
613 
614 	if (ident < NG_L2CAP_FIRST_IDENT)
615 		ident = NG_L2CAP_FIRST_IDENT;
616 
617 	while (ident != con->ident) {
618 		if (ng_l2cap_cmd_by_ident(con, ident) == NULL) {
619 			con->ident = ident;
620 
621 			return (ident);
622 		}
623 
624 		ident ++;
625 		if (ident < NG_L2CAP_FIRST_IDENT)
626 			ident = NG_L2CAP_FIRST_IDENT;
627 	}
628 
629 	return (NG_L2CAP_NULL_IDENT);
630 } /* ng_l2cap_get_ident */
631 
632