xref: /openbmc/linux/drivers/tty/ipwireless/tty.c (revision 05c7cd39)
1282361a0SGreg Kroah-Hartman /*
2282361a0SGreg Kroah-Hartman  * IPWireless 3G PCMCIA Network Driver
3282361a0SGreg Kroah-Hartman  *
4282361a0SGreg Kroah-Hartman  * Original code
5282361a0SGreg Kroah-Hartman  *   by Stephen Blackheath <stephen@blacksapphire.com>,
6282361a0SGreg Kroah-Hartman  *      Ben Martel <benm@symmetric.co.nz>
7282361a0SGreg Kroah-Hartman  *
8282361a0SGreg Kroah-Hartman  * Copyrighted as follows:
9282361a0SGreg Kroah-Hartman  *   Copyright (C) 2004 by Symmetric Systems Ltd (NZ)
10282361a0SGreg Kroah-Hartman  *
11282361a0SGreg Kroah-Hartman  * Various driver changes and rewrites, port to new kernels
12282361a0SGreg Kroah-Hartman  *   Copyright (C) 2006-2007 Jiri Kosina
13282361a0SGreg Kroah-Hartman  *
14282361a0SGreg Kroah-Hartman  * Misc code cleanups and updates
15282361a0SGreg Kroah-Hartman  *   Copyright (C) 2007 David Sterba
16282361a0SGreg Kroah-Hartman  */
17282361a0SGreg Kroah-Hartman 
18282361a0SGreg Kroah-Hartman #include <linux/init.h>
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 	int closing;
58282361a0SGreg Kroah-Hartman };
59282361a0SGreg Kroah-Hartman 
60282361a0SGreg Kroah-Hartman static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS];
61282361a0SGreg Kroah-Hartman 
62282361a0SGreg Kroah-Hartman static struct tty_driver *ipw_tty_driver;
63282361a0SGreg Kroah-Hartman 
64282361a0SGreg Kroah-Hartman static char *tty_type_name(int tty_type)
65282361a0SGreg Kroah-Hartman {
66282361a0SGreg Kroah-Hartman 	static char *channel_names[] = {
67282361a0SGreg Kroah-Hartman 		"modem",
68282361a0SGreg Kroah-Hartman 		"monitor",
69282361a0SGreg Kroah-Hartman 		"RAS-raw"
70282361a0SGreg Kroah-Hartman 	};
71282361a0SGreg Kroah-Hartman 
72282361a0SGreg Kroah-Hartman 	return channel_names[tty_type];
73282361a0SGreg Kroah-Hartman }
74282361a0SGreg Kroah-Hartman 
75ecaa3bdaSJiri Slaby static struct ipw_tty *get_tty(int index)
76282361a0SGreg Kroah-Hartman {
77282361a0SGreg Kroah-Hartman 	/*
78282361a0SGreg Kroah-Hartman 	 * The 'ras_raw' channel is only available when 'loopback' mode
79282361a0SGreg Kroah-Hartman 	 * is enabled.
80282361a0SGreg Kroah-Hartman 	 * Number of minor starts with 16 (_RANGE * _RAS_RAW).
81282361a0SGreg Kroah-Hartman 	 */
82ecaa3bdaSJiri Slaby 	if (!ipwireless_loopback && index >=
83282361a0SGreg Kroah-Hartman 			 IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW)
84282361a0SGreg Kroah-Hartman 		return NULL;
85282361a0SGreg Kroah-Hartman 
86ecaa3bdaSJiri Slaby 	return ttys[index];
87282361a0SGreg Kroah-Hartman }
88282361a0SGreg Kroah-Hartman 
89282361a0SGreg Kroah-Hartman static int ipw_open(struct tty_struct *linux_tty, struct file *filp)
90282361a0SGreg Kroah-Hartman {
91ecaa3bdaSJiri Slaby 	struct ipw_tty *tty = get_tty(linux_tty->index);
92282361a0SGreg Kroah-Hartman 
93282361a0SGreg Kroah-Hartman 	if (!tty)
94282361a0SGreg Kroah-Hartman 		return -ENODEV;
95282361a0SGreg Kroah-Hartman 
96282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
97282361a0SGreg Kroah-Hartman 
98282361a0SGreg Kroah-Hartman 	if (tty->closing) {
99282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
100282361a0SGreg Kroah-Hartman 		return -ENODEV;
101282361a0SGreg Kroah-Hartman 	}
1027393af80SJiri Slaby 	if (tty->port.count == 0)
103282361a0SGreg Kroah-Hartman 		tty->tx_bytes_queued = 0;
104282361a0SGreg Kroah-Hartman 
1057393af80SJiri Slaby 	tty->port.count++;
106282361a0SGreg Kroah-Hartman 
10719ef1b71SJiri Slaby 	tty->port.tty = linux_tty;
108282361a0SGreg Kroah-Hartman 	linux_tty->driver_data = tty;
109282361a0SGreg Kroah-Hartman 	linux_tty->low_latency = 1;
110282361a0SGreg Kroah-Hartman 
111282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM)
112282361a0SGreg Kroah-Hartman 		ipwireless_ppp_open(tty->network);
113282361a0SGreg Kroah-Hartman 
114282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
115282361a0SGreg Kroah-Hartman 
116282361a0SGreg Kroah-Hartman 	return 0;
117282361a0SGreg Kroah-Hartman }
118282361a0SGreg Kroah-Hartman 
119282361a0SGreg Kroah-Hartman static void do_ipw_close(struct ipw_tty *tty)
120282361a0SGreg Kroah-Hartman {
1217393af80SJiri Slaby 	tty->port.count--;
122282361a0SGreg Kroah-Hartman 
1237393af80SJiri Slaby 	if (tty->port.count == 0) {
12419ef1b71SJiri Slaby 		struct tty_struct *linux_tty = tty->port.tty;
125282361a0SGreg Kroah-Hartman 
126282361a0SGreg Kroah-Hartman 		if (linux_tty != NULL) {
12719ef1b71SJiri Slaby 			tty->port.tty = NULL;
128282361a0SGreg Kroah-Hartman 			linux_tty->driver_data = NULL;
129282361a0SGreg Kroah-Hartman 
130282361a0SGreg Kroah-Hartman 			if (tty->tty_type == TTYTYPE_MODEM)
131282361a0SGreg Kroah-Hartman 				ipwireless_ppp_close(tty->network);
132282361a0SGreg Kroah-Hartman 		}
133282361a0SGreg Kroah-Hartman 	}
134282361a0SGreg Kroah-Hartman }
135282361a0SGreg Kroah-Hartman 
136282361a0SGreg Kroah-Hartman static void ipw_hangup(struct tty_struct *linux_tty)
137282361a0SGreg Kroah-Hartman {
138282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
139282361a0SGreg Kroah-Hartman 
140282361a0SGreg Kroah-Hartman 	if (!tty)
141282361a0SGreg Kroah-Hartman 		return;
142282361a0SGreg Kroah-Hartman 
143282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
1447393af80SJiri Slaby 	if (tty->port.count == 0) {
145282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
146282361a0SGreg Kroah-Hartman 		return;
147282361a0SGreg Kroah-Hartman 	}
148282361a0SGreg Kroah-Hartman 
149282361a0SGreg Kroah-Hartman 	do_ipw_close(tty);
150282361a0SGreg Kroah-Hartman 
151282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
152282361a0SGreg Kroah-Hartman }
153282361a0SGreg Kroah-Hartman 
154282361a0SGreg Kroah-Hartman static void ipw_close(struct tty_struct *linux_tty, struct file *filp)
155282361a0SGreg Kroah-Hartman {
156282361a0SGreg Kroah-Hartman 	ipw_hangup(linux_tty);
157282361a0SGreg Kroah-Hartman }
158282361a0SGreg Kroah-Hartman 
159282361a0SGreg Kroah-Hartman /* Take data received from hardware, and send it out the tty */
160282361a0SGreg Kroah-Hartman void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data,
161282361a0SGreg Kroah-Hartman 			unsigned int length)
162282361a0SGreg Kroah-Hartman {
163282361a0SGreg Kroah-Hartman 	struct tty_struct *linux_tty;
164282361a0SGreg Kroah-Hartman 	int work = 0;
165282361a0SGreg Kroah-Hartman 
166282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
16719ef1b71SJiri Slaby 	linux_tty = tty->port.tty;
168282361a0SGreg Kroah-Hartman 	if (linux_tty == NULL) {
169282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
170282361a0SGreg Kroah-Hartman 		return;
171282361a0SGreg Kroah-Hartman 	}
172282361a0SGreg Kroah-Hartman 
1737393af80SJiri Slaby 	if (!tty->port.count) {
174282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
175282361a0SGreg Kroah-Hartman 		return;
176282361a0SGreg Kroah-Hartman 	}
177282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
178282361a0SGreg Kroah-Hartman 
17905c7cd39SJiri Slaby 	work = tty_insert_flip_string(&tty->port, data, length);
180282361a0SGreg Kroah-Hartman 
181282361a0SGreg Kroah-Hartman 	if (work != length)
182282361a0SGreg Kroah-Hartman 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
183282361a0SGreg Kroah-Hartman 				": %d chars not inserted to flip buffer!\n",
184282361a0SGreg Kroah-Hartman 				length - work);
185282361a0SGreg Kroah-Hartman 
186282361a0SGreg Kroah-Hartman 	/*
187282361a0SGreg Kroah-Hartman 	 * This may sleep if ->low_latency is set
188282361a0SGreg Kroah-Hartman 	 */
189282361a0SGreg Kroah-Hartman 	if (work)
190282361a0SGreg Kroah-Hartman 		tty_flip_buffer_push(linux_tty);
191282361a0SGreg Kroah-Hartman }
192282361a0SGreg Kroah-Hartman 
193282361a0SGreg Kroah-Hartman static void ipw_write_packet_sent_callback(void *callback_data,
194282361a0SGreg Kroah-Hartman 					   unsigned int packet_length)
195282361a0SGreg Kroah-Hartman {
196282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = callback_data;
197282361a0SGreg Kroah-Hartman 
198282361a0SGreg Kroah-Hartman 	/*
199282361a0SGreg Kroah-Hartman 	 * Packet has been sent, so we subtract the number of bytes from our
200282361a0SGreg Kroah-Hartman 	 * tally of outstanding TX bytes.
201282361a0SGreg Kroah-Hartman 	 */
202282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued -= packet_length;
203282361a0SGreg Kroah-Hartman }
204282361a0SGreg Kroah-Hartman 
205282361a0SGreg Kroah-Hartman static int ipw_write(struct tty_struct *linux_tty,
206282361a0SGreg Kroah-Hartman 		     const unsigned char *buf, int count)
207282361a0SGreg Kroah-Hartman {
208282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
209282361a0SGreg Kroah-Hartman 	int room, ret;
210282361a0SGreg Kroah-Hartman 
211282361a0SGreg Kroah-Hartman 	if (!tty)
212282361a0SGreg Kroah-Hartman 		return -ENODEV;
213282361a0SGreg Kroah-Hartman 
214282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
2157393af80SJiri Slaby 	if (!tty->port.count) {
216282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
217282361a0SGreg Kroah-Hartman 		return -EINVAL;
218282361a0SGreg Kroah-Hartman 	}
219282361a0SGreg Kroah-Hartman 
220282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
221282361a0SGreg Kroah-Hartman 	if (room < 0)
222282361a0SGreg Kroah-Hartman 		room = 0;
223282361a0SGreg Kroah-Hartman 	/* Don't allow caller to write any more than we have room for */
224282361a0SGreg Kroah-Hartman 	if (count > room)
225282361a0SGreg Kroah-Hartman 		count = room;
226282361a0SGreg Kroah-Hartman 
227282361a0SGreg Kroah-Hartman 	if (count == 0) {
228282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
229282361a0SGreg Kroah-Hartman 		return 0;
230282361a0SGreg Kroah-Hartman 	}
231282361a0SGreg Kroah-Hartman 
232282361a0SGreg Kroah-Hartman 	ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,
233282361a0SGreg Kroah-Hartman 			       buf, count,
234282361a0SGreg Kroah-Hartman 			       ipw_write_packet_sent_callback, tty);
235282361a0SGreg Kroah-Hartman 	if (ret == -1) {
236282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
237282361a0SGreg Kroah-Hartman 		return 0;
238282361a0SGreg Kroah-Hartman 	}
239282361a0SGreg Kroah-Hartman 
240282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued += count;
241282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
242282361a0SGreg Kroah-Hartman 
243282361a0SGreg Kroah-Hartman 	return count;
244282361a0SGreg Kroah-Hartman }
245282361a0SGreg Kroah-Hartman 
246282361a0SGreg Kroah-Hartman static int ipw_write_room(struct tty_struct *linux_tty)
247282361a0SGreg Kroah-Hartman {
248282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
249282361a0SGreg Kroah-Hartman 	int room;
250282361a0SGreg Kroah-Hartman 
251282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
252282361a0SGreg Kroah-Hartman 	if (!tty)
253282361a0SGreg Kroah-Hartman 		return -ENODEV;
254282361a0SGreg Kroah-Hartman 
2557393af80SJiri Slaby 	if (!tty->port.count)
256282361a0SGreg Kroah-Hartman 		return -EINVAL;
257282361a0SGreg Kroah-Hartman 
258282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
259282361a0SGreg Kroah-Hartman 	if (room < 0)
260282361a0SGreg Kroah-Hartman 		room = 0;
261282361a0SGreg Kroah-Hartman 
262282361a0SGreg Kroah-Hartman 	return room;
263282361a0SGreg Kroah-Hartman }
264282361a0SGreg Kroah-Hartman 
265282361a0SGreg Kroah-Hartman static int ipwireless_get_serial_info(struct ipw_tty *tty,
266282361a0SGreg Kroah-Hartman 				      struct serial_struct __user *retinfo)
267282361a0SGreg Kroah-Hartman {
268282361a0SGreg Kroah-Hartman 	struct serial_struct tmp;
269282361a0SGreg Kroah-Hartman 
270282361a0SGreg Kroah-Hartman 	if (!retinfo)
271282361a0SGreg Kroah-Hartman 		return (-EFAULT);
272282361a0SGreg Kroah-Hartman 
273282361a0SGreg Kroah-Hartman 	memset(&tmp, 0, sizeof(tmp));
274282361a0SGreg Kroah-Hartman 	tmp.type = PORT_UNKNOWN;
275282361a0SGreg Kroah-Hartman 	tmp.line = tty->index;
276282361a0SGreg Kroah-Hartman 	tmp.port = 0;
277282361a0SGreg Kroah-Hartman 	tmp.irq = 0;
278282361a0SGreg Kroah-Hartman 	tmp.flags = 0;
279282361a0SGreg Kroah-Hartman 	tmp.baud_base = 115200;
280282361a0SGreg Kroah-Hartman 	tmp.close_delay = 0;
281282361a0SGreg Kroah-Hartman 	tmp.closing_wait = 0;
282282361a0SGreg Kroah-Hartman 	tmp.custom_divisor = 0;
283282361a0SGreg Kroah-Hartman 	tmp.hub6 = 0;
284282361a0SGreg Kroah-Hartman 	if (copy_to_user(retinfo, &tmp, sizeof(*retinfo)))
285282361a0SGreg Kroah-Hartman 		return -EFAULT;
286282361a0SGreg Kroah-Hartman 
287282361a0SGreg Kroah-Hartman 	return 0;
288282361a0SGreg Kroah-Hartman }
289282361a0SGreg Kroah-Hartman 
290282361a0SGreg Kroah-Hartman static int ipw_chars_in_buffer(struct tty_struct *linux_tty)
291282361a0SGreg Kroah-Hartman {
292282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
293282361a0SGreg Kroah-Hartman 
294282361a0SGreg Kroah-Hartman 	if (!tty)
295282361a0SGreg Kroah-Hartman 		return 0;
296282361a0SGreg Kroah-Hartman 
2977393af80SJiri Slaby 	if (!tty->port.count)
298282361a0SGreg Kroah-Hartman 		return 0;
299282361a0SGreg Kroah-Hartman 
300282361a0SGreg Kroah-Hartman 	return tty->tx_bytes_queued;
301282361a0SGreg Kroah-Hartman }
302282361a0SGreg Kroah-Hartman 
303282361a0SGreg Kroah-Hartman static int get_control_lines(struct ipw_tty *tty)
304282361a0SGreg Kroah-Hartman {
305282361a0SGreg Kroah-Hartman 	unsigned int my = tty->control_lines;
306282361a0SGreg Kroah-Hartman 	unsigned int out = 0;
307282361a0SGreg Kroah-Hartman 
308282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_RTS)
309282361a0SGreg Kroah-Hartman 		out |= TIOCM_RTS;
310282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DTR)
311282361a0SGreg Kroah-Hartman 		out |= TIOCM_DTR;
312282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_CTS)
313282361a0SGreg Kroah-Hartman 		out |= TIOCM_CTS;
314282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DSR)
315282361a0SGreg Kroah-Hartman 		out |= TIOCM_DSR;
316282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DCD)
317282361a0SGreg Kroah-Hartman 		out |= TIOCM_CD;
318282361a0SGreg Kroah-Hartman 
319282361a0SGreg Kroah-Hartman 	return out;
320282361a0SGreg Kroah-Hartman }
321282361a0SGreg Kroah-Hartman 
322282361a0SGreg Kroah-Hartman static int set_control_lines(struct ipw_tty *tty, unsigned int set,
323282361a0SGreg Kroah-Hartman 			     unsigned int clear)
324282361a0SGreg Kroah-Hartman {
325282361a0SGreg Kroah-Hartman 	int ret;
326282361a0SGreg Kroah-Hartman 
327282361a0SGreg Kroah-Hartman 	if (set & TIOCM_RTS) {
328282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1);
329282361a0SGreg Kroah-Hartman 		if (ret)
330282361a0SGreg Kroah-Hartman 			return ret;
331282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
332282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
333282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 1);
334282361a0SGreg Kroah-Hartman 			if (ret)
335282361a0SGreg Kroah-Hartman 				return ret;
336282361a0SGreg Kroah-Hartman 		}
337282361a0SGreg Kroah-Hartman 	}
338282361a0SGreg Kroah-Hartman 	if (set & TIOCM_DTR) {
339282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);
340282361a0SGreg Kroah-Hartman 		if (ret)
341282361a0SGreg Kroah-Hartman 			return ret;
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, 1);
345282361a0SGreg Kroah-Hartman 			if (ret)
346282361a0SGreg Kroah-Hartman 				return ret;
347282361a0SGreg Kroah-Hartman 		}
348282361a0SGreg Kroah-Hartman 	}
349282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_RTS) {
350282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);
351282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
352282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
353282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
354282361a0SGreg Kroah-Hartman 			if (ret)
355282361a0SGreg Kroah-Hartman 				return ret;
356282361a0SGreg Kroah-Hartman 		}
357282361a0SGreg Kroah-Hartman 	}
358282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_DTR) {
359282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);
360282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
361282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_DTR(tty->hardware,
362282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
363282361a0SGreg Kroah-Hartman 			if (ret)
364282361a0SGreg Kroah-Hartman 				return ret;
365282361a0SGreg Kroah-Hartman 		}
366282361a0SGreg Kroah-Hartman 	}
367282361a0SGreg Kroah-Hartman 	return 0;
368282361a0SGreg Kroah-Hartman }
369282361a0SGreg Kroah-Hartman 
370282361a0SGreg Kroah-Hartman static int ipw_tiocmget(struct tty_struct *linux_tty)
371282361a0SGreg Kroah-Hartman {
372282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
373282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
374282361a0SGreg Kroah-Hartman 
375282361a0SGreg Kroah-Hartman 	if (!tty)
376282361a0SGreg Kroah-Hartman 		return -ENODEV;
377282361a0SGreg Kroah-Hartman 
3787393af80SJiri Slaby 	if (!tty->port.count)
379282361a0SGreg Kroah-Hartman 		return -EINVAL;
380282361a0SGreg Kroah-Hartman 
381282361a0SGreg Kroah-Hartman 	return get_control_lines(tty);
382282361a0SGreg Kroah-Hartman }
383282361a0SGreg Kroah-Hartman 
384282361a0SGreg Kroah-Hartman static int
385282361a0SGreg Kroah-Hartman ipw_tiocmset(struct tty_struct *linux_tty,
386282361a0SGreg Kroah-Hartman 	     unsigned int set, unsigned int clear)
387282361a0SGreg Kroah-Hartman {
388282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
389282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
390282361a0SGreg Kroah-Hartman 
391282361a0SGreg Kroah-Hartman 	if (!tty)
392282361a0SGreg Kroah-Hartman 		return -ENODEV;
393282361a0SGreg Kroah-Hartman 
3947393af80SJiri Slaby 	if (!tty->port.count)
395282361a0SGreg Kroah-Hartman 		return -EINVAL;
396282361a0SGreg Kroah-Hartman 
397282361a0SGreg Kroah-Hartman 	return set_control_lines(tty, set, clear);
398282361a0SGreg Kroah-Hartman }
399282361a0SGreg Kroah-Hartman 
400282361a0SGreg Kroah-Hartman static int ipw_ioctl(struct tty_struct *linux_tty,
401282361a0SGreg Kroah-Hartman 		     unsigned int cmd, unsigned long arg)
402282361a0SGreg Kroah-Hartman {
403282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
404282361a0SGreg Kroah-Hartman 
405282361a0SGreg Kroah-Hartman 	if (!tty)
406282361a0SGreg Kroah-Hartman 		return -ENODEV;
407282361a0SGreg Kroah-Hartman 
4087393af80SJiri Slaby 	if (!tty->port.count)
409282361a0SGreg Kroah-Hartman 		return -EINVAL;
410282361a0SGreg Kroah-Hartman 
411282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
412282361a0SGreg Kroah-Hartman 
413282361a0SGreg Kroah-Hartman 	switch (cmd) {
414282361a0SGreg Kroah-Hartman 	case TIOCGSERIAL:
415282361a0SGreg Kroah-Hartman 		return ipwireless_get_serial_info(tty, (void __user *) arg);
416282361a0SGreg Kroah-Hartman 
417282361a0SGreg Kroah-Hartman 	case TIOCSSERIAL:
418282361a0SGreg Kroah-Hartman 		return 0;	/* Keeps the PCMCIA scripts happy. */
419282361a0SGreg Kroah-Hartman 	}
420282361a0SGreg Kroah-Hartman 
421282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM) {
422282361a0SGreg Kroah-Hartman 		switch (cmd) {
423282361a0SGreg Kroah-Hartman 		case PPPIOCGCHAN:
424282361a0SGreg Kroah-Hartman 			{
425282361a0SGreg Kroah-Hartman 				int chan = ipwireless_ppp_channel_index(
426282361a0SGreg Kroah-Hartman 							tty->network);
427282361a0SGreg Kroah-Hartman 
428282361a0SGreg Kroah-Hartman 				if (chan < 0)
429282361a0SGreg Kroah-Hartman 					return -ENODEV;
430282361a0SGreg Kroah-Hartman 				if (put_user(chan, (int __user *) arg))
431282361a0SGreg Kroah-Hartman 					return -EFAULT;
432282361a0SGreg Kroah-Hartman 			}
433282361a0SGreg Kroah-Hartman 			return 0;
434282361a0SGreg Kroah-Hartman 
435282361a0SGreg Kroah-Hartman 		case PPPIOCGUNIT:
436282361a0SGreg Kroah-Hartman 			{
437282361a0SGreg Kroah-Hartman 				int unit = ipwireless_ppp_unit_number(
438282361a0SGreg Kroah-Hartman 						tty->network);
439282361a0SGreg Kroah-Hartman 
440282361a0SGreg Kroah-Hartman 				if (unit < 0)
441282361a0SGreg Kroah-Hartman 					return -ENODEV;
442282361a0SGreg Kroah-Hartman 				if (put_user(unit, (int __user *) arg))
443282361a0SGreg Kroah-Hartman 					return -EFAULT;
444282361a0SGreg Kroah-Hartman 			}
445282361a0SGreg Kroah-Hartman 			return 0;
446282361a0SGreg Kroah-Hartman 
447282361a0SGreg Kroah-Hartman 		case FIONREAD:
448282361a0SGreg Kroah-Hartman 			{
449282361a0SGreg Kroah-Hartman 				int val = 0;
450282361a0SGreg Kroah-Hartman 
451282361a0SGreg Kroah-Hartman 				if (put_user(val, (int __user *) arg))
452282361a0SGreg Kroah-Hartman 					return -EFAULT;
453282361a0SGreg Kroah-Hartman 			}
454282361a0SGreg Kroah-Hartman 			return 0;
455282361a0SGreg Kroah-Hartman 		case TCFLSH:
456282361a0SGreg Kroah-Hartman 			return tty_perform_flush(linux_tty, arg);
457282361a0SGreg Kroah-Hartman 		}
458282361a0SGreg Kroah-Hartman 	}
459282361a0SGreg Kroah-Hartman 	return -ENOIOCTLCMD;
460282361a0SGreg Kroah-Hartman }
461282361a0SGreg Kroah-Hartman 
462282361a0SGreg Kroah-Hartman static int add_tty(int j,
463282361a0SGreg Kroah-Hartman 		    struct ipw_hardware *hardware,
464282361a0SGreg Kroah-Hartman 		    struct ipw_network *network, int channel_idx,
465282361a0SGreg Kroah-Hartman 		    int secondary_channel_idx, int tty_type)
466282361a0SGreg Kroah-Hartman {
467282361a0SGreg Kroah-Hartman 	ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL);
468282361a0SGreg Kroah-Hartman 	if (!ttys[j])
469282361a0SGreg Kroah-Hartman 		return -ENOMEM;
470282361a0SGreg Kroah-Hartman 	ttys[j]->index = j;
471282361a0SGreg Kroah-Hartman 	ttys[j]->hardware = hardware;
472282361a0SGreg Kroah-Hartman 	ttys[j]->channel_idx = channel_idx;
473282361a0SGreg Kroah-Hartman 	ttys[j]->secondary_channel_idx = secondary_channel_idx;
474282361a0SGreg Kroah-Hartman 	ttys[j]->network = network;
475282361a0SGreg Kroah-Hartman 	ttys[j]->tty_type = tty_type;
476282361a0SGreg Kroah-Hartman 	mutex_init(&ttys[j]->ipw_tty_mutex);
4777393af80SJiri Slaby 	tty_port_init(&ttys[j]->port);
478282361a0SGreg Kroah-Hartman 
479734cc178SJiri Slaby 	tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);
480282361a0SGreg Kroah-Hartman 	ipwireless_associate_network_tty(network, channel_idx, ttys[j]);
481282361a0SGreg Kroah-Hartman 
482282361a0SGreg Kroah-Hartman 	if (secondary_channel_idx != -1)
483282361a0SGreg Kroah-Hartman 		ipwireless_associate_network_tty(network,
484282361a0SGreg Kroah-Hartman 						 secondary_channel_idx,
485282361a0SGreg Kroah-Hartman 						 ttys[j]);
486e6df3cceSJiri Slaby 	/* check if we provide raw device (if loopback is enabled) */
487e6df3cceSJiri Slaby 	if (get_tty(j))
488e6df3cceSJiri Slaby 		printk(KERN_INFO IPWIRELESS_PCCARD_NAME
489e6df3cceSJiri Slaby 		       ": registering %s device ttyIPWp%d\n",
490e6df3cceSJiri Slaby 		       tty_type_name(tty_type), j);
491e6df3cceSJiri Slaby 
492282361a0SGreg Kroah-Hartman 	return 0;
493282361a0SGreg Kroah-Hartman }
494282361a0SGreg Kroah-Hartman 
495282361a0SGreg Kroah-Hartman struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware,
496282361a0SGreg Kroah-Hartman 				      struct ipw_network *network)
497282361a0SGreg Kroah-Hartman {
498282361a0SGreg Kroah-Hartman 	int i, j;
499282361a0SGreg Kroah-Hartman 
500282361a0SGreg Kroah-Hartman 	for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) {
501282361a0SGreg Kroah-Hartman 		int allfree = 1;
502282361a0SGreg Kroah-Hartman 
503282361a0SGreg Kroah-Hartman 		for (j = i; j < IPWIRELESS_PCMCIA_MINORS;
504282361a0SGreg Kroah-Hartman 				j += IPWIRELESS_PCMCIA_MINOR_RANGE)
505282361a0SGreg Kroah-Hartman 			if (ttys[j] != NULL) {
506282361a0SGreg Kroah-Hartman 				allfree = 0;
507282361a0SGreg Kroah-Hartman 				break;
508282361a0SGreg Kroah-Hartman 			}
509282361a0SGreg Kroah-Hartman 
510282361a0SGreg Kroah-Hartman 		if (allfree) {
511282361a0SGreg Kroah-Hartman 			j = i;
512282361a0SGreg Kroah-Hartman 
513282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
514282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS,
515282361a0SGreg Kroah-Hartman 					TTYTYPE_MODEM))
516282361a0SGreg Kroah-Hartman 				return NULL;
517282361a0SGreg Kroah-Hartman 
518282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
519282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
520282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, -1,
521282361a0SGreg Kroah-Hartman 					TTYTYPE_MONITOR))
522282361a0SGreg Kroah-Hartman 				return NULL;
523282361a0SGreg Kroah-Hartman 
524282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
525282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
526282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_RAS, -1,
527282361a0SGreg Kroah-Hartman 					TTYTYPE_RAS_RAW))
528282361a0SGreg Kroah-Hartman 				return NULL;
529282361a0SGreg Kroah-Hartman 
530282361a0SGreg Kroah-Hartman 			return ttys[i];
531282361a0SGreg Kroah-Hartman 		}
532282361a0SGreg Kroah-Hartman 	}
533282361a0SGreg Kroah-Hartman 	return NULL;
534282361a0SGreg Kroah-Hartman }
535282361a0SGreg Kroah-Hartman 
536282361a0SGreg Kroah-Hartman /*
537282361a0SGreg Kroah-Hartman  * Must be called before ipwireless_network_free().
538282361a0SGreg Kroah-Hartman  */
539282361a0SGreg Kroah-Hartman void ipwireless_tty_free(struct ipw_tty *tty)
540282361a0SGreg Kroah-Hartman {
541282361a0SGreg Kroah-Hartman 	int j;
542282361a0SGreg Kroah-Hartman 	struct ipw_network *network = ttys[tty->index]->network;
543282361a0SGreg Kroah-Hartman 
544282361a0SGreg Kroah-Hartman 	for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS;
545282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE) {
546282361a0SGreg Kroah-Hartman 		struct ipw_tty *ttyj = ttys[j];
547282361a0SGreg Kroah-Hartman 
548282361a0SGreg Kroah-Hartman 		if (ttyj) {
549282361a0SGreg Kroah-Hartman 			mutex_lock(&ttyj->ipw_tty_mutex);
550e6df3cceSJiri Slaby 			if (get_tty(j))
551e6df3cceSJiri Slaby 				printk(KERN_INFO IPWIRELESS_PCCARD_NAME
552e6df3cceSJiri Slaby 				       ": deregistering %s device ttyIPWp%d\n",
553e6df3cceSJiri Slaby 				       tty_type_name(ttyj->tty_type), j);
554282361a0SGreg Kroah-Hartman 			ttyj->closing = 1;
55519ef1b71SJiri Slaby 			if (ttyj->port.tty != NULL) {
556282361a0SGreg Kroah-Hartman 				mutex_unlock(&ttyj->ipw_tty_mutex);
55719ef1b71SJiri Slaby 				tty_vhangup(ttyj->port.tty);
558282361a0SGreg Kroah-Hartman 				/* FIXME: Exactly how is the tty object locked here
559282361a0SGreg Kroah-Hartman 				   against a parallel ioctl etc */
560de3a60a3SJiri Slaby 				/* FIXME2: hangup does not mean all processes
561de3a60a3SJiri Slaby 				 * are gone */
562282361a0SGreg Kroah-Hartman 				mutex_lock(&ttyj->ipw_tty_mutex);
563282361a0SGreg Kroah-Hartman 			}
5647393af80SJiri Slaby 			while (ttyj->port.count)
565282361a0SGreg Kroah-Hartman 				do_ipw_close(ttyj);
566282361a0SGreg Kroah-Hartman 			ipwireless_disassociate_network_ttys(network,
567282361a0SGreg Kroah-Hartman 							     ttyj->channel_idx);
568282361a0SGreg Kroah-Hartman 			tty_unregister_device(ipw_tty_driver, j);
569191c5f10SJiri Slaby 			tty_port_destroy(&ttyj->port);
570282361a0SGreg Kroah-Hartman 			ttys[j] = NULL;
571282361a0SGreg Kroah-Hartman 			mutex_unlock(&ttyj->ipw_tty_mutex);
572282361a0SGreg Kroah-Hartman 			kfree(ttyj);
573282361a0SGreg Kroah-Hartman 		}
574282361a0SGreg Kroah-Hartman 	}
575282361a0SGreg Kroah-Hartman }
576282361a0SGreg Kroah-Hartman 
577282361a0SGreg Kroah-Hartman static const struct tty_operations tty_ops = {
578282361a0SGreg Kroah-Hartman 	.open = ipw_open,
579282361a0SGreg Kroah-Hartman 	.close = ipw_close,
580282361a0SGreg Kroah-Hartman 	.hangup = ipw_hangup,
581282361a0SGreg Kroah-Hartman 	.write = ipw_write,
582282361a0SGreg Kroah-Hartman 	.write_room = ipw_write_room,
583282361a0SGreg Kroah-Hartman 	.ioctl = ipw_ioctl,
584282361a0SGreg Kroah-Hartman 	.chars_in_buffer = ipw_chars_in_buffer,
585282361a0SGreg Kroah-Hartman 	.tiocmget = ipw_tiocmget,
586282361a0SGreg Kroah-Hartman 	.tiocmset = ipw_tiocmset,
587282361a0SGreg Kroah-Hartman };
588282361a0SGreg Kroah-Hartman 
589282361a0SGreg Kroah-Hartman int ipwireless_tty_init(void)
590282361a0SGreg Kroah-Hartman {
591282361a0SGreg Kroah-Hartman 	int result;
592282361a0SGreg Kroah-Hartman 
593282361a0SGreg Kroah-Hartman 	ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS);
594282361a0SGreg Kroah-Hartman 	if (!ipw_tty_driver)
595282361a0SGreg Kroah-Hartman 		return -ENOMEM;
596282361a0SGreg Kroah-Hartman 
597282361a0SGreg Kroah-Hartman 	ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME;
598282361a0SGreg Kroah-Hartman 	ipw_tty_driver->name = "ttyIPWp";
599282361a0SGreg Kroah-Hartman 	ipw_tty_driver->major = 0;
600282361a0SGreg Kroah-Hartman 	ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START;
601282361a0SGreg Kroah-Hartman 	ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
602282361a0SGreg Kroah-Hartman 	ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL;
603282361a0SGreg Kroah-Hartman 	ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
604282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios = tty_std_termios;
605282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_cflag =
606282361a0SGreg Kroah-Hartman 	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
607282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ispeed = 9600;
608282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ospeed = 9600;
609282361a0SGreg Kroah-Hartman 	tty_set_operations(ipw_tty_driver, &tty_ops);
610282361a0SGreg Kroah-Hartman 	result = tty_register_driver(ipw_tty_driver);
611282361a0SGreg Kroah-Hartman 	if (result) {
612282361a0SGreg Kroah-Hartman 		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
613282361a0SGreg Kroah-Hartman 		       ": failed to register tty driver\n");
614282361a0SGreg Kroah-Hartman 		put_tty_driver(ipw_tty_driver);
615282361a0SGreg Kroah-Hartman 		return result;
616282361a0SGreg Kroah-Hartman 	}
617282361a0SGreg Kroah-Hartman 
618282361a0SGreg Kroah-Hartman 	return 0;
619282361a0SGreg Kroah-Hartman }
620282361a0SGreg Kroah-Hartman 
621282361a0SGreg Kroah-Hartman void ipwireless_tty_release(void)
622282361a0SGreg Kroah-Hartman {
623282361a0SGreg Kroah-Hartman 	int ret;
624282361a0SGreg Kroah-Hartman 
625282361a0SGreg Kroah-Hartman 	ret = tty_unregister_driver(ipw_tty_driver);
626282361a0SGreg Kroah-Hartman 	put_tty_driver(ipw_tty_driver);
627282361a0SGreg Kroah-Hartman 	if (ret != 0)
628282361a0SGreg Kroah-Hartman 		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
629282361a0SGreg Kroah-Hartman 			": tty_unregister_driver failed with code %d\n", ret);
630282361a0SGreg Kroah-Hartman }
631282361a0SGreg Kroah-Hartman 
632282361a0SGreg Kroah-Hartman int ipwireless_tty_is_modem(struct ipw_tty *tty)
633282361a0SGreg Kroah-Hartman {
634282361a0SGreg Kroah-Hartman 	return tty->tty_type == TTYTYPE_MODEM;
635282361a0SGreg Kroah-Hartman }
636282361a0SGreg Kroah-Hartman 
637282361a0SGreg Kroah-Hartman void
638282361a0SGreg Kroah-Hartman ipwireless_tty_notify_control_line_change(struct ipw_tty *tty,
639282361a0SGreg Kroah-Hartman 					  unsigned int channel_idx,
640282361a0SGreg Kroah-Hartman 					  unsigned int control_lines,
641282361a0SGreg Kroah-Hartman 					  unsigned int changed_mask)
642282361a0SGreg Kroah-Hartman {
643282361a0SGreg Kroah-Hartman 	unsigned int old_control_lines = tty->control_lines;
644282361a0SGreg Kroah-Hartman 
645282361a0SGreg Kroah-Hartman 	tty->control_lines = (tty->control_lines & ~changed_mask)
646282361a0SGreg Kroah-Hartman 		| (control_lines & changed_mask);
647282361a0SGreg Kroah-Hartman 
648282361a0SGreg Kroah-Hartman 	/*
649282361a0SGreg Kroah-Hartman 	 * If DCD is de-asserted, we close the tty so pppd can tell that we
650282361a0SGreg Kroah-Hartman 	 * have gone offline.
651282361a0SGreg Kroah-Hartman 	 */
652282361a0SGreg Kroah-Hartman 	if ((old_control_lines & IPW_CONTROL_LINE_DCD)
653282361a0SGreg Kroah-Hartman 			&& !(tty->control_lines & IPW_CONTROL_LINE_DCD)
65419ef1b71SJiri Slaby 			&& tty->port.tty) {
65519ef1b71SJiri Slaby 		tty_hangup(tty->port.tty);
656282361a0SGreg Kroah-Hartman 	}
657282361a0SGreg Kroah-Hartman }
658282361a0SGreg Kroah-Hartman 
659