📄 isdn_tty.c
字号:
/* $Id: isdn_tty.c,v 1.94.6.9 2001/11/06 20:58:29 kai 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 "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 *, int);static void isdn_tty_check_esc(const u_char *, u_char, int, int *, int *, int);static void isdn_tty_modem_reset_regs(modem_info *, int);static void isdn_tty_cmd_ATA(modem_info *);static void isdn_tty_flush_buffer(struct tty_struct *);static void isdn_tty_modem_result(int, modem_info *);#ifdef CONFIG_ISDN_AUDIOstatic 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#ifdef CONFIG_DEVFS_FSstatic char *isdn_ttyname_ttyI = "isdn/ttyI%d";static char *isdn_ttyname_cui = "isdn/cui%d";#elsestatic char *isdn_ttyname_ttyI = "ttyI";static char *isdn_ttyname_cui = "cui";#endifstatic 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.94.6.9 $";/* 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 intisdn_tty_try_read(modem_info * info, struct sk_buff *skb){ int c; int len; struct tty_struct *tty; if (info->online) { if ((tty = info->tty)) { if (info->mcr & UART_MCR_RTS) { c = TTY_FLIPBUF_SIZE - tty->flip.count; len = skb->len#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_DLECOUNT(skb)#endif ; if (c >= len) {#ifdef CONFIG_ISDN_AUDIO if (ISDN_AUDIO_SKB_DLECOUNT(skb)) while (skb->len--) { if (*skb->data == DLE) tty_insert_flip_char(tty, DLE, 0); tty_insert_flip_char(tty, *skb->data++, 0); } else {#endif memcpy(tty->flip.char_buf_ptr, skb->data, len); tty->flip.count += len; tty->flip.char_buf_ptr += len; memset(tty->flip.flag_buf_ptr, 0, len); tty->flip.flag_buf_ptr += len;#ifdef CONFIG_ISDN_AUDIO }#endif if (info->emu.mdmreg[REG_CPPP] & BIT_CPPP) tty->flip.flag_buf_ptr[len - 1] = 0xff; queue_task(&tty->flip.tqueue, &tq_timer); 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. */voidisdn_tty_readmodem(void){ int resched = 0; int midx; int i; int c; int r; ulong flags; 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) { c = TTY_FLIPBUF_SIZE - tty->flip.count; if (c > 0) { save_flags(flags); cli(); r = isdn_readbchan(info->isdn_driver, info->isdn_channel, tty->flip.char_buf_ptr, tty->flip.flag_buf_ptr, c, 0); /* CISCO AsyncPPP Hack */ if (!(info->emu.mdmreg[REG_CPPP] & BIT_CPPP)) memset(tty->flip.flag_buf_ptr, 0, r); tty->flip.count += r; tty->flip.flag_buf_ptr += r; tty->flip.char_buf_ptr += r; if (r) queue_task(&tty->flip.tqueue, &tq_timer); restore_flags(flags); } } 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);}intisdn_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 if (skb_headroom(skb) < sizeof(isdn_audio_skb)) { printk(KERN_WARNING "isdn_audio: insufficient skb_headroom, dropping\n"); kfree_skb(skb); return 1; } ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; ISDN_AUDIO_SKB_LOCK(skb) = 0; if (info->vonline & 1) { /* voice conversion/compression */ switch (info->emu.vpar[3]) { case 2: case 3: case 4: /* adpcm * Since compressed data takes less * space, we can overwrite the buffer. */ skb_trim(skb, isdn_audio_xlaw2adpcm(info->adpcmr, ifmt, skb->data, skb->data, skb->len)); break; case 5: /* a-law */ if (!ifmt) isdn_audio_ulaw2alaw(skb->data, skb->len); break; case 6: /* u-law */ if (ifmt) isdn_audio_alaw2ulaw(skb->data, skb->len); break; } ISDN_AUDIO_SKB_DLECOUNT(skb) = isdn_tty_countDLE(skb->data, skb->len); }#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-flip-buf if queue is empty */ save_flags(flags); cli(); if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) if (isdn_tty_try_read(info, skb)) { restore_flags(flags); return 1; } /* Direct deliver failed or queue wasn't empty. * Queue up for later dequeueing via timer-irq. */ __skb_queue_tail(&dev->drv[di]->rpqueue[channel], skb); dev->drv[di]->rcvcount[channel] += (skb->len#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_DLECOUNT(skb)#endif ); restore_flags(flags); /* Schedule dequeuing */ if ((dev->modempoll) && (info->rcvsched)) isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); return 1;}voidisdn_tty_cleanup_xmit(modem_info * info){ unsigned long flags; save_flags(flags); cli(); skb_queue_purge(&info->xmit_queue);#ifdef CONFIG_ISDN_AUDIO skb_queue_purge(&info->dtmf_queue);#endif restore_flags(flags);}static voidisdn_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; if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && tty->ldisc.write_wakeup) (tty->ldisc.write_wakeup) (tty); wake_up_interruptible(&tty->write_wait); return; } if (slen < 0) { /* Error: no channel, already shutdown, or wrong parameter */ dev_kfree_skb(skb); return; } skb_queue_head(&info->xmit_queue, skb);}#ifdef CONFIG_ISDN_AUDIOstatic intisdn_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 intisdn_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 intisdn_tty_end_vrx(const char *buf, int c, int from_user){ char ch; while (c--) { if (from_user) get_user(ch, buf); else ch = *buf; if ((ch != 0x11) && (ch != 0x13)) return 1; buf++; } return 0;}static int voice_cf[7] ={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 voidisdn_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);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -