xref: /openbmc/linux/drivers/tty/ipwireless/tty.c (revision 03b3b1a2)
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 	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);
977393af80SJiri Slaby 	if (tty->port.count == 0)
98282361a0SGreg Kroah-Hartman 		tty->tx_bytes_queued = 0;
99282361a0SGreg Kroah-Hartman 
1007393af80SJiri Slaby 	tty->port.count++;
101282361a0SGreg Kroah-Hartman 
10219ef1b71SJiri Slaby 	tty->port.tty = linux_tty;
103282361a0SGreg Kroah-Hartman 	linux_tty->driver_data = tty;
104282361a0SGreg Kroah-Hartman 
105282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM)
106282361a0SGreg Kroah-Hartman 		ipwireless_ppp_open(tty->network);
107282361a0SGreg Kroah-Hartman 
108282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
109282361a0SGreg Kroah-Hartman 
110282361a0SGreg Kroah-Hartman 	return 0;
111282361a0SGreg Kroah-Hartman }
112282361a0SGreg Kroah-Hartman 
113282361a0SGreg Kroah-Hartman static void do_ipw_close(struct ipw_tty *tty)
114282361a0SGreg Kroah-Hartman {
1157393af80SJiri Slaby 	tty->port.count--;
116282361a0SGreg Kroah-Hartman 
1177393af80SJiri Slaby 	if (tty->port.count == 0) {
11819ef1b71SJiri Slaby 		struct tty_struct *linux_tty = tty->port.tty;
119282361a0SGreg Kroah-Hartman 
120282361a0SGreg Kroah-Hartman 		if (linux_tty != NULL) {
12119ef1b71SJiri Slaby 			tty->port.tty = NULL;
122282361a0SGreg Kroah-Hartman 			linux_tty->driver_data = NULL;
123282361a0SGreg Kroah-Hartman 
124282361a0SGreg Kroah-Hartman 			if (tty->tty_type == TTYTYPE_MODEM)
125282361a0SGreg Kroah-Hartman 				ipwireless_ppp_close(tty->network);
126282361a0SGreg Kroah-Hartman 		}
127282361a0SGreg Kroah-Hartman 	}
128282361a0SGreg Kroah-Hartman }
129282361a0SGreg Kroah-Hartman 
130282361a0SGreg Kroah-Hartman static void ipw_hangup(struct tty_struct *linux_tty)
131282361a0SGreg Kroah-Hartman {
132282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
133282361a0SGreg Kroah-Hartman 
134282361a0SGreg Kroah-Hartman 	if (!tty)
135282361a0SGreg Kroah-Hartman 		return;
136282361a0SGreg Kroah-Hartman 
137282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
1387393af80SJiri Slaby 	if (tty->port.count == 0) {
139282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
140282361a0SGreg Kroah-Hartman 		return;
141282361a0SGreg Kroah-Hartman 	}
142282361a0SGreg Kroah-Hartman 
143282361a0SGreg Kroah-Hartman 	do_ipw_close(tty);
144282361a0SGreg Kroah-Hartman 
145282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
146282361a0SGreg Kroah-Hartman }
147282361a0SGreg Kroah-Hartman 
148282361a0SGreg Kroah-Hartman static void ipw_close(struct tty_struct *linux_tty, struct file *filp)
149282361a0SGreg Kroah-Hartman {
150282361a0SGreg Kroah-Hartman 	ipw_hangup(linux_tty);
151282361a0SGreg Kroah-Hartman }
152282361a0SGreg Kroah-Hartman 
153282361a0SGreg Kroah-Hartman /* Take data received from hardware, and send it out the tty */
154282361a0SGreg Kroah-Hartman void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data,
155282361a0SGreg Kroah-Hartman 			unsigned int length)
156282361a0SGreg Kroah-Hartman {
157282361a0SGreg Kroah-Hartman 	int work = 0;
158282361a0SGreg Kroah-Hartman 
159282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
160282361a0SGreg Kroah-Hartman 
1617393af80SJiri Slaby 	if (!tty->port.count) {
162282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
163282361a0SGreg Kroah-Hartman 		return;
164282361a0SGreg Kroah-Hartman 	}
165282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
166282361a0SGreg Kroah-Hartman 
16705c7cd39SJiri Slaby 	work = tty_insert_flip_string(&tty->port, data, length);
168282361a0SGreg Kroah-Hartman 
169282361a0SGreg Kroah-Hartman 	if (work != length)
170282361a0SGreg Kroah-Hartman 		printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME
171282361a0SGreg Kroah-Hartman 				": %d chars not inserted to flip buffer!\n",
172282361a0SGreg Kroah-Hartman 				length - work);
173282361a0SGreg Kroah-Hartman 
174282361a0SGreg Kroah-Hartman 	if (work)
1752e124b4aSJiri Slaby 		tty_flip_buffer_push(&tty->port);
176282361a0SGreg Kroah-Hartman }
177282361a0SGreg Kroah-Hartman 
178282361a0SGreg Kroah-Hartman static void ipw_write_packet_sent_callback(void *callback_data,
179282361a0SGreg Kroah-Hartman 					   unsigned int packet_length)
180282361a0SGreg Kroah-Hartman {
181282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = callback_data;
182282361a0SGreg Kroah-Hartman 
183282361a0SGreg Kroah-Hartman 	/*
184282361a0SGreg Kroah-Hartman 	 * Packet has been sent, so we subtract the number of bytes from our
185282361a0SGreg Kroah-Hartman 	 * tally of outstanding TX bytes.
186282361a0SGreg Kroah-Hartman 	 */
187282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued -= packet_length;
188282361a0SGreg Kroah-Hartman }
189282361a0SGreg Kroah-Hartman 
190282361a0SGreg Kroah-Hartman static int ipw_write(struct tty_struct *linux_tty,
191282361a0SGreg Kroah-Hartman 		     const unsigned char *buf, int count)
192282361a0SGreg Kroah-Hartman {
193282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
194282361a0SGreg Kroah-Hartman 	int room, ret;
195282361a0SGreg Kroah-Hartman 
196282361a0SGreg Kroah-Hartman 	if (!tty)
197282361a0SGreg Kroah-Hartman 		return -ENODEV;
198282361a0SGreg Kroah-Hartman 
199282361a0SGreg Kroah-Hartman 	mutex_lock(&tty->ipw_tty_mutex);
2007393af80SJiri Slaby 	if (!tty->port.count) {
201282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
202282361a0SGreg Kroah-Hartman 		return -EINVAL;
203282361a0SGreg Kroah-Hartman 	}
204282361a0SGreg Kroah-Hartman 
205282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
206282361a0SGreg Kroah-Hartman 	if (room < 0)
207282361a0SGreg Kroah-Hartman 		room = 0;
208282361a0SGreg Kroah-Hartman 	/* Don't allow caller to write any more than we have room for */
209282361a0SGreg Kroah-Hartman 	if (count > room)
210282361a0SGreg Kroah-Hartman 		count = room;
211282361a0SGreg Kroah-Hartman 
212282361a0SGreg Kroah-Hartman 	if (count == 0) {
213282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
214282361a0SGreg Kroah-Hartman 		return 0;
215282361a0SGreg Kroah-Hartman 	}
216282361a0SGreg Kroah-Hartman 
217282361a0SGreg Kroah-Hartman 	ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,
218282361a0SGreg Kroah-Hartman 			       buf, count,
219282361a0SGreg Kroah-Hartman 			       ipw_write_packet_sent_callback, tty);
220db332356STong Zhang 	if (ret < 0) {
221282361a0SGreg Kroah-Hartman 		mutex_unlock(&tty->ipw_tty_mutex);
222282361a0SGreg Kroah-Hartman 		return 0;
223282361a0SGreg Kroah-Hartman 	}
224282361a0SGreg Kroah-Hartman 
225282361a0SGreg Kroah-Hartman 	tty->tx_bytes_queued += count;
226282361a0SGreg Kroah-Hartman 	mutex_unlock(&tty->ipw_tty_mutex);
227282361a0SGreg Kroah-Hartman 
228282361a0SGreg Kroah-Hartman 	return count;
229282361a0SGreg Kroah-Hartman }
230282361a0SGreg Kroah-Hartman 
231*03b3b1a2SJiri Slaby static unsigned int ipw_write_room(struct tty_struct *linux_tty)
232282361a0SGreg Kroah-Hartman {
233282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
234282361a0SGreg Kroah-Hartman 	int room;
235282361a0SGreg Kroah-Hartman 
236282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
237282361a0SGreg Kroah-Hartman 	if (!tty)
2386bfbfcfcSJiri Slaby 		return 0;
239282361a0SGreg Kroah-Hartman 
2407393af80SJiri Slaby 	if (!tty->port.count)
2416bfbfcfcSJiri Slaby 		return 0;
242282361a0SGreg Kroah-Hartman 
243282361a0SGreg Kroah-Hartman 	room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued;
244282361a0SGreg Kroah-Hartman 	if (room < 0)
245282361a0SGreg Kroah-Hartman 		room = 0;
246282361a0SGreg Kroah-Hartman 
247282361a0SGreg Kroah-Hartman 	return room;
248282361a0SGreg Kroah-Hartman }
249282361a0SGreg Kroah-Hartman 
250a7b06fcfSAl Viro static int ipwireless_get_serial_info(struct tty_struct *linux_tty,
251a7b06fcfSAl Viro 				      struct serial_struct *ss)
252282361a0SGreg Kroah-Hartman {
253a7b06fcfSAl Viro 	struct ipw_tty *tty = linux_tty->driver_data;
254282361a0SGreg Kroah-Hartman 
255a7b06fcfSAl Viro 	if (!tty)
256a7b06fcfSAl Viro 		return -ENODEV;
257fec4daecSJiri Slaby 
258a7b06fcfSAl Viro 	if (!tty->port.count)
259a7b06fcfSAl Viro 		return -EINVAL;
260282361a0SGreg Kroah-Hartman 
261a7b06fcfSAl Viro 	ss->type = PORT_UNKNOWN;
262a7b06fcfSAl Viro 	ss->line = tty->index;
263a7b06fcfSAl Viro 	ss->baud_base = 115200;
264282361a0SGreg Kroah-Hartman 	return 0;
265282361a0SGreg Kroah-Hartman }
266282361a0SGreg Kroah-Hartman 
267a7b06fcfSAl Viro static int ipwireless_set_serial_info(struct tty_struct *linux_tty,
268a7b06fcfSAl Viro 				      struct serial_struct *ss)
269a7b06fcfSAl Viro {
270a7b06fcfSAl Viro 	return 0;	/* Keeps the PCMCIA scripts happy. */
271a7b06fcfSAl Viro }
272a7b06fcfSAl Viro 
273282361a0SGreg Kroah-Hartman static int ipw_chars_in_buffer(struct tty_struct *linux_tty)
274282361a0SGreg Kroah-Hartman {
275282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
276282361a0SGreg Kroah-Hartman 
277282361a0SGreg Kroah-Hartman 	if (!tty)
278282361a0SGreg Kroah-Hartman 		return 0;
279282361a0SGreg Kroah-Hartman 
2807393af80SJiri Slaby 	if (!tty->port.count)
281282361a0SGreg Kroah-Hartman 		return 0;
282282361a0SGreg Kroah-Hartman 
283282361a0SGreg Kroah-Hartman 	return tty->tx_bytes_queued;
284282361a0SGreg Kroah-Hartman }
285282361a0SGreg Kroah-Hartman 
286282361a0SGreg Kroah-Hartman static int get_control_lines(struct ipw_tty *tty)
287282361a0SGreg Kroah-Hartman {
288282361a0SGreg Kroah-Hartman 	unsigned int my = tty->control_lines;
289282361a0SGreg Kroah-Hartman 	unsigned int out = 0;
290282361a0SGreg Kroah-Hartman 
291282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_RTS)
292282361a0SGreg Kroah-Hartman 		out |= TIOCM_RTS;
293282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DTR)
294282361a0SGreg Kroah-Hartman 		out |= TIOCM_DTR;
295282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_CTS)
296282361a0SGreg Kroah-Hartman 		out |= TIOCM_CTS;
297282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DSR)
298282361a0SGreg Kroah-Hartman 		out |= TIOCM_DSR;
299282361a0SGreg Kroah-Hartman 	if (my & IPW_CONTROL_LINE_DCD)
300282361a0SGreg Kroah-Hartman 		out |= TIOCM_CD;
301282361a0SGreg Kroah-Hartman 
302282361a0SGreg Kroah-Hartman 	return out;
303282361a0SGreg Kroah-Hartman }
304282361a0SGreg Kroah-Hartman 
305282361a0SGreg Kroah-Hartman static int set_control_lines(struct ipw_tty *tty, unsigned int set,
306282361a0SGreg Kroah-Hartman 			     unsigned int clear)
307282361a0SGreg Kroah-Hartman {
308282361a0SGreg Kroah-Hartman 	int ret;
309282361a0SGreg Kroah-Hartman 
310282361a0SGreg Kroah-Hartman 	if (set & TIOCM_RTS) {
311282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1);
312282361a0SGreg Kroah-Hartman 		if (ret)
313282361a0SGreg Kroah-Hartman 			return ret;
314282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
315282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
316282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 1);
317282361a0SGreg Kroah-Hartman 			if (ret)
318282361a0SGreg Kroah-Hartman 				return ret;
319282361a0SGreg Kroah-Hartman 		}
320282361a0SGreg Kroah-Hartman 	}
321282361a0SGreg Kroah-Hartman 	if (set & TIOCM_DTR) {
322282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);
323282361a0SGreg Kroah-Hartman 		if (ret)
324282361a0SGreg Kroah-Hartman 			return ret;
325282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
326282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_DTR(tty->hardware,
327282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 1);
328282361a0SGreg Kroah-Hartman 			if (ret)
329282361a0SGreg Kroah-Hartman 				return ret;
330282361a0SGreg Kroah-Hartman 		}
331282361a0SGreg Kroah-Hartman 	}
332282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_RTS) {
333282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);
334282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
335282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_RTS(tty->hardware,
336282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
337282361a0SGreg Kroah-Hartman 			if (ret)
338282361a0SGreg Kroah-Hartman 				return ret;
339282361a0SGreg Kroah-Hartman 		}
340282361a0SGreg Kroah-Hartman 	}
341282361a0SGreg Kroah-Hartman 	if (clear & TIOCM_DTR) {
342282361a0SGreg Kroah-Hartman 		ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);
343282361a0SGreg Kroah-Hartman 		if (tty->secondary_channel_idx != -1) {
344282361a0SGreg Kroah-Hartman 			ret = ipwireless_set_DTR(tty->hardware,
345282361a0SGreg Kroah-Hartman 					  tty->secondary_channel_idx, 0);
346282361a0SGreg Kroah-Hartman 			if (ret)
347282361a0SGreg Kroah-Hartman 				return ret;
348282361a0SGreg Kroah-Hartman 		}
349282361a0SGreg Kroah-Hartman 	}
350282361a0SGreg Kroah-Hartman 	return 0;
351282361a0SGreg Kroah-Hartman }
352282361a0SGreg Kroah-Hartman 
353282361a0SGreg Kroah-Hartman static int ipw_tiocmget(struct tty_struct *linux_tty)
354282361a0SGreg Kroah-Hartman {
355282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
356282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
357282361a0SGreg Kroah-Hartman 
358282361a0SGreg Kroah-Hartman 	if (!tty)
359282361a0SGreg Kroah-Hartman 		return -ENODEV;
360282361a0SGreg Kroah-Hartman 
3617393af80SJiri Slaby 	if (!tty->port.count)
362282361a0SGreg Kroah-Hartman 		return -EINVAL;
363282361a0SGreg Kroah-Hartman 
364282361a0SGreg Kroah-Hartman 	return get_control_lines(tty);
365282361a0SGreg Kroah-Hartman }
366282361a0SGreg Kroah-Hartman 
367282361a0SGreg Kroah-Hartman static int
368282361a0SGreg Kroah-Hartman ipw_tiocmset(struct tty_struct *linux_tty,
369282361a0SGreg Kroah-Hartman 	     unsigned int set, unsigned int clear)
370282361a0SGreg Kroah-Hartman {
371282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
372282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
373282361a0SGreg Kroah-Hartman 
374282361a0SGreg Kroah-Hartman 	if (!tty)
375282361a0SGreg Kroah-Hartman 		return -ENODEV;
376282361a0SGreg Kroah-Hartman 
3777393af80SJiri Slaby 	if (!tty->port.count)
378282361a0SGreg Kroah-Hartman 		return -EINVAL;
379282361a0SGreg Kroah-Hartman 
380282361a0SGreg Kroah-Hartman 	return set_control_lines(tty, set, clear);
381282361a0SGreg Kroah-Hartman }
382282361a0SGreg Kroah-Hartman 
383282361a0SGreg Kroah-Hartman static int ipw_ioctl(struct tty_struct *linux_tty,
384282361a0SGreg Kroah-Hartman 		     unsigned int cmd, unsigned long arg)
385282361a0SGreg Kroah-Hartman {
386282361a0SGreg Kroah-Hartman 	struct ipw_tty *tty = linux_tty->driver_data;
387282361a0SGreg Kroah-Hartman 
388282361a0SGreg Kroah-Hartman 	if (!tty)
389282361a0SGreg Kroah-Hartman 		return -ENODEV;
390282361a0SGreg Kroah-Hartman 
3917393af80SJiri Slaby 	if (!tty->port.count)
392282361a0SGreg Kroah-Hartman 		return -EINVAL;
393282361a0SGreg Kroah-Hartman 
394282361a0SGreg Kroah-Hartman 	/* FIXME: Exactly how is the tty object locked here .. */
395282361a0SGreg Kroah-Hartman 	if (tty->tty_type == TTYTYPE_MODEM) {
396282361a0SGreg Kroah-Hartman 		switch (cmd) {
397282361a0SGreg Kroah-Hartman 		case PPPIOCGCHAN:
398282361a0SGreg Kroah-Hartman 			{
399282361a0SGreg Kroah-Hartman 				int chan = ipwireless_ppp_channel_index(
400282361a0SGreg Kroah-Hartman 							tty->network);
401282361a0SGreg Kroah-Hartman 
402282361a0SGreg Kroah-Hartman 				if (chan < 0)
403282361a0SGreg Kroah-Hartman 					return -ENODEV;
404282361a0SGreg Kroah-Hartman 				if (put_user(chan, (int __user *) arg))
405282361a0SGreg Kroah-Hartman 					return -EFAULT;
406282361a0SGreg Kroah-Hartman 			}
407282361a0SGreg Kroah-Hartman 			return 0;
408282361a0SGreg Kroah-Hartman 
409282361a0SGreg Kroah-Hartman 		case PPPIOCGUNIT:
410282361a0SGreg Kroah-Hartman 			{
411282361a0SGreg Kroah-Hartman 				int unit = ipwireless_ppp_unit_number(
412282361a0SGreg Kroah-Hartman 						tty->network);
413282361a0SGreg Kroah-Hartman 
414282361a0SGreg Kroah-Hartman 				if (unit < 0)
415282361a0SGreg Kroah-Hartman 					return -ENODEV;
416282361a0SGreg Kroah-Hartman 				if (put_user(unit, (int __user *) arg))
417282361a0SGreg Kroah-Hartman 					return -EFAULT;
418282361a0SGreg Kroah-Hartman 			}
419282361a0SGreg Kroah-Hartman 			return 0;
420282361a0SGreg Kroah-Hartman 
421282361a0SGreg Kroah-Hartman 		case FIONREAD:
422282361a0SGreg Kroah-Hartman 			{
423282361a0SGreg Kroah-Hartman 				int val = 0;
424282361a0SGreg Kroah-Hartman 
425282361a0SGreg Kroah-Hartman 				if (put_user(val, (int __user *) arg))
426282361a0SGreg Kroah-Hartman 					return -EFAULT;
427282361a0SGreg Kroah-Hartman 			}
428282361a0SGreg Kroah-Hartman 			return 0;
429282361a0SGreg Kroah-Hartman 		case TCFLSH:
430282361a0SGreg Kroah-Hartman 			return tty_perform_flush(linux_tty, arg);
431282361a0SGreg Kroah-Hartman 		}
432282361a0SGreg Kroah-Hartman 	}
433282361a0SGreg Kroah-Hartman 	return -ENOIOCTLCMD;
434282361a0SGreg Kroah-Hartman }
435282361a0SGreg Kroah-Hartman 
436282361a0SGreg Kroah-Hartman static int add_tty(int j,
437282361a0SGreg Kroah-Hartman 		    struct ipw_hardware *hardware,
438282361a0SGreg Kroah-Hartman 		    struct ipw_network *network, int channel_idx,
439282361a0SGreg Kroah-Hartman 		    int secondary_channel_idx, int tty_type)
440282361a0SGreg Kroah-Hartman {
441282361a0SGreg Kroah-Hartman 	ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL);
442282361a0SGreg Kroah-Hartman 	if (!ttys[j])
443282361a0SGreg Kroah-Hartman 		return -ENOMEM;
444282361a0SGreg Kroah-Hartman 	ttys[j]->index = j;
445282361a0SGreg Kroah-Hartman 	ttys[j]->hardware = hardware;
446282361a0SGreg Kroah-Hartman 	ttys[j]->channel_idx = channel_idx;
447282361a0SGreg Kroah-Hartman 	ttys[j]->secondary_channel_idx = secondary_channel_idx;
448282361a0SGreg Kroah-Hartman 	ttys[j]->network = network;
449282361a0SGreg Kroah-Hartman 	ttys[j]->tty_type = tty_type;
450282361a0SGreg Kroah-Hartman 	mutex_init(&ttys[j]->ipw_tty_mutex);
4517393af80SJiri Slaby 	tty_port_init(&ttys[j]->port);
452282361a0SGreg Kroah-Hartman 
453734cc178SJiri Slaby 	tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);
454282361a0SGreg Kroah-Hartman 	ipwireless_associate_network_tty(network, channel_idx, ttys[j]);
455282361a0SGreg Kroah-Hartman 
456282361a0SGreg Kroah-Hartman 	if (secondary_channel_idx != -1)
457282361a0SGreg Kroah-Hartman 		ipwireless_associate_network_tty(network,
458282361a0SGreg Kroah-Hartman 						 secondary_channel_idx,
459282361a0SGreg Kroah-Hartman 						 ttys[j]);
460e6df3cceSJiri Slaby 	/* check if we provide raw device (if loopback is enabled) */
461e6df3cceSJiri Slaby 	if (get_tty(j))
462e6df3cceSJiri Slaby 		printk(KERN_INFO IPWIRELESS_PCCARD_NAME
463e6df3cceSJiri Slaby 		       ": registering %s device ttyIPWp%d\n",
464e6df3cceSJiri Slaby 		       tty_type_name(tty_type), j);
465e6df3cceSJiri Slaby 
466282361a0SGreg Kroah-Hartman 	return 0;
467282361a0SGreg Kroah-Hartman }
468282361a0SGreg Kroah-Hartman 
469282361a0SGreg Kroah-Hartman struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware,
470282361a0SGreg Kroah-Hartman 				      struct ipw_network *network)
471282361a0SGreg Kroah-Hartman {
472282361a0SGreg Kroah-Hartman 	int i, j;
473282361a0SGreg Kroah-Hartman 
474282361a0SGreg Kroah-Hartman 	for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) {
475282361a0SGreg Kroah-Hartman 		int allfree = 1;
476282361a0SGreg Kroah-Hartman 
477282361a0SGreg Kroah-Hartman 		for (j = i; j < IPWIRELESS_PCMCIA_MINORS;
478282361a0SGreg Kroah-Hartman 				j += IPWIRELESS_PCMCIA_MINOR_RANGE)
479282361a0SGreg Kroah-Hartman 			if (ttys[j] != NULL) {
480282361a0SGreg Kroah-Hartman 				allfree = 0;
481282361a0SGreg Kroah-Hartman 				break;
482282361a0SGreg Kroah-Hartman 			}
483282361a0SGreg Kroah-Hartman 
484282361a0SGreg Kroah-Hartman 		if (allfree) {
485282361a0SGreg Kroah-Hartman 			j = i;
486282361a0SGreg Kroah-Hartman 
487282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
488282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS,
489282361a0SGreg Kroah-Hartman 					TTYTYPE_MODEM))
490282361a0SGreg Kroah-Hartman 				return NULL;
491282361a0SGreg Kroah-Hartman 
492282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
493282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
494282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_DIALLER, -1,
495282361a0SGreg Kroah-Hartman 					TTYTYPE_MONITOR))
496282361a0SGreg Kroah-Hartman 				return NULL;
497282361a0SGreg Kroah-Hartman 
498282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE;
499282361a0SGreg Kroah-Hartman 			if (add_tty(j, hardware, network,
500282361a0SGreg Kroah-Hartman 					IPW_CHANNEL_RAS, -1,
501282361a0SGreg Kroah-Hartman 					TTYTYPE_RAS_RAW))
502282361a0SGreg Kroah-Hartman 				return NULL;
503282361a0SGreg Kroah-Hartman 
504282361a0SGreg Kroah-Hartman 			return ttys[i];
505282361a0SGreg Kroah-Hartman 		}
506282361a0SGreg Kroah-Hartman 	}
507282361a0SGreg Kroah-Hartman 	return NULL;
508282361a0SGreg Kroah-Hartman }
509282361a0SGreg Kroah-Hartman 
510282361a0SGreg Kroah-Hartman /*
511282361a0SGreg Kroah-Hartman  * Must be called before ipwireless_network_free().
512282361a0SGreg Kroah-Hartman  */
513282361a0SGreg Kroah-Hartman void ipwireless_tty_free(struct ipw_tty *tty)
514282361a0SGreg Kroah-Hartman {
515282361a0SGreg Kroah-Hartman 	int j;
516282361a0SGreg Kroah-Hartman 	struct ipw_network *network = ttys[tty->index]->network;
517282361a0SGreg Kroah-Hartman 
518282361a0SGreg Kroah-Hartman 	for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS;
519282361a0SGreg Kroah-Hartman 			j += IPWIRELESS_PCMCIA_MINOR_RANGE) {
520282361a0SGreg Kroah-Hartman 		struct ipw_tty *ttyj = ttys[j];
521282361a0SGreg Kroah-Hartman 
522282361a0SGreg Kroah-Hartman 		if (ttyj) {
523282361a0SGreg Kroah-Hartman 			mutex_lock(&ttyj->ipw_tty_mutex);
524e6df3cceSJiri Slaby 			if (get_tty(j))
525e6df3cceSJiri Slaby 				printk(KERN_INFO IPWIRELESS_PCCARD_NAME
526e6df3cceSJiri Slaby 				       ": deregistering %s device ttyIPWp%d\n",
527e6df3cceSJiri Slaby 				       tty_type_name(ttyj->tty_type), j);
528282361a0SGreg Kroah-Hartman 			ttyj->closing = 1;
52919ef1b71SJiri Slaby 			if (ttyj->port.tty != NULL) {
530282361a0SGreg Kroah-Hartman 				mutex_unlock(&ttyj->ipw_tty_mutex);
53119ef1b71SJiri Slaby 				tty_vhangup(ttyj->port.tty);
532282361a0SGreg Kroah-Hartman 				/* FIXME: Exactly how is the tty object locked here
533282361a0SGreg Kroah-Hartman 				   against a parallel ioctl etc */
534de3a60a3SJiri Slaby 				/* FIXME2: hangup does not mean all processes
535de3a60a3SJiri Slaby 				 * are gone */
536282361a0SGreg Kroah-Hartman 				mutex_lock(&ttyj->ipw_tty_mutex);
537282361a0SGreg Kroah-Hartman 			}
5387393af80SJiri Slaby 			while (ttyj->port.count)
539282361a0SGreg Kroah-Hartman 				do_ipw_close(ttyj);
540282361a0SGreg Kroah-Hartman 			ipwireless_disassociate_network_ttys(network,
541282361a0SGreg Kroah-Hartman 							     ttyj->channel_idx);
542282361a0SGreg Kroah-Hartman 			tty_unregister_device(ipw_tty_driver, j);
543191c5f10SJiri Slaby 			tty_port_destroy(&ttyj->port);
544282361a0SGreg Kroah-Hartman 			ttys[j] = NULL;
545282361a0SGreg Kroah-Hartman 			mutex_unlock(&ttyj->ipw_tty_mutex);
546282361a0SGreg Kroah-Hartman 			kfree(ttyj);
547282361a0SGreg Kroah-Hartman 		}
548282361a0SGreg Kroah-Hartman 	}
549282361a0SGreg Kroah-Hartman }
550282361a0SGreg Kroah-Hartman 
551282361a0SGreg Kroah-Hartman static const struct tty_operations tty_ops = {
552282361a0SGreg Kroah-Hartman 	.open = ipw_open,
553282361a0SGreg Kroah-Hartman 	.close = ipw_close,
554282361a0SGreg Kroah-Hartman 	.hangup = ipw_hangup,
555282361a0SGreg Kroah-Hartman 	.write = ipw_write,
556282361a0SGreg Kroah-Hartman 	.write_room = ipw_write_room,
557282361a0SGreg Kroah-Hartman 	.ioctl = ipw_ioctl,
558282361a0SGreg Kroah-Hartman 	.chars_in_buffer = ipw_chars_in_buffer,
559282361a0SGreg Kroah-Hartman 	.tiocmget = ipw_tiocmget,
560282361a0SGreg Kroah-Hartman 	.tiocmset = ipw_tiocmset,
561a7b06fcfSAl Viro 	.set_serial = ipwireless_set_serial_info,
562a7b06fcfSAl Viro 	.get_serial = ipwireless_get_serial_info,
563282361a0SGreg Kroah-Hartman };
564282361a0SGreg Kroah-Hartman 
565282361a0SGreg Kroah-Hartman int ipwireless_tty_init(void)
566282361a0SGreg Kroah-Hartman {
567282361a0SGreg Kroah-Hartman 	int result;
568282361a0SGreg Kroah-Hartman 
569282361a0SGreg Kroah-Hartman 	ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS);
570282361a0SGreg Kroah-Hartman 	if (!ipw_tty_driver)
571282361a0SGreg Kroah-Hartman 		return -ENOMEM;
572282361a0SGreg Kroah-Hartman 
573282361a0SGreg Kroah-Hartman 	ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME;
574282361a0SGreg Kroah-Hartman 	ipw_tty_driver->name = "ttyIPWp";
575282361a0SGreg Kroah-Hartman 	ipw_tty_driver->major = 0;
576282361a0SGreg Kroah-Hartman 	ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START;
577282361a0SGreg Kroah-Hartman 	ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
578282361a0SGreg Kroah-Hartman 	ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL;
579282361a0SGreg Kroah-Hartman 	ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
580282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios = tty_std_termios;
581282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_cflag =
582282361a0SGreg Kroah-Hartman 	    B9600 | CS8 | CREAD | HUPCL | CLOCAL;
583282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ispeed = 9600;
584282361a0SGreg Kroah-Hartman 	ipw_tty_driver->init_termios.c_ospeed = 9600;
585282361a0SGreg Kroah-Hartman 	tty_set_operations(ipw_tty_driver, &tty_ops);
586282361a0SGreg Kroah-Hartman 	result = tty_register_driver(ipw_tty_driver);
587282361a0SGreg Kroah-Hartman 	if (result) {
588282361a0SGreg Kroah-Hartman 		printk(KERN_ERR IPWIRELESS_PCCARD_NAME
589282361a0SGreg Kroah-Hartman 		       ": failed to register tty driver\n");
590282361a0SGreg Kroah-Hartman 		put_tty_driver(ipw_tty_driver);
591282361a0SGreg Kroah-Hartman 		return result;
592282361a0SGreg Kroah-Hartman 	}
593282361a0SGreg Kroah-Hartman 
594282361a0SGreg Kroah-Hartman 	return 0;
595282361a0SGreg Kroah-Hartman }
596282361a0SGreg Kroah-Hartman 
597282361a0SGreg Kroah-Hartman void ipwireless_tty_release(void)
598282361a0SGreg Kroah-Hartman {
5996c2e6317SJiri Slaby 	tty_unregister_driver(ipw_tty_driver);
600282361a0SGreg Kroah-Hartman 	put_tty_driver(ipw_tty_driver);
601282361a0SGreg Kroah-Hartman }
602282361a0SGreg Kroah-Hartman 
603282361a0SGreg Kroah-Hartman int ipwireless_tty_is_modem(struct ipw_tty *tty)
604282361a0SGreg Kroah-Hartman {
605282361a0SGreg Kroah-Hartman 	return tty->tty_type == TTYTYPE_MODEM;
606282361a0SGreg Kroah-Hartman }
607282361a0SGreg Kroah-Hartman 
608282361a0SGreg Kroah-Hartman void
609282361a0SGreg Kroah-Hartman ipwireless_tty_notify_control_line_change(struct ipw_tty *tty,
610282361a0SGreg Kroah-Hartman 					  unsigned int channel_idx,
611282361a0SGreg Kroah-Hartman 					  unsigned int control_lines,
612282361a0SGreg Kroah-Hartman 					  unsigned int changed_mask)
613282361a0SGreg Kroah-Hartman {
614282361a0SGreg Kroah-Hartman 	unsigned int old_control_lines = tty->control_lines;
615282361a0SGreg Kroah-Hartman 
616282361a0SGreg Kroah-Hartman 	tty->control_lines = (tty->control_lines & ~changed_mask)
617282361a0SGreg Kroah-Hartman 		| (control_lines & changed_mask);
618282361a0SGreg Kroah-Hartman 
619282361a0SGreg Kroah-Hartman 	/*
620282361a0SGreg Kroah-Hartman 	 * If DCD is de-asserted, we close the tty so pppd can tell that we
621282361a0SGreg Kroah-Hartman 	 * have gone offline.
622282361a0SGreg Kroah-Hartman 	 */
623282361a0SGreg Kroah-Hartman 	if ((old_control_lines & IPW_CONTROL_LINE_DCD)
624282361a0SGreg Kroah-Hartman 			&& !(tty->control_lines & IPW_CONTROL_LINE_DCD)
62519ef1b71SJiri Slaby 			&& tty->port.tty) {
62619ef1b71SJiri Slaby 		tty_hangup(tty->port.tty);
627282361a0SGreg Kroah-Hartman 	}
628282361a0SGreg Kroah-Hartman }
629282361a0SGreg Kroah-Hartman 
630