/* * IPWireless 3G PCMCIA Network Driver * * Original code * by Stephen Blackheath , * Ben Martel * * Copyrighted as follows: * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) * * Various driver changes and rewrites, port to new kernels * Copyright (C) 2006-2007 Jiri Kosina * * Misc code cleanups and updates * Copyright (C) 2007 David Sterba */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tty.h" #include "network.h" #include "hardware.h" #include "main.h" #define IPWIRELESS_PCMCIA_START (0) #define IPWIRELESS_PCMCIA_MINORS (24) #define IPWIRELESS_PCMCIA_MINOR_RANGE (8) #define TTYTYPE_MODEM (0) #define TTYTYPE_MONITOR (1) #define TTYTYPE_RAS_RAW (2) struct ipw_tty { struct tty_port port; int index; struct ipw_hardware *hardware; unsigned int channel_idx; unsigned int secondary_channel_idx; int tty_type; struct ipw_network *network; unsigned int control_lines; struct mutex ipw_tty_mutex; int tx_bytes_queued; int closing; }; static struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS]; static struct tty_driver *ipw_tty_driver; static char *tty_type_name(int tty_type) { static char *channel_names[] = { "modem", "monitor", "RAS-raw" }; return channel_names[tty_type]; } static struct ipw_tty *get_tty(int index) { /* * The 'ras_raw' channel is only available when 'loopback' mode * is enabled. * Number of minor starts with 16 (_RANGE * _RAS_RAW). */ if (!ipwireless_loopback && index >= IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW) return NULL; return ttys[index]; } static int ipw_open(struct tty_struct *linux_tty, struct file *filp) { struct ipw_tty *tty = get_tty(linux_tty->index); if (!tty) return -ENODEV; mutex_lock(&tty->ipw_tty_mutex); if (tty->port.count == 0) tty->tx_bytes_queued = 0; tty->port.count++; tty->port.tty = linux_tty; linux_tty->driver_data = tty; tty->port.low_latency = 1; if (tty->tty_type == TTYTYPE_MODEM) ipwireless_ppp_open(tty->network); mutex_unlock(&tty->ipw_tty_mutex); return 0; } static void do_ipw_close(struct ipw_tty *tty) { tty->port.count--; if (tty->port.count == 0) { struct tty_struct *linux_tty = tty->port.tty; if (linux_tty != NULL) { tty->port.tty = NULL; linux_tty->driver_data = NULL; if (tty->tty_type == TTYTYPE_MODEM) ipwireless_ppp_close(tty->network); } } } static void ipw_hangup(struct tty_struct *linux_tty) { struct ipw_tty *tty = linux_tty->driver_data; if (!tty) return; mutex_lock(&tty->ipw_tty_mutex); if (tty->port.count == 0) { mutex_unlock(&tty->ipw_tty_mutex); return; } do_ipw_close(tty); mutex_unlock(&tty->ipw_tty_mutex); } static void ipw_close(struct tty_struct *linux_tty, struct file *filp) { ipw_hangup(linux_tty); } /* Take data received from hardware, and send it out the tty */ void ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, unsigned int length) { int work = 0; mutex_lock(&tty->ipw_tty_mutex); if (!tty->port.count) { mutex_unlock(&tty->ipw_tty_mutex); return; } mutex_unlock(&tty->ipw_tty_mutex); work = tty_insert_flip_string(&tty->port, data, length); if (work != length) printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME ": %d chars not inserted to flip buffer!\n", length - work); if (work) tty_flip_buffer_push(&tty->port); } static void ipw_write_packet_sent_callback(void *callback_data, unsigned int packet_length) { struct ipw_tty *tty = callback_data; /* * Packet has been sent, so we subtract the number of bytes from our * tally of outstanding TX bytes. */ tty->tx_bytes_queued -= packet_length; } static int ipw_write(struct tty_struct *linux_tty, const unsigned char *buf, int count) { struct ipw_tty *tty = linux_tty->driver_data; int room, ret; if (!tty) return -ENODEV; mutex_lock(&tty->ipw_tty_mutex); if (!tty->port.count) { mutex_unlock(&tty->ipw_tty_mutex); return -EINVAL; } room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; if (room < 0) room = 0; /* Don't allow caller to write any more than we have room for */ if (count > room) count = room; if (count == 0) { mutex_unlock(&tty->ipw_tty_mutex); return 0; } ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS, buf, count, ipw_write_packet_sent_callback, tty); if (ret < 0) { mutex_unlock(&tty->ipw_tty_mutex); return 0; } tty->tx_bytes_queued += count; mutex_unlock(&tty->ipw_tty_mutex); return count; } static int ipw_write_room(struct tty_struct *linux_tty) { struct ipw_tty *tty = linux_tty->driver_data; int room; /* FIXME: Exactly how is the tty object locked here .. */ if (!tty) return -ENODEV; if (!tty->port.count) return -EINVAL; room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; if (room < 0) room = 0; return room; } static int ipwireless_get_serial_info(struct ipw_tty *tty, struct serial_struct __user *retinfo) { struct serial_struct tmp; memset(&tmp, 0, sizeof(tmp)); tmp.type = PORT_UNKNOWN; tmp.line = tty->index; tmp.baud_base = 115200; if (copy_to_user(retinfo, &tmp, sizeof(*retinfo))) return -EFAULT; return 0; } static int ipw_chars_in_buffer(struct tty_struct *linux_tty) { struct ipw_tty *tty = linux_tty->driver_data; if (!tty) return 0; if (!tty->port.count) return 0; return tty->tx_bytes_queued; } static int get_control_lines(struct ipw_tty *tty) { unsigned int my = tty->control_lines; unsigned int out = 0; if (my & IPW_CONTROL_LINE_RTS) out |= TIOCM_RTS; if (my & IPW_CONTROL_LINE_DTR) out |= TIOCM_DTR; if (my & IPW_CONTROL_LINE_CTS) out |= TIOCM_CTS; if (my & IPW_CONTROL_LINE_DSR) out |= TIOCM_DSR; if (my & IPW_CONTROL_LINE_DCD) out |= TIOCM_CD; return out; } static int set_control_lines(struct ipw_tty *tty, unsigned int set, unsigned int clear) { int ret; if (set & TIOCM_RTS) { ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1); if (ret) return ret; if (tty->secondary_channel_idx != -1) { ret = ipwireless_set_RTS(tty->hardware, tty->secondary_channel_idx, 1); if (ret) return ret; } } if (set & TIOCM_DTR) { ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1); if (ret) return ret; if (tty->secondary_channel_idx != -1) { ret = ipwireless_set_DTR(tty->hardware, tty->secondary_channel_idx, 1); if (ret) return ret; } } if (clear & TIOCM_RTS) { ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0); if (tty->secondary_channel_idx != -1) { ret = ipwireless_set_RTS(tty->hardware, tty->secondary_channel_idx, 0); if (ret) return ret; } } if (clear & TIOCM_DTR) { ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0); if (tty->secondary_channel_idx != -1) { ret = ipwireless_set_DTR(tty->hardware, tty->secondary_channel_idx, 0); if (ret) return ret; } } return 0; } static int ipw_tiocmget(struct tty_struct *linux_tty) { struct ipw_tty *tty = linux_tty->driver_data; /* FIXME: Exactly how is the tty object locked here .. */ if (!tty) return -ENODEV; if (!tty->port.count) return -EINVAL; return get_control_lines(tty); } static int ipw_tiocmset(struct tty_struct *linux_tty, unsigned int set, unsigned int clear) { struct ipw_tty *tty = linux_tty->driver_data; /* FIXME: Exactly how is the tty object locked here .. */ if (!tty) return -ENODEV; if (!tty->port.count) return -EINVAL; return set_control_lines(tty, set, clear); } static int ipw_ioctl(struct tty_struct *linux_tty, unsigned int cmd, unsigned long arg) { struct ipw_tty *tty = linux_tty->driver_data; if (!tty) return -ENODEV; if (!tty->port.count) return -EINVAL; /* FIXME: Exactly how is the tty object locked here .. */ switch (cmd) { case TIOCGSERIAL: return ipwireless_get_serial_info(tty, (void __user *) arg); case TIOCSSERIAL: return 0; /* Keeps the PCMCIA scripts happy. */ } if (tty->tty_type == TTYTYPE_MODEM) { switch (cmd) { case PPPIOCGCHAN: { int chan = ipwireless_ppp_channel_index( tty->network); if (chan < 0) return -ENODEV; if (put_user(chan, (int __user *) arg)) return -EFAULT; } return 0; case PPPIOCGUNIT: { int unit = ipwireless_ppp_unit_number( tty->network); if (unit < 0) return -ENODEV; if (put_user(unit, (int __user *) arg)) return -EFAULT; } return 0; case FIONREAD: { int val = 0; if (put_user(val, (int __user *) arg)) return -EFAULT; } return 0; case TCFLSH: return tty_perform_flush(linux_tty, arg); } } return -ENOIOCTLCMD; } static int add_tty(int j, struct ipw_hardware *hardware, struct ipw_network *network, int channel_idx, int secondary_channel_idx, int tty_type) { ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL); if (!ttys[j]) return -ENOMEM; ttys[j]->index = j; ttys[j]->hardware = hardware; ttys[j]->channel_idx = channel_idx; ttys[j]->secondary_channel_idx = secondary_channel_idx; ttys[j]->network = network; ttys[j]->tty_type = tty_type; mutex_init(&ttys[j]->ipw_tty_mutex); tty_port_init(&ttys[j]->port); tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL); ipwireless_associate_network_tty(network, channel_idx, ttys[j]); if (secondary_channel_idx != -1) ipwireless_associate_network_tty(network, secondary_channel_idx, ttys[j]); /* check if we provide raw device (if loopback is enabled) */ if (get_tty(j)) printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": registering %s device ttyIPWp%d\n", tty_type_name(tty_type), j); return 0; } struct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware, struct ipw_network *network) { int i, j; for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) { int allfree = 1; for (j = i; j < IPWIRELESS_PCMCIA_MINORS; j += IPWIRELESS_PCMCIA_MINOR_RANGE) if (ttys[j] != NULL) { allfree = 0; break; } if (allfree) { j = i; if (add_tty(j, hardware, network, IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS, TTYTYPE_MODEM)) return NULL; j += IPWIRELESS_PCMCIA_MINOR_RANGE; if (add_tty(j, hardware, network, IPW_CHANNEL_DIALLER, -1, TTYTYPE_MONITOR)) return NULL; j += IPWIRELESS_PCMCIA_MINOR_RANGE; if (add_tty(j, hardware, network, IPW_CHANNEL_RAS, -1, TTYTYPE_RAS_RAW)) return NULL; return ttys[i]; } } return NULL; } /* * Must be called before ipwireless_network_free(). */ void ipwireless_tty_free(struct ipw_tty *tty) { int j; struct ipw_network *network = ttys[tty->index]->network; for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; j += IPWIRELESS_PCMCIA_MINOR_RANGE) { struct ipw_tty *ttyj = ttys[j]; if (ttyj) { mutex_lock(&ttyj->ipw_tty_mutex); if (get_tty(j)) printk(KERN_INFO IPWIRELESS_PCCARD_NAME ": deregistering %s device ttyIPWp%d\n", tty_type_name(ttyj->tty_type), j); ttyj->closing = 1; if (ttyj->port.tty != NULL) { mutex_unlock(&ttyj->ipw_tty_mutex); tty_vhangup(ttyj->port.tty); /* FIXME: Exactly how is the tty object locked here against a parallel ioctl etc */ /* FIXME2: hangup does not mean all processes * are gone */ mutex_lock(&ttyj->ipw_tty_mutex); } while (ttyj->port.count) do_ipw_close(ttyj); ipwireless_disassociate_network_ttys(network, ttyj->channel_idx); tty_unregister_device(ipw_tty_driver, j); tty_port_destroy(&ttyj->port); ttys[j] = NULL; mutex_unlock(&ttyj->ipw_tty_mutex); kfree(ttyj); } } } static const struct tty_operations tty_ops = { .open = ipw_open, .close = ipw_close, .hangup = ipw_hangup, .write = ipw_write, .write_room = ipw_write_room, .ioctl = ipw_ioctl, .chars_in_buffer = ipw_chars_in_buffer, .tiocmget = ipw_tiocmget, .tiocmset = ipw_tiocmset, }; int ipwireless_tty_init(void) { int result; ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS); if (!ipw_tty_driver) return -ENOMEM; ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; ipw_tty_driver->name = "ttyIPWp"; ipw_tty_driver->major = 0; ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; ipw_tty_driver->init_termios = tty_std_termios; ipw_tty_driver->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; ipw_tty_driver->init_termios.c_ispeed = 9600; ipw_tty_driver->init_termios.c_ospeed = 9600; tty_set_operations(ipw_tty_driver, &tty_ops); result = tty_register_driver(ipw_tty_driver); if (result) { printk(KERN_ERR IPWIRELESS_PCCARD_NAME ": failed to register tty driver\n"); put_tty_driver(ipw_tty_driver); return result; } return 0; } void ipwireless_tty_release(void) { int ret; ret = tty_unregister_driver(ipw_tty_driver); put_tty_driver(ipw_tty_driver); if (ret != 0) printk(KERN_ERR IPWIRELESS_PCCARD_NAME ": tty_unregister_driver failed with code %d\n", ret); } int ipwireless_tty_is_modem(struct ipw_tty *tty) { return tty->tty_type == TTYTYPE_MODEM; } void ipwireless_tty_notify_control_line_change(struct ipw_tty *tty, unsigned int channel_idx, unsigned int control_lines, unsigned int changed_mask) { unsigned int old_control_lines = tty->control_lines; tty->control_lines = (tty->control_lines & ~changed_mask) | (control_lines & changed_mask); /* * If DCD is de-asserted, we close the tty so pppd can tell that we * have gone offline. */ if ((old_control_lines & IPW_CONTROL_LINE_DCD) && !(tty->control_lines & IPW_CONTROL_LINE_DCD) && tty->port.tty) { tty_hangup(tty->port.tty); } }