xref: /openbmc/linux/drivers/tty/ipwireless/network.c (revision cbecf716ca618fd44feda6bd9a64a8179d031fc5)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2282361a0SGreg Kroah-Hartman /*
3282361a0SGreg Kroah-Hartman  * IPWireless 3G PCMCIA Network Driver
4282361a0SGreg Kroah-Hartman  *
5282361a0SGreg Kroah-Hartman  * Original code
6282361a0SGreg Kroah-Hartman  *   by Stephen Blackheath <stephen@blacksapphire.com>,
7282361a0SGreg Kroah-Hartman  *      Ben Martel <benm@symmetric.co.nz>
8282361a0SGreg Kroah-Hartman  *
9282361a0SGreg Kroah-Hartman  * Copyrighted as follows:
10282361a0SGreg Kroah-Hartman  *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
11282361a0SGreg Kroah-Hartman  *
12282361a0SGreg Kroah-Hartman  * Various driver changes and rewrites, port to new kernels
13282361a0SGreg Kroah-Hartman  *   Copyright (C) 2006-2007 Jiri Kosina
14282361a0SGreg Kroah-Hartman  *
15282361a0SGreg Kroah-Hartman  * Misc code cleanups and updates
16282361a0SGreg Kroah-Hartman  *   Copyright (C) 2007 David Sterba
17282361a0SGreg Kroah-Hartman  */
18282361a0SGreg Kroah-Hartman 
19282361a0SGreg Kroah-Hartman #include <linux/interrupt.h>
20282361a0SGreg Kroah-Hartman #include <linux/kernel.h>
21282361a0SGreg Kroah-Hartman #include <linux/mutex.h>
22282361a0SGreg Kroah-Hartman #include <linux/netdevice.h>
23282361a0SGreg Kroah-Hartman #include <linux/ppp_channel.h>
24282361a0SGreg Kroah-Hartman #include <linux/ppp_defs.h>
25282361a0SGreg Kroah-Hartman #include <linux/slab.h>
264b32da2bSPaul Mackerras #include <linux/ppp-ioctl.h>
27282361a0SGreg Kroah-Hartman #include <linux/skbuff.h>
28282361a0SGreg Kroah-Hartman 
29282361a0SGreg Kroah-Hartman #include "network.h"
30282361a0SGreg Kroah-Hartman #include "hardware.h"
31282361a0SGreg Kroah-Hartman #include "main.h"
32282361a0SGreg Kroah-Hartman #include "tty.h"
33282361a0SGreg Kroah-Hartman 
34282361a0SGreg Kroah-Hartman #define MAX_ASSOCIATED_TTYS 2
35282361a0SGreg Kroah-Hartman 
36282361a0SGreg Kroah-Hartman #define SC_RCV_BITS     (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
37282361a0SGreg Kroah-Hartman 
38282361a0SGreg Kroah-Hartman struct ipw_network {
39282361a0SGreg Kroah-Hartman 	/* Hardware context, used for calls to hardware layer. */
40282361a0SGreg Kroah-Hartman 	struct ipw_hardware *hardware;
41282361a0SGreg Kroah-Hartman 	/* Context for kernel 'generic_ppp' functionality */
42282361a0SGreg Kroah-Hartman 	struct ppp_channel *ppp_channel;
43282361a0SGreg Kroah-Hartman 	/* tty context connected with IPW console */
44282361a0SGreg Kroah-Hartman 	struct ipw_tty *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS];
45282361a0SGreg Kroah-Hartman 	/* True if ppp needs waking up once we're ready to xmit */
46282361a0SGreg Kroah-Hartman 	int ppp_blocked;
47282361a0SGreg Kroah-Hartman 	/* Number of packets queued up in hardware module. */
48282361a0SGreg Kroah-Hartman 	int outgoing_packets_queued;
49282361a0SGreg Kroah-Hartman 	/* Spinlock to avoid interrupts during shutdown */
50282361a0SGreg Kroah-Hartman 	spinlock_t lock;
51282361a0SGreg Kroah-Hartman 	struct mutex close_lock;
52282361a0SGreg Kroah-Hartman 
53282361a0SGreg Kroah-Hartman 	/* PPP ioctl data, not actually used anywere */
54282361a0SGreg Kroah-Hartman 	unsigned int flags;
55282361a0SGreg Kroah-Hartman 	unsigned int rbits;
56282361a0SGreg Kroah-Hartman 	u32 xaccm[8];
57282361a0SGreg Kroah-Hartman 	u32 raccm;
58282361a0SGreg Kroah-Hartman 	int mru;
59282361a0SGreg Kroah-Hartman 
60282361a0SGreg Kroah-Hartman 	int shutting_down;
61282361a0SGreg Kroah-Hartman 	unsigned int ras_control_lines;
62282361a0SGreg Kroah-Hartman 
63282361a0SGreg Kroah-Hartman 	struct work_struct work_go_online;
64282361a0SGreg Kroah-Hartman 	struct work_struct work_go_offline;
65282361a0SGreg Kroah-Hartman };
66282361a0SGreg Kroah-Hartman 
notify_packet_sent(void * callback_data,unsigned int packet_length)67282361a0SGreg Kroah-Hartman static void notify_packet_sent(void *callback_data, unsigned int packet_length)
68282361a0SGreg Kroah-Hartman {
69282361a0SGreg Kroah-Hartman 	struct ipw_network *network = callback_data;
70282361a0SGreg Kroah-Hartman 	unsigned long flags;
71282361a0SGreg Kroah-Hartman 
72282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
73282361a0SGreg Kroah-Hartman 	network->outgoing_packets_queued--;
74282361a0SGreg Kroah-Hartman 	if (network->ppp_channel != NULL) {
75282361a0SGreg Kroah-Hartman 		if (network->ppp_blocked) {
76282361a0SGreg Kroah-Hartman 			network->ppp_blocked = 0;
77282361a0SGreg Kroah-Hartman 			spin_unlock_irqrestore(&network->lock, flags);
78282361a0SGreg Kroah-Hartman 			ppp_output_wakeup(network->ppp_channel);
79282361a0SGreg Kroah-Hartman 			if (ipwireless_debug)
80282361a0SGreg Kroah-Hartman 				printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
81282361a0SGreg Kroah-Hartman 				       ": ppp unblocked\n");
82282361a0SGreg Kroah-Hartman 		} else
83282361a0SGreg Kroah-Hartman 			spin_unlock_irqrestore(&network->lock, flags);
84282361a0SGreg Kroah-Hartman 	} else
85282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
86282361a0SGreg Kroah-Hartman }
87282361a0SGreg Kroah-Hartman 
88282361a0SGreg Kroah-Hartman /*
89282361a0SGreg Kroah-Hartman  * Called by the ppp system when it has a packet to send to the hardware.
90282361a0SGreg Kroah-Hartman  */
ipwireless_ppp_start_xmit(struct ppp_channel * ppp_channel,struct sk_buff * skb)91282361a0SGreg Kroah-Hartman static int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel,
92282361a0SGreg Kroah-Hartman 				     struct sk_buff *skb)
93282361a0SGreg Kroah-Hartman {
94282361a0SGreg Kroah-Hartman 	struct ipw_network *network = ppp_channel->private;
95282361a0SGreg Kroah-Hartman 	unsigned long flags;
96282361a0SGreg Kroah-Hartman 
97282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
98282361a0SGreg Kroah-Hartman 	if (network->outgoing_packets_queued < ipwireless_out_queue) {
99282361a0SGreg Kroah-Hartman 		unsigned char *buf;
100282361a0SGreg Kroah-Hartman 		static unsigned char header[] = {
101282361a0SGreg Kroah-Hartman 			PPP_ALLSTATIONS, /* 0xff */
102282361a0SGreg Kroah-Hartman 			PPP_UI,		 /* 0x03 */
103282361a0SGreg Kroah-Hartman 		};
104282361a0SGreg Kroah-Hartman 		int ret;
105282361a0SGreg Kroah-Hartman 
106282361a0SGreg Kroah-Hartman 		network->outgoing_packets_queued++;
107282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
108282361a0SGreg Kroah-Hartman 
109282361a0SGreg Kroah-Hartman 		/*
110282361a0SGreg Kroah-Hartman 		 * If we have the requested amount of headroom in the skb we
111282361a0SGreg Kroah-Hartman 		 * were handed, then we can add the header efficiently.
112282361a0SGreg Kroah-Hartman 		 */
113282361a0SGreg Kroah-Hartman 		if (skb_headroom(skb) >= 2) {
114282361a0SGreg Kroah-Hartman 			memcpy(skb_push(skb, 2), header, 2);
115282361a0SGreg Kroah-Hartman 			ret = ipwireless_send_packet(network->hardware,
116282361a0SGreg Kroah-Hartman 					       IPW_CHANNEL_RAS, skb->data,
117282361a0SGreg Kroah-Hartman 					       skb->len,
118282361a0SGreg Kroah-Hartman 					       notify_packet_sent,
119282361a0SGreg Kroah-Hartman 					       network);
120*db332356STong Zhang 			if (ret < 0) {
121282361a0SGreg Kroah-Hartman 				skb_pull(skb, 2);
122282361a0SGreg Kroah-Hartman 				return 0;
123282361a0SGreg Kroah-Hartman 			}
124282361a0SGreg Kroah-Hartman 		} else {
125282361a0SGreg Kroah-Hartman 			/* Otherwise (rarely) we do it inefficiently. */
126282361a0SGreg Kroah-Hartman 			buf = kmalloc(skb->len + 2, GFP_ATOMIC);
127282361a0SGreg Kroah-Hartman 			if (!buf)
128282361a0SGreg Kroah-Hartman 				return 0;
129282361a0SGreg Kroah-Hartman 			memcpy(buf + 2, skb->data, skb->len);
130282361a0SGreg Kroah-Hartman 			memcpy(buf, header, 2);
131282361a0SGreg Kroah-Hartman 			ret = ipwireless_send_packet(network->hardware,
132282361a0SGreg Kroah-Hartman 					       IPW_CHANNEL_RAS, buf,
133282361a0SGreg Kroah-Hartman 					       skb->len + 2,
134282361a0SGreg Kroah-Hartman 					       notify_packet_sent,
135282361a0SGreg Kroah-Hartman 					       network);
136282361a0SGreg Kroah-Hartman 			kfree(buf);
137*db332356STong Zhang 			if (ret < 0)
138282361a0SGreg Kroah-Hartman 				return 0;
139282361a0SGreg Kroah-Hartman 		}
140282361a0SGreg Kroah-Hartman 		kfree_skb(skb);
141282361a0SGreg Kroah-Hartman 		return 1;
142282361a0SGreg Kroah-Hartman 	} else {
143282361a0SGreg Kroah-Hartman 		/*
144282361a0SGreg Kroah-Hartman 		 * Otherwise reject the packet, and flag that the ppp system
145282361a0SGreg Kroah-Hartman 		 * needs to be unblocked once we are ready to send.
146282361a0SGreg Kroah-Hartman 		 */
147282361a0SGreg Kroah-Hartman 		network->ppp_blocked = 1;
148282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
149282361a0SGreg Kroah-Hartman 		if (ipwireless_debug)
150282361a0SGreg Kroah-Hartman 			printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": ppp blocked\n");
151282361a0SGreg Kroah-Hartman 		return 0;
152282361a0SGreg Kroah-Hartman 	}
153282361a0SGreg Kroah-Hartman }
154282361a0SGreg Kroah-Hartman 
155282361a0SGreg Kroah-Hartman /* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */
ipwireless_ppp_ioctl(struct ppp_channel * ppp_channel,unsigned int cmd,unsigned long arg)156282361a0SGreg Kroah-Hartman static int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel,
157282361a0SGreg Kroah-Hartman 				unsigned int cmd, unsigned long arg)
158282361a0SGreg Kroah-Hartman {
159282361a0SGreg Kroah-Hartman 	struct ipw_network *network = ppp_channel->private;
160282361a0SGreg Kroah-Hartman 	int err, val;
161282361a0SGreg Kroah-Hartman 	u32 accm[8];
162282361a0SGreg Kroah-Hartman 	int __user *user_arg = (int __user *) arg;
163282361a0SGreg Kroah-Hartman 
164282361a0SGreg Kroah-Hartman 	err = -EFAULT;
165282361a0SGreg Kroah-Hartman 	switch (cmd) {
166282361a0SGreg Kroah-Hartman 	case PPPIOCGFLAGS:
167282361a0SGreg Kroah-Hartman 		val = network->flags | network->rbits;
168282361a0SGreg Kroah-Hartman 		if (put_user(val, user_arg))
169282361a0SGreg Kroah-Hartman 			break;
170282361a0SGreg Kroah-Hartman 		err = 0;
171282361a0SGreg Kroah-Hartman 		break;
172282361a0SGreg Kroah-Hartman 
173282361a0SGreg Kroah-Hartman 	case PPPIOCSFLAGS:
174282361a0SGreg Kroah-Hartman 		if (get_user(val, user_arg))
175282361a0SGreg Kroah-Hartman 			break;
176282361a0SGreg Kroah-Hartman 		network->flags = val & ~SC_RCV_BITS;
177282361a0SGreg Kroah-Hartman 		network->rbits = val & SC_RCV_BITS;
178282361a0SGreg Kroah-Hartman 		err = 0;
179282361a0SGreg Kroah-Hartman 		break;
180282361a0SGreg Kroah-Hartman 
181282361a0SGreg Kroah-Hartman 	case PPPIOCGASYNCMAP:
182282361a0SGreg Kroah-Hartman 		if (put_user(network->xaccm[0], user_arg))
183282361a0SGreg Kroah-Hartman 			break;
184282361a0SGreg Kroah-Hartman 		err = 0;
185282361a0SGreg Kroah-Hartman 		break;
186282361a0SGreg Kroah-Hartman 
187282361a0SGreg Kroah-Hartman 	case PPPIOCSASYNCMAP:
188282361a0SGreg Kroah-Hartman 		if (get_user(network->xaccm[0], user_arg))
189282361a0SGreg Kroah-Hartman 			break;
190282361a0SGreg Kroah-Hartman 		err = 0;
191282361a0SGreg Kroah-Hartman 		break;
192282361a0SGreg Kroah-Hartman 
193282361a0SGreg Kroah-Hartman 	case PPPIOCGRASYNCMAP:
194282361a0SGreg Kroah-Hartman 		if (put_user(network->raccm, user_arg))
195282361a0SGreg Kroah-Hartman 			break;
196282361a0SGreg Kroah-Hartman 		err = 0;
197282361a0SGreg Kroah-Hartman 		break;
198282361a0SGreg Kroah-Hartman 
199282361a0SGreg Kroah-Hartman 	case PPPIOCSRASYNCMAP:
200282361a0SGreg Kroah-Hartman 		if (get_user(network->raccm, user_arg))
201282361a0SGreg Kroah-Hartman 			break;
202282361a0SGreg Kroah-Hartman 		err = 0;
203282361a0SGreg Kroah-Hartman 		break;
204282361a0SGreg Kroah-Hartman 
205282361a0SGreg Kroah-Hartman 	case PPPIOCGXASYNCMAP:
206282361a0SGreg Kroah-Hartman 		if (copy_to_user((void __user *) arg, network->xaccm,
207282361a0SGreg Kroah-Hartman 					sizeof(network->xaccm)))
208282361a0SGreg Kroah-Hartman 			break;
209282361a0SGreg Kroah-Hartman 		err = 0;
210282361a0SGreg Kroah-Hartman 		break;
211282361a0SGreg Kroah-Hartman 
212282361a0SGreg Kroah-Hartman 	case PPPIOCSXASYNCMAP:
213282361a0SGreg Kroah-Hartman 		if (copy_from_user(accm, (void __user *) arg, sizeof(accm)))
214282361a0SGreg Kroah-Hartman 			break;
215282361a0SGreg Kroah-Hartman 		accm[2] &= ~0x40000000U;	/* can't escape 0x5e */
216282361a0SGreg Kroah-Hartman 		accm[3] |= 0x60000000U;	/* must escape 0x7d, 0x7e */
217282361a0SGreg Kroah-Hartman 		memcpy(network->xaccm, accm, sizeof(network->xaccm));
218282361a0SGreg Kroah-Hartman 		err = 0;
219282361a0SGreg Kroah-Hartman 		break;
220282361a0SGreg Kroah-Hartman 
221282361a0SGreg Kroah-Hartman 	case PPPIOCGMRU:
222282361a0SGreg Kroah-Hartman 		if (put_user(network->mru, user_arg))
223282361a0SGreg Kroah-Hartman 			break;
224282361a0SGreg Kroah-Hartman 		err = 0;
225282361a0SGreg Kroah-Hartman 		break;
226282361a0SGreg Kroah-Hartman 
227282361a0SGreg Kroah-Hartman 	case PPPIOCSMRU:
228282361a0SGreg Kroah-Hartman 		if (get_user(val, user_arg))
229282361a0SGreg Kroah-Hartman 			break;
230282361a0SGreg Kroah-Hartman 		if (val < PPP_MRU)
231282361a0SGreg Kroah-Hartman 			val = PPP_MRU;
232282361a0SGreg Kroah-Hartman 		network->mru = val;
233282361a0SGreg Kroah-Hartman 		err = 0;
234282361a0SGreg Kroah-Hartman 		break;
235282361a0SGreg Kroah-Hartman 
236282361a0SGreg Kroah-Hartman 	default:
237282361a0SGreg Kroah-Hartman 		err = -ENOTTY;
238282361a0SGreg Kroah-Hartman 	}
239282361a0SGreg Kroah-Hartman 
240282361a0SGreg Kroah-Hartman 	return err;
241282361a0SGreg Kroah-Hartman }
242282361a0SGreg Kroah-Hartman 
243282361a0SGreg Kroah-Hartman static const struct ppp_channel_ops ipwireless_ppp_channel_ops = {
244282361a0SGreg Kroah-Hartman 	.start_xmit = ipwireless_ppp_start_xmit,
245282361a0SGreg Kroah-Hartman 	.ioctl      = ipwireless_ppp_ioctl
246282361a0SGreg Kroah-Hartman };
247282361a0SGreg Kroah-Hartman 
do_go_online(struct work_struct * work_go_online)248282361a0SGreg Kroah-Hartman static void do_go_online(struct work_struct *work_go_online)
249282361a0SGreg Kroah-Hartman {
250282361a0SGreg Kroah-Hartman 	struct ipw_network *network =
251282361a0SGreg Kroah-Hartman 		container_of(work_go_online, struct ipw_network,
252282361a0SGreg Kroah-Hartman 				work_go_online);
253282361a0SGreg Kroah-Hartman 	unsigned long flags;
254282361a0SGreg Kroah-Hartman 
255282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
256282361a0SGreg Kroah-Hartman 	if (!network->ppp_channel) {
257282361a0SGreg Kroah-Hartman 		struct ppp_channel *channel;
258282361a0SGreg Kroah-Hartman 
259282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
260282361a0SGreg Kroah-Hartman 		channel = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL);
261282361a0SGreg Kroah-Hartman 		if (!channel) {
262282361a0SGreg Kroah-Hartman 			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
263282361a0SGreg Kroah-Hartman 					": unable to allocate PPP channel\n");
264282361a0SGreg Kroah-Hartman 			return;
265282361a0SGreg Kroah-Hartman 		}
266282361a0SGreg Kroah-Hartman 		channel->private = network;
267282361a0SGreg Kroah-Hartman 		channel->mtu = 16384;	/* Wild guess */
268282361a0SGreg Kroah-Hartman 		channel->hdrlen = 2;
269282361a0SGreg Kroah-Hartman 		channel->ops = &ipwireless_ppp_channel_ops;
270282361a0SGreg Kroah-Hartman 
271282361a0SGreg Kroah-Hartman 		network->flags = 0;
272282361a0SGreg Kroah-Hartman 		network->rbits = 0;
273282361a0SGreg Kroah-Hartman 		network->mru = PPP_MRU;
274282361a0SGreg Kroah-Hartman 		memset(network->xaccm, 0, sizeof(network->xaccm));
275282361a0SGreg Kroah-Hartman 		network->xaccm[0] = ~0U;
276282361a0SGreg Kroah-Hartman 		network->xaccm[3] = 0x60000000U;
277282361a0SGreg Kroah-Hartman 		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 		}
284282361a0SGreg Kroah-Hartman 		spin_lock_irqsave(&network->lock, flags);
285282361a0SGreg Kroah-Hartman 		network->ppp_channel = channel;
286282361a0SGreg Kroah-Hartman 	}
287282361a0SGreg Kroah-Hartman 	spin_unlock_irqrestore(&network->lock, flags);
288282361a0SGreg Kroah-Hartman }
289282361a0SGreg Kroah-Hartman 
do_go_offline(struct work_struct * work_go_offline)290282361a0SGreg Kroah-Hartman static void do_go_offline(struct work_struct *work_go_offline)
291282361a0SGreg Kroah-Hartman {
292282361a0SGreg Kroah-Hartman 	struct ipw_network *network =
293282361a0SGreg Kroah-Hartman 		container_of(work_go_offline, struct ipw_network,
294282361a0SGreg Kroah-Hartman 				work_go_offline);
295282361a0SGreg Kroah-Hartman 	unsigned long flags;
296282361a0SGreg Kroah-Hartman 
297282361a0SGreg Kroah-Hartman 	mutex_lock(&network->close_lock);
298282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
299282361a0SGreg Kroah-Hartman 	if (network->ppp_channel != NULL) {
300282361a0SGreg Kroah-Hartman 		struct ppp_channel *channel = network->ppp_channel;
301282361a0SGreg Kroah-Hartman 
302282361a0SGreg Kroah-Hartman 		network->ppp_channel = NULL;
303282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
304282361a0SGreg Kroah-Hartman 		mutex_unlock(&network->close_lock);
305282361a0SGreg Kroah-Hartman 		ppp_unregister_channel(channel);
306282361a0SGreg Kroah-Hartman 	} else {
307282361a0SGreg Kroah-Hartman 		spin_unlock_irqrestore(&network->lock, flags);
308282361a0SGreg Kroah-Hartman 		mutex_unlock(&network->close_lock);
309282361a0SGreg Kroah-Hartman 	}
310282361a0SGreg Kroah-Hartman }
311282361a0SGreg Kroah-Hartman 
ipwireless_network_notify_control_line_change(struct ipw_network * network,unsigned int channel_idx,unsigned int control_lines,unsigned int changed_mask)312282361a0SGreg Kroah-Hartman void ipwireless_network_notify_control_line_change(struct ipw_network *network,
313282361a0SGreg Kroah-Hartman 						   unsigned int channel_idx,
314282361a0SGreg Kroah-Hartman 						   unsigned int control_lines,
315282361a0SGreg Kroah-Hartman 						   unsigned int changed_mask)
316282361a0SGreg Kroah-Hartman {
317282361a0SGreg Kroah-Hartman 	int i;
318282361a0SGreg Kroah-Hartman 
319282361a0SGreg Kroah-Hartman 	if (channel_idx == IPW_CHANNEL_RAS)
320282361a0SGreg Kroah-Hartman 		network->ras_control_lines = control_lines;
321282361a0SGreg Kroah-Hartman 
322282361a0SGreg Kroah-Hartman 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
323282361a0SGreg Kroah-Hartman 		struct ipw_tty *tty =
324282361a0SGreg Kroah-Hartman 			network->associated_ttys[channel_idx][i];
325282361a0SGreg Kroah-Hartman 
326282361a0SGreg Kroah-Hartman 		/*
327282361a0SGreg Kroah-Hartman 		 * If it's associated with a tty (other than the RAS channel
328282361a0SGreg Kroah-Hartman 		 * when we're online), then send the data to that tty.  The RAS
329282361a0SGreg Kroah-Hartman 		 * channel's data is handled above - it always goes through
330282361a0SGreg Kroah-Hartman 		 * ppp_generic.
331282361a0SGreg Kroah-Hartman 		 */
332282361a0SGreg Kroah-Hartman 		if (tty)
333282361a0SGreg Kroah-Hartman 			ipwireless_tty_notify_control_line_change(tty,
334282361a0SGreg Kroah-Hartman 								  channel_idx,
335282361a0SGreg Kroah-Hartman 								  control_lines,
336282361a0SGreg Kroah-Hartman 								  changed_mask);
337282361a0SGreg Kroah-Hartman 	}
338282361a0SGreg Kroah-Hartman }
339282361a0SGreg Kroah-Hartman 
340282361a0SGreg Kroah-Hartman /*
341282361a0SGreg Kroah-Hartman  * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI)
342282361a0SGreg Kroah-Hartman  * bytes, which are required on sent packet, but not always present on received
343282361a0SGreg Kroah-Hartman  * packets
344282361a0SGreg Kroah-Hartman  */
ipw_packet_received_skb(unsigned char * data,unsigned int length)345282361a0SGreg Kroah-Hartman static struct sk_buff *ipw_packet_received_skb(unsigned char *data,
346282361a0SGreg Kroah-Hartman 					       unsigned int length)
347282361a0SGreg Kroah-Hartman {
348282361a0SGreg Kroah-Hartman 	struct sk_buff *skb;
349282361a0SGreg Kroah-Hartman 
350282361a0SGreg Kroah-Hartman 	if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) {
351282361a0SGreg Kroah-Hartman 		length -= 2;
352282361a0SGreg Kroah-Hartman 		data += 2;
353282361a0SGreg Kroah-Hartman 	}
354282361a0SGreg Kroah-Hartman 
355282361a0SGreg Kroah-Hartman 	skb = dev_alloc_skb(length + 4);
356d1519e23SAlan Cox 	if (skb == NULL)
357d1519e23SAlan Cox 		return NULL;
358282361a0SGreg Kroah-Hartman 	skb_reserve(skb, 2);
35959ae1d12SJohannes Berg 	skb_put_data(skb, data, length);
360282361a0SGreg Kroah-Hartman 
361282361a0SGreg Kroah-Hartman 	return skb;
362282361a0SGreg Kroah-Hartman }
363282361a0SGreg Kroah-Hartman 
ipwireless_network_packet_received(struct ipw_network * network,unsigned int channel_idx,unsigned char * data,unsigned int length)364282361a0SGreg Kroah-Hartman void ipwireless_network_packet_received(struct ipw_network *network,
365282361a0SGreg Kroah-Hartman 					unsigned int channel_idx,
366282361a0SGreg Kroah-Hartman 					unsigned char *data,
367282361a0SGreg Kroah-Hartman 					unsigned int length)
368282361a0SGreg Kroah-Hartman {
369282361a0SGreg Kroah-Hartman 	int i;
370282361a0SGreg Kroah-Hartman 	unsigned long flags;
371282361a0SGreg Kroah-Hartman 
372282361a0SGreg Kroah-Hartman 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
373282361a0SGreg Kroah-Hartman 		struct ipw_tty *tty = network->associated_ttys[channel_idx][i];
374282361a0SGreg Kroah-Hartman 
375282361a0SGreg Kroah-Hartman 		if (!tty)
376282361a0SGreg Kroah-Hartman 			continue;
377282361a0SGreg Kroah-Hartman 
378282361a0SGreg Kroah-Hartman 		/*
379282361a0SGreg Kroah-Hartman 		 * If it's associated with a tty (other than the RAS channel
380282361a0SGreg Kroah-Hartman 		 * when we're online), then send the data to that tty.  The RAS
381282361a0SGreg Kroah-Hartman 		 * channel's data is handled above - it always goes through
382282361a0SGreg Kroah-Hartman 		 * ppp_generic.
383282361a0SGreg Kroah-Hartman 		 */
384282361a0SGreg Kroah-Hartman 		if (channel_idx == IPW_CHANNEL_RAS
385282361a0SGreg Kroah-Hartman 				&& (network->ras_control_lines &
386282361a0SGreg Kroah-Hartman 					IPW_CONTROL_LINE_DCD) != 0
387282361a0SGreg Kroah-Hartman 				&& ipwireless_tty_is_modem(tty)) {
388282361a0SGreg Kroah-Hartman 			/*
389282361a0SGreg Kroah-Hartman 			 * If data came in on the RAS channel and this tty is
390282361a0SGreg Kroah-Hartman 			 * the modem tty, and we are online, then we send it to
391282361a0SGreg Kroah-Hartman 			 * the PPP layer.
392282361a0SGreg Kroah-Hartman 			 */
393282361a0SGreg Kroah-Hartman 			mutex_lock(&network->close_lock);
394282361a0SGreg Kroah-Hartman 			spin_lock_irqsave(&network->lock, flags);
395282361a0SGreg Kroah-Hartman 			if (network->ppp_channel != NULL) {
396282361a0SGreg Kroah-Hartman 				struct sk_buff *skb;
397282361a0SGreg Kroah-Hartman 
398282361a0SGreg Kroah-Hartman 				spin_unlock_irqrestore(&network->lock,
399282361a0SGreg Kroah-Hartman 						flags);
400282361a0SGreg Kroah-Hartman 
401282361a0SGreg Kroah-Hartman 				/* Send the data to the ppp_generic module. */
402282361a0SGreg Kroah-Hartman 				skb = ipw_packet_received_skb(data, length);
403d1519e23SAlan Cox 				if (skb)
404282361a0SGreg Kroah-Hartman 					ppp_input(network->ppp_channel, skb);
405282361a0SGreg Kroah-Hartman 			} else
406282361a0SGreg Kroah-Hartman 				spin_unlock_irqrestore(&network->lock,
407282361a0SGreg Kroah-Hartman 						flags);
408282361a0SGreg Kroah-Hartman 			mutex_unlock(&network->close_lock);
409282361a0SGreg Kroah-Hartman 		}
410282361a0SGreg Kroah-Hartman 		/* Otherwise we send it out the tty. */
411282361a0SGreg Kroah-Hartman 		else
412282361a0SGreg Kroah-Hartman 			ipwireless_tty_received(tty, data, length);
413282361a0SGreg Kroah-Hartman 	}
414282361a0SGreg Kroah-Hartman }
415282361a0SGreg Kroah-Hartman 
ipwireless_network_create(struct ipw_hardware * hw)416282361a0SGreg Kroah-Hartman struct ipw_network *ipwireless_network_create(struct ipw_hardware *hw)
417282361a0SGreg Kroah-Hartman {
418282361a0SGreg Kroah-Hartman 	struct ipw_network *network =
41913eac05bSJia-Ju Bai 		kzalloc(sizeof(struct ipw_network), GFP_KERNEL);
420282361a0SGreg Kroah-Hartman 
421282361a0SGreg Kroah-Hartman 	if (!network)
422282361a0SGreg Kroah-Hartman 		return NULL;
423282361a0SGreg Kroah-Hartman 
424282361a0SGreg Kroah-Hartman 	spin_lock_init(&network->lock);
425282361a0SGreg Kroah-Hartman 	mutex_init(&network->close_lock);
426282361a0SGreg Kroah-Hartman 
427282361a0SGreg Kroah-Hartman 	network->hardware = hw;
428282361a0SGreg Kroah-Hartman 
429282361a0SGreg Kroah-Hartman 	INIT_WORK(&network->work_go_online, do_go_online);
430282361a0SGreg Kroah-Hartman 	INIT_WORK(&network->work_go_offline, do_go_offline);
431282361a0SGreg Kroah-Hartman 
432282361a0SGreg Kroah-Hartman 	ipwireless_associate_network(hw, network);
433282361a0SGreg Kroah-Hartman 
434282361a0SGreg Kroah-Hartman 	return network;
435282361a0SGreg Kroah-Hartman }
436282361a0SGreg Kroah-Hartman 
ipwireless_network_free(struct ipw_network * network)437282361a0SGreg Kroah-Hartman void ipwireless_network_free(struct ipw_network *network)
438282361a0SGreg Kroah-Hartman {
439282361a0SGreg Kroah-Hartman 	network->shutting_down = 1;
440282361a0SGreg Kroah-Hartman 
441282361a0SGreg Kroah-Hartman 	ipwireless_ppp_close(network);
44243829731STejun Heo 	flush_work(&network->work_go_online);
44343829731STejun Heo 	flush_work(&network->work_go_offline);
444282361a0SGreg Kroah-Hartman 
445282361a0SGreg Kroah-Hartman 	ipwireless_stop_interrupts(network->hardware);
446282361a0SGreg Kroah-Hartman 	ipwireless_associate_network(network->hardware, NULL);
447282361a0SGreg Kroah-Hartman 
448282361a0SGreg Kroah-Hartman 	kfree(network);
449282361a0SGreg Kroah-Hartman }
450282361a0SGreg Kroah-Hartman 
ipwireless_associate_network_tty(struct ipw_network * network,unsigned int channel_idx,struct ipw_tty * tty)451282361a0SGreg Kroah-Hartman void ipwireless_associate_network_tty(struct ipw_network *network,
452282361a0SGreg Kroah-Hartman 				      unsigned int channel_idx,
453282361a0SGreg Kroah-Hartman 				      struct ipw_tty *tty)
454282361a0SGreg Kroah-Hartman {
455282361a0SGreg Kroah-Hartman 	int i;
456282361a0SGreg Kroah-Hartman 
457282361a0SGreg Kroah-Hartman 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
458282361a0SGreg Kroah-Hartman 		if (network->associated_ttys[channel_idx][i] == NULL) {
459282361a0SGreg Kroah-Hartman 			network->associated_ttys[channel_idx][i] = tty;
460282361a0SGreg Kroah-Hartman 			break;
461282361a0SGreg Kroah-Hartman 		}
462282361a0SGreg Kroah-Hartman }
463282361a0SGreg Kroah-Hartman 
ipwireless_disassociate_network_ttys(struct ipw_network * network,unsigned int channel_idx)464282361a0SGreg Kroah-Hartman void ipwireless_disassociate_network_ttys(struct ipw_network *network,
465282361a0SGreg Kroah-Hartman 					  unsigned int channel_idx)
466282361a0SGreg Kroah-Hartman {
467282361a0SGreg Kroah-Hartman 	int i;
468282361a0SGreg Kroah-Hartman 
469282361a0SGreg Kroah-Hartman 	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
470282361a0SGreg Kroah-Hartman 		network->associated_ttys[channel_idx][i] = NULL;
471282361a0SGreg Kroah-Hartman }
472282361a0SGreg Kroah-Hartman 
ipwireless_ppp_open(struct ipw_network * network)473282361a0SGreg Kroah-Hartman void ipwireless_ppp_open(struct ipw_network *network)
474282361a0SGreg Kroah-Hartman {
475282361a0SGreg Kroah-Hartman 	if (ipwireless_debug)
476282361a0SGreg Kroah-Hartman 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n");
477282361a0SGreg Kroah-Hartman 	schedule_work(&network->work_go_online);
478282361a0SGreg Kroah-Hartman }
479282361a0SGreg Kroah-Hartman 
ipwireless_ppp_close(struct ipw_network * network)480282361a0SGreg Kroah-Hartman void ipwireless_ppp_close(struct ipw_network *network)
481282361a0SGreg Kroah-Hartman {
482282361a0SGreg Kroah-Hartman 	/* Disconnect from the wireless network. */
483282361a0SGreg Kroah-Hartman 	if (ipwireless_debug)
484282361a0SGreg Kroah-Hartman 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n");
485282361a0SGreg Kroah-Hartman 	schedule_work(&network->work_go_offline);
486282361a0SGreg Kroah-Hartman }
487282361a0SGreg Kroah-Hartman 
ipwireless_ppp_channel_index(struct ipw_network * network)488282361a0SGreg Kroah-Hartman int ipwireless_ppp_channel_index(struct ipw_network *network)
489282361a0SGreg Kroah-Hartman {
490282361a0SGreg Kroah-Hartman 	int ret = -1;
491282361a0SGreg Kroah-Hartman 	unsigned long flags;
492282361a0SGreg Kroah-Hartman 
493282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
494282361a0SGreg Kroah-Hartman 	if (network->ppp_channel != NULL)
495282361a0SGreg Kroah-Hartman 		ret = ppp_channel_index(network->ppp_channel);
496282361a0SGreg Kroah-Hartman 	spin_unlock_irqrestore(&network->lock, flags);
497282361a0SGreg Kroah-Hartman 
498282361a0SGreg Kroah-Hartman 	return ret;
499282361a0SGreg Kroah-Hartman }
500282361a0SGreg Kroah-Hartman 
ipwireless_ppp_unit_number(struct ipw_network * network)501282361a0SGreg Kroah-Hartman int ipwireless_ppp_unit_number(struct ipw_network *network)
502282361a0SGreg Kroah-Hartman {
503282361a0SGreg Kroah-Hartman 	int ret = -1;
504282361a0SGreg Kroah-Hartman 	unsigned long flags;
505282361a0SGreg Kroah-Hartman 
506282361a0SGreg Kroah-Hartman 	spin_lock_irqsave(&network->lock, flags);
507282361a0SGreg Kroah-Hartman 	if (network->ppp_channel != NULL)
508282361a0SGreg Kroah-Hartman 		ret = ppp_unit_number(network->ppp_channel);
509282361a0SGreg Kroah-Hartman 	spin_unlock_irqrestore(&network->lock, flags);
510282361a0SGreg Kroah-Hartman 
511282361a0SGreg Kroah-Hartman 	return ret;
512282361a0SGreg Kroah-Hartman }
513282361a0SGreg Kroah-Hartman 
ipwireless_ppp_mru(const struct ipw_network * network)514282361a0SGreg Kroah-Hartman int ipwireless_ppp_mru(const struct ipw_network *network)
515282361a0SGreg Kroah-Hartman {
516282361a0SGreg Kroah-Hartman 	return network->mru;
517282361a0SGreg Kroah-Hartman }
518