18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * IPWireless 3G PCMCIA Network Driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Original code 68c2ecf20Sopenharmony_ci * by Stephen Blackheath <stephen@blacksapphire.com>, 78c2ecf20Sopenharmony_ci * Ben Martel <benm@symmetric.co.nz> 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyrighted as follows: 108c2ecf20Sopenharmony_ci * Copyright (C) 2004 by Symmetric Systems Ltd (NZ) 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * Various driver changes and rewrites, port to new kernels 138c2ecf20Sopenharmony_ci * Copyright (C) 2006-2007 Jiri Kosina 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Misc code cleanups and updates 168c2ecf20Sopenharmony_ci * Copyright (C) 2007 David Sterba 178c2ecf20Sopenharmony_ci */ 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/kernel.h> 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/mutex.h> 228c2ecf20Sopenharmony_ci#include <linux/ppp_defs.h> 238c2ecf20Sopenharmony_ci#include <linux/if.h> 248c2ecf20Sopenharmony_ci#include <linux/ppp-ioctl.h> 258c2ecf20Sopenharmony_ci#include <linux/sched.h> 268c2ecf20Sopenharmony_ci#include <linux/serial.h> 278c2ecf20Sopenharmony_ci#include <linux/slab.h> 288c2ecf20Sopenharmony_ci#include <linux/tty.h> 298c2ecf20Sopenharmony_ci#include <linux/tty_driver.h> 308c2ecf20Sopenharmony_ci#include <linux/tty_flip.h> 318c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci#include "tty.h" 348c2ecf20Sopenharmony_ci#include "network.h" 358c2ecf20Sopenharmony_ci#include "hardware.h" 368c2ecf20Sopenharmony_ci#include "main.h" 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_START (0) 398c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINORS (24) 408c2ecf20Sopenharmony_ci#define IPWIRELESS_PCMCIA_MINOR_RANGE (8) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define TTYTYPE_MODEM (0) 438c2ecf20Sopenharmony_ci#define TTYTYPE_MONITOR (1) 448c2ecf20Sopenharmony_ci#define TTYTYPE_RAS_RAW (2) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_cistruct ipw_tty { 478c2ecf20Sopenharmony_ci struct tty_port port; 488c2ecf20Sopenharmony_ci int index; 498c2ecf20Sopenharmony_ci struct ipw_hardware *hardware; 508c2ecf20Sopenharmony_ci unsigned int channel_idx; 518c2ecf20Sopenharmony_ci unsigned int secondary_channel_idx; 528c2ecf20Sopenharmony_ci int tty_type; 538c2ecf20Sopenharmony_ci struct ipw_network *network; 548c2ecf20Sopenharmony_ci unsigned int control_lines; 558c2ecf20Sopenharmony_ci struct mutex ipw_tty_mutex; 568c2ecf20Sopenharmony_ci int tx_bytes_queued; 578c2ecf20Sopenharmony_ci int closing; 588c2ecf20Sopenharmony_ci}; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic struct ipw_tty *ttys[IPWIRELESS_PCMCIA_MINORS]; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic struct tty_driver *ipw_tty_driver; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic char *tty_type_name(int tty_type) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci static char *channel_names[] = { 678c2ecf20Sopenharmony_ci "modem", 688c2ecf20Sopenharmony_ci "monitor", 698c2ecf20Sopenharmony_ci "RAS-raw" 708c2ecf20Sopenharmony_ci }; 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci return channel_names[tty_type]; 738c2ecf20Sopenharmony_ci} 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic struct ipw_tty *get_tty(int index) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci /* 788c2ecf20Sopenharmony_ci * The 'ras_raw' channel is only available when 'loopback' mode 798c2ecf20Sopenharmony_ci * is enabled. 808c2ecf20Sopenharmony_ci * Number of minor starts with 16 (_RANGE * _RAS_RAW). 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci if (!ipwireless_loopback && index >= 838c2ecf20Sopenharmony_ci IPWIRELESS_PCMCIA_MINOR_RANGE * TTYTYPE_RAS_RAW) 848c2ecf20Sopenharmony_ci return NULL; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci return ttys[index]; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_cistatic int ipw_open(struct tty_struct *linux_tty, struct file *filp) 908c2ecf20Sopenharmony_ci{ 918c2ecf20Sopenharmony_ci struct ipw_tty *tty = get_tty(linux_tty->index); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (!tty) 948c2ecf20Sopenharmony_ci return -ENODEV; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 978c2ecf20Sopenharmony_ci if (tty->port.count == 0) 988c2ecf20Sopenharmony_ci tty->tx_bytes_queued = 0; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci tty->port.count++; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci tty->port.tty = linux_tty; 1038c2ecf20Sopenharmony_ci linux_tty->driver_data = tty; 1048c2ecf20Sopenharmony_ci tty->port.low_latency = 1; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) 1078c2ecf20Sopenharmony_ci ipwireless_ppp_open(tty->network); 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci return 0; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic void do_ipw_close(struct ipw_tty *tty) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci tty->port.count--; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if (tty->port.count == 0) { 1198c2ecf20Sopenharmony_ci struct tty_struct *linux_tty = tty->port.tty; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci if (linux_tty != NULL) { 1228c2ecf20Sopenharmony_ci tty->port.tty = NULL; 1238c2ecf20Sopenharmony_ci linux_tty->driver_data = NULL; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) 1268c2ecf20Sopenharmony_ci ipwireless_ppp_close(tty->network); 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci } 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic void ipw_hangup(struct tty_struct *linux_tty) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (!tty) 1368c2ecf20Sopenharmony_ci return; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 1398c2ecf20Sopenharmony_ci if (tty->port.count == 0) { 1408c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 1418c2ecf20Sopenharmony_ci return; 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci do_ipw_close(tty); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_cistatic void ipw_close(struct tty_struct *linux_tty, struct file *filp) 1508c2ecf20Sopenharmony_ci{ 1518c2ecf20Sopenharmony_ci ipw_hangup(linux_tty); 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci/* Take data received from hardware, and send it out the tty */ 1558c2ecf20Sopenharmony_civoid ipwireless_tty_received(struct ipw_tty *tty, unsigned char *data, 1568c2ecf20Sopenharmony_ci unsigned int length) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci int work = 0; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (!tty->port.count) { 1638c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 1648c2ecf20Sopenharmony_ci return; 1658c2ecf20Sopenharmony_ci } 1668c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci work = tty_insert_flip_string(&tty->port, data, length); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci if (work != length) 1718c2ecf20Sopenharmony_ci printk(KERN_DEBUG IPWIRELESS_PCCARD_NAME 1728c2ecf20Sopenharmony_ci ": %d chars not inserted to flip buffer!\n", 1738c2ecf20Sopenharmony_ci length - work); 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci if (work) 1768c2ecf20Sopenharmony_ci tty_flip_buffer_push(&tty->port); 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic void ipw_write_packet_sent_callback(void *callback_data, 1808c2ecf20Sopenharmony_ci unsigned int packet_length) 1818c2ecf20Sopenharmony_ci{ 1828c2ecf20Sopenharmony_ci struct ipw_tty *tty = callback_data; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* 1858c2ecf20Sopenharmony_ci * Packet has been sent, so we subtract the number of bytes from our 1868c2ecf20Sopenharmony_ci * tally of outstanding TX bytes. 1878c2ecf20Sopenharmony_ci */ 1888c2ecf20Sopenharmony_ci tty->tx_bytes_queued -= packet_length; 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic int ipw_write(struct tty_struct *linux_tty, 1928c2ecf20Sopenharmony_ci const unsigned char *buf, int count) 1938c2ecf20Sopenharmony_ci{ 1948c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 1958c2ecf20Sopenharmony_ci int room, ret; 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (!tty) 1988c2ecf20Sopenharmony_ci return -ENODEV; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci mutex_lock(&tty->ipw_tty_mutex); 2018c2ecf20Sopenharmony_ci if (!tty->port.count) { 2028c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 2038c2ecf20Sopenharmony_ci return -EINVAL; 2048c2ecf20Sopenharmony_ci } 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; 2078c2ecf20Sopenharmony_ci if (room < 0) 2088c2ecf20Sopenharmony_ci room = 0; 2098c2ecf20Sopenharmony_ci /* Don't allow caller to write any more than we have room for */ 2108c2ecf20Sopenharmony_ci if (count > room) 2118c2ecf20Sopenharmony_ci count = room; 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci if (count == 0) { 2148c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci ret = ipwireless_send_packet(tty->hardware, IPW_CHANNEL_RAS, 2198c2ecf20Sopenharmony_ci buf, count, 2208c2ecf20Sopenharmony_ci ipw_write_packet_sent_callback, tty); 2218c2ecf20Sopenharmony_ci if (ret < 0) { 2228c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 2238c2ecf20Sopenharmony_ci return 0; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci tty->tx_bytes_queued += count; 2278c2ecf20Sopenharmony_ci mutex_unlock(&tty->ipw_tty_mutex); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return count; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int ipw_write_room(struct tty_struct *linux_tty) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 2358c2ecf20Sopenharmony_ci int room; 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 2388c2ecf20Sopenharmony_ci if (!tty) 2398c2ecf20Sopenharmony_ci return -ENODEV; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci if (!tty->port.count) 2428c2ecf20Sopenharmony_ci return -EINVAL; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci room = IPWIRELESS_TX_QUEUE_SIZE - tty->tx_bytes_queued; 2458c2ecf20Sopenharmony_ci if (room < 0) 2468c2ecf20Sopenharmony_ci room = 0; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return room; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic int ipwireless_get_serial_info(struct tty_struct *linux_tty, 2528c2ecf20Sopenharmony_ci struct serial_struct *ss) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci if (!tty) 2578c2ecf20Sopenharmony_ci return -ENODEV; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci if (!tty->port.count) 2608c2ecf20Sopenharmony_ci return -EINVAL; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci ss->type = PORT_UNKNOWN; 2638c2ecf20Sopenharmony_ci ss->line = tty->index; 2648c2ecf20Sopenharmony_ci ss->baud_base = 115200; 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ci 2688c2ecf20Sopenharmony_cistatic int ipwireless_set_serial_info(struct tty_struct *linux_tty, 2698c2ecf20Sopenharmony_ci struct serial_struct *ss) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci return 0; /* Keeps the PCMCIA scripts happy. */ 2728c2ecf20Sopenharmony_ci} 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int ipw_chars_in_buffer(struct tty_struct *linux_tty) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci if (!tty) 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci if (!tty->port.count) 2828c2ecf20Sopenharmony_ci return 0; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci return tty->tx_bytes_queued; 2858c2ecf20Sopenharmony_ci} 2868c2ecf20Sopenharmony_ci 2878c2ecf20Sopenharmony_cistatic int get_control_lines(struct ipw_tty *tty) 2888c2ecf20Sopenharmony_ci{ 2898c2ecf20Sopenharmony_ci unsigned int my = tty->control_lines; 2908c2ecf20Sopenharmony_ci unsigned int out = 0; 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci if (my & IPW_CONTROL_LINE_RTS) 2938c2ecf20Sopenharmony_ci out |= TIOCM_RTS; 2948c2ecf20Sopenharmony_ci if (my & IPW_CONTROL_LINE_DTR) 2958c2ecf20Sopenharmony_ci out |= TIOCM_DTR; 2968c2ecf20Sopenharmony_ci if (my & IPW_CONTROL_LINE_CTS) 2978c2ecf20Sopenharmony_ci out |= TIOCM_CTS; 2988c2ecf20Sopenharmony_ci if (my & IPW_CONTROL_LINE_DSR) 2998c2ecf20Sopenharmony_ci out |= TIOCM_DSR; 3008c2ecf20Sopenharmony_ci if (my & IPW_CONTROL_LINE_DCD) 3018c2ecf20Sopenharmony_ci out |= TIOCM_CD; 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci return out; 3048c2ecf20Sopenharmony_ci} 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic int set_control_lines(struct ipw_tty *tty, unsigned int set, 3078c2ecf20Sopenharmony_ci unsigned int clear) 3088c2ecf20Sopenharmony_ci{ 3098c2ecf20Sopenharmony_ci int ret; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci if (set & TIOCM_RTS) { 3128c2ecf20Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 1); 3138c2ecf20Sopenharmony_ci if (ret) 3148c2ecf20Sopenharmony_ci return ret; 3158c2ecf20Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 3168c2ecf20Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, 3178c2ecf20Sopenharmony_ci tty->secondary_channel_idx, 1); 3188c2ecf20Sopenharmony_ci if (ret) 3198c2ecf20Sopenharmony_ci return ret; 3208c2ecf20Sopenharmony_ci } 3218c2ecf20Sopenharmony_ci } 3228c2ecf20Sopenharmony_ci if (set & TIOCM_DTR) { 3238c2ecf20Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 1); 3248c2ecf20Sopenharmony_ci if (ret) 3258c2ecf20Sopenharmony_ci return ret; 3268c2ecf20Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 3278c2ecf20Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, 3288c2ecf20Sopenharmony_ci tty->secondary_channel_idx, 1); 3298c2ecf20Sopenharmony_ci if (ret) 3308c2ecf20Sopenharmony_ci return ret; 3318c2ecf20Sopenharmony_ci } 3328c2ecf20Sopenharmony_ci } 3338c2ecf20Sopenharmony_ci if (clear & TIOCM_RTS) { 3348c2ecf20Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, tty->channel_idx, 0); 3358c2ecf20Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 3368c2ecf20Sopenharmony_ci ret = ipwireless_set_RTS(tty->hardware, 3378c2ecf20Sopenharmony_ci tty->secondary_channel_idx, 0); 3388c2ecf20Sopenharmony_ci if (ret) 3398c2ecf20Sopenharmony_ci return ret; 3408c2ecf20Sopenharmony_ci } 3418c2ecf20Sopenharmony_ci } 3428c2ecf20Sopenharmony_ci if (clear & TIOCM_DTR) { 3438c2ecf20Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, tty->channel_idx, 0); 3448c2ecf20Sopenharmony_ci if (tty->secondary_channel_idx != -1) { 3458c2ecf20Sopenharmony_ci ret = ipwireless_set_DTR(tty->hardware, 3468c2ecf20Sopenharmony_ci tty->secondary_channel_idx, 0); 3478c2ecf20Sopenharmony_ci if (ret) 3488c2ecf20Sopenharmony_ci return ret; 3498c2ecf20Sopenharmony_ci } 3508c2ecf20Sopenharmony_ci } 3518c2ecf20Sopenharmony_ci return 0; 3528c2ecf20Sopenharmony_ci} 3538c2ecf20Sopenharmony_ci 3548c2ecf20Sopenharmony_cistatic int ipw_tiocmget(struct tty_struct *linux_tty) 3558c2ecf20Sopenharmony_ci{ 3568c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 3578c2ecf20Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_ci if (!tty) 3608c2ecf20Sopenharmony_ci return -ENODEV; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci if (!tty->port.count) 3638c2ecf20Sopenharmony_ci return -EINVAL; 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci return get_control_lines(tty); 3668c2ecf20Sopenharmony_ci} 3678c2ecf20Sopenharmony_ci 3688c2ecf20Sopenharmony_cistatic int 3698c2ecf20Sopenharmony_ciipw_tiocmset(struct tty_struct *linux_tty, 3708c2ecf20Sopenharmony_ci unsigned int set, unsigned int clear) 3718c2ecf20Sopenharmony_ci{ 3728c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 3738c2ecf20Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci if (!tty) 3768c2ecf20Sopenharmony_ci return -ENODEV; 3778c2ecf20Sopenharmony_ci 3788c2ecf20Sopenharmony_ci if (!tty->port.count) 3798c2ecf20Sopenharmony_ci return -EINVAL; 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci return set_control_lines(tty, set, clear); 3828c2ecf20Sopenharmony_ci} 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_cistatic int ipw_ioctl(struct tty_struct *linux_tty, 3858c2ecf20Sopenharmony_ci unsigned int cmd, unsigned long arg) 3868c2ecf20Sopenharmony_ci{ 3878c2ecf20Sopenharmony_ci struct ipw_tty *tty = linux_tty->driver_data; 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci if (!tty) 3908c2ecf20Sopenharmony_ci return -ENODEV; 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_ci if (!tty->port.count) 3938c2ecf20Sopenharmony_ci return -EINVAL; 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here .. */ 3968c2ecf20Sopenharmony_ci if (tty->tty_type == TTYTYPE_MODEM) { 3978c2ecf20Sopenharmony_ci switch (cmd) { 3988c2ecf20Sopenharmony_ci case PPPIOCGCHAN: 3998c2ecf20Sopenharmony_ci { 4008c2ecf20Sopenharmony_ci int chan = ipwireless_ppp_channel_index( 4018c2ecf20Sopenharmony_ci tty->network); 4028c2ecf20Sopenharmony_ci 4038c2ecf20Sopenharmony_ci if (chan < 0) 4048c2ecf20Sopenharmony_ci return -ENODEV; 4058c2ecf20Sopenharmony_ci if (put_user(chan, (int __user *) arg)) 4068c2ecf20Sopenharmony_ci return -EFAULT; 4078c2ecf20Sopenharmony_ci } 4088c2ecf20Sopenharmony_ci return 0; 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_ci case PPPIOCGUNIT: 4118c2ecf20Sopenharmony_ci { 4128c2ecf20Sopenharmony_ci int unit = ipwireless_ppp_unit_number( 4138c2ecf20Sopenharmony_ci tty->network); 4148c2ecf20Sopenharmony_ci 4158c2ecf20Sopenharmony_ci if (unit < 0) 4168c2ecf20Sopenharmony_ci return -ENODEV; 4178c2ecf20Sopenharmony_ci if (put_user(unit, (int __user *) arg)) 4188c2ecf20Sopenharmony_ci return -EFAULT; 4198c2ecf20Sopenharmony_ci } 4208c2ecf20Sopenharmony_ci return 0; 4218c2ecf20Sopenharmony_ci 4228c2ecf20Sopenharmony_ci case FIONREAD: 4238c2ecf20Sopenharmony_ci { 4248c2ecf20Sopenharmony_ci int val = 0; 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_ci if (put_user(val, (int __user *) arg)) 4278c2ecf20Sopenharmony_ci return -EFAULT; 4288c2ecf20Sopenharmony_ci } 4298c2ecf20Sopenharmony_ci return 0; 4308c2ecf20Sopenharmony_ci case TCFLSH: 4318c2ecf20Sopenharmony_ci return tty_perform_flush(linux_tty, arg); 4328c2ecf20Sopenharmony_ci } 4338c2ecf20Sopenharmony_ci } 4348c2ecf20Sopenharmony_ci return -ENOIOCTLCMD; 4358c2ecf20Sopenharmony_ci} 4368c2ecf20Sopenharmony_ci 4378c2ecf20Sopenharmony_cistatic int add_tty(int j, 4388c2ecf20Sopenharmony_ci struct ipw_hardware *hardware, 4398c2ecf20Sopenharmony_ci struct ipw_network *network, int channel_idx, 4408c2ecf20Sopenharmony_ci int secondary_channel_idx, int tty_type) 4418c2ecf20Sopenharmony_ci{ 4428c2ecf20Sopenharmony_ci ttys[j] = kzalloc(sizeof(struct ipw_tty), GFP_KERNEL); 4438c2ecf20Sopenharmony_ci if (!ttys[j]) 4448c2ecf20Sopenharmony_ci return -ENOMEM; 4458c2ecf20Sopenharmony_ci ttys[j]->index = j; 4468c2ecf20Sopenharmony_ci ttys[j]->hardware = hardware; 4478c2ecf20Sopenharmony_ci ttys[j]->channel_idx = channel_idx; 4488c2ecf20Sopenharmony_ci ttys[j]->secondary_channel_idx = secondary_channel_idx; 4498c2ecf20Sopenharmony_ci ttys[j]->network = network; 4508c2ecf20Sopenharmony_ci ttys[j]->tty_type = tty_type; 4518c2ecf20Sopenharmony_ci mutex_init(&ttys[j]->ipw_tty_mutex); 4528c2ecf20Sopenharmony_ci tty_port_init(&ttys[j]->port); 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci tty_port_register_device(&ttys[j]->port, ipw_tty_driver, j, NULL); 4558c2ecf20Sopenharmony_ci ipwireless_associate_network_tty(network, channel_idx, ttys[j]); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci if (secondary_channel_idx != -1) 4588c2ecf20Sopenharmony_ci ipwireless_associate_network_tty(network, 4598c2ecf20Sopenharmony_ci secondary_channel_idx, 4608c2ecf20Sopenharmony_ci ttys[j]); 4618c2ecf20Sopenharmony_ci /* check if we provide raw device (if loopback is enabled) */ 4628c2ecf20Sopenharmony_ci if (get_tty(j)) 4638c2ecf20Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 4648c2ecf20Sopenharmony_ci ": registering %s device ttyIPWp%d\n", 4658c2ecf20Sopenharmony_ci tty_type_name(tty_type), j); 4668c2ecf20Sopenharmony_ci 4678c2ecf20Sopenharmony_ci return 0; 4688c2ecf20Sopenharmony_ci} 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_cistruct ipw_tty *ipwireless_tty_create(struct ipw_hardware *hardware, 4718c2ecf20Sopenharmony_ci struct ipw_network *network) 4728c2ecf20Sopenharmony_ci{ 4738c2ecf20Sopenharmony_ci int i, j; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci for (i = 0; i < IPWIRELESS_PCMCIA_MINOR_RANGE; i++) { 4768c2ecf20Sopenharmony_ci int allfree = 1; 4778c2ecf20Sopenharmony_ci 4788c2ecf20Sopenharmony_ci for (j = i; j < IPWIRELESS_PCMCIA_MINORS; 4798c2ecf20Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE) 4808c2ecf20Sopenharmony_ci if (ttys[j] != NULL) { 4818c2ecf20Sopenharmony_ci allfree = 0; 4828c2ecf20Sopenharmony_ci break; 4838c2ecf20Sopenharmony_ci } 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci if (allfree) { 4868c2ecf20Sopenharmony_ci j = i; 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci if (add_tty(j, hardware, network, 4898c2ecf20Sopenharmony_ci IPW_CHANNEL_DIALLER, IPW_CHANNEL_RAS, 4908c2ecf20Sopenharmony_ci TTYTYPE_MODEM)) 4918c2ecf20Sopenharmony_ci return NULL; 4928c2ecf20Sopenharmony_ci 4938c2ecf20Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE; 4948c2ecf20Sopenharmony_ci if (add_tty(j, hardware, network, 4958c2ecf20Sopenharmony_ci IPW_CHANNEL_DIALLER, -1, 4968c2ecf20Sopenharmony_ci TTYTYPE_MONITOR)) 4978c2ecf20Sopenharmony_ci return NULL; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE; 5008c2ecf20Sopenharmony_ci if (add_tty(j, hardware, network, 5018c2ecf20Sopenharmony_ci IPW_CHANNEL_RAS, -1, 5028c2ecf20Sopenharmony_ci TTYTYPE_RAS_RAW)) 5038c2ecf20Sopenharmony_ci return NULL; 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci return ttys[i]; 5068c2ecf20Sopenharmony_ci } 5078c2ecf20Sopenharmony_ci } 5088c2ecf20Sopenharmony_ci return NULL; 5098c2ecf20Sopenharmony_ci} 5108c2ecf20Sopenharmony_ci 5118c2ecf20Sopenharmony_ci/* 5128c2ecf20Sopenharmony_ci * Must be called before ipwireless_network_free(). 5138c2ecf20Sopenharmony_ci */ 5148c2ecf20Sopenharmony_civoid ipwireless_tty_free(struct ipw_tty *tty) 5158c2ecf20Sopenharmony_ci{ 5168c2ecf20Sopenharmony_ci int j; 5178c2ecf20Sopenharmony_ci struct ipw_network *network = ttys[tty->index]->network; 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci for (j = tty->index; j < IPWIRELESS_PCMCIA_MINORS; 5208c2ecf20Sopenharmony_ci j += IPWIRELESS_PCMCIA_MINOR_RANGE) { 5218c2ecf20Sopenharmony_ci struct ipw_tty *ttyj = ttys[j]; 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_ci if (ttyj) { 5248c2ecf20Sopenharmony_ci mutex_lock(&ttyj->ipw_tty_mutex); 5258c2ecf20Sopenharmony_ci if (get_tty(j)) 5268c2ecf20Sopenharmony_ci printk(KERN_INFO IPWIRELESS_PCCARD_NAME 5278c2ecf20Sopenharmony_ci ": deregistering %s device ttyIPWp%d\n", 5288c2ecf20Sopenharmony_ci tty_type_name(ttyj->tty_type), j); 5298c2ecf20Sopenharmony_ci ttyj->closing = 1; 5308c2ecf20Sopenharmony_ci if (ttyj->port.tty != NULL) { 5318c2ecf20Sopenharmony_ci mutex_unlock(&ttyj->ipw_tty_mutex); 5328c2ecf20Sopenharmony_ci tty_vhangup(ttyj->port.tty); 5338c2ecf20Sopenharmony_ci /* FIXME: Exactly how is the tty object locked here 5348c2ecf20Sopenharmony_ci against a parallel ioctl etc */ 5358c2ecf20Sopenharmony_ci /* FIXME2: hangup does not mean all processes 5368c2ecf20Sopenharmony_ci * are gone */ 5378c2ecf20Sopenharmony_ci mutex_lock(&ttyj->ipw_tty_mutex); 5388c2ecf20Sopenharmony_ci } 5398c2ecf20Sopenharmony_ci while (ttyj->port.count) 5408c2ecf20Sopenharmony_ci do_ipw_close(ttyj); 5418c2ecf20Sopenharmony_ci ipwireless_disassociate_network_ttys(network, 5428c2ecf20Sopenharmony_ci ttyj->channel_idx); 5438c2ecf20Sopenharmony_ci tty_unregister_device(ipw_tty_driver, j); 5448c2ecf20Sopenharmony_ci tty_port_destroy(&ttyj->port); 5458c2ecf20Sopenharmony_ci ttys[j] = NULL; 5468c2ecf20Sopenharmony_ci mutex_unlock(&ttyj->ipw_tty_mutex); 5478c2ecf20Sopenharmony_ci kfree(ttyj); 5488c2ecf20Sopenharmony_ci } 5498c2ecf20Sopenharmony_ci } 5508c2ecf20Sopenharmony_ci} 5518c2ecf20Sopenharmony_ci 5528c2ecf20Sopenharmony_cistatic const struct tty_operations tty_ops = { 5538c2ecf20Sopenharmony_ci .open = ipw_open, 5548c2ecf20Sopenharmony_ci .close = ipw_close, 5558c2ecf20Sopenharmony_ci .hangup = ipw_hangup, 5568c2ecf20Sopenharmony_ci .write = ipw_write, 5578c2ecf20Sopenharmony_ci .write_room = ipw_write_room, 5588c2ecf20Sopenharmony_ci .ioctl = ipw_ioctl, 5598c2ecf20Sopenharmony_ci .chars_in_buffer = ipw_chars_in_buffer, 5608c2ecf20Sopenharmony_ci .tiocmget = ipw_tiocmget, 5618c2ecf20Sopenharmony_ci .tiocmset = ipw_tiocmset, 5628c2ecf20Sopenharmony_ci .set_serial = ipwireless_set_serial_info, 5638c2ecf20Sopenharmony_ci .get_serial = ipwireless_get_serial_info, 5648c2ecf20Sopenharmony_ci}; 5658c2ecf20Sopenharmony_ci 5668c2ecf20Sopenharmony_ciint ipwireless_tty_init(void) 5678c2ecf20Sopenharmony_ci{ 5688c2ecf20Sopenharmony_ci int result; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci ipw_tty_driver = alloc_tty_driver(IPWIRELESS_PCMCIA_MINORS); 5718c2ecf20Sopenharmony_ci if (!ipw_tty_driver) 5728c2ecf20Sopenharmony_ci return -ENOMEM; 5738c2ecf20Sopenharmony_ci 5748c2ecf20Sopenharmony_ci ipw_tty_driver->driver_name = IPWIRELESS_PCCARD_NAME; 5758c2ecf20Sopenharmony_ci ipw_tty_driver->name = "ttyIPWp"; 5768c2ecf20Sopenharmony_ci ipw_tty_driver->major = 0; 5778c2ecf20Sopenharmony_ci ipw_tty_driver->minor_start = IPWIRELESS_PCMCIA_START; 5788c2ecf20Sopenharmony_ci ipw_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; 5798c2ecf20Sopenharmony_ci ipw_tty_driver->subtype = SERIAL_TYPE_NORMAL; 5808c2ecf20Sopenharmony_ci ipw_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; 5818c2ecf20Sopenharmony_ci ipw_tty_driver->init_termios = tty_std_termios; 5828c2ecf20Sopenharmony_ci ipw_tty_driver->init_termios.c_cflag = 5838c2ecf20Sopenharmony_ci B9600 | CS8 | CREAD | HUPCL | CLOCAL; 5848c2ecf20Sopenharmony_ci ipw_tty_driver->init_termios.c_ispeed = 9600; 5858c2ecf20Sopenharmony_ci ipw_tty_driver->init_termios.c_ospeed = 9600; 5868c2ecf20Sopenharmony_ci tty_set_operations(ipw_tty_driver, &tty_ops); 5878c2ecf20Sopenharmony_ci result = tty_register_driver(ipw_tty_driver); 5888c2ecf20Sopenharmony_ci if (result) { 5898c2ecf20Sopenharmony_ci printk(KERN_ERR IPWIRELESS_PCCARD_NAME 5908c2ecf20Sopenharmony_ci ": failed to register tty driver\n"); 5918c2ecf20Sopenharmony_ci put_tty_driver(ipw_tty_driver); 5928c2ecf20Sopenharmony_ci return result; 5938c2ecf20Sopenharmony_ci } 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_ci return 0; 5968c2ecf20Sopenharmony_ci} 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_civoid ipwireless_tty_release(void) 5998c2ecf20Sopenharmony_ci{ 6008c2ecf20Sopenharmony_ci int ret; 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci ret = tty_unregister_driver(ipw_tty_driver); 6038c2ecf20Sopenharmony_ci put_tty_driver(ipw_tty_driver); 6048c2ecf20Sopenharmony_ci if (ret != 0) 6058c2ecf20Sopenharmony_ci printk(KERN_ERR IPWIRELESS_PCCARD_NAME 6068c2ecf20Sopenharmony_ci ": tty_unregister_driver failed with code %d\n", ret); 6078c2ecf20Sopenharmony_ci} 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ciint ipwireless_tty_is_modem(struct ipw_tty *tty) 6108c2ecf20Sopenharmony_ci{ 6118c2ecf20Sopenharmony_ci return tty->tty_type == TTYTYPE_MODEM; 6128c2ecf20Sopenharmony_ci} 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_civoid 6158c2ecf20Sopenharmony_ciipwireless_tty_notify_control_line_change(struct ipw_tty *tty, 6168c2ecf20Sopenharmony_ci unsigned int channel_idx, 6178c2ecf20Sopenharmony_ci unsigned int control_lines, 6188c2ecf20Sopenharmony_ci unsigned int changed_mask) 6198c2ecf20Sopenharmony_ci{ 6208c2ecf20Sopenharmony_ci unsigned int old_control_lines = tty->control_lines; 6218c2ecf20Sopenharmony_ci 6228c2ecf20Sopenharmony_ci tty->control_lines = (tty->control_lines & ~changed_mask) 6238c2ecf20Sopenharmony_ci | (control_lines & changed_mask); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci /* 6268c2ecf20Sopenharmony_ci * If DCD is de-asserted, we close the tty so pppd can tell that we 6278c2ecf20Sopenharmony_ci * have gone offline. 6288c2ecf20Sopenharmony_ci */ 6298c2ecf20Sopenharmony_ci if ((old_control_lines & IPW_CONTROL_LINE_DCD) 6308c2ecf20Sopenharmony_ci && !(tty->control_lines & IPW_CONTROL_LINE_DCD) 6318c2ecf20Sopenharmony_ci && tty->port.tty) { 6328c2ecf20Sopenharmony_ci tty_hangup(tty->port.tty); 6338c2ecf20Sopenharmony_ci } 6348c2ecf20Sopenharmony_ci} 6358c2ecf20Sopenharmony_ci 636