18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * IPWireless 3G PCMCIA Network Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Original code
68c2ecf20Sopenharmony_ci *   by Stephen Blackheath <stephen@blacksapphire.com>,
78c2ecf20Sopenharmony_ci *      Ben Martel <benm@symmetric.co.nz>
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * Copyrighted as follows:
108c2ecf20Sopenharmony_ci *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
118c2ecf20Sopenharmony_ci *
128c2ecf20Sopenharmony_ci * Various driver changes and rewrites, port to new kernels
138c2ecf20Sopenharmony_ci *   Copyright (C) 2006-2007 Jiri Kosina
148c2ecf20Sopenharmony_ci *
158c2ecf20Sopenharmony_ci * Misc code cleanups and updates
168c2ecf20Sopenharmony_ci *   Copyright (C) 2007 David Sterba
178c2ecf20Sopenharmony_ci */
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/kernel.h>
208c2ecf20Sopenharmony_ci#include <linux/module.h>
218c2ecf20Sopenharmony_ci#include <linux/mutex.h>
228c2ecf20Sopenharmony_ci#include <linux/ppp_defs.h>
238c2ecf20Sopenharmony_ci#include <linux/if.h>
248c2ecf20Sopenharmony_ci#include <linux/ppp-ioctl.h>
258c2ecf20Sopenharmony_ci#include <linux/sched.h>
268c2ecf20Sopenharmony_ci#include <linux/serial.h>
278c2ecf20Sopenharmony_ci#include <linux/slab.h>
288c2ecf20Sopenharmony_ci#include <linux/tty.h>
298c2ecf20Sopenharmony_ci#include <linux/tty_driver.h>
308c2ecf20Sopenharmony_ci#include <linux/tty_flip.h>
318c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#include "tty.h"
348c2ecf20Sopenharmony_ci#include "network.h"
358c2ecf20Sopenharmony_ci#include "hardware.h"
368c2ecf20Sopenharmony_ci#include "main.h"
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_START 	(0)
398c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINORS	(24)
408c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINOR_RANGE	(8)
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci#define TTYTYPE_MODEM    (0)
438c2ecf20Sopenharmony_ci#define TTYTYPE_MONITOR  (1)
448c2ecf20Sopenharmony_ci#define TTYTYPE_RAS_RAW  (2)
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistruct ipw_tty {
478c2ecf20Sopenharmony_ci	struct tty_port port;
488c2ecf20Sopenharmony_ci	int index;
498c2ecf20Sopenharmony_ci	struct ipw_hardware *hardware;
508c2ecf20Sopenharmony_ci	unsigned int channel_idx;
518c2ecf20Sopenharmony_ci	unsigned int secondary_channel_idx;
528c2ecf20Sopenharmony_ci	int tty_type;
538c2ecf20Sopenharmony_ci	struct ipw_network *network;
548c2ecf20Sopenharmony_ci	unsigned int control_lines;
558c2ecf20Sopenharmony_ci	struct mutex ipw_tty_mutex;
568c2ecf20Sopenharmony_ci	int tx_bytes_queued;
578c2ecf20Sopenharmony_ci	int closing;
588c2ecf20Sopenharmony_ci};
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS];
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic struct tty_driver *ipw_tty_driver;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic char *tty_type_name(int tty_type)
658c2ecf20Sopenharmony_ci{
668c2ecf20Sopenharmony_ci	static char *channel_names[] = {
678c2ecf20Sopenharmony_ci		"modem",
688c2ecf20Sopenharmony_ci		"monitor",
698c2ecf20Sopenharmony_ci		"RAS-raw"
708c2ecf20Sopenharmony_ci	};
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	return channel_names[tty_type];
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic struct ipw_tty *get_tty(int index)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	/*
788c2ecf20Sopenharmony_ci	 * The 'ras_raw' channel is only available when 'loopback' mode
798c2ecf20Sopenharmony_ci	 * is enabled.
808c2ecf20Sopenharmony_ci	 * Number of minor starts with 16 (_RANGE * _RAS_RAW).
818c2ecf20Sopenharmony_ci	 */
828c2ecf20Sopenharmony_ci	if (!ipwireless_loopback && index >=
838c2ecf20Sopenharmony_ci			 IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW)
848c2ecf20Sopenharmony_ci		return NULL;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	return ttys[index];
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic int ipw_open(struct tty_struct *linux_tty, struct file *filp)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct ipw_tty *tty = get_tty(linux_tty->index);
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	if (!tty)
948c2ecf20Sopenharmony_ci		return -ENODEV;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	mutex_lock(&tty->ipw_tty_mutex);
978c2ecf20Sopenharmony_ci	if (tty->port.count == 0)
988c2ecf20Sopenharmony_ci		tty->tx_bytes_queued = 0;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	tty->port.count++;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	tty->port.tty = linux_tty;
1038c2ecf20Sopenharmony_ci	linux_tty->driver_data = tty;
1048c2ecf20Sopenharmony_ci	tty->port.low_latency = 1;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (tty->tty_type == TTYTYPE_MODEM)
1078c2ecf20Sopenharmony_ci		ipwireless_ppp_open(tty->network);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	mutex_unlock(&tty->ipw_tty_mutex);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	return 0;
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic void do_ipw_close(struct ipw_tty *tty)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	tty->port.count--;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (tty->port.count == 0) {
1198c2ecf20Sopenharmony_ci		struct tty_struct *linux_tty = tty->port.tty;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci		if (linux_tty != NULL) {
1228c2ecf20Sopenharmony_ci			tty->port.tty = NULL;
1238c2ecf20Sopenharmony_ci			linux_tty->driver_data = NULL;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci			if (tty->tty_type == TTYTYPE_MODEM)
1268c2ecf20Sopenharmony_ci				ipwireless_ppp_close(tty->network);
1278c2ecf20Sopenharmony_ci		}
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci}
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_cistatic void ipw_hangup(struct tty_struct *linux_tty)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (!tty)
1368c2ecf20Sopenharmony_ci		return;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	mutex_lock(&tty->ipw_tty_mutex);
1398c2ecf20Sopenharmony_ci	if (tty->port.count == 0) {
1408c2ecf20Sopenharmony_ci		mutex_unlock(&tty->ipw_tty_mutex);
1418c2ecf20Sopenharmony_ci		return;
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	do_ipw_close(tty);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	mutex_unlock(&tty->ipw_tty_mutex);
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic void ipw_close(struct tty_struct *linux_tty, struct file *filp)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	ipw_hangup(linux_tty);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci/* Take data received from hardware, and send it out the tty */
1558c2ecf20Sopenharmony_civoid ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data,
1568c2ecf20Sopenharmony_ci			unsigned int length)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	int work = 0;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	mutex_lock(&tty->ipw_tty_mutex);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (!tty->port.count) {
1638c2ecf20Sopenharmony_ci		mutex_unlock(&tty->ipw_tty_mutex);
1648c2ecf20Sopenharmony_ci		return;
1658c2ecf20Sopenharmony_ci	}
1668c2ecf20Sopenharmony_ci	mutex_unlock(&tty->ipw_tty_mutex);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	work = tty_insert_flip_string(&tty->port, data, length);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (work != length)
1718c2ecf20Sopenharmony_ci		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
1728c2ecf20Sopenharmony_ci				": %d chars not inserted to flip buffer!\n",
1738c2ecf20Sopenharmony_ci				length - work);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	if (work)
1768c2ecf20Sopenharmony_ci		tty_flip_buffer_push(&tty->port);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_cistatic void ipw_write_packet_sent_callback(void *callback_data,
1808c2ecf20Sopenharmony_ci					   unsigned int packet_length)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct ipw_tty *tty = callback_data;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	/*
1858c2ecf20Sopenharmony_ci	 * Packet has been sent, so we subtract the number of bytes from our
1868c2ecf20Sopenharmony_ci	 * tally of outstanding TX bytes.
1878c2ecf20Sopenharmony_ci	 */
1888c2ecf20Sopenharmony_ci	tty->tx_bytes_queued -= packet_length;
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_cistatic int ipw_write(struct tty_struct *linux_tty,
1928c2ecf20Sopenharmony_ci		     const unsigned char *buf, int count)
1938c2ecf20Sopenharmony_ci{
1948c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
1958c2ecf20Sopenharmony_ci	int room, ret;
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	if (!tty)
1988c2ecf20Sopenharmony_ci		return -ENODEV;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	mutex_lock(&tty->ipw_tty_mutex);
2018c2ecf20Sopenharmony_ci	if (!tty->port.count) {
2028c2ecf20Sopenharmony_ci		mutex_unlock(&tty->ipw_tty_mutex);
2038c2ecf20Sopenharmony_ci		return -EINVAL;
2048c2ecf20Sopenharmony_ci	}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
2078c2ecf20Sopenharmony_ci	if (room < 0)
2088c2ecf20Sopenharmony_ci		room = 0;
2098c2ecf20Sopenharmony_ci	/* Don't allow caller to write any more than we have room for */
2108c2ecf20Sopenharmony_ci	if (count > room)
2118c2ecf20Sopenharmony_ci		count = room;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (count == 0) {
2148c2ecf20Sopenharmony_ci		mutex_unlock(&tty->ipw_tty_mutex);
2158c2ecf20Sopenharmony_ci		return 0;
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,
2198c2ecf20Sopenharmony_ci			       buf, count,
2208c2ecf20Sopenharmony_ci			       ipw_write_packet_sent_callback, tty);
2218c2ecf20Sopenharmony_ci	if (ret < 0) {
2228c2ecf20Sopenharmony_ci		mutex_unlock(&tty->ipw_tty_mutex);
2238c2ecf20Sopenharmony_ci		return 0;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	tty->tx_bytes_queued += count;
2278c2ecf20Sopenharmony_ci	mutex_unlock(&tty->ipw_tty_mutex);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	return count;
2308c2ecf20Sopenharmony_ci}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_cistatic int ipw_write_room(struct tty_struct *linux_tty)
2338c2ecf20Sopenharmony_ci{
2348c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
2358c2ecf20Sopenharmony_ci	int room;
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	/* FIXME: Exactly how is the tty object locked here .. */
2388c2ecf20Sopenharmony_ci	if (!tty)
2398c2ecf20Sopenharmony_ci		return -ENODEV;
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	if (!tty->port.count)
2428c2ecf20Sopenharmony_ci		return -EINVAL;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
2458c2ecf20Sopenharmony_ci	if (room < 0)
2468c2ecf20Sopenharmony_ci		room = 0;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	return room;
2498c2ecf20Sopenharmony_ci}
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic int ipwireless_get_serial_info(struct tty_struct *linux_tty,
2528c2ecf20Sopenharmony_ci				      struct serial_struct *ss)
2538c2ecf20Sopenharmony_ci{
2548c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (!tty)
2578c2ecf20Sopenharmony_ci		return -ENODEV;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (!tty->port.count)
2608c2ecf20Sopenharmony_ci		return -EINVAL;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	ss->type = PORT_UNKNOWN;
2638c2ecf20Sopenharmony_ci	ss->line = tty->index;
2648c2ecf20Sopenharmony_ci	ss->baud_base = 115200;
2658c2ecf20Sopenharmony_ci	return 0;
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic int ipwireless_set_serial_info(struct tty_struct *linux_tty,
2698c2ecf20Sopenharmony_ci				      struct serial_struct *ss)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	return 0;	/* Keeps the PCMCIA scripts happy. */
2728c2ecf20Sopenharmony_ci}
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_cistatic int ipw_chars_in_buffer(struct tty_struct *linux_tty)
2758c2ecf20Sopenharmony_ci{
2768c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
2778c2ecf20Sopenharmony_ci
2788c2ecf20Sopenharmony_ci	if (!tty)
2798c2ecf20Sopenharmony_ci		return 0;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	if (!tty->port.count)
2828c2ecf20Sopenharmony_ci		return 0;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	return tty->tx_bytes_queued;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_cistatic int get_control_lines(struct ipw_tty *tty)
2888c2ecf20Sopenharmony_ci{
2898c2ecf20Sopenharmony_ci	unsigned int my = tty->control_lines;
2908c2ecf20Sopenharmony_ci	unsigned int out = 0;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	if (my & IPW_CONTROL_LINE_RTS)
2938c2ecf20Sopenharmony_ci		out |= TIOCM_RTS;
2948c2ecf20Sopenharmony_ci	if (my & IPW_CONTROL_LINE_DTR)
2958c2ecf20Sopenharmony_ci		out |= TIOCM_DTR;
2968c2ecf20Sopenharmony_ci	if (my & IPW_CONTROL_LINE_CTS)
2978c2ecf20Sopenharmony_ci		out |= TIOCM_CTS;
2988c2ecf20Sopenharmony_ci	if (my & IPW_CONTROL_LINE_DSR)
2998c2ecf20Sopenharmony_ci		out |= TIOCM_DSR;
3008c2ecf20Sopenharmony_ci	if (my & IPW_CONTROL_LINE_DCD)
3018c2ecf20Sopenharmony_ci		out |= TIOCM_CD;
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_ci	return out;
3048c2ecf20Sopenharmony_ci}
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_cistatic int set_control_lines(struct ipw_tty *tty, unsigned int set,
3078c2ecf20Sopenharmony_ci			     unsigned int clear)
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	int ret;
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	if (set & TIOCM_RTS) {
3128c2ecf20Sopenharmony_ci		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1);
3138c2ecf20Sopenharmony_ci		if (ret)
3148c2ecf20Sopenharmony_ci			return ret;
3158c2ecf20Sopenharmony_ci		if (tty->secondary_channel_idx != -1) {
3168c2ecf20Sopenharmony_ci			ret = ipwireless_set_RTS(tty->hardware,
3178c2ecf20Sopenharmony_ci					  tty->secondary_channel_idx, 1);
3188c2ecf20Sopenharmony_ci			if (ret)
3198c2ecf20Sopenharmony_ci				return ret;
3208c2ecf20Sopenharmony_ci		}
3218c2ecf20Sopenharmony_ci	}
3228c2ecf20Sopenharmony_ci	if (set & TIOCM_DTR) {
3238c2ecf20Sopenharmony_ci		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);
3248c2ecf20Sopenharmony_ci		if (ret)
3258c2ecf20Sopenharmony_ci			return ret;
3268c2ecf20Sopenharmony_ci		if (tty->secondary_channel_idx != -1) {
3278c2ecf20Sopenharmony_ci			ret = ipwireless_set_DTR(tty->hardware,
3288c2ecf20Sopenharmony_ci					  tty->secondary_channel_idx, 1);
3298c2ecf20Sopenharmony_ci			if (ret)
3308c2ecf20Sopenharmony_ci				return ret;
3318c2ecf20Sopenharmony_ci		}
3328c2ecf20Sopenharmony_ci	}
3338c2ecf20Sopenharmony_ci	if (clear & TIOCM_RTS) {
3348c2ecf20Sopenharmony_ci		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);
3358c2ecf20Sopenharmony_ci		if (tty->secondary_channel_idx != -1) {
3368c2ecf20Sopenharmony_ci			ret = ipwireless_set_RTS(tty->hardware,
3378c2ecf20Sopenharmony_ci					  tty->secondary_channel_idx, 0);
3388c2ecf20Sopenharmony_ci			if (ret)
3398c2ecf20Sopenharmony_ci				return ret;
3408c2ecf20Sopenharmony_ci		}
3418c2ecf20Sopenharmony_ci	}
3428c2ecf20Sopenharmony_ci	if (clear & TIOCM_DTR) {
3438c2ecf20Sopenharmony_ci		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);
3448c2ecf20Sopenharmony_ci		if (tty->secondary_channel_idx != -1) {
3458c2ecf20Sopenharmony_ci			ret = ipwireless_set_DTR(tty->hardware,
3468c2ecf20Sopenharmony_ci					  tty->secondary_channel_idx, 0);
3478c2ecf20Sopenharmony_ci			if (ret)
3488c2ecf20Sopenharmony_ci				return ret;
3498c2ecf20Sopenharmony_ci		}
3508c2ecf20Sopenharmony_ci	}
3518c2ecf20Sopenharmony_ci	return 0;
3528c2ecf20Sopenharmony_ci}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic int ipw_tiocmget(struct tty_struct *linux_tty)
3558c2ecf20Sopenharmony_ci{
3568c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
3578c2ecf20Sopenharmony_ci	/* FIXME: Exactly how is the tty object locked here .. */
3588c2ecf20Sopenharmony_ci
3598c2ecf20Sopenharmony_ci	if (!tty)
3608c2ecf20Sopenharmony_ci		return -ENODEV;
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	if (!tty->port.count)
3638c2ecf20Sopenharmony_ci		return -EINVAL;
3648c2ecf20Sopenharmony_ci
3658c2ecf20Sopenharmony_ci	return get_control_lines(tty);
3668c2ecf20Sopenharmony_ci}
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_cistatic int
3698c2ecf20Sopenharmony_ciipw_tiocmset(struct tty_struct *linux_tty,
3708c2ecf20Sopenharmony_ci	     unsigned int set, unsigned int clear)
3718c2ecf20Sopenharmony_ci{
3728c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
3738c2ecf20Sopenharmony_ci	/* FIXME: Exactly how is the tty object locked here .. */
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	if (!tty)
3768c2ecf20Sopenharmony_ci		return -ENODEV;
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	if (!tty->port.count)
3798c2ecf20Sopenharmony_ci		return -EINVAL;
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci	return set_control_lines(tty, set, clear);
3828c2ecf20Sopenharmony_ci}
3838c2ecf20Sopenharmony_ci
3848c2ecf20Sopenharmony_cistatic int ipw_ioctl(struct tty_struct *linux_tty,
3858c2ecf20Sopenharmony_ci		     unsigned int cmd, unsigned long arg)
3868c2ecf20Sopenharmony_ci{
3878c2ecf20Sopenharmony_ci	struct ipw_tty *tty = linux_tty->driver_data;
3888c2ecf20Sopenharmony_ci
3898c2ecf20Sopenharmony_ci	if (!tty)
3908c2ecf20Sopenharmony_ci		return -ENODEV;
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_ci	if (!tty->port.count)
3938c2ecf20Sopenharmony_ci		return -EINVAL;
3948c2ecf20Sopenharmony_ci
3958c2ecf20Sopenharmony_ci	/* FIXME: Exactly how is the tty object locked here .. */
3968c2ecf20Sopenharmony_ci	if (tty->tty_type == TTYTYPE_MODEM) {
3978c2ecf20Sopenharmony_ci		switch (cmd) {
3988c2ecf20Sopenharmony_ci		case PPPIOCGCHAN:
3998c2ecf20Sopenharmony_ci			{
4008c2ecf20Sopenharmony_ci				int chan = ipwireless_ppp_channel_index(
4018c2ecf20Sopenharmony_ci							tty->network);
4028c2ecf20Sopenharmony_ci
4038c2ecf20Sopenharmony_ci				if (chan < 0)
4048c2ecf20Sopenharmony_ci					return -ENODEV;
4058c2ecf20Sopenharmony_ci				if (put_user(chan, (int __user *) arg))
4068c2ecf20Sopenharmony_ci					return -EFAULT;
4078c2ecf20Sopenharmony_ci			}
4088c2ecf20Sopenharmony_ci			return 0;
4098c2ecf20Sopenharmony_ci
4108c2ecf20Sopenharmony_ci		case PPPIOCGUNIT:
4118c2ecf20Sopenharmony_ci			{
4128c2ecf20Sopenharmony_ci				int unit = ipwireless_ppp_unit_number(
4138c2ecf20Sopenharmony_ci						tty->network);
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci				if (unit < 0)
4168c2ecf20Sopenharmony_ci					return -ENODEV;
4178c2ecf20Sopenharmony_ci				if (put_user(unit, (int __user *) arg))
4188c2ecf20Sopenharmony_ci					return -EFAULT;
4198c2ecf20Sopenharmony_ci			}
4208c2ecf20Sopenharmony_ci			return 0;
4218c2ecf20Sopenharmony_ci
4228c2ecf20Sopenharmony_ci		case FIONREAD:
4238c2ecf20Sopenharmony_ci			{
4248c2ecf20Sopenharmony_ci				int val = 0;
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_ci				if (put_user(val, (int __user *) arg))
4278c2ecf20Sopenharmony_ci					return -EFAULT;
4288c2ecf20Sopenharmony_ci			}
4298c2ecf20Sopenharmony_ci			return 0;
4308c2ecf20Sopenharmony_ci		case TCFLSH:
4318c2ecf20Sopenharmony_ci			return tty_perform_flush(linux_tty, arg);
4328c2ecf20Sopenharmony_ci		}
4338c2ecf20Sopenharmony_ci	}
4348c2ecf20Sopenharmony_ci	return -ENOIOCTLCMD;
4358c2ecf20Sopenharmony_ci}
4368c2ecf20Sopenharmony_ci
4378c2ecf20Sopenharmony_cistatic int add_tty(int j,
4388c2ecf20Sopenharmony_ci		    struct ipw_hardware *hardware,
4398c2ecf20Sopenharmony_ci		    struct ipw_network *network, int channel_idx,
4408c2ecf20Sopenharmony_ci		    int secondary_channel_idx, int tty_type)
4418c2ecf20Sopenharmony_ci{
4428c2ecf20Sopenharmony_ci	ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL);
4438c2ecf20Sopenharmony_ci	if (!ttys[j])
4448c2ecf20Sopenharmony_ci		return -ENOMEM;
4458c2ecf20Sopenharmony_ci	ttys[j]->index = j;
4468c2ecf20Sopenharmony_ci	ttys[j]->hardware = hardware;
4478c2ecf20Sopenharmony_ci	ttys[j]->channel_idx = channel_idx;
4488c2ecf20Sopenharmony_ci	ttys[j]->secondary_channel_idx = secondary_channel_idx;
4498c2ecf20Sopenharmony_ci	ttys[j]->network = network;
4508c2ecf20Sopenharmony_ci	ttys[j]->tty_type = tty_type;
4518c2ecf20Sopenharmony_ci	mutex_init(&ttys[j]->ipw_tty_mutex);
4528c2ecf20Sopenharmony_ci	tty_port_init(&ttys[j]->port);
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);
4558c2ecf20Sopenharmony_ci	ipwireless_associate_network_tty(network, channel_idx, ttys[j]);
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_ci	if (secondary_channel_idx != -1)
4588c2ecf20Sopenharmony_ci		ipwireless_associate_network_tty(network,
4598c2ecf20Sopenharmony_ci						 secondary_channel_idx,
4608c2ecf20Sopenharmony_ci						 ttys[j]);
4618c2ecf20Sopenharmony_ci	/* check if we provide raw device (if loopback is enabled) */
4628c2ecf20Sopenharmony_ci	if (get_tty(j))
4638c2ecf20Sopenharmony_ci		printk(KERN_INFO IPWIRELESS_PCCARD_NAME
4648c2ecf20Sopenharmony_ci		       ": registering %s device ttyIPWp%d\n",
4658c2ecf20Sopenharmony_ci		       tty_type_name(tty_type), j);
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci	return 0;
4688c2ecf20Sopenharmony_ci}
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_cistruct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware,
4718c2ecf20Sopenharmony_ci				      struct ipw_network *network)
4728c2ecf20Sopenharmony_ci{
4738c2ecf20Sopenharmony_ci	int i, j;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) {
4768c2ecf20Sopenharmony_ci		int allfree = 1;
4778c2ecf20Sopenharmony_ci
4788c2ecf20Sopenharmony_ci		for (j = i; j < IPWIRELESS_PCMCIA_MINORS;
4798c2ecf20Sopenharmony_ci				j += IPWIRELESS_PCMCIA_MINOR_RANGE)
4808c2ecf20Sopenharmony_ci			if (ttys[j] != NULL) {
4818c2ecf20Sopenharmony_ci				allfree = 0;
4828c2ecf20Sopenharmony_ci				break;
4838c2ecf20Sopenharmony_ci			}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_ci		if (allfree) {
4868c2ecf20Sopenharmony_ci			j = i;
4878c2ecf20Sopenharmony_ci
4888c2ecf20Sopenharmony_ci			if (add_tty(j, hardware, network,
4898c2ecf20Sopenharmony_ci					IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS,
4908c2ecf20Sopenharmony_ci					TTYTYPE_MODEM))
4918c2ecf20Sopenharmony_ci				return NULL;
4928c2ecf20Sopenharmony_ci
4938c2ecf20Sopenharmony_ci			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
4948c2ecf20Sopenharmony_ci			if (add_tty(j, hardware, network,
4958c2ecf20Sopenharmony_ci					IPW_CHANNEL_DIALLER, -1,
4968c2ecf20Sopenharmony_ci					TTYTYPE_MONITOR))
4978c2ecf20Sopenharmony_ci				return NULL;
4988c2ecf20Sopenharmony_ci
4998c2ecf20Sopenharmony_ci			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
5008c2ecf20Sopenharmony_ci			if (add_tty(j, hardware, network,
5018c2ecf20Sopenharmony_ci					IPW_CHANNEL_RAS, -1,
5028c2ecf20Sopenharmony_ci					TTYTYPE_RAS_RAW))
5038c2ecf20Sopenharmony_ci				return NULL;
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci			return ttys[i];
5068c2ecf20Sopenharmony_ci		}
5078c2ecf20Sopenharmony_ci	}
5088c2ecf20Sopenharmony_ci	return NULL;
5098c2ecf20Sopenharmony_ci}
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci/*
5128c2ecf20Sopenharmony_ci * Must be called before ipwireless_network_free().
5138c2ecf20Sopenharmony_ci */
5148c2ecf20Sopenharmony_civoid ipwireless_tty_free(struct ipw_tty *tty)
5158c2ecf20Sopenharmony_ci{
5168c2ecf20Sopenharmony_ci	int j;
5178c2ecf20Sopenharmony_ci	struct ipw_network *network = ttys[tty->index]->network;
5188c2ecf20Sopenharmony_ci
5198c2ecf20Sopenharmony_ci	for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS;
5208c2ecf20Sopenharmony_ci			j += IPWIRELESS_PCMCIA_MINOR_RANGE) {
5218c2ecf20Sopenharmony_ci		struct ipw_tty *ttyj = ttys[j];
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_ci		if (ttyj) {
5248c2ecf20Sopenharmony_ci			mutex_lock(&ttyj->ipw_tty_mutex);
5258c2ecf20Sopenharmony_ci			if (get_tty(j))
5268c2ecf20Sopenharmony_ci				printk(KERN_INFO IPWIRELESS_PCCARD_NAME
5278c2ecf20Sopenharmony_ci				       ": deregistering %s device ttyIPWp%d\n",
5288c2ecf20Sopenharmony_ci				       tty_type_name(ttyj->tty_type), j);
5298c2ecf20Sopenharmony_ci			ttyj->closing = 1;
5308c2ecf20Sopenharmony_ci			if (ttyj->port.tty != NULL) {
5318c2ecf20Sopenharmony_ci				mutex_unlock(&ttyj->ipw_tty_mutex);
5328c2ecf20Sopenharmony_ci				tty_vhangup(ttyj->port.tty);
5338c2ecf20Sopenharmony_ci				/* FIXME: Exactly how is the tty object locked here
5348c2ecf20Sopenharmony_ci				   against a parallel ioctl etc */
5358c2ecf20Sopenharmony_ci				/* FIXME2: hangup does not mean all processes
5368c2ecf20Sopenharmony_ci				 * are gone */
5378c2ecf20Sopenharmony_ci				mutex_lock(&ttyj->ipw_tty_mutex);
5388c2ecf20Sopenharmony_ci			}
5398c2ecf20Sopenharmony_ci			while (ttyj->port.count)
5408c2ecf20Sopenharmony_ci				do_ipw_close(ttyj);
5418c2ecf20Sopenharmony_ci			ipwireless_disassociate_network_ttys(network,
5428c2ecf20Sopenharmony_ci							     ttyj->channel_idx);
5438c2ecf20Sopenharmony_ci			tty_unregister_device(ipw_tty_driver, j);
5448c2ecf20Sopenharmony_ci			tty_port_destroy(&ttyj->port);
5458c2ecf20Sopenharmony_ci			ttys[j] = NULL;
5468c2ecf20Sopenharmony_ci			mutex_unlock(&ttyj->ipw_tty_mutex);
5478c2ecf20Sopenharmony_ci			kfree(ttyj);
5488c2ecf20Sopenharmony_ci		}
5498c2ecf20Sopenharmony_ci	}
5508c2ecf20Sopenharmony_ci}
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_cistatic const struct tty_operations tty_ops = {
5538c2ecf20Sopenharmony_ci	.open = ipw_open,
5548c2ecf20Sopenharmony_ci	.close = ipw_close,
5558c2ecf20Sopenharmony_ci	.hangup = ipw_hangup,
5568c2ecf20Sopenharmony_ci	.write = ipw_write,
5578c2ecf20Sopenharmony_ci	.write_room = ipw_write_room,
5588c2ecf20Sopenharmony_ci	.ioctl = ipw_ioctl,
5598c2ecf20Sopenharmony_ci	.chars_in_buffer = ipw_chars_in_buffer,
5608c2ecf20Sopenharmony_ci	.tiocmget = ipw_tiocmget,
5618c2ecf20Sopenharmony_ci	.tiocmset = ipw_tiocmset,
5628c2ecf20Sopenharmony_ci	.set_serial = ipwireless_set_serial_info,
5638c2ecf20Sopenharmony_ci	.get_serial = ipwireless_get_serial_info,
5648c2ecf20Sopenharmony_ci};
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ciint ipwireless_tty_init(void)
5678c2ecf20Sopenharmony_ci{
5688c2ecf20Sopenharmony_ci	int result;
5698c2ecf20Sopenharmony_ci
5708c2ecf20Sopenharmony_ci	ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS);
5718c2ecf20Sopenharmony_ci	if (!ipw_tty_driver)
5728c2ecf20Sopenharmony_ci		return -ENOMEM;
5738c2ecf20Sopenharmony_ci
5748c2ecf20Sopenharmony_ci	ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME;
5758c2ecf20Sopenharmony_ci	ipw_tty_driver->name = "ttyIPWp";
5768c2ecf20Sopenharmony_ci	ipw_tty_driver->major = 0;
5778c2ecf20Sopenharmony_ci	ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START;
5788c2ecf20Sopenharmony_ci	ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
5798c2ecf20Sopenharmony_ci	ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL;
5808c2ecf20Sopenharmony_ci	ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
5818c2ecf20Sopenharmony_ci	ipw_tty_driver->init_termios = tty_std_termios;
5828c2ecf20Sopenharmony_ci	ipw_tty_driver->init_termios.c_cflag =
5838c2ecf20Sopenharmony_ci	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
5848c2ecf20Sopenharmony_ci	ipw_tty_driver->init_termios.c_ispeed = 9600;
5858c2ecf20Sopenharmony_ci	ipw_tty_driver->init_termios.c_ospeed = 9600;
5868c2ecf20Sopenharmony_ci	tty_set_operations(ipw_tty_driver, &tty_ops);
5878c2ecf20Sopenharmony_ci	result = tty_register_driver(ipw_tty_driver);
5888c2ecf20Sopenharmony_ci	if (result) {
5898c2ecf20Sopenharmony_ci		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
5908c2ecf20Sopenharmony_ci		       ": failed to register tty driver\n");
5918c2ecf20Sopenharmony_ci		put_tty_driver(ipw_tty_driver);
5928c2ecf20Sopenharmony_ci		return result;
5938c2ecf20Sopenharmony_ci	}
5948c2ecf20Sopenharmony_ci
5958c2ecf20Sopenharmony_ci	return 0;
5968c2ecf20Sopenharmony_ci}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_civoid ipwireless_tty_release(void)
5998c2ecf20Sopenharmony_ci{
6008c2ecf20Sopenharmony_ci	int ret;
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_ci	ret = tty_unregister_driver(ipw_tty_driver);
6038c2ecf20Sopenharmony_ci	put_tty_driver(ipw_tty_driver);
6048c2ecf20Sopenharmony_ci	if (ret != 0)
6058c2ecf20Sopenharmony_ci		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
6068c2ecf20Sopenharmony_ci			": tty_unregister_driver failed with code %d\n", ret);
6078c2ecf20Sopenharmony_ci}
6088c2ecf20Sopenharmony_ci
6098c2ecf20Sopenharmony_ciint ipwireless_tty_is_modem(struct ipw_tty *tty)
6108c2ecf20Sopenharmony_ci{
6118c2ecf20Sopenharmony_ci	return tty->tty_type == TTYTYPE_MODEM;
6128c2ecf20Sopenharmony_ci}
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_civoid
6158c2ecf20Sopenharmony_ciipwireless_tty_notify_control_line_change(struct ipw_tty *tty,
6168c2ecf20Sopenharmony_ci					  unsigned int channel_idx,
6178c2ecf20Sopenharmony_ci					  unsigned int control_lines,
6188c2ecf20Sopenharmony_ci					  unsigned int changed_mask)
6198c2ecf20Sopenharmony_ci{
6208c2ecf20Sopenharmony_ci	unsigned int old_control_lines = tty->control_lines;
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_ci	tty->control_lines = (tty->control_lines & ~changed_mask)
6238c2ecf20Sopenharmony_ci		| (control_lines & changed_mask);
6248c2ecf20Sopenharmony_ci
6258c2ecf20Sopenharmony_ci	/*
6268c2ecf20Sopenharmony_ci	 * If DCD is de-asserted, we close the tty so pppd can tell that we
6278c2ecf20Sopenharmony_ci	 * have gone offline.
6288c2ecf20Sopenharmony_ci	 */
6298c2ecf20Sopenharmony_ci	if ((old_control_lines & IPW_CONTROL_LINE_DCD)
6308c2ecf20Sopenharmony_ci			&& !(tty->control_lines & IPW_CONTROL_LINE_DCD)
6318c2ecf20Sopenharmony_ci			&& tty->port.tty) {
6328c2ecf20Sopenharmony_ci		tty_hangup(tty->port.tty);
6338c2ecf20Sopenharmony_ci	}
6348c2ecf20Sopenharmony_ci}
6358c2ecf20Sopenharmony_ci
636