1 | /* $NetBSD: l2cap_lower.c,v 1.10 2014/08/05 07:55:31 rtr Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2005 Iain Hibbert. |
5 | * Copyright (c) 2006 Itronix Inc. |
6 | * All rights reserved. |
7 | * |
8 | * Redistribution and use in source and binary forms, with or without |
9 | * modification, are permitted provided that the following conditions |
10 | * are met: |
11 | * 1. Redistributions of source code must retain the above copyright |
12 | * notice, this list of conditions and the following disclaimer. |
13 | * 2. Redistributions in binary form must reproduce the above copyright |
14 | * notice, this list of conditions and the following disclaimer in the |
15 | * documentation and/or other materials provided with the distribution. |
16 | * 3. The name of Itronix Inc. may not be used to endorse |
17 | * or promote products derived from this software without specific |
18 | * prior written permission. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``AS IS'' AND |
21 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ITRONIX INC. BE LIABLE FOR ANY |
24 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
25 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
26 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
27 | * ON ANY THEORY OF LIABILITY, WHETHER IN |
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 | * POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | #include <sys/cdefs.h> |
34 | __KERNEL_RCSID(0, "$NetBSD: l2cap_lower.c,v 1.10 2014/08/05 07:55:31 rtr Exp $" ); |
35 | |
36 | #include <sys/param.h> |
37 | #include <sys/kernel.h> |
38 | #include <sys/malloc.h> |
39 | #include <sys/mbuf.h> |
40 | #include <sys/proc.h> |
41 | #include <sys/queue.h> |
42 | #include <sys/systm.h> |
43 | |
44 | #include <netbt/bluetooth.h> |
45 | #include <netbt/hci.h> |
46 | #include <netbt/l2cap.h> |
47 | |
48 | /**************************************************************************** |
49 | * |
50 | * L2CAP Channel Lower Layer interface |
51 | */ |
52 | |
53 | /* |
54 | * L2CAP channel is disconnected, could be: |
55 | * |
56 | * HCI layer received "Disconnect Complete" event for ACL link |
57 | * some Request timed out |
58 | * Config failed |
59 | * Other end reported invalid CID |
60 | * Normal disconnection |
61 | * Change link mode failed |
62 | */ |
63 | void |
64 | l2cap_close(struct l2cap_channel *chan, int err) |
65 | { |
66 | struct l2cap_pdu *pdu; |
67 | struct l2cap_req *req, *n; |
68 | |
69 | if (chan->lc_state == L2CAP_CLOSED) |
70 | return; |
71 | |
72 | /* |
73 | * Since any potential PDU could be half sent we just let it go, |
74 | * but disassociate ourselves from it as links deal with ownerless |
75 | * PDU's in any case. We could try harder to flush unsent packets |
76 | * but maybe its better to leave them in the queue? |
77 | */ |
78 | TAILQ_FOREACH(pdu, &chan->lc_link->hl_txq, lp_next) { |
79 | if (pdu->lp_chan == chan) |
80 | pdu->lp_chan = NULL; |
81 | } |
82 | |
83 | /* |
84 | * and clear any outstanding requests.. |
85 | */ |
86 | req = TAILQ_FIRST(&chan->lc_link->hl_reqs); |
87 | while (req != NULL) { |
88 | n = TAILQ_NEXT(req, lr_next); |
89 | if (req->lr_chan == chan) |
90 | l2cap_request_free(req); |
91 | |
92 | req = n; |
93 | } |
94 | |
95 | chan->lc_pending = 0; |
96 | chan->lc_state = L2CAP_CLOSED; |
97 | hci_acl_close(chan->lc_link, err); |
98 | chan->lc_link = NULL; |
99 | |
100 | (*chan->lc_proto->disconnected)(chan->lc_upper, err); |
101 | } |
102 | |
103 | /* |
104 | * Process incoming L2CAP frame from ACL link. We take off the B-Frame |
105 | * header (which is present in all packets), verify the data length |
106 | * and distribute the rest of the frame to the relevant channel |
107 | * handler. |
108 | */ |
109 | void |
110 | l2cap_recv_frame(struct mbuf *m, struct hci_link *link) |
111 | { |
112 | struct l2cap_channel *chan; |
113 | l2cap_hdr_t hdr; |
114 | |
115 | m_copydata(m, 0, sizeof(hdr), &hdr); |
116 | m_adj(m, sizeof(hdr)); |
117 | |
118 | hdr.length = le16toh(hdr.length); |
119 | hdr.dcid = le16toh(hdr.dcid); |
120 | |
121 | DPRINTFN(5, "(%s) received packet (%d bytes)\n" , |
122 | device_xname(link->hl_unit->hci_dev), hdr.length); |
123 | |
124 | if (hdr.length != m->m_pkthdr.len) |
125 | goto failed; |
126 | |
127 | if (hdr.dcid == L2CAP_SIGNAL_CID) { |
128 | l2cap_recv_signal(m, link); |
129 | return; |
130 | } |
131 | |
132 | if (hdr.dcid == L2CAP_CLT_CID) { |
133 | m_freem(m); /* TODO */ |
134 | return; |
135 | } |
136 | |
137 | chan = l2cap_cid_lookup(hdr.dcid); |
138 | if (chan != NULL && chan->lc_link == link |
139 | && chan->lc_imtu >= hdr.length |
140 | && chan->lc_state == L2CAP_OPEN) { |
141 | (*chan->lc_proto->input)(chan->lc_upper, m); |
142 | return; |
143 | } |
144 | |
145 | DPRINTF("(%s) invalid L2CAP packet dropped, CID #%d, length %d\n" , |
146 | device_xname(link->hl_unit->hci_dev), hdr.dcid, hdr.length); |
147 | |
148 | failed: |
149 | m_freem(m); |
150 | } |
151 | |
152 | /* |
153 | * Start another L2CAP packet on its way. This is called from l2cap_send_pcb |
154 | * (when no PDU is pending) and hci_acl_start (when PDU has been placed on |
155 | * device queue). Thus we can have more than one PDU waiting at the device |
156 | * if space is available but no single channel will hog the link. |
157 | */ |
158 | int |
159 | l2cap_start(struct l2cap_channel *chan) |
160 | { |
161 | struct mbuf *m; |
162 | int err = 0; |
163 | |
164 | if (chan->lc_state != L2CAP_OPEN) |
165 | return 0; |
166 | |
167 | if (MBUFQ_FIRST(&chan->lc_txq) == NULL) { |
168 | DPRINTFN(5, "no data, pending = %d\n" , chan->lc_pending); |
169 | /* |
170 | * If we are just waiting for the queue to flush |
171 | * and it has, we may disconnect.. |
172 | */ |
173 | if (chan->lc_flags & L2CAP_SHUTDOWN |
174 | && chan->lc_pending == 0) { |
175 | chan->lc_state = L2CAP_WAIT_DISCONNECT; |
176 | err = l2cap_send_disconnect_req(chan); |
177 | if (err) |
178 | l2cap_close(chan, err); |
179 | } |
180 | |
181 | return err; |
182 | } |
183 | |
184 | /* |
185 | * We could check QoS/RFC mode here and optionally not send |
186 | * the packet if we are not ready for any reason |
187 | * |
188 | * Also to support flush timeout then we might want to start |
189 | * the timer going? (would need to keep some kind of record |
190 | * of packets sent, possibly change it so that we allocate |
191 | * the l2cap_pdu and fragment the packet, then hand it down |
192 | * and get it back when its completed). Hm. |
193 | */ |
194 | |
195 | MBUFQ_DEQUEUE(&chan->lc_txq, m); |
196 | |
197 | KASSERT(chan->lc_link != NULL); |
198 | KASSERT(m != NULL); |
199 | |
200 | DPRINTFN(5, "CID #%d sending packet (%d bytes)\n" , |
201 | chan->lc_lcid, m->m_pkthdr.len); |
202 | |
203 | chan->lc_pending++; |
204 | return hci_acl_send(m, chan->lc_link, chan); |
205 | } |
206 | |