1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * IPWireless 3G PCMCIA Network Driver |
4 | * |
5 | * Original code |
6 | * by Stephen Blackheath <stephen@blacksapphire.com>, |
7 | * Ben Martel <benm@symmetric.co.nz> |
8 | * |
9 | * Copyrighted as follows: |
10 | * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) |
11 | * |
12 | * Various driver changes and rewrites, port to new kernels |
13 | * Copyright (C) 2006-2007 Jiri Kosina |
14 | * |
15 | * Misc code cleanups and updates |
16 | * Copyright (C) 2007 David Sterba |
17 | */ |
18 | |
19 | #include <linux/kernel.h> |
20 | #include <linux/module.h> |
21 | #include <linux/mutex.h> |
22 | #include <linux/ppp_defs.h> |
23 | #include <linux/if.h> |
24 | #include <linux/ppp-ioctl.h> |
25 | #include <linux/sched.h> |
26 | #include <linux/serial.h> |
27 | #include <linux/slab.h> |
28 | #include <linux/tty.h> |
29 | #include <linux/tty_driver.h> |
30 | #include <linux/tty_flip.h> |
31 | #include <linux/uaccess.h> |
32 | |
33 | #include "tty.h" |
34 | #include "network.h" |
35 | #include "hardware.h" |
36 | #include "main.h" |
37 | |
38 | #define IPWIRELESS_PCMCIA_START (0) |
39 | #define IPWIRELESS_PCMCIA_MINORS (24) |
40 | #define IPWIRELESS_PCMCIA_MINOR_RANGE (8) |
41 | |
42 | #define TTYTYPE_MODEM (0) |
43 | #define TTYTYPE_MONITOR (1) |
44 | #define TTYTYPE_RAS_RAW (2) |
45 | |
46 | struct ipw_tty { |
47 | struct tty_port port; |
48 | int index; |
49 | struct ipw_hardware *hardware; |
50 | unsigned int channel_idx; |
51 | unsigned int secondary_channel_idx; |
52 | int tty_type; |
53 | struct ipw_network *network; |
54 | unsigned int control_lines; |
55 | struct mutex ipw_tty_mutex; |
56 | int tx_bytes_queued; |
57 | }; |
58 | |
59 | static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS]; |
60 | |
61 | static struct tty_driver *ipw_tty_driver; |
62 | |
63 | static char *tty_type_name(int tty_type) |
64 | { |
65 | static char *channel_names[] = { |
66 | "modem" , |
67 | "monitor" , |
68 | "RAS-raw" |
69 | }; |
70 | |
71 | return channel_names[tty_type]; |
72 | } |
73 | |
74 | static struct ipw_tty *get_tty(int index) |
75 | { |
76 | /* |
77 | * The 'ras_raw' channel is only available when 'loopback' mode |
78 | * is enabled. |
79 | * Number of minor starts with 16 (_RANGE * _RAS_RAW). |
80 | */ |
81 | if (!ipwireless_loopback && index >= |
82 | IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW) |
83 | return NULL; |
84 | |
85 | return ttys[index]; |
86 | } |
87 | |
88 | static int ipw_open(struct tty_struct *linux_tty, struct file *filp) |
89 | { |
90 | struct ipw_tty *tty = get_tty(index: linux_tty->index); |
91 | |
92 | if (!tty) |
93 | return -ENODEV; |
94 | |
95 | mutex_lock(&tty->ipw_tty_mutex); |
96 | if (tty->port.count == 0) |
97 | tty->tx_bytes_queued = 0; |
98 | |
99 | tty->port.count++; |
100 | |
101 | tty->port.tty = linux_tty; |
102 | linux_tty->driver_data = tty; |
103 | |
104 | if (tty->tty_type == TTYTYPE_MODEM) |
105 | ipwireless_ppp_open(net: tty->network); |
106 | |
107 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | static void do_ipw_close(struct ipw_tty *tty) |
113 | { |
114 | tty->port.count--; |
115 | |
116 | if (tty->port.count == 0) { |
117 | struct tty_struct *linux_tty = tty->port.tty; |
118 | |
119 | if (linux_tty != NULL) { |
120 | tty->port.tty = NULL; |
121 | linux_tty->driver_data = NULL; |
122 | |
123 | if (tty->tty_type == TTYTYPE_MODEM) |
124 | ipwireless_ppp_close(net: tty->network); |
125 | } |
126 | } |
127 | } |
128 | |
129 | static void ipw_hangup(struct tty_struct *linux_tty) |
130 | { |
131 | struct ipw_tty *tty = linux_tty->driver_data; |
132 | |
133 | if (!tty) |
134 | return; |
135 | |
136 | mutex_lock(&tty->ipw_tty_mutex); |
137 | if (tty->port.count == 0) { |
138 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
139 | return; |
140 | } |
141 | |
142 | do_ipw_close(tty); |
143 | |
144 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
145 | } |
146 | |
147 | static void ipw_close(struct tty_struct *linux_tty, struct file *filp) |
148 | { |
149 | ipw_hangup(linux_tty); |
150 | } |
151 | |
152 | /* Take data received from hardware, and send it out the tty */ |
153 | void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, |
154 | unsigned int length) |
155 | { |
156 | int work = 0; |
157 | |
158 | mutex_lock(&tty->ipw_tty_mutex); |
159 | |
160 | if (!tty->port.count) { |
161 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
162 | return; |
163 | } |
164 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
165 | |
166 | work = tty_insert_flip_string(port: &tty->port, chars: data, size: length); |
167 | |
168 | if (work != length) |
169 | printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME |
170 | ": %d chars not inserted to flip buffer!\n" , |
171 | length - work); |
172 | |
173 | if (work) |
174 | tty_flip_buffer_push(port: &tty->port); |
175 | } |
176 | |
177 | static void ipw_write_packet_sent_callback(void *callback_data, |
178 | unsigned int packet_length) |
179 | { |
180 | struct ipw_tty *tty = callback_data; |
181 | |
182 | /* |
183 | * Packet has been sent, so we subtract the number of bytes from our |
184 | * tally of outstanding TX bytes. |
185 | */ |
186 | tty->tx_bytes_queued -= packet_length; |
187 | } |
188 | |
189 | static ssize_t ipw_write(struct tty_struct *linux_tty, const u8 *buf, |
190 | size_t count) |
191 | { |
192 | struct ipw_tty *tty = linux_tty->driver_data; |
193 | int room, ret; |
194 | |
195 | if (!tty) |
196 | return -ENODEV; |
197 | |
198 | mutex_lock(&tty->ipw_tty_mutex); |
199 | if (!tty->port.count) { |
200 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
201 | return -EINVAL; |
202 | } |
203 | |
204 | room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; |
205 | if (room < 0) |
206 | room = 0; |
207 | /* Don't allow caller to write any more than we have room for */ |
208 | if (count > room) |
209 | count = room; |
210 | |
211 | if (count == 0) { |
212 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
213 | return 0; |
214 | } |
215 | |
216 | ret = ipwireless_send_packet(hw: tty->hardware, IPW_CHANNEL_RAS, |
217 | data: buf, length: count, |
218 | packet_sent_callback: ipw_write_packet_sent_callback, sent_cb_data: tty); |
219 | if (ret < 0) { |
220 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
221 | return 0; |
222 | } |
223 | |
224 | tty->tx_bytes_queued += count; |
225 | mutex_unlock(lock: &tty->ipw_tty_mutex); |
226 | |
227 | return count; |
228 | } |
229 | |
230 | static unsigned int ipw_write_room(struct tty_struct *linux_tty) |
231 | { |
232 | struct ipw_tty *tty = linux_tty->driver_data; |
233 | int room; |
234 | |
235 | /* FIXME: Exactly how is the tty object locked here .. */ |
236 | if (!tty) |
237 | return 0; |
238 | |
239 | if (!tty->port.count) |
240 | return 0; |
241 | |
242 | room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; |
243 | if (room < 0) |
244 | room = 0; |
245 | |
246 | return room; |
247 | } |
248 | |
249 | static int ipwireless_get_serial_info(struct tty_struct *linux_tty, |
250 | struct serial_struct *ss) |
251 | { |
252 | struct ipw_tty *tty = linux_tty->driver_data; |
253 | |
254 | if (!tty) |
255 | return -ENODEV; |
256 | |
257 | if (!tty->port.count) |
258 | return -EINVAL; |
259 | |
260 | ss->type = PORT_UNKNOWN; |
261 | ss->line = tty->index; |
262 | ss->baud_base = 115200; |
263 | return 0; |
264 | } |
265 | |
266 | static int ipwireless_set_serial_info(struct tty_struct *linux_tty, |
267 | struct serial_struct *ss) |
268 | { |
269 | return 0; /* Keeps the PCMCIA scripts happy. */ |
270 | } |
271 | |
272 | static unsigned int ipw_chars_in_buffer(struct tty_struct *linux_tty) |
273 | { |
274 | struct ipw_tty *tty = linux_tty->driver_data; |
275 | |
276 | if (!tty) |
277 | return 0; |
278 | |
279 | if (!tty->port.count) |
280 | return 0; |
281 | |
282 | return tty->tx_bytes_queued; |
283 | } |
284 | |
285 | static int get_control_lines(struct ipw_tty *tty) |
286 | { |
287 | unsigned int my = tty->control_lines; |
288 | unsigned int out = 0; |
289 | |
290 | if (my & IPW_CONTROL_LINE_RTS) |
291 | out |= TIOCM_RTS; |
292 | if (my & IPW_CONTROL_LINE_DTR) |
293 | out |= TIOCM_DTR; |
294 | if (my & IPW_CONTROL_LINE_CTS) |
295 | out |= TIOCM_CTS; |
296 | if (my & IPW_CONTROL_LINE_DSR) |
297 | out |= TIOCM_DSR; |
298 | if (my & IPW_CONTROL_LINE_DCD) |
299 | out |= TIOCM_CD; |
300 | |
301 | return out; |
302 | } |
303 | |
304 | static int set_control_lines(struct ipw_tty *tty, unsigned int set, |
305 | unsigned int clear) |
306 | { |
307 | int ret; |
308 | |
309 | if (set & TIOCM_RTS) { |
310 | ret = ipwireless_set_RTS(hw: tty->hardware, channel_idx: tty->channel_idx, state: 1); |
311 | if (ret) |
312 | return ret; |
313 | if (tty->secondary_channel_idx != -1) { |
314 | ret = ipwireless_set_RTS(hw: tty->hardware, |
315 | channel_idx: tty->secondary_channel_idx, state: 1); |
316 | if (ret) |
317 | return ret; |
318 | } |
319 | } |
320 | if (set & TIOCM_DTR) { |
321 | ret = ipwireless_set_DTR(hw: tty->hardware, channel_idx: tty->channel_idx, state: 1); |
322 | if (ret) |
323 | return ret; |
324 | if (tty->secondary_channel_idx != -1) { |
325 | ret = ipwireless_set_DTR(hw: tty->hardware, |
326 | channel_idx: tty->secondary_channel_idx, state: 1); |
327 | if (ret) |
328 | return ret; |
329 | } |
330 | } |
331 | if (clear & TIOCM_RTS) { |
332 | ret = ipwireless_set_RTS(hw: tty->hardware, channel_idx: tty->channel_idx, state: 0); |
333 | if (tty->secondary_channel_idx != -1) { |
334 | ret = ipwireless_set_RTS(hw: tty->hardware, |
335 | channel_idx: tty->secondary_channel_idx, state: 0); |
336 | if (ret) |
337 | return ret; |
338 | } |
339 | } |
340 | if (clear & TIOCM_DTR) { |
341 | ret = ipwireless_set_DTR(hw: tty->hardware, channel_idx: tty->channel_idx, state: 0); |
342 | if (tty->secondary_channel_idx != -1) { |
343 | ret = ipwireless_set_DTR(hw: tty->hardware, |
344 | channel_idx: tty->secondary_channel_idx, state: 0); |
345 | if (ret) |
346 | return ret; |
347 | } |
348 | } |
349 | return 0; |
350 | } |
351 | |
352 | static int ipw_tiocmget(struct tty_struct *linux_tty) |
353 | { |
354 | struct ipw_tty *tty = linux_tty->driver_data; |
355 | /* FIXME: Exactly how is the tty object locked here .. */ |
356 | |
357 | if (!tty) |
358 | return -ENODEV; |
359 | |
360 | if (!tty->port.count) |
361 | return -EINVAL; |
362 | |
363 | return get_control_lines(tty); |
364 | } |
365 | |
366 | static int |
367 | ipw_tiocmset(struct tty_struct *linux_tty, |
368 | unsigned int set, unsigned int clear) |
369 | { |
370 | struct ipw_tty *tty = linux_tty->driver_data; |
371 | /* FIXME: Exactly how is the tty object locked here .. */ |
372 | |
373 | if (!tty) |
374 | return -ENODEV; |
375 | |
376 | if (!tty->port.count) |
377 | return -EINVAL; |
378 | |
379 | return set_control_lines(tty, set, clear); |
380 | } |
381 | |
382 | static int ipw_ioctl(struct tty_struct *linux_tty, |
383 | unsigned int cmd, unsigned long arg) |
384 | { |
385 | struct ipw_tty *tty = linux_tty->driver_data; |
386 | |
387 | if (!tty) |
388 | return -ENODEV; |
389 | |
390 | if (!tty->port.count) |
391 | return -EINVAL; |
392 | |
393 | /* FIXME: Exactly how is the tty object locked here .. */ |
394 | if (tty->tty_type == TTYTYPE_MODEM) { |
395 | switch (cmd) { |
396 | case PPPIOCGCHAN: |
397 | { |
398 | int chan = ipwireless_ppp_channel_index( |
399 | net: tty->network); |
400 | |
401 | if (chan < 0) |
402 | return -ENODEV; |
403 | if (put_user(chan, (int __user *) arg)) |
404 | return -EFAULT; |
405 | } |
406 | return 0; |
407 | |
408 | case PPPIOCGUNIT: |
409 | { |
410 | int unit = ipwireless_ppp_unit_number( |
411 | net: tty->network); |
412 | |
413 | if (unit < 0) |
414 | return -ENODEV; |
415 | if (put_user(unit, (int __user *) arg)) |
416 | return -EFAULT; |
417 | } |
418 | return 0; |
419 | |
420 | case FIONREAD: |
421 | { |
422 | int val = 0; |
423 | |
424 | if (put_user(val, (int __user *) arg)) |
425 | return -EFAULT; |
426 | } |
427 | return 0; |
428 | case TCFLSH: |
429 | return tty_perform_flush(tty: linux_tty, arg); |
430 | } |
431 | } |
432 | return -ENOIOCTLCMD; |
433 | } |
434 | |
435 | static int add_tty(int j, |
436 | struct ipw_hardware *hardware, |
437 | struct ipw_network *network, int channel_idx, |
438 | int secondary_channel_idx, int tty_type) |
439 | { |
440 | ttys[j] = kzalloc(size: sizeof(struct ipw_tty), GFP_KERNEL); |
441 | if (!ttys[j]) |
442 | return -ENOMEM; |
443 | ttys[j]->index = j; |
444 | ttys[j]->hardware = hardware; |
445 | ttys[j]->channel_idx = channel_idx; |
446 | ttys[j]->secondary_channel_idx = secondary_channel_idx; |
447 | ttys[j]->network = network; |
448 | ttys[j]->tty_type = tty_type; |
449 | mutex_init(&ttys[j]->ipw_tty_mutex); |
450 | tty_port_init(port: &ttys[j]->port); |
451 | |
452 | tty_port_register_device(port: &ttys[j]->port, driver: ipw_tty_driver, index: j, NULL); |
453 | ipwireless_associate_network_tty(net: network, channel_idx, tty: ttys[j]); |
454 | |
455 | if (secondary_channel_idx != -1) |
456 | ipwireless_associate_network_tty(net: network, |
457 | channel_idx: secondary_channel_idx, |
458 | tty: ttys[j]); |
459 | /* check if we provide raw device (if loopback is enabled) */ |
460 | if (get_tty(index: j)) |
461 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME |
462 | ": registering %s device ttyIPWp%d\n" , |
463 | tty_type_name(tty_type), j); |
464 | |
465 | return 0; |
466 | } |
467 | |
468 | struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware, |
469 | struct ipw_network *network) |
470 | { |
471 | int i, j; |
472 | |
473 | for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) { |
474 | int allfree = 1; |
475 | |
476 | for (j = i; j < IPWIRELESS_PCMCIA_MINORS; |
477 | j += IPWIRELESS_PCMCIA_MINOR_RANGE) |
478 | if (ttys[j] != NULL) { |
479 | allfree = 0; |
480 | break; |
481 | } |
482 | |
483 | if (allfree) { |
484 | j = i; |
485 | |
486 | if (add_tty(j, hardware, network, |
487 | IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS, |
488 | TTYTYPE_MODEM)) |
489 | return NULL; |
490 | |
491 | j += IPWIRELESS_PCMCIA_MINOR_RANGE; |
492 | if (add_tty(j, hardware, network, |
493 | IPW_CHANNEL_DIALLER, secondary_channel_idx: -1, |
494 | TTYTYPE_MONITOR)) |
495 | return NULL; |
496 | |
497 | j += IPWIRELESS_PCMCIA_MINOR_RANGE; |
498 | if (add_tty(j, hardware, network, |
499 | IPW_CHANNEL_RAS, secondary_channel_idx: -1, |
500 | TTYTYPE_RAS_RAW)) |
501 | return NULL; |
502 | |
503 | return ttys[i]; |
504 | } |
505 | } |
506 | return NULL; |
507 | } |
508 | |
509 | /* |
510 | * Must be called before ipwireless_network_free(). |
511 | */ |
512 | void ipwireless_tty_free(struct ipw_tty *tty) |
513 | { |
514 | int j; |
515 | struct ipw_network *network = ttys[tty->index]->network; |
516 | |
517 | for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; |
518 | j += IPWIRELESS_PCMCIA_MINOR_RANGE) { |
519 | struct ipw_tty *ttyj = ttys[j]; |
520 | |
521 | if (ttyj) { |
522 | mutex_lock(&ttyj->ipw_tty_mutex); |
523 | if (get_tty(index: j)) |
524 | printk(KERN_INFO IPWIRELESS_PCCARD_NAME |
525 | ": deregistering %s device ttyIPWp%d\n" , |
526 | tty_type_name(ttyj->tty_type), j); |
527 | if (ttyj->port.tty != NULL) { |
528 | mutex_unlock(lock: &ttyj->ipw_tty_mutex); |
529 | tty_vhangup(tty: ttyj->port.tty); |
530 | /* FIXME: Exactly how is the tty object locked here |
531 | against a parallel ioctl etc */ |
532 | /* FIXME2: hangup does not mean all processes |
533 | * are gone */ |
534 | mutex_lock(&ttyj->ipw_tty_mutex); |
535 | } |
536 | while (ttyj->port.count) |
537 | do_ipw_close(tty: ttyj); |
538 | ipwireless_disassociate_network_ttys(net: network, |
539 | channel_idx: ttyj->channel_idx); |
540 | tty_unregister_device(driver: ipw_tty_driver, index: j); |
541 | tty_port_destroy(port: &ttyj->port); |
542 | ttys[j] = NULL; |
543 | mutex_unlock(lock: &ttyj->ipw_tty_mutex); |
544 | kfree(objp: ttyj); |
545 | } |
546 | } |
547 | } |
548 | |
549 | static const struct tty_operations tty_ops = { |
550 | .open = ipw_open, |
551 | .close = ipw_close, |
552 | .hangup = ipw_hangup, |
553 | .write = ipw_write, |
554 | .write_room = ipw_write_room, |
555 | .ioctl = ipw_ioctl, |
556 | .chars_in_buffer = ipw_chars_in_buffer, |
557 | .tiocmget = ipw_tiocmget, |
558 | .tiocmset = ipw_tiocmset, |
559 | .set_serial = ipwireless_set_serial_info, |
560 | .get_serial = ipwireless_get_serial_info, |
561 | }; |
562 | |
563 | int ipwireless_tty_init(void) |
564 | { |
565 | int result; |
566 | |
567 | ipw_tty_driver = tty_alloc_driver(IPWIRELESS_PCMCIA_MINORS, |
568 | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); |
569 | if (IS_ERR(ptr: ipw_tty_driver)) |
570 | return PTR_ERR(ptr: ipw_tty_driver); |
571 | |
572 | ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; |
573 | ipw_tty_driver->name = "ttyIPWp" ; |
574 | ipw_tty_driver->major = 0; |
575 | ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; |
576 | ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; |
577 | ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; |
578 | ipw_tty_driver->init_termios = tty_std_termios; |
579 | ipw_tty_driver->init_termios.c_cflag = |
580 | B9600 | CS8 | CREAD | HUPCL | CLOCAL; |
581 | ipw_tty_driver->init_termios.c_ispeed = 9600; |
582 | ipw_tty_driver->init_termios.c_ospeed = 9600; |
583 | tty_set_operations(driver: ipw_tty_driver, op: &tty_ops); |
584 | result = tty_register_driver(driver: ipw_tty_driver); |
585 | if (result) { |
586 | printk(KERN_ERR IPWIRELESS_PCCARD_NAME |
587 | ": failed to register tty driver\n" ); |
588 | tty_driver_kref_put(driver: ipw_tty_driver); |
589 | return result; |
590 | } |
591 | |
592 | return 0; |
593 | } |
594 | |
595 | void ipwireless_tty_release(void) |
596 | { |
597 | tty_unregister_driver(driver: ipw_tty_driver); |
598 | tty_driver_kref_put(driver: ipw_tty_driver); |
599 | } |
600 | |
601 | int ipwireless_tty_is_modem(struct ipw_tty *tty) |
602 | { |
603 | return tty->tty_type == TTYTYPE_MODEM; |
604 | } |
605 | |
606 | void |
607 | ipwireless_tty_notify_control_line_change(struct ipw_tty *tty, |
608 | unsigned int channel_idx, |
609 | unsigned int control_lines, |
610 | unsigned int changed_mask) |
611 | { |
612 | unsigned int old_control_lines = tty->control_lines; |
613 | |
614 | tty->control_lines = (tty->control_lines & ~changed_mask) |
615 | | (control_lines & changed_mask); |
616 | |
617 | /* |
618 | * If DCD is de-asserted, we close the tty so pppd can tell that we |
619 | * have gone offline. |
620 | */ |
621 | if ((old_control_lines & IPW_CONTROL_LINE_DCD) |
622 | && !(tty->control_lines & IPW_CONTROL_LINE_DCD) |
623 | && tty->port.tty) { |
624 | tty_hangup(tty: tty->port.tty); |
625 | } |
626 | } |
627 | |
628 | |