linux/drivers/isdn/i4l/isdn_tty.c

3773 lines
89 KiB
C
Raw Normal View History

/*
* Linux ISDN subsystem, tty functions and AT-command emulator (linklevel).
*
* Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de)
* Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#undef ISDN_TTY_STAT_DEBUG
#include <linux/isdn.h>
#include <linux/serial.h> /* ASYNC_* flags */
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include "isdn_common.h"
#include "isdn_tty.h"
#ifdef CONFIG_ISDN_AUDIO
#include "isdn_audio.h"
#define VBUF 0x3e0
#define VBUFX (VBUF/16)
#endif
#define FIX_FILE_TRANSFER
#define DUMMY_HAYES_AT
/* Prototypes */
static DEFINE_MUTEX(modem_info_mutex);
static int isdn_tty_edit_at(const char *, int, modem_info *);
static void isdn_tty_check_esc(const u_char *, u_char, int, int *, u_long *);
static void isdn_tty_modem_reset_regs(modem_info *, int);
static void isdn_tty_cmd_ATA(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 int bit2si[8] =
{1, 5, 7, 7, 7, 7, 7, 7};
static int si2bit[8] =
{4, 1, 4, 4, 4, 4, 4, 4};
/* 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.
* Return:
* 1 = Success
* 0 = Failure, data has to be buffered and later processed by
* isdn_tty_readmodem().
*/
static int
isdn_tty_try_read(modem_info *info, struct sk_buff *skb)
{
struct tty_port *port = &info->port;
int c;
int len;
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
char last;
if (!info->online)
return 0;
if (!(info->mcr & UART_MCR_RTS))
return 0;
len = skb->len
#ifdef CONFIG_ISDN_AUDIO
+ ISDN_AUDIO_SKB_DLECOUNT(skb)
#endif
;
c = tty_buffer_request_room(port, len);
if (c < len)
return 0;
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
#ifdef CONFIG_ISDN_AUDIO
if (ISDN_AUDIO_SKB_DLECOUNT(skb)) {
int l = skb->len;
unsigned char *dp = skb->data;
while (--l) {
if (*dp == DLE)
tty_insert_flip_char(port, DLE, 0);
tty_insert_flip_char(port, *dp++, 0);
}
if (*dp == DLE)
tty_insert_flip_char(port, DLE, 0);
last = *dp;
} else {
#endif
if (len > 1)
tty_insert_flip_string(port, skb->data, len - 1);
last = skb->data[len - 1];
#ifdef CONFIG_ISDN_AUDIO
}
#endif
if (info->emu.mdmreg[REG_CPPP] & BIT_CPPP)
tty_insert_flip_char(port, last, 0xFF);
else
tty_insert_flip_char(port, last, TTY_NORMAL);
tty_flip_buffer_push(port);
kfree_skb(skb);
return 1;
}
/* isdn_tty_readmodem() is called periodically from within timer-interrupt.
* It tries getting received data from the receive queue an stuff it into
* the tty's flip-buffer.
*/
void
isdn_tty_readmodem(void)
{
int resched = 0;
int midx;
int i;
int r;
modem_info *info;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
midx = dev->m_idx[i];
if (midx < 0)
continue;
info = &dev->mdm.info[midx];
if (!info->online)
continue;
r = 0;
#ifdef CONFIG_ISDN_AUDIO
isdn_audio_eval_dtmf(info);
if ((info->vonline & 1) && (info->emu.vpar[1]))
isdn_audio_eval_silence(info);
#endif
if (info->mcr & UART_MCR_RTS) {
/* CISCO AsyncPPP Hack */
if (!(info->emu.mdmreg[REG_CPPP] & BIT_CPPP))
r = isdn_readbchan_tty(info->isdn_driver,
info->isdn_channel,
&info->port, 0);
else
r = isdn_readbchan_tty(info->isdn_driver,
info->isdn_channel,
&info->port, 1);
if (r)
tty_flip_buffer_push(&info->port);
} else
r = 1;
if (r) {
info->rcvsched = 0;
resched = 1;
} else
info->rcvsched = 1;
}
if (!resched)
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 0);
}
int
isdn_tty_rcv_skb(int i, int di, int channel, struct sk_buff *skb)
{
ulong flags;
int midx;
#ifdef CONFIG_ISDN_AUDIO
int ifmt;
#endif
modem_info *info;
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) && (!info->emu.vpar[4]))
isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt);
if ((info->vonline & 1) && (info->emu.vpar[1]))
isdn_audio_calc_silence(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 */
kfree_skb(skb);
return 1;
}
if (info->emu.mdmreg[REG_T70] & BIT_T70) {
if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT) {
/* T.70 decoding: throw away the T.70 header (2 or 4 bytes) */
if (skb->data[0] == 3) /* pure data packet -> 4 byte headers */
skb_pull(skb, 4);
else
if (skb->data[0] == 1) /* keepalive packet -> 2 byte hdr */
skb_pull(skb, 2);
} else
/* 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
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);
}
#ifdef CONFIG_ISDN_TTY_FAX
else {
if (info->faxonline & 2) {
isdn_tty_fax_bitorder(info, skb);
ISDN_AUDIO_SKB_DLECOUNT(skb) =
isdn_tty_countDLE(skb->data, skb->len);
}
}
#endif
#endif
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
/* Try to deliver directly via tty-buf if queue is empty */
spin_lock_irqsave(&info->readlock, flags);
if (skb_queue_empty(&dev->drv[di]->rpqueue[channel]))
if (isdn_tty_try_read(info, skb)) {
spin_unlock_irqrestore(&info->readlock, 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
);
spin_unlock_irqrestore(&info->readlock, flags);
/* Schedule dequeuing */
if ((dev->modempoll) && (info->rcvsched))
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
return 1;
}
static void
isdn_tty_cleanup_xmit(modem_info *info)
{
skb_queue_purge(&info->xmit_queue);
#ifdef CONFIG_ISDN_AUDIO
skb_queue_purge(&info->dtmf_queue);
#endif
}
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, 1, skb)) == len) {
struct tty_struct *tty = info->port.tty;
info->send_outstanding++;
info->msr &= ~UART_MSR_CTS;
info->lsr &= ~UART_LSR_TEMT;
tty_wakeup(tty);
return;
}
if (slen < 0) {
/* Error: no channel, already shutdown, or wrong parameter */
dev_kfree_skb(skb);
return;
}
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)
{
unsigned char *p = &info->port.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;
#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)
{
char ch;
while (c--) {
ch = *buf;
if ((ch != 0x11) && (ch != 0x13))
return 1;
buf++;
}
return 0;
}
static int voice_cf[7] =
{0, 0, 4, 3, 2, 0, 0};
#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)
{
int buflen;
int skb_res;
#ifdef CONFIG_ISDN_AUDIO
int audio_len;
#endif
struct sk_buff *skb;
#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
if (!(buflen = info->xmit_count))
return;
if ((info->emu.mdmreg[REG_CTS] & BIT_CTS) != 0)
info->msr &= ~UART_MSR_CTS;
info->lsr &= ~UART_LSR_TEMT;
/* info->xmit_count is modified here and in isdn_tty_write().
* So we return here if isdn_tty_write() is in the
* critical section.
*/
atomic_inc(&info->xmit_lock);
if (!(atomic_dec_and_test(&info->xmit_lock)))
return;
if (info->isdn_driver < 0) {
info->xmit_count = 0;
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) {
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->port.xmit_buf, buflen);
info->xmit_count = 0;
#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 */
if (info->emu.mdmreg[REG_T70] & BIT_T70) {
/* Add T.70 simplified header */
if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT)
memcpy(skb_push(skb, 2), "\1\0", 2);
else
memcpy(skb_push(skb, 4), "\1\0\1\0", 4);
}
skb_queue_tail(&info->xmit_queue, skb);
}
/************************************************************
*
* Modem-functions
*
* mostly "stolen" from original Linux-serial.c and friends.
*
************************************************************/
/* The next routine is called once from within timer-interrupt
* triggered within isdn_tty_modem_ncarrier(). It calls
* isdn_tty_modem_result() to stuff a "NO CARRIER" Message
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
* into the tty's buffer.
*/
static void
isdn_tty_modem_do_ncarrier(unsigned long data)
{
modem_info *info = (modem_info *) data;
isdn_tty_modem_result(RESULT_NO_CARRIER, 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)
{
if (info->ncarrier) {
info->nc_timer.expires = jiffies + HZ;
add_timer(&info->nc_timer);
}
}
/*
* return the usage calculated by si and layer 2 protocol
*/
static int
isdn_calc_usage(int si, int l2)
{
int usg = ISDN_USAGE_MODEM;
#ifdef CONFIG_ISDN_AUDIO
if (si == 1) {
switch (l2) {
case ISDN_PROTO_L2_MODEM:
usg = ISDN_USAGE_MODEM;
break;
#ifdef CONFIG_ISDN_TTY_FAX
case ISDN_PROTO_L2_FAX:
usg = ISDN_USAGE_FAX;
break;
#endif
case ISDN_PROTO_L2_TRANS:
default:
usg = ISDN_USAGE_VOICE;
break;
}
}
#endif
return (usg);
}
/* 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)
{
int usg = ISDN_USAGE_MODEM;
int si = 7;
int l2 = m->mdmreg[REG_L2PROT];
u_long flags;
isdn_ctrl cmd;
int i;
int j;
for (j = 7; j >= 0; j--)
if (m->mdmreg[REG_SI1] & (1 << j)) {
si = bit2si[j];
break;
}
usg = isdn_calc_usage(si, l2);
#ifdef CONFIG_ISDN_AUDIO
if ((si == 1) &&
(l2 != ISDN_PROTO_L2_MODEM)
#ifdef CONFIG_ISDN_TTY_FAX
&& (l2 != ISDN_PROTO_L2_FAX)
#endif
) {
l2 = ISDN_PROTO_L2_TRANS;
usg = ISDN_USAGE_VOICE;
}
#endif
m->mdmreg[REG_SI1I] = si2bit[si];
spin_lock_irqsave(&dev->lock, flags);
i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn);
if (i < 0) {
spin_unlock_irqrestore(&dev->lock, flags);
isdn_tty_modem_result(RESULT_NO_DIALTONE, 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;
info->last_dir = 1;
strcpy(info->last_num, n);
isdn_info_update();
spin_unlock_irqrestore(&dev->lock, flags);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.command = ISDN_CMD_CLREAZ;
isdn_command(&cmd);
strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver));
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETEAZ;
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL2;
info->last_l2 = l2;
cmd.arg = info->isdn_channel + (l2 << 8);
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL3;
cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8);
#ifdef CONFIG_ISDN_TTY_FAX
if (l2 == ISDN_PROTO_L2_FAX) {
cmd.parm.fax = info->fax;
info->fax->direction = ISDN_TTY_FAX_CONN_OUT;
}
#endif
isdn_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[REG_SI2];
cmd.command = ISDN_CMD_DIAL;
info->dialing = 1;
info->emu.carrierwait = 0;
strcpy(dev->num[i], n);
isdn_info_update();
isdn_command(&cmd);
isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1);
}
}
/* isdn_tty_hangup() disassociates a tty from the real
* ISDN-line (hangup). The usage-status is cleared
* and some cleanup is done also.
*/
void
isdn_tty_modem_hup(modem_info *info, int local)
{
isdn_ctrl cmd;
int di, ch;
if (!info)
return;
di = info->isdn_driver;
ch = info->isdn_channel;
if (di < 0 || ch < 0)
return;
info->isdn_driver = -1;
info->isdn_channel = -1;
#ifdef ISDN_DEBUG_MODEM_HUP
printk(KERN_DEBUG "Mhup ttyI%d\n", info->line);
#endif
info->rcvsched = 0;
isdn_tty_flush_buffer(info->port.tty);
if (info->online) {
info->last_lhup = local;
info->online = 0;
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
}
#ifdef CONFIG_ISDN_AUDIO
info->vonline = 0;
#ifdef CONFIG_ISDN_TTY_FAX
info->faxonline = 0;
info->fax->phase = ISDN_FAX_PHASE_IDLE;
#endif
info->emu.vpar[4] = 0;
info->emu.vpar[5] = 8;
kfree(info->dtmf_state);
info->dtmf_state = NULL;
kfree(info->silence_state);
info->silence_state = NULL;
kfree(info->adpcms);
info->adpcms = NULL;
kfree(info->adpcmr);
info->adpcmr = NULL;
#endif
if ((info->msr & UART_MSR_RI) &&
(info->emu.mdmreg[REG_RUNG] & BIT_RUNG))
isdn_tty_modem_result(RESULT_RUNG, info);
info->msr &= ~(UART_MSR_DCD | UART_MSR_RI);
info->lsr |= UART_LSR_TEMT;
if (local) {
cmd.driver = di;
cmd.command = ISDN_CMD_HANGUP;
cmd.arg = ch;
isdn_command(&cmd);
}
isdn_all_eaz(di, ch);
info->emu.mdmreg[REG_RINGCNT] = 0;
isdn_free_channel(di, ch, 0);
if (info->drv_index >= 0) {
dev->m_idx[info->drv_index] = -1;
info->drv_index = -1;
}
}
/*
* Begin of a CAPI like interface, currently used only for
* supplementary service (CAPI 2.0 part III)
*/
#include <linux/isdn/capicmd.h>
#include <linux/module.h>
int
isdn_tty_capi_facility(capi_msg *cm) {
return (-1); /* dummy */
}
/* isdn_tty_suspend() tries to suspend the current tty connection
*/
static void
isdn_tty_suspend(char *id, modem_info *info, atemu *m)
{
isdn_ctrl cmd;
int l;
if (!info)
return;
#ifdef ISDN_DEBUG_MODEM_SERVICES
printk(KERN_DEBUG "Msusp ttyI%d\n", info->line);
#endif
l = strlen(id);
if ((info->isdn_driver >= 0)) {
cmd.parm.cmsg.Length = l + 18;
cmd.parm.cmsg.Command = CAPI_FACILITY;
cmd.parm.cmsg.Subcommand = CAPI_REQ;
cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1;
cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */
cmd.parm.cmsg.para[1] = 0;
cmd.parm.cmsg.para[2] = l + 3;
cmd.parm.cmsg.para[3] = 4; /* 16 bit 0x0004 Suspend */
cmd.parm.cmsg.para[4] = 0;
cmd.parm.cmsg.para[5] = l;
strncpy(&cmd.parm.cmsg.para[6], id, l);
cmd.command = CAPI_PUT_MESSAGE;
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
isdn_command(&cmd);
}
}
/* isdn_tty_resume() tries to resume a suspended call
* setup of the lower levels before that. unfortunately here is no
* checking for compatibility of used protocols implemented by Q931
* It does the same things like isdn_tty_dial, the last command
* is different, may be we can merge it.
*/
static void
isdn_tty_resume(char *id, modem_info *info, atemu *m)
{
int usg = ISDN_USAGE_MODEM;
int si = 7;
int l2 = m->mdmreg[REG_L2PROT];
isdn_ctrl cmd;
ulong flags;
int i;
int j;
int l;
l = strlen(id);
for (j = 7; j >= 0; j--)
if (m->mdmreg[REG_SI1] & (1 << j)) {
si = bit2si[j];
break;
}
usg = isdn_calc_usage(si, l2);
#ifdef CONFIG_ISDN_AUDIO
if ((si == 1) &&
(l2 != ISDN_PROTO_L2_MODEM)
#ifdef CONFIG_ISDN_TTY_FAX
&& (l2 != ISDN_PROTO_L2_FAX)
#endif
) {
l2 = ISDN_PROTO_L2_TRANS;
usg = ISDN_USAGE_VOICE;
}
#endif
m->mdmreg[REG_SI1I] = si2bit[si];
spin_lock_irqsave(&dev->lock, flags);
i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn);
if (i < 0) {
spin_unlock_irqrestore(&dev->lock, flags);
isdn_tty_modem_result(RESULT_NO_DIALTONE, 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;
info->last_dir = 1;
// strcpy(info->last_num, n);
isdn_info_update();
spin_unlock_irqrestore(&dev->lock, flags);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.command = ISDN_CMD_CLREAZ;
isdn_command(&cmd);
strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver));
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETEAZ;
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL2;
info->last_l2 = l2;
cmd.arg = info->isdn_channel + (l2 << 8);
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL3;
cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8);
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.parm.cmsg.Length = l + 18;
cmd.parm.cmsg.Command = CAPI_FACILITY;
cmd.parm.cmsg.Subcommand = CAPI_REQ;
cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1;
cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */
cmd.parm.cmsg.para[1] = 0;
cmd.parm.cmsg.para[2] = l + 3;
cmd.parm.cmsg.para[3] = 5; /* 16 bit 0x0005 Resume */
cmd.parm.cmsg.para[4] = 0;
cmd.parm.cmsg.para[5] = l;
strncpy(&cmd.parm.cmsg.para[6], id, l);
cmd.command = CAPI_PUT_MESSAGE;
info->dialing = 1;
// strcpy(dev->num[i], n);
isdn_info_update();
isdn_command(&cmd);
isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1);
}
}
/* isdn_tty_send_msg() sends a message to a HL driver
* This is used for hybrid modem cards to send AT commands to it
*/
static void
isdn_tty_send_msg(modem_info *info, atemu *m, char *msg)
{
int usg = ISDN_USAGE_MODEM;
int si = 7;
int l2 = m->mdmreg[REG_L2PROT];
isdn_ctrl cmd;
ulong flags;
int i;
int j;
int l;
l = min(strlen(msg), sizeof(cmd.parm) - sizeof(cmd.parm.cmsg)
+ sizeof(cmd.parm.cmsg.para) - 2);
if (!l) {
isdn_tty_modem_result(RESULT_ERROR, info);
return;
}
for (j = 7; j >= 0; j--)
if (m->mdmreg[REG_SI1] & (1 << j)) {
si = bit2si[j];
break;
}
usg = isdn_calc_usage(si, l2);
#ifdef CONFIG_ISDN_AUDIO
if ((si == 1) &&
(l2 != ISDN_PROTO_L2_MODEM)
#ifdef CONFIG_ISDN_TTY_FAX
&& (l2 != ISDN_PROTO_L2_FAX)
#endif
) {
l2 = ISDN_PROTO_L2_TRANS;
usg = ISDN_USAGE_VOICE;
}
#endif
m->mdmreg[REG_SI1I] = si2bit[si];
spin_lock_irqsave(&dev->lock, flags);
i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn);
if (i < 0) {
spin_unlock_irqrestore(&dev->lock, flags);
isdn_tty_modem_result(RESULT_NO_DIALTONE, 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;
info->last_dir = 1;
isdn_info_update();
spin_unlock_irqrestore(&dev->lock, flags);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.command = ISDN_CMD_CLREAZ;
isdn_command(&cmd);
strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver));
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETEAZ;
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL2;
info->last_l2 = l2;
cmd.arg = info->isdn_channel + (l2 << 8);
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL3;
cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8);
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.parm.cmsg.Length = l + 14;
cmd.parm.cmsg.Command = CAPI_MANUFACTURER;
cmd.parm.cmsg.Subcommand = CAPI_REQ;
cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1;
cmd.parm.cmsg.para[0] = l + 1;
strncpy(&cmd.parm.cmsg.para[1], msg, l);
cmd.parm.cmsg.para[l + 1] = 0xd;
cmd.command = CAPI_PUT_MESSAGE;
/* info->dialing = 1;
strcpy(dev->num[i], n);
isdn_info_update();
*/
isdn_command(&cmd);
}
}
static inline int
isdn_tty_paranoia_check(modem_info *info, char *name, const char *routine)
{
#ifdef MODEM_PARANOIA_CHECK
if (!info) {
printk(KERN_WARNING "isdn_tty: null info_struct for %s in %s\n",
name, routine);
return 1;
}
if (info->magic != ISDN_ASYNC_MAGIC) {
printk(KERN_WARNING "isdn_tty: bad magic for modem struct %s in %s\n",
name, routine);
return 1;
}
#endif
return 0;
}
/*
* 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)
{
struct tty_port *port = &info->port;
uint cflag,
cval,
quot;
int i;
if (!port->tty)
return;
cflag = port->tty->termios.c_cflag;
quot = i = cflag & CBAUD;
if (i & CBAUDEX) {
i &= ~CBAUDEX;
if (i < 1 || i > 2)
port->tty->termios.c_cflag &= ~CBAUDEX;
else
i += 15;
}
if (quot) {
info->mcr |= UART_MCR_DTR;
isdn_tty_modem_ncarrier(info);
} else {
info->mcr &= ~UART_MCR_DTR;
if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) {
#ifdef ISDN_DEBUG_MODEM_HUP
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, 1);
}
return;
}
/* byte size and parity */
cval = cflag & (CSIZE | CSTOPB);
cval >>= 4;
if (cflag & PARENB)
cval |= UART_LCR_PARITY;
if (!(cflag & PARODD))
cval |= UART_LCR_EPAR;
tty_port_set_check_carrier(port, ~cflag & CLOCAL);
}
static int
isdn_tty_startup(modem_info *info)
{
if (tty_port_initialized(&info->port))
return 0;
isdn_lock_drivers();
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "starting up ttyi%d ...\n", info->line);
#endif
/*
* Now, initialize the UART
*/
info->mcr = UART_MCR_DTR | UART_MCR_RTS | UART_MCR_OUT2;
if (info->port.tty)
clear_bit(TTY_IO_ERROR, &info->port.tty->flags);
/*
* and set the speed of the serial port
*/
isdn_tty_change_speed(info);
tty_port_set_initialized(&info->port, 1);
info->msr |= (UART_MSR_DSR | UART_MSR_CTS);
info->send_outstanding = 0;
return 0;
}
/*
* 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)
{
if (!tty_port_initialized(&info->port))
return;
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "Shutting down isdnmodem port %d ....\n", info->line);
#endif
isdn_unlock_drivers();
info->msr &= ~UART_MSR_RI;
if (!info->port.tty || (info->port.tty->termios.c_cflag & HUPCL)) {
info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS);
if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) {
isdn_tty_modem_reset_regs(info, 0);
#ifdef ISDN_DEBUG_MODEM_HUP
printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n");
#endif
isdn_tty_modem_hup(info, 1);
}
}
if (info->port.tty)
set_bit(TTY_IO_ERROR, &info->port.tty->flags);
tty_port_set_initialized(&info->port, 0);
}
/* isdn_tty_write() is the main send-routine. It is called from the upper
* levels within the kernel to perform sending data. Depending on the
* online-flag it either directs output to the at-command-interpreter or
* to the lower level. Additional tasks done here:
* - If online, check for escape-sequence (+++)
* - If sending audio-data, call isdn_tty_DLEdown() to parse DLE-codes.
* - 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, const u_char *buf, int count)
{
int c;
int total = 0;
modem_info *info = (modem_info *) tty->driver_data;
atemu *m = &info->emu;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_write"))
return 0;
/* See isdn_tty_senddown() */
atomic_inc(&info->xmit_lock);
while (1) {
c = count;
if (c > info->xmit_size - info->xmit_count)
c = info->xmit_size - info->xmit_count;
if (info->isdn_driver >= 0 && c > dev->drv[info->isdn_driver]->maxbufsize)
c = dev->drv[info->isdn_driver]->maxbufsize;
if (c <= 0)
break;
if ((info->online > 1)
#ifdef CONFIG_ISDN_AUDIO
|| (info->vonline & 3)
#endif
) {
#ifdef CONFIG_ISDN_AUDIO
if (!info->vonline)
#endif
isdn_tty_check_esc(buf, m->mdmreg[REG_ESC], c,
&(m->pluscount),
&(m->lastplus));
memcpy(&info->port.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.
*/
tty_wakeup(tty);
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)) {
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
if (TTY_IS_FCLASS1(info)) {
int cc = isdn_tty_handleDLEdown(info, m, c);
if (info->vonline & 4) { /* ETX seen */
isdn_ctrl c;
c.command = ISDN_CMD_FAXCMD;
c.driver = info->isdn_driver;
c.arg = info->isdn_channel;
c.parm.aux.cmd = ISDN_FAX_CLASS1_CTRL;
c.parm.aux.subcmd = ETX;
isdn_command(&c);
}
info->vonline = 0;
#ifdef ISDN_DEBUG_MODEM_VOICE
printk(KERN_DEBUG "fax dle cc/c %d/%d\n", cc, c);
#endif
info->xmit_count += cc;
} else
#endif
info->xmit_count += c;
} 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");
#endif
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
isdn_tty_modem_hup(info, 1);
} else
c = isdn_tty_edit_at(buf, c, info);
}
buf += c;
count -= c;
total += c;
}
atomic_dec(&info->xmit_lock);
if ((info->xmit_count) || !skb_queue_empty(&info->xmit_queue)) {
if (m->mdmreg[REG_DXMT] & BIT_DXMT) {
isdn_tty_senddown(info);
isdn_tty_tint(info);
}
isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1);
}
return total;
}
static int
isdn_tty_write_room(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
int ret;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_write_room"))
return 0;
if (!info->online)
return info->xmit_size;
ret = info->xmit_size - info->xmit_count;
return (ret < 0) ? 0 : ret;
}
static int
isdn_tty_chars_in_buffer(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_chars_in_buffer"))
return 0;
if (!info->online)
return 0;
return (info->xmit_count);
}
static void
isdn_tty_flush_buffer(struct tty_struct *tty)
{
modem_info *info;
if (!tty) {
return;
}
info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_flush_buffer")) {
return;
}
isdn_tty_cleanup_xmit(info);
info->xmit_count = 0;
tty_wakeup(tty);
}
static void
isdn_tty_flush_chars(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_flush_chars"))
return;
if ((info->xmit_count) || !skb_queue_empty(&info->xmit_queue))
isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1);
}
/*
* ------------------------------------------------------------
* 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)
{
modem_info *info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_throttle"))
return;
if (I_IXOFF(tty))
info->x_char = STOP_CHAR(tty);
info->mcr &= ~UART_MCR_RTS;
}
static void
isdn_tty_unthrottle(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_unthrottle"))
return;
if (I_IXOFF(tty)) {
if (info->x_char)
info->x_char = 0;
else
info->x_char = START_CHAR(tty);
}
info->mcr |= UART_MCR_RTS;
}
/*
* ------------------------------------------------------------
* isdn_tty_ioctl() and friends
* ------------------------------------------------------------
*/
/*
* isdn_tty_get_lsr_info - get line status register info
*
* Purpose: Let user call ioctl() to get info when the UART physically
* is emptied. On bus types like RS485, the transmitter must
* release the bus after transmitting. This must be done when
* the transmit shift register is empty, not be done when the
* transmit holding register is empty. This functionality
* allows RS485 driver to be written in user space.
*/
static int
isdn_tty_get_lsr_info(modem_info *info, uint __user *value)
{
u_char status;
uint result;
status = info->lsr;
result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0);
return put_user(result, value);
}
static int
isdn_tty_tiocmget(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
u_char control, status;
if (isdn_tty_paranoia_check(info, tty->name, __func__))
return -ENODEV;
if (tty_io_error(tty))
return -EIO;
mutex_lock(&modem_info_mutex);
#ifdef ISDN_DEBUG_MODEM_IOCTL
printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line);
#endif
control = info->mcr;
status = info->msr;
mutex_unlock(&modem_info_mutex);
return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0)
| ((control & UART_MCR_DTR) ? TIOCM_DTR : 0)
| ((status & UART_MSR_DCD) ? TIOCM_CAR : 0)
| ((status & UART_MSR_RI) ? TIOCM_RNG : 0)
| ((status & UART_MSR_DSR) ? TIOCM_DSR : 0)
| ((status & UART_MSR_CTS) ? TIOCM_CTS : 0);
}
static int
isdn_tty_tiocmset(struct tty_struct *tty,
unsigned int set, unsigned int clear)
{
modem_info *info = (modem_info *) tty->driver_data;
if (isdn_tty_paranoia_check(info, tty->name, __func__))
return -ENODEV;
if (tty_io_error(tty))
return -EIO;
#ifdef ISDN_DEBUG_MODEM_IOCTL
printk(KERN_DEBUG "ttyI%d ioctl TIOCMxxx: %x %x\n", info->line, set, clear);
#endif
mutex_lock(&modem_info_mutex);
if (set & TIOCM_RTS)
info->mcr |= UART_MCR_RTS;
if (set & TIOCM_DTR) {
info->mcr |= UART_MCR_DTR;
isdn_tty_modem_ncarrier(info);
}
if (clear & TIOCM_RTS)
info->mcr &= ~UART_MCR_RTS;
if (clear & TIOCM_DTR) {
info->mcr &= ~UART_MCR_DTR;
if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) {
isdn_tty_modem_reset_regs(info, 0);
#ifdef ISDN_DEBUG_MODEM_HUP
printk(KERN_DEBUG "Mhup in TIOCMSET\n");
#endif
if (info->online)
info->ncarrier = 1;
isdn_tty_modem_hup(info, 1);
}
}
mutex_unlock(&modem_info_mutex);
return 0;
}
static int
isdn_tty_ioctl(struct tty_struct *tty, uint cmd, ulong arg)
{
modem_info *info = (modem_info *) tty->driver_data;
int retval;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_ioctl"))
return -ENODEV;
if (tty_io_error(tty))
return -EIO;
switch (cmd) {
case TCSBRK: /* SVID version: non-zero arg --> no break */
#ifdef ISDN_DEBUG_MODEM_IOCTL
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() */
#ifdef ISDN_DEBUG_MODEM_IOCTL
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 TIOCSERGETLSR: /* Get line status register */
#ifdef ISDN_DEBUG_MODEM_IOCTL
printk(KERN_DEBUG "ttyI%d ioctl TIOCSERGETLSR\n", info->line);
#endif
return isdn_tty_get_lsr_info(info, (uint __user *) arg);
default:
#ifdef ISDN_DEBUG_MODEM_IOCTL
printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on ttyi%d\n", cmd, info->line);
#endif
return -ENOIOCTLCMD;
}
return 0;
}
static void
isdn_tty_set_termios(struct tty_struct *tty, struct ktermios *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 &&
tty->termios.c_ispeed == old_termios->c_ispeed &&
tty->termios.c_ospeed == old_termios->c_ospeed)
return;
isdn_tty_change_speed(info);
}
}
/*
* ------------------------------------------------------------
* isdn_tty_open() and friends
* ------------------------------------------------------------
*/
static int isdn_tty_install(struct tty_driver *driver, struct tty_struct *tty)
{
modem_info *info = &dev->mdm.info[tty->index];
if (isdn_tty_paranoia_check(info, tty->name, __func__))
return -ENODEV;
tty->driver_data = info;
return tty_port_install(&info->port, driver, tty);
}
/*
* This routine is called whenever a serial port is opened. It
* enables interrupts for a serial port, linking in its async structure into
* the IRQ chain. It also performs the serial-specific
* initialization for the tty structure.
*/
static int
isdn_tty_open(struct tty_struct *tty, struct file *filp)
{
modem_info *info = tty->driver_data;
struct tty_port *port = &info->port;
int retval;
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_open %s, count = %d\n", tty->name,
port->count);
#endif
port->count++;
port->tty = tty;
/*
* Start up serial port
*/
retval = isdn_tty_startup(info);
if (retval) {
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_open return after startup\n");
#endif
return retval;
}
retval = tty_port_block_til_ready(port, tty, filp);
if (retval) {
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_open return after isdn_tty_block_til_ready \n");
#endif
return retval;
}
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_open ttyi%d successful...\n", info->line);
#endif
dev->modempoll++;
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_open normal exit\n");
#endif
return 0;
}
static void
isdn_tty_close(struct tty_struct *tty, struct file *filp)
{
modem_info *info = (modem_info *) tty->driver_data;
struct tty_port *port = &info->port;
ulong timeout;
if (!info || isdn_tty_paranoia_check(info, tty->name, "isdn_tty_close"))
return;
if (tty_hung_up_p(filp)) {
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_close return after tty_hung_up_p\n");
#endif
return;
}
if ((tty->count == 1) && (port->count != 1)) {
/*
* Uh, oh. tty->count is 1, which means that the tty
* structure will be freed. Info->count should always
* be one in these conditions. If it's greater than
* one, we've got real problems, since it means the
* serial port won't be shutdown.
*/
printk(KERN_ERR "isdn_tty_close: bad port count; tty->count is 1, "
"info->count is %d\n", port->count);
port->count = 1;
}
if (--port->count < 0) {
printk(KERN_ERR "isdn_tty_close: bad port count for ttyi%d: %d\n",
info->line, port->count);
port->count = 0;
}
if (port->count) {
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_close after info->count != 0\n");
#endif
return;
}
info->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
* interrupt driver to stop checking the data ready bit in the
* line status register.
*/
if (tty_port_initialized(port)) {
tty: Remove tty_wait_until_sent_from_close() tty_wait_until_sent_from_close() drops the tty lock while waiting for the tty driver to finish sending previously accepted data (ie., data remaining in its write buffer and transmit fifo). tty_wait_until_sent_from_close() was added by commit a57a7bf3fc7e ("TTY: define tty_wait_until_sent_from_close") to prevent the entire tty subsystem from being unable to open new ttys while waiting for one tty to close while output drained. However, since commit 0911261d4cb6 ("tty: Don't take tty_mutex for tty count changes"), holding a tty lock while closing does not prevent other ttys from being opened/closed/hung up, but only prevents lifetime event changes for the tty under lock. Holding the tty lock while waiting for output to drain does prevent parallel non-blocking opens (O_NONBLOCK) from advancing or returning while the tty lock is held. However, all parallel opens _already_ block even if the tty lock is dropped while closing and the parallel open advances. Blocking in open has been in mainline since at least 2.6.29 (see tty_port_block_til_ready(); note the test for O_NONBLOCK is _after_ the wait while ASYNC_CLOSING). IOW, before this patch a non-blocking open will sleep anyway for the _entire_ duration of a parallel hardware shutdown, and when it wakes, the error return will cause a release of its tty, and it will restart with a fresh attempt to open. Similarly with a blocking open that is already waiting; when it's woken, the hardware shutdown has already completed to ASYNC_INITIALIZED is not set, which forces a release and restart as well. So, holding the tty lock across the _entire_ close (which is what this patch does), even while waiting for output to drain, is equivalent to the current outcome wrt parallel opens. Cc: Alan Cox <alan@linux.intel.com> Cc: David Laight <David.Laight@aculab.com> CC: Arnd Bergmann <arnd@arndb.de> CC: Karsten Keil <isdn@linux-pingi.de> CC: linuxppc-dev@lists.ozlabs.org Signed-off-by: Peter Hurley <peter@hurleysoftware.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-10-11 04:00:51 +08:00
tty_wait_until_sent(tty, 3000); /* 30 seconds timeout */
/*
* Before we drop DTR, make sure the UART transmitter
* has completely drained; this is especially
* important if there is a transmit FIFO!
*/
timeout = jiffies + HZ;
while (!(info->lsr & UART_LSR_TEMT)) {
schedule_timeout_interruptible(20);
if (time_after(jiffies, timeout))
break;
}
}
dev->modempoll--;
isdn_tty_shutdown(info);
isdn_tty_flush_buffer(tty);
tty_ldisc_flush(tty);
port->tty = NULL;
info->ncarrier = 0;
tty_port_close_end(port, tty);
info->closing = 0;
#ifdef ISDN_DEBUG_MODEM_OPEN
printk(KERN_DEBUG "isdn_tty_close normal exit\n");
#endif
}
/*
* isdn_tty_hangup() --- called by tty_hangup() when a hangup is signaled.
*/
static void
isdn_tty_hangup(struct tty_struct *tty)
{
modem_info *info = (modem_info *) tty->driver_data;
struct tty_port *port = &info->port;
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_hangup"))
return;
isdn_tty_shutdown(info);
port->count = 0;
tty_port_set_active(port, 0);
port->tty = NULL;
wake_up_interruptible(&port->open_wait);
}
/* This routine initializes all emulator-data.
*/
static void
isdn_tty_reset_profile(atemu *m)
{
m->profile[0] = 0;
m->profile[1] = 0;
m->profile[2] = 43;
m->profile[3] = 13;
m->profile[4] = 10;
m->profile[5] = 8;
m->profile[6] = 3;
m->profile[7] = 60;
m->profile[8] = 2;
m->profile[9] = 6;
m->profile[10] = 7;
m->profile[11] = 70;
m->profile[12] = 0x45;
m->profile[13] = 4;
m->profile[14] = ISDN_PROTO_L2_X75I;
m->profile[15] = ISDN_PROTO_L3_TRANS;
m->profile[16] = ISDN_SERIAL_XMIT_SIZE / 16;
m->profile[17] = ISDN_MODEM_WINSIZE;
m->profile[18] = 4;
m->profile[19] = 0;
m->profile[20] = 0;
m->profile[23] = 0;
m->pmsn[0] = '\0';
m->plmsn[0] = '\0';
}
#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[4] = 0; /* DTMF detection level (0 = softcode ) */
m->vpar[5] = 8; /* DTMF interval (8 * 5 ms. ) */
}
#endif
#ifdef CONFIG_ISDN_TTY_FAX
static void
isdn_tty_modem_reset_faxpar(modem_info *info)
{
T30_s *f = info->fax;
f->code = 0;
f->phase = ISDN_FAX_PHASE_IDLE;
f->direction = 0;
f->resolution = 1; /* fine */
f->rate = 5; /* 14400 bit/s */
f->width = 0;
f->length = 0;
f->compression = 0;
f->ecm = 0;
f->binary = 0;
f->scantime = 0;
memset(&f->id[0], 32, FAXIDLEN - 1);
f->id[FAXIDLEN - 1] = 0;
f->badlin = 0;
f->badmul = 0;
f->bor = 0;
f->nbc = 0;
f->cq = 0;
f->cr = 0;
f->ctcrty = 0;
f->minsp = 0;
f->phcto = 30;
f->rel = 0;
memset(&f->pollid[0], 32, FAXIDLEN - 1);
f->pollid[FAXIDLEN - 1] = 0;
}
#endif
static void
isdn_tty_modem_reset_regs(modem_info *info, int force)
{
atemu *m = &info->emu;
if ((m->mdmreg[REG_DTRR] & BIT_DTRR) || force) {
memcpy(m->mdmreg, m->profile, ISDN_MODEM_NUMREG);
memcpy(m->msn, m->pmsn, ISDN_MSNLEN);
memcpy(m->lmsn, m->plmsn, ISDN_LMSNLEN);
info->xmit_size = m->mdmreg[REG_PSIZE] * 16;
}
#ifdef CONFIG_ISDN_AUDIO
isdn_tty_modem_reset_vpar(m);
#endif
#ifdef CONFIG_ISDN_TTY_FAX
isdn_tty_modem_reset_faxpar(info);
#endif
m->mdmcmdl = 0;
}
static void
modem_write_profile(atemu *m)
{
memcpy(m->profile, m->mdmreg, ISDN_MODEM_NUMREG);
memcpy(m->pmsn, m->msn, ISDN_MSNLEN);
memcpy(m->plmsn, m->lmsn, ISDN_LMSNLEN);
if (dev->profd)
send_sig(SIGIO, dev->profd, 1);
}
static const struct tty_operations modem_ops = {
.install = isdn_tty_install,
.open = isdn_tty_open,
.close = isdn_tty_close,
.write = isdn_tty_write,
.flush_chars = isdn_tty_flush_chars,
.write_room = isdn_tty_write_room,
.chars_in_buffer = isdn_tty_chars_in_buffer,
.flush_buffer = isdn_tty_flush_buffer,
.ioctl = isdn_tty_ioctl,
.throttle = isdn_tty_throttle,
.unthrottle = isdn_tty_unthrottle,
.set_termios = isdn_tty_set_termios,
.hangup = isdn_tty_hangup,
.tiocmget = isdn_tty_tiocmget,
.tiocmset = isdn_tty_tiocmset,
};
static int isdn_tty_carrier_raised(struct tty_port *port)
{
modem_info *info = container_of(port, modem_info, port);
return info->msr & UART_MSR_DCD;
}
static const struct tty_port_operations isdn_tty_port_ops = {
.carrier_raised = isdn_tty_carrier_raised,
};
int
isdn_tty_modem_init(void)
{
isdn_modem_t *m;
int i, retval;
modem_info *info;
m = &dev->mdm;
m->tty_modem = alloc_tty_driver(ISDN_MAX_CHANNELS);
if (!m->tty_modem)
return -ENOMEM;
m->tty_modem->name = "ttyI";
m->tty_modem->major = ISDN_TTY_MAJOR;
m->tty_modem->minor_start = 0;
m->tty_modem->type = TTY_DRIVER_TYPE_SERIAL;
m->tty_modem->subtype = SERIAL_TYPE_NORMAL;
m->tty_modem->init_termios = tty_std_termios;
m->tty_modem->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
m->tty_modem->flags = TTY_DRIVER_REAL_RAW;
m->tty_modem->driver_name = "isdn_tty";
tty_set_operations(m->tty_modem, &modem_ops);
retval = tty_register_driver(m->tty_modem);
if (retval) {
printk(KERN_WARNING "isdn_tty: Couldn't register modem-device\n");
goto err;
}
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
info = &m->info[i];
#ifdef CONFIG_ISDN_TTY_FAX
if (!(info->fax = kmalloc(sizeof(T30_s), GFP_KERNEL))) {
printk(KERN_ERR "Could not allocate fax t30-buffer\n");
retval = -ENOMEM;
goto err_unregister;
}
#endif
tty_port_init(&info->port);
info->port.ops = &isdn_tty_port_ops;
spin_lock_init(&info->readlock);
sprintf(info->last_cause, "0000");
sprintf(info->last_num, "none");
info->last_dir = 0;
info->last_lhup = 1;
info->last_l2 = -1;
info->last_si = 0;
isdn_tty_reset_profile(&info->emu);
isdn_tty_modem_reset_regs(info, 1);
info->magic = ISDN_ASYNC_MAGIC;
info->line = i;
info->x_char = 0;
info->isdn_driver = -1;
info->isdn_channel = -1;
info->drv_index = -1;
info->xmit_size = ISDN_SERIAL_XMIT_SIZE;
init_timer(&info->nc_timer);
info->nc_timer.function = isdn_tty_modem_do_ncarrier;
info->nc_timer.data = (unsigned long) info;
skb_queue_head_init(&info->xmit_queue);
#ifdef CONFIG_ISDN_AUDIO
skb_queue_head_init(&info->dtmf_queue);
#endif
info->port.xmit_buf = kmalloc(ISDN_SERIAL_XMIT_MAX + 5,
GFP_KERNEL);
if (!info->port.xmit_buf) {
printk(KERN_ERR "Could not allocate modem xmit-buffer\n");
retval = -ENOMEM;
goto err_unregister;
}
/* Make room for T.70 header */
info->port.xmit_buf += 4;
}
return 0;
err_unregister:
for (i--; i >= 0; i--) {
info = &m->info[i];
#ifdef CONFIG_ISDN_TTY_FAX
kfree(info->fax);
#endif
kfree(info->port.xmit_buf - 4);
info->port.xmit_buf = NULL;
tty_port_destroy(&info->port);
}
tty_unregister_driver(m->tty_modem);
err:
put_tty_driver(m->tty_modem);
m->tty_modem = NULL;
return retval;
}
void
isdn_tty_exit(void)
{
modem_info *info;
int i;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
info = &dev->mdm.info[i];
isdn_tty_cleanup_xmit(info);
#ifdef CONFIG_ISDN_TTY_FAX
kfree(info->fax);
#endif
kfree(info->port.xmit_buf - 4);
info->port.xmit_buf = NULL;
tty_port_destroy(&info->port);
}
tty_unregister_driver(dev->mdm.tty_modem);
put_tty_driver(dev->mdm.tty_modem);
dev->mdm.tty_modem = NULL;
}
/*
* isdn_tty_match_icall(char *MSN, atemu *tty_emulator, int dev_idx)
* match the MSN against the MSNs (glob patterns) defined for tty_emulator,
* and return 0 for match, 1 for no match, 2 if MSN could match if longer.
*/
static int
isdn_tty_match_icall(char *cid, atemu *emu, int di)
{
#ifdef ISDN_DEBUG_MODEM_ICALL
printk(KERN_DEBUG "m_fi: msn=%s lmsn=%s mmsn=%s mreg[SI1]=%d mreg[SI2]=%d\n",
emu->msn, emu->lmsn, isdn_map_eaz2msn(emu->msn, di),
emu->mdmreg[REG_SI1], emu->mdmreg[REG_SI2]);
#endif
if (strlen(emu->lmsn)) {
char *p = emu->lmsn;
char *q;
int tmp;
int ret = 0;
while (1) {
if ((q = strchr(p, ';')))
*q = '\0';
if ((tmp = isdn_msncmp(cid, isdn_map_eaz2msn(p, di))) > ret)
ret = tmp;
#ifdef ISDN_DEBUG_MODEM_ICALL
printk(KERN_DEBUG "m_fi: lmsnX=%s mmsn=%s -> tmp=%d\n",
p, isdn_map_eaz2msn(emu->msn, di), tmp);
#endif
if (q) {
*q = ';';
p = q;
p++;
}
if (!tmp)
return 0;
if (!q)
break;
}
return ret;
} else {
int tmp;
tmp = isdn_msncmp(cid, isdn_map_eaz2msn(emu->msn, di));
#ifdef ISDN_DEBUG_MODEM_ICALL
printk(KERN_DEBUG "m_fi: mmsn=%s -> tmp=%d\n",
isdn_map_eaz2msn(emu->msn, di), tmp);
#endif
return tmp;
}
}
/*
* An incoming call-request has arrived.
* Search the tty-devices for an appropriate device and bind
* it to the ISDN-Channel.
* Return:
*
* 0 = No matching device found.
* 1 = A matching device found.
* 3 = No match found, but eventually would match, if
* CID is longer.
*/
int
isdn_tty_find_icall(int di, int ch, setup_parm *setup)
{
char *eaz;
int i;
int wret;
int idx;
int si1;
int si2;
char *nr;
ulong flags;
if (!setup->phone[0]) {
nr = "0";
printk(KERN_INFO "isdn_tty: Incoming call without OAD, assuming '0'\n");
} else
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
wret = 0;
spin_lock_irqsave(&dev->lock, flags);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
modem_info *info = &dev->mdm.info[i];
if (info->port.count == 0)
continue;
if ((info->emu.mdmreg[REG_SI1] & si2bit[si1]) && /* SI1 is matching */
(info->emu.mdmreg[REG_SI2] == si2)) { /* SI2 is matching */
idx = isdn_dc2minor(di, ch);
#ifdef ISDN_DEBUG_MODEM_ICALL
printk(KERN_DEBUG "m_fi: match1 wret=%d\n", wret);
printk(KERN_DEBUG "m_fi: idx=%d flags=%08lx drv=%d ch=%d usg=%d\n", idx,
info->port.flags, info->isdn_driver,
info->isdn_channel, dev->usage[idx]);
#endif
if (
#ifndef FIX_FILE_TRANSFER
tty_port_active(&info->port) &&
#endif
(info->isdn_driver == -1) &&
(info->isdn_channel == -1) &&
(USG_NONE(dev->usage[idx]))) {
int matchret;
if ((matchret = isdn_tty_match_icall(eaz, &info->emu, di)) > wret)
wret = matchret;
if (!matchret) { /* EAZ is matching */
info->isdn_driver = di;
info->isdn_channel = ch;
info->drv_index = idx;
dev->m_idx[idx] = info->line;
dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE;
dev->usage[idx] |= isdn_calc_usage(si1, info->emu.mdmreg[REG_L2PROT]);
strcpy(dev->num[idx], nr);
strcpy(info->emu.cpn, eaz);
info->emu.mdmreg[REG_SI1I] = si2bit[si1];
info->emu.mdmreg[REG_PLAN] = setup->plan;
info->emu.mdmreg[REG_SCREEN] = setup->screen;
isdn_info_update();
spin_unlock_irqrestore(&dev->lock, 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(RESULT_RING, info);
isdn_timer_ctrl(ISDN_TIMER_MODEMRING, 1);
return 1;
}
}
}
}
spin_unlock_irqrestore(&dev->lock, flags);
printk(KERN_INFO "isdn_tty: call from %s -> %s %s\n", nr, eaz,
((dev->drv[di]->flags & DRV_FLAG_REJBUS) && (wret != 2)) ? "rejected" : "ignored");
return (wret == 2) ? 3 : 0;
}
int
isdn_tty_stat_callback(int i, isdn_ctrl *c)
{
int mi;
modem_info *info;
char *e;
if (i < 0)
return 0;
if ((mi = dev->m_idx[i]) >= 0) {
info = &dev->mdm.info[mi];
switch (c->command) {
case ISDN_STAT_CINF:
printk(KERN_DEBUG "CHARGEINFO on ttyI%d: %ld %s\n", info->line, c->arg, c->parm.num);
info->emu.charge = (unsigned) simple_strtoul(c->parm.num, &e, 10);
if (e == (char *)c->parm.num)
info->emu.charge = 0;
break;
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_DISPLAY:
#ifdef ISDN_TTY_STAT_DEBUG
printk(KERN_DEBUG "tty_STAT_DISPLAY ttyI%d\n", info->line);
#endif
/* Signal display to tty-device */
if ((info->emu.mdmreg[REG_DISPLAY] & BIT_DISPLAY) &&
!(info->emu.mdmreg[REG_RESPNUM] & BIT_RESPNUM)) {
isdn_tty_at_cout("\r\n", info);
isdn_tty_at_cout("DISPLAY: ", info);
isdn_tty_at_cout(c->parm.display, info);
isdn_tty_at_cout("\r\n", info);
}
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_port_active(&info->port)) {
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_port_active(&info->port)) {
if (info->dialing == 1)
isdn_tty_modem_result(RESULT_BUSY, info);
if (info->dialing > 1)
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
info->dialing = 0;
#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
/* Wake up any processes waiting
* for incoming call of this device when
* DCD follow the state of incoming carrier
*/
if (info->port.blocked_open &&
(info->emu.mdmreg[REG_DCD] & BIT_DCD)) {
wake_up_interruptible(&info->port.open_wait);
}
/* Schedule CONNECT-Message to any tty
* waiting for it and
* set DCD-bit of its modem-status.
*/
if (tty_port_active(&info->port) ||
(info->port.blocked_open &&
(info->emu.mdmreg[REG_DCD] & BIT_DCD))) {
info->msr |= UART_MSR_DCD;
info->emu.charge = 0;
if (info->dialing & 0xf)
info->last_dir = 1;
else
info->last_dir = 0;
info->dialing = 0;
info->rcvsched = 1;
if (USG_MODEM(dev->usage[i])) {
if (info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) {
strcpy(info->emu.connmsg, c->parm.num);
isdn_tty_modem_result(RESULT_CONNECT, info);
} else
isdn_tty_modem_result(RESULT_CONNECT64000, info);
}
if (USG_VOICE(dev->usage[i]))
isdn_tty_modem_result(RESULT_VCON, 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_port_active(&info->port)) {
#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_port_active(&info->port)) {
if (info->dialing) {
info->dialing = 0;
info->last_l2 = -1;
info->last_si = 0;
sprintf(info->last_cause, "0000");
isdn_tty_modem_result(RESULT_NO_DIALTONE, info);
}
isdn_tty_modem_hup(info, 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;
#ifdef CONFIG_ISDN_TTY_FAX
case ISDN_STAT_FAXIND:
if (tty_port_active(&info->port)) {
isdn_tty_fax_command(info, c);
}
break;
#endif
#ifdef CONFIG_ISDN_AUDIO
case ISDN_STAT_AUDIO:
if (tty_port_active(&info->port)) {
switch (c->parm.num[0]) {
case ISDN_AUDIO_DTMF:
if (info->vonline) {
isdn_audio_put_dle_code(info,
c->parm.num[1]);
}
break;
}
}
break;
#endif
}
}
return 0;
}
/*********************************************************************
Modem-Emulator-Routines
*********************************************************************/
#define cmdchar(c) ((c >= ' ') && (c <= 0x7f))
/*
* 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.
*/
void
isdn_tty_at_cout(char *msg, modem_info *info)
{
struct tty_port *port = &info->port;
atemu *m = &info->emu;
char *p;
char c;
u_long flags;
struct sk_buff *skb = NULL;
char *sp = NULL;
int l;
if (!msg) {
printk(KERN_WARNING "isdn_tty: Null-Message in isdn_tty_at_cout\n");
return;
}
l = strlen(msg);
spin_lock_irqsave(&info->readlock, flags);
if (info->closing) {
spin_unlock_irqrestore(&info->readlock, flags);
return;
}
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
/* use queue instead of direct, if online and */
/* data is in queue or buffer is full */
if (info->online && ((tty_buffer_request_room(port, l) < l) ||
!skb_queue_empty(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel]))) {
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
skb = alloc_skb(l, GFP_ATOMIC);
if (!skb) {
spin_unlock_irqrestore(&info->readlock, flags);
return;
}
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
sp = skb_put(skb, l);
#ifdef CONFIG_ISDN_AUDIO
ISDN_AUDIO_SKB_DLECOUNT(skb) = 0;
ISDN_AUDIO_SKB_LOCK(skb) = 0;
#endif
}
for (p = msg; *p; p++) {
switch (*p) {
case '\r':
c = m->mdmreg[REG_CR];
break;
case '\n':
c = m->mdmreg[REG_LF];
break;
case '\b':
c = m->mdmreg[REG_BS];
break;
default:
c = *p;
}
if (skb) {
*sp++ = c;
} else {
if (tty_insert_flip_char(port, c, TTY_NORMAL) == 0)
break;
}
}
if (skb) {
__skb_queue_tail(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel], skb);
dev->drv[info->isdn_driver]->rcvcount[info->isdn_channel] += skb->len;
spin_unlock_irqrestore(&info->readlock, flags);
/* Schedule dequeuing */
[PATCH] TTY layer buffering revamp The API and code have been through various bits of initial review by serial driver people but they definitely need to live somewhere for a while so the unconverted drivers can get knocked into shape, existing drivers that have been updated can be better tuned and bugs whacked out. This replaces the tty flip buffers with kmalloc objects in rings. In the normal situation for an IRQ driven serial port at typical speeds the behaviour is pretty much the same, two buffers end up allocated and the kernel cycles between them as before. When there are delays or at high speed we now behave far better as the buffer pool can grow a bit rather than lose characters. This also means that we can operate at higher speeds reliably. For drivers that receive characters in blocks (DMA based, USB and especially virtualisation) the layer allows a lot of driver specific code that works around the tty layer with private secondary queues to be removed. The IBM folks need this sort of layer, the smart serial port people do, the virtualisers do (because a virtualised tty typically operates at infinite speed rather than emulating 9600 baud). Finally many drivers had invalid and unsafe attempts to avoid buffer overflows by directly invoking tty methods extracted out of the innards of work queue structs. These are no longer needed and all go away. That fixes various random hangs with serial ports on overflow. The other change in here is to optimise the receive_room path that is used by some callers. It turns out that only one ldisc uses receive room except asa constant and it updates it far far less than the value is read. We thus make it a variable not a function call. I expect the code to contain bugs due to the size alone but I'll be watching and squashing them and feeding out new patches as it goes. Because the buffers now dynamically expand you should only run out of buffering when the kernel runs out of memory for real. That means a lot of the horrible hacks high performance drivers used to do just aren't needed any more. Description: tty_insert_flip_char is an old API and continues to work as before, as does tty_flip_buffer_push() [this is why many drivers dont need modification]. It does now also return the number of chars inserted There are also tty_buffer_request_room(tty, len) which asks for a buffer block of the length requested and returns the space found. This improves efficiency with hardware that knows how much to transfer. and tty_insert_flip_string_flags(tty, str, flags, len) to insert a string of characters and flags For a smart interface the usual code is len = tty_request_buffer_room(tty, amount_hardware_says); tty_insert_flip_string(tty, buffer_from_card, len); More description! At the moment tty buffers are attached directly to the tty. This is causing a lot of the problems related to tty layer locking, also problems at high speed and also with bursty data (such as occurs in virtualised environments) I'm working on ripping out the flip buffers and replacing them with a pool of dynamically allocated buffers. This allows both for old style "byte I/O" devices and also helps virtualisation and smart devices where large blocks of data suddenely materialise and need storing. So far so good. Lots of drivers reference tty->flip.*. Several of them also call directly and unsafely into function pointers it provides. This will all break. Most drivers can use tty_insert_flip_char which can be kept as an API but others need more. At the moment I've added the following interfaces, if people think more will be needed now is a good time to say int tty_buffer_request_room(tty, size) Try and ensure at least size bytes are available, returns actual room (may be zero). At the moment it just uses the flipbuf space but that will change. Repeated calls without characters being added are not cumulative. (ie if you call it with 1, 1, 1, and then 4 you'll have four characters of space. The other functions will also try and grow buffers in future but this will be a more efficient way when you know block sizes. int tty_insert_flip_char(tty, ch, flag) As before insert a character if there is room. Now returns 1 for success, 0 for failure. int tty_insert_flip_string(tty, str, len) Insert a block of non error characters. Returns the number inserted. int tty_prepare_flip_string(tty, strptr, len) Adjust the buffer to allow len characters to be added. Returns a buffer pointer in strptr and the length available. This allows for hardware that needs to use functions like insl or mencpy_fromio. Signed-off-by: Alan Cox <alan@redhat.com> Cc: Paul Fulghum <paulkf@microgate.com> Signed-off-by: Hirokazu Takata <takata@linux-m32r.org> Signed-off-by: Serge Hallyn <serue@us.ibm.com> Signed-off-by: Jeff Dike <jdike@addtoit.com> Signed-off-by: John Hawkes <hawkes@sgi.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Adrian Bunk <bunk@stusta.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 12:54:13 +08:00
if (dev->modempoll && info->rcvsched)
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
} else {
spin_unlock_irqrestore(&info->readlock, flags);
tty_flip_buffer_push(port);
}
}
/*
* Perform ATH Hangup
*/
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_hup(info, 1);
}
}
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 */
/*
* Check Buffer for Modem-escape-sequence, activate timer-callback to
* isdn_tty_modem_escape() if sequence found.
*
* Parameters:
* p pointer to databuffer
* plus escape-character
* count length of buffer
* 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,
u_long *lastplus)
{
if (plus > 127)
return;
if (count > 3) {
p += count - 3;
count = 3;
*pluscount = 0;
}
while (count > 0) {
if (*(p++) == plus) {
if ((*pluscount)++) {
/* Time since last '+' > 0.5 sec. ? */
if (time_after(jiffies, *lastplus + PLUSWAIT1))
*pluscount = 1;
} else {
/* Time since last non-'+' < 1.5 sec. ? */
if (time_before(jiffies, *lastplus + PLUSWAIT2))
*pluscount = 0;
}
if ((*pluscount == 3) && (count == 1))
isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, 1);
if (*pluscount > 3)
*pluscount = 1;
} else
*pluscount = 0;
*lastplus = jiffies;
count--;
}
}
/*
* Return result of AT-emulator to tty-receive-buffer, depending on
* modem-register 12, bit 0 and 1.
* For CONNECT-messages also switch to online-mode.
* For RING-message handle auto-ATA if register 0 != 0
*/
static void
isdn_tty_modem_result(int code, modem_info *info)
{
atemu *m = &info->emu;
static char *msg[] =
{"OK", "CONNECT", "RING", "NO CARRIER", "ERROR",
"CONNECT 64000", "NO DIALTONE", "BUSY", "NO ANSWER",
"RINGING", "NO MSN/EAZ", "VCON", "RUNG"};
char s[ISDN_MSNLEN + 10];
switch (code) {
case RESULT_RING:
m->mdmreg[REG_RINGCNT]++;
if (m->mdmreg[REG_RINGCNT] == m->mdmreg[REG_RINGATA])
/* Automatically accept incoming call */
isdn_tty_cmd_ATA(info);
break;
case RESULT_NO_CARRIER:
#ifdef ISDN_DEBUG_MODEM_HUP
printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n",
info->closing, !info->port.tty);
#endif
m->mdmreg[REG_RINGCNT] = 0;
del_timer(&info->nc_timer);
info->ncarrier = 0;
if (info->closing || !info->port.tty)
return;
#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 RESULT_CONNECT:
case RESULT_CONNECT64000:
sprintf(info->last_cause, "0000");
if (!info->online)
info->online = 2;
break;
case RESULT_VCON:
#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;
} /* switch (code) */
if (m->mdmreg[REG_RESP] & BIT_RESP) {
/* Show results */
if (m->mdmreg[REG_RESPNUM] & BIT_RESPNUM) {
/* Show numeric results only */
sprintf(s, "\r\n%d\r\n", code);
isdn_tty_at_cout(s, info);
} else {
if (code == RESULT_RING) {
/* return if "show RUNG" and ringcounter>1 */
if ((m->mdmreg[REG_RUNG] & BIT_RUNG) &&
(m->mdmreg[REG_RINGCNT] > 1))
return;
/* print CID, _before_ _every_ ring */
if (!(m->mdmreg[REG_CIDONCE] & BIT_CIDONCE)) {
isdn_tty_at_cout("\r\nCALLER NUMBER: ", info);
isdn_tty_at_cout(dev->num[info->drv_index], info);
if (m->mdmreg[REG_CDN] & BIT_CDN) {
isdn_tty_at_cout("\r\nCALLED NUMBER: ", info);
isdn_tty_at_cout(info->emu.cpn, info);
}
}
}
isdn_tty_at_cout("\r\n", info);
isdn_tty_at_cout(msg[code], info);
switch (code) {
case RESULT_CONNECT:
switch (m->mdmreg[REG_L2PROT]) {
case ISDN_PROTO_L2_MODEM:
isdn_tty_at_cout(" ", info);
isdn_tty_at_cout(m->connmsg, info);
break;
}
break;
case RESULT_RING:
/* Append CPN, if enabled */
if ((m->mdmreg[REG_CPN] & BIT_CPN)) {
sprintf(s, "/%s", m->cpn);
isdn_tty_at_cout(s, info);
}
/* Print CID only once, _after_ 1st RING */
if ((m->mdmreg[REG_CIDONCE] & BIT_CIDONCE) &&
(m->mdmreg[REG_RINGCNT] == 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);
if (m->mdmreg[REG_CDN] & BIT_CDN) {
isdn_tty_at_cout("\r\nCALLED NUMBER: ", info);
isdn_tty_at_cout(info->emu.cpn, info);
}
}
break;
case RESULT_NO_CARRIER:
case RESULT_NO_DIALTONE:
case RESULT_BUSY:
case RESULT_NO_ANSWER:
m->mdmreg[REG_RINGCNT] = 0;
/* Append Cause-Message if enabled */
if (m->mdmreg[REG_RESPXT] & BIT_RESPXT) {
sprintf(s, "/%s", info->last_cause);
isdn_tty_at_cout(s, info);
}
break;
case RESULT_CONNECT64000:
/* Append Protocol to CONNECT message */
switch (m->mdmreg[REG_L2PROT]) {
case ISDN_PROTO_L2_X75I:
case ISDN_PROTO_L2_X75UI:
case ISDN_PROTO_L2_X75BUI:
isdn_tty_at_cout("/X.75", info);
break;
case ISDN_PROTO_L2_HDLC:
isdn_tty_at_cout("/HDLC", info);
break;
case ISDN_PROTO_L2_V11096:
isdn_tty_at_cout("/V110/9600", info);
break;
case ISDN_PROTO_L2_V11019:
isdn_tty_at_cout("/V110/19200", info);
break;
case ISDN_PROTO_L2_V11038:
isdn_tty_at_cout("/V110/38400", info);
break;
}
if (m->mdmreg[REG_T70] & BIT_T70) {
isdn_tty_at_cout("/T.70", info);
if (m->mdmreg[REG_T70] & BIT_T70_EXT)
isdn_tty_at_cout("+", info);
}
break;
}
isdn_tty_at_cout("\r\n", info);
}
}
if (code == RESULT_NO_CARRIER) {
if (info->closing || (!info->port.tty))
return;
if (tty_port_check_carrier(&info->port))
tty_hangup(info->port.tty);
}
}
/*
* Display a modem-register-value.
*/
static void
isdn_tty_show_profile(int ridx, modem_info *info)
{
char v[6];
sprintf(v, "\r\n%d", info->emu.mdmreg[ridx]);
isdn_tty_at_cout(v, info);
}
/*
* Get MSN-string from char-pointer, set pointer to end of number
*/
static void
isdn_tty_get_msnstr(char *n, char **p)
{
int limit = ISDN_MSNLEN - 1;
while (((*p[0] >= '0' && *p[0] <= '9') ||
/* Why a comma ??? */
(*p[0] == ',') || (*p[0] == ':')) &&
(limit--))
*n++ = *p[0]++;
*n = '\0';
}
/*
* Get phone-number from modem-commandbuffer
*/
static void
isdn_tty_getdial(char *p, char *q, int cnt)
{
int first = 1;
int limit = ISDN_MSNLEN - 1; /* MUST match the size of interface var to avoid
buffer overflow */
while (strchr(" 0123456789,#.*WPTSR-", *p) && *p && --cnt > 0) {
if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first) ||
((*p == 'R') && first) ||
(*p == '*') || (*p == '#')) {
*q++ = *p;
limit--;
}
if (!limit)
break;
p++;
first = 0;
}
*q = 0;
}
#define PARSE_ERROR { isdn_tty_modem_result(RESULT_ERROR, info); return; }
#define PARSE_ERROR1 { isdn_tty_modem_result(RESULT_ERROR, 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("X.75i", info);
break;
case ISDN_PROTO_L2_X75UI:
isdn_tty_at_cout("X.75ui", info);
break;
case ISDN_PROTO_L2_X75BUI:
isdn_tty_at_cout("X.75bui", info);
break;
case ISDN_PROTO_L2_HDLC:
isdn_tty_at_cout("HDLC", info);
break;
case ISDN_PROTO_L2_V11096:
isdn_tty_at_cout("V.110 9600 Baud", info);
break;
case ISDN_PROTO_L2_V11019:
isdn_tty_at_cout("V.110 19200 Baud", info);
break;
case ISDN_PROTO_L2_V11038:
isdn_tty_at_cout("V.110 38400 Baud", info);
break;
case ISDN_PROTO_L2_TRANS:
isdn_tty_at_cout("transparent", info);
break;
case ISDN_PROTO_L2_MODEM:
isdn_tty_at_cout("modem", info);
break;
case ISDN_PROTO_L2_FAX:
isdn_tty_at_cout("fax", info);
break;
default:
isdn_tty_at_cout("unknown", info);
break;
}
if (m->mdmreg[REG_T70] & BIT_T70) {
isdn_tty_at_cout("/T.70", info);
if (m->mdmreg[REG_T70] & BIT_T70_EXT)
isdn_tty_at_cout("+", info);
}
isdn_tty_at_cout("\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)
{
atemu *m = &info->emu;
int i;
char rb[100];
#define MAXRB (sizeof(rb) - 1)
switch (*p[0]) {
case 'B':
/* &B - Set Buffersize */
p[0]++;
i = isdn_getnum(p);
if ((i < 0) || (i > ISDN_SERIAL_XMIT_MAX))
PARSE_ERROR1;
#ifdef CONFIG_ISDN_AUDIO
if ((m->mdmreg[REG_SI1] & 1) && (i > VBUF))
PARSE_ERROR1;
#endif
m->mdmreg[REG_PSIZE] = i / 16;
info->xmit_size = m->mdmreg[REG_PSIZE] * 16;
switch (m->mdmreg[REG_L2PROT]) {
case ISDN_PROTO_L2_V11096:
case ISDN_PROTO_L2_V11019:
case ISDN_PROTO_L2_V11038:
info->xmit_size /= 10;
}
break;
case 'C':
/* &C - DCD Status */
p[0]++;
switch (isdn_getnum(p)) {
case 0:
m->mdmreg[REG_DCD] &= ~BIT_DCD;
break;
case 1:
m->mdmreg[REG_DCD] |= BIT_DCD;
break;
default:
PARSE_ERROR1
}
break;
case 'D':
/* &D - Set DTR-Low-behavior */
p[0]++;
switch (isdn_getnum(p)) {
case 0:
m->mdmreg[REG_DTRHUP] &= ~BIT_DTRHUP;
m->mdmreg[REG_DTRR] &= ~BIT_DTRR;
break;
case 2:
m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP;
m->mdmreg[REG_DTRR] &= ~BIT_DTRR;
break;
case 3:
m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP;
m->mdmreg[REG_DTRR] |= BIT_DTRR;
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]++;
if (info->msr & UART_MSR_DCD)
PARSE_ERROR1;
isdn_tty_reset_profile(m);
isdn_tty_modem_reset_regs(info, 1);
break;
#ifdef DUMMY_HAYES_AT
case 'K':
/* only for be compilant with common scripts */
/* &K Flowcontrol - no function */
p[0]++;
isdn_getnum(p);
break;
#endif
case 'L':
/* &L -Set Numbers to listen on */
p[0]++;
i = 0;
while (*p[0] && (strchr("0123456789,-*[]?;", *p[0])) &&
(i < ISDN_LMSNLEN - 1))
m->lmsn[i++] = *p[0]++;
m->lmsn[i] = '\0';
break;
case 'R':
/* &R - Set V.110 bitrate adaption */
p[0]++;
i = isdn_getnum(p);
switch (i) {
case 0:
/* Switch off V.110, back to X.75 */
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I;
m->mdmreg[REG_SI2] = 0;
info->xmit_size = m->mdmreg[REG_PSIZE] * 16;
break;
case 9600:
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11096;
m->mdmreg[REG_SI2] = 197;
info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10;
break;
case 19200:
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11019;
m->mdmreg[REG_SI2] = 199;
info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10;
break;
case 38400:
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11038;
m->mdmreg[REG_SI2] = 198; /* no existing standard for this */
info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10;
break;
default:
PARSE_ERROR1;
}
/* Switch off T.70 */
m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT);
/* Set Service 7 */
m->mdmreg[REG_SI1] |= 4;
break;
case 'S':
/* &S - Set Windowsize */
p[0]++;
i = isdn_getnum(p);
if ((i > 0) && (i < 9))
m->mdmreg[REG_WSIZE] = 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_NUMREG; 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: %.50s\r\n",
strlen(m->msn) ? m->msn : "None");
isdn_tty_at_cout(rb, info);
if (strlen(m->lmsn)) {
isdn_tty_at_cout("\r\nListen: ", info);
isdn_tty_at_cout(m->lmsn, info);
isdn_tty_at_cout("\r\n", 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 and T.70 */
p[0]++;
switch (isdn_getnum(p)) {
case 0:
m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT);
info->xmit_size = m->mdmreg[REG_PSIZE] * 16;
break;
case 1:
m->mdmreg[REG_T70] |= BIT_T70;
m->mdmreg[REG_T70] &= ~BIT_T70_EXT;
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I;
info->xmit_size = 112;
m->mdmreg[REG_SI1] = 4;
m->mdmreg[REG_SI2] = 0;
break;
case 2:
m->mdmreg[REG_T70] |= (BIT_T70 | BIT_T70_EXT);
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I;
info->xmit_size = 112;
m->mdmreg[REG_SI1] = 4;
m->mdmreg[REG_SI2] = 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 REG_L2PROT:
if (mval > ISDN_PROTO_L2_MAX)
return 1;
break;
case REG_PSIZE:
if ((mval * 16) > ISDN_SERIAL_XMIT_MAX)
return 1;
#ifdef CONFIG_ISDN_AUDIO
if ((m->mdmreg[REG_SI1] & 1) && (mval > VBUFX))
return 1;
#endif
info->xmit_size = mval * 16;
switch (m->mdmreg[REG_L2PROT]) {
case ISDN_PROTO_L2_V11096:
case ISDN_PROTO_L2_V11019:
case ISDN_PROTO_L2_V11038:
info->xmit_size /= 10;
}
break;
case REG_SI1I:
case REG_PLAN:
case REG_SCREEN:
/* readonly registers */
return 1;
}
return 0;
}
/*
* Perform ATS command
*/
static int
isdn_tty_cmd_ATS(char **p, modem_info *info)
{
atemu *m = &info->emu;
int bitpos;
int mreg;
int mval;
int bval;
mreg = isdn_getnum(p);
if (mreg < 0 || mreg >= ISDN_MODEM_NUMREG)
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)
{
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[REG_RINGCNT] = 0;
info->msr &= ~UART_MSR_RI;
l2 = m->mdmreg[REG_L2PROT];
#ifdef CONFIG_ISDN_AUDIO
/* If more than one bit set in reg18, autoselect Layer2 */
if ((m->mdmreg[REG_SI1] & m->mdmreg[REG_SI1I]) != m->mdmreg[REG_SI1]) {
if (m->mdmreg[REG_SI1I] == 1) {
if ((l2 != ISDN_PROTO_L2_MODEM) && (l2 != ISDN_PROTO_L2_FAX))
l2 = ISDN_PROTO_L2_TRANS;
} else
l2 = ISDN_PROTO_L2_X75I;
}
#endif
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL2;
cmd.arg = info->isdn_channel + (l2 << 8);
info->last_l2 = l2;
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_SETL3;
cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8);
#ifdef CONFIG_ISDN_TTY_FAX
if (l2 == ISDN_PROTO_L2_FAX) {
cmd.parm.fax = info->fax;
info->fax->direction = ISDN_TTY_FAX_CONN_IN;
}
#endif
isdn_command(&cmd);
cmd.driver = info->isdn_driver;
cmd.arg = info->isdn_channel;
cmd.command = ISDN_CMD_ACCEPTD;
info->dialing = 16;
info->emu.carrierwait = 0;
isdn_command(&cmd);
isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1);
} else
isdn_tty_modem_result(RESULT_NO_ANSWER, info);
}
#ifdef CONFIG_ISDN_AUDIO
/*
* Parse AT+F.. commands
*/
static int
isdn_tty_cmd_PLUSF(char **p, modem_info *info)
{
atemu *m = &info->emu;
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[REG_SI1] & 1) ? 8 : 0);
#ifdef CONFIG_ISDN_TTY_FAX
if (TTY_IS_FCLASS2(info))
sprintf(rs, "\r\n2");
else if (TTY_IS_FCLASS1(info))
sprintf(rs, "\r\n1");
#endif
isdn_tty_at_cout(rs, info);
break;
case '=':
p[0]++;
switch (*p[0]) {
case '0':
p[0]++;
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I;
m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS;
m->mdmreg[REG_SI1] = 4;
info->xmit_size =
m->mdmreg[REG_PSIZE] * 16;
break;
#ifdef CONFIG_ISDN_TTY_FAX
case '1':
p[0]++;
if (!(dev->global_features &
ISDN_FEATURE_L3_FCLASS1))
PARSE_ERROR1;
m->mdmreg[REG_SI1] = 1;
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX;
m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS1;
info->xmit_size =
m->mdmreg[REG_PSIZE] * 16;
break;
case '2':
p[0]++;
if (!(dev->global_features &
ISDN_FEATURE_L3_FCLASS2))
PARSE_ERROR1;
m->mdmreg[REG_SI1] = 1;
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX;
m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS2;
info->xmit_size =
m->mdmreg[REG_PSIZE] * 16;
break;
#endif
case '8':
p[0]++;
/* L2 will change on dialout with si=1 */
m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I;
m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS;
m->mdmreg[REG_SI1] = 5;
info->xmit_size = VBUF;
break;
case '?':
p[0]++;
strcpy(rs, "\r\n0,");
#ifdef CONFIG_ISDN_TTY_FAX
if (dev->global_features &
ISDN_FEATURE_L3_FCLASS1)
strcat(rs, "1,");
if (dev->global_features &
ISDN_FEATURE_L3_FCLASS2)
strcat(rs, "2,");
#endif
strcat(rs, "8");
isdn_tty_at_cout(rs, info);
break;
default:
PARSE_ERROR1;
}
break;
default:
PARSE_ERROR1;
}
return 0;
}
#ifdef CONFIG_ISDN_TTY_FAX
return (isdn_tty_cmd_PLUSF_FAX(p, info));
#else
PARSE_ERROR1;
#endif
}
/*
* Parse AT+V.. commands
*/
static int
isdn_tty_cmd_PLUSV(char **p, modem_info *info)
{
atemu *m = &info->emu;
isdn_ctrl cmd;
static char *vcmd[] =
{"NH", "IP", "LS", "RX", "SD", "SM", "TX", "DD", 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(RESULT_NO_ANSWER, 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;
}
info->silence_state = isdn_audio_silence_init(info->silence_state);
if (!info->silence_state) {
printk(KERN_WARNING "isdn_tty: Couldn't malloc silence 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(RESULT_CONNECT, 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]++;
if ((*p[0] >= '0') && (*p[0] <= '9')) {
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;
} else
if (*p[0] == '?') {
p[0]++;
isdn_tty_at_cout("\r\n<0-31>,<0-255>",
info);
break;
} else
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)\r\n",
info);
isdn_tty_at_cout("6;ULAW;8;0;(8000)\r\n",
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(RESULT_NO_ANSWER, 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(RESULT_CONNECT, info);
return 0;
break;
case 7:
/* AT+VDD - DTMF detection */
switch (*p[0]) {
case '?':
p[0]++;
sprintf(rs, "\r\n<%d>,<%d>",
m->vpar[4],
m->vpar[5]);
isdn_tty_at_cout(rs, info);
break;
case '=':
p[0]++;
if ((*p[0] >= '0') && (*p[0] <= '9')) {
if (info->online != 1)
PARSE_ERROR1;
par1 = isdn_getnum(p);
if ((par1 < 0) || (par1 > 15))
PARSE_ERROR1;
if (*p[0] != ',')
PARSE_ERROR1;
p[0]++;
par2 = isdn_getnum(p);
if ((par2 < 0) || (par2 > 255))
PARSE_ERROR1;
m->vpar[4] = par1;
m->vpar[5] = par2;
cmd.driver = info->isdn_driver;
cmd.command = ISDN_CMD_AUDIO;
cmd.arg = info->isdn_channel + (ISDN_AUDIO_SETDD << 8);
cmd.parm.num[0] = par1;
cmd.parm.num[1] = par2;
isdn_command(&cmd);
break;
} else
if (*p[0] == '?') {
p[0]++;
isdn_tty_at_cout("\r\n<0-15>,<0-255>",
info);
break;
} else
PARSE_ERROR1;
break;
default:
PARSE_ERROR1;
}
break;
default:
PARSE_ERROR1;
}
return 0;
}
#endif /* CONFIG_ISDN_AUDIO */
/*
* Parse and perform an AT-command-line.
*/
static void
isdn_tty_parse_at(modem_info *info)
{
atemu *m = &info->emu;
char *p;
char ds[ISDN_MSNLEN];
#ifdef ISDN_DEBUG_AT
printk(KERN_DEBUG "AT: '%s'\n", m->mdmcmd);
#endif
for (p = &m->mdmcmd[2]; *p;) {
switch (*p) {
case ' ':
p++;
break;
case 'A':
/* A - Accept incoming call */
p++;
isdn_tty_cmd_ATA(info);
return;
case 'D':
/* D - Dial */
if (info->msr & UART_MSR_DCD)
PARSE_ERROR;
if (info->msr & UART_MSR_RI) {
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
return;
}
isdn_tty_getdial(++p, ds, sizeof ds);
p += strlen(p);
if (!strlen(m->msn))
isdn_tty_modem_result(RESULT_NO_MSN_EAZ, 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[REG_ECHO] &= ~BIT_ECHO;
break;
case 1:
m->mdmreg[REG_ECHO] |= BIT_ECHO;
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;
case '3':
p++;
snprintf(ds, sizeof(ds), "\r\n%d", info->emu.charge);
isdn_tty_at_cout(ds, info);
break;
default:;
}
break;
#ifdef DUMMY_HAYES_AT
case 'L':
case 'M':
/* only for be compilant with common scripts */
/* no function */
p++;
isdn_getnum(&p);
break;
#endif
case 'O':
/* O - Go online */
p++;
if (info->msr & UART_MSR_DCD)
/* if B-Channel is up */
isdn_tty_modem_result((m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) ? RESULT_CONNECT : RESULT_CONNECT64000, info);
else
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
return;
case 'Q':
/* Q - Turn Emulator messages on/off */
p++;
switch (isdn_getnum(&p)) {
case 0:
m->mdmreg[REG_RESP] |= BIT_RESP;
break;
case 1:
m->mdmreg[REG_RESP] &= ~BIT_RESP;
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[REG_RESP] |= BIT_RESPNUM;
break;
case 1:
m->mdmreg[REG_RESP] &= ~BIT_RESPNUM;
break;
default:
PARSE_ERROR;
}
break;
case 'Z':
/* Z - Load Registers from Profile */
p++;
if (info->msr & UART_MSR_DCD) {
info->online = 0;
isdn_tty_on_hook(info);
}
isdn_tty_modem_reset_regs(info, 1);
break;
case '+':
p++;
switch (*p) {
#ifdef CONFIG_ISDN_AUDIO
case 'F':
p++;
if (isdn_tty_cmd_PLUSF(&p, info))
return;
break;
case 'V':
if ((!(m->mdmreg[REG_SI1] & 1)) ||
(m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM))
PARSE_ERROR;
p++;
if (isdn_tty_cmd_PLUSV(&p, info))
return;
break;
#endif /* CONFIG_ISDN_AUDIO */
case 'S': /* SUSPEND */
p++;
isdn_tty_get_msnstr(ds, &p);
isdn_tty_suspend(ds, info, m);
break;
case 'R': /* RESUME */
p++;
isdn_tty_get_msnstr(ds, &p);
isdn_tty_resume(ds, info, m);
break;
case 'M': /* MESSAGE */
p++;
isdn_tty_send_msg(info, m, p);
break;
default:
PARSE_ERROR;
}
break;
case '&':
p++;
if (isdn_tty_cmd_ATand(&p, info))
return;
break;
default:
PARSE_ERROR;
}
}
#ifdef CONFIG_ISDN_AUDIO
if (!info->vonline)
#endif
isdn_tty_modem_result(RESULT_OK, info);
}
/* Need own toupper() because standard-toupper is not available
* within modules.
*/
#define my_toupper(c) (((c >= 'a') && (c <= 'z')) ? (c & 0xdf) : c)
/*
* Perform line-editing of AT-commands
*
* Parameters:
* p inputbuffer
* count length of buffer
* channel index to line (minor-device)
*/
static int
isdn_tty_edit_at(const char *p, int count, modem_info *info)
{
atemu *m = &info->emu;
int total = 0;
u_char c;
char eb[2];
int cnt;
for (cnt = count; cnt > 0; p++, cnt--) {
c = *p;
total++;
if (c == m->mdmreg[REG_CR] || c == m->mdmreg[REG_LF]) {
/* Separator (CR or LF) */
m->mdmcmd[m->mdmcmdl] = 0;
if (m->mdmreg[REG_ECHO] & BIT_ECHO) {
eb[0] = c;
eb[1] = 0;
isdn_tty_at_cout(eb, info);
}
if ((m->mdmcmdl >= 2) && (!(strncmp(m->mdmcmd, "AT", 2))))
isdn_tty_parse_at(info);
m->mdmcmdl = 0;
continue;
}
if (c == m->mdmreg[REG_BS] && m->mdmreg[REG_BS] < 128) {
/* Backspace-Function */
if ((m->mdmcmdl > 2) || (!m->mdmcmdl)) {
if (m->mdmcmdl)
m->mdmcmdl--;
if (m->mdmreg[REG_ECHO] & BIT_ECHO)
isdn_tty_at_cout("\b", info);
}
continue;
}
if (cmdchar(c)) {
if (m->mdmreg[REG_ECHO] & BIT_ECHO) {
eb[0] = c;
eb[1] = 0;
isdn_tty_at_cout(eb, info);
}
if (m->mdmcmdl < 255) {
c = my_toupper(c);
switch (m->mdmcmdl) {
case 1:
if (c == 'T') {
m->mdmcmd[m->mdmcmdl] = c;
m->mdmcmd[++m->mdmcmdl] = 0;
break;
} else
m->mdmcmdl = 0;
/* Fall through, check for 'A' */
case 0:
if (c == 'A') {
m->mdmcmd[m->mdmcmdl] = c;
m->mdmcmd[++m->mdmcmdl] = 0;
}
break;
default:
m->mdmcmd[m->mdmcmdl] = c;
m->mdmcmd[++m->mdmcmdl] = 0;
}
}
}
}
return total;
}
/*
* 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
* timer-dispatcher isdn_timer_function()
*/
void
isdn_tty_modem_escape(void)
{
int ton = 0;
int i;
int midx;
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (USG_MODEM(dev->usage[i]) && (midx = dev->m_idx[i]) >= 0) {
modem_info *info = &dev->mdm.info[midx];
if (info->online) {
ton = 1;
if ((info->emu.pluscount == 3) &&
time_after(jiffies,
info->emu.lastplus + PLUSWAIT2)) {
info->emu.pluscount = 0;
info->online = 0;
isdn_tty_modem_result(RESULT_OK, 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
* timer-dispatcher isdn_timer_function()
*/
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(RESULT_RING, info);
}
}
isdn_timer_ctrl(ISDN_TIMER_MODEMRING, ton);
}
/*
* For all online tty's, try sending data to
* the lower levels.
*/
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);
}
}
isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, ton);
}
/*
* Check all channels if we have a 'no carrier' timeout.
* Timeout value is set by Register S7.
*/
void
isdn_tty_carrier_timeout(void)
{
int ton = 0;
int i;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
modem_info *info = &dev->mdm.info[i];
if (!info->dialing)
continue;
if (info->emu.carrierwait++ > info->emu.mdmreg[REG_WAITC]) {
info->dialing = 0;
isdn_tty_modem_result(RESULT_NO_CARRIER, info);
isdn_tty_modem_hup(info, 1);
} else
ton = 1;
}
isdn_timer_ctrl(ISDN_TIMER_CARRIER, ton);
}