patch-2.0.31 linux/drivers/isdn/isdn_tty.c

Next file: linux/drivers/isdn/isdn_tty.h
Previous file: linux/drivers/isdn/isdn_syms.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.0.30/linux/drivers/isdn/isdn_tty.c linux/drivers/isdn/isdn_tty.c
@@ -1,10 +1,10 @@
-/* $Id: isdn_tty.c,v 1.23 1996/10/22 23:14:02 fritz Exp $
- *
+/* $Id: isdn_tty.c,v 1.41 1997/05/27 15:17:31 fritz Exp $
+
  * Linux ISDN subsystem, tty functions and AT-command emulator (linklevel).
  *
  * Copyright 1994,95,96 by Fritz Elfert (fritz@wuemaus.franken.de)
  * Copyright 1995,96    by Thinking Objects Software GmbH Wuerzburg
- * 
+ *
  * 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)
@@ -17,9 +17,80 @@
  *
  * 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. 
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  *
  * $Log: isdn_tty.c,v $
+ * Revision 1.41  1997/05/27 15:17:31  fritz
+ * Added changes for recent 2.1.x kernels:
+ *   changed return type of isdn_close
+ *   queue_task_* -> queue_task
+ *   clear/set_bit -> test_and_... where apropriate.
+ *   changed type of hard_header_cache parameter.
+ *
+ * Revision 1.40  1997/03/24 22:55:27  fritz
+ * Added debug code for status callbacks.
+ *
+ * Revision 1.39  1997/03/21 18:25:56  fritz
+ * Corrected CTS handling.
+ *
+ * Revision 1.38  1997/03/07 12:13:35  fritz
+ * Bugfix: Send audio in adpcm format was broken.
+ * Bugfix: CTS handling was wrong.
+ *
+ * Revision 1.37  1997/03/07 01:37:34  fritz
+ * Bugfix: Did not compile with CONFIG_ISDN_AUDIO disabled.
+ * Bugfix: isdn_tty_tint() did not handle lowlevel errors correctly.
+ * Bugfix: conversion was wrong when sending ulaw audio.
+ * Added proper ifdef's for CONFIG_ISDN_AUDIO
+ *
+ * Revision 1.36  1997/03/04 21:41:55  fritz
+ * Fix: Excessive stack usage of isdn_tty_senddown()
+ *      and isdn_tty_end_vrx() could lead to problems.
+ *
+ * Revision 1.35  1997/03/02 19:05:52  fritz
+ * Bugfix: Avoid recursion.
+ *
+ * Revision 1.34  1997/03/02 14:29:22  fritz
+ * More ttyI related cleanup.
+ *
+ * Revision 1.33  1997/02/28 02:32:45  fritz
+ * Cleanup: Moved some tty related stuff from isdn_common.c
+ *          to isdn_tty.c
+ * Bugfix:  Bisync protocol did not behave like documented.
+ *
+ * Revision 1.32  1997/02/23 15:43:03  fritz
+ * Small change in handling of incoming calls
+ * documented in newest version of ttyI.4
+ *
+ * Revision 1.31  1997/02/21 13:05:57  fritz
+ * Bugfix: Remote hangup did not set location-info on ttyI's
+ *
+ * Revision 1.30  1997/02/18 09:41:05  fritz
+ * Added support for bitwise access to modem registers (ATSx.y=n, ATSx.y?).
+ * Beautified output of AT&V.
+ *
+ * Revision 1.29  1997/02/16 12:11:51  fritz
+ * Added S13,Bit4 option.
+ *
+ * Revision 1.28  1997/02/10 22:07:08  fritz
+ * Added 2 modem registers for numbering plan and screening info.
+ *
+ * Revision 1.27  1997/02/10 21:31:14  fritz
+ * Changed setup-interface (incoming and outgoing).
+ *
+ * Revision 1.26  1997/02/10 20:12:48  fritz
+ * Changed interface for reporting incoming calls.
+ *
+ * Revision 1.25  1997/02/03 23:04:30  fritz
+ * Reformatted according CodingStyle.
+ * skb->free stuff replaced by macro.
+ * Finished full-duplex audio.
+ *
+ * Revision 1.24  1997/01/14 01:32:42  fritz
+ * Changed audio receive not to rely on skb->users and skb->lock.
+ * Added ATI2 and related variables.
+ * Started adding full-duplex audio capability.
+ *
  * Revision 1.23  1996/10/22 23:14:02  fritz
  * Changes for compatibility to 2.0.X and 2.1.X kernels.
  *
@@ -114,6 +185,7 @@
  * Initial revision
  *
  */
+#undef ISDN_TTY_STAT_DEBUG
 
 #define __NO_VERSION__
 #include <linux/config.h>
@@ -129,29 +201,35 @@
 
 /* Prototypes */
 
-static int  isdn_tty_edit_at(const char *, int, modem_info *, int);
+static int isdn_tty_edit_at(const char *, int, modem_info *, int);
 static void isdn_tty_check_esc(const u_char *, u_char, int, int *, int *, int);
 static void isdn_tty_modem_reset_regs(modem_info *, int);
 static void isdn_tty_cmd_ATA(modem_info *);
 static void isdn_tty_at_cout(char *, modem_info *);
 static void isdn_tty_flush_buffer(struct tty_struct *);
+static void isdn_tty_modem_result(int, modem_info *);
+#ifdef CONFIG_ISDN_AUDIO
+static int isdn_tty_countDLE(unsigned char *, int);
+#endif
 
 /* Leave this unchanged unless you know what you do! */
 #define MODEM_PARANOIA_CHECK
 #define MODEM_DO_RESTART
 
 static char *isdn_ttyname_ttyI = "ttyI";
-static char *isdn_ttyname_cui  = "cui";
-static int bit2si[8] = {1,5,7,7,7,7,7,7};
-static int si2bit[8] = {4,1,4,4,4,4,4,4};
-                                
-char *isdn_tty_revision        = "$Revision: 1.23 $";
+static char *isdn_ttyname_cui = "cui";
+static int bit2si[8] =
+{1, 5, 7, 7, 7, 7, 7, 7};
+static int si2bit[8] =
+{4, 1, 4, 4, 4, 4, 4, 4};
+
+char *isdn_tty_revision = "$Revision: 1.41 $";
 
 #define DLE 0x10
 #define ETX 0x03
 #define DC4 0x14
 
-/* isdn_tty_try_read() is called from within isdn_receive_callback()
+/* isdn_tty_try_read() is called from within isdn_tty_rcv_skb()
  * to stuff incoming data directly into a tty's flip-buffer. This
  * is done to speed up tty-receiving if the receive-queue is empty.
  * This routine MUST be called with interrupts off.
@@ -160,37 +238,45 @@
  *  0 = Failure, data has to be buffered and later processed by
  *      isdn_tty_readmodem().
  */
-int isdn_tty_try_read(modem_info *info, struct sk_buff *skb)
+static int
+isdn_tty_try_read(modem_info * info, struct sk_buff *skb)
 {
-        int c;
-        int len;
-        struct tty_struct *tty;
+	int c;
+	int len;
+	struct tty_struct *tty;
 
 	if (info->online) {
 		if ((tty = info->tty)) {
 			if (info->mcr & UART_MCR_RTS) {
 				c = TTY_FLIPBUF_SIZE - tty->flip.count;
-                                len = skb->len + skb->users;
+				len = skb->len
+#ifdef CONFIG_ISDN_AUDIO
+					+ ISDN_AUDIO_SKB_DLECOUNT(skb)
+#endif
+					;
 				if (c >= len) {
-                                        if (skb->users)
-                                                while (skb->len--) {
-                                                        if (*skb->data == DLE)
-                                                                tty_insert_flip_char(tty, DLE, 0);
-                                                        tty_insert_flip_char(tty, *skb->data++, 0);
-                                                }
-                                        else {
-                                                memcpy(tty->flip.char_buf_ptr,
-                                                       skb->data, len);
-                                                tty->flip.count += len;
-                                                tty->flip.char_buf_ptr += len;
-                                                memset(tty->flip.flag_buf_ptr, 0, len);
-                                                tty->flip.flag_buf_ptr += len;
-                                        }
-                                        if (info->emu.mdmreg[12] & 128)
-                                                tty->flip.flag_buf_ptr[len - 1] = 0xff;
-					queue_task_irq_off(&tty->flip.tqueue, &tq_timer);
-                                        skb->free = 1;
-                                        kfree_skb(skb, FREE_READ);
+#ifdef CONFIG_ISDN_AUDIO
+					if (ISDN_AUDIO_SKB_DLECOUNT(skb))
+						while (skb->len--) {
+							if (*skb->data == DLE)
+								tty_insert_flip_char(tty, DLE, 0);
+							tty_insert_flip_char(tty, *skb->data++, 0);
+					} else {
+#endif
+						memcpy(tty->flip.char_buf_ptr,
+						       skb->data, len);
+						tty->flip.count += len;
+						tty->flip.char_buf_ptr += len;
+						memset(tty->flip.flag_buf_ptr, 0, len);
+						tty->flip.flag_buf_ptr += len;
+#ifdef CONFIG_ISDN_AUDIO
+					}
+#endif
+					if (info->emu.mdmreg[12] & 128)
+						tty->flip.flag_buf_ptr[len - 1] = 0xff;
+					queue_task(&tty->flip.tqueue, &tq_timer);
+					SET_SKB_FREE(skb);
+					kfree_skb(skb, FREE_READ);
 					return 1;
 				}
 			}
@@ -203,7 +289,8 @@
  * It tries getting received data from the receive queue an stuff it into
  * the tty's flip-buffer.
  */
-void isdn_tty_readmodem(void)
+void
+isdn_tty_readmodem(void)
 {
 	int resched = 0;
 	int midx;
@@ -216,7 +303,7 @@
 
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
 		if ((midx = dev->m_idx[i]) >= 0) {
-                        info = &dev->mdm.info[midx];
+			info = &dev->mdm.info[midx];
 			if (info->online) {
 				r = 0;
 #ifdef CONFIG_ISDN_AUDIO
@@ -226,20 +313,20 @@
 					if (info->mcr & UART_MCR_RTS) {
 						c = TTY_FLIPBUF_SIZE - tty->flip.count;
 						if (c > 0) {
-                                                        save_flags(flags);
-                                                        cli();
+							save_flags(flags);
+							cli();
 							r = isdn_readbchan(info->isdn_driver, info->isdn_channel,
-								      tty->flip.char_buf_ptr,
-								      tty->flip.flag_buf_ptr, c, 0);
-                                                        /* CISCO AsyncPPP Hack */
+									   tty->flip.char_buf_ptr,
+									   tty->flip.flag_buf_ptr, c, 0);
+							/* CISCO AsyncPPP Hack */
 							if (!(info->emu.mdmreg[12] & 128))
 								memset(tty->flip.flag_buf_ptr, 0, r);
 							tty->flip.count += r;
 							tty->flip.flag_buf_ptr += r;
 							tty->flip.char_buf_ptr += r;
 							if (r)
-								queue_task_irq_off(&tty->flip.tqueue, &tq_timer);
-                                                        restore_flags(flags);
+								queue_task(&tty->flip.tqueue, &tq_timer);
+							restore_flags(flags);
 						}
 					} else
 						r = 1;
@@ -251,257 +338,383 @@
 				} else
 					info->rcvsched = 1;
 			}
-                }
+		}
 	}
 	if (!resched)
 		isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 0);
 }
 
-void isdn_tty_cleanup_xmit(modem_info *info)
+int
+isdn_tty_rcv_skb(int i, int di, int channel, struct sk_buff *skb)
 {
-        struct sk_buff *skb;
-        unsigned long flags;
+	ulong flags;
+	int midx;
+#ifdef CONFIG_ISDN_AUDIO
+	int ifmt;
+#endif
+	modem_info *info;
 
-        save_flags(flags);
-        cli();
-        if (skb_queue_len(&info->xmit_queue))
-                while ((skb = skb_dequeue(&info->xmit_queue))) {
-                        skb->free = 1;
-                        kfree_skb(skb, FREE_WRITE);
-                }
-        if (skb_queue_len(&info->dtmf_queue))
-                while ((skb = skb_dequeue(&info->dtmf_queue))) {
-                        skb->free = 1;
-                        kfree_skb(skb, FREE_WRITE);
-                }
-        restore_flags(flags);
-}
-
-static void isdn_tty_tint(modem_info *info)
-{
-        struct sk_buff *skb = skb_dequeue(&info->xmit_queue);
-        int len, slen;
-
-        if (!skb)
-                return;
-        len = skb->len;
-        if ((slen = isdn_writebuf_skb_stub(info->isdn_driver,
-                                         info->isdn_channel, skb)) == len) {
-                struct tty_struct *tty = info->tty;
-                info->send_outstanding++;
-                info->msr |= UART_MSR_CTS;
-                info->lsr |= UART_LSR_TEMT;
-                if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-                    tty->ldisc.write_wakeup)
-                        (tty->ldisc.write_wakeup) (tty);
-                wake_up_interruptible(&tty->write_wait);
-                return;
-        }
-        if (slen > 0)
-                skb_pull(skb,slen);
-        skb_queue_head(&info->xmit_queue, skb);
+	if ((midx = dev->m_idx[i]) < 0) {
+		/* if midx is invalid, packet is not for tty */
+		return 0;
+	}
+	info = &dev->mdm.info[midx];
+#ifdef CONFIG_ISDN_AUDIO
+	ifmt = 1;
+	
+	if (info->vonline)
+		isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt);
+#endif
+	if ((info->online < 2)
+#ifdef CONFIG_ISDN_AUDIO
+	    && (!(info->vonline & 1))
+#endif
+		) {
+		/* If Modem not listening, drop data */
+		SET_SKB_FREE(skb);
+		kfree_skb(skb, FREE_READ);
+		return 1;
+	}
+	if (info->emu.mdmreg[13] & 2)
+		/* T.70 decoding: Simply throw away the T.70 header (4 bytes) */
+		if ((skb->data[0] == 1) && ((skb->data[1] == 0) || (skb->data[1] == 1)))
+			skb_pull(skb, 4);
+#ifdef CONFIG_ISDN_AUDIO
+	if (skb_headroom(skb) < sizeof(isdn_audio_skb)) {
+		printk(KERN_WARNING
+		       "isdn_audio: insufficient skb_headroom, dropping\n");
+		SET_SKB_FREE(skb);
+		kfree_skb(skb, FREE_READ);
+		return 1;
+	}
+	ISDN_AUDIO_SKB_DLECOUNT(skb) = 0;
+	ISDN_AUDIO_SKB_LOCK(skb) = 0;
+	if (info->vonline & 1) {
+		/* voice conversion/compression */
+		switch (info->emu.vpar[3]) {
+			case 2:
+			case 3:
+			case 4:
+				/* adpcm
+				 * Since compressed data takes less
+				 * space, we can overwrite the buffer.
+				 */
+				skb_trim(skb, isdn_audio_xlaw2adpcm(info->adpcmr,
+								    ifmt,
+								    skb->data,
+								    skb->data,
+								    skb->len));
+				break;
+			case 5:
+				/* a-law */
+				if (!ifmt)
+					isdn_audio_ulaw2alaw(skb->data, skb->len);
+				break;
+			case 6:
+				/* u-law */
+				if (ifmt)
+					isdn_audio_alaw2ulaw(skb->data, skb->len);
+				break;
+		}
+		ISDN_AUDIO_SKB_DLECOUNT(skb) =
+			isdn_tty_countDLE(skb->data, skb->len);
+	}
+#endif
+	/* Try to deliver directly via tty-flip-buf if queue is empty */
+	save_flags(flags);
+	cli();
+	if (skb_queue_empty(&dev->drv[di]->rpqueue[channel]))
+		if (isdn_tty_try_read(info, skb)) {
+			restore_flags(flags);
+			return 1;
+		}
+	/* Direct deliver failed or queue wasn't empty.
+	 * Queue up for later dequeueing via timer-irq.
+	 */
+	__skb_queue_tail(&dev->drv[di]->rpqueue[channel], skb);
+	dev->drv[di]->rcvcount[channel] +=
+		(skb->len
+#ifdef CONFIG_ISDN_AUDIO
+		 + ISDN_AUDIO_SKB_DLECOUNT(skb)
+#endif
+			);
+	restore_flags(flags);
+	/* Schedule dequeuing */
+	if ((dev->modempoll) && (info->rcvsched))
+		isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
+	return 1;
 }
 
+void
+isdn_tty_cleanup_xmit(modem_info * info)
+{
+	struct sk_buff *skb;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+	if (skb_queue_len(&info->xmit_queue))
+		while ((skb = skb_dequeue(&info->xmit_queue))) {
+			SET_SKB_FREE(skb);
+			kfree_skb(skb, FREE_WRITE);
+		}
 #ifdef CONFIG_ISDN_AUDIO
-int isdn_tty_countDLE(unsigned char *buf, int len)
-{
-        int count = 0;
-
-        while (len--)
-                if (*buf++ == DLE)
-                        count++;
-        return count;
+	if (skb_queue_len(&info->dtmf_queue))
+		while ((skb = skb_dequeue(&info->dtmf_queue))) {
+			SET_SKB_FREE(skb);
+			kfree_skb(skb, FREE_WRITE);
+		}
+#endif
+	restore_flags(flags);
+}
+
+static void
+isdn_tty_tint(modem_info * info)
+{
+	struct sk_buff *skb = skb_dequeue(&info->xmit_queue);
+	int len,
+	 slen;
+
+	if (!skb)
+		return;
+	len = skb->len;
+	if ((slen = isdn_writebuf_skb_stub(info->isdn_driver,
+					   info->isdn_channel, skb)) == len) {
+		struct tty_struct *tty = info->tty;
+		info->send_outstanding++;
+		info->msr |= UART_MSR_CTS;
+		info->lsr |= UART_LSR_TEMT;
+		if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+		    tty->ldisc.write_wakeup)
+			(tty->ldisc.write_wakeup) (tty);
+		wake_up_interruptible(&tty->write_wait);
+		return;
+	}
+	if (slen < 0) {
+		/* Error: no channel, already shutdown, or wrong parameter */
+		SET_SKB_FREE(skb);
+		dev_kfree_skb(skb, FREE_WRITE);
+		return;
+	}
+	if (slen)
+		skb_pull(skb, slen);
+	skb_queue_head(&info->xmit_queue, skb);
+}
+
+#ifdef CONFIG_ISDN_AUDIO
+static int
+isdn_tty_countDLE(unsigned char *buf, int len)
+{
+	int count = 0;
+
+	while (len--)
+		if (*buf++ == DLE)
+			count++;
+	return count;
 }
 
 /* This routine is called from within isdn_tty_write() to perform
  * DLE-decoding when sending audio-data.
  */
-static int isdn_tty_handleDLEdown(modem_info *info, atemu *m, int len)
+static int
+isdn_tty_handleDLEdown(modem_info * info, atemu * m, int len)
 {
-        unsigned char *p = &info->xmit_buf[info->xmit_count];
-        int count = 0;
+	unsigned char *p = &info->xmit_buf[info->xmit_count];
+	int count = 0;
 
-        while (len>0) {
-                if (m->lastDLE) {
-                        m->lastDLE = 0;
-                        switch (*p) {
-                                case DLE:
-                                        /* Escape code */
-                                        if (len>1)
-                                                memmove(p,p+1,len-1);
-                                        p--;
-                                        count++;
-                                        break;
-                                case ETX:
-                                        /* End of data */
-                                        info->vonline |= 4;
-                                        return count;
-                                case DC4:
-                                        /* Abort RX */
-                                        info->vonline &= ~1;
-                                        isdn_tty_at_cout("\020\003", info);
-                                        if (!info->vonline)
-                                                isdn_tty_at_cout("\r\nVCON\r\n", info);
-                                        /* Fall through */
-                                case 'q':
-                                case 's':
-                                        /* Silence */
-                                        if (len>1)
-                                                memmove(p,p+1,len-1);
-                                        p--;
-                                        break;
-                        }
-                } else {
-                        if (*p == DLE)
-                                m->lastDLE = 1;
-                        else
-                                count++;
-                }
-                p++;
-                len--;
-        }
-        if (len<0) {
-                printk(KERN_WARNING "isdn_tty: len<0 in DLEdown\n");
-                return 0;
-        }
-        return count;
+	while (len > 0) {
+		if (m->lastDLE) {
+			m->lastDLE = 0;
+			switch (*p) {
+				case DLE:
+					/* Escape code */
+					if (len > 1)
+						memmove(p, p + 1, len - 1);
+					p--;
+					count++;
+					break;
+				case ETX:
+					/* End of data */
+					info->vonline |= 4;
+					return count;
+				case DC4:
+					/* Abort RX */
+					info->vonline &= ~1;
+#ifdef ISDN_DEBUG_MODEM_VOICE
+					printk(KERN_DEBUG
+					       "DLEdown: got DLE-DC4, send DLE-ETX on ttyI%d\n",
+					       info->line);
+#endif
+					isdn_tty_at_cout("\020\003", info);
+					if (!info->vonline) {
+#ifdef ISDN_DEBUG_MODEM_VOICE
+						printk(KERN_DEBUG
+						       "DLEdown: send VCON on ttyI%d\n",
+						       info->line);
+#endif
+						isdn_tty_at_cout("\r\nVCON\r\n", info);
+					}
+					/* Fall through */
+				case 'q':
+				case 's':
+					/* Silence */
+					if (len > 1)
+						memmove(p, p + 1, len - 1);
+					p--;
+					break;
+			}
+		} else {
+			if (*p == DLE)
+				m->lastDLE = 1;
+			else
+				count++;
+		}
+		p++;
+		len--;
+	}
+	if (len < 0) {
+		printk(KERN_WARNING "isdn_tty: len<0 in DLEdown\n");
+		return 0;
+	}
+	return count;
 }
 
 /* This routine is called from within isdn_tty_write() when receiving
  * audio-data. It interrupts receiving, if an character other than
  * ^S or ^Q is sent.
  */
-static int isdn_tty_end_vrx(const char *buf, int c, int from_user)
+static int
+isdn_tty_end_vrx(const char *buf, int c, int from_user)
 {
-	char tmpbuf[VBUF];
-        char *p;
+	char ch;
 
-        if (c > VBUF) {
-                printk(KERN_ERR "isdn_tty: (end_vrx) BUFFER OVERFLOW!!!\n");
-                return 1;
-        }
-	if (from_user) {
-		copy_from_user(tmpbuf, buf, c);
-                p = tmpbuf;
-        } else
-                p = (char *)buf;
-        while (c--) {
-                if ((*p != 0x11) && (*p != 0x13))
-                        return 1;
-                p++;
-        }
-        return 0;
+	while (c--) {
+		if (from_user)
+			GET_USER(ch, buf);
+		else
+			ch = *buf;
+		if ((ch != 0x11) && (ch != 0x13))
+			return 1;
+		buf++;
+	}
+	return 0;
 }
 
-static int voice_cf[7] = { 1, 1, 4, 3, 2, 1, 1 };
+static int voice_cf[7] =
+{0, 0, 4, 3, 2, 0, 0};
 
-#endif        /* CONFIG_ISDN_AUDIO */
+#endif                          /* CONFIG_ISDN_AUDIO */
 
 /* isdn_tty_senddown() is called either directly from within isdn_tty_write()
  * or via timer-interrupt from within isdn_tty_modem_xmit(). It pulls
  * outgoing data from the tty's xmit-buffer, handles voice-decompression or
  * T.70 if necessary, and finally queues it up for sending via isdn_tty_tint.
  */
-static void isdn_tty_senddown(modem_info * info)
+static void
+isdn_tty_senddown(modem_info * info)
 {
-        unsigned char *buf = info->xmit_buf;
-        int buflen;
-        int skb_res;
-        struct sk_buff *skb;
-        unsigned long flags;
-
-        save_flags(flags);
-        cli();
-        if (!(buflen = info->xmit_count)) {
-                restore_flags(flags);
-                return;
-        }
-        if (info->isdn_driver < 0) {
-                info->xmit_count = 0;
-                restore_flags(flags);
-                return;
-        }
-        skb_res = dev->drv[info->isdn_driver]->interface->hl_hdrlen + 4;
-#ifdef CONFIG_ISDN_AUDIO
-        if (info->vonline & 2) {
-                /* For now, ifmt is fixed to 1 (alaw), since this
-                 * is used with ISDN everywhere in the world, except
-                 * US, Canada and Japan.
-                 * Later, when US-ISDN protocols are implemented,
-                 * this setting will depend on the D-channel protocol.
-                 */
-                int ifmt = 1;
-                int skb_len;
-                unsigned char hbuf[VBUF];
-
-                memcpy(hbuf,info->xmit_buf,buflen);
-                info->xmit_count = 0;
-                restore_flags(flags);
-                /* voice conversion/decompression */
-                skb_len = buflen * voice_cf[info->emu.vpar[3]];
-                skb = dev_alloc_skb(skb_len + skb_res);
-                if (!skb) {
-                        printk(KERN_WARNING
-                               "isdn_tty: Out of memory in ttyI%d senddown\n", info->line);
-                        return;
-                }
-                skb_reserve(skb, skb_res);
-                switch (info->emu.vpar[3]) {
-                        case 2:
-                        case 3:
-                        case 4:
-                                /* adpcm, compatible to ZyXel 1496 modem
-                                 * with ROM revision 6.01
-                                 */
-                                buflen = isdn_audio_adpcm2xlaw(info->adpcms,
-                                                               ifmt,
-                                                               hbuf,
-                                                               skb_put(skb,skb_len),
-                                                               buflen);
-                                skb_trim(skb, buflen);
-                                break;
-                        case 5:
-                                /* a-law */
-                                if (!ifmt)
-                                        isdn_audio_alaw2ulaw(hbuf,buflen);
-                                memcpy(skb_put(skb,buflen),hbuf,buflen);
-                                break;
-                        case 6:
-                                /* u-law */
-                                if (ifmt)
-                                        isdn_audio_ulaw2alaw(hbuf,buflen);
-                                memcpy(skb_put(skb,buflen),hbuf,buflen);
-                                break;
-                }
-                if (info->vonline & 4) {
-                        info->vonline &= ~6;
-                        if (!info->vonline)
-                                isdn_tty_at_cout("\r\nVCON\r\n",info);
-                }
-        } else {
-#endif        /* CONFIG_ISDN_AUDIO */
-                skb = dev_alloc_skb(buflen + skb_res);
-                if (!skb) {
-                        printk(KERN_WARNING
-                               "isdn_tty: Out of memory in ttyI%d senddown\n", info->line);
-                        restore_flags(flags);
-                        return;
-                }
-                skb_reserve(skb, skb_res);
-                memcpy(skb_put(skb,buflen),buf,buflen);
-                info->xmit_count = 0;
-                restore_flags(flags);
-#ifdef CONFIG_ISDN_AUDIO
-        }
-#endif
-        skb->free = 1;
-        if (info->emu.mdmreg[13] & 2)
-                /* Add T.70 simplified header */
-                memcpy(skb_push(skb, 4), "\1\0\1\0", 4);
-        skb_queue_tail(&info->xmit_queue, skb);
-        if ((info->emu.mdmreg[12] & 0x10) != 0)
-                info->msr &= UART_MSR_CTS;
-        info->lsr &= UART_LSR_TEMT;
+	int buflen;
+	int skb_res;
+#ifdef CONFIG_ISDN_AUDIO
+	int audio_len;
+#endif
+	struct sk_buff *skb;
+	unsigned long flags;
+
+#ifdef CONFIG_ISDN_AUDIO
+	if (info->vonline & 4) {
+		info->vonline &= ~6;
+		if (!info->vonline) {
+#ifdef ISDN_DEBUG_MODEM_VOICE
+			printk(KERN_DEBUG
+			       "senddown: send VCON on ttyI%d\n",
+			       info->line);
+#endif
+			isdn_tty_at_cout("\r\nVCON\r\n", info);
+		}
+	}
+#endif
+	save_flags(flags);
+	cli();
+	if (!(buflen = info->xmit_count)) {
+		restore_flags(flags);
+		return;
+	}
+	if ((info->emu.mdmreg[12] & 0x10) != 0)
+		info->msr &= ~UART_MSR_CTS;
+	info->lsr &= ~UART_LSR_TEMT;
+	if (info->isdn_driver < 0) {
+		info->xmit_count = 0;
+		restore_flags(flags);
+		return;
+	}
+	skb_res = dev->drv[info->isdn_driver]->interface->hl_hdrlen + 4;
+#ifdef CONFIG_ISDN_AUDIO
+	if (info->vonline & 2)
+		audio_len = buflen * voice_cf[info->emu.vpar[3]];
+	else
+		audio_len = 0;
+	skb = dev_alloc_skb(skb_res + buflen + audio_len);
+#else
+	skb = dev_alloc_skb(skb_res + buflen);
+#endif
+	if (!skb) {
+		restore_flags(flags);
+		printk(KERN_WARNING
+		       "isdn_tty: Out of memory in ttyI%d senddown\n",
+		       info->line);
+		return;
+	}
+	skb_reserve(skb, skb_res);
+	memcpy(skb_put(skb, buflen), info->xmit_buf, buflen);
+	info->xmit_count = 0;
+	restore_flags(flags);
+#ifdef CONFIG_ISDN_AUDIO
+	if (info->vonline & 2) {
+		/* For now, ifmt is fixed to 1 (alaw), since this
+		 * is used with ISDN everywhere in the world, except
+		 * US, Canada and Japan.
+		 * Later, when US-ISDN protocols are implemented,
+		 * this setting will depend on the D-channel protocol.
+		 */
+		int ifmt = 1;
+
+		/* voice conversion/decompression */
+		switch (info->emu.vpar[3]) {
+			case 2:
+			case 3:
+			case 4:
+				/* adpcm, compatible to ZyXel 1496 modem
+				 * with ROM revision 6.01
+				 */
+				audio_len = isdn_audio_adpcm2xlaw(info->adpcms,
+								  ifmt,
+								  skb->data,
+						    skb_put(skb, audio_len),
+								  buflen);
+				skb_pull(skb, buflen);
+				skb_trim(skb, audio_len);
+				break;
+			case 5:
+				/* a-law */
+				if (!ifmt)
+					isdn_audio_alaw2ulaw(skb->data,
+							     buflen);
+				break;
+			case 6:
+				/* u-law */
+				if (ifmt)
+					isdn_audio_ulaw2alaw(skb->data,
+							     buflen);
+				break;
+		}
+	}
+#endif                          /* CONFIG_ISDN_AUDIO */
+	SET_SKB_FREE(skb);
+	if (info->emu.mdmreg[13] & 2)
+		/* Add T.70 simplified header */
+		memcpy(skb_push(skb, 4), "\1\0\1\0", 4);
+	skb_queue_tail(&info->xmit_queue, skb);
 }
 
 /************************************************************
@@ -517,53 +730,55 @@
  * isdn_tty_modem_result() to stuff a "NO CARRIER" Message
  * into the tty's flip-buffer.
  */
-static void isdn_tty_modem_do_ncarrier(unsigned long data)
+static void
+isdn_tty_modem_do_ncarrier(unsigned long data)
 {
-        modem_info * info = (modem_info *)data;
-        isdn_tty_modem_result(3, info);
+	modem_info *info = (modem_info *) data;
+	isdn_tty_modem_result(3, info);
 }
 
 /* Next routine is called, whenever the DTR-signal is raised.
  * It checks the ncarrier-flag, and triggers the above routine
  * when necessary. The ncarrier-flag is set, whenever DTR goes
  * low.
- */      
-static void isdn_tty_modem_ncarrier(modem_info * info)
+ */
+static void
+isdn_tty_modem_ncarrier(modem_info * info)
 {
-        if (info->ncarrier) {
-                info->ncarrier = 0;
-                info->nc_timer.expires = jiffies + HZ;
-                info->nc_timer.function = isdn_tty_modem_do_ncarrier;
-                info->nc_timer.data = (unsigned long)info;
-                add_timer(&info->nc_timer);
-        }
+	if (info->ncarrier) {
+		info->nc_timer.expires = jiffies + HZ;
+		info->nc_timer.function = isdn_tty_modem_do_ncarrier;
+		info->nc_timer.data = (unsigned long) info;
+		add_timer(&info->nc_timer);
+	}
 }
 
 /* isdn_tty_dial() performs dialing of a tty an the necessary
  * setup of the lower levels before that.
  */
-static void isdn_tty_dial(char *n, modem_info * info, atemu * m)
+static void
+isdn_tty_dial(char *n, modem_info * info, atemu * m)
 {
-        int usg = ISDN_USAGE_MODEM;
-        int si = 7;
-        int l2 = m->mdmreg[14];
+	int usg = ISDN_USAGE_MODEM;
+	int si = 7;
+	int l2 = m->mdmreg[14];
 	isdn_ctrl cmd;
 	ulong flags;
 	int i;
-        int j;
+	int j;
 
-        for (j=7;j>=0;j--)
-                if (m->mdmreg[18] & (1<<j)) {
-                        si = bit2si[j];
-                        break;
-                }
-#ifdef CONFIG_ISDN_AUDIO
-                if (si == 1) {
-                        l2 = 4;
-                        usg = ISDN_USAGE_VOICE;
-                }
+	for (j = 7; j >= 0; j--)
+		if (m->mdmreg[18] & (1 << j)) {
+			si = bit2si[j];
+			break;
+		}
+#ifdef CONFIG_ISDN_AUDIO
+	if (si == 1) {
+		l2 = 4;
+		usg = ISDN_USAGE_VOICE;
+	}
 #endif
-        m->mdmreg[20] = si2bit[si];
+	m->mdmreg[20] = si2bit[si];
 	save_flags(flags);
 	cli();
 	i = isdn_get_free_channel(usg, l2, m->mdmreg[15], -1, -1);
@@ -571,38 +786,44 @@
 		restore_flags(flags);
 		isdn_tty_modem_result(6, info);
 	} else {
-                info->isdn_driver = dev->drvmap[i];
-                info->isdn_channel = dev->chanmap[i];
-                info->drv_index = i;
-                dev->m_idx[i] = info->line;
-                dev->usage[i] |= ISDN_USAGE_OUTGOING;
-                isdn_info_update();
-                restore_flags(flags);
-                cmd.driver = info->isdn_driver;
-                cmd.arg = info->isdn_channel;
-                cmd.command = ISDN_CMD_CLREAZ;
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                strcpy(cmd.num, isdn_map_eaz2msn(m->msn, info->isdn_driver));
-                cmd.driver = info->isdn_driver;
-                cmd.command = ISDN_CMD_SETEAZ;
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                cmd.driver = info->isdn_driver;
-                cmd.command = ISDN_CMD_SETL2;
-                cmd.arg = info->isdn_channel + (l2 << 8);
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                cmd.driver = info->isdn_driver;
-                cmd.command = ISDN_CMD_SETL3;
-                cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8);
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                cmd.driver = info->isdn_driver;
-                cmd.arg = info->isdn_channel;
-                sprintf(cmd.num, "%s,%s,%d,%d", n, isdn_map_eaz2msn(m->msn, info->isdn_driver),
-                        si, m->mdmreg[19]);
-                cmd.command = ISDN_CMD_DIAL;
-                info->dialing = 1;
-                strcpy(dev->num[i], n);
-                isdn_info_update();
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
+		info->isdn_driver = dev->drvmap[i];
+		info->isdn_channel = dev->chanmap[i];
+		info->drv_index = i;
+		dev->m_idx[i] = info->line;
+		dev->usage[i] |= ISDN_USAGE_OUTGOING;
+		info->last_dir = 1;
+		strcpy(info->last_num, n);
+		isdn_info_update();
+		restore_flags(flags);
+		cmd.driver = info->isdn_driver;
+		cmd.arg = info->isdn_channel;
+		cmd.command = ISDN_CMD_CLREAZ;
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver));
+		cmd.driver = info->isdn_driver;
+		cmd.command = ISDN_CMD_SETEAZ;
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		cmd.driver = info->isdn_driver;
+		cmd.command = ISDN_CMD_SETL2;
+		info->last_l2 = l2;
+		cmd.arg = info->isdn_channel + (l2 << 8);
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		cmd.driver = info->isdn_driver;
+		cmd.command = ISDN_CMD_SETL3;
+		cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8);
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		cmd.driver = info->isdn_driver;
+		cmd.arg = info->isdn_channel;
+		sprintf(cmd.parm.setup.phone, "%s", n);
+		sprintf(cmd.parm.setup.eazmsn, "%s",
+			isdn_map_eaz2msn(m->msn, info->isdn_driver));
+		cmd.parm.setup.si1 = si;
+		cmd.parm.setup.si2 = m->mdmreg[19];
+		cmd.command = ISDN_CMD_DIAL;
+		info->dialing = 1;
+		strcpy(dev->num[i], n);
+		isdn_info_update();
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
 	}
 }
 
@@ -610,64 +831,66 @@
  * ISDN-line (hangup). The usage-status is cleared
  * and some cleanup is done also.
  */
-void isdn_tty_modem_hup(modem_info * info)
+static void
+isdn_tty_modem_hup(modem_info * info, int local)
 {
 	isdn_ctrl cmd;
-        int usage;
+	int usage;
 
-        if (!info)
-                return;
+	if (!info)
+		return;
 #ifdef ISDN_DEBUG_MODEM_HUP
-        printk(KERN_DEBUG "Mhup ttyI%d\n", info->line);
+	printk(KERN_DEBUG "Mhup ttyI%d\n", info->line);
 #endif
-        info->rcvsched = 0;
-        info->online = 0;
-        isdn_tty_flush_buffer(info->tty);
-        if (info->vonline & 1) {
-                /* voice-recording, add DLE-ETX */
-                isdn_tty_at_cout("\020\003", info);
-        }
-        if (info->vonline & 2) {
-                /* voice-playing, add DLE-DC4 */
-                isdn_tty_at_cout("\020\024", info);
-        }
-        info->vonline = 0;
-#ifdef CONFIG_ISDN_AUDIO
-        if (info->dtmf_state) {
-                kfree(info->dtmf_state);
-                info->dtmf_state = NULL;
-        }
-        if (info->adpcms) {
-                kfree(info->adpcms);
-                info->adpcms = NULL;
-        }
-        if (info->adpcmr) {
-                kfree(info->adpcmr);
-                info->adpcmr = NULL;
-        }
+	info->rcvsched = 0;
+	isdn_tty_flush_buffer(info->tty);
+	if (info->online) {
+		info->last_lhup = local;
+		info->online = 0;
+		/* NO CARRIER message */
+		isdn_tty_modem_result(3, info);
+	}
+#ifdef CONFIG_ISDN_AUDIO
+	info->vonline = 0;
+	if (info->dtmf_state) {
+		kfree(info->dtmf_state);
+		info->dtmf_state = NULL;
+	}
+	if (info->adpcms) {
+		kfree(info->adpcms);
+		info->adpcms = NULL;
+	}
+	if (info->adpcmr) {
+		kfree(info->adpcmr);
+		info->adpcmr = NULL;
+	}
 #endif
-        info->msr &= ~(UART_MSR_DCD | UART_MSR_RI);
-        info->lsr |= UART_LSR_TEMT;
+	info->msr &= ~(UART_MSR_DCD | UART_MSR_RI);
+	info->lsr |= UART_LSR_TEMT;
 	if (info->isdn_driver >= 0) {
-		cmd.driver = info->isdn_driver;
-		cmd.command = ISDN_CMD_HANGUP;
-		cmd.arg = info->isdn_channel;
-		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		if (local) {
+			cmd.driver = info->isdn_driver;
+			cmd.command = ISDN_CMD_HANGUP;
+			cmd.arg = info->isdn_channel;
+			dev->drv[info->isdn_driver]->interface->command(&cmd);
+		}
 		isdn_all_eaz(info->isdn_driver, info->isdn_channel);
-                usage = (info->emu.mdmreg[20] == 1)?
-                        ISDN_USAGE_VOICE:ISDN_USAGE_MODEM;
+		info->emu.mdmreg[1] = 0;
+		usage = (info->emu.mdmreg[20] == 1) ?
+		    ISDN_USAGE_VOICE : ISDN_USAGE_MODEM;
 		isdn_free_channel(info->isdn_driver, info->isdn_channel,
-                                  usage);
+				  usage);
 	}
 	info->isdn_driver = -1;
 	info->isdn_channel = -1;
-        if (info->drv_index >= 0) {
-                dev->m_idx[info->drv_index] = -1;
-                info->drv_index = -1;
-        }
+	if (info->drv_index >= 0) {
+		dev->m_idx[info->drv_index] = -1;
+		info->drv_index = -1;
+	}
 }
 
-static inline int isdn_tty_paranoia_check(modem_info * info, kdev_t device, const char *routine)
+static inline int
+isdn_tty_paranoia_check(modem_info * info, kdev_t device, const char *routine)
 {
 #ifdef MODEM_PARANOIA_CHECK
 	if (!info) {
@@ -688,9 +911,13 @@
  * This routine is called to set the UART divisor registers to match
  * the specified baud rate for a serial port.
  */
-static void isdn_tty_change_speed(modem_info * info)
+static void
+isdn_tty_change_speed(modem_info * info)
 {
-	uint cflag, cval, fcr, quot;
+	uint cflag,
+	 cval,
+	 fcr,
+	 quot;
 	int i;
 
 	if (!info->tty || !info->tty->termios)
@@ -707,19 +934,19 @@
 	}
 	if (quot) {
 		info->mcr |= UART_MCR_DTR;
-                isdn_tty_modem_ncarrier(info);                
+		isdn_tty_modem_ncarrier(info);
 	} else {
 		info->mcr &= ~UART_MCR_DTR;
-                if (info->emu.mdmreg[13] & 4) {
+		if (info->emu.mdmreg[13] & 4) {
 #ifdef ISDN_DEBUG_MODEM_HUP
-                        printk(KERN_DEBUG "Mhup in changespeed\n");
+			printk(KERN_DEBUG "Mhup in changespeed\n");
 #endif
-                        if (info->online)
-                                info->ncarrier = 1;
-                        isdn_tty_modem_reset_regs(info, 0);
-                        isdn_tty_modem_hup(info);
-                }
-                return;
+			if (info->online)
+				info->ncarrier = 1;
+			isdn_tty_modem_reset_regs(info, 0);
+			isdn_tty_modem_hup(info, 1);
+		}
+		return;
 	}
 	/* byte size and parity */
 	cval = cflag & (CSIZE | CSTOPB);
@@ -742,7 +969,8 @@
 	}
 }
 
-static int isdn_tty_startup(modem_info * info)
+static int
+isdn_tty_startup(modem_info * info)
 {
 	ulong flags;
 
@@ -750,12 +978,12 @@
 		return 0;
 	save_flags(flags);
 	cli();
-        isdn_MOD_INC_USE_COUNT();
+	isdn_MOD_INC_USE_COUNT();
 #ifdef ISDN_DEBUG_MODEM_OPEN
 	printk(KERN_DEBUG "starting up ttyi%d ...\n", info->line);
 #endif
 	/*
-	 * Now, initialize the UART 
+	 * Now, initialize the UART
 	 */
 	info->mcr = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
 	if (info->tty)
@@ -776,7 +1004,8 @@
  * 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 isdn_tty_shutdown(modem_info * info)
+static void
+isdn_tty_shutdown(modem_info * info)
 {
 	ulong flags;
 
@@ -786,17 +1015,18 @@
 	printk(KERN_DEBUG "Shutting down isdnmodem port %d ....\n", info->line);
 #endif
 	save_flags(flags);
-	cli();			/* Disable interrupts */
-        isdn_MOD_DEC_USE_COUNT();
+	cli();                  /* Disable interrupts */
+	isdn_MOD_DEC_USE_COUNT();
+	info->msr &= ~UART_MSR_RI;
 	if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) {
 		info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS);
-                if (info->emu.mdmreg[13] & 4) {
-                        isdn_tty_modem_reset_regs(info, 0);
+		if (info->emu.mdmreg[13] & 4) {
+			isdn_tty_modem_reset_regs(info, 0);
 #ifdef ISDN_DEBUG_MODEM_HUP
-                        printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n");
+			printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n");
 #endif
-                        isdn_tty_modem_hup(info);
-                }
+			isdn_tty_modem_hup(info, 1);
+		}
 	}
 	if (info->tty)
 		set_bit(TTY_IO_ERROR, &info->tty->flags);
@@ -814,9 +1044,11 @@
  *  - If receiving audio-data, call isdn_tty_end_vrx() to abort if needed.
  *  - If dialing, abort dial.
  */
-static int isdn_tty_write(struct tty_struct *tty, int from_user, const u_char * buf, int count)
+static int
+isdn_tty_write(struct tty_struct *tty, int from_user, const u_char * buf, int count)
 {
-	int c, total = 0;
+	int c,
+	 total = 0;
 	ulong flags;
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -824,70 +1056,82 @@
 		return 0;
 	if (!tty)
 		return 0;
-        save_flags(flags);
-        cli();
+	save_flags(flags);
+	cli();
 	while (1) {
 		c = MIN(count, info->xmit_size - info->xmit_count);
 		if (info->isdn_driver >= 0)
 			c = MIN(c, dev->drv[info->isdn_driver]->maxbufsize);
 		if (c <= 0)
 			break;
-                if ((info->online > 1) ||
-                    (info->vonline & 2)) {
-                        atemu *m = &info->emu;
-
-                        if (!(info->vonline & 2))
-                                isdn_tty_check_esc(buf, m->mdmreg[2], c,
-                                                   &(m->pluscount),
-                                                   &(m->lastplus),
-                                                   from_user);
-                        if (from_user)
-                                copy_from_user(&(info->xmit_buf[info->xmit_count]), buf, c);
-                        else
-                                memcpy(&(info->xmit_buf[info->xmit_count]), buf, c);
-#ifdef CONFIG_ISDN_AUDIO
-                        if (info->vonline & 2) {
-                                int cc;
-
-                                if (!(cc = isdn_tty_handleDLEdown(info,m,c))) {
-                                        /* If DLE decoding results in zero-transmit, but
-                                         * c originally was non-zero, do a wakeup.
-                                         */
-                                        if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
-                                            tty->ldisc.write_wakeup)
-                                                (tty->ldisc.write_wakeup) (tty);
-                                        wake_up_interruptible(&tty->write_wait);
-                                        info->msr |= UART_MSR_CTS;
-                                        info->lsr |= UART_LSR_TEMT;
-                                }
-                                info->xmit_count += cc;
-                        } else
+		if ((info->online > 1)
+#ifdef CONFIG_ISDN_AUDIO
+		    || (info->vonline & 3)
 #endif
-                                info->xmit_count += c;
-			if (m->mdmreg[13] & 1) {
-                                isdn_tty_senddown(info);
-                                isdn_tty_tint(info);
-                        }
-		} else {
-                        info->msr |= UART_MSR_CTS;
-                        info->lsr |= UART_LSR_TEMT;
+			) {
+			atemu *m = &info->emu;
+
 #ifdef CONFIG_ISDN_AUDIO
-                        if (info->vonline & 1) {
-                                if (isdn_tty_end_vrx(buf, c, from_user)) {
-                                        info->vonline &= ~1;
-                                        isdn_tty_at_cout("\020\003\r\nVCON\r\n", info);
-                                }
-                        } else
+			if (!info->vonline)
+#endif
+				isdn_tty_check_esc(buf, m->mdmreg[2], c,
+						   &(m->pluscount),
+						   &(m->lastplus),
+						   from_user);
+			if (from_user)
+				copy_from_user(&(info->xmit_buf[info->xmit_count]), buf, c);
+			else
+				memcpy(&(info->xmit_buf[info->xmit_count]), buf, c);
+#ifdef CONFIG_ISDN_AUDIO
+			if (info->vonline) {
+				int cc = isdn_tty_handleDLEdown(info, m, c);
+				if (info->vonline & 2) {
+					if (!cc) {
+						/* If DLE decoding results in zero-transmit, but
+						 * c originally was non-zero, do a wakeup.
+						 */
+						if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
+						 tty->ldisc.write_wakeup)
+							(tty->ldisc.write_wakeup) (tty);
+						wake_up_interruptible(&tty->write_wait);
+						info->msr |= UART_MSR_CTS;
+						info->lsr |= UART_LSR_TEMT;
+					}
+					info->xmit_count += cc;
+				}
+				if ((info->vonline & 3) == 1) {
+					/* Do NOT handle Ctrl-Q or Ctrl-S
+					 * when in full-duplex audio mode.
+					 */
+					if (isdn_tty_end_vrx(buf, c, from_user)) {
+						info->vonline &= ~1;
+#ifdef ISDN_DEBUG_MODEM_VOICE
+						printk(KERN_DEBUG
+						       "got !^Q/^S, send DLE-ETX,VCON on ttyI%d\n",
+						       info->line);
+#endif
+						isdn_tty_at_cout("\020\003\r\nVCON\r\n", info);
+					}
+				}
+			} else
 #endif
-                                if (info->dialing) {
-                                        info->dialing = 0;
+				info->xmit_count += c;
+			if (m->mdmreg[13] & 1) {
+				isdn_tty_senddown(info);
+				isdn_tty_tint(info);
+			}
+		} else {
+			info->msr |= UART_MSR_CTS;
+			info->lsr |= UART_LSR_TEMT;
+			if (info->dialing) {
+				info->dialing = 0;
 #ifdef ISDN_DEBUG_MODEM_HUP
-                                        printk(KERN_DEBUG "Mhup in isdn_tty_write\n");
+				printk(KERN_DEBUG "Mhup in isdn_tty_write\n");
 #endif
-                                        isdn_tty_modem_result(3, info);
-                                        isdn_tty_modem_hup(info);
-                                } else
-                                        c = isdn_tty_edit_at(buf, c, info, from_user);
+				isdn_tty_modem_result(3, info);
+				isdn_tty_modem_hup(info, 1);
+			} else
+				c = isdn_tty_edit_at(buf, c, info, from_user);
 		}
 		buf += c;
 		count -= c;
@@ -895,11 +1139,12 @@
 	}
 	if ((info->xmit_count) || (skb_queue_len(&info->xmit_queue)))
 		isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1);
-        restore_flags(flags);
+	restore_flags(flags);
 	return total;
 }
 
-static int isdn_tty_write_room(struct tty_struct *tty)
+static int
+isdn_tty_write_room(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 	int ret;
@@ -912,7 +1157,8 @@
 	return (ret < 0) ? 0 : ret;
 }
 
-static int isdn_tty_chars_in_buffer(struct tty_struct *tty)
+static int
+isdn_tty_chars_in_buffer(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -923,32 +1169,34 @@
 	return (info->xmit_count);
 }
 
-static void isdn_tty_flush_buffer(struct tty_struct *tty)
+static void
+isdn_tty_flush_buffer(struct tty_struct *tty)
 {
 	modem_info *info;
-        unsigned long flags;
+	unsigned long flags;
 
-        save_flags(flags);
-        cli();
-        if (!tty) {
-                restore_flags(flags);
-                return;
-        }
-        info =  (modem_info *) tty->driver_data;
+	save_flags(flags);
+	cli();
+	if (!tty) {
+		restore_flags(flags);
+		return;
+	}
+	info = (modem_info *) tty->driver_data;
 	if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_flush_buffer")) {
-                restore_flags(flags);
+		restore_flags(flags);
 		return;
-        }
-        isdn_tty_cleanup_xmit(info);
-        info->xmit_count = 0;
-        restore_flags(flags);
+	}
+	isdn_tty_cleanup_xmit(info);
+	info->xmit_count = 0;
+	restore_flags(flags);
 	wake_up_interruptible(&tty->write_wait);
 	if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) &&
 	    tty->ldisc.write_wakeup)
 		(tty->ldisc.write_wakeup) (tty);
 }
 
-static void isdn_tty_flush_chars(struct tty_struct *tty)
+static void
+isdn_tty_flush_chars(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -961,12 +1209,13 @@
 /*
  * ------------------------------------------------------------
  * isdn_tty_throttle()
- * 
+ *
  * This routine is called by the upper-layer tty layer to signal that
  * incoming characters should be throttled.
  * ------------------------------------------------------------
  */
-static void isdn_tty_throttle(struct tty_struct *tty)
+static void
+isdn_tty_throttle(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -977,7 +1226,8 @@
 	info->mcr &= ~UART_MCR_RTS;
 }
 
-static void isdn_tty_unthrottle(struct tty_struct *tty)
+static void
+isdn_tty_unthrottle(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -1006,9 +1256,10 @@
  *          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. 
+ *          allows RS485 driver to be written in user space.
  */
-static int isdn_tty_get_lsr_info(modem_info * info, uint * value)
+static int
+isdn_tty_get_lsr_info(modem_info * info, uint * value)
 {
 	u_char status;
 	uint result;
@@ -1024,9 +1275,11 @@
 }
 
 
-static int isdn_tty_get_modem_info(modem_info * info, uint * value)
+static int
+isdn_tty_get_modem_info(modem_info * info, uint * value)
 {
-	u_char control, status;
+	u_char control,
+	 status;
 	uint result;
 	ulong flags;
 
@@ -1045,76 +1298,78 @@
 	return 0;
 }
 
-static int isdn_tty_set_modem_info(modem_info * info, uint cmd, uint * value)
+static int
+isdn_tty_set_modem_info(modem_info * info, uint cmd, uint * value)
 {
 	uint arg;
-        int pre_dtr;
+	int pre_dtr;
 
-    GET_USER(arg, (uint *)value);
+	GET_USER(arg, (uint *) value);
 	switch (cmd) {
-                case TIOCMBIS:
+		case TIOCMBIS:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIS\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIS\n", info->line);
 #endif
-                        if (arg & TIOCM_RTS) {
-                                info->mcr |= UART_MCR_RTS;
-                        }
-                        if (arg & TIOCM_DTR) {
-                                info->mcr |= UART_MCR_DTR;
-                                isdn_tty_modem_ncarrier(info);
-                        }
-                        break;
-                case TIOCMBIC:
+			if (arg & TIOCM_RTS) {
+				info->mcr |= UART_MCR_RTS;
+			}
+			if (arg & TIOCM_DTR) {
+				info->mcr |= UART_MCR_DTR;
+				isdn_tty_modem_ncarrier(info);
+			}
+			break;
+		case TIOCMBIC:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIC\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCMBIC\n", info->line);
 #endif
-                        if (arg & TIOCM_RTS) {
-                                info->mcr &= ~UART_MCR_RTS;
-                        }
-                        if (arg & TIOCM_DTR) {
-                                info->mcr &= ~UART_MCR_DTR;
-                                if (info->emu.mdmreg[13] & 4) {
-                                        isdn_tty_modem_reset_regs(info, 0);
+			if (arg & TIOCM_RTS) {
+				info->mcr &= ~UART_MCR_RTS;
+			}
+			if (arg & TIOCM_DTR) {
+				info->mcr &= ~UART_MCR_DTR;
+				if (info->emu.mdmreg[13] & 4) {
+					isdn_tty_modem_reset_regs(info, 0);
 #ifdef ISDN_DEBUG_MODEM_HUP
-                                        printk(KERN_DEBUG "Mhup in TIOCMBIC\n");
+					printk(KERN_DEBUG "Mhup in TIOCMBIC\n");
 #endif
-                                        if (info->online)
-                                                info->ncarrier = 1;
-                                        isdn_tty_modem_hup(info);
-                                }
-                        }
-                        break;
-                case TIOCMSET:
+					if (info->online)
+						info->ncarrier = 1;
+					isdn_tty_modem_hup(info, 1);
+				}
+			}
+			break;
+		case TIOCMSET:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCMSET\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCMSET\n", info->line);
 #endif
-                        pre_dtr = (info->mcr & UART_MCR_DTR);
-                        info->mcr = ((info->mcr & ~(UART_MCR_RTS | UART_MCR_DTR))
-                                     | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
-                                     | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
-                        if (pre_dtr |= (info->mcr & UART_MCR_DTR)) {
-                                if (!(info->mcr & UART_MCR_DTR)) {
-                                        if (info->emu.mdmreg[13] & 4) {
-                                                isdn_tty_modem_reset_regs(info, 0);
+			pre_dtr = (info->mcr & UART_MCR_DTR);
+			info->mcr = ((info->mcr & ~(UART_MCR_RTS | UART_MCR_DTR))
+				 | ((arg & TIOCM_RTS) ? UART_MCR_RTS : 0)
+			       | ((arg & TIOCM_DTR) ? UART_MCR_DTR : 0));
+			if (pre_dtr |= (info->mcr & UART_MCR_DTR)) {
+				if (!(info->mcr & UART_MCR_DTR)) {
+					if (info->emu.mdmreg[13] & 4) {
+						isdn_tty_modem_reset_regs(info, 0);
 #ifdef ISDN_DEBUG_MODEM_HUP
-                                                printk(KERN_DEBUG "Mhup in TIOCMSET\n");
+						printk(KERN_DEBUG "Mhup in TIOCMSET\n");
 #endif
-                                                if (info->online)
-                                                        info->ncarrier = 1;
-                                                isdn_tty_modem_hup(info);
-                                        }
-                                } else
-                                        isdn_tty_modem_ncarrier(info);
-                        }
-                        break;
-                default:
-                        return -EINVAL;
+						if (info->online)
+							info->ncarrier = 1;
+						isdn_tty_modem_hup(info, 1);
+					}
+				} else
+					isdn_tty_modem_ncarrier(info);
+			}
+			break;
+		default:
+			return -EINVAL;
 	}
 	return 0;
 }
 
-static int isdn_tty_ioctl(struct tty_struct *tty, struct file *file,
-		       uint cmd, ulong arg)
+static int
+isdn_tty_ioctl(struct tty_struct *tty, struct file *file,
+	       uint cmd, ulong arg)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 	int error;
@@ -1122,97 +1377,98 @@
 
 	if (isdn_tty_paranoia_check(info, tty->device, "isdn_tty_ioctl"))
 		return -ENODEV;
-        if (tty->flags & (1 << TTY_IO_ERROR))
-                return -EIO;
+	if (tty->flags & (1 << TTY_IO_ERROR))
+		return -EIO;
 	switch (cmd) {
-                case TCSBRK:		/* SVID version: non-zero arg --> no break */
+		case TCSBRK:   /* SVID version: non-zero arg --> no break */
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TCSBRK\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TCSBRK\n", 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() */
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			return 0;
+		case TCSBRKP:  /* support for POSIX tcsendbreak() */
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TCSBRKP\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TCSBRKP\n", info->line);
 #endif
-                        retval = tty_check_change(tty);
-                        if (retval)
-                                return retval;
-                        tty_wait_until_sent(tty, 0);
-                        return 0;
-                case TIOCGSOFTCAR:
+			retval = tty_check_change(tty);
+			if (retval)
+				return retval;
+			tty_wait_until_sent(tty, 0);
+			return 0;
+		case TIOCGSOFTCAR:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCGSOFTCAR\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCGSOFTCAR\n", info->line);
 #endif
-                        error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(long));
-                        if (error)
-                                return error;
-                        put_user(C_CLOCAL(tty) ? 1 : 0, (ulong *) arg);
-                        return 0;
-                case TIOCSSOFTCAR:
+			error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(long));
+			if (error)
+				return error;
+			put_user(C_CLOCAL(tty) ? 1 : 0, (ulong *) arg);
+			return 0;
+		case TIOCSSOFTCAR:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCSSOFTCAR\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCSSOFTCAR\n", info->line);
 #endif
-                        error = verify_area(VERIFY_READ, (void *) arg, sizeof(long));
-                        if (error)
-                                return error;
-                        GET_USER(arg, (ulong *) arg);
-                        tty->termios->c_cflag =
-                                ((tty->termios->c_cflag & ~CLOCAL) |
-                                 (arg ? CLOCAL : 0));
-                        return 0;
-                case TIOCMGET:
+			error = verify_area(VERIFY_READ, (void *) arg, sizeof(long));
+			if (error)
+				return error;
+			GET_USER(arg, (ulong *) arg);
+			tty->termios->c_cflag =
+			    ((tty->termios->c_cflag & ~CLOCAL) |
+			     (arg ? CLOCAL : 0));
+			return 0;
+		case TIOCMGET:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line);
 #endif
-                        error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(uint));
-                        if (error)
-                                return error;
-                        return isdn_tty_get_modem_info(info, (uint *) arg);
-                case TIOCMBIS:
-                case TIOCMBIC:
-                case TIOCMSET:
-                        error = verify_area(VERIFY_READ, (void *) arg, sizeof(uint));
-                        if (error)
-                                return error;
-                        return isdn_tty_set_modem_info(info, cmd, (uint *) arg);
-                case TIOCSERGETLSR:	/* Get line status register */
+			error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(uint));
+			if (error)
+				return error;
+			return isdn_tty_get_modem_info(info, (uint *) arg);
+		case TIOCMBIS:
+		case TIOCMBIC:
+		case TIOCMSET:
+			error = verify_area(VERIFY_READ, (void *) arg, sizeof(uint));
+			if (error)
+				return error;
+			return isdn_tty_set_modem_info(info, cmd, (uint *) arg);
+		case TIOCSERGETLSR:	/* Get line status register */
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "ttyI%d ioctl TIOCSERGETLSR\n", info->line);
+			printk(KERN_DEBUG "ttyI%d ioctl TIOCSERGETLSR\n", info->line);
 #endif
-                        error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(uint));
-                        if (error)
-                                return error;
-                        else
-                                return isdn_tty_get_lsr_info(info, (uint *) arg);
-                        
-                default:
+			error = verify_area(VERIFY_WRITE, (void *) arg, sizeof(uint));
+			if (error)
+				return error;
+			else
+				return isdn_tty_get_lsr_info(info, (uint *) arg);
+
+		default:
 #ifdef ISDN_DEBUG_MODEM_IOCTL
-                        printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on ttyi%d\n", cmd, info->line);
+			printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on ttyi%d\n", cmd, info->line);
 #endif
-                        return -ENOIOCTLCMD;
+			return -ENOIOCTLCMD;
 	}
 	return 0;
 }
 
-static void isdn_tty_set_termios(struct tty_struct *tty, struct termios *old_termios)
+static void
+isdn_tty_set_termios(struct tty_struct *tty, struct termios *old_termios)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
-        if (!old_termios)
-                isdn_tty_change_speed(info);
-        else {
-                if (tty->termios->c_cflag == old_termios->c_cflag)
-                        return;
-                isdn_tty_change_speed(info);
-                if ((old_termios->c_cflag & CRTSCTS) &&
-                    !(tty->termios->c_cflag & CRTSCTS)) {
-                        tty->hw_stopped = 0;
-                }
-        }
+	if (!old_termios)
+		isdn_tty_change_speed(info);
+	else {
+		if (tty->termios->c_cflag == old_termios->c_cflag)
+			return;
+		isdn_tty_change_speed(info);
+		if ((old_termios->c_cflag & CRTSCTS) &&
+		    !(tty->termios->c_cflag & CRTSCTS)) {
+			tty->hw_stopped = 0;
+		}
+	}
 }
 
 /*
@@ -1220,9 +1476,11 @@
  * isdn_tty_open() and friends
  * ------------------------------------------------------------
  */
-static int isdn_tty_block_til_ready(struct tty_struct *tty, struct file *filp, modem_info * info)
+static int
+isdn_tty_block_til_ready(struct tty_struct *tty, struct file *filp, modem_info * info)
 {
-	struct wait_queue wait = {current, NULL};
+	struct wait_queue wait =
+	{current, NULL};
 	int do_clocal = 0;
 	unsigned long flags;
 	int retval;
@@ -1233,8 +1491,8 @@
 	 */
 	if (tty_hung_up_p(filp) ||
 	    (info->flags & ISDN_ASYNC_CLOSING)) {
-                if (info->flags & ISDN_ASYNC_CLOSING)
-                        interruptible_sleep_on(&info->close_wait);
+		if (info->flags & ISDN_ASYNC_CLOSING)
+			interruptible_sleep_on(&info->close_wait);
 #ifdef MODEM_DO_RESTART
 		if (info->flags & ISDN_ASYNC_HUP_NOTIFY)
 			return -EAGAIN;
@@ -1267,7 +1525,7 @@
 	 * and then exit.
 	 */
 	if ((filp->f_flags & O_NONBLOCK) ||
-            (tty->flags & (1 << TTY_IO_ERROR))) {
+	    (tty->flags & (1 << TTY_IO_ERROR))) {
 		if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE)
 			return -EBUSY;
 		info->flags |= ISDN_ASYNC_NORMAL_ACTIVE;
@@ -1293,11 +1551,11 @@
 	printk(KERN_DEBUG "isdn_tty_block_til_ready before block: ttyi%d, count = %d\n",
 	       info->line, info->count);
 #endif
-        save_flags(flags);
-        cli();
-        if (!(tty_hung_up_p(filp)))
-                info->count--;
-        restore_flags(flags);
+	save_flags(flags);
+	cli();
+	if (!(tty_hung_up_p(filp)))
+		info->count--;
+	restore_flags(flags);
 	info->blocked_open++;
 	while (1) {
 		current->state = TASK_INTERRUPTIBLE;
@@ -1349,10 +1607,12 @@
  * the IRQ chain.   It also performs the serial-specific
  * initialization for the tty structure.
  */
-static int isdn_tty_open(struct tty_struct *tty, struct file *filp)
+static int
+isdn_tty_open(struct tty_struct *tty, struct file *filp)
 {
 	modem_info *info;
-	int retval, line;
+	int retval,
+	 line;
 
 	line = MINOR(tty->device) - tty->driver.minor_start;
 	if (line < 0 || line > ISDN_MAX_CHANNELS)
@@ -1403,7 +1663,8 @@
 	return 0;
 }
 
-static void isdn_tty_close(struct tty_struct *tty, struct file *filp)
+static void
+isdn_tty_close(struct tty_struct *tty, struct file *filp)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 	ulong flags;
@@ -1454,7 +1715,7 @@
 	if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE)
 		info->callout_termios = *tty->termios;
 
-        tty->closing = 1;
+	tty->closing = 1;
 	/*
 	 * At this point we stop accepting input.  To do this, we
 	 * disable the receive line status interrupts, and tell the
@@ -1462,7 +1723,7 @@
 	 * line status register.
 	 */
 	if (info->flags & ISDN_ASYNC_INITIALIZED) {
-		tty_wait_until_sent(tty, 3000);		/* 30 seconds timeout */
+		tty_wait_until_sent(tty, 3000);	/* 30 seconds timeout */
 		/*
 		 * Before we drop DTR, make sure the UART transmitter
 		 * has completely drained; this is especially
@@ -1484,12 +1745,12 @@
 	if (tty->ldisc.flush_buffer)
 		tty->ldisc.flush_buffer(tty);
 	info->tty = 0;
-        info->ncarrier = 0;
+	info->ncarrier = 0;
 	tty->closing = 0;
 	if (info->blocked_open) {
-                current->state = TASK_INTERRUPTIBLE;
-                current->timeout = jiffies + 50;
-                schedule();
+		current->state = TASK_INTERRUPTIBLE;
+		current->timeout = jiffies + 50;
+		schedule();
 		wake_up_interruptible(&info->open_wait);
 	}
 	info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE |
@@ -1504,7 +1765,8 @@
 /*
  * isdn_tty_hangup() --- called by tty_hangup() when a hangup is signaled.
  */
-static void isdn_tty_hangup(struct tty_struct *tty)
+static void
+isdn_tty_hangup(struct tty_struct *tty)
 {
 	modem_info *info = (modem_info *) tty->driver_data;
 
@@ -1519,7 +1781,8 @@
 
 /* This routine initializes all emulator-data.
  */
-static void isdn_tty_reset_profile(atemu * m)
+static void
+isdn_tty_reset_profile(atemu * m)
 {
 	m->profile[0] = 0;
 	m->profile[1] = 0;
@@ -1545,27 +1808,34 @@
 	m->pmsn[0] = '\0';
 }
 
-static void isdn_tty_modem_reset_vpar(atemu *m)
+#ifdef CONFIG_ISDN_AUDIO
+static void
+isdn_tty_modem_reset_vpar(atemu * m)
 {
-        m->vpar[0] = 2;  /* Voice-device            (2 = phone line) */
-        m->vpar[1] = 0;  /* Silence detection level (0 = none      ) */
-        m->vpar[2] = 70; /* Silence interval        (7 sec.        ) */
-        m->vpar[3] = 2;  /* Compression type        (1 = ADPCM-2   ) */
+	m->vpar[0] = 2;         /* Voice-device            (2 = phone line) */
+	m->vpar[1] = 0;         /* Silence detection level (0 = none      ) */
+	m->vpar[2] = 70;        /* Silence interval        (7 sec.        ) */
+	m->vpar[3] = 2;         /* Compression type        (1 = ADPCM-2   ) */
 }
+#endif
 
-static void isdn_tty_modem_reset_regs(modem_info *info, int force)
+static void
+isdn_tty_modem_reset_regs(modem_info * info, int force)
 {
-        atemu *m = &info->emu;
+	atemu *m = &info->emu;
 	if ((m->mdmreg[12] & 32) || force) {
 		memcpy(m->mdmreg, m->profile, ISDN_MODEM_ANZREG);
 		memcpy(m->msn, m->pmsn, ISDN_MSNLEN);
-                info->xmit_size = m->mdmreg[16] * 16;
+		info->xmit_size = m->mdmreg[16] * 16;
 	}
-        isdn_tty_modem_reset_vpar(m);
+#ifdef CONFIG_ISDN_AUDIO
+	isdn_tty_modem_reset_vpar(m);
+#endif
 	m->mdmcmdl = 0;
 }
 
-static void modem_write_profile(atemu * m)
+static void
+modem_write_profile(atemu * m)
 {
 	memcpy(m->profile, m->mdmreg, ISDN_MODEM_ANZREG);
 	memcpy(m->pmsn, m->msn, ISDN_MSNLEN);
@@ -1573,7 +1843,8 @@
 		send_sig(SIGIO, dev->profd, 1);
 }
 
-int isdn_tty_modem_init(void)
+int
+isdn_tty_modem_init(void)
 {
 	modem *m;
 	int i;
@@ -1630,6 +1901,12 @@
 	}
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
 		info = &m->info[i];
+		sprintf(info->last_cause, "0000");
+		sprintf(info->last_num, "none");
+		info->last_dir = 0;
+		info->last_lhup = 1;
+		info->last_l2 = 0;
+		info->last_si = 0;
 		isdn_tty_reset_profile(&info->emu);
 		isdn_tty_modem_reset_regs(info, 1);
 		info->magic = ISDN_ASYNC_MAGIC;
@@ -1646,14 +1923,16 @@
 		info->isdn_channel = -1;
 		info->drv_index = -1;
 		info->xmit_size = ISDN_SERIAL_XMIT_SIZE;
-                skb_queue_head_init(&info->xmit_queue);
-                skb_queue_head_init(&info->dtmf_queue);
-                if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_SIZE + 5, GFP_KERNEL))) {
-                        printk(KERN_ERR "Could not allocate modem xmit-buffer\n");
-                        return -3;
-                }
-                /* Make room for T.70 header */
-                info->xmit_buf += 4;
+		skb_queue_head_init(&info->xmit_queue);
+#ifdef CONFIG_ISDN_AUDIO
+		skb_queue_head_init(&info->dtmf_queue);
+#endif
+		if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_SIZE + 5, GFP_KERNEL))) {
+			printk(KERN_ERR "Could not allocate modem xmit-buffer\n");
+			return -3;
+		}
+		/* Make room for T.70 header */
+		info->xmit_buf += 4;
 	}
 	return 0;
 }
@@ -1664,61 +1943,46 @@
  * it to the ISDN-Channel.
  * Return Index to dev->mdm or -1 if none found.
  */
-int isdn_tty_find_icall(int di, int ch, char *num)
+int
+isdn_tty_find_icall(int di, int ch, setup_parm setup)
 {
 	char *eaz;
 	int i;
 	int idx;
 	int si1;
 	int si2;
-	char *s;
-	char nr[31];
+	char nr[32];
 	ulong flags;
 
 	save_flags(flags);
 	cli();
-	if (num[0] == ',') {
+	if (!setup.phone[0]) {
 		nr[0] = '0';
-		strncpy(&nr[1], num, 29);
+		nr[1] = '\0';
 		printk(KERN_INFO "isdn_tty: Incoming call without OAD, assuming '0'\n");
 	} else
-		strncpy(nr, num, 30);
-	s = strtok(nr, ",");
-	s = strtok(NULL, ",");
-	if (!s) {
-		printk(KERN_WARNING "isdn_tty: Incoming callinfo garbled, ignored: %s\n",
-		       num);
-		restore_flags(flags);
-		return -1;
-	}
-	si1 = (int)simple_strtoul(s,NULL,10);
-	s = strtok(NULL, ",");
-	if (!s) {
-		printk(KERN_WARNING "isdn_tty: Incoming callinfo garbled, ignored: %s\n",
-		       num);
-		restore_flags(flags);
-		return -1;
-	}
-	si2 = (int)simple_strtoul(s,NULL,10);
-	eaz = strtok(NULL, ",");
-	if (!eaz) {
+		strcpy(nr, setup.phone);
+	si1 = (int) setup.si1;
+	si2 = (int) setup.si2;
+	if (!setup.eazmsn[0]) {
 		printk(KERN_WARNING "isdn_tty: Incoming call without CPN, assuming '0'\n");
 		eaz = "0";
-	}
+	} else
+		eaz = setup.eazmsn;
 #ifdef ISDN_DEBUG_MODEM_ICALL
 	printk(KERN_DEBUG "m_fi: eaz=%s si1=%d si2=%d\n", eaz, si1, si2);
 #endif
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
-                modem_info *info = &dev->mdm.info[i];
+		modem_info *info = &dev->mdm.info[i];
 #ifdef ISDN_DEBUG_MODEM_ICALL
 		printk(KERN_DEBUG "m_fi: i=%d msn=%s mmsn=%s mreg18=%d mreg19=%d\n", i,
 		       info->emu.msn, isdn_map_eaz2msn(info->emu.msn, di),
 		       info->emu.mdmreg[18], info->emu.mdmreg[19]);
 #endif
 		if ((!strcmp(isdn_map_eaz2msn(info->emu.msn, di),
-                             eaz)) &&                             /* EAZ is matching      */
-		    (info->emu.mdmreg[18] & si2bit[si1]) &&       /* SI1 is matching      */
-		    ((info->emu.mdmreg[19] == si2) || !si2)) {    /* SI2 is matching or 0 */
+			     eaz)) &&	/* EAZ is matching      */
+		    (info->emu.mdmreg[18] & si2bit[si1]) &&	/* SI1 is matching      */
+		    ((info->emu.mdmreg[19] == si2) || !si2)) {	/* SI2 is matching or 0 */
 			idx = isdn_dc2minor(di, ch);
 #ifdef ISDN_DEBUG_MODEM_ICALL
 			printk(KERN_DEBUG "m_fi: match1\n");
@@ -1735,13 +1999,18 @@
 				info->drv_index = idx;
 				dev->m_idx[idx] = info->line;
 				dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE;
-				dev->usage[idx] |= (si1==1)?ISDN_USAGE_VOICE:ISDN_USAGE_MODEM;
+				dev->usage[idx] |= (si1 == 1) ? ISDN_USAGE_VOICE : ISDN_USAGE_MODEM;
 				strcpy(dev->num[idx], nr);
-                                info->emu.mdmreg[20] = si2bit[si1];
+				info->emu.mdmreg[20] = si2bit[si1];
+				info->emu.mdmreg[21] = setup.plan;
+				info->emu.mdmreg[22] = setup.screen;
 				isdn_info_update();
 				restore_flags(flags);
 				printk(KERN_INFO "isdn_tty: call from %s, -> RING on ttyI%d\n", nr,
 				       info->line);
+				info->msr |= UART_MSR_RI;
+				isdn_tty_modem_result(2, info);
+				isdn_timer_ctrl(ISDN_TIMER_MODEMRING, 1);
 				return info->line;
 			}
 		}
@@ -1752,6 +2021,140 @@
 	return -1;
 }
 
+#define TTY_IS_ACTIVE(info) \
+	(info->flags & (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE))
+
+int
+isdn_tty_stat_callback(int i, isdn_ctrl * c)
+{
+	int mi;
+	modem_info *info;
+
+	if (i < 0)
+		return 0;
+	if ((mi = dev->m_idx[i]) >= 0) {
+		info = &dev->mdm.info[mi];
+		switch (c->command) {
+			case ISDN_STAT_BSENT:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_BSENT ttyI%d\n", info->line);
+#endif
+				if ((info->isdn_driver == c->driver) &&
+				    (info->isdn_channel == c->arg)) {
+					info->msr |= UART_MSR_CTS;
+					if (info->send_outstanding)
+						if (!(--info->send_outstanding))
+							info->lsr |= UART_LSR_TEMT;
+					isdn_tty_tint(info);
+					return 1;
+				}
+				break;
+			case ISDN_STAT_CAUSE:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_CAUSE ttyI%d\n", info->line);
+#endif
+				/* Signal cause to tty-device */
+				strncpy(info->last_cause, c->parm.num, 5);
+				return 1;
+			case ISDN_STAT_DCONN:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_DCONN ttyI%d\n", info->line);
+#endif
+				if (TTY_IS_ACTIVE(info)) {
+					if (info->dialing == 1) {
+						info->dialing = 2;
+						return 1;
+					}
+				}
+				break;
+			case ISDN_STAT_DHUP:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_DHUP ttyI%d\n", info->line);
+#endif
+				if (TTY_IS_ACTIVE(info)) {
+					if (info->dialing == 1) {
+						info->dialing = 0;
+						isdn_tty_modem_result(7, info);
+					}
+#ifdef ISDN_DEBUG_MODEM_HUP
+					printk(KERN_DEBUG "Mhup in ISDN_STAT_DHUP\n");
+#endif
+					isdn_tty_modem_hup(info, 0);
+					return 1;
+				}
+				break;
+			case ISDN_STAT_BCONN:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_BCONN ttyI%d\n", info->line);
+#endif
+				/* Schedule CONNECT-Message to any tty
+				 * waiting for it and
+				 * set DCD-bit of its modem-status.
+				 */
+				if (TTY_IS_ACTIVE(info)) {
+					info->msr |= UART_MSR_DCD;
+					if (info->dialing) {
+						info->dialing = 0;
+						info->last_dir = 1;
+					} else
+						info->last_dir = 0;
+					info->rcvsched = 1;
+					if (USG_MODEM(dev->usage[i]))
+						isdn_tty_modem_result(5, info);
+					if (USG_VOICE(dev->usage[i]))
+						isdn_tty_modem_result(11, info);
+					return 1;
+				}
+				break;
+			case ISDN_STAT_BHUP:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_BHUP ttyI%d\n", info->line);
+#endif
+				if (TTY_IS_ACTIVE(info)) {
+#ifdef ISDN_DEBUG_MODEM_HUP
+					printk(KERN_DEBUG "Mhup in ISDN_STAT_BHUP\n");
+#endif
+					isdn_tty_modem_hup(info, 0);
+					return 1;
+				}
+				break;
+			case ISDN_STAT_NODCH:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_NODCH ttyI%d\n", info->line);
+#endif
+				if (TTY_IS_ACTIVE(info)) {
+					if (info->dialing) {
+						info->dialing = 0;
+						info->last_l2 = -1;
+						info->last_si = 0;
+						sprintf(info->last_cause, "0000");
+						isdn_tty_modem_result(6, info);
+					}
+					info->msr &= ~UART_MSR_DCD;
+					if (info->online) {
+						isdn_tty_modem_result(3, info);
+						info->online = 0;
+					}
+					return 1;
+				}
+				break;
+			case ISDN_STAT_UNLOAD:
+#ifdef ISDN_TTY_STAT_DEBUG
+				printk(KERN_DEBUG "tty_STAT_UNLOAD ttyI%d\n", info->line);
+#endif
+				for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
+					info = &dev->mdm.info[i];
+					if (info->isdn_driver == c->driver) {
+						if (info->online)
+							isdn_tty_modem_hup(info, 1);
+					}
+				}
+				return 1;
+		}
+	}
+	return 0;
+}
+
 /*********************************************************************
  Modem-Emulator-Routines
  *********************************************************************/
@@ -1762,7 +2165,8 @@
  * Put a message from the AT-emulator into receive-buffer of tty,
  * convert CR, LF, and BS to values in modem-registers 3, 4 and 5.
  */
-static void isdn_tty_at_cout(char *msg, modem_info * info)
+static void
+isdn_tty_at_cout(char *msg, modem_info * info)
 {
 	struct tty_struct *tty;
 	atemu *m = &info->emu;
@@ -1779,17 +2183,17 @@
 	tty = info->tty;
 	for (p = msg; *p; p++) {
 		switch (*p) {
-                        case '\r':
-                                c = m->mdmreg[3];
-                                break;
-                        case '\n':
-                                c = m->mdmreg[4];
-                                break;
-                        case '\b':
-                                c = m->mdmreg[5];
-                                break;
-                        default:
-                                c = *p;
+			case '\r':
+				c = m->mdmreg[3];
+				break;
+			case '\n':
+				c = m->mdmreg[4];
+				break;
+			case '\b':
+				c = m->mdmreg[5];
+				break;
+			default:
+				c = *p;
 		}
 		if ((info->flags & ISDN_ASYNC_CLOSING) || (!tty)) {
 			restore_flags(flags);
@@ -1806,24 +2210,25 @@
 /*
  * Perform ATH Hangup
  */
-static void isdn_tty_on_hook(modem_info * info)
+static void
+isdn_tty_on_hook(modem_info * info)
 {
 	if (info->isdn_channel >= 0) {
 #ifdef ISDN_DEBUG_MODEM_HUP
 		printk(KERN_DEBUG "Mhup in isdn_tty_on_hook\n");
 #endif
-		isdn_tty_modem_result(3, info);
-		isdn_tty_modem_hup(info);
+		isdn_tty_modem_hup(info, 1);
 	}
 }
 
-static void isdn_tty_off_hook(void)
+static void
+isdn_tty_off_hook(void)
 {
 	printk(KERN_DEBUG "isdn_tty_off_hook\n");
 }
 
-#define PLUSWAIT1 (HZ/2)	/* 0.5 sec. */
-#define PLUSWAIT2 (HZ*3/2)	/* 1.5 sec */
+#define PLUSWAIT1 (HZ/2)        /* 0.5 sec. */
+#define PLUSWAIT2 (HZ*3/2)      /* 1.5 sec */
 
 /*
  * Check Buffer for Modem-escape-sequence, activate timer-callback to
@@ -1836,8 +2241,9 @@
  *   pluscount  count of valid escape-characters so far
  *   lastplus   timestamp of last character
  */
-static void isdn_tty_check_esc(const u_char * p, u_char plus, int count, int *pluscount,
-			   int *lastplus, int from_user)
+static void
+isdn_tty_check_esc(const u_char * p, u_char plus, int count, int *pluscount,
+		   int *lastplus, int from_user)
 {
 	char cbuf[3];
 
@@ -1880,7 +2286,8 @@
  * For CONNECT-messages also switch to online-mode.
  * For RING-message handle auto-ATA if register 0 != 0
  */
-void isdn_tty_modem_result(int code, modem_info * info)
+static void
+isdn_tty_modem_result(int code, modem_info * info)
 {
 	atemu *m = &info->emu;
 	static char *msg[] =
@@ -1888,47 +2295,66 @@
 	 "CONNECT 64000", "NO DIALTONE", "BUSY", "NO ANSWER",
 	 "RINGING", "NO MSN/EAZ", "VCON"};
 	ulong flags;
-	char s[4];
+	char s[10];
 
 	switch (code) {
-                case 2:
-                        m->mdmreg[1]++;	/* RING */
-                        if (m->mdmreg[1] == m->mdmreg[0])
-                                /* Automatically accept incoming call */
-                                isdn_tty_cmd_ATA(info);
-                        break;
-                case 3:
-                        /* NO CARRIER */
-                        save_flags(flags);
-                        cli();
+		case 2:
+			m->mdmreg[1]++;	/* RING */
+			if (m->mdmreg[1] == m->mdmreg[0])
+				/* Automatically accept incoming call */
+				isdn_tty_cmd_ATA(info);
+			break;
+		case 3:
+			/* NO CARRIER */
 #ifdef ISDN_DEBUG_MODEM_HUP
-                        printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n",
-                               (info->flags & ISDN_ASYNC_CLOSING),
-                               (!info->tty));
-#endif
-                        if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) {
-                                restore_flags(flags);
-                                return;
-                        }
-                        restore_flags(flags);
-                        if (info->vonline & 1) {
-                                /* voice-recording, add DLE-ETX */
-                                isdn_tty_at_cout("\020\003", info);
-                        }
-                        if (info->vonline & 2) {
-                                /* voice-playing, add DLE-DC4 */
-                                isdn_tty_at_cout("\020\024", info);
-                        }
-                        break;
-                case 1:
-                case 5:
-                        if (!info->online)
-                                info->online = 2;
-                        break;
-                case 11:
-                        if (!info->online)
-                                info->online = 1;
-                        break;
+			printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n",
+			       (info->flags & ISDN_ASYNC_CLOSING),
+			       (!info->tty));
+#endif
+			save_flags(flags);
+			cli();
+			m->mdmreg[1] = 0;
+			del_timer(&info->nc_timer);
+			info->ncarrier = 0;
+			if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) {
+				restore_flags(flags);
+				return;
+			}
+			restore_flags(flags);
+#ifdef CONFIG_ISDN_AUDIO
+			if (info->vonline & 1) {
+#ifdef ISDN_DEBUG_MODEM_VOICE
+				printk(KERN_DEBUG "res3: send DLE-ETX on ttyI%d\n",
+				       info->line);
+#endif
+				/* voice-recording, add DLE-ETX */
+				isdn_tty_at_cout("\020\003", info);
+			}
+			if (info->vonline & 2) {
+#ifdef ISDN_DEBUG_MODEM_VOICE
+				printk(KERN_DEBUG "res3: send DLE-DC4 on ttyI%d\n",
+				       info->line);
+#endif
+				/* voice-playing, add DLE-DC4 */
+				isdn_tty_at_cout("\020\024", info);
+			}
+#endif
+			break;
+		case 1:
+		case 5:
+			sprintf(info->last_cause, "0000");
+			if (!info->online)
+				info->online = 2;
+			break;
+		case 11:
+#ifdef ISDN_DEBUG_MODEM_VOICE
+			printk(KERN_DEBUG "res3: send VCON on ttyI%d\n",
+			       info->line);
+#endif
+			sprintf(info->last_cause, "0000");
+			if (!info->online)
+				info->online = 1;
+			break;
 	}
 	if (m->mdmreg[12] & 1) {
 		/* Show results */
@@ -1938,17 +2364,38 @@
 			sprintf(s, "%d", code);
 			isdn_tty_at_cout(s, info);
 		} else {
-			if (code == 2) {
+			if ((code == 2) && (!(m->mdmreg[13] & 16))) {
 				isdn_tty_at_cout("CALLER NUMBER: ", info);
 				isdn_tty_at_cout(dev->num[info->drv_index], info);
 				isdn_tty_at_cout("\r\n", info);
 			}
 			isdn_tty_at_cout(msg[code], info);
-			if (code == 5) {
-				/* Append Protocol to CONNECT message */
-				isdn_tty_at_cout((m->mdmreg[14] != 3) ? "/X.75" : "/HDLC", info);
-				if (m->mdmreg[13] & 2)
-					isdn_tty_at_cout("/T.70", info);
+			switch (code) {
+				case 2:
+					/* Print CID only once, _after_ 1.st RING */
+					if ((m->mdmreg[13] & 16) && (m->mdmreg[1] == 1)) {
+						isdn_tty_at_cout("\r\n", info);
+						isdn_tty_at_cout("CALLER NUMBER: ", info);
+						isdn_tty_at_cout(dev->num[info->drv_index], info);
+					}
+					break;
+				case 3:
+				case 6:
+				case 7:
+				case 8:
+					m->mdmreg[1] = 0;
+					/* Append Cause-Message if enabled */
+					if (m->mdmreg[13] & 8) {
+						sprintf(s, "/%s", info->last_cause);
+						isdn_tty_at_cout(s, info);
+					}
+					break;
+				case 5:
+					/* Append Protocol to CONNECT message */
+					isdn_tty_at_cout((m->mdmreg[14] != 3) ? "/X.75" : "/HDLC", info);
+					if (m->mdmreg[13] & 2)
+						isdn_tty_at_cout("/T.70", info);
+					break;
 			}
 		}
 		isdn_tty_at_cout("\r\n", info);
@@ -1960,13 +2407,13 @@
 			restore_flags(flags);
 			return;
 		}
-                if (info->tty->ldisc.flush_buffer)
-                        info->tty->ldisc.flush_buffer(info->tty);
+		if (info->tty->ldisc.flush_buffer)
+			info->tty->ldisc.flush_buffer(info->tty);
 		if ((info->flags & ISDN_ASYNC_CHECK_CD) &&
 		    (!((info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) &&
 		       (info->flags & ISDN_ASYNC_CALLOUT_NOHUP)))) {
 			tty_hangup(info->tty);
-                }
+		}
 		restore_flags(flags);
 	}
 }
@@ -1974,7 +2421,8 @@
 /*
  * Display a modem-register-value.
  */
-static void isdn_tty_show_profile(int ridx, modem_info * info)
+static void
+isdn_tty_show_profile(int ridx, modem_info * info)
 {
 	char v[6];
 
@@ -1985,7 +2433,8 @@
 /*
  * Get MSN-string from char-pointer, set pointer to end of number
  */
-static void isdn_tty_get_msnstr(char *n, char **p)
+static void
+isdn_tty_get_msnstr(char *n, char **p)
 {
 	while ((*p[0] >= '0' && *p[0] <= '9') || (*p[0] == ','))
 		*n++ = *p[0]++;
@@ -1995,13 +2444,17 @@
 /*
  * Get phone-number from modem-commandbuffer
  */
-static void isdn_tty_getdial(char *p, char *q)
+static void
+isdn_tty_getdial(char *p, char *q, int max)
 {
 	int first = 1;
 
-	while (strchr("0123456789,#.*WPTS-", *p) && *p) {
-		if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first))
+	max--;
+	while (strchr("0123456789,#.*WPTS-", *p) && *p && (max > 0)) {
+		if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first)) {
 			*q++ = *p;
+			max--;
+		}
 		p++;
 		first = 0;
 	}
@@ -2011,648 +2464,775 @@
 #define PARSE_ERROR { isdn_tty_modem_result(4, info); return; }
 #define PARSE_ERROR1 { isdn_tty_modem_result(4, info); return 1; }
 
+static void
+isdn_tty_report(modem_info * info)
+{
+	atemu *m = &info->emu;
+	char s[80];
+
+	isdn_tty_at_cout("\r\nStatistics of last connection:\r\n\r\n", info);
+	sprintf(s, "    Remote Number:    %s\r\n", info->last_num);
+	isdn_tty_at_cout(s, info);
+	sprintf(s, "    Direction:        %s\r\n", info->last_dir ? "outgoing" : "incoming");
+	isdn_tty_at_cout(s, info);
+	isdn_tty_at_cout("    Layer-2 Protocol: ", info);
+	switch (info->last_l2) {
+		case ISDN_PROTO_L2_X75I:
+			isdn_tty_at_cout("x75i", info);
+			break;
+		case ISDN_PROTO_L2_X75UI:
+			isdn_tty_at_cout("x75ui", info);
+			break;
+		case ISDN_PROTO_L2_X75BUI:
+			isdn_tty_at_cout("x75bui", info);
+			break;
+		case ISDN_PROTO_L2_HDLC:
+			isdn_tty_at_cout("hdlc", info);
+			break;
+		case ISDN_PROTO_L2_TRANS:
+			isdn_tty_at_cout("transparent", info);
+			break;
+		default:
+			isdn_tty_at_cout("unknown", info);
+			break;
+	}
+	isdn_tty_at_cout((m->mdmreg[13] & 2) ? "/t.70\r\n" : "\r\n", info);
+	isdn_tty_at_cout("    Service:          ", info);
+	switch (info->last_si) {
+		case 1:
+			isdn_tty_at_cout("audio\r\n", info);
+			break;
+		case 5:
+			isdn_tty_at_cout("btx\r\n", info);
+			break;
+		case 7:
+			isdn_tty_at_cout("data\r\n", info);
+			break;
+		default:
+			sprintf(s, "%d\r\n", info->last_si);
+			isdn_tty_at_cout(s, info);
+			break;
+	}
+	sprintf(s, "    Hangup location:  %s\r\n", info->last_lhup ? "local" : "remote");
+	isdn_tty_at_cout(s, info);
+	sprintf(s, "    Last cause:       %s\r\n", info->last_cause);
+	isdn_tty_at_cout(s, info);
+}
+
 /*
  * Parse AT&.. commands.
  */
-static int isdn_tty_cmd_ATand(char **p, modem_info * info)
+static int
+isdn_tty_cmd_ATand(char **p, modem_info * info)
 {
-        atemu *m = &info->emu;
-        int i;
-        char rb[100];
-
-        switch (*p[0]) {
-                case 'B':
-                        /* &B - Set Buffersize */
-                        p[0]++;
-                        i = isdn_getnum(p);
-                        if ((i < 0) || (i > ISDN_SERIAL_XMIT_SIZE))
-                                PARSE_ERROR1;
-#ifdef CONFIG_ISDN_AUDIO
-                        if ((m->mdmreg[18] & 1) && (i > VBUF))
-                                PARSE_ERROR1;
-#endif
-                        m->mdmreg[16] = i / 16;
-                        info->xmit_size = m->mdmreg[16] * 16;
-                        break;
-                case 'D':
-                        /* &D - Set DCD-Low-behavior */
-                        p[0]++;
-                        switch (isdn_getnum(p)) {
-                                case 0:
-                                        m->mdmreg[13] &= ~4;
-                                        m->mdmreg[12] &= ~32;
-                                        break;
-                                case 2:
-                                        m->mdmreg[13] |= 4;
-                                        m->mdmreg[12] &= ~32;
-                                        break;
-                                case 3:
-                                        m->mdmreg[13] |= 4;
-                                        m->mdmreg[12] |= 32;
-                                        break;
-                                default:
-                                        PARSE_ERROR1
-                        }
-                        break;
-                case 'E':
-                        /* &E -Set EAZ/MSN */
-                        p[0]++;
-                        isdn_tty_get_msnstr(m->msn, p);
-                        break;
-                case 'F':
-                        /* &F -Set Factory-Defaults */
-                        p[0]++;
-                        isdn_tty_reset_profile(m);
-                        isdn_tty_modem_reset_regs(info, 1);
-                        break;
-                case 'S':
-                        /* &S - Set Windowsize */
-                        p[0]++;
-                        i = isdn_getnum(p);
-                        if ((i > 0) && (i < 9))
-                                m->mdmreg[17] = i;
-                        else
-                                PARSE_ERROR1;
-                        break;
-                case 'V':
-                        /* &V - Show registers */
-                        p[0]++;
-                        for (i = 0; i < ISDN_MODEM_ANZREG; i++) {
-                                sprintf(rb, "S%d=%d%s", i, 
-                                        m->mdmreg[i], (i == 6) ? "\r\n" : " ");
-                                isdn_tty_at_cout(rb, info);
-                        }
-                        sprintf(rb, "\r\nEAZ/MSN: %s\r\n",
-                                strlen(m->msn) ? m->msn : "None");
-                        isdn_tty_at_cout(rb, info);
-                        break;
-                case 'W':
-                        /* &W - Write Profile */
-                        p[0]++;
-                        switch (*p[0]) {
-                                case '0':
-                                        p[0]++;
-                                        modem_write_profile(m);
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                case 'X':
-                        /* &X - Switch to BTX-Mode */
-                        p[0]++;
-                        switch (isdn_getnum(p)) {
-                                case 0:
-                                        m->mdmreg[13] &= ~2;
-                                        info->xmit_size = m->mdmreg[16] * 16;
-                                        break;
-                                case 1:
-                                        m->mdmreg[13] |= 2;
-                                        m->mdmreg[14] = 0;
-                                        info->xmit_size = 112;
-                                        m->mdmreg[18] = 4;
-                                        m->mdmreg[19] = 0;
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                default:
-                        PARSE_ERROR1;
-        }
-        return 0;
+	atemu *m = &info->emu;
+	int i;
+	char rb[100];
+
+	switch (*p[0]) {
+		case 'B':
+			/* &B - Set Buffersize */
+			p[0]++;
+			i = isdn_getnum(p);
+			if ((i < 0) || (i > ISDN_SERIAL_XMIT_SIZE))
+				PARSE_ERROR1;
+#ifdef CONFIG_ISDN_AUDIO
+			if ((m->mdmreg[18] & 1) && (i > VBUF))
+				PARSE_ERROR1;
+#endif
+			m->mdmreg[16] = i / 16;
+			info->xmit_size = m->mdmreg[16] * 16;
+			break;
+		case 'D':
+			/* &D - Set DCD-Low-behavior */
+			p[0]++;
+			switch (isdn_getnum(p)) {
+				case 0:
+					m->mdmreg[13] &= ~4;
+					m->mdmreg[12] &= ~32;
+					break;
+				case 2:
+					m->mdmreg[13] |= 4;
+					m->mdmreg[12] &= ~32;
+					break;
+				case 3:
+					m->mdmreg[13] |= 4;
+					m->mdmreg[12] |= 32;
+					break;
+				default:
+					PARSE_ERROR1
+			}
+			break;
+		case 'E':
+			/* &E -Set EAZ/MSN */
+			p[0]++;
+			isdn_tty_get_msnstr(m->msn, p);
+			break;
+		case 'F':
+			/* &F -Set Factory-Defaults */
+			p[0]++;
+			isdn_tty_reset_profile(m);
+			isdn_tty_modem_reset_regs(info, 1);
+			break;
+		case 'S':
+			/* &S - Set Windowsize */
+			p[0]++;
+			i = isdn_getnum(p);
+			if ((i > 0) && (i < 9))
+				m->mdmreg[17] = i;
+			else
+				PARSE_ERROR1;
+			break;
+		case 'V':
+			/* &V - Show registers */
+			p[0]++;
+			isdn_tty_at_cout("\r\n", info);
+			for (i = 0; i < ISDN_MODEM_ANZREG; i++) {
+				sprintf(rb, "S%02d=%03d%s", i,
+					m->mdmreg[i], ((i + 1) % 10) ? " " : "\r\n");
+				isdn_tty_at_cout(rb, info);
+			}
+			sprintf(rb, "\r\nEAZ/MSN: %s\r\n",
+				strlen(m->msn) ? m->msn : "None");
+			isdn_tty_at_cout(rb, info);
+			break;
+		case 'W':
+			/* &W - Write Profile */
+			p[0]++;
+			switch (*p[0]) {
+				case '0':
+					p[0]++;
+					modem_write_profile(m);
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case 'X':
+			/* &X - Switch to BTX-Mode */
+			p[0]++;
+			switch (isdn_getnum(p)) {
+				case 0:
+					m->mdmreg[13] &= ~2;
+					info->xmit_size = m->mdmreg[16] * 16;
+					break;
+				case 1:
+					m->mdmreg[13] |= 2;
+					m->mdmreg[14] = 0;
+					info->xmit_size = 112;
+					m->mdmreg[18] = 4;
+					m->mdmreg[19] = 0;
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		default:
+			PARSE_ERROR1;
+	}
+	return 0;
+}
+
+static int
+isdn_tty_check_ats(int mreg, int mval, modem_info * info, atemu * m)
+{
+	/* Some plausibility checks */
+	switch (mreg) {
+		case 14:
+			if (mval > ISDN_PROTO_L2_TRANS)
+				return 1;
+			break;
+		case 16:
+			if ((mval * 16) > ISDN_SERIAL_XMIT_SIZE)
+				return 1;
+#ifdef CONFIG_ISDN_AUDIO
+			if ((m->mdmreg[18] & 1) && (mval > VBUFX))
+				return 1;
+#endif
+			info->xmit_size = mval * 16;
+			break;
+		case 20:
+		case 21:
+		case 22:
+			/* readonly registers */
+			return 1;
+	}
+	return 0;
 }
 
 /*
  * Perform ATS command
  */
-static int isdn_tty_cmd_ATS(char **p, modem_info * info)
+static int
+isdn_tty_cmd_ATS(char **p, modem_info * info)
 {
-        atemu *m = &info->emu;
-        int mreg;
-        int mval;
-
-        mreg = isdn_getnum(p);
-        if (mreg < 0 || mreg > ISDN_MODEM_ANZREG)
-                PARSE_ERROR1;
-        switch (*p[0]) {
-                case '=':
-                        p[0]++;
-                        mval = isdn_getnum(p);
-                        if (mval < 0 || mval > 255)
-                                PARSE_ERROR1;
-                        switch (mreg) {
-                                /* Some plausibility checks */
-                                case 14:
-                                        if (mval > ISDN_PROTO_L2_TRANS)
-                                                PARSE_ERROR1;
-                                        break;
-                                case 16:
-                                        if ((mval * 16) > ISDN_SERIAL_XMIT_SIZE)
-                                                PARSE_ERROR1;
-#ifdef CONFIG_ISDN_AUDIO
-                                        if ((m->mdmreg[18] & 1) && (mval > VBUFX))
-                                                PARSE_ERROR1;
-#endif
-                                        info->xmit_size = mval * 16;
-                                        break;
-                                case 20:
-                                        PARSE_ERROR1;
-                        }
-                        m->mdmreg[mreg] = mval;
-                        break;
-                case '?':
-                        p[0]++;
-                        isdn_tty_show_profile(mreg, info);
-                        break;
-                default:
-                        PARSE_ERROR1;
-                        break;
-        }
-        return 0;
+	atemu *m = &info->emu;
+	int bitpos;
+	int mreg;
+	int mval;
+	int bval;
+
+	mreg = isdn_getnum(p);
+	if (mreg < 0 || mreg > ISDN_MODEM_ANZREG)
+		PARSE_ERROR1;
+	switch (*p[0]) {
+		case '=':
+			p[0]++;
+			mval = isdn_getnum(p);
+			if (mval < 0 || mval > 255)
+				PARSE_ERROR1;
+			if (isdn_tty_check_ats(mreg, mval, info, m))
+				PARSE_ERROR1;
+			m->mdmreg[mreg] = mval;
+			break;
+		case '.':
+			/* Set/Clear a single bit */
+			p[0]++;
+			bitpos = isdn_getnum(p);
+			if ((bitpos < 0) || (bitpos > 7))
+				PARSE_ERROR1;
+			switch (*p[0]) {
+				case '=':
+					p[0]++;
+					bval = isdn_getnum(p);
+					if (bval < 0 || bval > 1)
+						PARSE_ERROR1;
+					if (bval)
+						mval = m->mdmreg[mreg] | (1 << bitpos);
+					else
+						mval = m->mdmreg[mreg] & ~(1 << bitpos);
+					if (isdn_tty_check_ats(mreg, mval, info, m))
+						PARSE_ERROR1;
+					m->mdmreg[mreg] = mval;
+					break;
+				case '?':
+					p[0]++;
+					isdn_tty_at_cout("\r\n", info);
+					isdn_tty_at_cout((m->mdmreg[mreg] & (1 << bitpos)) ? "1" : "0",
+							 info);
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case '?':
+			p[0]++;
+			isdn_tty_show_profile(mreg, info);
+			break;
+		default:
+			PARSE_ERROR1;
+			break;
+	}
+	return 0;
 }
 
 /*
  * Perform ATA command
  */
-static void isdn_tty_cmd_ATA(modem_info * info)
+static void
+isdn_tty_cmd_ATA(modem_info * info)
 {
-        atemu *m = &info->emu;
-        isdn_ctrl cmd;
-        int l2;
-
-        if (info->msr & UART_MSR_RI) {
-                /* Accept incoming call */
-                m->mdmreg[1] = 0;
-                info->msr &= ~UART_MSR_RI;
-                l2 = m->mdmreg[14];
-#ifdef CONFIG_ISDN_AUDIO
-                /* If more than one bit set in reg18, autoselect Layer2 */
-                if ((m->mdmreg[18] & m->mdmreg[20]) != m->mdmreg[18])
-                        if (m->mdmreg[20] == 1) l2 = 4;
-#endif
-                cmd.driver = info->isdn_driver;
-                cmd.command = ISDN_CMD_SETL2;
-                cmd.arg = info->isdn_channel + (l2 << 8);
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                cmd.driver = info->isdn_driver;
-                cmd.command = ISDN_CMD_SETL3;
-                cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8);
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-                cmd.driver = info->isdn_driver;
-                cmd.arg = info->isdn_channel;
-                cmd.command = ISDN_CMD_ACCEPTD;
-                dev->drv[info->isdn_driver]->interface->command(&cmd);
-        } else
-                isdn_tty_modem_result(8, info);
+	atemu *m = &info->emu;
+	isdn_ctrl cmd;
+	int l2;
+
+	if (info->msr & UART_MSR_RI) {
+		/* Accept incoming call */
+		info->last_dir = 0;
+		strcpy(info->last_num, dev->num[info->drv_index]);
+		m->mdmreg[1] = 0;
+		info->msr &= ~UART_MSR_RI;
+		l2 = m->mdmreg[14];
+#ifdef CONFIG_ISDN_AUDIO
+		/* If more than one bit set in reg18, autoselect Layer2 */
+		if ((m->mdmreg[18] & m->mdmreg[20]) != m->mdmreg[18]) {
+			if (m->mdmreg[20] == 1)
+				l2 = 4;
+			else
+				l2 = 0;
+		}
+#endif
+		cmd.driver = info->isdn_driver;
+		cmd.command = ISDN_CMD_SETL2;
+		cmd.arg = info->isdn_channel + (l2 << 8);
+		info->last_l2 = l2;
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		cmd.driver = info->isdn_driver;
+		cmd.command = ISDN_CMD_SETL3;
+		cmd.arg = info->isdn_channel + (m->mdmreg[15] << 8);
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+		cmd.driver = info->isdn_driver;
+		cmd.arg = info->isdn_channel;
+		cmd.command = ISDN_CMD_ACCEPTD;
+		dev->drv[info->isdn_driver]->interface->command(&cmd);
+	} else
+		isdn_tty_modem_result(8, info);
 }
 
 #ifdef CONFIG_ISDN_AUDIO
 /*
  * Parse AT+F.. commands
  */
-static int isdn_tty_cmd_PLUSF(char **p, modem_info * info)
+static int
+isdn_tty_cmd_PLUSF(char **p, modem_info * info)
 {
-        atemu *m = &info->emu;
-        int par;
+	atemu *m = &info->emu;
+	int par;
 	char rs[20];
 
-        if (!strncmp(p[0],"CLASS",5)) {
-                p[0] += 5;
-                switch (*p[0]) {
-                        case '?':
-                                p[0]++;
-                                sprintf(rs,"\r\n%d",
-                                        (m->mdmreg[18]&1)?8:0);
-                                isdn_tty_at_cout(rs, info);
-                                break;
-                        case '=':
-                                p[0]++;
-                                switch (*p[0]) {
-                                        case '0':
-                                                p[0]++;
-                                                m->mdmreg[18] = 4;
-                                                info->xmit_size =
-                                                        m->mdmreg[16] * 16;
-                                                break;
-                                        case '8':
-                                                p[0]++;
-                                                m->mdmreg[18] = 5;
-                                                info->xmit_size = VBUF;
-                                                break;
-                                        case '?':
-                                                p[0]++;
-                                                isdn_tty_at_cout("\r\n0,8",
-                                                                 info);
-                                                break;
-                                        default:
-                                                PARSE_ERROR1;
-                                }
-                                break;
-                        default:
-                                PARSE_ERROR1;
-                }
-                return 0;
-        }        
-        if (!strncmp(p[0],"AA",2)) {
-                p[0] += 2;
-                switch (*p[0]) {
-                        case '?':
-                                p[0]++;
-                                sprintf(rs,"\r\n%d",
-                                        m->mdmreg[0]);
-                                isdn_tty_at_cout(rs, info);
-                                break;
-                        case '=':
-                                p[0]++;
-                                par = isdn_getnum(p);
-                                if ((par < 0) || (par > 255))
-                                        PARSE_ERROR1;
-                                m->mdmreg[0]=par;
-                                break;
-                        default:
-                                PARSE_ERROR1;                                
-                }
-                return 0;
-        }
-        PARSE_ERROR1;
+	if (!strncmp(p[0], "CLASS", 5)) {
+		p[0] += 5;
+		switch (*p[0]) {
+			case '?':
+				p[0]++;
+				sprintf(rs, "\r\n%d",
+					(m->mdmreg[18] & 1) ? 8 : 0);
+				isdn_tty_at_cout(rs, info);
+				break;
+			case '=':
+				p[0]++;
+				switch (*p[0]) {
+					case '0':
+						p[0]++;
+						m->mdmreg[18] = 4;
+						info->xmit_size =
+						    m->mdmreg[16] * 16;
+						break;
+					case '8':
+						p[0]++;
+						m->mdmreg[18] = 5;
+						info->xmit_size = VBUF;
+						break;
+					case '?':
+						p[0]++;
+						isdn_tty_at_cout("\r\n0,8",
+								 info);
+						break;
+					default:
+						PARSE_ERROR1;
+				}
+				break;
+			default:
+				PARSE_ERROR1;
+		}
+		return 0;
+	}
+	if (!strncmp(p[0], "AA", 2)) {
+		p[0] += 2;
+		switch (*p[0]) {
+			case '?':
+				p[0]++;
+				sprintf(rs, "\r\n%d",
+					m->mdmreg[0]);
+				isdn_tty_at_cout(rs, info);
+				break;
+			case '=':
+				p[0]++;
+				par = isdn_getnum(p);
+				if ((par < 0) || (par > 255))
+					PARSE_ERROR1;
+				m->mdmreg[0] = par;
+				break;
+			default:
+				PARSE_ERROR1;
+		}
+		return 0;
+	}
+	PARSE_ERROR1;
 }
 
 /*
  * Parse AT+V.. commands
  */
-static int isdn_tty_cmd_PLUSV(char **p, modem_info * info)
+static int
+isdn_tty_cmd_PLUSV(char **p, modem_info * info)
 {
-        atemu *m = &info->emu;
-        static char *vcmd[] = {"NH","IP","LS","RX","SD","SM","TX",NULL};
-        int i;
+	atemu *m = &info->emu;
+	static char *vcmd[] =
+	{"NH", "IP", "LS", "RX", "SD", "SM", "TX", NULL};
+	int i;
 	int par1;
 	int par2;
 	char rs[20];
 
-        i = 0;
-        while (vcmd[i]) {
-                if (!strncmp(vcmd[i],p[0],2)) {
-                        p[0] += 2;
-                        break;
-                }
-                i++;
-        }
-        switch (i) {
-                case 0:
-                        /* AT+VNH - Auto hangup feature */
-                        switch (*p[0]) {
-                                case '?':
-                                        p[0]++;
-                                        isdn_tty_at_cout("\r\n1", info);
-                                        break;
-                                case '=':
-                                        p[0]++;
-                                        switch (*p[0]) {
-                                                case '1':
-                                                        p[0]++;
-                                                        break;
-                                                case '?':
-                                                        p[0]++;
-                                                        isdn_tty_at_cout("\r\n1", info);
-                                                        break;
-                                                default:
-                                                        PARSE_ERROR1;
-                                        }
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                case 1:
-                        /* AT+VIP - Reset all voice parameters */
-                        isdn_tty_modem_reset_vpar(m);
-                        break;
-                case 2:
-                        /* AT+VLS - Select device, accept incoming call */
-                        switch (*p[0]) {
-                                case '?':
-                                        p[0]++;
-                                        sprintf(rs,"\r\n%d",m->vpar[0]);
-                                        isdn_tty_at_cout(rs, info);
-                                        break;
-                                case '=':
-                                        p[0]++;
-                                        switch (*p[0]) {
-                                                case '0':
-                                                        p[0]++;
-                                                        m->vpar[0] = 0;
-                                                        break;
-                                                case '2':
-                                                        p[0]++;
-                                                        m->vpar[0] = 2;
-                                                        break;
-                                                case '?':
-                                                        p[0]++;
-                                                        isdn_tty_at_cout("\r\n0,2", info);
-                                                        break;
-                                                default:
-                                                        PARSE_ERROR1;
-                                        }
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                case 3:
-                        /* AT+VRX - Start recording */
-                        if (!m->vpar[0])
-                                PARSE_ERROR1;
-                        if (info->online != 1) {
-                                isdn_tty_modem_result(8, info);
-                                return 1;
-                        }
-                        info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state);
-                        if (!info->dtmf_state) {
-                                printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n");
-                                PARSE_ERROR1;
-                        }
-                        if (m->vpar[3] < 5) {
-                                info->adpcmr = isdn_audio_adpcm_init(info->adpcmr, m->vpar[3]);
-                                if (!info->adpcmr) {
-                                        printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
-                                        PARSE_ERROR1;
-                                }
-                        }
-                        info->vonline = 1;
-                        isdn_tty_modem_result(1, info);
-                        return 1;
-                        break;
-                case 4:
-                        /* AT+VSD - Silence detection */
-                        switch (*p[0]) {
-                                case '?':
-                                        p[0]++;
-                                        sprintf(rs,"\r\n<%d>,<%d>",
-                                                m->vpar[1],
-                                                m->vpar[2]);
-                                        isdn_tty_at_cout(rs, info);
-                                        break;
-                                case '=':
-                                        p[0]++;
-                                        switch (*p[0]) {
-                                                case '0':
-                                                case '1':
-                                                case '2':
-                                                case '3':
-                                                        par1 = isdn_getnum(p);
-                                                        if ((par1 < 0) || (par1 > 31))
-                                                                PARSE_ERROR1;
-                                                        if (*p[0] != ',')
-                                                                PARSE_ERROR1;
-                                                        p[0]++;
-                                                        par2 = isdn_getnum(p);
-                                                        if ((par2 < 0) || (par2 > 255))
-                                                                PARSE_ERROR1;
-                                                        m->vpar[1] = par1;
-                                                        m->vpar[2] = par2;
-                                                        break;
-                                                case '?':
-                                                        p[0]++;
-                                                        isdn_tty_at_cout("\r\n<0-31>,<0-255>",
-                                                                         info);
-                                                        break;
-                                                default:
-                                                        PARSE_ERROR1;
-                                        }
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                case 5:
-                        /* AT+VSM - Select compression */
-                        switch (*p[0]) {
-                                case '?':
-                                        p[0]++;
-                                        sprintf(rs,"\r\n<%d>,<%d><8000>",
-                                                m->vpar[3],
-                                                m->vpar[1]);
-                                        isdn_tty_at_cout(rs, info);
-                                        break;
-                                case '=':
-                                        p[0]++;
-                                        switch (*p[0]) {
-                                                case '2':
-                                                case '3':
-                                                case '4':
-                                                case '5':
-                                                case '6':
-                                                        par1 = isdn_getnum(p);
-                                                        if ((par1 < 2) || (par1 > 6))
-                                                                PARSE_ERROR1;
-                                                        m->vpar[3] = par1;
-                                                        break;
-                                                case '?':
-                                                        p[0]++;
-                                                        isdn_tty_at_cout("\r\n2;ADPCM;2;0;(8000)\r\n",
-                                                                         info);
-                                                        isdn_tty_at_cout("3;ADPCM;3;0;(8000)\r\n",
-                                                                         info);
-                                                        isdn_tty_at_cout("4;ADPCM;4;0;(8000)\r\n",
-                                                                         info);
-                                                        isdn_tty_at_cout("5;ALAW;8;0;(8000)",
-                                                                         info);
-                                                        isdn_tty_at_cout("6;ULAW;8;0;(8000)",
-                                                                         info);
-                                                        break;
-                                                default:
-                                                        PARSE_ERROR1;
-                                        }
-                                        break;
-                                default:
-                                        PARSE_ERROR1;
-                        }
-                        break;
-                case 6:
-                        /* AT+VTX - Start sending */
-                        if (!m->vpar[0])
-                                PARSE_ERROR1;
-                        if (info->online != 1) {
-                                isdn_tty_modem_result(8, info);
-                                return 1;
-                        }
-                        info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state);
-                        if (!info->dtmf_state) {
-                                printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n");
-                                PARSE_ERROR1;
-                        }
-                        if (m->vpar[3] < 5) {
-                                info->adpcms = isdn_audio_adpcm_init(info->adpcms, m->vpar[3]);
-                                if (!info->adpcms) {
-                                        printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
-                                        PARSE_ERROR1;
-                                }
-                        }
-                        m->lastDLE = 0;
-                        info->vonline = 2;
-                        isdn_tty_modem_result(1, info);
-                        return 1;
-                        break;
-                default:
-                        PARSE_ERROR1;
-        }
-        return 0;
+	i = 0;
+	while (vcmd[i]) {
+		if (!strncmp(vcmd[i], p[0], 2)) {
+			p[0] += 2;
+			break;
+		}
+		i++;
+	}
+	switch (i) {
+		case 0:
+			/* AT+VNH - Auto hangup feature */
+			switch (*p[0]) {
+				case '?':
+					p[0]++;
+					isdn_tty_at_cout("\r\n1", info);
+					break;
+				case '=':
+					p[0]++;
+					switch (*p[0]) {
+						case '1':
+							p[0]++;
+							break;
+						case '?':
+							p[0]++;
+							isdn_tty_at_cout("\r\n1", info);
+							break;
+						default:
+							PARSE_ERROR1;
+					}
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case 1:
+			/* AT+VIP - Reset all voice parameters */
+			isdn_tty_modem_reset_vpar(m);
+			break;
+		case 2:
+			/* AT+VLS - Select device, accept incoming call */
+			switch (*p[0]) {
+				case '?':
+					p[0]++;
+					sprintf(rs, "\r\n%d", m->vpar[0]);
+					isdn_tty_at_cout(rs, info);
+					break;
+				case '=':
+					p[0]++;
+					switch (*p[0]) {
+						case '0':
+							p[0]++;
+							m->vpar[0] = 0;
+							break;
+						case '2':
+							p[0]++;
+							m->vpar[0] = 2;
+							break;
+						case '?':
+							p[0]++;
+							isdn_tty_at_cout("\r\n0,2", info);
+							break;
+						default:
+							PARSE_ERROR1;
+					}
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case 3:
+			/* AT+VRX - Start recording */
+			if (!m->vpar[0])
+				PARSE_ERROR1;
+			if (info->online != 1) {
+				isdn_tty_modem_result(8, info);
+				return 1;
+			}
+			info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state);
+			if (!info->dtmf_state) {
+				printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n");
+				PARSE_ERROR1;
+			}
+			if (m->vpar[3] < 5) {
+				info->adpcmr = isdn_audio_adpcm_init(info->adpcmr, m->vpar[3]);
+				if (!info->adpcmr) {
+					printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
+					PARSE_ERROR1;
+				}
+			}
+#ifdef ISDN_DEBUG_AT
+			printk(KERN_DEBUG "AT: +VRX\n");
+#endif
+			info->vonline |= 1;
+			isdn_tty_modem_result(1, info);
+			return 0;
+			break;
+		case 4:
+			/* AT+VSD - Silence detection */
+			switch (*p[0]) {
+				case '?':
+					p[0]++;
+					sprintf(rs, "\r\n<%d>,<%d>",
+						m->vpar[1],
+						m->vpar[2]);
+					isdn_tty_at_cout(rs, info);
+					break;
+				case '=':
+					p[0]++;
+					switch (*p[0]) {
+						case '0':
+						case '1':
+						case '2':
+						case '3':
+							par1 = isdn_getnum(p);
+							if ((par1 < 0) || (par1 > 31))
+								PARSE_ERROR1;
+							if (*p[0] != ',')
+								PARSE_ERROR1;
+							p[0]++;
+							par2 = isdn_getnum(p);
+							if ((par2 < 0) || (par2 > 255))
+								PARSE_ERROR1;
+							m->vpar[1] = par1;
+							m->vpar[2] = par2;
+							break;
+						case '?':
+							p[0]++;
+							isdn_tty_at_cout("\r\n<0-31>,<0-255>",
+								   info);
+							break;
+						default:
+							PARSE_ERROR1;
+					}
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case 5:
+			/* AT+VSM - Select compression */
+			switch (*p[0]) {
+				case '?':
+					p[0]++;
+					sprintf(rs, "\r\n<%d>,<%d><8000>",
+						m->vpar[3],
+						m->vpar[1]);
+					isdn_tty_at_cout(rs, info);
+					break;
+				case '=':
+					p[0]++;
+					switch (*p[0]) {
+						case '2':
+						case '3':
+						case '4':
+						case '5':
+						case '6':
+							par1 = isdn_getnum(p);
+							if ((par1 < 2) || (par1 > 6))
+								PARSE_ERROR1;
+							m->vpar[3] = par1;
+							break;
+						case '?':
+							p[0]++;
+							isdn_tty_at_cout("\r\n2;ADPCM;2;0;(8000)\r\n",
+								   info);
+							isdn_tty_at_cout("3;ADPCM;3;0;(8000)\r\n",
+								   info);
+							isdn_tty_at_cout("4;ADPCM;4;0;(8000)\r\n",
+								   info);
+							isdn_tty_at_cout("5;ALAW;8;0;(8000)",
+								   info);
+							isdn_tty_at_cout("6;ULAW;8;0;(8000)",
+								   info);
+							break;
+						default:
+							PARSE_ERROR1;
+					}
+					break;
+				default:
+					PARSE_ERROR1;
+			}
+			break;
+		case 6:
+			/* AT+VTX - Start sending */
+			if (!m->vpar[0])
+				PARSE_ERROR1;
+			if (info->online != 1) {
+				isdn_tty_modem_result(8, info);
+				return 1;
+			}
+			info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state);
+			if (!info->dtmf_state) {
+				printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n");
+				PARSE_ERROR1;
+			}
+			if (m->vpar[3] < 5) {
+				info->adpcms = isdn_audio_adpcm_init(info->adpcms, m->vpar[3]);
+				if (!info->adpcms) {
+					printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n");
+					PARSE_ERROR1;
+				}
+			}
+#ifdef ISDN_DEBUG_AT
+			printk(KERN_DEBUG "AT: +VTX\n");
+#endif
+			m->lastDLE = 0;
+			info->vonline |= 2;
+			isdn_tty_modem_result(1, info);
+			return 0;
+			break;
+		default:
+			PARSE_ERROR1;
+	}
+	return 0;
 }
-#endif        /* CONFIG_ISDN_AUDIO */
+#endif                          /* CONFIG_ISDN_AUDIO */
 
 /*
  * Parse and perform an AT-command-line.
  */
-static void isdn_tty_parse_at(modem_info * info)
+static void
+isdn_tty_parse_at(modem_info * info)
 {
-        atemu *m = &info->emu;
-        char *p;
-        char ds[40];
+	atemu *m = &info->emu;
+	char *p;
+	char ds[40];
 
 #ifdef ISDN_DEBUG_AT
-        printk(KERN_DEBUG "AT: '%s'\n", m->mdmcmd);
+	printk(KERN_DEBUG "AT: '%s'\n", m->mdmcmd);
+#endif
+	for (p = &m->mdmcmd[2]; *p;) {
+		switch (*p) {
+			case 'A':
+				/* A - Accept incoming call */
+				p++;
+				isdn_tty_cmd_ATA(info);
+				return;
+				break;
+			case 'D':
+				/* D - Dial */
+				isdn_tty_getdial(++p, ds, sizeof(ds));
+				p += strlen(p);
+				if (!strlen(m->msn))
+					isdn_tty_modem_result(10, info);
+				else if (strlen(ds))
+					isdn_tty_dial(ds, info, m);
+				else
+					PARSE_ERROR;
+				return;
+			case 'E':
+				/* E - Turn Echo on/off */
+				p++;
+				switch (isdn_getnum(&p)) {
+					case 0:
+						m->mdmreg[12] &= ~4;
+						break;
+					case 1:
+						m->mdmreg[12] |= 4;
+						break;
+					default:
+						PARSE_ERROR;
+				}
+				break;
+			case 'H':
+				/* H - On/Off-hook */
+				p++;
+				switch (*p) {
+					case '0':
+						p++;
+						isdn_tty_on_hook(info);
+						break;
+					case '1':
+						p++;
+						isdn_tty_off_hook();
+						break;
+					default:
+						isdn_tty_on_hook(info);
+						break;
+				}
+				break;
+			case 'I':
+				/* I - Information */
+				p++;
+				isdn_tty_at_cout("\r\nLinux ISDN", info);
+				switch (*p) {
+					case '0':
+					case '1':
+						p++;
+						break;
+					case '2':
+						p++;
+						isdn_tty_report(info);
+						break;
+					default:
+				}
+				break;
+			case 'O':
+				/* O - Go online */
+				p++;
+				if (info->msr & UART_MSR_DCD)
+					/* if B-Channel is up */
+					isdn_tty_modem_result(5, info);
+				else
+					isdn_tty_modem_result(3, info);
+				return;
+			case 'Q':
+				/* Q - Turn Emulator messages on/off */
+				p++;
+				switch (isdn_getnum(&p)) {
+					case 0:
+						m->mdmreg[12] |= 1;
+						break;
+					case 1:
+						m->mdmreg[12] &= ~1;
+						break;
+					default:
+						PARSE_ERROR;
+				}
+				break;
+			case 'S':
+				/* S - Set/Get Register */
+				p++;
+				if (isdn_tty_cmd_ATS(&p, info))
+					return;
+				break;
+			case 'V':
+				/* V - Numeric or ASCII Emulator-messages */
+				p++;
+				switch (isdn_getnum(&p)) {
+					case 0:
+						m->mdmreg[12] |= 2;
+						break;
+					case 1:
+						m->mdmreg[12] &= ~2;
+						break;
+					default:
+						PARSE_ERROR;
+				}
+				break;
+			case 'Z':
+				/* Z - Load Registers from Profile */
+				p++;
+				isdn_tty_modem_reset_regs(info, 1);
+				break;
+#ifdef CONFIG_ISDN_AUDIO
+			case '+':
+				p++;
+				switch (*p) {
+					case 'F':
+						p++;
+						if (isdn_tty_cmd_PLUSF(&p, info))
+							return;
+						break;
+					case 'V':
+						if (!(m->mdmreg[18] & 1))
+							PARSE_ERROR;
+						p++;
+						if (isdn_tty_cmd_PLUSV(&p, info))
+							return;
+						break;
+					default:
+						PARSE_ERROR;
+				}
+				break;
+#endif                          /* CONFIG_ISDN_AUDIO */
+			case '&':
+				p++;
+				if (isdn_tty_cmd_ATand(&p, info))
+					return;
+				break;
+			default:
+				PARSE_ERROR;
+		}
+	}
+#ifdef CONFIG_ISDN_AUDIO
+	if (!info->vonline)
 #endif
-        for (p = &m->mdmcmd[2]; *p;) {
-                switch (*p) {
-                        case 'A':
-                                /* A - Accept incoming call */
-                                p++;
-                                isdn_tty_cmd_ATA(info);
-                                return;
-                                break;
-                        case 'D':
-                                /* D - Dial */
-                                isdn_tty_getdial(++p, ds);
-                                p += strlen(p);
-                                if (!strlen(m->msn))
-                                        isdn_tty_modem_result(10, info);
-                                else if (strlen(ds))
-                                        isdn_tty_dial(ds, info, m);
-                                else
-                                        isdn_tty_modem_result(4, info);
-                                return;
-                        case 'E':
-                                /* E - Turn Echo on/off */
-                                p++;
-                                switch (isdn_getnum(&p)) {
-                                        case 0:
-                                                m->mdmreg[12] &= ~4;
-                                                break;
-                                        case 1:
-                                                m->mdmreg[12] |= 4;
-                                                break;
-                                        default:
-                                                PARSE_ERROR;
-                                }
-                                break;
-                        case 'H':
-                                /* H - On/Off-hook */
-                                p++;
-                                switch (*p) {
-                                        case '0':
-                                                p++;
-                                                isdn_tty_on_hook(info);
-                                                break;
-                                        case '1':
-                                                p++;
-                                                isdn_tty_off_hook();
-                                                break;
-                                        default:
-                                                isdn_tty_on_hook(info);
-                                                break;
-                                }
-                                break;
-                        case 'I':
-                                /* I - Information */
-                                p++;
-                                isdn_tty_at_cout("\r\nLinux ISDN", info);
-                                switch (*p) {
-                                        case '0':
-                                        case '1':
-                                                p++;
-                                                break;
-                                        default:
-                                }
-                                break;
-                        case 'O':
-                                /* O - Go online */
-                                p++;
-                                if (info->msr & UART_MSR_DCD)
-                                        /* if B-Channel is up */
-                                        isdn_tty_modem_result(5, info);
-                                else
-                                        isdn_tty_modem_result(3, info);
-                                return;
-                        case 'Q':
-                                /* Q - Turn Emulator messages on/off */
-                                p++;
-                                switch (isdn_getnum(&p)) {
-                                        case 0:
-                                                m->mdmreg[12] |= 1;
-                                                break;
-                                        case 1:
-                                                m->mdmreg[12] &= ~1;
-                                                break;
-                                        default:
-                                                PARSE_ERROR;
-                                }
-                                break;
-                        case 'S':
-                                /* S - Set/Get Register */
-                                p++;
-                                if (isdn_tty_cmd_ATS(&p, info))
-                                        return;
-                                break;
-                        case 'V':
-                                /* V - Numeric or ASCII Emulator-messages */
-                                p++;
-                                switch (isdn_getnum(&p)) {
-                                        case 0:
-                                                m->mdmreg[12] |= 2;
-                                                break;
-                                        case 1:
-                                                m->mdmreg[12] &= ~2;
-                                                break;
-                                        default:
-                                                PARSE_ERROR;
-                                }
-                                break;
-                        case 'Z':
-                                /* Z - Load Registers from Profile */
-                                p++;
-                                isdn_tty_modem_reset_regs(info, 1);
-                                break;
-#ifdef CONFIG_ISDN_AUDIO
-                        case '+':
-                                p++;
-                                switch (*p) {
-                                        case 'F':
-                                                p++;
-                                                if (isdn_tty_cmd_PLUSF(&p, info))
-                                                        return;
-                                                break;
-                                        case 'V':
-                                                if (!(m->mdmreg[18] & 1))
-                                                        PARSE_ERROR;
-                                                p++;
-                                                if (isdn_tty_cmd_PLUSV(&p, info))
-                                                        return;
-                                                break;
-                                }
-                                break;
-#endif        /* CONFIG_ISDN_AUDIO */
-                        case '&':
-                                p++;
-                                if (isdn_tty_cmd_ATand(&p, info))
-                                        return;
-                                break;
-                        default:
-                                isdn_tty_modem_result(4, info);
-                                return;
-                }
-        }
-        isdn_tty_modem_result(0, info);
+		isdn_tty_modem_result(0, info);
 }
 
 /* Need own toupper() because standard-toupper is not available
@@ -2669,7 +3249,8 @@
  *   channel  index to line (minor-device)
  *   user     flag: buffer is in userspace
  */
-static int isdn_tty_edit_at(const char *p, int count, modem_info * info, int user)
+static int
+isdn_tty_edit_at(const char *p, int count, modem_info * info, int user)
 {
 	atemu *m = &info->emu;
 	int total = 0;
@@ -2715,16 +3296,16 @@
 			if (m->mdmcmdl < 255) {
 				c = my_toupper(c);
 				switch (m->mdmcmdl) {
-                                        case 0:
-                                                if (c == 'A')
-                                                        m->mdmcmd[m->mdmcmdl++] = c;
-                                                break;
-                                        case 1:
-                                                if (c == 'T')
-                                                        m->mdmcmd[m->mdmcmdl++] = c;
-                                                break;
-                                        default:
-                                                m->mdmcmd[m->mdmcmdl++] = c;
+					case 0:
+						if (c == 'A')
+							m->mdmcmd[m->mdmcmdl++] = c;
+						break;
+					case 1:
+						if (c == 'T')
+							m->mdmcmd[m->mdmcmdl++] = c;
+						break;
+					default:
+						m->mdmcmd[m->mdmcmdl++] = c;
 				}
 			}
 		}
@@ -2735,10 +3316,11 @@
 /*
  * Switch all modem-channels who are online and got a valid
  * escape-sequence 1.5 seconds ago, to command-mode.
- * This function is called every second via timer-interrupt from within 
+ * This function is called every second via timer-interrupt from within
  * timer-dispatcher isdn_timer_function()
  */
-void isdn_tty_modem_escape(void)
+void
+isdn_tty_modem_escape(void)
 {
 	int ton = 0;
 	int i;
@@ -2747,7 +3329,7 @@
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++)
 		if (USG_MODEM(dev->usage[i]))
 			if ((midx = dev->m_idx[i]) >= 0) {
-                                modem_info *info = &dev->mdm.info[midx];
+				modem_info *info = &dev->mdm.info[midx];
 				if (info->online) {
 					ton = 1;
 					if ((info->emu.pluscount == 3) &&
@@ -2757,27 +3339,28 @@
 						isdn_tty_modem_result(0, info);
 					}
 				}
-                        }
+			}
 	isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, ton);
 }
 
 /*
  * Put a RING-message to all modem-channels who have the RI-bit set.
- * This function is called every second via timer-interrupt from within 
+ * This function is called every second via timer-interrupt from within
  * timer-dispatcher isdn_timer_function()
  */
-void isdn_tty_modem_ring(void)
+void
+isdn_tty_modem_ring(void)
 {
 	int ton = 0;
 	int i;
 
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
-                modem_info *info = &dev->mdm.info[i];
-                if (info->msr & UART_MSR_RI) {
-                        ton = 1;
-                        isdn_tty_modem_result(2, info);
-                }
-        }
+		modem_info *info = &dev->mdm.info[i];
+		if (info->msr & UART_MSR_RI) {
+			ton = 1;
+			isdn_tty_modem_result(2, info);
+		}
+	}
 	isdn_timer_ctrl(ISDN_TIMER_MODEMRING, ton);
 }
 
@@ -2785,41 +3368,19 @@
  * For all online tty's, try sending data to
  * the lower levels.
  */
-void isdn_tty_modem_xmit(void)
+void
+isdn_tty_modem_xmit(void)
 {
 	int ton = 1;
 	int i;
 
 	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
-                modem_info *info = &dev->mdm.info[i];
-                if (info->online) {
-                        ton = 1;
-                        isdn_tty_senddown(info);
-                        isdn_tty_tint(info);
-                }
-        }
+		modem_info *info = &dev->mdm.info[i];
+		if (info->online) {
+			ton = 1;
+			isdn_tty_senddown(info);
+			isdn_tty_tint(info);
+		}
+	}
 	isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, ton);
-}
-
-/*
- * A packet has been output successfully.
- * Search the tty-devices for an appropriate device, decrement its
- * counter for outstanding packets, and set CTS.
- */
-void isdn_tty_bsent(int drv, int chan)
-{
-	int i;
-
-	for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
-                modem_info *info = &dev->mdm.info[i];
-                if ((info->isdn_driver == drv) &&
-                    (info->isdn_channel == chan) ) {
-                        info->msr |= UART_MSR_CTS;
-                        if (info->send_outstanding)
-                                if (!(--info->send_outstanding))
-                                        info->lsr |= UART_LSR_TEMT;
-                        isdn_tty_tint(info);
-                }
-        }
-	return;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov