| /* | |
| * $Id: ctctty.c,v 1.26 2004/08/04 11:06:55 mschwide Exp $ | |
| * | |
| * CTC / ESCON network driver, tty interface. | |
| * | |
| * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation | |
| * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com) | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <linux/config.h> | |
| #include <linux/module.h> | |
| #include <linux/tty.h> | |
| #include <linux/serial_reg.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/delay.h> | |
| #include <asm/uaccess.h> | |
| #include <linux/devfs_fs_kernel.h> | |
| #include "ctctty.h" | |
| #include "ctcdbug.h" | |
| #define CTC_TTY_MAJOR 43 | |
| #define CTC_TTY_MAX_DEVICES 64 | |
| #define CTC_ASYNC_MAGIC 0x49344C01 /* for paranoia-checking */ | |
| #define CTC_ASYNC_INITIALIZED 0x80000000 /* port was initialized */ | |
| #define CTC_ASYNC_NORMAL_ACTIVE 0x20000000 /* Normal device active */ | |
| #define CTC_ASYNC_CLOSING 0x08000000 /* Serial port is closing */ | |
| #define CTC_ASYNC_CTS_FLOW 0x04000000 /* Do CTS flow control */ | |
| #define CTC_ASYNC_CHECK_CD 0x02000000 /* i.e., CLOCAL */ | |
| #define CTC_ASYNC_HUP_NOTIFY 0x0001 /* Notify tty on hangups/closes */ | |
| #define CTC_ASYNC_NETDEV_OPEN 0x0002 /* Underlying netdev is open */ | |
| #define CTC_ASYNC_TX_LINESTAT 0x0004 /* Must send line status */ | |
| #define CTC_ASYNC_SPLIT_TERMIOS 0x0008 /* Sep. termios for dialin/out */ | |
| #define CTC_TTY_XMIT_SIZE 1024 /* Default bufsize for write */ | |
| #define CTC_SERIAL_XMIT_MAX 4000 /* Maximum bufsize for write */ | |
| /* Private data (similar to async_struct in <linux/serial.h>) */ | |
| typedef struct { | |
| int magic; | |
| int flags; /* defined in tty.h */ | |
| int mcr; /* Modem control register */ | |
| int msr; /* Modem status register */ | |
| int lsr; /* Line status register */ | |
| int line; | |
| int count; /* # of fd on device */ | |
| int blocked_open; /* # of blocked opens */ | |
| struct net_device *netdev; | |
| struct sk_buff_head tx_queue; /* transmit queue */ | |
| struct sk_buff_head rx_queue; /* receive queue */ | |
| struct tty_struct *tty; /* Pointer to corresponding tty */ | |
| wait_queue_head_t open_wait; | |
| wait_queue_head_t close_wait; | |
| struct semaphore write_sem; | |
| struct tasklet_struct tasklet; | |
| struct timer_list stoptimer; | |
| } ctc_tty_info; | |
| /* Description of one CTC-tty */ | |
| typedef struct { | |
| struct tty_driver *ctc_tty_device; /* tty-device */ | |
| ctc_tty_info info[CTC_TTY_MAX_DEVICES]; /* Private data */ | |
| } ctc_tty_driver; | |
| static ctc_tty_driver *driver; | |
| /* Leave this unchanged unless you know what you do! */ | |
| #define MODEM_PARANOIA_CHECK | |
| #define MODEM_DO_RESTART | |
| #define CTC_TTY_NAME "ctctty" | |
| static __u32 ctc_tty_magic = CTC_ASYNC_MAGIC; | |
| static int ctc_tty_shuttingdown = 0; | |
| static spinlock_t ctc_tty_lock; | |
| /* ctc_tty_try_read() is called from within ctc_tty_rcv_skb() | |
| * to stuff incoming data directly into a tty's flip-buffer. If the | |
| * flip buffer is full, the packet gets queued up. | |
| * | |
| * Return: | |
| * 1 = Success | |
| * 0 = Failure, data has to be buffered and later processed by | |
| * ctc_tty_readmodem(). | |
| */ | |
| static int | |
| ctc_tty_try_read(ctc_tty_info * info, struct sk_buff *skb) | |
| { | |
| int c; | |
| int len; | |
| struct tty_struct *tty; | |
| DBF_TEXT(trace, 5, __FUNCTION__); | |
| if ((tty = info->tty)) { | |
| if (info->mcr & UART_MCR_RTS) { | |
| c = TTY_FLIPBUF_SIZE - tty->flip.count; | |
| len = skb->len; | |
| if (c >= len) { | |
| memcpy(tty->flip.char_buf_ptr, skb->data, len); | |
| memset(tty->flip.flag_buf_ptr, 0, len); | |
| tty->flip.count += len; | |
| tty->flip.char_buf_ptr += len; | |
| tty->flip.flag_buf_ptr += len; | |
| tty_flip_buffer_push(tty); | |
| kfree_skb(skb); | |
| return 1; | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| /* ctc_tty_readmodem() is called periodically from within timer-interrupt. | |
| * It tries getting received data from the receive queue an stuff it into | |
| * the tty's flip-buffer. | |
| */ | |
| static int | |
| ctc_tty_readmodem(ctc_tty_info *info) | |
| { | |
| int ret = 1; | |
| struct tty_struct *tty; | |
| DBF_TEXT(trace, 5, __FUNCTION__); | |
| if ((tty = info->tty)) { | |
| if (info->mcr & UART_MCR_RTS) { | |
| int c = TTY_FLIPBUF_SIZE - tty->flip.count; | |
| struct sk_buff *skb; | |
| if ((c > 0) && (skb = skb_dequeue(&info->rx_queue))) { | |
| int len = skb->len; | |
| if (len > c) | |
| len = c; | |
| memcpy(tty->flip.char_buf_ptr, skb->data, len); | |
| skb_pull(skb, len); | |
| memset(tty->flip.flag_buf_ptr, 0, len); | |
| tty->flip.count += len; | |
| tty->flip.char_buf_ptr += len; | |
| tty->flip.flag_buf_ptr += len; | |
| tty_flip_buffer_push(tty); | |
| if (skb->len > 0) | |
| skb_queue_head(&info->rx_queue, skb); | |
| else { | |
| kfree_skb(skb); | |
| ret = skb_queue_len(&info->rx_queue); | |
| } | |
| } | |
| } | |
| } | |
| return ret; | |
| } | |
| void | |
| ctc_tty_setcarrier(struct net_device *netdev, int on) | |
| { | |
| int i; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if ((!driver) || ctc_tty_shuttingdown) | |
| return; | |
| for (i = 0; i < CTC_TTY_MAX_DEVICES; i++) | |
| if (driver->info[i].netdev == netdev) { | |
| ctc_tty_info *info = &driver->info[i]; | |
| if (on) | |
| info->msr |= UART_MSR_DCD; | |
| else | |
| info->msr &= ~UART_MSR_DCD; | |
| if ((info->flags & CTC_ASYNC_CHECK_CD) && (!on)) | |
| tty_hangup(info->tty); | |
| } | |
| } | |
| void | |
| ctc_tty_netif_rx(struct sk_buff *skb) | |
| { | |
| int i; | |
| ctc_tty_info *info = NULL; | |
| DBF_TEXT(trace, 5, __FUNCTION__); | |
| if (!skb) | |
| return; | |
| if ((!skb->dev) || (!driver) || ctc_tty_shuttingdown) { | |
| dev_kfree_skb(skb); | |
| return; | |
| } | |
| for (i = 0; i < CTC_TTY_MAX_DEVICES; i++) | |
| if (driver->info[i].netdev == skb->dev) { | |
| info = &driver->info[i]; | |
| break; | |
| } | |
| if (!info) { | |
| dev_kfree_skb(skb); | |
| return; | |
| } | |
| if (skb->len < 6) { | |
| dev_kfree_skb(skb); | |
| return; | |
| } | |
| if (memcmp(skb->data, &ctc_tty_magic, sizeof(__u32))) { | |
| dev_kfree_skb(skb); | |
| return; | |
| } | |
| skb_pull(skb, sizeof(__u32)); | |
| i = *((int *)skb->data); | |
| skb_pull(skb, sizeof(info->mcr)); | |
| if (i & UART_MCR_RTS) { | |
| info->msr |= UART_MSR_CTS; | |
| if (info->flags & CTC_ASYNC_CTS_FLOW) | |
| info->tty->hw_stopped = 0; | |
| } else { | |
| info->msr &= ~UART_MSR_CTS; | |
| if (info->flags & CTC_ASYNC_CTS_FLOW) | |
| info->tty->hw_stopped = 1; | |
| } | |
| if (i & UART_MCR_DTR) | |
| info->msr |= UART_MSR_DSR; | |
| else | |
| info->msr &= ~UART_MSR_DSR; | |
| if (skb->len <= 0) { | |
| kfree_skb(skb); | |
| return; | |
| } | |
| /* Try to deliver directly via tty-flip-buf if queue is empty */ | |
| if (skb_queue_empty(&info->rx_queue)) | |
| if (ctc_tty_try_read(info, skb)) | |
| return; | |
| /* Direct deliver failed or queue wasn't empty. | |
| * Queue up for later dequeueing via timer-irq. | |
| */ | |
| skb_queue_tail(&info->rx_queue, skb); | |
| /* Schedule dequeuing */ | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| static int | |
| ctc_tty_tint(ctc_tty_info * info) | |
| { | |
| struct sk_buff *skb = skb_dequeue(&info->tx_queue); | |
| int stopped = (info->tty->hw_stopped || info->tty->stopped); | |
| int wake = 1; | |
| int rc; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (!info->netdev) { | |
| if (skb) | |
| kfree_skb(skb); | |
| return 0; | |
| } | |
| if (info->flags & CTC_ASYNC_TX_LINESTAT) { | |
| int skb_res = info->netdev->hard_header_len + | |
| sizeof(info->mcr) + sizeof(__u32); | |
| /* If we must update line status, | |
| * create an empty dummy skb and insert it. | |
| */ | |
| if (skb) | |
| skb_queue_head(&info->tx_queue, skb); | |
| skb = dev_alloc_skb(skb_res); | |
| if (!skb) { | |
| printk(KERN_WARNING | |
| "ctc_tty: Out of memory in %s%d tint\n", | |
| CTC_TTY_NAME, info->line); | |
| return 1; | |
| } | |
| skb_reserve(skb, skb_res); | |
| stopped = 0; | |
| wake = 0; | |
| } | |
| if (!skb) | |
| return 0; | |
| if (stopped) { | |
| skb_queue_head(&info->tx_queue, skb); | |
| return 1; | |
| } | |
| #if 0 | |
| if (skb->len > 0) | |
| printk(KERN_DEBUG "tint: %d %02x\n", skb->len, *(skb->data)); | |
| else | |
| printk(KERN_DEBUG "tint: %d STAT\n", skb->len); | |
| #endif | |
| memcpy(skb_push(skb, sizeof(info->mcr)), &info->mcr, sizeof(info->mcr)); | |
| memcpy(skb_push(skb, sizeof(__u32)), &ctc_tty_magic, sizeof(__u32)); | |
| rc = info->netdev->hard_start_xmit(skb, info->netdev); | |
| if (rc) { | |
| skb_pull(skb, sizeof(info->mcr) + sizeof(__u32)); | |
| if (skb->len > 0) | |
| skb_queue_head(&info->tx_queue, skb); | |
| else | |
| kfree_skb(skb); | |
| } else { | |
| struct tty_struct *tty = info->tty; | |
| info->flags &= ~CTC_ASYNC_TX_LINESTAT; | |
| if (tty) { | |
| tty_wakeup(tty); | |
| } | |
| } | |
| return (skb_queue_empty(&info->tx_queue) ? 0 : 1); | |
| } | |
| /************************************************************ | |
| * | |
| * Modem-functions | |
| * | |
| * mostly "stolen" from original Linux-serial.c and friends. | |
| * | |
| ************************************************************/ | |
| static inline int | |
| ctc_tty_paranoia_check(ctc_tty_info * info, char *name, const char *routine) | |
| { | |
| #ifdef MODEM_PARANOIA_CHECK | |
| if (!info) { | |
| printk(KERN_WARNING "ctc_tty: null info_struct for %s in %s\n", | |
| name, routine); | |
| return 1; | |
| } | |
| if (info->magic != CTC_ASYNC_MAGIC) { | |
| printk(KERN_WARNING "ctc_tty: bad magic for info struct %s in %s\n", | |
| name, routine); | |
| return 1; | |
| } | |
| #endif | |
| return 0; | |
| } | |
| static void | |
| ctc_tty_inject(ctc_tty_info *info, char c) | |
| { | |
| int skb_res; | |
| struct sk_buff *skb; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_shuttingdown) | |
| return; | |
| skb_res = info->netdev->hard_header_len + sizeof(info->mcr) + | |
| sizeof(__u32) + 1; | |
| skb = dev_alloc_skb(skb_res); | |
| if (!skb) { | |
| printk(KERN_WARNING | |
| "ctc_tty: Out of memory in %s%d tx_inject\n", | |
| CTC_TTY_NAME, info->line); | |
| return; | |
| } | |
| skb_reserve(skb, skb_res); | |
| *(skb_put(skb, 1)) = c; | |
| skb_queue_head(&info->tx_queue, skb); | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| static void | |
| ctc_tty_transmit_status(ctc_tty_info *info) | |
| { | |
| DBF_TEXT(trace, 5, __FUNCTION__); | |
| if (ctc_tty_shuttingdown) | |
| return; | |
| info->flags |= CTC_ASYNC_TX_LINESTAT; | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| static void | |
| ctc_tty_change_speed(ctc_tty_info * info) | |
| { | |
| unsigned int cflag; | |
| unsigned int quot; | |
| int i; | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| if (!info->tty || !info->tty->termios) | |
| return; | |
| cflag = info->tty->termios->c_cflag; | |
| quot = i = cflag & CBAUD; | |
| if (i & CBAUDEX) { | |
| i &= ~CBAUDEX; | |
| if (i < 1 || i > 2) | |
| info->tty->termios->c_cflag &= ~CBAUDEX; | |
| else | |
| i += 15; | |
| } | |
| if (quot) { | |
| info->mcr |= UART_MCR_DTR; | |
| info->mcr |= UART_MCR_RTS; | |
| ctc_tty_transmit_status(info); | |
| } else { | |
| info->mcr &= ~UART_MCR_DTR; | |
| info->mcr &= ~UART_MCR_RTS; | |
| ctc_tty_transmit_status(info); | |
| return; | |
| } | |
| /* CTS flow control flag and modem status interrupts */ | |
| if (cflag & CRTSCTS) { | |
| info->flags |= CTC_ASYNC_CTS_FLOW; | |
| } else | |
| info->flags &= ~CTC_ASYNC_CTS_FLOW; | |
| if (cflag & CLOCAL) | |
| info->flags &= ~CTC_ASYNC_CHECK_CD; | |
| else { | |
| info->flags |= CTC_ASYNC_CHECK_CD; | |
| } | |
| } | |
| static int | |
| ctc_tty_startup(ctc_tty_info * info) | |
| { | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| if (info->flags & CTC_ASYNC_INITIALIZED) | |
| return 0; | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "starting up %s%d ...\n", CTC_TTY_NAME, info->line); | |
| #endif | |
| /* | |
| * Now, initialize the UART | |
| */ | |
| info->mcr = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2; | |
| if (info->tty) | |
| clear_bit(TTY_IO_ERROR, &info->tty->flags); | |
| /* | |
| * and set the speed of the serial port | |
| */ | |
| ctc_tty_change_speed(info); | |
| info->flags |= CTC_ASYNC_INITIALIZED; | |
| if (!(info->flags & CTC_ASYNC_NETDEV_OPEN)) | |
| info->netdev->open(info->netdev); | |
| info->flags |= CTC_ASYNC_NETDEV_OPEN; | |
| return 0; | |
| } | |
| static void | |
| ctc_tty_stopdev(unsigned long data) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *)data; | |
| if ((!info) || (!info->netdev) || | |
| (info->flags & CTC_ASYNC_INITIALIZED)) | |
| return; | |
| info->netdev->stop(info->netdev); | |
| info->flags &= ~CTC_ASYNC_NETDEV_OPEN; | |
| } | |
| /* | |
| * This routine will shutdown a serial port; interrupts are disabled, and | |
| * DTR is dropped if the hangup on close termio flag is on. | |
| */ | |
| static void | |
| ctc_tty_shutdown(ctc_tty_info * info) | |
| { | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| if (!(info->flags & CTC_ASYNC_INITIALIZED)) | |
| return; | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "Shutting down %s%d ....\n", CTC_TTY_NAME, info->line); | |
| #endif | |
| info->msr &= ~UART_MSR_RI; | |
| if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) | |
| info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS); | |
| if (info->tty) | |
| set_bit(TTY_IO_ERROR, &info->tty->flags); | |
| mod_timer(&info->stoptimer, jiffies + (10 * HZ)); | |
| skb_queue_purge(&info->tx_queue); | |
| skb_queue_purge(&info->rx_queue); | |
| info->flags &= ~CTC_ASYNC_INITIALIZED; | |
| } | |
| /* ctc_tty_write() is the main send-routine. It is called from the upper | |
| * levels within the kernel to perform sending data. Depending on the | |
| * online-flag it either directs output to the at-command-interpreter or | |
| * to the lower level. Additional tasks done here: | |
| * - If online, check for escape-sequence (+++) | |
| * - If sending audio-data, call ctc_tty_DLEdown() to parse DLE-codes. | |
| * - If receiving audio-data, call ctc_tty_end_vrx() to abort if needed. | |
| * - If dialing, abort dial. | |
| */ | |
| static int | |
| ctc_tty_write(struct tty_struct *tty, const u_char * buf, int count) | |
| { | |
| int c; | |
| int total = 0; | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| DBF_TEXT(trace, 5, __FUNCTION__); | |
| if (ctc_tty_shuttingdown) | |
| goto ex; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_write")) | |
| goto ex; | |
| if (!tty) | |
| goto ex; | |
| if (!info->netdev) { | |
| total = -ENODEV; | |
| goto ex; | |
| } | |
| while (1) { | |
| struct sk_buff *skb; | |
| int skb_res; | |
| c = (count < CTC_TTY_XMIT_SIZE) ? count : CTC_TTY_XMIT_SIZE; | |
| if (c <= 0) | |
| break; | |
| skb_res = info->netdev->hard_header_len + sizeof(info->mcr) + | |
| + sizeof(__u32); | |
| skb = dev_alloc_skb(skb_res + c); | |
| if (!skb) { | |
| printk(KERN_WARNING | |
| "ctc_tty: Out of memory in %s%d write\n", | |
| CTC_TTY_NAME, info->line); | |
| break; | |
| } | |
| skb_reserve(skb, skb_res); | |
| memcpy(skb_put(skb, c), buf, c); | |
| skb_queue_tail(&info->tx_queue, skb); | |
| buf += c; | |
| total += c; | |
| count -= c; | |
| } | |
| if (skb_queue_len(&info->tx_queue)) { | |
| info->lsr &= ~UART_LSR_TEMT; | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| ex: | |
| DBF_TEXT(trace, 6, __FUNCTION__); | |
| return total; | |
| } | |
| static int | |
| ctc_tty_write_room(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_write_room")) | |
| return 0; | |
| return CTC_TTY_XMIT_SIZE; | |
| } | |
| static int | |
| ctc_tty_chars_in_buffer(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_chars_in_buffer")) | |
| return 0; | |
| return 0; | |
| } | |
| static void | |
| ctc_tty_flush_buffer(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info; | |
| unsigned long flags; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (!tty) | |
| goto ex; | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| info = (ctc_tty_info *) tty->driver_data; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_flush_buffer")) { | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| goto ex; | |
| } | |
| skb_queue_purge(&info->tx_queue); | |
| info->lsr |= UART_LSR_TEMT; | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| wake_up_interruptible(&tty->write_wait); | |
| tty_wakeup(tty); | |
| ex: | |
| DBF_TEXT_(trace, 2, "ex: %s ", __FUNCTION__); | |
| return; | |
| } | |
| static void | |
| ctc_tty_flush_chars(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_shuttingdown) | |
| return; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_flush_chars")) | |
| return; | |
| if (tty->stopped || tty->hw_stopped || (!skb_queue_len(&info->tx_queue))) | |
| return; | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| /* | |
| * ------------------------------------------------------------ | |
| * ctc_tty_throttle() | |
| * | |
| * This routine is called by the upper-layer tty layer to signal that | |
| * incoming characters should be throttled. | |
| * ------------------------------------------------------------ | |
| */ | |
| static void | |
| ctc_tty_throttle(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_throttle")) | |
| return; | |
| info->mcr &= ~UART_MCR_RTS; | |
| if (I_IXOFF(tty)) | |
| ctc_tty_inject(info, STOP_CHAR(tty)); | |
| ctc_tty_transmit_status(info); | |
| } | |
| static void | |
| ctc_tty_unthrottle(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_unthrottle")) | |
| return; | |
| info->mcr |= UART_MCR_RTS; | |
| if (I_IXOFF(tty)) | |
| ctc_tty_inject(info, START_CHAR(tty)); | |
| ctc_tty_transmit_status(info); | |
| } | |
| /* | |
| * ------------------------------------------------------------ | |
| * ctc_tty_ioctl() and friends | |
| * ------------------------------------------------------------ | |
| */ | |
| /* | |
| * ctc_tty_get_lsr_info - get line status register info | |
| * | |
| * Purpose: Let user call ioctl() to get info when the UART physically | |
| * is emptied. On bus types like RS485, the transmitter must | |
| * release the bus after transmitting. This must be done when | |
| * the transmit shift register is empty, not be done when the | |
| * transmit holding register is empty. This functionality | |
| * allows RS485 driver to be written in user space. | |
| */ | |
| static int | |
| ctc_tty_get_lsr_info(ctc_tty_info * info, uint __user *value) | |
| { | |
| u_char status; | |
| uint result; | |
| ulong flags; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| status = info->lsr; | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); | |
| put_user(result, value); | |
| return 0; | |
| } | |
| static int ctc_tty_tiocmget(struct tty_struct *tty, struct file *file) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| u_char control, | |
| status; | |
| uint result; | |
| ulong flags; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl")) | |
| return -ENODEV; | |
| if (tty->flags & (1 << TTY_IO_ERROR)) | |
| return -EIO; | |
| control = info->mcr; | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| status = info->msr; | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| result = ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) | |
| | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) | |
| | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) | |
| | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) | |
| | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) | |
| | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0); | |
| return result; | |
| } | |
| static int | |
| ctc_tty_tiocmset(struct tty_struct *tty, struct file *file, | |
| unsigned int set, unsigned int clear) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl")) | |
| return -ENODEV; | |
| if (tty->flags & (1 << TTY_IO_ERROR)) | |
| return -EIO; | |
| if (set & TIOCM_RTS) | |
| info->mcr |= UART_MCR_RTS; | |
| if (set & TIOCM_DTR) | |
| info->mcr |= UART_MCR_DTR; | |
| if (clear & TIOCM_RTS) | |
| info->mcr &= ~UART_MCR_RTS; | |
| if (clear & TIOCM_DTR) | |
| info->mcr &= ~UART_MCR_DTR; | |
| if ((set | clear) & (TIOCM_RTS|TIOCM_DTR)) | |
| ctc_tty_transmit_status(info); | |
| return 0; | |
| } | |
| static int | |
| ctc_tty_ioctl(struct tty_struct *tty, struct file *file, | |
| uint cmd, ulong arg) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| int error; | |
| int retval; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_ioctl")) | |
| return -ENODEV; | |
| if (tty->flags & (1 << TTY_IO_ERROR)) | |
| return -EIO; | |
| switch (cmd) { | |
| case TCSBRK: /* SVID version: non-zero arg --> no break */ | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "%s%d ioctl TCSBRK\n", CTC_TTY_NAME, info->line); | |
| #endif | |
| retval = tty_check_change(tty); | |
| if (retval) | |
| return retval; | |
| tty_wait_until_sent(tty, 0); | |
| return 0; | |
| case TCSBRKP: /* support for POSIX tcsendbreak() */ | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "%s%d ioctl TCSBRKP\n", CTC_TTY_NAME, info->line); | |
| #endif | |
| retval = tty_check_change(tty); | |
| if (retval) | |
| return retval; | |
| tty_wait_until_sent(tty, 0); | |
| return 0; | |
| case TIOCGSOFTCAR: | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "%s%d ioctl TIOCGSOFTCAR\n", CTC_TTY_NAME, | |
| info->line); | |
| #endif | |
| error = put_user(C_CLOCAL(tty) ? 1 : 0, (ulong __user *) arg); | |
| return error; | |
| case TIOCSSOFTCAR: | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "%s%d ioctl TIOCSSOFTCAR\n", CTC_TTY_NAME, | |
| info->line); | |
| #endif | |
| error = get_user(arg, (ulong __user *) arg); | |
| if (error) | |
| return error; | |
| tty->termios->c_cflag = | |
| ((tty->termios->c_cflag & ~CLOCAL) | | |
| (arg ? CLOCAL : 0)); | |
| return 0; | |
| case TIOCSERGETLSR: /* Get line status register */ | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "%s%d ioctl TIOCSERGETLSR\n", CTC_TTY_NAME, | |
| info->line); | |
| #endif | |
| error = verify_area(VERIFY_WRITE, (void __user *) arg, sizeof(uint)); | |
| if (error) | |
| return error; | |
| else | |
| return ctc_tty_get_lsr_info(info, (uint __user *) arg); | |
| default: | |
| #ifdef CTC_DEBUG_MODEM_IOCTL | |
| printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on %s%d\n", cmd, | |
| CTC_TTY_NAME, info->line); | |
| #endif | |
| return -ENOIOCTLCMD; | |
| } | |
| return 0; | |
| } | |
| static void | |
| ctc_tty_set_termios(struct tty_struct *tty, struct termios *old_termios) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| unsigned int cflag = tty->termios->c_cflag; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| ctc_tty_change_speed(info); | |
| /* Handle transition to B0 */ | |
| if ((old_termios->c_cflag & CBAUD) && !(cflag & CBAUD)) { | |
| info->mcr &= ~(UART_MCR_DTR|UART_MCR_RTS); | |
| ctc_tty_transmit_status(info); | |
| } | |
| /* Handle transition from B0 to other */ | |
| if (!(old_termios->c_cflag & CBAUD) && (cflag & CBAUD)) { | |
| info->mcr |= UART_MCR_DTR; | |
| if (!(tty->termios->c_cflag & CRTSCTS) || | |
| !test_bit(TTY_THROTTLED, &tty->flags)) { | |
| info->mcr |= UART_MCR_RTS; | |
| } | |
| ctc_tty_transmit_status(info); | |
| } | |
| /* Handle turning off CRTSCTS */ | |
| if ((old_termios->c_cflag & CRTSCTS) && | |
| !(tty->termios->c_cflag & CRTSCTS)) | |
| tty->hw_stopped = 0; | |
| } | |
| /* | |
| * ------------------------------------------------------------ | |
| * ctc_tty_open() and friends | |
| * ------------------------------------------------------------ | |
| */ | |
| static int | |
| ctc_tty_block_til_ready(struct tty_struct *tty, struct file *filp, ctc_tty_info *info) | |
| { | |
| DECLARE_WAITQUEUE(wait, NULL); | |
| int do_clocal = 0; | |
| unsigned long flags; | |
| int retval; | |
| DBF_TEXT(trace, 4, __FUNCTION__); | |
| /* | |
| * If the device is in the middle of being closed, then block | |
| * until it's done, and then try again. | |
| */ | |
| if (tty_hung_up_p(filp) || | |
| (info->flags & CTC_ASYNC_CLOSING)) { | |
| if (info->flags & CTC_ASYNC_CLOSING) | |
| wait_event(info->close_wait, | |
| !(info->flags & CTC_ASYNC_CLOSING)); | |
| #ifdef MODEM_DO_RESTART | |
| if (info->flags & CTC_ASYNC_HUP_NOTIFY) | |
| return -EAGAIN; | |
| else | |
| return -ERESTARTSYS; | |
| #else | |
| return -EAGAIN; | |
| #endif | |
| } | |
| /* | |
| * If non-blocking mode is set, then make the check up front | |
| * and then exit. | |
| */ | |
| if ((filp->f_flags & O_NONBLOCK) || | |
| (tty->flags & (1 << TTY_IO_ERROR))) { | |
| info->flags |= CTC_ASYNC_NORMAL_ACTIVE; | |
| return 0; | |
| } | |
| if (tty->termios->c_cflag & CLOCAL) | |
| do_clocal = 1; | |
| /* | |
| * Block waiting for the carrier detect and the line to become | |
| * free (i.e., not in use by the callout). While we are in | |
| * this loop, info->count is dropped by one, so that | |
| * ctc_tty_close() knows when to free things. We restore it upon | |
| * exit, either normal or abnormal. | |
| */ | |
| retval = 0; | |
| add_wait_queue(&info->open_wait, &wait); | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_block_til_ready before block: %s%d, count = %d\n", | |
| CTC_TTY_NAME, info->line, info->count); | |
| #endif | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| if (!(tty_hung_up_p(filp))) | |
| info->count--; | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| info->blocked_open++; | |
| while (1) { | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| if (tty_hung_up_p(filp) || | |
| !(info->flags & CTC_ASYNC_INITIALIZED)) { | |
| #ifdef MODEM_DO_RESTART | |
| if (info->flags & CTC_ASYNC_HUP_NOTIFY) | |
| retval = -EAGAIN; | |
| else | |
| retval = -ERESTARTSYS; | |
| #else | |
| retval = -EAGAIN; | |
| #endif | |
| break; | |
| } | |
| if (!(info->flags & CTC_ASYNC_CLOSING) && | |
| (do_clocal || (info->msr & UART_MSR_DCD))) { | |
| break; | |
| } | |
| if (signal_pending(current)) { | |
| retval = -ERESTARTSYS; | |
| break; | |
| } | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_block_til_ready blocking: %s%d, count = %d\n", | |
| CTC_TTY_NAME, info->line, info->count); | |
| #endif | |
| schedule(); | |
| } | |
| current->state = TASK_RUNNING; | |
| remove_wait_queue(&info->open_wait, &wait); | |
| if (!tty_hung_up_p(filp)) | |
| info->count++; | |
| info->blocked_open--; | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_block_til_ready after blocking: %s%d, count = %d\n", | |
| CTC_TTY_NAME, info->line, info->count); | |
| #endif | |
| if (retval) | |
| return retval; | |
| info->flags |= CTC_ASYNC_NORMAL_ACTIVE; | |
| return 0; | |
| } | |
| /* | |
| * This routine is called whenever a serial port is opened. It | |
| * enables interrupts for a serial port, linking in its async structure into | |
| * the IRQ chain. It also performs the serial-specific | |
| * initialization for the tty structure. | |
| */ | |
| static int | |
| ctc_tty_open(struct tty_struct *tty, struct file *filp) | |
| { | |
| ctc_tty_info *info; | |
| unsigned long saveflags; | |
| int retval, | |
| line; | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| line = tty->index; | |
| if (line < 0 || line > CTC_TTY_MAX_DEVICES) | |
| return -ENODEV; | |
| info = &driver->info[line]; | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_open")) | |
| return -ENODEV; | |
| if (!info->netdev) | |
| return -ENODEV; | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_open %s, count = %d\n", tty->name, | |
| info->count); | |
| #endif | |
| spin_lock_irqsave(&ctc_tty_lock, saveflags); | |
| info->count++; | |
| tty->driver_data = info; | |
| info->tty = tty; | |
| spin_unlock_irqrestore(&ctc_tty_lock, saveflags); | |
| /* | |
| * Start up serial port | |
| */ | |
| retval = ctc_tty_startup(info); | |
| if (retval) { | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_open return after startup\n"); | |
| #endif | |
| return retval; | |
| } | |
| retval = ctc_tty_block_til_ready(tty, filp, info); | |
| if (retval) { | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_open return after ctc_tty_block_til_ready \n"); | |
| #endif | |
| return retval; | |
| } | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_open %s successful...\n", tty->name); | |
| #endif | |
| return 0; | |
| } | |
| static void | |
| ctc_tty_close(struct tty_struct *tty, struct file *filp) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *) tty->driver_data; | |
| ulong flags; | |
| ulong timeout; | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| if (!info || ctc_tty_paranoia_check(info, tty->name, "ctc_tty_close")) | |
| return; | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| if (tty_hung_up_p(filp)) { | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_close return after tty_hung_up_p\n"); | |
| #endif | |
| return; | |
| } | |
| if ((tty->count == 1) && (info->count != 1)) { | |
| /* | |
| * Uh, oh. tty->count is 1, which means that the tty | |
| * structure will be freed. Info->count should always | |
| * be one in these conditions. If it's greater than | |
| * one, we've got real problems, since it means the | |
| * serial port won't be shutdown. | |
| */ | |
| printk(KERN_ERR "ctc_tty_close: bad port count; tty->count is 1, " | |
| "info->count is %d\n", info->count); | |
| info->count = 1; | |
| } | |
| if (--info->count < 0) { | |
| printk(KERN_ERR "ctc_tty_close: bad port count for %s%d: %d\n", | |
| CTC_TTY_NAME, info->line, info->count); | |
| info->count = 0; | |
| } | |
| if (info->count) { | |
| local_irq_restore(flags); | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_close after info->count != 0\n"); | |
| #endif | |
| return; | |
| } | |
| info->flags |= CTC_ASYNC_CLOSING; | |
| tty->closing = 1; | |
| /* | |
| * At this point we stop accepting input. To do this, we | |
| * disable the receive line status interrupts, and tell the | |
| * interrupt driver to stop checking the data ready bit in the | |
| * line status register. | |
| */ | |
| if (info->flags & CTC_ASYNC_INITIALIZED) { | |
| tty_wait_until_sent(tty, 30*HZ); /* 30 seconds timeout */ | |
| /* | |
| * Before we drop DTR, make sure the UART transmitter | |
| * has completely drained; this is especially | |
| * important if there is a transmit FIFO! | |
| */ | |
| timeout = jiffies + HZ; | |
| while (!(info->lsr & UART_LSR_TEMT)) { | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| msleep(500); | |
| spin_lock_irqsave(&ctc_tty_lock, flags); | |
| if (time_after(jiffies,timeout)) | |
| break; | |
| } | |
| } | |
| ctc_tty_shutdown(info); | |
| if (tty->driver->flush_buffer) { | |
| skb_queue_purge(&info->tx_queue); | |
| info->lsr |= UART_LSR_TEMT; | |
| } | |
| tty_ldisc_flush(tty); | |
| info->tty = 0; | |
| tty->closing = 0; | |
| if (info->blocked_open) { | |
| set_current_state(TASK_INTERRUPTIBLE); | |
| schedule_timeout(HZ/2); | |
| wake_up_interruptible(&info->open_wait); | |
| } | |
| info->flags &= ~(CTC_ASYNC_NORMAL_ACTIVE | CTC_ASYNC_CLOSING); | |
| wake_up_interruptible(&info->close_wait); | |
| spin_unlock_irqrestore(&ctc_tty_lock, flags); | |
| #ifdef CTC_DEBUG_MODEM_OPEN | |
| printk(KERN_DEBUG "ctc_tty_close normal exit\n"); | |
| #endif | |
| } | |
| /* | |
| * ctc_tty_hangup() --- called by tty_hangup() when a hangup is signaled. | |
| */ | |
| static void | |
| ctc_tty_hangup(struct tty_struct *tty) | |
| { | |
| ctc_tty_info *info = (ctc_tty_info *)tty->driver_data; | |
| unsigned long saveflags; | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| if (ctc_tty_paranoia_check(info, tty->name, "ctc_tty_hangup")) | |
| return; | |
| ctc_tty_shutdown(info); | |
| info->count = 0; | |
| info->flags &= ~CTC_ASYNC_NORMAL_ACTIVE; | |
| spin_lock_irqsave(&ctc_tty_lock, saveflags); | |
| info->tty = 0; | |
| spin_unlock_irqrestore(&ctc_tty_lock, saveflags); | |
| wake_up_interruptible(&info->open_wait); | |
| } | |
| /* | |
| * For all online tty's, try sending data to | |
| * the lower levels. | |
| */ | |
| static void | |
| ctc_tty_task(unsigned long arg) | |
| { | |
| ctc_tty_info *info = (void *)arg; | |
| unsigned long saveflags; | |
| int again; | |
| DBF_TEXT(trace, 3, __FUNCTION__); | |
| spin_lock_irqsave(&ctc_tty_lock, saveflags); | |
| if ((!ctc_tty_shuttingdown) && info) { | |
| again = ctc_tty_tint(info); | |
| if (!again) | |
| info->lsr |= UART_LSR_TEMT; | |
| again |= ctc_tty_readmodem(info); | |
| if (again) { | |
| tasklet_schedule(&info->tasklet); | |
| } | |
| } | |
| spin_unlock_irqrestore(&ctc_tty_lock, saveflags); | |
| } | |
| static struct tty_operations ctc_ops = { | |
| .open = ctc_tty_open, | |
| .close = ctc_tty_close, | |
| .write = ctc_tty_write, | |
| .flush_chars = ctc_tty_flush_chars, | |
| .write_room = ctc_tty_write_room, | |
| .chars_in_buffer = ctc_tty_chars_in_buffer, | |
| .flush_buffer = ctc_tty_flush_buffer, | |
| .ioctl = ctc_tty_ioctl, | |
| .throttle = ctc_tty_throttle, | |
| .unthrottle = ctc_tty_unthrottle, | |
| .set_termios = ctc_tty_set_termios, | |
| .hangup = ctc_tty_hangup, | |
| .tiocmget = ctc_tty_tiocmget, | |
| .tiocmset = ctc_tty_tiocmset, | |
| }; | |
| int | |
| ctc_tty_init(void) | |
| { | |
| int i; | |
| ctc_tty_info *info; | |
| struct tty_driver *device; | |
| DBF_TEXT(trace, 2, __FUNCTION__); | |
| driver = kmalloc(sizeof(ctc_tty_driver), GFP_KERNEL); | |
| if (driver == NULL) { | |
| printk(KERN_WARNING "Out of memory in ctc_tty_modem_init\n"); | |
| return -ENOMEM; | |
| } | |
| memset(driver, 0, sizeof(ctc_tty_driver)); | |
| device = alloc_tty_driver(CTC_TTY_MAX_DEVICES); | |
| if (!device) { | |
| kfree(driver); | |
| printk(KERN_WARNING "Out of memory in ctc_tty_modem_init\n"); | |
| return -ENOMEM; | |
| } | |
| device->devfs_name = "ctc/" CTC_TTY_NAME; | |
| device->name = CTC_TTY_NAME; | |
| device->major = CTC_TTY_MAJOR; | |
| device->minor_start = 0; | |
| device->type = TTY_DRIVER_TYPE_SERIAL; | |
| device->subtype = SERIAL_TYPE_NORMAL; | |
| device->init_termios = tty_std_termios; | |
| device->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; | |
| device->flags = TTY_DRIVER_REAL_RAW; | |
| device->driver_name = "ctc_tty", | |
| tty_set_operations(device, &ctc_ops); | |
| if (tty_register_driver(device)) { | |
| printk(KERN_WARNING "ctc_tty: Couldn't register serial-device\n"); | |
| put_tty_driver(device); | |
| kfree(driver); | |
| return -1; | |
| } | |
| driver->ctc_tty_device = device; | |
| for (i = 0; i < CTC_TTY_MAX_DEVICES; i++) { | |
| info = &driver->info[i]; | |
| init_MUTEX(&info->write_sem); | |
| tasklet_init(&info->tasklet, ctc_tty_task, | |
| (unsigned long) info); | |
| info->magic = CTC_ASYNC_MAGIC; | |
| info->line = i; | |
| info->tty = 0; | |
| info->count = 0; | |
| info->blocked_open = 0; | |
| init_waitqueue_head(&info->open_wait); | |
| init_waitqueue_head(&info->close_wait); | |
| skb_queue_head_init(&info->tx_queue); | |
| skb_queue_head_init(&info->rx_queue); | |
| init_timer(&info->stoptimer); | |
| info->stoptimer.function = ctc_tty_stopdev; | |
| info->stoptimer.data = (unsigned long)info; | |
| info->mcr = UART_MCR_RTS; | |
| } | |
| return 0; | |
| } | |
| int | |
| ctc_tty_register_netdev(struct net_device *dev) { | |
| int ttynum; | |
| char *err; | |
| char *p; | |
| DBF_TEXT(trace, 2, __FUNCTION__); | |
| if ((!dev) || (!dev->name)) { | |
| printk(KERN_WARNING | |
| "ctc_tty_register_netdev called " | |
| "with NULL dev or NULL dev-name\n"); | |
| return -1; | |
| } | |
| /* | |
| * If the name is a format string the caller wants us to | |
| * do a name allocation : format string must end with %d | |
| */ | |
| if (strchr(dev->name, '%')) | |
| { | |
| int err = dev_alloc_name(dev, dev->name); // dev->name is changed by this | |
| if (err < 0) { | |
| printk(KERN_DEBUG "dev_alloc returned error %d\n", err); | |
| return err; | |
| } | |
| } | |
| for (p = dev->name; p && ((*p < '0') || (*p > '9')); p++); | |
| ttynum = simple_strtoul(p, &err, 0); | |
| if ((ttynum < 0) || (ttynum >= CTC_TTY_MAX_DEVICES) || | |
| (err && *err)) { | |
| printk(KERN_WARNING | |
| "ctc_tty_register_netdev called " | |
| "with number in name '%s'\n", dev->name); | |
| return -1; | |
| } | |
| if (driver->info[ttynum].netdev) { | |
| printk(KERN_WARNING | |
| "ctc_tty_register_netdev called " | |
| "for already registered device '%s'\n", | |
| dev->name); | |
| return -1; | |
| } | |
| driver->info[ttynum].netdev = dev; | |
| return 0; | |
| } | |
| void | |
| ctc_tty_unregister_netdev(struct net_device *dev) { | |
| int i; | |
| unsigned long saveflags; | |
| ctc_tty_info *info = NULL; | |
| DBF_TEXT(trace, 2, __FUNCTION__); | |
| spin_lock_irqsave(&ctc_tty_lock, saveflags); | |
| for (i = 0; i < CTC_TTY_MAX_DEVICES; i++) | |
| if (driver->info[i].netdev == dev) { | |
| info = &driver->info[i]; | |
| break; | |
| } | |
| if (info) { | |
| info->netdev = NULL; | |
| skb_queue_purge(&info->tx_queue); | |
| skb_queue_purge(&info->rx_queue); | |
| } | |
| spin_unlock_irqrestore(&ctc_tty_lock, saveflags); | |
| } | |
| void | |
| ctc_tty_cleanup(void) { | |
| unsigned long saveflags; | |
| DBF_TEXT(trace, 2, __FUNCTION__); | |
| spin_lock_irqsave(&ctc_tty_lock, saveflags); | |
| ctc_tty_shuttingdown = 1; | |
| spin_unlock_irqrestore(&ctc_tty_lock, saveflags); | |
| tty_unregister_driver(driver->ctc_tty_device); | |
| put_tty_driver(driver->ctc_tty_device); | |
| kfree(driver); | |
| driver = NULL; | |
| } |