mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-18 09:44:18 +08:00
33f0f88f1c
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>
3900 lines
95 KiB
C
3900 lines
95 KiB
C
/* $Id: isdn_tty.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $
|
|
*
|
|
* 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/config.h>
|
|
#include <linux/isdn.h>
|
|
#include <linux/delay.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 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};
|
|
|
|
char *isdn_tty_revision = "$Revision: 1.1.2.3 $";
|
|
|
|
|
|
/* 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)
|
|
{
|
|
int c;
|
|
int len;
|
|
struct tty_struct *tty;
|
|
char last;
|
|
|
|
if (info->online) {
|
|
if ((tty = info->tty)) {
|
|
if (info->mcr & UART_MCR_RTS) {
|
|
len = skb->len
|
|
#ifdef CONFIG_ISDN_AUDIO
|
|
+ ISDN_AUDIO_SKB_DLECOUNT(skb)
|
|
#endif
|
|
;
|
|
|
|
c = tty_buffer_request_room(tty, len);
|
|
if (c >= len) {
|
|
#ifdef CONFIG_ISDN_AUDIO
|
|
if (ISDN_AUDIO_SKB_DLECOUNT(skb)) {
|
|
int l = skb->len;
|
|
unsigned char *dp = skb->data;
|
|
while (--l) {
|
|
if (*skb->data == DLE)
|
|
tty_insert_flip_char(tty, DLE, 0);
|
|
tty_insert_flip_char(tty, *dp++, 0);
|
|
}
|
|
last = *dp;
|
|
} else {
|
|
#endif
|
|
if(len > 1)
|
|
tty_insert_flip_string(tty, 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(tty, last, 0xFF);
|
|
else
|
|
tty_insert_flip_char(tty, last, TTY_NORMAL);
|
|
tty_flip_buffer_push(tty);
|
|
kfree_skb(skb);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* 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;
|
|
struct tty_struct *tty;
|
|
modem_info *info;
|
|
|
|
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
|
|
if ((midx = dev->m_idx[i]) >= 0) {
|
|
info = &dev->mdm.info[midx];
|
|
if (info->online) {
|
|
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 ((tty = info->tty)) {
|
|
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, tty, 0);
|
|
else
|
|
r = isdn_readbchan_tty(info->isdn_driver, info->isdn_channel, tty, 1);
|
|
if (r)
|
|
tty_flip_buffer_push(tty);
|
|
} else
|
|
r = 1;
|
|
} 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
|
|
/* 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->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->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->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
|
|
* 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->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>
|
|
|
|
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. unfortunatly 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 = strlen(msg);
|
|
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)
|
|
{
|
|
uint cflag,
|
|
cval,
|
|
fcr,
|
|
quot;
|
|
int i;
|
|
|
|
if (!info->tty || !info->tty->termios)
|
|
return;
|
|
cflag = info->tty->termios->c_cflag;
|
|
|
|
quot = i = cflag & CBAUD;
|
|
if (i & CBAUDEX) {
|
|
i &= ~CBAUDEX;
|
|
if (i < 1 || i > 2)
|
|
info->tty->termios->c_cflag &= ~CBAUDEX;
|
|
else
|
|
i += 15;
|
|
}
|
|
if (quot) {
|
|
info->mcr |= UART_MCR_DTR;
|
|
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;
|
|
fcr = 0;
|
|
|
|
/* CTS flow control flag and modem status interrupts */
|
|
if (cflag & CRTSCTS) {
|
|
info->flags |= ISDN_ASYNC_CTS_FLOW;
|
|
} else
|
|
info->flags &= ~ISDN_ASYNC_CTS_FLOW;
|
|
if (cflag & CLOCAL)
|
|
info->flags &= ~ISDN_ASYNC_CHECK_CD;
|
|
else {
|
|
info->flags |= ISDN_ASYNC_CHECK_CD;
|
|
}
|
|
}
|
|
|
|
static int
|
|
isdn_tty_startup(modem_info * info)
|
|
{
|
|
if (info->flags & ISDN_ASYNC_INITIALIZED)
|
|
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->tty)
|
|
clear_bit(TTY_IO_ERROR, &info->tty->flags);
|
|
/*
|
|
* and set the speed of the serial port
|
|
*/
|
|
isdn_tty_change_speed(info);
|
|
|
|
info->flags |= ISDN_ASYNC_INITIALIZED;
|
|
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 (!(info->flags & ISDN_ASYNC_INITIALIZED))
|
|
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->tty || (info->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->tty)
|
|
set_bit(TTY_IO_ERROR, &info->tty->flags);
|
|
|
|
info->flags &= ~ISDN_ASYNC_INITIALIZED;
|
|
}
|
|
|
|
/* 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->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;
|
|
wake_up_interruptible(&tty->write_wait);
|
|
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, struct file *file)
|
|
{
|
|
modem_info *info = (modem_info *) tty->driver_data;
|
|
u_char control, status;
|
|
|
|
if (isdn_tty_paranoia_check(info, tty->name, __FUNCTION__))
|
|
return -ENODEV;
|
|
if (tty->flags & (1 << TTY_IO_ERROR))
|
|
return -EIO;
|
|
|
|
#ifdef ISDN_DEBUG_MODEM_IOCTL
|
|
printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line);
|
|
#endif
|
|
|
|
control = info->mcr;
|
|
status = info->msr;
|
|
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, struct file *file,
|
|
unsigned int set, unsigned int clear)
|
|
{
|
|
modem_info *info = (modem_info *) tty->driver_data;
|
|
|
|
if (isdn_tty_paranoia_check(info, tty->name, __FUNCTION__))
|
|
return -ENODEV;
|
|
if (tty->flags & (1 << TTY_IO_ERROR))
|
|
return -EIO;
|
|
|
|
#ifdef ISDN_DEBUG_MODEM_IOCTL
|
|
printk(KERN_DEBUG "ttyI%d ioctl TIOCMxxx: %x %x\n", info->line, set, clear);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
isdn_tty_ioctl(struct tty_struct *tty, struct file *file,
|
|
uint cmd, ulong arg)
|
|
{
|
|
modem_info *info = (modem_info *) tty->driver_data;
|
|
int retval;
|
|
|
|
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_ioctl"))
|
|
return -ENODEV;
|
|
if (tty->flags & (1 << TTY_IO_ERROR))
|
|
return -EIO;
|
|
switch (cmd) {
|
|
case TCSBRK: /* SVID version: non-zero arg --> no break */
|
|
#ifdef 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 TIOCGSOFTCAR:
|
|
#ifdef ISDN_DEBUG_MODEM_IOCTL
|
|
printk(KERN_DEBUG "ttyI%d ioctl TIOCGSOFTCAR\n", info->line);
|
|
#endif
|
|
return put_user(C_CLOCAL(tty) ? 1 : 0, (ulong __user *) arg);
|
|
case TIOCSSOFTCAR:
|
|
#ifdef ISDN_DEBUG_MODEM_IOCTL
|
|
printk(KERN_DEBUG "ttyI%d ioctl TIOCSSOFTCAR\n", info->line);
|
|
#endif
|
|
if (get_user(arg, (ulong __user *) arg))
|
|
return -EFAULT;
|
|
tty->termios->c_cflag =
|
|
((tty->termios->c_cflag & ~CLOCAL) |
|
|
(arg ? CLOCAL : 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 termios *old_termios)
|
|
{
|
|
modem_info *info = (modem_info *) tty->driver_data;
|
|
|
|
if (!old_termios)
|
|
isdn_tty_change_speed(info);
|
|
else {
|
|
if (tty->termios->c_cflag == old_termios->c_cflag)
|
|
return;
|
|
isdn_tty_change_speed(info);
|
|
if ((old_termios->c_cflag & CRTSCTS) &&
|
|
!(tty->termios->c_cflag & CRTSCTS)) {
|
|
tty->hw_stopped = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* ------------------------------------------------------------
|
|
* isdn_tty_open() and friends
|
|
* ------------------------------------------------------------
|
|
*/
|
|
static int
|
|
isdn_tty_block_til_ready(struct tty_struct *tty, struct file *filp, modem_info * info)
|
|
{
|
|
DECLARE_WAITQUEUE(wait, NULL);
|
|
int do_clocal = 0;
|
|
int retval;
|
|
|
|
/*
|
|
* If the device is in the middle of being closed, then block
|
|
* until it's done, and then try again.
|
|
*/
|
|
if (tty_hung_up_p(filp) ||
|
|
(info->flags & ISDN_ASYNC_CLOSING)) {
|
|
if (info->flags & ISDN_ASYNC_CLOSING)
|
|
interruptible_sleep_on(&info->close_wait);
|
|
#ifdef MODEM_DO_RESTART
|
|
if (info->flags & ISDN_ASYNC_HUP_NOTIFY)
|
|
return -EAGAIN;
|
|
else
|
|
return -ERESTARTSYS;
|
|
#else
|
|
return -EAGAIN;
|
|
#endif
|
|
}
|
|
/*
|
|
* If non-blocking mode is set, then make the check up front
|
|
* and then exit.
|
|
*/
|
|
if ((filp->f_flags & O_NONBLOCK) ||
|
|
(tty->flags & (1 << TTY_IO_ERROR))) {
|
|
if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE)
|
|
return -EBUSY;
|
|
info->flags |= ISDN_ASYNC_NORMAL_ACTIVE;
|
|
return 0;
|
|
}
|
|
if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) {
|
|
if (info->normal_termios.c_cflag & CLOCAL)
|
|
do_clocal = 1;
|
|
} else {
|
|
if (tty->termios->c_cflag & CLOCAL)
|
|
do_clocal = 1;
|
|
}
|
|
/*
|
|
* Block waiting for the carrier detect and the line to become
|
|
* free (i.e., not in use by the callout). While we are in
|
|
* this loop, info->count is dropped by one, so that
|
|
* isdn_tty_close() knows when to free things. We restore it upon
|
|
* exit, either normal or abnormal.
|
|
*/
|
|
retval = 0;
|
|
add_wait_queue(&info->open_wait, &wait);
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_block_til_ready before block: ttyi%d, count = %d\n",
|
|
info->line, info->count);
|
|
#endif
|
|
if (!(tty_hung_up_p(filp)))
|
|
info->count--;
|
|
info->blocked_open++;
|
|
while (1) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (tty_hung_up_p(filp) ||
|
|
!(info->flags & ISDN_ASYNC_INITIALIZED)) {
|
|
#ifdef MODEM_DO_RESTART
|
|
if (info->flags & ISDN_ASYNC_HUP_NOTIFY)
|
|
retval = -EAGAIN;
|
|
else
|
|
retval = -ERESTARTSYS;
|
|
#else
|
|
retval = -EAGAIN;
|
|
#endif
|
|
break;
|
|
}
|
|
if (!(info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) &&
|
|
!(info->flags & ISDN_ASYNC_CLOSING) &&
|
|
(do_clocal || (info->msr & UART_MSR_DCD))) {
|
|
break;
|
|
}
|
|
if (signal_pending(current)) {
|
|
retval = -ERESTARTSYS;
|
|
break;
|
|
}
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_block_til_ready blocking: ttyi%d, count = %d\n",
|
|
info->line, info->count);
|
|
#endif
|
|
schedule();
|
|
}
|
|
current->state = TASK_RUNNING;
|
|
remove_wait_queue(&info->open_wait, &wait);
|
|
if (!tty_hung_up_p(filp))
|
|
info->count++;
|
|
info->blocked_open--;
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_block_til_ready after blocking: ttyi%d, count = %d\n",
|
|
info->line, info->count);
|
|
#endif
|
|
if (retval)
|
|
return retval;
|
|
info->flags |= ISDN_ASYNC_NORMAL_ACTIVE;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This routine is called whenever a serial port is opened. It
|
|
* enables interrupts for a serial port, linking in its async structure into
|
|
* the IRQ chain. It also performs the serial-specific
|
|
* initialization for the tty structure.
|
|
*/
|
|
static int
|
|
isdn_tty_open(struct tty_struct *tty, struct file *filp)
|
|
{
|
|
modem_info *info;
|
|
int retval, line;
|
|
|
|
line = tty->index;
|
|
if (line < 0 || line > ISDN_MAX_CHANNELS)
|
|
return -ENODEV;
|
|
info = &dev->mdm.info[line];
|
|
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_open"))
|
|
return -ENODEV;
|
|
if (!try_module_get(info->owner)) {
|
|
printk(KERN_WARNING "%s: cannot reserve module\n", __FUNCTION__);
|
|
return -ENODEV;
|
|
}
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_open %s, count = %d\n", tty->name,
|
|
info->count);
|
|
#endif
|
|
info->count++;
|
|
tty->driver_data = info;
|
|
info->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
|
|
module_put(info->owner);
|
|
return retval;
|
|
}
|
|
retval = isdn_tty_block_til_ready(tty, filp, info);
|
|
if (retval) {
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_open return after isdn_tty_block_til_ready \n");
|
|
#endif
|
|
module_put(info->owner);
|
|
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;
|
|
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) && (info->count != 1)) {
|
|
/*
|
|
* Uh, oh. tty->count is 1, which means that the tty
|
|
* structure will be freed. Info->count should always
|
|
* be one in these conditions. If it's greater than
|
|
* one, we've got real problems, since it means the
|
|
* serial port won't be shutdown.
|
|
*/
|
|
printk(KERN_ERR "isdn_tty_close: bad port count; tty->count is 1, "
|
|
"info->count is %d\n", info->count);
|
|
info->count = 1;
|
|
}
|
|
if (--info->count < 0) {
|
|
printk(KERN_ERR "isdn_tty_close: bad port count for ttyi%d: %d\n",
|
|
info->line, info->count);
|
|
info->count = 0;
|
|
}
|
|
if (info->count) {
|
|
#ifdef ISDN_DEBUG_MODEM_OPEN
|
|
printk(KERN_DEBUG "isdn_tty_close after info->count != 0\n");
|
|
#endif
|
|
return;
|
|
}
|
|
info->flags |= ISDN_ASYNC_CLOSING;
|
|
/*
|
|
* Save the termios structure, since this port may have
|
|
* separate termios for callout and dialin.
|
|
*/
|
|
if (info->flags & ISDN_ASYNC_NORMAL_ACTIVE)
|
|
info->normal_termios = *tty->termios;
|
|
if (info->flags & ISDN_ASYNC_CALLOUT_ACTIVE)
|
|
info->callout_termios = *tty->termios;
|
|
|
|
tty->closing = 1;
|
|
/*
|
|
* At this point we stop accepting input. To do this, we
|
|
* disable the receive line status interrupts, and tell the
|
|
* interrupt driver to stop checking the data ready bit in the
|
|
* line status register.
|
|
*/
|
|
if (info->flags & ISDN_ASYNC_INITIALIZED) {
|
|
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);
|
|
|
|
if (tty->driver->flush_buffer)
|
|
tty->driver->flush_buffer(tty);
|
|
tty_ldisc_flush(tty);
|
|
info->tty = NULL;
|
|
info->ncarrier = 0;
|
|
tty->closing = 0;
|
|
module_put(info->owner);
|
|
if (info->blocked_open) {
|
|
msleep_interruptible(500);
|
|
wake_up_interruptible(&info->open_wait);
|
|
}
|
|
info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CLOSING);
|
|
wake_up_interruptible(&info->close_wait);
|
|
#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;
|
|
|
|
if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_hangup"))
|
|
return;
|
|
isdn_tty_shutdown(info);
|
|
info->count = 0;
|
|
info->flags &= ~(ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE);
|
|
info->tty = NULL;
|
|
wake_up_interruptible(&info->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 struct tty_operations modem_ops = {
|
|
.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,
|
|
};
|
|
|
|
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->devfs_name = "isdn/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 | TTY_DRIVER_NO_DEVFS;
|
|
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
|
|
#ifdef MODULE
|
|
info->owner = THIS_MODULE;
|
|
#endif
|
|
spin_lock_init(&info->readlock);
|
|
init_MUTEX(&info->write_sem);
|
|
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->tty = NULL;
|
|
info->x_char = 0;
|
|
info->count = 0;
|
|
info->blocked_open = 0;
|
|
init_waitqueue_head(&info->open_wait);
|
|
init_waitqueue_head(&info->close_wait);
|
|
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
|
|
if (!(info->xmit_buf = kmalloc(ISDN_SERIAL_XMIT_MAX + 5, GFP_KERNEL))) {
|
|
printk(KERN_ERR "Could not allocate modem xmit-buffer\n");
|
|
retval = -ENOMEM;
|
|
goto err_unregister;
|
|
}
|
|
/* Make room for T.70 header */
|
|
info->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->xmit_buf - 4);
|
|
}
|
|
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->xmit_buf - 4);
|
|
}
|
|
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->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->flags, info->isdn_driver, info->isdn_channel,
|
|
dev->usage[idx]);
|
|
#endif
|
|
if (
|
|
#ifndef FIX_FILE_TRANSFER
|
|
(info->flags & ISDN_ASYNC_NORMAL_ACTIVE) &&
|
|
#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;
|
|
}
|
|
|
|
#define TTY_IS_ACTIVE(info) \
|
|
(info->flags & (ISDN_ASYNC_NORMAL_ACTIVE | ISDN_ASYNC_CALLOUT_ACTIVE))
|
|
|
|
int
|
|
isdn_tty_stat_callback(int i, isdn_ctrl *c)
|
|
{
|
|
int mi;
|
|
modem_info *info;
|
|
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_IS_ACTIVE(info)) {
|
|
if (info->dialing == 1) {
|
|
info->dialing = 2;
|
|
return 1;
|
|
}
|
|
}
|
|
break;
|
|
case ISDN_STAT_DHUP:
|
|
#ifdef ISDN_TTY_STAT_DEBUG
|
|
printk(KERN_DEBUG "tty_STAT_DHUP ttyI%d\n", info->line);
|
|
#endif
|
|
if (TTY_IS_ACTIVE(info)) {
|
|
if (info->dialing == 1)
|
|
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->blocked_open &&
|
|
(info->emu.mdmreg[REG_DCD] & BIT_DCD)) {
|
|
wake_up_interruptible(&info->open_wait);
|
|
}
|
|
|
|
/* Schedule CONNECT-Message to any tty
|
|
* waiting for it and
|
|
* set DCD-bit of its modem-status.
|
|
*/
|
|
if (TTY_IS_ACTIVE(info) ||
|
|
(info->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_IS_ACTIVE(info)) {
|
|
#ifdef ISDN_DEBUG_MODEM_HUP
|
|
printk(KERN_DEBUG "Mhup in ISDN_STAT_BHUP\n");
|
|
#endif
|
|
isdn_tty_modem_hup(info, 0);
|
|
return 1;
|
|
}
|
|
break;
|
|
case ISDN_STAT_NODCH:
|
|
#ifdef ISDN_TTY_STAT_DEBUG
|
|
printk(KERN_DEBUG "tty_STAT_NODCH ttyI%d\n", info->line);
|
|
#endif
|
|
if (TTY_IS_ACTIVE(info)) {
|
|
if (info->dialing) {
|
|
info->dialing = 0;
|
|
info->last_l2 = -1;
|
|
info->last_si = 0;
|
|
sprintf(info->last_cause, "0000");
|
|
isdn_tty_modem_result(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_IS_ACTIVE(info)) {
|
|
isdn_tty_fax_command(info, c);
|
|
}
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_ISDN_AUDIO
|
|
case ISDN_STAT_AUDIO:
|
|
if (TTY_IS_ACTIVE(info)) {
|
|
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_struct *tty;
|
|
atemu *m = &info->emu;
|
|
char *p;
|
|
char c;
|
|
u_long flags;
|
|
struct sk_buff *skb = NULL;
|
|
char *sp = NULL;
|
|
int l = strlen(msg);
|
|
|
|
if (!msg) {
|
|
printk(KERN_WARNING "isdn_tty: Null-Message in isdn_tty_at_cout\n");
|
|
return;
|
|
}
|
|
spin_lock_irqsave(&info->readlock, flags);
|
|
tty = info->tty;
|
|
if ((info->flags & ISDN_ASYNC_CLOSING) || (!tty)) {
|
|
spin_unlock_irqrestore(&info->readlock, flags);
|
|
return;
|
|
}
|
|
|
|
/* use queue instead of direct, if online and */
|
|
/* data is in queue or buffer is full */
|
|
if ((info->online && tty_buffer_request_room(tty, l) < l) ||
|
|
(!skb_queue_empty(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel]))) {
|
|
skb = alloc_skb(l, GFP_ATOMIC);
|
|
if (!skb) {
|
|
spin_unlock_irqrestore(&info->readlock, flags);
|
|
return;
|
|
}
|
|
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(tty, 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 */
|
|
if (dev->modempoll && info->rcvsched)
|
|
isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1);
|
|
|
|
} else {
|
|
spin_unlock_irqrestore(&info->readlock, flags);
|
|
tty_flip_buffer_push(tty);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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->flags & ISDN_ASYNC_CLOSING),
|
|
(!info->tty));
|
|
#endif
|
|
m->mdmreg[REG_RINGCNT] = 0;
|
|
del_timer(&info->nc_timer);
|
|
info->ncarrier = 0;
|
|
if ((info->flags & ISDN_ASYNC_CLOSING) || (!info->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->flags & ISDN_ASYNC_CLOSING) || (!info->tty)) {
|
|
return;
|
|
}
|
|
tty_ldisc_flush(info->tty);
|
|
if ((info->flags & ISDN_ASYNC_CHECK_CD) &&
|
|
(!((info->flags & ISDN_ASYNC_CALLOUT_ACTIVE) &&
|
|
(info->flags & ISDN_ASYNC_CALLOUT_NOHUP)))) {
|
|
tty_hangup(info->tty);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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,#.*WPTS-", *p) && *p && --cnt>0) {
|
|
if ((*p >= '0' && *p <= '9') || ((*p == 'S') && 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))
|
|
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[40];
|
|
|
|
#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;
|
|
break;
|
|
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++;
|
|
sprintf(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]))
|
|
if ((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) {
|
|
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);
|
|
}
|