xref: /linux/drivers/tty/ipwireless/network.c (revision cbecf716ca618fd44feda6bd9a64a8179d031fc5)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2099dc4fbSDavid Sterba /*
3099dc4fbSDavid Sterba  * IPWireless 3G PCMCIA Network Driver
4099dc4fbSDavid Sterba  *
5099dc4fbSDavid Sterba  * Original code
6099dc4fbSDavid Sterba  *   by Stephen Blackheath <stephen@blacksapphire.com>,
7099dc4fbSDavid Sterba  *      Ben Martel <benm@symmetric.co.nz>
8099dc4fbSDavid Sterba  *
9099dc4fbSDavid Sterba  * Copyrighted as follows:
10099dc4fbSDavid Sterba  *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
11099dc4fbSDavid Sterba  *
12099dc4fbSDavid Sterba  * Various driver changes and rewrites, port to new kernels
13099dc4fbSDavid Sterba  *   Copyright (C) 2006-2007 Jiri Kosina
14099dc4fbSDavid Sterba  *
15099dc4fbSDavid Sterba  * Misc code cleanups and updates
16099dc4fbSDavid Sterba  *   Copyright (C) 2007 David Sterba
17099dc4fbSDavid Sterba  */
18099dc4fbSDavid Sterba 
19099dc4fbSDavid Sterba #include <linux/interrupt.h>
20099dc4fbSDavid Sterba #include <linux/kernel.h>
21099dc4fbSDavid Sterba #include <linux/mutex.h>
22099dc4fbSDavid Sterba #include <linux/netdevice.h>
23099dc4fbSDavid Sterba #include <linux/ppp_channel.h>
24099dc4fbSDavid Sterba #include <linux/ppp_defs.h>
255a0e3ad6STejun Heo #include <linux/slab.h>
264b32da2bSPaul Mackerras #include <linux/ppp-ioctl.h>
27099dc4fbSDavid Sterba #include <linux/skbuff.h>
28099dc4fbSDavid Sterba 
29099dc4fbSDavid Sterba #include "network.h"
30099dc4fbSDavid Sterba #include "hardware.h"
31099dc4fbSDavid Sterba #include "main.h"
32099dc4fbSDavid Sterba #include "tty.h"
33099dc4fbSDavid Sterba 
34099dc4fbSDavid Sterba #define MAX_ASSOCIATED_TTYS 2
35099dc4fbSDavid Sterba 
36099dc4fbSDavid Sterba #define SC_RCV_BITS     (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
37099dc4fbSDavid Sterba 
38099dc4fbSDavid Sterba struct ipw_network {
39099dc4fbSDavid Sterba 	/* Hardware context, used for calls to hardware layer. */
40099dc4fbSDavid Sterba 	struct ipw_hardware *hardware;
41099dc4fbSDavid Sterba 	/* Context for kernel 'generic_ppp' functionality */
42099dc4fbSDavid Sterba 	struct ppp_channel *ppp_channel;
43099dc4fbSDavid Sterba 	/* tty context connected with IPW console */
44099dc4fbSDavid Sterba 	struct ipw_tty *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS];
45099dc4fbSDavid Sterba 	/* True if ppp needs waking up once we're ready to xmit */
46099dc4fbSDavid Sterba 	int ppp_blocked;
47099dc4fbSDavid Sterba 	/* Number of packets queued up in hardware module. */
48099dc4fbSDavid Sterba 	int outgoing_packets_queued;
49099dc4fbSDavid Sterba 	/* Spinlock to avoid interrupts during shutdown */
5063c4dbd1SDavid Sterba 	spinlock_t lock;
51099dc4fbSDavid Sterba 	struct mutex close_lock;
52099dc4fbSDavid Sterba 
53099dc4fbSDavid Sterba 	/* PPP ioctl data, not actually used anywere */
54099dc4fbSDavid Sterba 	unsigned int flags;
55099dc4fbSDavid Sterba 	unsigned int rbits;
56099dc4fbSDavid Sterba 	u32 xaccm[8];
57099dc4fbSDavid Sterba 	u32 raccm;
58099dc4fbSDavid Sterba 	int mru;
59099dc4fbSDavid Sterba 
60099dc4fbSDavid Sterba 	int shutting_down;
61099dc4fbSDavid Sterba 	unsigned int ras_control_lines;
62099dc4fbSDavid Sterba 
63099dc4fbSDavid Sterba 	struct work_struct work_go_online;
64099dc4fbSDavid Sterba 	struct work_struct work_go_offline;
65099dc4fbSDavid Sterba };
66099dc4fbSDavid Sterba 
67099dc4fbSDavid Sterba static void notify_packet_sent(void *callback_data, unsigned int packet_length)
68099dc4fbSDavid Sterba {
69099dc4fbSDavid Sterba 	struct ipw_network *network = callback_data;
70099dc4fbSDavid Sterba 	unsigned long flags;
71099dc4fbSDavid Sterba 
7263c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
73099dc4fbSDavid Sterba 	network->outgoing_packets_queued--;
74099dc4fbSDavid Sterba 	if (network->ppp_channel != NULL) {
75099dc4fbSDavid Sterba 		if (network->ppp_blocked) {
76099dc4fbSDavid Sterba 			network->ppp_blocked = 0;
7763c4dbd1SDavid Sterba 			spin_unlock_irqrestore(&network->lock, flags);
78099dc4fbSDavid Sterba 			ppp_output_wakeup(network->ppp_channel);
79099dc4fbSDavid Sterba 			if (ipwireless_debug)
80bee9c7c0SDavid Sterba 				printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
81099dc4fbSDavid Sterba 				       ": ppp unblocked\n");
82099dc4fbSDavid Sterba 		} else
8363c4dbd1SDavid Sterba 			spin_unlock_irqrestore(&network->lock, flags);
84099dc4fbSDavid Sterba 	} else
8563c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
86099dc4fbSDavid Sterba }
87099dc4fbSDavid Sterba 
88099dc4fbSDavid Sterba /*
89099dc4fbSDavid Sterba  * Called by the ppp system when it has a packet to send to the hardware.
90099dc4fbSDavid Sterba  */
91099dc4fbSDavid Sterba static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel,
92099dc4fbSDavid Sterba 				     struct sk_buff *skb)
93099dc4fbSDavid Sterba {
94099dc4fbSDavid Sterba 	struct ipw_network *network = ppp_channel->private;
95099dc4fbSDavid Sterba 	unsigned long flags;
96099dc4fbSDavid Sterba 
9763c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
982e713165SDavid Sterba 	if (network->outgoing_packets_queued < ipwireless_out_queue) {
99099dc4fbSDavid Sterba 		unsigned char *buf;
100099dc4fbSDavid Sterba 		static unsigned char header[] = {
101099dc4fbSDavid Sterba 			PPP_ALLSTATIONS, /* 0xff */
102099dc4fbSDavid Sterba 			PPP_UI,		 /* 0x03 */
103099dc4fbSDavid Sterba 		};
104099dc4fbSDavid Sterba 		int ret;
105099dc4fbSDavid Sterba 
106099dc4fbSDavid Sterba 		network->outgoing_packets_queued++;
10763c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
108099dc4fbSDavid Sterba 
109099dc4fbSDavid Sterba 		/*
110099dc4fbSDavid Sterba 		 * If we have the requested amount of headroom in the skb we
111099dc4fbSDavid Sterba 		 * were handed, then we can add the header efficiently.
112099dc4fbSDavid Sterba 		 */
113099dc4fbSDavid Sterba 		if (skb_headroom(skb) >= 2) {
114099dc4fbSDavid Sterba 			memcpy(skb_push(skb, 2), header, 2);
115099dc4fbSDavid Sterba 			ret = ipwireless_send_packet(network->hardware,
116099dc4fbSDavid Sterba 					       IPW_CHANNEL_RAS, skb->data,
117099dc4fbSDavid Sterba 					       skb->len,
118099dc4fbSDavid Sterba 					       notify_packet_sent,
119099dc4fbSDavid Sterba 					       network);
120*db332356STong Zhang 			if (ret < 0) {
121099dc4fbSDavid Sterba 				skb_pull(skb, 2);
122099dc4fbSDavid Sterba 				return 0;
123099dc4fbSDavid Sterba 			}
124099dc4fbSDavid Sterba 		} else {
125099dc4fbSDavid Sterba 			/* Otherwise (rarely) we do it inefficiently. */
126099dc4fbSDavid Sterba 			buf = kmalloc(skb->len + 2, GFP_ATOMIC);
127099dc4fbSDavid Sterba 			if (!buf)
128099dc4fbSDavid Sterba 				return 0;
129099dc4fbSDavid Sterba 			memcpy(buf + 2, skb->data, skb->len);
130099dc4fbSDavid Sterba 			memcpy(buf, header, 2);
131099dc4fbSDavid Sterba 			ret = ipwireless_send_packet(network->hardware,
132099dc4fbSDavid Sterba 					       IPW_CHANNEL_RAS, buf,
133099dc4fbSDavid Sterba 					       skb->len + 2,
134099dc4fbSDavid Sterba 					       notify_packet_sent,
135099dc4fbSDavid Sterba 					       network);
136099dc4fbSDavid Sterba 			kfree(buf);
137*db332356STong Zhang 			if (ret < 0)
138099dc4fbSDavid Sterba 				return 0;
139099dc4fbSDavid Sterba 		}
140099dc4fbSDavid Sterba 		kfree_skb(skb);
141099dc4fbSDavid Sterba 		return 1;
142099dc4fbSDavid Sterba 	} else {
143099dc4fbSDavid Sterba 		/*
144099dc4fbSDavid Sterba 		 * Otherwise reject the packet, and flag that the ppp system
145099dc4fbSDavid Sterba 		 * needs to be unblocked once we are ready to send.
146099dc4fbSDavid Sterba 		 */
147099dc4fbSDavid Sterba 		network->ppp_blocked = 1;
14863c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
149bee9c7c0SDavid Sterba 		if (ipwireless_debug)
150bee9c7c0SDavid Sterba 			printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": ppp blocked\n");
151099dc4fbSDavid Sterba 		return 0;
152099dc4fbSDavid Sterba 	}
153099dc4fbSDavid Sterba }
154099dc4fbSDavid Sterba 
155099dc4fbSDavid Sterba /* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */
156099dc4fbSDavid Sterba static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel,
157099dc4fbSDavid Sterba 				unsigned int cmd, unsigned long arg)
158099dc4fbSDavid Sterba {
159099dc4fbSDavid Sterba 	struct ipw_network *network = ppp_channel->private;
160099dc4fbSDavid Sterba 	int err, val;
161099dc4fbSDavid Sterba 	u32 accm[8];
162099dc4fbSDavid Sterba 	int __user *user_arg = (int __user *) arg;
163099dc4fbSDavid Sterba 
164099dc4fbSDavid Sterba 	err = -EFAULT;
165099dc4fbSDavid Sterba 	switch (cmd) {
166099dc4fbSDavid Sterba 	case PPPIOCGFLAGS:
167099dc4fbSDavid Sterba 		val = network->flags | network->rbits;
168099dc4fbSDavid Sterba 		if (put_user(val, user_arg))
169099dc4fbSDavid Sterba 			break;
170099dc4fbSDavid Sterba 		err = 0;
171099dc4fbSDavid Sterba 		break;
172099dc4fbSDavid Sterba 
173099dc4fbSDavid Sterba 	case PPPIOCSFLAGS:
174099dc4fbSDavid Sterba 		if (get_user(val, user_arg))
175099dc4fbSDavid Sterba 			break;
176099dc4fbSDavid Sterba 		network->flags = val & ~SC_RCV_BITS;
177099dc4fbSDavid Sterba 		network->rbits = val & SC_RCV_BITS;
178099dc4fbSDavid Sterba 		err = 0;
179099dc4fbSDavid Sterba 		break;
180099dc4fbSDavid Sterba 
181099dc4fbSDavid Sterba 	case PPPIOCGASYNCMAP:
182099dc4fbSDavid Sterba 		if (put_user(network->xaccm[0], user_arg))
183099dc4fbSDavid Sterba 			break;
184099dc4fbSDavid Sterba 		err = 0;
185099dc4fbSDavid Sterba 		break;
186099dc4fbSDavid Sterba 
187099dc4fbSDavid Sterba 	case PPPIOCSASYNCMAP:
188099dc4fbSDavid Sterba 		if (get_user(network->xaccm[0], user_arg))
189099dc4fbSDavid Sterba 			break;
190099dc4fbSDavid Sterba 		err = 0;
191099dc4fbSDavid Sterba 		break;
192099dc4fbSDavid Sterba 
193099dc4fbSDavid Sterba 	case PPPIOCGRASYNCMAP:
194099dc4fbSDavid Sterba 		if (put_user(network->raccm, user_arg))
195099dc4fbSDavid Sterba 			break;
196099dc4fbSDavid Sterba 		err = 0;
197099dc4fbSDavid Sterba 		break;
198099dc4fbSDavid Sterba 
199099dc4fbSDavid Sterba 	case PPPIOCSRASYNCMAP:
200099dc4fbSDavid Sterba 		if (get_user(network->raccm, user_arg))
201099dc4fbSDavid Sterba 			break;
202099dc4fbSDavid Sterba 		err = 0;
203099dc4fbSDavid Sterba 		break;
204099dc4fbSDavid Sterba 
205099dc4fbSDavid Sterba 	case PPPIOCGXASYNCMAP:
206099dc4fbSDavid Sterba 		if (copy_to_user((void __user *) arg, network->xaccm,
207099dc4fbSDavid Sterba 					sizeof(network->xaccm)))
208099dc4fbSDavid Sterba 			break;
209099dc4fbSDavid Sterba 		err = 0;
210099dc4fbSDavid Sterba 		break;
211099dc4fbSDavid Sterba 
212099dc4fbSDavid Sterba 	case PPPIOCSXASYNCMAP:
213099dc4fbSDavid Sterba 		if (copy_from_user(accm, (void __user *) arg, sizeof(accm)))
214099dc4fbSDavid Sterba 			break;
215099dc4fbSDavid Sterba 		accm[2] &= ~0x40000000U;	/* can't escape 0x5e */
216099dc4fbSDavid Sterba 		accm[3] |= 0x60000000U;	/* must escape 0x7d, 0x7e */
217099dc4fbSDavid Sterba 		memcpy(network->xaccm, accm, sizeof(network->xaccm));
218099dc4fbSDavid Sterba 		err = 0;
219099dc4fbSDavid Sterba 		break;
220099dc4fbSDavid Sterba 
221099dc4fbSDavid Sterba 	case PPPIOCGMRU:
222099dc4fbSDavid Sterba 		if (put_user(network->mru, user_arg))
223099dc4fbSDavid Sterba 			break;
224099dc4fbSDavid Sterba 		err = 0;
225099dc4fbSDavid Sterba 		break;
226099dc4fbSDavid Sterba 
227099dc4fbSDavid Sterba 	case PPPIOCSMRU:
228099dc4fbSDavid Sterba 		if (get_user(val, user_arg))
229099dc4fbSDavid Sterba 			break;
230099dc4fbSDavid Sterba 		if (val < PPP_MRU)
231099dc4fbSDavid Sterba 			val = PPP_MRU;
232099dc4fbSDavid Sterba 		network->mru = val;
233099dc4fbSDavid Sterba 		err = 0;
234099dc4fbSDavid Sterba 		break;
235099dc4fbSDavid Sterba 
236099dc4fbSDavid Sterba 	default:
237099dc4fbSDavid Sterba 		err = -ENOTTY;
238099dc4fbSDavid Sterba 	}
239099dc4fbSDavid Sterba 
240099dc4fbSDavid Sterba 	return err;
241099dc4fbSDavid Sterba }
242099dc4fbSDavid Sterba 
243d7100da0Sstephen hemminger static const struct ppp_channel_ops ipwireless_ppp_channel_ops = {
244099dc4fbSDavid Sterba 	.start_xmit = ipwireless_ppp_start_xmit,
245099dc4fbSDavid Sterba 	.ioctl      = ipwireless_ppp_ioctl
246099dc4fbSDavid Sterba };
247099dc4fbSDavid Sterba 
248099dc4fbSDavid Sterba static void do_go_online(struct work_struct *work_go_online)
249099dc4fbSDavid Sterba {
250099dc4fbSDavid Sterba 	struct ipw_network *network =
251099dc4fbSDavid Sterba 		container_of(work_go_online, struct ipw_network,
252099dc4fbSDavid Sterba 				work_go_online);
253099dc4fbSDavid Sterba 	unsigned long flags;
254099dc4fbSDavid Sterba 
25563c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
256099dc4fbSDavid Sterba 	if (!network->ppp_channel) {
257099dc4fbSDavid Sterba 		struct ppp_channel *channel;
258099dc4fbSDavid Sterba 
25963c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
260099dc4fbSDavid Sterba 		channel = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL);
261099dc4fbSDavid Sterba 		if (!channel) {
262099dc4fbSDavid Sterba 			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
263099dc4fbSDavid Sterba 					": unable to allocate PPP channel\n");
264099dc4fbSDavid Sterba 			return;
265099dc4fbSDavid Sterba 		}
266099dc4fbSDavid Sterba 		channel->private = network;
267099dc4fbSDavid Sterba 		channel->mtu = 16384;	/* Wild guess */
268099dc4fbSDavid Sterba 		channel->hdrlen = 2;
269099dc4fbSDavid Sterba 		channel->ops = &ipwireless_ppp_channel_ops;
270099dc4fbSDavid Sterba 
271099dc4fbSDavid Sterba 		network->flags = 0;
272099dc4fbSDavid Sterba 		network->rbits = 0;
273099dc4fbSDavid Sterba 		network->mru = PPP_MRU;
274099dc4fbSDavid Sterba 		memset(network->xaccm, 0, sizeof(network->xaccm));
275099dc4fbSDavid Sterba 		network->xaccm[0] = ~0U;
276099dc4fbSDavid Sterba 		network->xaccm[3] = 0x60000000U;
277099dc4fbSDavid Sterba 		network->raccm = ~0U;
278b6abc904SAlan Cox 		if (ppp_register_channel(channel) < 0) {
279b6abc904SAlan Cox 			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
280b6abc904SAlan Cox 					": unable to register PPP channel\n");
281b6abc904SAlan Cox 			kfree(channel);
282b6abc904SAlan Cox 			return;
283b6abc904SAlan Cox 		}
28463c4dbd1SDavid Sterba 		spin_lock_irqsave(&network->lock, flags);
285099dc4fbSDavid Sterba 		network->ppp_channel = channel;
286099dc4fbSDavid Sterba 	}
28763c4dbd1SDavid Sterba 	spin_unlock_irqrestore(&network->lock, flags);
288099dc4fbSDavid Sterba }
289099dc4fbSDavid Sterba 
290099dc4fbSDavid Sterba static void do_go_offline(struct work_struct *work_go_offline)
291099dc4fbSDavid Sterba {
292099dc4fbSDavid Sterba 	struct ipw_network *network =
293099dc4fbSDavid Sterba 		container_of(work_go_offline, struct ipw_network,
294099dc4fbSDavid Sterba 				work_go_offline);
295099dc4fbSDavid Sterba 	unsigned long flags;
296099dc4fbSDavid Sterba 
297099dc4fbSDavid Sterba 	mutex_lock(&network->close_lock);
29863c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
299099dc4fbSDavid Sterba 	if (network->ppp_channel != NULL) {
300099dc4fbSDavid Sterba 		struct ppp_channel *channel = network->ppp_channel;
301099dc4fbSDavid Sterba 
302099dc4fbSDavid Sterba 		network->ppp_channel = NULL;
30363c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
304099dc4fbSDavid Sterba 		mutex_unlock(&network->close_lock);
305099dc4fbSDavid Sterba 		ppp_unregister_channel(channel);
306099dc4fbSDavid Sterba 	} else {
30763c4dbd1SDavid Sterba 		spin_unlock_irqrestore(&network->lock, flags);
308099dc4fbSDavid Sterba 		mutex_unlock(&network->close_lock);
309099dc4fbSDavid Sterba 	}
310099dc4fbSDavid Sterba }
311099dc4fbSDavid Sterba 
312099dc4fbSDavid Sterba void ipwireless_network_notify_control_line_change(struct ipw_network *network,
313099dc4fbSDavid Sterba 						   unsigned int channel_idx,
314099dc4fbSDavid Sterba 						   unsigned int control_lines,
315099dc4fbSDavid Sterba 						   unsigned int changed_mask)
316099dc4fbSDavid Sterba {
317099dc4fbSDavid Sterba 	int i;
318099dc4fbSDavid Sterba 
319099dc4fbSDavid Sterba 	if (channel_idx == IPW_CHANNEL_RAS)
320099dc4fbSDavid Sterba 		network->ras_control_lines = control_lines;
321099dc4fbSDavid Sterba 
322099dc4fbSDavid Sterba 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
323099dc4fbSDavid Sterba 		struct ipw_tty *tty =
324099dc4fbSDavid Sterba 			network->associated_ttys[channel_idx][i];
325099dc4fbSDavid Sterba 
326099dc4fbSDavid Sterba 		/*
327099dc4fbSDavid Sterba 		 * If it's associated with a tty (other than the RAS channel
328099dc4fbSDavid Sterba 		 * when we're online), then send the data to that tty.  The RAS
329099dc4fbSDavid Sterba 		 * channel's data is handled above - it always goes through
330099dc4fbSDavid Sterba 		 * ppp_generic.
331099dc4fbSDavid Sterba 		 */
332099dc4fbSDavid Sterba 		if (tty)
333099dc4fbSDavid Sterba 			ipwireless_tty_notify_control_line_change(tty,
334099dc4fbSDavid Sterba 								  channel_idx,
335099dc4fbSDavid Sterba 								  control_lines,
336099dc4fbSDavid Sterba 								  changed_mask);
337099dc4fbSDavid Sterba 	}
338099dc4fbSDavid Sterba }
339099dc4fbSDavid Sterba 
340099dc4fbSDavid Sterba /*
341099dc4fbSDavid Sterba  * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI)
342099dc4fbSDavid Sterba  * bytes, which are required on sent packet, but not always present on received
343099dc4fbSDavid Sterba  * packets
344099dc4fbSDavid Sterba  */
345099dc4fbSDavid Sterba static struct sk_buff *ipw_packet_received_skb(unsigned char *data,
346099dc4fbSDavid Sterba 					       unsigned int length)
347099dc4fbSDavid Sterba {
348099dc4fbSDavid Sterba 	struct sk_buff *skb;
349099dc4fbSDavid Sterba 
350099dc4fbSDavid Sterba 	if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) {
351099dc4fbSDavid Sterba 		length -= 2;
352099dc4fbSDavid Sterba 		data += 2;
353099dc4fbSDavid Sterba 	}
354099dc4fbSDavid Sterba 
355099dc4fbSDavid Sterba 	skb = dev_alloc_skb(length + 4);
356d1519e23SAlan Cox 	if (skb == NULL)
357d1519e23SAlan Cox 		return NULL;
358099dc4fbSDavid Sterba 	skb_reserve(skb, 2);
35959ae1d12SJohannes Berg 	skb_put_data(skb, data, length);
360099dc4fbSDavid Sterba 
361099dc4fbSDavid Sterba 	return skb;
362099dc4fbSDavid Sterba }
363099dc4fbSDavid Sterba 
364099dc4fbSDavid Sterba void ipwireless_network_packet_received(struct ipw_network *network,
365099dc4fbSDavid Sterba 					unsigned int channel_idx,
366099dc4fbSDavid Sterba 					unsigned char *data,
367099dc4fbSDavid Sterba 					unsigned int length)
368099dc4fbSDavid Sterba {
369099dc4fbSDavid Sterba 	int i;
370099dc4fbSDavid Sterba 	unsigned long flags;
371099dc4fbSDavid Sterba 
372099dc4fbSDavid Sterba 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
373099dc4fbSDavid Sterba 		struct ipw_tty *tty = network->associated_ttys[channel_idx][i];
374099dc4fbSDavid Sterba 
375a51f4124SDavid Sterba 		if (!tty)
376a51f4124SDavid Sterba 			continue;
377a51f4124SDavid Sterba 
378099dc4fbSDavid Sterba 		/*
379099dc4fbSDavid Sterba 		 * If it's associated with a tty (other than the RAS channel
380099dc4fbSDavid Sterba 		 * when we're online), then send the data to that tty.  The RAS
381099dc4fbSDavid Sterba 		 * channel's data is handled above - it always goes through
382099dc4fbSDavid Sterba 		 * ppp_generic.
383099dc4fbSDavid Sterba 		 */
384a51f4124SDavid Sterba 		if (channel_idx == IPW_CHANNEL_RAS
385099dc4fbSDavid Sterba 				&& (network->ras_control_lines &
386099dc4fbSDavid Sterba 					IPW_CONTROL_LINE_DCD) != 0
387099dc4fbSDavid Sterba 				&& ipwireless_tty_is_modem(tty)) {
388099dc4fbSDavid Sterba 			/*
389099dc4fbSDavid Sterba 			 * If data came in on the RAS channel and this tty is
390099dc4fbSDavid Sterba 			 * the modem tty, and we are online, then we send it to
391099dc4fbSDavid Sterba 			 * the PPP layer.
392099dc4fbSDavid Sterba 			 */
393099dc4fbSDavid Sterba 			mutex_lock(&network->close_lock);
39463c4dbd1SDavid Sterba 			spin_lock_irqsave(&network->lock, flags);
395099dc4fbSDavid Sterba 			if (network->ppp_channel != NULL) {
396099dc4fbSDavid Sterba 				struct sk_buff *skb;
397099dc4fbSDavid Sterba 
39863c4dbd1SDavid Sterba 				spin_unlock_irqrestore(&network->lock,
399099dc4fbSDavid Sterba 						flags);
400099dc4fbSDavid Sterba 
401099dc4fbSDavid Sterba 				/* Send the data to the ppp_generic module. */
402099dc4fbSDavid Sterba 				skb = ipw_packet_received_skb(data, length);
403d1519e23SAlan Cox 				if (skb)
404099dc4fbSDavid Sterba 					ppp_input(network->ppp_channel, skb);
405099dc4fbSDavid Sterba 			} else
40663c4dbd1SDavid Sterba 				spin_unlock_irqrestore(&network->lock,
407099dc4fbSDavid Sterba 						flags);
408099dc4fbSDavid Sterba 			mutex_unlock(&network->close_lock);
409099dc4fbSDavid Sterba 		}
410099dc4fbSDavid Sterba 		/* Otherwise we send it out the tty. */
411099dc4fbSDavid Sterba 		else
412099dc4fbSDavid Sterba 			ipwireless_tty_received(tty, data, length);
413099dc4fbSDavid Sterba 	}
414099dc4fbSDavid Sterba }
415099dc4fbSDavid Sterba 
416099dc4fbSDavid Sterba struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw)
417099dc4fbSDavid Sterba {
418099dc4fbSDavid Sterba 	struct ipw_network *network =
41913eac05bSJia-Ju Bai 		kzalloc(sizeof(struct ipw_network), GFP_KERNEL);
420099dc4fbSDavid Sterba 
421099dc4fbSDavid Sterba 	if (!network)
422099dc4fbSDavid Sterba 		return NULL;
423099dc4fbSDavid Sterba 
42463c4dbd1SDavid Sterba 	spin_lock_init(&network->lock);
425099dc4fbSDavid Sterba 	mutex_init(&network->close_lock);
426099dc4fbSDavid Sterba 
427099dc4fbSDavid Sterba 	network->hardware = hw;
428099dc4fbSDavid Sterba 
429099dc4fbSDavid Sterba 	INIT_WORK(&network->work_go_online, do_go_online);
430099dc4fbSDavid Sterba 	INIT_WORK(&network->work_go_offline, do_go_offline);
431099dc4fbSDavid Sterba 
432099dc4fbSDavid Sterba 	ipwireless_associate_network(hw, network);
433099dc4fbSDavid Sterba 
434099dc4fbSDavid Sterba 	return network;
435099dc4fbSDavid Sterba }
436099dc4fbSDavid Sterba 
437099dc4fbSDavid Sterba void ipwireless_network_free(struct ipw_network *network)
438099dc4fbSDavid Sterba {
439099dc4fbSDavid Sterba 	network->shutting_down = 1;
440099dc4fbSDavid Sterba 
441099dc4fbSDavid Sterba 	ipwireless_ppp_close(network);
44243829731STejun Heo 	flush_work(&network->work_go_online);
44343829731STejun Heo 	flush_work(&network->work_go_offline);
444099dc4fbSDavid Sterba 
445099dc4fbSDavid Sterba 	ipwireless_stop_interrupts(network->hardware);
446099dc4fbSDavid Sterba 	ipwireless_associate_network(network->hardware, NULL);
447099dc4fbSDavid Sterba 
448099dc4fbSDavid Sterba 	kfree(network);
449099dc4fbSDavid Sterba }
450099dc4fbSDavid Sterba 
451099dc4fbSDavid Sterba void ipwireless_associate_network_tty(struct ipw_network *network,
452099dc4fbSDavid Sterba 				      unsigned int channel_idx,
453099dc4fbSDavid Sterba 				      struct ipw_tty *tty)
454099dc4fbSDavid Sterba {
455099dc4fbSDavid Sterba 	int i;
456099dc4fbSDavid Sterba 
457099dc4fbSDavid Sterba 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
458099dc4fbSDavid Sterba 		if (network->associated_ttys[channel_idx][i] == NULL) {
459099dc4fbSDavid Sterba 			network->associated_ttys[channel_idx][i] = tty;
460099dc4fbSDavid Sterba 			break;
461099dc4fbSDavid Sterba 		}
462099dc4fbSDavid Sterba }
463099dc4fbSDavid Sterba 
464099dc4fbSDavid Sterba void ipwireless_disassociate_network_ttys(struct ipw_network *network,
465099dc4fbSDavid Sterba 					  unsigned int channel_idx)
466099dc4fbSDavid Sterba {
467099dc4fbSDavid Sterba 	int i;
468099dc4fbSDavid Sterba 
469099dc4fbSDavid Sterba 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
470099dc4fbSDavid Sterba 		network->associated_ttys[channel_idx][i] = NULL;
471099dc4fbSDavid Sterba }
472099dc4fbSDavid Sterba 
473099dc4fbSDavid Sterba void ipwireless_ppp_open(struct ipw_network *network)
474099dc4fbSDavid Sterba {
475099dc4fbSDavid Sterba 	if (ipwireless_debug)
476099dc4fbSDavid Sterba 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n");
477099dc4fbSDavid Sterba 	schedule_work(&network->work_go_online);
478099dc4fbSDavid Sterba }
479099dc4fbSDavid Sterba 
480099dc4fbSDavid Sterba void ipwireless_ppp_close(struct ipw_network *network)
481099dc4fbSDavid Sterba {
482099dc4fbSDavid Sterba 	/* Disconnect from the wireless network. */
483099dc4fbSDavid Sterba 	if (ipwireless_debug)
484099dc4fbSDavid Sterba 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n");
485099dc4fbSDavid Sterba 	schedule_work(&network->work_go_offline);
486099dc4fbSDavid Sterba }
487099dc4fbSDavid Sterba 
488099dc4fbSDavid Sterba int ipwireless_ppp_channel_index(struct ipw_network *network)
489099dc4fbSDavid Sterba {
490099dc4fbSDavid Sterba 	int ret = -1;
491099dc4fbSDavid Sterba 	unsigned long flags;
492099dc4fbSDavid Sterba 
49363c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
494099dc4fbSDavid Sterba 	if (network->ppp_channel != NULL)
495099dc4fbSDavid Sterba 		ret = ppp_channel_index(network->ppp_channel);
49663c4dbd1SDavid Sterba 	spin_unlock_irqrestore(&network->lock, flags);
497099dc4fbSDavid Sterba 
498099dc4fbSDavid Sterba 	return ret;
499099dc4fbSDavid Sterba }
500099dc4fbSDavid Sterba 
501099dc4fbSDavid Sterba int ipwireless_ppp_unit_number(struct ipw_network *network)
502099dc4fbSDavid Sterba {
503099dc4fbSDavid Sterba 	int ret = -1;
504099dc4fbSDavid Sterba 	unsigned long flags;
505099dc4fbSDavid Sterba 
50663c4dbd1SDavid Sterba 	spin_lock_irqsave(&network->lock, flags);
507099dc4fbSDavid Sterba 	if (network->ppp_channel != NULL)
508099dc4fbSDavid Sterba 		ret = ppp_unit_number(network->ppp_channel);
50963c4dbd1SDavid Sterba 	spin_unlock_irqrestore(&network->lock, flags);
510099dc4fbSDavid Sterba 
511099dc4fbSDavid Sterba 	return ret;
512099dc4fbSDavid Sterba }
513a0138692SDavid Sterba 
514a0138692SDavid Sterba int ipwireless_ppp_mru(const struct ipw_network *network)
515a0138692SDavid Sterba {
516a0138692SDavid Sterba 	return network->mru;
517a0138692SDavid Sterba }
518