628 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * IPWireless 3G PCMCIA Network Driver4 *5 * Original code6 * 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 kernels13 * Copyright (C) 2006-2007 Jiri Kosina14 *15 * Misc code cleanups and updates16 * Copyright (C) 2007 David Sterba17 */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 46struct 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 59static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS];60 61static struct tty_driver *ipw_tty_driver;62 63static 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 74static struct ipw_tty *get_tty(int index)75{76 /*77 * The 'ras_raw' channel is only available when 'loopback' mode78 * 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 88static int ipw_open(struct tty_struct *linux_tty, struct file *filp)89{90 struct ipw_tty *tty = get_tty(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(tty->network);106 107 mutex_unlock(&tty->ipw_tty_mutex);108 109 return 0;110}111 112static 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(tty->network);125 }126 }127}128 129static 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(&tty->ipw_tty_mutex);139 return;140 }141 142 do_ipw_close(tty);143 144 mutex_unlock(&tty->ipw_tty_mutex);145}146 147static 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 */153void 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(&tty->ipw_tty_mutex);162 return;163 }164 mutex_unlock(&tty->ipw_tty_mutex);165 166 work = tty_insert_flip_string(&tty->port, data, length);167 168 if (work != length)169 printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME170 ": %d chars not inserted to flip buffer!\n",171 length - work);172 173 if (work)174 tty_flip_buffer_push(&tty->port);175}176 177static 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 our184 * tally of outstanding TX bytes.185 */186 tty->tx_bytes_queued -= packet_length;187}188 189static 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(&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(&tty->ipw_tty_mutex);213 return 0;214 }215 216 ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS,217 buf, count,218 ipw_write_packet_sent_callback, tty);219 if (ret < 0) {220 mutex_unlock(&tty->ipw_tty_mutex);221 return 0;222 }223 224 tty->tx_bytes_queued += count;225 mutex_unlock(&tty->ipw_tty_mutex);226 227 return count;228}229 230static 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 249static 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 266static 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 272static 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 285static 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 304static 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(tty->hardware, tty->channel_idx, 1);311 if (ret)312 return ret;313 if (tty->secondary_channel_idx != -1) {314 ret = ipwireless_set_RTS(tty->hardware,315 tty->secondary_channel_idx, 1);316 if (ret)317 return ret;318 }319 }320 if (set & TIOCM_DTR) {321 ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1);322 if (ret)323 return ret;324 if (tty->secondary_channel_idx != -1) {325 ret = ipwireless_set_DTR(tty->hardware,326 tty->secondary_channel_idx, 1);327 if (ret)328 return ret;329 }330 }331 if (clear & TIOCM_RTS) {332 ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0);333 if (tty->secondary_channel_idx != -1) {334 ret = ipwireless_set_RTS(tty->hardware,335 tty->secondary_channel_idx, 0);336 if (ret)337 return ret;338 }339 }340 if (clear & TIOCM_DTR) {341 ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0);342 if (tty->secondary_channel_idx != -1) {343 ret = ipwireless_set_DTR(tty->hardware,344 tty->secondary_channel_idx, 0);345 if (ret)346 return ret;347 }348 }349 return 0;350}351 352static 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 366static int367ipw_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 382static 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 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 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(linux_tty, arg);430 }431 }432 return -ENOIOCTLCMD;433}434 435static 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(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(&ttys[j]->port);451 452 tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL);453 ipwireless_associate_network_tty(network, channel_idx, ttys[j]);454 455 if (secondary_channel_idx != -1)456 ipwireless_associate_network_tty(network,457 secondary_channel_idx,458 ttys[j]);459 /* check if we provide raw device (if loopback is enabled) */460 if (get_tty(j))461 printk(KERN_INFO IPWIRELESS_PCCARD_NAME462 ": registering %s device ttyIPWp%d\n",463 tty_type_name(tty_type), j);464 465 return 0;466}467 468struct 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, -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, -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 */512void 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(j))524 printk(KERN_INFO IPWIRELESS_PCCARD_NAME525 ": deregistering %s device ttyIPWp%d\n",526 tty_type_name(ttyj->tty_type), j);527 if (ttyj->port.tty != NULL) {528 mutex_unlock(&ttyj->ipw_tty_mutex);529 tty_vhangup(ttyj->port.tty);530 /* FIXME: Exactly how is the tty object locked here531 against a parallel ioctl etc */532 /* FIXME2: hangup does not mean all processes533 * are gone */534 mutex_lock(&ttyj->ipw_tty_mutex);535 }536 while (ttyj->port.count)537 do_ipw_close(ttyj);538 ipwireless_disassociate_network_ttys(network,539 ttyj->channel_idx);540 tty_unregister_device(ipw_tty_driver, j);541 tty_port_destroy(&ttyj->port);542 ttys[j] = NULL;543 mutex_unlock(&ttyj->ipw_tty_mutex);544 kfree(ttyj);545 }546 }547}548 549static 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 563int 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(ipw_tty_driver))570 return PTR_ERR(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(ipw_tty_driver, &tty_ops);584 result = tty_register_driver(ipw_tty_driver);585 if (result) {586 printk(KERN_ERR IPWIRELESS_PCCARD_NAME587 ": failed to register tty driver\n");588 tty_driver_kref_put(ipw_tty_driver);589 return result;590 }591 592 return 0;593}594 595void ipwireless_tty_release(void)596{597 tty_unregister_driver(ipw_tty_driver);598 tty_driver_kref_put(ipw_tty_driver);599}600 601int ipwireless_tty_is_modem(struct ipw_tty *tty)602{603 return tty->tty_type == TTYTYPE_MODEM;604}605 606void607ipwireless_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 we619 * 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->port.tty);625 }626}627 628