/* * IPWireless 3G PCMCIA Network Driver * * Original code * by Stephen Blackheath <stephen@blacksapphire.com>, * Ben Martel <benm@symmetric.co.nz> * * 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 <linux/init.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/ppp_defs.h>#include <linux/if.h>#include <linux/ppp-ioctl.h>#include <linux/sched.h>#include <linux/serial.h>#include <linux/slab.h>#include <linux/tty.h>#include <linux/tty_driver.h>#include <linux/tty_flip.h>#include <linux/uaccess.h> #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->closing) { mutex_unlock(&tty->ipw_tty_mutex); return -ENODEV; } 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); /* * This may sleep if ->low_latency is set */ 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 == -1) { 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; if (!retinfo) return (-EFAULT); memset(&tmp, 0, sizeof(tmp)); tmp.type = PORT_UNKNOWN; tmp.line = tty->index; tmp.port = 0; tmp.irq = 0; tmp.flags = 0; tmp.baud_base = 115200; tmp.close_delay = 0; tmp.closing_wait = 0; tmp.custom_divisor = 0; tmp.hub6 = 0; 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 intipw_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;Lines 1–500 of 652. Load more
/* * IPWireless 3G PCMCIA Network Driver * * Original code * by Stephen Blackheath <stephen@blacksapphire.com>, * Ben Martel <benm@symmetric.co.nz> * * 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 <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> #include <linux/ppp_defs.h> #include <linux/if.h> #include <linux/ppp-ioctl.h> #include <linux/sched.h> #include <linux/serial.h> #include <linux/slab.h> #include <linux/tty.h> #include <linux/tty_driver.h> #include <linux/tty_flip.h> #include <linux/uaccess.h> #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->closing) { mutex_unlock(&tty->ipw_tty_mutex); return -ENODEV; } 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); /* * This may sleep if ->low_latency is set */ 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 == -1) { 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; if (!retinfo) return (-EFAULT); memset(&tmp, 0, sizeof(tmp)); tmp.type = PORT_UNKNOWN; tmp.line = tty->index; tmp.port = 0; tmp.irq = 0; tmp.flags = 0; tmp.baud_base = 115200; tmp.close_delay = 0; tmp.closing_wait = 0; tmp.custom_divisor = 0; tmp.hub6 = 0; 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;