1/*********************************************************************
2 *
3 * Filename:      ircomm_lmp.c
4 * Version:       1.0
5 * Description:   Interface between IrCOMM and IrLMP
6 * Status:        Stable
7 * Author:        Dag Brattli <dagb@cs.uit.no>
8 * Created at:    Sun Jun  6 20:48:27 1999
9 * Modified at:   Sun Dec 12 13:44:17 1999
10 * Modified by:   Dag Brattli <dagb@cs.uit.no>
11 * Sources:       Previous IrLPT work by Thomas Davis
12 *
13 *     Copyright (c) 1999 Dag Brattli, 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 *     This program is distributed in the hope that it will be useful,
22 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
23 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 *     GNU General Public License for more details.
25 *
26 *     You should have received a copy of the GNU General Public License
27 *     along with this program; if not, see <http://www.gnu.org/licenses/>.
28 *
29 ********************************************************************/
30
31#include <linux/init.h>
32#include <linux/gfp.h>
33
34#include <net/irda/irda.h>
35#include <net/irda/irlmp.h>
36#include <net/irda/iriap.h>
37#include <net/irda/irda_device.h>	/* struct irda_skb_cb */
38
39#include <net/irda/ircomm_event.h>
40#include <net/irda/ircomm_lmp.h>
41
42
43/*
44 * Function ircomm_lmp_connect_request (self, userdata)
45 *
46 *
47 *
48 */
49static int ircomm_lmp_connect_request(struct ircomm_cb *self,
50				      struct sk_buff *userdata,
51				      struct ircomm_info *info)
52{
53	int ret = 0;
54
55	/* Don't forget to refcount it - should be NULL anyway */
56	if(userdata)
57		skb_get(userdata);
58
59	ret = irlmp_connect_request(self->lsap, info->dlsap_sel,
60				    info->saddr, info->daddr, NULL, userdata);
61	return ret;
62}
63
64/*
65 * Function ircomm_lmp_connect_response (self, skb)
66 *
67 *
68 *
69 */
70static int ircomm_lmp_connect_response(struct ircomm_cb *self,
71				       struct sk_buff *userdata)
72{
73	struct sk_buff *tx_skb;
74
75	/* Any userdata supplied? */
76	if (userdata == NULL) {
77		tx_skb = alloc_skb(LMP_MAX_HEADER, GFP_ATOMIC);
78		if (!tx_skb)
79			return -ENOMEM;
80
81		/* Reserve space for MUX and LAP header */
82		skb_reserve(tx_skb, LMP_MAX_HEADER);
83	} else {
84		/*
85		 *  Check that the client has reserved enough space for
86		 *  headers
87		 */
88		IRDA_ASSERT(skb_headroom(userdata) >= LMP_MAX_HEADER,
89			    return -1;);
90
91		/* Don't forget to refcount it - should be NULL anyway */
92		skb_get(userdata);
93		tx_skb = userdata;
94	}
95
96	return irlmp_connect_response(self->lsap, tx_skb);
97}
98
99static int ircomm_lmp_disconnect_request(struct ircomm_cb *self,
100					 struct sk_buff *userdata,
101					 struct ircomm_info *info)
102{
103	struct sk_buff *tx_skb;
104	int ret;
105
106	if (!userdata) {
107		tx_skb = alloc_skb(LMP_MAX_HEADER, GFP_ATOMIC);
108		if (!tx_skb)
109			return -ENOMEM;
110
111		/*  Reserve space for MUX and LAP header */
112		skb_reserve(tx_skb, LMP_MAX_HEADER);
113		userdata = tx_skb;
114	} else {
115		/* Don't forget to refcount it - should be NULL anyway */
116		skb_get(userdata);
117	}
118
119	ret = irlmp_disconnect_request(self->lsap, userdata);
120
121	return ret;
122}
123
124/*
125 * Function ircomm_lmp_flow_control (skb)
126 *
127 *    This function is called when a data frame we have sent to IrLAP has
128 *    been deallocated. We do this to make sure we don't flood IrLAP with
129 *    frames, since we are not using the IrTTP flow control mechanism
130 */
131static void ircomm_lmp_flow_control(struct sk_buff *skb)
132{
133	struct irda_skb_cb *cb;
134	struct ircomm_cb *self;
135	int line;
136
137	IRDA_ASSERT(skb != NULL, return;);
138
139	cb = (struct irda_skb_cb *) skb->cb;
140
141	line = cb->line;
142
143	self = (struct ircomm_cb *) hashbin_lock_find(ircomm, line, NULL);
144	if (!self) {
145		pr_debug("%s(), didn't find myself\n", __func__);
146		return;
147	}
148
149	IRDA_ASSERT(self != NULL, return;);
150	IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;);
151
152	self->pkt_count--;
153
154	if ((self->pkt_count < 2) && (self->flow_status == FLOW_STOP)) {
155		pr_debug("%s(), asking TTY to start again!\n", __func__);
156		self->flow_status = FLOW_START;
157		if (self->notify.flow_indication)
158			self->notify.flow_indication(self->notify.instance,
159						     self, FLOW_START);
160	}
161}
162
163/*
164 * Function ircomm_lmp_data_request (self, userdata)
165 *
166 *    Send data frame to peer device
167 *
168 */
169static int ircomm_lmp_data_request(struct ircomm_cb *self,
170				   struct sk_buff *skb,
171				   int not_used)
172{
173	struct irda_skb_cb *cb;
174	int ret;
175
176	IRDA_ASSERT(skb != NULL, return -1;);
177
178	cb = (struct irda_skb_cb *) skb->cb;
179
180	cb->line = self->line;
181
182	pr_debug("%s(), sending frame\n", __func__);
183
184	/* Don't forget to refcount it - see ircomm_tty_do_softint() */
185	skb_get(skb);
186
187	skb_orphan(skb);
188	skb->destructor = ircomm_lmp_flow_control;
189
190	if ((self->pkt_count++ > 7) && (self->flow_status == FLOW_START)) {
191		pr_debug("%s(), asking TTY to slow down!\n", __func__);
192		self->flow_status = FLOW_STOP;
193		if (self->notify.flow_indication)
194			self->notify.flow_indication(self->notify.instance,
195						     self, FLOW_STOP);
196	}
197	ret = irlmp_data_request(self->lsap, skb);
198	if (ret) {
199		net_err_ratelimited("%s(), failed\n", __func__);
200		/* irlmp_data_request already free the packet */
201	}
202
203	return ret;
204}
205
206/*
207 * Function ircomm_lmp_data_indication (instance, sap, skb)
208 *
209 *    Incoming data which we must deliver to the state machine, to check
210 *    we are still connected.
211 */
212static int ircomm_lmp_data_indication(void *instance, void *sap,
213				      struct sk_buff *skb)
214{
215	struct ircomm_cb *self = (struct ircomm_cb *) instance;
216
217	IRDA_ASSERT(self != NULL, return -1;);
218	IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
219	IRDA_ASSERT(skb != NULL, return -1;);
220
221	ircomm_do_event(self, IRCOMM_LMP_DATA_INDICATION, skb, NULL);
222
223	/* Drop reference count - see ircomm_tty_data_indication(). */
224	dev_kfree_skb(skb);
225
226	return 0;
227}
228
229/*
230 * Function ircomm_lmp_connect_confirm (instance, sap, qos, max_sdu_size,
231 *                                       max_header_size, skb)
232 *
233 *    Connection has been confirmed by peer device
234 *
235 */
236static void ircomm_lmp_connect_confirm(void *instance, void *sap,
237				       struct qos_info *qos,
238				       __u32 max_seg_size,
239				       __u8 max_header_size,
240				       struct sk_buff *skb)
241{
242	struct ircomm_cb *self = (struct ircomm_cb *) instance;
243	struct ircomm_info info;
244
245	IRDA_ASSERT(self != NULL, return;);
246	IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;);
247	IRDA_ASSERT(skb != NULL, return;);
248	IRDA_ASSERT(qos != NULL, return;);
249
250	info.max_data_size = max_seg_size;
251	info.max_header_size = max_header_size;
252	info.qos = qos;
253
254	ircomm_do_event(self, IRCOMM_LMP_CONNECT_CONFIRM, skb, &info);
255
256	/* Drop reference count - see ircomm_tty_connect_confirm(). */
257	dev_kfree_skb(skb);
258}
259
260/*
261 * Function ircomm_lmp_connect_indication (instance, sap, qos, max_sdu_size,
262 *                                         max_header_size, skb)
263 *
264 *    Peer device wants to make a connection with us
265 *
266 */
267static void ircomm_lmp_connect_indication(void *instance, void *sap,
268					  struct qos_info *qos,
269					  __u32 max_seg_size,
270					  __u8 max_header_size,
271					  struct sk_buff *skb)
272{
273	struct ircomm_cb *self = (struct ircomm_cb *)instance;
274	struct ircomm_info info;
275
276	IRDA_ASSERT(self != NULL, return;);
277	IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;);
278	IRDA_ASSERT(skb != NULL, return;);
279	IRDA_ASSERT(qos != NULL, return;);
280
281	info.max_data_size = max_seg_size;
282	info.max_header_size = max_header_size;
283	info.qos = qos;
284
285	ircomm_do_event(self, IRCOMM_LMP_CONNECT_INDICATION, skb, &info);
286
287	/* Drop reference count - see ircomm_tty_connect_indication(). */
288	dev_kfree_skb(skb);
289}
290
291/*
292 * Function ircomm_lmp_disconnect_indication (instance, sap, reason, skb)
293 *
294 *    Peer device has closed the connection, or the link went down for some
295 *    other reason
296 */
297static void ircomm_lmp_disconnect_indication(void *instance, void *sap,
298					     LM_REASON reason,
299					     struct sk_buff *skb)
300{
301	struct ircomm_cb *self = (struct ircomm_cb *) instance;
302	struct ircomm_info info;
303
304	IRDA_ASSERT(self != NULL, return;);
305	IRDA_ASSERT(self->magic == IRCOMM_MAGIC, return;);
306
307	info.reason = reason;
308
309	ircomm_do_event(self, IRCOMM_LMP_DISCONNECT_INDICATION, skb, &info);
310
311	/* Drop reference count - see ircomm_tty_disconnect_indication(). */
312	if(skb)
313		dev_kfree_skb(skb);
314}
315/*
316 * Function ircomm_open_lsap (self)
317 *
318 *    Open LSAP. This function will only be used when using "raw" services
319 *
320 */
321int ircomm_open_lsap(struct ircomm_cb *self)
322{
323	notify_t notify;
324
325	/* Register callbacks */
326	irda_notify_init(&notify);
327	notify.data_indication       = ircomm_lmp_data_indication;
328	notify.connect_confirm       = ircomm_lmp_connect_confirm;
329	notify.connect_indication    = ircomm_lmp_connect_indication;
330	notify.disconnect_indication = ircomm_lmp_disconnect_indication;
331	notify.instance = self;
332	strlcpy(notify.name, "IrCOMM", sizeof(notify.name));
333
334	self->lsap = irlmp_open_lsap(LSAP_ANY, &notify, 0);
335	if (!self->lsap) {
336		pr_debug("%sfailed to allocate tsap\n", __func__);
337		return -1;
338	}
339	self->slsap_sel = self->lsap->slsap_sel;
340
341	/*
342	 *  Initialize the call-table for issuing commands
343	 */
344	self->issue.data_request       = ircomm_lmp_data_request;
345	self->issue.connect_request    = ircomm_lmp_connect_request;
346	self->issue.connect_response   = ircomm_lmp_connect_response;
347	self->issue.disconnect_request = ircomm_lmp_disconnect_request;
348
349	return 0;
350}
351