xref: /openbmc/linux/drivers/tty/ipwireless/tty.c (revision 95713967)
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/kernel.h>
20282361a0SGreg Kroah-Hartman #include <linux/module.h>
21282361a0SGreg Kroah-Hartman #include <linux/mutex.h>
22282361a0SGreg Kroah-Hartman #include <linux/ppp_defs.h>
23282361a0SGreg Kroah-Hartman #include <linux/if.h>
244b32da2bSPaul Mackerras #include <linux/ppp-ioctl.h>
25282361a0SGreg Kroah-Hartman #include <linux/sched.h>
26282361a0SGreg Kroah-Hartman #include <linux/serial.h>
27282361a0SGreg Kroah-Hartman #include <linux/slab.h>
28282361a0SGreg Kroah-Hartman #include <linux/tty.h>
29282361a0SGreg Kroah-Hartman #include <linux/tty_driver.h>
30282361a0SGreg Kroah-Hartman #include <linux/tty_flip.h>
31282361a0SGreg Kroah-Hartman #include <linux/uaccess.h>
32282361a0SGreg Kroah-Hartman 
33282361a0SGreg Kroah-Hartman #include "tty.h"
34282361a0SGreg Kroah-Hartman #include "network.h"
35282361a0SGreg Kroah-Hartman #include "hardware.h"
36282361a0SGreg Kroah-Hartman #include "main.h"
37282361a0SGreg Kroah-Hartman 
38282361a0SGreg Kroah-Hartman #define IPWIRELESS_PCMCIA_START 	(0)
39282361a0SGreg Kroah-Hartman #define IPWIRELESS_PCMCIA_MINORS	(24)
40282361a0SGreg Kroah-Hartman #define IPWIRELESS_PCMCIA_MINOR_RANGE	(8)
41282361a0SGreg Kroah-Hartman 
42282361a0SGreg Kroah-Hartman #define TTYTYPE_MODEM    (0)
43282361a0SGreg Kroah-Hartman #define TTYTYPE_MONITOR  (1)
44282361a0SGreg Kroah-Hartman #define TTYTYPE_RAS_RAW  (2)
45282361a0SGreg Kroah-Hartman 
46282361a0SGreg Kroah-Hartman struct ipw_tty {
477393af80SJiri Slaby 	struct tty_port port;
48282361a0SGreg Kroah-Hartman 	int index;
49282361a0SGreg Kroah-Hartman 	struct ipw_hardware *hardware;
50282361a0SGreg Kroah-Hartman 	unsigned int channel_idx;
51282361a0SGreg Kroah-Hartman 	unsigned int secondary_channel_idx;
52282361a0SGreg Kroah-Hartman 	int tty_type;
53282361a0SGreg Kroah-Hartman 	struct ipw_network *network;
54282361a0SGreg Kroah-Hartman 	unsigned int control_lines;
55282361a0SGreg Kroah-Hartman 	struct mutex ipw_tty_mutex;
56282361a0SGreg Kroah-Hartman 	int tx_bytes_queued;
57282361a0SGreg Kroah-Hartman };
58282361a0SGreg Kroah-Hartman 
59282361a0SGreg Kroah-Hartman static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS];
60282361a0SGreg Kroah-Hartman 
61282361a0SGreg Kroah-Hartman static struct tty_driver *ipw_tty_driver;
62282361a0SGreg Kroah-Hartman 
tty_type_name(int tty_type)63282361a0SGreg Kroah-Hartman static char *tty_type_name(int tty_type)
64282361a0SGreg Kroah-Hartman {
65282361a0SGreg Kroah-Hartman 	static char *channel_names[] = {
66282361a0SGreg Kroah-Hartman 		"modem",
67282361a0SGreg Kroah-Hartman 		"monitor",
68282361a0SGreg Kroah-Hartman 		"RAS-raw"
69282361a0SGreg Kroah-Hartman 	};
70282361a0SGreg Kroah-Hartman 
71282361a0SGreg Kroah-Hartman 	return channel_names[tty_type];
72282361a0SGreg Kroah-Hartman }
73282361a0SGreg Kroah-Hartman 
get_tty(int index)74ecaa3bdaSJiri Slaby static struct ipw_tty *get_tty(int index)
75282361a0SGreg Kroah-Hartman {
76282361a0SGreg Kroah-Hartman 	/*
77282361a0SGreg Kroah-Hartman 	 * The 'ras_raw' channel is only available when 'loopback' mode
78282361a0SGreg Kroah-Hartman 	 * is enabled.
79282361a0SGreg Kroah-Hartman 	 * Number of minor starts with 16 (_RANGE * _RAS_RAW).
80282361a0SGreg Kroah-Hartman 	 */
81ecaa3bdaSJiri Slaby 	if (!ipwireless_loopback && index >=
82282361a0SGreg Kroah-Hartman 			 IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW)
83282361a0SGreg Kroah-Hartman 		return NULL;
84282361a0SGreg Kroah-Hartman 
85ecaa3bdaSJiri Slaby 	return ttys[index];
86282361a0SGreg Kroah-Hartman }
87282361a0SGreg Kroah-Hartman 
ipw_open(struct tty_struct * linux_tty,struct file * filp)88282361a0SGreg Kroah-Hartman static int ipw_open(struct tty_struct *linux_tty, struct file *filp)
89282361a0SGreg Kroah-Hartman {
90ecaa3bdaSJiri Slaby 	struct ipw_tty *tty = get_tty(linux_tty->index);
91282361a0SGreg Kroah-Hartman 
92282361a0SGreg Kroah-Hartman 	if (!tty)
93282361a0SGreg Kroah-Hartman 		return -ENODEV;
94282361a0SGreg Kroah-Hartman 
95282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
967393af80SJiri Slaby 	if (tty->port.count == 0)
97282361a0SGreg Kroah-Hartman 		tty->tx_bytes_queued = 0;
98282361a0SGreg Kroah-Hartman 
997393af80SJiri Slaby 	tty->port.count++;
100282361a0SGreg Kroah-Hartman 
10119ef1b71SJiri Slaby 	tty->port.tty = linux_tty;
102282361a0SGreg Kroah-Hartman 	linux_tty->driver_data = tty;
103282361a0SGreg Kroah-Hartman 
104282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM)
105282361a0SGreg Kroah-Hartman 		ipwireless_ppp_open(tty->network);
106282361a0SGreg Kroah-Hartman 
107282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
108282361a0SGreg Kroah-Hartman 
109282361a0SGreg Kroah-Hartman 	return 0;
110282361a0SGreg Kroah-Hartman }
111282361a0SGreg Kroah-Hartman 
do_ipw_close(struct ipw_tty * tty)112282361a0SGreg Kroah-Hartman static void do_ipw_close(struct ipw_tty *tty)
113282361a0SGreg Kroah-Hartman {
1147393af80SJiri Slaby 	tty->port.count--;
115282361a0SGreg Kroah-Hartman 
1167393af80SJiri Slaby 	if (tty->port.count == 0) {
11719ef1b71SJiri Slaby 		struct tty_struct *linux_tty = tty->port.tty;
118282361a0SGreg Kroah-Hartman 
119282361a0SGreg Kroah-Hartman 		if (linux_tty != NULL) {
12019ef1b71SJiri Slaby 			tty->port.tty = NULL;
121282361a0SGreg Kroah-Hartman 			linux_tty->driver_data = NULL;
122282361a0SGreg Kroah-Hartman 
123282361a0SGreg Kroah-Hartman 			if (tty->tty_type == TTYTYPE_MODEM)
124282361a0SGreg Kroah-Hartman 				ipwireless_ppp_close(tty->network);
125282361a0SGreg Kroah-Hartman 		}
126282361a0SGreg Kroah-Hartman 	}
127282361a0SGreg Kroah-Hartman }
128282361a0SGreg Kroah-Hartman 
ipw_hangup(struct tty_struct * linux_tty)129282361a0SGreg Kroah-Hartman static void ipw_hangup(struct tty_struct *linux_tty)
130282361a0SGreg Kroah-Hartman {
131282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
132282361a0SGreg Kroah-Hartman 
133282361a0SGreg Kroah-Hartman 	if (!tty)
134282361a0SGreg Kroah-Hartman 		return;
135282361a0SGreg Kroah-Hartman 
136282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
1377393af80SJiri Slaby 	if (tty->port.count == 0) {
138282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
139282361a0SGreg Kroah-Hartman 		return;
140282361a0SGreg Kroah-Hartman 	}
141282361a0SGreg Kroah-Hartman 
142282361a0SGreg Kroah-Hartman 	do_ipw_close(tty);
143282361a0SGreg Kroah-Hartman 
144282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
145282361a0SGreg Kroah-Hartman }
146282361a0SGreg Kroah-Hartman 
ipw_close(struct tty_struct * linux_tty,struct file * filp)147282361a0SGreg Kroah-Hartman static void ipw_close(struct tty_struct *linux_tty, struct file *filp)
148282361a0SGreg Kroah-Hartman {
149282361a0SGreg Kroah-Hartman 	ipw_hangup(linux_tty);
150282361a0SGreg Kroah-Hartman }
151282361a0SGreg Kroah-Hartman 
152282361a0SGreg Kroah-Hartman /* Take data received from hardware, and send it out the tty */
ipwireless_tty_received(struct ipw_tty * tty,unsigned char * data,unsigned int length)153282361a0SGreg Kroah-Hartman void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data,
154282361a0SGreg Kroah-Hartman 			unsigned int length)
155282361a0SGreg Kroah-Hartman {
156282361a0SGreg Kroah-Hartman 	int work = 0;
157282361a0SGreg Kroah-Hartman 
158282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
159282361a0SGreg Kroah-Hartman 
1607393af80SJiri Slaby 	if (!tty->port.count) {
161282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
162282361a0SGreg Kroah-Hartman 		return;
163282361a0SGreg Kroah-Hartman 	}
164282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
165282361a0SGreg Kroah-Hartman 
16605c7cd39SJiri Slaby 	work = tty_insert_flip_string(&tty->port, data, length);
167282361a0SGreg Kroah-Hartman 
168282361a0SGreg Kroah-Hartman 	if (work != length)
169282361a0SGreg Kroah-Hartman 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
170282361a0SGreg Kroah-Hartman 				": %d chars not inserted to flip buffer!\n",
171282361a0SGreg Kroah-Hartman 				length - work);
172282361a0SGreg Kroah-Hartman 
173282361a0SGreg Kroah-Hartman 	if (work)
1742e124b4aSJiri Slaby 		tty_flip_buffer_push(&tty->port);
175282361a0SGreg Kroah-Hartman }
176282361a0SGreg Kroah-Hartman 
ipw_write_packet_sent_callback(void * callback_data,unsigned int packet_length)177282361a0SGreg Kroah-Hartman static void ipw_write_packet_sent_callback(void *callback_data,
178282361a0SGreg Kroah-Hartman 					   unsigned int packet_length)
179282361a0SGreg Kroah-Hartman {
180282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = callback_data;
181282361a0SGreg Kroah-Hartman 
182282361a0SGreg Kroah-Hartman 	/*
183282361a0SGreg Kroah-Hartman 	 * Packet has been sent, so we subtract the number of bytes from our
184282361a0SGreg Kroah-Hartman 	 * tally of outstanding TX bytes.
185282361a0SGreg Kroah-Hartman 	 */
186282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued -= packet_length;
187282361a0SGreg Kroah-Hartman }
188282361a0SGreg Kroah-Hartman 
ipw_write(struct tty_struct * linux_tty,const u8 * buf,size_t count)189*95713967SJiri Slaby (SUSE) static ssize_t ipw_write(struct tty_struct *linux_tty, const u8 *buf,
190*95713967SJiri Slaby (SUSE) 			 size_t count)
191282361a0SGreg Kroah-Hartman {
192282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
193282361a0SGreg Kroah-Hartman 	int room, ret;
194282361a0SGreg Kroah-Hartman 
195282361a0SGreg Kroah-Hartman 	if (!tty)
196282361a0SGreg Kroah-Hartman 		return -ENODEV;
197282361a0SGreg Kroah-Hartman 
198282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
1997393af80SJiri Slaby 	if (!tty->port.count) {
200282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
201282361a0SGreg Kroah-Hartman 		return -EINVAL;
202282361a0SGreg Kroah-Hartman 	}
203282361a0SGreg Kroah-Hartman 
204282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
205282361a0SGreg Kroah-Hartman 	if (room < 0)
206282361a0SGreg Kroah-Hartman 		room = 0;
207282361a0SGreg Kroah-Hartman 	/* Don't allow caller to write any more than we have room for */
208282361a0SGreg Kroah-Hartman 	if (count > room)
209282361a0SGreg Kroah-Hartman 		count = room;
210282361a0SGreg Kroah-Hartman 
211282361a0SGreg Kroah-Hartman 	if (count == 0) {
212282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
213282361a0SGreg Kroah-Hartman 		return 0;
214282361a0SGreg Kroah-Hartman 	}
215282361a0SGreg Kroah-Hartman 
216282361a0SGreg Kroah-Hartman 	ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,
217282361a0SGreg Kroah-Hartman 			       buf, count,
218282361a0SGreg Kroah-Hartman 			       ipw_write_packet_sent_callback, tty);
219db332356STong Zhang 	if (ret < 0) {
220282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
221282361a0SGreg Kroah-Hartman 		return 0;
222282361a0SGreg Kroah-Hartman 	}
223282361a0SGreg Kroah-Hartman 
224282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued += count;
225282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
226282361a0SGreg Kroah-Hartman 
227282361a0SGreg Kroah-Hartman 	return count;
228282361a0SGreg Kroah-Hartman }
229282361a0SGreg Kroah-Hartman 
ipw_write_room(struct tty_struct * linux_tty)23003b3b1a2SJiri Slaby static unsigned int ipw_write_room(struct tty_struct *linux_tty)
231282361a0SGreg Kroah-Hartman {
232282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
233282361a0SGreg Kroah-Hartman 	int room;
234282361a0SGreg Kroah-Hartman 
235282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
236282361a0SGreg Kroah-Hartman 	if (!tty)
2376bfbfcfcSJiri Slaby 		return 0;
238282361a0SGreg Kroah-Hartman 
2397393af80SJiri Slaby 	if (!tty->port.count)
2406bfbfcfcSJiri Slaby 		return 0;
241282361a0SGreg Kroah-Hartman 
242282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
243282361a0SGreg Kroah-Hartman 	if (room < 0)
244282361a0SGreg Kroah-Hartman 		room = 0;
245282361a0SGreg Kroah-Hartman 
246282361a0SGreg Kroah-Hartman 	return room;
247282361a0SGreg Kroah-Hartman }
248282361a0SGreg Kroah-Hartman 
ipwireless_get_serial_info(struct tty_struct * linux_tty,struct serial_struct * ss)249a7b06fcfSAl Viro static int ipwireless_get_serial_info(struct tty_struct *linux_tty,
250a7b06fcfSAl Viro 				      struct serial_struct *ss)
251282361a0SGreg Kroah-Hartman {
252a7b06fcfSAl Viro 	struct ipw_tty *tty = linux_tty->driver_data;
253282361a0SGreg Kroah-Hartman 
254a7b06fcfSAl Viro 	if (!tty)
255a7b06fcfSAl Viro 		return -ENODEV;
256fec4daecSJiri Slaby 
257a7b06fcfSAl Viro 	if (!tty->port.count)
258a7b06fcfSAl Viro 		return -EINVAL;
259282361a0SGreg Kroah-Hartman 
260a7b06fcfSAl Viro 	ss->type = PORT_UNKNOWN;
261a7b06fcfSAl Viro 	ss->line = tty->index;
262a7b06fcfSAl Viro 	ss->baud_base = 115200;
263282361a0SGreg Kroah-Hartman 	return 0;
264282361a0SGreg Kroah-Hartman }
265282361a0SGreg Kroah-Hartman 
ipwireless_set_serial_info(struct tty_struct * linux_tty,struct serial_struct * ss)266a7b06fcfSAl Viro static int ipwireless_set_serial_info(struct tty_struct *linux_tty,
267a7b06fcfSAl Viro 				      struct serial_struct *ss)
268a7b06fcfSAl Viro {
269a7b06fcfSAl Viro 	return 0;	/* Keeps the PCMCIA scripts happy. */
270a7b06fcfSAl Viro }
271a7b06fcfSAl Viro 
ipw_chars_in_buffer(struct tty_struct * linux_tty)272fff4ef17SJiri Slaby static unsigned int ipw_chars_in_buffer(struct tty_struct *linux_tty)
273282361a0SGreg Kroah-Hartman {
274282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
275282361a0SGreg Kroah-Hartman 
276282361a0SGreg Kroah-Hartman 	if (!tty)
277282361a0SGreg Kroah-Hartman 		return 0;
278282361a0SGreg Kroah-Hartman 
2797393af80SJiri Slaby 	if (!tty->port.count)
280282361a0SGreg Kroah-Hartman 		return 0;
281282361a0SGreg Kroah-Hartman 
282282361a0SGreg Kroah-Hartman 	return tty->tx_bytes_queued;
283282361a0SGreg Kroah-Hartman }
284282361a0SGreg Kroah-Hartman 
get_control_lines(struct ipw_tty * tty)285282361a0SGreg Kroah-Hartman static int get_control_lines(struct ipw_tty *tty)
286282361a0SGreg Kroah-Hartman {
287282361a0SGreg Kroah-Hartman 	unsigned int my = tty->control_lines;
288282361a0SGreg Kroah-Hartman 	unsigned int out = 0;
289282361a0SGreg Kroah-Hartman 
290282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_RTS)
291282361a0SGreg Kroah-Hartman 		out |= TIOCM_RTS;
292282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DTR)
293282361a0SGreg Kroah-Hartman 		out |= TIOCM_DTR;
294282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_CTS)
295282361a0SGreg Kroah-Hartman 		out |= TIOCM_CTS;
296282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DSR)
297282361a0SGreg Kroah-Hartman 		out |= TIOCM_DSR;
298282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DCD)
299282361a0SGreg Kroah-Hartman 		out |= TIOCM_CD;
300282361a0SGreg Kroah-Hartman 
301282361a0SGreg Kroah-Hartman 	return out;
302282361a0SGreg Kroah-Hartman }
303282361a0SGreg Kroah-Hartman 
set_control_lines(struct ipw_tty * tty,unsigned int set,unsigned int clear)304282361a0SGreg Kroah-Hartman static int set_control_lines(struct ipw_tty *tty, unsigned int set,
305282361a0SGreg Kroah-Hartman 			     unsigned int clear)
306282361a0SGreg Kroah-Hartman {
307282361a0SGreg Kroah-Hartman 	int ret;
308282361a0SGreg Kroah-Hartman 
309282361a0SGreg Kroah-Hartman 	if (set & TIOCM_RTS) {
310282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1);
311282361a0SGreg Kroah-Hartman 		if (ret)
312282361a0SGreg Kroah-Hartman 			return ret;
313282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
314282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
315282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 1);
316282361a0SGreg Kroah-Hartman 			if (ret)
317282361a0SGreg Kroah-Hartman 				return ret;
318282361a0SGreg Kroah-Hartman 		}
319282361a0SGreg Kroah-Hartman 	}
320282361a0SGreg Kroah-Hartman 	if (set & TIOCM_DTR) {
321282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);
322282361a0SGreg Kroah-Hartman 		if (ret)
323282361a0SGreg Kroah-Hartman 			return ret;
324282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
325282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_DTR(tty->hardware,
326282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 1);
327282361a0SGreg Kroah-Hartman 			if (ret)
328282361a0SGreg Kroah-Hartman 				return ret;
329282361a0SGreg Kroah-Hartman 		}
330282361a0SGreg Kroah-Hartman 	}
331282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_RTS) {
332282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);
333282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
334282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
335282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
336282361a0SGreg Kroah-Hartman 			if (ret)
337282361a0SGreg Kroah-Hartman 				return ret;
338282361a0SGreg Kroah-Hartman 		}
339282361a0SGreg Kroah-Hartman 	}
340282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_DTR) {
341282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);
342282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
343282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_DTR(tty->hardware,
344282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
345282361a0SGreg Kroah-Hartman 			if (ret)
346282361a0SGreg Kroah-Hartman 				return ret;
347282361a0SGreg Kroah-Hartman 		}
348282361a0SGreg Kroah-Hartman 	}
349282361a0SGreg Kroah-Hartman 	return 0;
350282361a0SGreg Kroah-Hartman }
351282361a0SGreg Kroah-Hartman 
ipw_tiocmget(struct tty_struct * linux_tty)352282361a0SGreg Kroah-Hartman static int ipw_tiocmget(struct tty_struct *linux_tty)
353282361a0SGreg Kroah-Hartman {
354282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
355282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
356282361a0SGreg Kroah-Hartman 
357282361a0SGreg Kroah-Hartman 	if (!tty)
358282361a0SGreg Kroah-Hartman 		return -ENODEV;
359282361a0SGreg Kroah-Hartman 
3607393af80SJiri Slaby 	if (!tty->port.count)
361282361a0SGreg Kroah-Hartman 		return -EINVAL;
362282361a0SGreg Kroah-Hartman 
363282361a0SGreg Kroah-Hartman 	return get_control_lines(tty);
364282361a0SGreg Kroah-Hartman }
365282361a0SGreg Kroah-Hartman 
366282361a0SGreg Kroah-Hartman static int
ipw_tiocmset(struct tty_struct * linux_tty,unsigned int set,unsigned int clear)367282361a0SGreg Kroah-Hartman ipw_tiocmset(struct tty_struct *linux_tty,
368282361a0SGreg Kroah-Hartman 	     unsigned int set, unsigned int clear)
369282361a0SGreg Kroah-Hartman {
370282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
371282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
372282361a0SGreg Kroah-Hartman 
373282361a0SGreg Kroah-Hartman 	if (!tty)
374282361a0SGreg Kroah-Hartman 		return -ENODEV;
375282361a0SGreg Kroah-Hartman 
3767393af80SJiri Slaby 	if (!tty->port.count)
377282361a0SGreg Kroah-Hartman 		return -EINVAL;
378282361a0SGreg Kroah-Hartman 
379282361a0SGreg Kroah-Hartman 	return set_control_lines(tty, set, clear);
380282361a0SGreg Kroah-Hartman }
381282361a0SGreg Kroah-Hartman 
ipw_ioctl(struct tty_struct * linux_tty,unsigned int cmd,unsigned long arg)382282361a0SGreg Kroah-Hartman static int ipw_ioctl(struct tty_struct *linux_tty,
383282361a0SGreg Kroah-Hartman 		     unsigned int cmd, unsigned long arg)
384282361a0SGreg Kroah-Hartman {
385282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
386282361a0SGreg Kroah-Hartman 
387282361a0SGreg Kroah-Hartman 	if (!tty)
388282361a0SGreg Kroah-Hartman 		return -ENODEV;
389282361a0SGreg Kroah-Hartman 
3907393af80SJiri Slaby 	if (!tty->port.count)
391282361a0SGreg Kroah-Hartman 		return -EINVAL;
392282361a0SGreg Kroah-Hartman 
393282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
394282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM) {
395282361a0SGreg Kroah-Hartman 		switch (cmd) {
396282361a0SGreg Kroah-Hartman 		case PPPIOCGCHAN:
397282361a0SGreg Kroah-Hartman 			{
398282361a0SGreg Kroah-Hartman 				int chan = ipwireless_ppp_channel_index(
399282361a0SGreg Kroah-Hartman 							tty->network);
400282361a0SGreg Kroah-Hartman 
401282361a0SGreg Kroah-Hartman 				if (chan < 0)
402282361a0SGreg Kroah-Hartman 					return -ENODEV;
403282361a0SGreg Kroah-Hartman 				if (put_user(chan, (int __user *) arg))
404282361a0SGreg Kroah-Hartman 					return -EFAULT;
405282361a0SGreg Kroah-Hartman 			}
406282361a0SGreg Kroah-Hartman 			return 0;
407282361a0SGreg Kroah-Hartman 
408282361a0SGreg Kroah-Hartman 		case PPPIOCGUNIT:
409282361a0SGreg Kroah-Hartman 			{
410282361a0SGreg Kroah-Hartman 				int unit = ipwireless_ppp_unit_number(
411282361a0SGreg Kroah-Hartman 						tty->network);
412282361a0SGreg Kroah-Hartman 
413282361a0SGreg Kroah-Hartman 				if (unit < 0)
414282361a0SGreg Kroah-Hartman 					return -ENODEV;
415282361a0SGreg Kroah-Hartman 				if (put_user(unit, (int __user *) arg))
416282361a0SGreg Kroah-Hartman 					return -EFAULT;
417282361a0SGreg Kroah-Hartman 			}
418282361a0SGreg Kroah-Hartman 			return 0;
419282361a0SGreg Kroah-Hartman 
420282361a0SGreg Kroah-Hartman 		case FIONREAD:
421282361a0SGreg Kroah-Hartman 			{
422282361a0SGreg Kroah-Hartman 				int val = 0;
423282361a0SGreg Kroah-Hartman 
424282361a0SGreg Kroah-Hartman 				if (put_user(val, (int __user *) arg))
425282361a0SGreg Kroah-Hartman 					return -EFAULT;
426282361a0SGreg Kroah-Hartman 			}
427282361a0SGreg Kroah-Hartman 			return 0;
428282361a0SGreg Kroah-Hartman 		case TCFLSH:
429282361a0SGreg Kroah-Hartman 			return tty_perform_flush(linux_tty, arg);
430282361a0SGreg Kroah-Hartman 		}
431282361a0SGreg Kroah-Hartman 	}
432282361a0SGreg Kroah-Hartman 	return -ENOIOCTLCMD;
433282361a0SGreg Kroah-Hartman }
434282361a0SGreg Kroah-Hartman 
add_tty(int j,struct ipw_hardware * hardware,struct ipw_network * network,int channel_idx,int secondary_channel_idx,int tty_type)435282361a0SGreg Kroah-Hartman static int add_tty(int j,
436282361a0SGreg Kroah-Hartman 		    struct ipw_hardware *hardware,
437282361a0SGreg Kroah-Hartman 		    struct ipw_network *network, int channel_idx,
438282361a0SGreg Kroah-Hartman 		    int secondary_channel_idx, int tty_type)
439282361a0SGreg Kroah-Hartman {
440282361a0SGreg Kroah-Hartman 	ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL);
441282361a0SGreg Kroah-Hartman 	if (!ttys[j])
442282361a0SGreg Kroah-Hartman 		return -ENOMEM;
443282361a0SGreg Kroah-Hartman 	ttys[j]->index = j;
444282361a0SGreg Kroah-Hartman 	ttys[j]->hardware = hardware;
445282361a0SGreg Kroah-Hartman 	ttys[j]->channel_idx = channel_idx;
446282361a0SGreg Kroah-Hartman 	ttys[j]->secondary_channel_idx = secondary_channel_idx;
447282361a0SGreg Kroah-Hartman 	ttys[j]->network = network;
448282361a0SGreg Kroah-Hartman 	ttys[j]->tty_type = tty_type;
449282361a0SGreg Kroah-Hartman 	mutex_init(&ttys[j]->ipw_tty_mutex);
4507393af80SJiri Slaby 	tty_port_init(&ttys[j]->port);
451282361a0SGreg Kroah-Hartman 
452734cc178SJiri Slaby 	tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);
453282361a0SGreg Kroah-Hartman 	ipwireless_associate_network_tty(network, channel_idx, ttys[j]);
454282361a0SGreg Kroah-Hartman 
455282361a0SGreg Kroah-Hartman 	if (secondary_channel_idx != -1)
456282361a0SGreg Kroah-Hartman 		ipwireless_associate_network_tty(network,
457282361a0SGreg Kroah-Hartman 						 secondary_channel_idx,
458282361a0SGreg Kroah-Hartman 						 ttys[j]);
459e6df3cceSJiri Slaby 	/* check if we provide raw device (if loopback is enabled) */
460e6df3cceSJiri Slaby 	if (get_tty(j))
461e6df3cceSJiri Slaby 		printk(KERN_INFO IPWIRELESS_PCCARD_NAME
462e6df3cceSJiri Slaby 		       ": registering %s device ttyIPWp%d\n",
463e6df3cceSJiri Slaby 		       tty_type_name(tty_type), j);
464e6df3cceSJiri Slaby 
465282361a0SGreg Kroah-Hartman 	return 0;
466282361a0SGreg Kroah-Hartman }
467282361a0SGreg Kroah-Hartman 
ipwireless_tty_create(struct ipw_hardware * hardware,struct ipw_network * network)468282361a0SGreg Kroah-Hartman struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware,
469282361a0SGreg Kroah-Hartman 				      struct ipw_network *network)
470282361a0SGreg Kroah-Hartman {
471282361a0SGreg Kroah-Hartman 	int i, j;
472282361a0SGreg Kroah-Hartman 
473282361a0SGreg Kroah-Hartman 	for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) {
474282361a0SGreg Kroah-Hartman 		int allfree = 1;
475282361a0SGreg Kroah-Hartman 
476282361a0SGreg Kroah-Hartman 		for (j = i; j < IPWIRELESS_PCMCIA_MINORS;
477282361a0SGreg Kroah-Hartman 				j += IPWIRELESS_PCMCIA_MINOR_RANGE)
478282361a0SGreg Kroah-Hartman 			if (ttys[j] != NULL) {
479282361a0SGreg Kroah-Hartman 				allfree = 0;
480282361a0SGreg Kroah-Hartman 				break;
481282361a0SGreg Kroah-Hartman 			}
482282361a0SGreg Kroah-Hartman 
483282361a0SGreg Kroah-Hartman 		if (allfree) {
484282361a0SGreg Kroah-Hartman 			j = i;
485282361a0SGreg Kroah-Hartman 
486282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
487282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS,
488282361a0SGreg Kroah-Hartman 					TTYTYPE_MODEM))
489282361a0SGreg Kroah-Hartman 				return NULL;
490282361a0SGreg Kroah-Hartman 
491282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
492282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
493282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, -1,
494282361a0SGreg Kroah-Hartman 					TTYTYPE_MONITOR))
495282361a0SGreg Kroah-Hartman 				return NULL;
496282361a0SGreg Kroah-Hartman 
497282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
498282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
499282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_RAS, -1,
500282361a0SGreg Kroah-Hartman 					TTYTYPE_RAS_RAW))
501282361a0SGreg Kroah-Hartman 				return NULL;
502282361a0SGreg Kroah-Hartman 
503282361a0SGreg Kroah-Hartman 			return ttys[i];
504282361a0SGreg Kroah-Hartman 		}
505282361a0SGreg Kroah-Hartman 	}
506282361a0SGreg Kroah-Hartman 	return NULL;
507282361a0SGreg Kroah-Hartman }
508282361a0SGreg Kroah-Hartman 
509282361a0SGreg Kroah-Hartman /*
510282361a0SGreg Kroah-Hartman  * Must be called before ipwireless_network_free().
511282361a0SGreg Kroah-Hartman  */
ipwireless_tty_free(struct ipw_tty * tty)512282361a0SGreg Kroah-Hartman void ipwireless_tty_free(struct ipw_tty *tty)
513282361a0SGreg Kroah-Hartman {
514282361a0SGreg Kroah-Hartman 	int j;
515282361a0SGreg Kroah-Hartman 	struct ipw_network *network = ttys[tty->index]->network;
516282361a0SGreg Kroah-Hartman 
517282361a0SGreg Kroah-Hartman 	for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS;
518282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE) {
519282361a0SGreg Kroah-Hartman 		struct ipw_tty *ttyj = ttys[j];
520282361a0SGreg Kroah-Hartman 
521282361a0SGreg Kroah-Hartman 		if (ttyj) {
522282361a0SGreg Kroah-Hartman 			mutex_lock(&ttyj->ipw_tty_mutex);
523e6df3cceSJiri Slaby 			if (get_tty(j))
524e6df3cceSJiri Slaby 				printk(KERN_INFO IPWIRELESS_PCCARD_NAME
525e6df3cceSJiri Slaby 				       ": deregistering %s device ttyIPWp%d\n",
526e6df3cceSJiri Slaby 				       tty_type_name(ttyj->tty_type), j);
52719ef1b71SJiri Slaby 			if (ttyj->port.tty != NULL) {
528282361a0SGreg Kroah-Hartman 				mutex_unlock(&ttyj->ipw_tty_mutex);
52919ef1b71SJiri Slaby 				tty_vhangup(ttyj->port.tty);
530282361a0SGreg Kroah-Hartman 				/* FIXME: Exactly how is the tty object locked here
531282361a0SGreg Kroah-Hartman 				   against a parallel ioctl etc */
532de3a60a3SJiri Slaby 				/* FIXME2: hangup does not mean all processes
533de3a60a3SJiri Slaby 				 * are gone */
534282361a0SGreg Kroah-Hartman 				mutex_lock(&ttyj->ipw_tty_mutex);
535282361a0SGreg Kroah-Hartman 			}
5367393af80SJiri Slaby 			while (ttyj->port.count)
537282361a0SGreg Kroah-Hartman 				do_ipw_close(ttyj);
538282361a0SGreg Kroah-Hartman 			ipwireless_disassociate_network_ttys(network,
539282361a0SGreg Kroah-Hartman 							     ttyj->channel_idx);
540282361a0SGreg Kroah-Hartman 			tty_unregister_device(ipw_tty_driver, j);
541191c5f10SJiri Slaby 			tty_port_destroy(&ttyj->port);
542282361a0SGreg Kroah-Hartman 			ttys[j] = NULL;
543282361a0SGreg Kroah-Hartman 			mutex_unlock(&ttyj->ipw_tty_mutex);
544282361a0SGreg Kroah-Hartman 			kfree(ttyj);
545282361a0SGreg Kroah-Hartman 		}
546282361a0SGreg Kroah-Hartman 	}
547282361a0SGreg Kroah-Hartman }
548282361a0SGreg Kroah-Hartman 
549282361a0SGreg Kroah-Hartman static const struct tty_operations tty_ops = {
550282361a0SGreg Kroah-Hartman 	.open = ipw_open,
551282361a0SGreg Kroah-Hartman 	.close = ipw_close,
552282361a0SGreg Kroah-Hartman 	.hangup = ipw_hangup,
553282361a0SGreg Kroah-Hartman 	.write = ipw_write,
554282361a0SGreg Kroah-Hartman 	.write_room = ipw_write_room,
555282361a0SGreg Kroah-Hartman 	.ioctl = ipw_ioctl,
556282361a0SGreg Kroah-Hartman 	.chars_in_buffer = ipw_chars_in_buffer,
557282361a0SGreg Kroah-Hartman 	.tiocmget = ipw_tiocmget,
558282361a0SGreg Kroah-Hartman 	.tiocmset = ipw_tiocmset,
559a7b06fcfSAl Viro 	.set_serial = ipwireless_set_serial_info,
560a7b06fcfSAl Viro 	.get_serial = ipwireless_get_serial_info,
561282361a0SGreg Kroah-Hartman };
562282361a0SGreg Kroah-Hartman 
ipwireless_tty_init(void)563282361a0SGreg Kroah-Hartman int ipwireless_tty_init(void)
564282361a0SGreg Kroah-Hartman {
565282361a0SGreg Kroah-Hartman 	int result;
566282361a0SGreg Kroah-Hartman 
56739b7b42bSJiri Slaby 	ipw_tty_driver = tty_alloc_driver(IPWIRELESS_PCMCIA_MINORS,
56839b7b42bSJiri Slaby 			TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV);
56939b7b42bSJiri Slaby 	if (IS_ERR(ipw_tty_driver))
57039b7b42bSJiri Slaby 		return PTR_ERR(ipw_tty_driver);
571282361a0SGreg Kroah-Hartman 
572282361a0SGreg Kroah-Hartman 	ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME;
573282361a0SGreg Kroah-Hartman 	ipw_tty_driver->name = "ttyIPWp";
574282361a0SGreg Kroah-Hartman 	ipw_tty_driver->major = 0;
575282361a0SGreg Kroah-Hartman 	ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START;
576282361a0SGreg Kroah-Hartman 	ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
577282361a0SGreg Kroah-Hartman 	ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL;
578282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios = tty_std_termios;
579282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_cflag =
580282361a0SGreg Kroah-Hartman 	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
581282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ispeed = 9600;
582282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ospeed = 9600;
583282361a0SGreg Kroah-Hartman 	tty_set_operations(ipw_tty_driver, &tty_ops);
584282361a0SGreg Kroah-Hartman 	result = tty_register_driver(ipw_tty_driver);
585282361a0SGreg Kroah-Hartman 	if (result) {
586282361a0SGreg Kroah-Hartman 		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
587282361a0SGreg Kroah-Hartman 		       ": failed to register tty driver\n");
5889f90a4ddSJiri Slaby 		tty_driver_kref_put(ipw_tty_driver);
589282361a0SGreg Kroah-Hartman 		return result;
590282361a0SGreg Kroah-Hartman 	}
591282361a0SGreg Kroah-Hartman 
592282361a0SGreg Kroah-Hartman 	return 0;
593282361a0SGreg Kroah-Hartman }
594282361a0SGreg Kroah-Hartman 
ipwireless_tty_release(void)595282361a0SGreg Kroah-Hartman void ipwireless_tty_release(void)
596282361a0SGreg Kroah-Hartman {
5976c2e6317SJiri Slaby 	tty_unregister_driver(ipw_tty_driver);
5989f90a4ddSJiri Slaby 	tty_driver_kref_put(ipw_tty_driver);
599282361a0SGreg Kroah-Hartman }
600282361a0SGreg Kroah-Hartman 
ipwireless_tty_is_modem(struct ipw_tty * tty)601282361a0SGreg Kroah-Hartman int ipwireless_tty_is_modem(struct ipw_tty *tty)
602282361a0SGreg Kroah-Hartman {
603282361a0SGreg Kroah-Hartman 	return tty->tty_type == TTYTYPE_MODEM;
604282361a0SGreg Kroah-Hartman }
605282361a0SGreg Kroah-Hartman 
606282361a0SGreg Kroah-Hartman void
ipwireless_tty_notify_control_line_change(struct ipw_tty * tty,unsigned int channel_idx,unsigned int control_lines,unsigned int changed_mask)607282361a0SGreg Kroah-Hartman ipwireless_tty_notify_control_line_change(struct ipw_tty *tty,
608282361a0SGreg Kroah-Hartman 					  unsigned int channel_idx,
609282361a0SGreg Kroah-Hartman 					  unsigned int control_lines,
610282361a0SGreg Kroah-Hartman 					  unsigned int changed_mask)
611282361a0SGreg Kroah-Hartman {
612282361a0SGreg Kroah-Hartman 	unsigned int old_control_lines = tty->control_lines;
613282361a0SGreg Kroah-Hartman 
614282361a0SGreg Kroah-Hartman 	tty->control_lines = (tty->control_lines & ~changed_mask)
615282361a0SGreg Kroah-Hartman 		| (control_lines & changed_mask);
616282361a0SGreg Kroah-Hartman 
617282361a0SGreg Kroah-Hartman 	/*
618282361a0SGreg Kroah-Hartman 	 * If DCD is de-asserted, we close the tty so pppd can tell that we
619282361a0SGreg Kroah-Hartman 	 * have gone offline.
620282361a0SGreg Kroah-Hartman 	 */
621282361a0SGreg Kroah-Hartman 	if ((old_control_lines & IPW_CONTROL_LINE_DCD)
622282361a0SGreg Kroah-Hartman 			&& !(tty->control_lines & IPW_CONTROL_LINE_DCD)
62319ef1b71SJiri Slaby 			&& tty->port.tty) {
62419ef1b71SJiri Slaby 		tty_hangup(tty->port.tty);
625282361a0SGreg Kroah-Hartman 	}
626282361a0SGreg Kroah-Hartman }
627282361a0SGreg Kroah-Hartman 
628