162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IPWireless 3G PCMCIA Network Driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Original code
662306a36Sopenharmony_ci *   by Stephen Blackheath <stephen@blacksapphire.com>,
762306a36Sopenharmony_ci *      Ben Martel <benm@symmetric.co.nz>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyrighted as follows:
1062306a36Sopenharmony_ci *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Various driver changes and rewrites, port to new kernels
1362306a36Sopenharmony_ci *   Copyright (C) 2006-2007 Jiri Kosina
1462306a36Sopenharmony_ci *
1562306a36Sopenharmony_ci * Misc code cleanups and updates
1662306a36Sopenharmony_ci *   Copyright (C) 2007 David Sterba
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/kernel.h>
2162306a36Sopenharmony_ci#include <linux/mutex.h>
2262306a36Sopenharmony_ci#include <linux/netdevice.h>
2362306a36Sopenharmony_ci#include <linux/ppp_channel.h>
2462306a36Sopenharmony_ci#include <linux/ppp_defs.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci#include <linux/ppp-ioctl.h>
2762306a36Sopenharmony_ci#include <linux/skbuff.h>
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#include "network.h"
3062306a36Sopenharmony_ci#include "hardware.h"
3162306a36Sopenharmony_ci#include "main.h"
3262306a36Sopenharmony_ci#include "tty.h"
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define MAX_ASSOCIATED_TTYS 2
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define SC_RCV_BITS     (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct ipw_network {
3962306a36Sopenharmony_ci	/* Hardware context, used for calls to hardware layer. */
4062306a36Sopenharmony_ci	struct ipw_hardware *hardware;
4162306a36Sopenharmony_ci	/* Context for kernel 'generic_ppp' functionality */
4262306a36Sopenharmony_ci	struct ppp_channel *ppp_channel;
4362306a36Sopenharmony_ci	/* tty context connected with IPW console */
4462306a36Sopenharmony_ci	struct ipw_tty *associated_ttys[NO_OF_IPW_CHANNELS][MAX_ASSOCIATED_TTYS];
4562306a36Sopenharmony_ci	/* True if ppp needs waking up once we're ready to xmit */
4662306a36Sopenharmony_ci	int ppp_blocked;
4762306a36Sopenharmony_ci	/* Number of packets queued up in hardware module. */
4862306a36Sopenharmony_ci	int outgoing_packets_queued;
4962306a36Sopenharmony_ci	/* Spinlock to avoid interrupts during shutdown */
5062306a36Sopenharmony_ci	spinlock_t lock;
5162306a36Sopenharmony_ci	struct mutex close_lock;
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	/* PPP ioctl data, not actually used anywere */
5462306a36Sopenharmony_ci	unsigned int flags;
5562306a36Sopenharmony_ci	unsigned int rbits;
5662306a36Sopenharmony_ci	u32 xaccm[8];
5762306a36Sopenharmony_ci	u32 raccm;
5862306a36Sopenharmony_ci	int mru;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	int shutting_down;
6162306a36Sopenharmony_ci	unsigned int ras_control_lines;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	struct work_struct work_go_online;
6462306a36Sopenharmony_ci	struct work_struct work_go_offline;
6562306a36Sopenharmony_ci};
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic void notify_packet_sent(void *callback_data, unsigned int packet_length)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	struct ipw_network *network = callback_data;
7062306a36Sopenharmony_ci	unsigned long flags;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
7362306a36Sopenharmony_ci	network->outgoing_packets_queued--;
7462306a36Sopenharmony_ci	if (network->ppp_channel != NULL) {
7562306a36Sopenharmony_ci		if (network->ppp_blocked) {
7662306a36Sopenharmony_ci			network->ppp_blocked = 0;
7762306a36Sopenharmony_ci			spin_unlock_irqrestore(&network->lock, flags);
7862306a36Sopenharmony_ci			ppp_output_wakeup(network->ppp_channel);
7962306a36Sopenharmony_ci			if (ipwireless_debug)
8062306a36Sopenharmony_ci				printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
8162306a36Sopenharmony_ci				       ": ppp unblocked\n");
8262306a36Sopenharmony_ci		} else
8362306a36Sopenharmony_ci			spin_unlock_irqrestore(&network->lock, flags);
8462306a36Sopenharmony_ci	} else
8562306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
8662306a36Sopenharmony_ci}
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci/*
8962306a36Sopenharmony_ci * Called by the ppp system when it has a packet to send to the hardware.
9062306a36Sopenharmony_ci */
9162306a36Sopenharmony_cistatic int ipwireless_ppp_start_xmit(struct ppp_channel *ppp_channel,
9262306a36Sopenharmony_ci				     struct sk_buff *skb)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct ipw_network *network = ppp_channel->private;
9562306a36Sopenharmony_ci	unsigned long flags;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
9862306a36Sopenharmony_ci	if (network->outgoing_packets_queued < ipwireless_out_queue) {
9962306a36Sopenharmony_ci		unsigned char *buf;
10062306a36Sopenharmony_ci		static unsigned char header[] = {
10162306a36Sopenharmony_ci			PPP_ALLSTATIONS, /* 0xff */
10262306a36Sopenharmony_ci			PPP_UI,		 /* 0x03 */
10362306a36Sopenharmony_ci		};
10462306a36Sopenharmony_ci		int ret;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		network->outgoing_packets_queued++;
10762306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		/*
11062306a36Sopenharmony_ci		 * If we have the requested amount of headroom in the skb we
11162306a36Sopenharmony_ci		 * were handed, then we can add the header efficiently.
11262306a36Sopenharmony_ci		 */
11362306a36Sopenharmony_ci		if (skb_headroom(skb) >= 2) {
11462306a36Sopenharmony_ci			memcpy(skb_push(skb, 2), header, 2);
11562306a36Sopenharmony_ci			ret = ipwireless_send_packet(network->hardware,
11662306a36Sopenharmony_ci					       IPW_CHANNEL_RAS, skb->data,
11762306a36Sopenharmony_ci					       skb->len,
11862306a36Sopenharmony_ci					       notify_packet_sent,
11962306a36Sopenharmony_ci					       network);
12062306a36Sopenharmony_ci			if (ret < 0) {
12162306a36Sopenharmony_ci				skb_pull(skb, 2);
12262306a36Sopenharmony_ci				return 0;
12362306a36Sopenharmony_ci			}
12462306a36Sopenharmony_ci		} else {
12562306a36Sopenharmony_ci			/* Otherwise (rarely) we do it inefficiently. */
12662306a36Sopenharmony_ci			buf = kmalloc(skb->len + 2, GFP_ATOMIC);
12762306a36Sopenharmony_ci			if (!buf)
12862306a36Sopenharmony_ci				return 0;
12962306a36Sopenharmony_ci			memcpy(buf + 2, skb->data, skb->len);
13062306a36Sopenharmony_ci			memcpy(buf, header, 2);
13162306a36Sopenharmony_ci			ret = ipwireless_send_packet(network->hardware,
13262306a36Sopenharmony_ci					       IPW_CHANNEL_RAS, buf,
13362306a36Sopenharmony_ci					       skb->len + 2,
13462306a36Sopenharmony_ci					       notify_packet_sent,
13562306a36Sopenharmony_ci					       network);
13662306a36Sopenharmony_ci			kfree(buf);
13762306a36Sopenharmony_ci			if (ret < 0)
13862306a36Sopenharmony_ci				return 0;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci		kfree_skb(skb);
14162306a36Sopenharmony_ci		return 1;
14262306a36Sopenharmony_ci	} else {
14362306a36Sopenharmony_ci		/*
14462306a36Sopenharmony_ci		 * Otherwise reject the packet, and flag that the ppp system
14562306a36Sopenharmony_ci		 * needs to be unblocked once we are ready to send.
14662306a36Sopenharmony_ci		 */
14762306a36Sopenharmony_ci		network->ppp_blocked = 1;
14862306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
14962306a36Sopenharmony_ci		if (ipwireless_debug)
15062306a36Sopenharmony_ci			printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": ppp blocked\n");
15162306a36Sopenharmony_ci		return 0;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/* Handle an ioctl call that has come in via ppp. (copy of ppp_async_ioctl() */
15662306a36Sopenharmony_cistatic int ipwireless_ppp_ioctl(struct ppp_channel *ppp_channel,
15762306a36Sopenharmony_ci				unsigned int cmd, unsigned long arg)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	struct ipw_network *network = ppp_channel->private;
16062306a36Sopenharmony_ci	int err, val;
16162306a36Sopenharmony_ci	u32 accm[8];
16262306a36Sopenharmony_ci	int __user *user_arg = (int __user *) arg;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	err = -EFAULT;
16562306a36Sopenharmony_ci	switch (cmd) {
16662306a36Sopenharmony_ci	case PPPIOCGFLAGS:
16762306a36Sopenharmony_ci		val = network->flags | network->rbits;
16862306a36Sopenharmony_ci		if (put_user(val, user_arg))
16962306a36Sopenharmony_ci			break;
17062306a36Sopenharmony_ci		err = 0;
17162306a36Sopenharmony_ci		break;
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	case PPPIOCSFLAGS:
17462306a36Sopenharmony_ci		if (get_user(val, user_arg))
17562306a36Sopenharmony_ci			break;
17662306a36Sopenharmony_ci		network->flags = val & ~SC_RCV_BITS;
17762306a36Sopenharmony_ci		network->rbits = val & SC_RCV_BITS;
17862306a36Sopenharmony_ci		err = 0;
17962306a36Sopenharmony_ci		break;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	case PPPIOCGASYNCMAP:
18262306a36Sopenharmony_ci		if (put_user(network->xaccm[0], user_arg))
18362306a36Sopenharmony_ci			break;
18462306a36Sopenharmony_ci		err = 0;
18562306a36Sopenharmony_ci		break;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	case PPPIOCSASYNCMAP:
18862306a36Sopenharmony_ci		if (get_user(network->xaccm[0], user_arg))
18962306a36Sopenharmony_ci			break;
19062306a36Sopenharmony_ci		err = 0;
19162306a36Sopenharmony_ci		break;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	case PPPIOCGRASYNCMAP:
19462306a36Sopenharmony_ci		if (put_user(network->raccm, user_arg))
19562306a36Sopenharmony_ci			break;
19662306a36Sopenharmony_ci		err = 0;
19762306a36Sopenharmony_ci		break;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	case PPPIOCSRASYNCMAP:
20062306a36Sopenharmony_ci		if (get_user(network->raccm, user_arg))
20162306a36Sopenharmony_ci			break;
20262306a36Sopenharmony_ci		err = 0;
20362306a36Sopenharmony_ci		break;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	case PPPIOCGXASYNCMAP:
20662306a36Sopenharmony_ci		if (copy_to_user((void __user *) arg, network->xaccm,
20762306a36Sopenharmony_ci					sizeof(network->xaccm)))
20862306a36Sopenharmony_ci			break;
20962306a36Sopenharmony_ci		err = 0;
21062306a36Sopenharmony_ci		break;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	case PPPIOCSXASYNCMAP:
21362306a36Sopenharmony_ci		if (copy_from_user(accm, (void __user *) arg, sizeof(accm)))
21462306a36Sopenharmony_ci			break;
21562306a36Sopenharmony_ci		accm[2] &= ~0x40000000U;	/* can't escape 0x5e */
21662306a36Sopenharmony_ci		accm[3] |= 0x60000000U;	/* must escape 0x7d, 0x7e */
21762306a36Sopenharmony_ci		memcpy(network->xaccm, accm, sizeof(network->xaccm));
21862306a36Sopenharmony_ci		err = 0;
21962306a36Sopenharmony_ci		break;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	case PPPIOCGMRU:
22262306a36Sopenharmony_ci		if (put_user(network->mru, user_arg))
22362306a36Sopenharmony_ci			break;
22462306a36Sopenharmony_ci		err = 0;
22562306a36Sopenharmony_ci		break;
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	case PPPIOCSMRU:
22862306a36Sopenharmony_ci		if (get_user(val, user_arg))
22962306a36Sopenharmony_ci			break;
23062306a36Sopenharmony_ci		if (val < PPP_MRU)
23162306a36Sopenharmony_ci			val = PPP_MRU;
23262306a36Sopenharmony_ci		network->mru = val;
23362306a36Sopenharmony_ci		err = 0;
23462306a36Sopenharmony_ci		break;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	default:
23762306a36Sopenharmony_ci		err = -ENOTTY;
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	return err;
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cistatic const struct ppp_channel_ops ipwireless_ppp_channel_ops = {
24462306a36Sopenharmony_ci	.start_xmit = ipwireless_ppp_start_xmit,
24562306a36Sopenharmony_ci	.ioctl      = ipwireless_ppp_ioctl
24662306a36Sopenharmony_ci};
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_cistatic void do_go_online(struct work_struct *work_go_online)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct ipw_network *network =
25162306a36Sopenharmony_ci		container_of(work_go_online, struct ipw_network,
25262306a36Sopenharmony_ci				work_go_online);
25362306a36Sopenharmony_ci	unsigned long flags;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
25662306a36Sopenharmony_ci	if (!network->ppp_channel) {
25762306a36Sopenharmony_ci		struct ppp_channel *channel;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
26062306a36Sopenharmony_ci		channel = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL);
26162306a36Sopenharmony_ci		if (!channel) {
26262306a36Sopenharmony_ci			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
26362306a36Sopenharmony_ci					": unable to allocate PPP channel\n");
26462306a36Sopenharmony_ci			return;
26562306a36Sopenharmony_ci		}
26662306a36Sopenharmony_ci		channel->private = network;
26762306a36Sopenharmony_ci		channel->mtu = 16384;	/* Wild guess */
26862306a36Sopenharmony_ci		channel->hdrlen = 2;
26962306a36Sopenharmony_ci		channel->ops = &ipwireless_ppp_channel_ops;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		network->flags = 0;
27262306a36Sopenharmony_ci		network->rbits = 0;
27362306a36Sopenharmony_ci		network->mru = PPP_MRU;
27462306a36Sopenharmony_ci		memset(network->xaccm, 0, sizeof(network->xaccm));
27562306a36Sopenharmony_ci		network->xaccm[0] = ~0U;
27662306a36Sopenharmony_ci		network->xaccm[3] = 0x60000000U;
27762306a36Sopenharmony_ci		network->raccm = ~0U;
27862306a36Sopenharmony_ci		if (ppp_register_channel(channel) < 0) {
27962306a36Sopenharmony_ci			printk(KERN_ERR IPWIRELESS_PCCARD_NAME
28062306a36Sopenharmony_ci					": unable to register PPP channel\n");
28162306a36Sopenharmony_ci			kfree(channel);
28262306a36Sopenharmony_ci			return;
28362306a36Sopenharmony_ci		}
28462306a36Sopenharmony_ci		spin_lock_irqsave(&network->lock, flags);
28562306a36Sopenharmony_ci		network->ppp_channel = channel;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci	spin_unlock_irqrestore(&network->lock, flags);
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cistatic void do_go_offline(struct work_struct *work_go_offline)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	struct ipw_network *network =
29362306a36Sopenharmony_ci		container_of(work_go_offline, struct ipw_network,
29462306a36Sopenharmony_ci				work_go_offline);
29562306a36Sopenharmony_ci	unsigned long flags;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	mutex_lock(&network->close_lock);
29862306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
29962306a36Sopenharmony_ci	if (network->ppp_channel != NULL) {
30062306a36Sopenharmony_ci		struct ppp_channel *channel = network->ppp_channel;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci		network->ppp_channel = NULL;
30362306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
30462306a36Sopenharmony_ci		mutex_unlock(&network->close_lock);
30562306a36Sopenharmony_ci		ppp_unregister_channel(channel);
30662306a36Sopenharmony_ci	} else {
30762306a36Sopenharmony_ci		spin_unlock_irqrestore(&network->lock, flags);
30862306a36Sopenharmony_ci		mutex_unlock(&network->close_lock);
30962306a36Sopenharmony_ci	}
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_civoid ipwireless_network_notify_control_line_change(struct ipw_network *network,
31362306a36Sopenharmony_ci						   unsigned int channel_idx,
31462306a36Sopenharmony_ci						   unsigned int control_lines,
31562306a36Sopenharmony_ci						   unsigned int changed_mask)
31662306a36Sopenharmony_ci{
31762306a36Sopenharmony_ci	int i;
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	if (channel_idx == IPW_CHANNEL_RAS)
32062306a36Sopenharmony_ci		network->ras_control_lines = control_lines;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
32362306a36Sopenharmony_ci		struct ipw_tty *tty =
32462306a36Sopenharmony_ci			network->associated_ttys[channel_idx][i];
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci		/*
32762306a36Sopenharmony_ci		 * If it's associated with a tty (other than the RAS channel
32862306a36Sopenharmony_ci		 * when we're online), then send the data to that tty.  The RAS
32962306a36Sopenharmony_ci		 * channel's data is handled above - it always goes through
33062306a36Sopenharmony_ci		 * ppp_generic.
33162306a36Sopenharmony_ci		 */
33262306a36Sopenharmony_ci		if (tty)
33362306a36Sopenharmony_ci			ipwireless_tty_notify_control_line_change(tty,
33462306a36Sopenharmony_ci								  channel_idx,
33562306a36Sopenharmony_ci								  control_lines,
33662306a36Sopenharmony_ci								  changed_mask);
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci/*
34162306a36Sopenharmony_ci * Some versions of firmware stuff packets with 0xff 0x03 (PPP: ALLSTATIONS, UI)
34262306a36Sopenharmony_ci * bytes, which are required on sent packet, but not always present on received
34362306a36Sopenharmony_ci * packets
34462306a36Sopenharmony_ci */
34562306a36Sopenharmony_cistatic struct sk_buff *ipw_packet_received_skb(unsigned char *data,
34662306a36Sopenharmony_ci					       unsigned int length)
34762306a36Sopenharmony_ci{
34862306a36Sopenharmony_ci	struct sk_buff *skb;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	if (length > 2 && data[0] == PPP_ALLSTATIONS && data[1] == PPP_UI) {
35162306a36Sopenharmony_ci		length -= 2;
35262306a36Sopenharmony_ci		data += 2;
35362306a36Sopenharmony_ci	}
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci	skb = dev_alloc_skb(length + 4);
35662306a36Sopenharmony_ci	if (skb == NULL)
35762306a36Sopenharmony_ci		return NULL;
35862306a36Sopenharmony_ci	skb_reserve(skb, 2);
35962306a36Sopenharmony_ci	skb_put_data(skb, data, length);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	return skb;
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_civoid ipwireless_network_packet_received(struct ipw_network *network,
36562306a36Sopenharmony_ci					unsigned int channel_idx,
36662306a36Sopenharmony_ci					unsigned char *data,
36762306a36Sopenharmony_ci					unsigned int length)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	int i;
37062306a36Sopenharmony_ci	unsigned long flags;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++) {
37362306a36Sopenharmony_ci		struct ipw_tty *tty = network->associated_ttys[channel_idx][i];
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci		if (!tty)
37662306a36Sopenharmony_ci			continue;
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci		/*
37962306a36Sopenharmony_ci		 * If it's associated with a tty (other than the RAS channel
38062306a36Sopenharmony_ci		 * when we're online), then send the data to that tty.  The RAS
38162306a36Sopenharmony_ci		 * channel's data is handled above - it always goes through
38262306a36Sopenharmony_ci		 * ppp_generic.
38362306a36Sopenharmony_ci		 */
38462306a36Sopenharmony_ci		if (channel_idx == IPW_CHANNEL_RAS
38562306a36Sopenharmony_ci				&& (network->ras_control_lines &
38662306a36Sopenharmony_ci					IPW_CONTROL_LINE_DCD) != 0
38762306a36Sopenharmony_ci				&& ipwireless_tty_is_modem(tty)) {
38862306a36Sopenharmony_ci			/*
38962306a36Sopenharmony_ci			 * If data came in on the RAS channel and this tty is
39062306a36Sopenharmony_ci			 * the modem tty, and we are online, then we send it to
39162306a36Sopenharmony_ci			 * the PPP layer.
39262306a36Sopenharmony_ci			 */
39362306a36Sopenharmony_ci			mutex_lock(&network->close_lock);
39462306a36Sopenharmony_ci			spin_lock_irqsave(&network->lock, flags);
39562306a36Sopenharmony_ci			if (network->ppp_channel != NULL) {
39662306a36Sopenharmony_ci				struct sk_buff *skb;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci				spin_unlock_irqrestore(&network->lock,
39962306a36Sopenharmony_ci						flags);
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci				/* Send the data to the ppp_generic module. */
40262306a36Sopenharmony_ci				skb = ipw_packet_received_skb(data, length);
40362306a36Sopenharmony_ci				if (skb)
40462306a36Sopenharmony_ci					ppp_input(network->ppp_channel, skb);
40562306a36Sopenharmony_ci			} else
40662306a36Sopenharmony_ci				spin_unlock_irqrestore(&network->lock,
40762306a36Sopenharmony_ci						flags);
40862306a36Sopenharmony_ci			mutex_unlock(&network->close_lock);
40962306a36Sopenharmony_ci		}
41062306a36Sopenharmony_ci		/* Otherwise we send it out the tty. */
41162306a36Sopenharmony_ci		else
41262306a36Sopenharmony_ci			ipwireless_tty_received(tty, data, length);
41362306a36Sopenharmony_ci	}
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_cistruct ipw_network *ipwireless_network_create(struct ipw_hardware *hw)
41762306a36Sopenharmony_ci{
41862306a36Sopenharmony_ci	struct ipw_network *network =
41962306a36Sopenharmony_ci		kzalloc(sizeof(struct ipw_network), GFP_KERNEL);
42062306a36Sopenharmony_ci
42162306a36Sopenharmony_ci	if (!network)
42262306a36Sopenharmony_ci		return NULL;
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	spin_lock_init(&network->lock);
42562306a36Sopenharmony_ci	mutex_init(&network->close_lock);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	network->hardware = hw;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	INIT_WORK(&network->work_go_online, do_go_online);
43062306a36Sopenharmony_ci	INIT_WORK(&network->work_go_offline, do_go_offline);
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	ipwireless_associate_network(hw, network);
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	return network;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_civoid ipwireless_network_free(struct ipw_network *network)
43862306a36Sopenharmony_ci{
43962306a36Sopenharmony_ci	network->shutting_down = 1;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	ipwireless_ppp_close(network);
44262306a36Sopenharmony_ci	flush_work(&network->work_go_online);
44362306a36Sopenharmony_ci	flush_work(&network->work_go_offline);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	ipwireless_stop_interrupts(network->hardware);
44662306a36Sopenharmony_ci	ipwireless_associate_network(network->hardware, NULL);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	kfree(network);
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_civoid ipwireless_associate_network_tty(struct ipw_network *network,
45262306a36Sopenharmony_ci				      unsigned int channel_idx,
45362306a36Sopenharmony_ci				      struct ipw_tty *tty)
45462306a36Sopenharmony_ci{
45562306a36Sopenharmony_ci	int i;
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
45862306a36Sopenharmony_ci		if (network->associated_ttys[channel_idx][i] == NULL) {
45962306a36Sopenharmony_ci			network->associated_ttys[channel_idx][i] = tty;
46062306a36Sopenharmony_ci			break;
46162306a36Sopenharmony_ci		}
46262306a36Sopenharmony_ci}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_civoid ipwireless_disassociate_network_ttys(struct ipw_network *network,
46562306a36Sopenharmony_ci					  unsigned int channel_idx)
46662306a36Sopenharmony_ci{
46762306a36Sopenharmony_ci	int i;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	for (i = 0; i < MAX_ASSOCIATED_TTYS; i++)
47062306a36Sopenharmony_ci		network->associated_ttys[channel_idx][i] = NULL;
47162306a36Sopenharmony_ci}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_civoid ipwireless_ppp_open(struct ipw_network *network)
47462306a36Sopenharmony_ci{
47562306a36Sopenharmony_ci	if (ipwireless_debug)
47662306a36Sopenharmony_ci		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": online\n");
47762306a36Sopenharmony_ci	schedule_work(&network->work_go_online);
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_civoid ipwireless_ppp_close(struct ipw_network *network)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	/* Disconnect from the wireless network. */
48362306a36Sopenharmony_ci	if (ipwireless_debug)
48462306a36Sopenharmony_ci		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": offline\n");
48562306a36Sopenharmony_ci	schedule_work(&network->work_go_offline);
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ciint ipwireless_ppp_channel_index(struct ipw_network *network)
48962306a36Sopenharmony_ci{
49062306a36Sopenharmony_ci	int ret = -1;
49162306a36Sopenharmony_ci	unsigned long flags;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
49462306a36Sopenharmony_ci	if (network->ppp_channel != NULL)
49562306a36Sopenharmony_ci		ret = ppp_channel_index(network->ppp_channel);
49662306a36Sopenharmony_ci	spin_unlock_irqrestore(&network->lock, flags);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	return ret;
49962306a36Sopenharmony_ci}
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ciint ipwireless_ppp_unit_number(struct ipw_network *network)
50262306a36Sopenharmony_ci{
50362306a36Sopenharmony_ci	int ret = -1;
50462306a36Sopenharmony_ci	unsigned long flags;
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	spin_lock_irqsave(&network->lock, flags);
50762306a36Sopenharmony_ci	if (network->ppp_channel != NULL)
50862306a36Sopenharmony_ci		ret = ppp_unit_number(network->ppp_channel);
50962306a36Sopenharmony_ci	spin_unlock_irqrestore(&network->lock, flags);
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci	return ret;
51262306a36Sopenharmony_ci}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ciint ipwireless_ppp_mru(const struct ipw_network *network)
51562306a36Sopenharmony_ci{
51662306a36Sopenharmony_ci	return network->mru;
51762306a36Sopenharmony_ci}
518