162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * IPWireless 3G PCMCIA Network Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Original code 662306a36Sopenharmony_ci * by Stephen Blackheath <stephen@blacksapphire.com>, 762306a36Sopenharmony_ci * Ben Martel <benm@symmetric.co.nz> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * Copyrighted as follows: 1062306a36Sopenharmony_ci * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) 1162306a36Sopenharmony_ci * 1262306a36Sopenharmony_ci * Various driver changes and rewrites, port to new kernels 1362306a36Sopenharmony_ci * Copyright (C) 2006-2007 Jiri Kosina 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Misc code cleanups and updates 1662306a36Sopenharmony_ci * Copyright (C) 2007 David Sterba 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci#include <linux/kernel.h> 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci#include <linux/mutex.h> 2262306a36Sopenharmony_ci#include <linux/ppp_defs.h> 2362306a36Sopenharmony_ci#include <linux/if.h> 2462306a36Sopenharmony_ci#include <linux/ppp-ioctl.h> 2562306a36Sopenharmony_ci#include <linux/sched.h> 2662306a36Sopenharmony_ci#include <linux/serial.h> 2762306a36Sopenharmony_ci#include <linux/slab.h> 2862306a36Sopenharmony_ci#include <linux/tty.h> 2962306a36Sopenharmony_ci#include <linux/tty_driver.h> 3062306a36Sopenharmony_ci#include <linux/tty_flip.h> 3162306a36Sopenharmony_ci#include <linux/uaccess.h> 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci#include "tty.h" 3462306a36Sopenharmony_ci#include "network.h" 3562306a36Sopenharmony_ci#include "hardware.h" 3662306a36Sopenharmony_ci#include "main.h" 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define IPWIRELESS_PCMCIA_START (0) 3962306a36Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINORS (24) 4062306a36Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINOR_RANGE (8) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define TTYTYPE_MODEM (0) 4362306a36Sopenharmony_ci#define TTYTYPE_MONITOR (1) 4462306a36Sopenharmony_ci#define TTYTYPE_RAS_RAW (2) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistruct ipw_tty { 4762306a36Sopenharmony_ci struct tty_port port; 4862306a36Sopenharmony_ci int index; 4962306a36Sopenharmony_ci struct ipw_hardware *hardware; 5062306a36Sopenharmony_ci unsigned int channel_idx; 5162306a36Sopenharmony_ci unsigned int secondary_channel_idx; 5262306a36Sopenharmony_ci int tty_type; 5362306a36Sopenharmony_ci struct ipw_network *network; 5462306a36Sopenharmony_ci unsigned int control_lines; 5562306a36Sopenharmony_ci struct mutex ipw_tty_mutex; 5662306a36Sopenharmony_ci int tx_bytes_queued; 5762306a36Sopenharmony_ci}; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS]; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic struct tty_driver *ipw_tty_driver; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_cistatic char *tty_type_name(int tty_type) 6462306a36Sopenharmony_ci{ 6562306a36Sopenharmony_ci static char *channel_names[] = { 6662306a36Sopenharmony_ci "modem", 6762306a36Sopenharmony_ci "monitor", 6862306a36Sopenharmony_ci "RAS-raw" 6962306a36Sopenharmony_ci }; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return channel_names[tty_type]; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic struct ipw_tty *get_tty(int index) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci /* 7762306a36Sopenharmony_ci * The 'ras_raw' channel is only available when 'loopback' mode 7862306a36Sopenharmony_ci * is enabled. 7962306a36Sopenharmony_ci * Number of minor starts with 16 (_RANGE * _RAS_RAW). 8062306a36Sopenharmony_ci */ 8162306a36Sopenharmony_ci if (!ipwireless_loopback && index >= 8262306a36Sopenharmony_ci IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW) 8362306a36Sopenharmony_ci return NULL; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return ttys[index]; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int ipw_open(struct tty_struct *linux_tty, struct file *filp) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct ipw_tty *tty = get_tty(linux_tty->index); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (!tty) 9362306a36Sopenharmony_ci return -ENODEV; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 9662306a36Sopenharmony_ci if (tty->port.count == 0) 9762306a36Sopenharmony_ci tty->tx_bytes_queued = 0; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci tty->port.count++; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci tty->port.tty = linux_tty; 10262306a36Sopenharmony_ci linux_tty->driver_data = tty; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) 10562306a36Sopenharmony_ci ipwireless_ppp_open(tty->network); 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic void do_ipw_close(struct ipw_tty *tty) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci tty->port.count--; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (tty->port.count == 0) { 11762306a36Sopenharmony_ci struct tty_struct *linux_tty = tty->port.tty; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (linux_tty != NULL) { 12062306a36Sopenharmony_ci tty->port.tty = NULL; 12162306a36Sopenharmony_ci linux_tty->driver_data = NULL; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) 12462306a36Sopenharmony_ci ipwireless_ppp_close(tty->network); 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci} 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_cistatic void ipw_hangup(struct tty_struct *linux_tty) 13062306a36Sopenharmony_ci{ 13162306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!tty) 13462306a36Sopenharmony_ci return; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 13762306a36Sopenharmony_ci if (tty->port.count == 0) { 13862306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 13962306a36Sopenharmony_ci return; 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci do_ipw_close(tty); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic void ipw_close(struct tty_struct *linux_tty, struct file *filp) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci ipw_hangup(linux_tty); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci/* Take data received from hardware, and send it out the tty */ 15362306a36Sopenharmony_civoid ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, 15462306a36Sopenharmony_ci unsigned int length) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci int work = 0; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci if (!tty->port.count) { 16162306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 16262306a36Sopenharmony_ci return; 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci work = tty_insert_flip_string(&tty->port, data, length); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (work != length) 16962306a36Sopenharmony_ci printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME 17062306a36Sopenharmony_ci ": %d chars not inserted to flip buffer!\n", 17162306a36Sopenharmony_ci length - work); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (work) 17462306a36Sopenharmony_ci tty_flip_buffer_push(&tty->port); 17562306a36Sopenharmony_ci} 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_cistatic void ipw_write_packet_sent_callback(void *callback_data, 17862306a36Sopenharmony_ci unsigned int packet_length) 17962306a36Sopenharmony_ci{ 18062306a36Sopenharmony_ci struct ipw_tty *tty = callback_data; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* 18362306a36Sopenharmony_ci * Packet has been sent, so we subtract the number of bytes from our 18462306a36Sopenharmony_ci * tally of outstanding TX bytes. 18562306a36Sopenharmony_ci */ 18662306a36Sopenharmony_ci tty->tx_bytes_queued -= packet_length; 18762306a36Sopenharmony_ci} 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_cistatic ssize_t ipw_write(struct tty_struct *linux_tty, const u8 *buf, 19062306a36Sopenharmony_ci size_t count) 19162306a36Sopenharmony_ci{ 19262306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 19362306a36Sopenharmony_ci int room, ret; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci if (!tty) 19662306a36Sopenharmony_ci return -ENODEV; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 19962306a36Sopenharmony_ci if (!tty->port.count) { 20062306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 20162306a36Sopenharmony_ci return -EINVAL; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; 20562306a36Sopenharmony_ci if (room < 0) 20662306a36Sopenharmony_ci room = 0; 20762306a36Sopenharmony_ci /* Don't allow caller to write any more than we have room for */ 20862306a36Sopenharmony_ci if (count > room) 20962306a36Sopenharmony_ci count = room; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (count == 0) { 21262306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 21362306a36Sopenharmony_ci return 0; 21462306a36Sopenharmony_ci } 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS, 21762306a36Sopenharmony_ci buf, count, 21862306a36Sopenharmony_ci ipw_write_packet_sent_callback, tty); 21962306a36Sopenharmony_ci if (ret < 0) { 22062306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 22162306a36Sopenharmony_ci return 0; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci tty->tx_bytes_queued += count; 22562306a36Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return count; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic unsigned int ipw_write_room(struct tty_struct *linux_tty) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 23362306a36Sopenharmony_ci int room; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 23662306a36Sopenharmony_ci if (!tty) 23762306a36Sopenharmony_ci return 0; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (!tty->port.count) 24062306a36Sopenharmony_ci return 0; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; 24362306a36Sopenharmony_ci if (room < 0) 24462306a36Sopenharmony_ci room = 0; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci return room; 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic int ipwireless_get_serial_info(struct tty_struct *linux_tty, 25062306a36Sopenharmony_ci struct serial_struct *ss) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci if (!tty) 25562306a36Sopenharmony_ci return -ENODEV; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (!tty->port.count) 25862306a36Sopenharmony_ci return -EINVAL; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci ss->type = PORT_UNKNOWN; 26162306a36Sopenharmony_ci ss->line = tty->index; 26262306a36Sopenharmony_ci ss->baud_base = 115200; 26362306a36Sopenharmony_ci return 0; 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic int ipwireless_set_serial_info(struct tty_struct *linux_tty, 26762306a36Sopenharmony_ci struct serial_struct *ss) 26862306a36Sopenharmony_ci{ 26962306a36Sopenharmony_ci return 0; /* Keeps the PCMCIA scripts happy. */ 27062306a36Sopenharmony_ci} 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cistatic unsigned int ipw_chars_in_buffer(struct tty_struct *linux_tty) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci if (!tty) 27762306a36Sopenharmony_ci return 0; 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (!tty->port.count) 28062306a36Sopenharmony_ci return 0; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return tty->tx_bytes_queued; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int get_control_lines(struct ipw_tty *tty) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci unsigned int my = tty->control_lines; 28862306a36Sopenharmony_ci unsigned int out = 0; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if (my & IPW_CONTROL_LINE_RTS) 29162306a36Sopenharmony_ci out |= TIOCM_RTS; 29262306a36Sopenharmony_ci if (my & IPW_CONTROL_LINE_DTR) 29362306a36Sopenharmony_ci out |= TIOCM_DTR; 29462306a36Sopenharmony_ci if (my & IPW_CONTROL_LINE_CTS) 29562306a36Sopenharmony_ci out |= TIOCM_CTS; 29662306a36Sopenharmony_ci if (my & IPW_CONTROL_LINE_DSR) 29762306a36Sopenharmony_ci out |= TIOCM_DSR; 29862306a36Sopenharmony_ci if (my & IPW_CONTROL_LINE_DCD) 29962306a36Sopenharmony_ci out |= TIOCM_CD; 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_ci return out; 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic int set_control_lines(struct ipw_tty *tty, unsigned int set, 30562306a36Sopenharmony_ci unsigned int clear) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci int ret; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (set & TIOCM_RTS) { 31062306a36Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1); 31162306a36Sopenharmony_ci if (ret) 31262306a36Sopenharmony_ci return ret; 31362306a36Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 31462306a36Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, 31562306a36Sopenharmony_ci tty->secondary_channel_idx, 1); 31662306a36Sopenharmony_ci if (ret) 31762306a36Sopenharmony_ci return ret; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci } 32062306a36Sopenharmony_ci if (set & TIOCM_DTR) { 32162306a36Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1); 32262306a36Sopenharmony_ci if (ret) 32362306a36Sopenharmony_ci return ret; 32462306a36Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 32562306a36Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, 32662306a36Sopenharmony_ci tty->secondary_channel_idx, 1); 32762306a36Sopenharmony_ci if (ret) 32862306a36Sopenharmony_ci return ret; 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci } 33162306a36Sopenharmony_ci if (clear & TIOCM_RTS) { 33262306a36Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0); 33362306a36Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 33462306a36Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, 33562306a36Sopenharmony_ci tty->secondary_channel_idx, 0); 33662306a36Sopenharmony_ci if (ret) 33762306a36Sopenharmony_ci return ret; 33862306a36Sopenharmony_ci } 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci if (clear & TIOCM_DTR) { 34162306a36Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0); 34262306a36Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 34362306a36Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, 34462306a36Sopenharmony_ci tty->secondary_channel_idx, 0); 34562306a36Sopenharmony_ci if (ret) 34662306a36Sopenharmony_ci return ret; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci } 34962306a36Sopenharmony_ci return 0; 35062306a36Sopenharmony_ci} 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_cistatic int ipw_tiocmget(struct tty_struct *linux_tty) 35362306a36Sopenharmony_ci{ 35462306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 35562306a36Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci if (!tty) 35862306a36Sopenharmony_ci return -ENODEV; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci if (!tty->port.count) 36162306a36Sopenharmony_ci return -EINVAL; 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci return get_control_lines(tty); 36462306a36Sopenharmony_ci} 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_cistatic int 36762306a36Sopenharmony_ciipw_tiocmset(struct tty_struct *linux_tty, 36862306a36Sopenharmony_ci unsigned int set, unsigned int clear) 36962306a36Sopenharmony_ci{ 37062306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 37162306a36Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci if (!tty) 37462306a36Sopenharmony_ci return -ENODEV; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci if (!tty->port.count) 37762306a36Sopenharmony_ci return -EINVAL; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci return set_control_lines(tty, set, clear); 38062306a36Sopenharmony_ci} 38162306a36Sopenharmony_ci 38262306a36Sopenharmony_cistatic int ipw_ioctl(struct tty_struct *linux_tty, 38362306a36Sopenharmony_ci unsigned int cmd, unsigned long arg) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci if (!tty) 38862306a36Sopenharmony_ci return -ENODEV; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if (!tty->port.count) 39162306a36Sopenharmony_ci return -EINVAL; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 39462306a36Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) { 39562306a36Sopenharmony_ci switch (cmd) { 39662306a36Sopenharmony_ci case PPPIOCGCHAN: 39762306a36Sopenharmony_ci { 39862306a36Sopenharmony_ci int chan = ipwireless_ppp_channel_index( 39962306a36Sopenharmony_ci tty->network); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci if (chan < 0) 40262306a36Sopenharmony_ci return -ENODEV; 40362306a36Sopenharmony_ci if (put_user(chan, (int __user *) arg)) 40462306a36Sopenharmony_ci return -EFAULT; 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci return 0; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci case PPPIOCGUNIT: 40962306a36Sopenharmony_ci { 41062306a36Sopenharmony_ci int unit = ipwireless_ppp_unit_number( 41162306a36Sopenharmony_ci tty->network); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci if (unit < 0) 41462306a36Sopenharmony_ci return -ENODEV; 41562306a36Sopenharmony_ci if (put_user(unit, (int __user *) arg)) 41662306a36Sopenharmony_ci return -EFAULT; 41762306a36Sopenharmony_ci } 41862306a36Sopenharmony_ci return 0; 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci case FIONREAD: 42162306a36Sopenharmony_ci { 42262306a36Sopenharmony_ci int val = 0; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci if (put_user(val, (int __user *) arg)) 42562306a36Sopenharmony_ci return -EFAULT; 42662306a36Sopenharmony_ci } 42762306a36Sopenharmony_ci return 0; 42862306a36Sopenharmony_ci case TCFLSH: 42962306a36Sopenharmony_ci return tty_perform_flush(linux_tty, arg); 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci return -ENOIOCTLCMD; 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cistatic int add_tty(int j, 43662306a36Sopenharmony_ci struct ipw_hardware *hardware, 43762306a36Sopenharmony_ci struct ipw_network *network, int channel_idx, 43862306a36Sopenharmony_ci int secondary_channel_idx, int tty_type) 43962306a36Sopenharmony_ci{ 44062306a36Sopenharmony_ci ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL); 44162306a36Sopenharmony_ci if (!ttys[j]) 44262306a36Sopenharmony_ci return -ENOMEM; 44362306a36Sopenharmony_ci ttys[j]->index = j; 44462306a36Sopenharmony_ci ttys[j]->hardware = hardware; 44562306a36Sopenharmony_ci ttys[j]->channel_idx = channel_idx; 44662306a36Sopenharmony_ci ttys[j]->secondary_channel_idx = secondary_channel_idx; 44762306a36Sopenharmony_ci ttys[j]->network = network; 44862306a36Sopenharmony_ci ttys[j]->tty_type = tty_type; 44962306a36Sopenharmony_ci mutex_init(&ttys[j]->ipw_tty_mutex); 45062306a36Sopenharmony_ci tty_port_init(&ttys[j]->port); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL); 45362306a36Sopenharmony_ci ipwireless_associate_network_tty(network, channel_idx, ttys[j]); 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci if (secondary_channel_idx != -1) 45662306a36Sopenharmony_ci ipwireless_associate_network_tty(network, 45762306a36Sopenharmony_ci secondary_channel_idx, 45862306a36Sopenharmony_ci ttys[j]); 45962306a36Sopenharmony_ci /* check if we provide raw device (if loopback is enabled) */ 46062306a36Sopenharmony_ci if (get_tty(j)) 46162306a36Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 46262306a36Sopenharmony_ci ": registering %s device ttyIPWp%d\n", 46362306a36Sopenharmony_ci tty_type_name(tty_type), j); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci return 0; 46662306a36Sopenharmony_ci} 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_cistruct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware, 46962306a36Sopenharmony_ci struct ipw_network *network) 47062306a36Sopenharmony_ci{ 47162306a36Sopenharmony_ci int i, j; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) { 47462306a36Sopenharmony_ci int allfree = 1; 47562306a36Sopenharmony_ci 47662306a36Sopenharmony_ci for (j = i; j < IPWIRELESS_PCMCIA_MINORS; 47762306a36Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE) 47862306a36Sopenharmony_ci if (ttys[j] != NULL) { 47962306a36Sopenharmony_ci allfree = 0; 48062306a36Sopenharmony_ci break; 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci if (allfree) { 48462306a36Sopenharmony_ci j = i; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci if (add_tty(j, hardware, network, 48762306a36Sopenharmony_ci IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS, 48862306a36Sopenharmony_ci TTYTYPE_MODEM)) 48962306a36Sopenharmony_ci return NULL; 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE; 49262306a36Sopenharmony_ci if (add_tty(j, hardware, network, 49362306a36Sopenharmony_ci IPW_CHANNEL_DIALLER, -1, 49462306a36Sopenharmony_ci TTYTYPE_MONITOR)) 49562306a36Sopenharmony_ci return NULL; 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE; 49862306a36Sopenharmony_ci if (add_tty(j, hardware, network, 49962306a36Sopenharmony_ci IPW_CHANNEL_RAS, -1, 50062306a36Sopenharmony_ci TTYTYPE_RAS_RAW)) 50162306a36Sopenharmony_ci return NULL; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci return ttys[i]; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci } 50662306a36Sopenharmony_ci return NULL; 50762306a36Sopenharmony_ci} 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci/* 51062306a36Sopenharmony_ci * Must be called before ipwireless_network_free(). 51162306a36Sopenharmony_ci */ 51262306a36Sopenharmony_civoid ipwireless_tty_free(struct ipw_tty *tty) 51362306a36Sopenharmony_ci{ 51462306a36Sopenharmony_ci int j; 51562306a36Sopenharmony_ci struct ipw_network *network = ttys[tty->index]->network; 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; 51862306a36Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE) { 51962306a36Sopenharmony_ci struct ipw_tty *ttyj = ttys[j]; 52062306a36Sopenharmony_ci 52162306a36Sopenharmony_ci if (ttyj) { 52262306a36Sopenharmony_ci mutex_lock(&ttyj->ipw_tty_mutex); 52362306a36Sopenharmony_ci if (get_tty(j)) 52462306a36Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 52562306a36Sopenharmony_ci ": deregistering %s device ttyIPWp%d\n", 52662306a36Sopenharmony_ci tty_type_name(ttyj->tty_type), j); 52762306a36Sopenharmony_ci if (ttyj->port.tty != NULL) { 52862306a36Sopenharmony_ci mutex_unlock(&ttyj->ipw_tty_mutex); 52962306a36Sopenharmony_ci tty_vhangup(ttyj->port.tty); 53062306a36Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here 53162306a36Sopenharmony_ci against a parallel ioctl etc */ 53262306a36Sopenharmony_ci /* FIXME2: hangup does not mean all processes 53362306a36Sopenharmony_ci * are gone */ 53462306a36Sopenharmony_ci mutex_lock(&ttyj->ipw_tty_mutex); 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci while (ttyj->port.count) 53762306a36Sopenharmony_ci do_ipw_close(ttyj); 53862306a36Sopenharmony_ci ipwireless_disassociate_network_ttys(network, 53962306a36Sopenharmony_ci ttyj->channel_idx); 54062306a36Sopenharmony_ci tty_unregister_device(ipw_tty_driver, j); 54162306a36Sopenharmony_ci tty_port_destroy(&ttyj->port); 54262306a36Sopenharmony_ci ttys[j] = NULL; 54362306a36Sopenharmony_ci mutex_unlock(&ttyj->ipw_tty_mutex); 54462306a36Sopenharmony_ci kfree(ttyj); 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci } 54762306a36Sopenharmony_ci} 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_cistatic const struct tty_operations tty_ops = { 55062306a36Sopenharmony_ci .open = ipw_open, 55162306a36Sopenharmony_ci .close = ipw_close, 55262306a36Sopenharmony_ci .hangup = ipw_hangup, 55362306a36Sopenharmony_ci .write = ipw_write, 55462306a36Sopenharmony_ci .write_room = ipw_write_room, 55562306a36Sopenharmony_ci .ioctl = ipw_ioctl, 55662306a36Sopenharmony_ci .chars_in_buffer = ipw_chars_in_buffer, 55762306a36Sopenharmony_ci .tiocmget = ipw_tiocmget, 55862306a36Sopenharmony_ci .tiocmset = ipw_tiocmset, 55962306a36Sopenharmony_ci .set_serial = ipwireless_set_serial_info, 56062306a36Sopenharmony_ci .get_serial = ipwireless_get_serial_info, 56162306a36Sopenharmony_ci}; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ciint ipwireless_tty_init(void) 56462306a36Sopenharmony_ci{ 56562306a36Sopenharmony_ci int result; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci ipw_tty_driver = tty_alloc_driver(IPWIRELESS_PCMCIA_MINORS, 56862306a36Sopenharmony_ci TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV); 56962306a36Sopenharmony_ci if (IS_ERR(ipw_tty_driver)) 57062306a36Sopenharmony_ci return PTR_ERR(ipw_tty_driver); 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; 57362306a36Sopenharmony_ci ipw_tty_driver->name = "ttyIPWp"; 57462306a36Sopenharmony_ci ipw_tty_driver->major = 0; 57562306a36Sopenharmony_ci ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; 57662306a36Sopenharmony_ci ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; 57762306a36Sopenharmony_ci ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; 57862306a36Sopenharmony_ci ipw_tty_driver->init_termios = tty_std_termios; 57962306a36Sopenharmony_ci ipw_tty_driver->init_termios.c_cflag = 58062306a36Sopenharmony_ci B9600 | CS8 | CREAD | HUPCL | CLOCAL; 58162306a36Sopenharmony_ci ipw_tty_driver->init_termios.c_ispeed = 9600; 58262306a36Sopenharmony_ci ipw_tty_driver->init_termios.c_ospeed = 9600; 58362306a36Sopenharmony_ci tty_set_operations(ipw_tty_driver, &tty_ops); 58462306a36Sopenharmony_ci result = tty_register_driver(ipw_tty_driver); 58562306a36Sopenharmony_ci if (result) { 58662306a36Sopenharmony_ci printk(KERN_ERR IPWIRELESS_PCCARD_NAME 58762306a36Sopenharmony_ci ": failed to register tty driver\n"); 58862306a36Sopenharmony_ci tty_driver_kref_put(ipw_tty_driver); 58962306a36Sopenharmony_ci return result; 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci return 0; 59362306a36Sopenharmony_ci} 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_civoid ipwireless_tty_release(void) 59662306a36Sopenharmony_ci{ 59762306a36Sopenharmony_ci tty_unregister_driver(ipw_tty_driver); 59862306a36Sopenharmony_ci tty_driver_kref_put(ipw_tty_driver); 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ciint ipwireless_tty_is_modem(struct ipw_tty *tty) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci return tty->tty_type == TTYTYPE_MODEM; 60462306a36Sopenharmony_ci} 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_civoid 60762306a36Sopenharmony_ciipwireless_tty_notify_control_line_change(struct ipw_tty *tty, 60862306a36Sopenharmony_ci unsigned int channel_idx, 60962306a36Sopenharmony_ci unsigned int control_lines, 61062306a36Sopenharmony_ci unsigned int changed_mask) 61162306a36Sopenharmony_ci{ 61262306a36Sopenharmony_ci unsigned int old_control_lines = tty->control_lines; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci tty->control_lines = (tty->control_lines & ~changed_mask) 61562306a36Sopenharmony_ci | (control_lines & changed_mask); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci /* 61862306a36Sopenharmony_ci * If DCD is de-asserted, we close the tty so pppd can tell that we 61962306a36Sopenharmony_ci * have gone offline. 62062306a36Sopenharmony_ci */ 62162306a36Sopenharmony_ci if ((old_control_lines & IPW_CONTROL_LINE_DCD) 62262306a36Sopenharmony_ci && !(tty->control_lines & IPW_CONTROL_LINE_DCD) 62362306a36Sopenharmony_ci && tty->port.tty) { 62462306a36Sopenharmony_ci tty_hangup(tty->port.tty); 62562306a36Sopenharmony_ci } 62662306a36Sopenharmony_ci} 62762306a36Sopenharmony_ci 628