diff options
author | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 12:17:53 -0700 |
---|---|---|
committer | Yunhong Jiang <yunhong.jiang@intel.com> | 2015-08-04 15:44:42 -0700 |
commit | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (patch) | |
tree | 1c9cafbcd35f783a87880a10f85d1a060db1a563 /kernel/drivers/isdn/i4l | |
parent | 98260f3884f4a202f9ca5eabed40b1354c489b29 (diff) |
Add the rt linux 4.1.3-rt3 as base
Import the rt linux 4.1.3-rt3 as OPNFV kvm base.
It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and
the base is:
commit 0917f823c59692d751951bf5ea699a2d1e2f26a2
Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Date: Sat Jul 25 12:13:34 2015 +0200
Prepare v4.1.3-rt3
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
We lose all the git history this way and it's not good. We
should apply another opnfv project repo in future.
Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423
Signed-off-by: Yunhong Jiang <yunhong.jiang@intel.com>
Diffstat (limited to 'kernel/drivers/isdn/i4l')
22 files changed, 17494 insertions, 0 deletions
diff --git a/kernel/drivers/isdn/i4l/Kconfig b/kernel/drivers/isdn/i4l/Kconfig new file mode 100644 index 000000000..9c6650ea8 --- /dev/null +++ b/kernel/drivers/isdn/i4l/Kconfig @@ -0,0 +1,140 @@ +# +# Old ISDN4Linux config +# + +if ISDN_I4L + +config ISDN_PPP + bool "Support synchronous PPP" + depends on INET + select SLHC + help + Over digital connections such as ISDN, there is no need to + synchronize sender and recipient's clocks with start and stop bits + as is done over analog telephone lines. Instead, one can use + "synchronous PPP". Saying Y here will include this protocol. This + protocol is used by Cisco and Sun for example. So you want to say Y + here if the other end of your ISDN connection supports it. You will + need a special version of pppd (called ipppd) for using this + feature. See <file:Documentation/isdn/README.syncppp> and + <file:Documentation/isdn/syncPPP.FAQ> for more information. + +config ISDN_PPP_VJ + bool "Use VJ-compression with synchronous PPP" + depends on ISDN_PPP + help + This enables Van Jacobson header compression for synchronous PPP. + Say Y if the other end of the connection supports it. + +config ISDN_MPP + bool "Support generic MP (RFC 1717)" + depends on ISDN_PPP + help + With synchronous PPP enabled, it is possible to increase throughput + by bundling several ISDN-connections, using this protocol. See + <file:Documentation/isdn/README.syncppp> for more information. + +config IPPP_FILTER + bool "Filtering for synchronous PPP" + depends on ISDN_PPP + help + Say Y here if you want to be able to filter the packets passing over + IPPP interfaces. This allows you to control which packets count as + activity (i.e. which packets will reset the idle timer or bring up + a demand-dialled link) and which packets are to be dropped entirely. + You need to say Y here if you wish to use the pass-filter and + active-filter options to ipppd. + +config ISDN_PPP_BSDCOMP + tristate "Support BSD compression" + depends on ISDN_PPP + help + Support for the BSD-Compress compression method for PPP, which uses + the LZW compression method to compress each PPP packet before it is + sent over the wire. The machine at the other end of the PPP link + (usually your ISP) has to support the BSD-Compress compression + method as well for this to be useful. Even if they don't support it, + it is safe to say Y here. + +config ISDN_AUDIO + bool "Support audio via ISDN" + help + If you say Y here, the modem-emulator will support a subset of the + EIA Class 8 Voice commands. Using a getty with voice-support + (mgetty+sendfax by <gert@greenie.muc.de> with an extension, available + with the ISDN utility package for example), you will be able to use + your Linux box as an ISDN-answering machine. Of course, this must be + supported by the lowlevel driver also. Currently, the HiSax driver + is the only voice-supporting driver. See + <file:Documentation/isdn/README.audio> for more information. + +config ISDN_TTY_FAX + bool "Support AT-Fax Class 1 and 2 commands" + depends on ISDN_AUDIO + help + If you say Y here, the modem-emulator will support a subset of the + Fax Class 1 and 2 commands. Using a getty with fax-support + (mgetty+sendfax, hylafax), you will be able to use your Linux box as + an ISDN-fax-machine. This must be supported by the lowlevel driver + also. See <file:Documentation/isdn/README.fax> for more information. + +config ISDN_X25 + bool "X.25 PLP on top of ISDN" + depends on X25 + help + This feature provides the X.25 protocol over ISDN connections. + See <file:Documentation/isdn/README.x25> for more information + if you are thinking about using this. + + +menu "ISDN feature submodules" + +config ISDN_DRV_LOOP + tristate "isdnloop support" + depends on BROKEN_ON_SMP + help + This driver provides a virtual ISDN card. Its primary purpose is + testing of linklevel features or configuration without getting + charged by your service-provider for lots of phone calls. + You need will need the loopctrl utility from the latest isdn4k-utils + package to set up this driver. + +config ISDN_DIVERSION + tristate "Support isdn diversion services" + help + This option allows you to use some supplementary diversion + services in conjunction with the HiSax driver on an EURO/DSS1 + line. + + Supported options are CD (call deflection), CFU (Call forward + unconditional), CFB (Call forward when busy) and CFNR (call forward + not reachable). Additionally the actual CFU, CFB and CFNR state may + be interrogated. + + The use of CFU, CFB, CFNR and interrogation may be limited to some + countries. The keypad protocol is still not implemented. CD should + work in all countries if the service has been subscribed to. + + Please read the file <file:Documentation/isdn/README.diversion>. + +endmenu + +comment "ISDN4Linux hardware drivers" + +source "drivers/isdn/hisax/Kconfig" + + +menu "Active cards" + +source "drivers/isdn/icn/Kconfig" + +source "drivers/isdn/pcbit/Kconfig" + +source "drivers/isdn/sc/Kconfig" + +source "drivers/isdn/act2000/Kconfig" + +endmenu +# end ISDN_I4L +endif + diff --git a/kernel/drivers/isdn/i4l/Makefile b/kernel/drivers/isdn/i4l/Makefile new file mode 100644 index 000000000..cb9d3bb9f --- /dev/null +++ b/kernel/drivers/isdn/i4l/Makefile @@ -0,0 +1,19 @@ +# Makefile for the kernel ISDN subsystem and device drivers. + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_I4L) += isdn.o +obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o +obj-$(CONFIG_ISDN_HDLC) += isdnhdlc.o + +# Multipart objects. + +isdn-y := isdn_net.o isdn_tty.o isdn_v110.o isdn_common.o + +# Optional parts of multipart objects. + +isdn-$(CONFIG_ISDN_PPP) += isdn_ppp.o +isdn-$(CONFIG_ISDN_X25) += isdn_concap.o isdn_x25iface.o +isdn-$(CONFIG_ISDN_AUDIO) += isdn_audio.o +isdn-$(CONFIG_ISDN_TTY_FAX) += isdn_ttyfax.o + diff --git a/kernel/drivers/isdn/i4l/isdn_audio.c b/kernel/drivers/isdn/i4l/isdn_audio.c new file mode 100644 index 000000000..78ce42214 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_audio.c @@ -0,0 +1,711 @@ +/* $Id: isdn_audio.c,v 1.1.2.2 2004/01/12 22:37:18 keil Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * DTMF code (c) 1996 by Christian Mock (cm@kukuruz.ping.at) + * Silence detection (c) 1998 by Armin Schindler (mac@gismo.telekom.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/isdn.h> +#include <linux/slab.h> +#include "isdn_audio.h" +#include "isdn_common.h" + +char *isdn_audio_revision = "$Revision: 1.1.2.2 $"; + +/* + * Misc. lookup-tables. + */ + +/* ulaw -> signed 16-bit */ +static short isdn_audio_ulaw_to_s16[] = +{ + 0x8284, 0x8684, 0x8a84, 0x8e84, 0x9284, 0x9684, 0x9a84, 0x9e84, + 0xa284, 0xa684, 0xaa84, 0xae84, 0xb284, 0xb684, 0xba84, 0xbe84, + 0xc184, 0xc384, 0xc584, 0xc784, 0xc984, 0xcb84, 0xcd84, 0xcf84, + 0xd184, 0xd384, 0xd584, 0xd784, 0xd984, 0xdb84, 0xdd84, 0xdf84, + 0xe104, 0xe204, 0xe304, 0xe404, 0xe504, 0xe604, 0xe704, 0xe804, + 0xe904, 0xea04, 0xeb04, 0xec04, 0xed04, 0xee04, 0xef04, 0xf004, + 0xf0c4, 0xf144, 0xf1c4, 0xf244, 0xf2c4, 0xf344, 0xf3c4, 0xf444, + 0xf4c4, 0xf544, 0xf5c4, 0xf644, 0xf6c4, 0xf744, 0xf7c4, 0xf844, + 0xf8a4, 0xf8e4, 0xf924, 0xf964, 0xf9a4, 0xf9e4, 0xfa24, 0xfa64, + 0xfaa4, 0xfae4, 0xfb24, 0xfb64, 0xfba4, 0xfbe4, 0xfc24, 0xfc64, + 0xfc94, 0xfcb4, 0xfcd4, 0xfcf4, 0xfd14, 0xfd34, 0xfd54, 0xfd74, + 0xfd94, 0xfdb4, 0xfdd4, 0xfdf4, 0xfe14, 0xfe34, 0xfe54, 0xfe74, + 0xfe8c, 0xfe9c, 0xfeac, 0xfebc, 0xfecc, 0xfedc, 0xfeec, 0xfefc, + 0xff0c, 0xff1c, 0xff2c, 0xff3c, 0xff4c, 0xff5c, 0xff6c, 0xff7c, + 0xff88, 0xff90, 0xff98, 0xffa0, 0xffa8, 0xffb0, 0xffb8, 0xffc0, + 0xffc8, 0xffd0, 0xffd8, 0xffe0, 0xffe8, 0xfff0, 0xfff8, 0x0000, + 0x7d7c, 0x797c, 0x757c, 0x717c, 0x6d7c, 0x697c, 0x657c, 0x617c, + 0x5d7c, 0x597c, 0x557c, 0x517c, 0x4d7c, 0x497c, 0x457c, 0x417c, + 0x3e7c, 0x3c7c, 0x3a7c, 0x387c, 0x367c, 0x347c, 0x327c, 0x307c, + 0x2e7c, 0x2c7c, 0x2a7c, 0x287c, 0x267c, 0x247c, 0x227c, 0x207c, + 0x1efc, 0x1dfc, 0x1cfc, 0x1bfc, 0x1afc, 0x19fc, 0x18fc, 0x17fc, + 0x16fc, 0x15fc, 0x14fc, 0x13fc, 0x12fc, 0x11fc, 0x10fc, 0x0ffc, + 0x0f3c, 0x0ebc, 0x0e3c, 0x0dbc, 0x0d3c, 0x0cbc, 0x0c3c, 0x0bbc, + 0x0b3c, 0x0abc, 0x0a3c, 0x09bc, 0x093c, 0x08bc, 0x083c, 0x07bc, + 0x075c, 0x071c, 0x06dc, 0x069c, 0x065c, 0x061c, 0x05dc, 0x059c, + 0x055c, 0x051c, 0x04dc, 0x049c, 0x045c, 0x041c, 0x03dc, 0x039c, + 0x036c, 0x034c, 0x032c, 0x030c, 0x02ec, 0x02cc, 0x02ac, 0x028c, + 0x026c, 0x024c, 0x022c, 0x020c, 0x01ec, 0x01cc, 0x01ac, 0x018c, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, + 0x00f4, 0x00e4, 0x00d4, 0x00c4, 0x00b4, 0x00a4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, + 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +/* alaw -> signed 16-bit */ +static short isdn_audio_alaw_to_s16[] = +{ + 0x13fc, 0xec04, 0x0144, 0xfebc, 0x517c, 0xae84, 0x051c, 0xfae4, + 0x0a3c, 0xf5c4, 0x0048, 0xffb8, 0x287c, 0xd784, 0x028c, 0xfd74, + 0x1bfc, 0xe404, 0x01cc, 0xfe34, 0x717c, 0x8e84, 0x071c, 0xf8e4, + 0x0e3c, 0xf1c4, 0x00c4, 0xff3c, 0x387c, 0xc784, 0x039c, 0xfc64, + 0x0ffc, 0xf004, 0x0104, 0xfefc, 0x417c, 0xbe84, 0x041c, 0xfbe4, + 0x083c, 0xf7c4, 0x0008, 0xfff8, 0x207c, 0xdf84, 0x020c, 0xfdf4, + 0x17fc, 0xe804, 0x018c, 0xfe74, 0x617c, 0x9e84, 0x061c, 0xf9e4, + 0x0c3c, 0xf3c4, 0x0084, 0xff7c, 0x307c, 0xcf84, 0x030c, 0xfcf4, + 0x15fc, 0xea04, 0x0164, 0xfe9c, 0x597c, 0xa684, 0x059c, 0xfa64, + 0x0b3c, 0xf4c4, 0x0068, 0xff98, 0x2c7c, 0xd384, 0x02cc, 0xfd34, + 0x1dfc, 0xe204, 0x01ec, 0xfe14, 0x797c, 0x8684, 0x07bc, 0xf844, + 0x0f3c, 0xf0c4, 0x00e4, 0xff1c, 0x3c7c, 0xc384, 0x03dc, 0xfc24, + 0x11fc, 0xee04, 0x0124, 0xfedc, 0x497c, 0xb684, 0x049c, 0xfb64, + 0x093c, 0xf6c4, 0x0028, 0xffd8, 0x247c, 0xdb84, 0x024c, 0xfdb4, + 0x19fc, 0xe604, 0x01ac, 0xfe54, 0x697c, 0x9684, 0x069c, 0xf964, + 0x0d3c, 0xf2c4, 0x00a4, 0xff5c, 0x347c, 0xcb84, 0x034c, 0xfcb4, + 0x12fc, 0xed04, 0x0134, 0xfecc, 0x4d7c, 0xb284, 0x04dc, 0xfb24, + 0x09bc, 0xf644, 0x0038, 0xffc8, 0x267c, 0xd984, 0x026c, 0xfd94, + 0x1afc, 0xe504, 0x01ac, 0xfe54, 0x6d7c, 0x9284, 0x06dc, 0xf924, + 0x0dbc, 0xf244, 0x00b4, 0xff4c, 0x367c, 0xc984, 0x036c, 0xfc94, + 0x0f3c, 0xf0c4, 0x00f4, 0xff0c, 0x3e7c, 0xc184, 0x03dc, 0xfc24, + 0x07bc, 0xf844, 0x0008, 0xfff8, 0x1efc, 0xe104, 0x01ec, 0xfe14, + 0x16fc, 0xe904, 0x0174, 0xfe8c, 0x5d7c, 0xa284, 0x05dc, 0xfa24, + 0x0bbc, 0xf444, 0x0078, 0xff88, 0x2e7c, 0xd184, 0x02ec, 0xfd14, + 0x14fc, 0xeb04, 0x0154, 0xfeac, 0x557c, 0xaa84, 0x055c, 0xfaa4, + 0x0abc, 0xf544, 0x0058, 0xffa8, 0x2a7c, 0xd584, 0x02ac, 0xfd54, + 0x1cfc, 0xe304, 0x01cc, 0xfe34, 0x757c, 0x8a84, 0x075c, 0xf8a4, + 0x0ebc, 0xf144, 0x00d4, 0xff2c, 0x3a7c, 0xc584, 0x039c, 0xfc64, + 0x10fc, 0xef04, 0x0114, 0xfeec, 0x457c, 0xba84, 0x045c, 0xfba4, + 0x08bc, 0xf744, 0x0018, 0xffe8, 0x227c, 0xdd84, 0x022c, 0xfdd4, + 0x18fc, 0xe704, 0x018c, 0xfe74, 0x657c, 0x9a84, 0x065c, 0xf9a4, + 0x0cbc, 0xf344, 0x0094, 0xff6c, 0x327c, 0xcd84, 0x032c, 0xfcd4 +}; + +/* alaw -> ulaw */ +static char isdn_audio_alaw_to_ulaw[] = +{ + 0xab, 0x2b, 0xe3, 0x63, 0x8b, 0x0b, 0xc9, 0x49, + 0xba, 0x3a, 0xf6, 0x76, 0x9b, 0x1b, 0xd7, 0x57, + 0xa3, 0x23, 0xdd, 0x5d, 0x83, 0x03, 0xc1, 0x41, + 0xb2, 0x32, 0xeb, 0x6b, 0x93, 0x13, 0xcf, 0x4f, + 0xaf, 0x2f, 0xe7, 0x67, 0x8f, 0x0f, 0xcd, 0x4d, + 0xbe, 0x3e, 0xfe, 0x7e, 0x9f, 0x1f, 0xdb, 0x5b, + 0xa7, 0x27, 0xdf, 0x5f, 0x87, 0x07, 0xc5, 0x45, + 0xb6, 0x36, 0xef, 0x6f, 0x97, 0x17, 0xd3, 0x53, + 0xa9, 0x29, 0xe1, 0x61, 0x89, 0x09, 0xc7, 0x47, + 0xb8, 0x38, 0xf2, 0x72, 0x99, 0x19, 0xd5, 0x55, + 0xa1, 0x21, 0xdc, 0x5c, 0x81, 0x01, 0xbf, 0x3f, + 0xb0, 0x30, 0xe9, 0x69, 0x91, 0x11, 0xce, 0x4e, + 0xad, 0x2d, 0xe5, 0x65, 0x8d, 0x0d, 0xcb, 0x4b, + 0xbc, 0x3c, 0xfa, 0x7a, 0x9d, 0x1d, 0xd9, 0x59, + 0xa5, 0x25, 0xde, 0x5e, 0x85, 0x05, 0xc3, 0x43, + 0xb4, 0x34, 0xed, 0x6d, 0x95, 0x15, 0xd1, 0x51, + 0xac, 0x2c, 0xe4, 0x64, 0x8c, 0x0c, 0xca, 0x4a, + 0xbb, 0x3b, 0xf8, 0x78, 0x9c, 0x1c, 0xd8, 0x58, + 0xa4, 0x24, 0xde, 0x5e, 0x84, 0x04, 0xc2, 0x42, + 0xb3, 0x33, 0xec, 0x6c, 0x94, 0x14, 0xd0, 0x50, + 0xb0, 0x30, 0xe8, 0x68, 0x90, 0x10, 0xce, 0x4e, + 0xbf, 0x3f, 0xfe, 0x7e, 0xa0, 0x20, 0xdc, 0x5c, + 0xa8, 0x28, 0xe0, 0x60, 0x88, 0x08, 0xc6, 0x46, + 0xb7, 0x37, 0xf0, 0x70, 0x98, 0x18, 0xd4, 0x54, + 0xaa, 0x2a, 0xe2, 0x62, 0x8a, 0x0a, 0xc8, 0x48, + 0xb9, 0x39, 0xf4, 0x74, 0x9a, 0x1a, 0xd6, 0x56, + 0xa2, 0x22, 0xdd, 0x5d, 0x82, 0x02, 0xc0, 0x40, + 0xb1, 0x31, 0xea, 0x6a, 0x92, 0x12, 0xcf, 0x4f, + 0xae, 0x2e, 0xe6, 0x66, 0x8e, 0x0e, 0xcc, 0x4c, + 0xbd, 0x3d, 0xfc, 0x7c, 0x9e, 0x1e, 0xda, 0x5a, + 0xa6, 0x26, 0xdf, 0x5f, 0x86, 0x06, 0xc4, 0x44, + 0xb5, 0x35, 0xee, 0x6e, 0x96, 0x16, 0xd2, 0x52 +}; + +/* ulaw -> alaw */ +static char isdn_audio_ulaw_to_alaw[] = +{ + 0xab, 0x55, 0xd5, 0x15, 0x95, 0x75, 0xf5, 0x35, + 0xb5, 0x45, 0xc5, 0x05, 0x85, 0x65, 0xe5, 0x25, + 0xa5, 0x5d, 0xdd, 0x1d, 0x9d, 0x7d, 0xfd, 0x3d, + 0xbd, 0x4d, 0xcd, 0x0d, 0x8d, 0x6d, 0xed, 0x2d, + 0xad, 0x51, 0xd1, 0x11, 0x91, 0x71, 0xf1, 0x31, + 0xb1, 0x41, 0xc1, 0x01, 0x81, 0x61, 0xe1, 0x21, + 0x59, 0xd9, 0x19, 0x99, 0x79, 0xf9, 0x39, 0xb9, + 0x49, 0xc9, 0x09, 0x89, 0x69, 0xe9, 0x29, 0xa9, + 0xd7, 0x17, 0x97, 0x77, 0xf7, 0x37, 0xb7, 0x47, + 0xc7, 0x07, 0x87, 0x67, 0xe7, 0x27, 0xa7, 0xdf, + 0x9f, 0x7f, 0xff, 0x3f, 0xbf, 0x4f, 0xcf, 0x0f, + 0x8f, 0x6f, 0xef, 0x2f, 0x53, 0x13, 0x73, 0x33, + 0xb3, 0x43, 0xc3, 0x03, 0x83, 0x63, 0xe3, 0x23, + 0xa3, 0x5b, 0xdb, 0x1b, 0x9b, 0x7b, 0xfb, 0x3b, + 0xbb, 0xbb, 0x4b, 0x4b, 0xcb, 0xcb, 0x0b, 0x0b, + 0x8b, 0x8b, 0x6b, 0x6b, 0xeb, 0xeb, 0x2b, 0x2b, + 0xab, 0x54, 0xd4, 0x14, 0x94, 0x74, 0xf4, 0x34, + 0xb4, 0x44, 0xc4, 0x04, 0x84, 0x64, 0xe4, 0x24, + 0xa4, 0x5c, 0xdc, 0x1c, 0x9c, 0x7c, 0xfc, 0x3c, + 0xbc, 0x4c, 0xcc, 0x0c, 0x8c, 0x6c, 0xec, 0x2c, + 0xac, 0x50, 0xd0, 0x10, 0x90, 0x70, 0xf0, 0x30, + 0xb0, 0x40, 0xc0, 0x00, 0x80, 0x60, 0xe0, 0x20, + 0x58, 0xd8, 0x18, 0x98, 0x78, 0xf8, 0x38, 0xb8, + 0x48, 0xc8, 0x08, 0x88, 0x68, 0xe8, 0x28, 0xa8, + 0xd6, 0x16, 0x96, 0x76, 0xf6, 0x36, 0xb6, 0x46, + 0xc6, 0x06, 0x86, 0x66, 0xe6, 0x26, 0xa6, 0xde, + 0x9e, 0x7e, 0xfe, 0x3e, 0xbe, 0x4e, 0xce, 0x0e, + 0x8e, 0x6e, 0xee, 0x2e, 0x52, 0x12, 0x72, 0x32, + 0xb2, 0x42, 0xc2, 0x02, 0x82, 0x62, 0xe2, 0x22, + 0xa2, 0x5a, 0xda, 0x1a, 0x9a, 0x7a, 0xfa, 0x3a, + 0xba, 0xba, 0x4a, 0x4a, 0xca, 0xca, 0x0a, 0x0a, + 0x8a, 0x8a, 0x6a, 0x6a, 0xea, 0xea, 0x2a, 0x2a +}; + +#define NCOEFF 8 /* number of frequencies to be analyzed */ +#define DTMF_TRESH 4000 /* above this is dtmf */ +#define SILENCE_TRESH 200 /* below this is silence */ +#define AMP_BITS 9 /* bits per sample, reduced to avoid overflow */ +#define LOGRP 0 +#define HIGRP 1 + +/* For DTMF recognition: + * 2 * cos(2 * PI * k / N) precalculated for all k + */ +static int cos2pik[NCOEFF] = +{ + 55813, 53604, 51193, 48591, 38114, 33057, 25889, 18332 +}; + +static char dtmf_matrix[4][4] = +{ + {'1', '2', '3', 'A'}, + {'4', '5', '6', 'B'}, + {'7', '8', '9', 'C'}, + {'*', '0', '#', 'D'} +}; + +static inline void +isdn_audio_tlookup(const u_char *table, u_char *buff, unsigned long n) +{ +#ifdef __i386__ + unsigned long d0, d1, d2, d3; + __asm__ __volatile__( + "cld\n" + "1:\tlodsb\n\t" + "xlatb\n\t" + "stosb\n\t" + "loop 1b\n\t" + : "=&b"(d0), "=&c"(d1), "=&D"(d2), "=&S"(d3) + : "0"((long) table), "1"(n), "2"((long) buff), "3"((long) buff) + : "memory", "ax"); +#else + while (n--) + *buff = table[*(unsigned char *)buff], buff++; +#endif +} + +void +isdn_audio_ulaw2alaw(unsigned char *buff, unsigned long len) +{ + isdn_audio_tlookup(isdn_audio_ulaw_to_alaw, buff, len); +} + +void +isdn_audio_alaw2ulaw(unsigned char *buff, unsigned long len) +{ + isdn_audio_tlookup(isdn_audio_alaw_to_ulaw, buff, len); +} + +/* + * linear <-> adpcm conversion stuff + * Most parts from the mgetty-package. + * (C) by Gert Doering and Klaus Weidner + * Used by permission of Gert Doering + */ + + +#define ZEROTRAP /* turn on the trap as per the MIL-STD */ +#undef ZEROTRAP +#define BIAS 0x84 /* define the add-in bias for 16 bit samples */ +#define CLIP 32635 + +static unsigned char +isdn_audio_linear2ulaw(int sample) +{ + static int exp_lut[256] = + { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + }; + int sign, + exponent, + mantissa; + unsigned char ulawbyte; + + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if (sign != 0) + sample = -sample; /* get magnitude */ + if (sample > CLIP) + sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut[(sample >> 7) & 0xFF]; + mantissa = (sample >> (exponent + 3)) & 0x0F; + ulawbyte = ~(sign | (exponent << 4) | mantissa); +#ifdef ZEROTRAP + /* optional CCITT trap */ + if (ulawbyte == 0) + ulawbyte = 0x02; +#endif + return (ulawbyte); +} + + +static int Mx[3][8] = +{ + {0x3800, 0x5600, 0, 0, 0, 0, 0, 0}, + {0x399a, 0x3a9f, 0x4d14, 0x6607, 0, 0, 0, 0}, + {0x3556, 0x3556, 0x399A, 0x3A9F, 0x4200, 0x4D14, 0x6607, 0x6607}, +}; + +static int bitmask[9] = +{ + 0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; + +static int +isdn_audio_get_bits(adpcm_state *s, unsigned char **in, int *len) +{ + while (s->nleft < s->nbits) { + int d = *((*in)++); + (*len)--; + s->word = (s->word << 8) | d; + s->nleft += 8; + } + s->nleft -= s->nbits; + return (s->word >> s->nleft) & bitmask[s->nbits]; +} + +static void +isdn_audio_put_bits(int data, int nbits, adpcm_state *s, + unsigned char **out, int *len) +{ + s->word = (s->word << nbits) | (data & bitmask[nbits]); + s->nleft += nbits; + while (s->nleft >= 8) { + int d = (s->word >> (s->nleft - 8)); + *(out[0]++) = d & 255; + (*len)++; + s->nleft -= 8; + } +} + +adpcm_state * +isdn_audio_adpcm_init(adpcm_state *s, int nbits) +{ + if (!s) + s = kmalloc(sizeof(adpcm_state), GFP_ATOMIC); + if (s) { + s->a = 0; + s->d = 5; + s->word = 0; + s->nleft = 0; + s->nbits = nbits; + } + return s; +} + +dtmf_state * +isdn_audio_dtmf_init(dtmf_state *s) +{ + if (!s) + s = kmalloc(sizeof(dtmf_state), GFP_ATOMIC); + if (s) { + s->idx = 0; + s->last = ' '; + } + return s; +} + +/* + * Decompression of adpcm data to a/u-law + * + */ + +int +isdn_audio_adpcm2xlaw(adpcm_state *s, int fmt, unsigned char *in, + unsigned char *out, int len) +{ + int a = s->a; + int d = s->d; + int nbits = s->nbits; + int olen = 0; + + while (len) { + int e = isdn_audio_get_bits(s, &in, &len); + int sign; + + if (nbits == 4 && e == 0) + d = 4; + sign = (e >> (nbits - 1)) ? -1 : 1; + e &= bitmask[nbits - 1]; + a += sign * ((e << 1) + 1) * d >> 1; + if (d & 1) + a++; + if (fmt) + *out++ = isdn_audio_ulaw_to_alaw[ + isdn_audio_linear2ulaw(a << 2)]; + else + *out++ = isdn_audio_linear2ulaw(a << 2); + olen++; + d = (d * Mx[nbits - 2][e] + 0x2000) >> 14; + if (d < 5) + d = 5; + } + s->a = a; + s->d = d; + return olen; +} + +int +isdn_audio_xlaw2adpcm(adpcm_state *s, int fmt, unsigned char *in, + unsigned char *out, int len) +{ + int a = s->a; + int d = s->d; + int nbits = s->nbits; + int olen = 0; + + while (len--) { + int e = 0, + nmax = 1 << (nbits - 1); + int sign, + delta; + + if (fmt) + delta = (isdn_audio_alaw_to_s16[*in++] >> 2) - a; + else + delta = (isdn_audio_ulaw_to_s16[*in++] >> 2) - a; + if (delta < 0) { + e = nmax; + delta = -delta; + } + while (--nmax && delta > d) { + delta -= d; + e++; + } + if (nbits == 4 && ((e & 0x0f) == 0)) + e = 8; + isdn_audio_put_bits(e, nbits, s, &out, &olen); + sign = (e >> (nbits - 1)) ? -1 : 1; + e &= bitmask[nbits - 1]; + + a += sign * ((e << 1) + 1) * d >> 1; + if (d & 1) + a++; + d = (d * Mx[nbits - 2][e] + 0x2000) >> 14; + if (d < 5) + d = 5; + } + s->a = a; + s->d = d; + return olen; +} + +/* + * Goertzel algorithm. + * See http://ptolemy.eecs.berkeley.edu/papers/96/dtmf_ict/ + * for more info. + * Result is stored into an sk_buff and queued up for later + * evaluation. + */ +static void +isdn_audio_goertzel(int *sample, modem_info *info) +{ + int sk, + sk1, + sk2; + int k, + n; + struct sk_buff *skb; + int *result; + + skb = dev_alloc_skb(sizeof(int) * NCOEFF); + if (!skb) { + printk(KERN_WARNING + "isdn_audio: Could not alloc DTMF result for ttyI%d\n", + info->line); + return; + } + result = (int *) skb_put(skb, sizeof(int) * NCOEFF); + for (k = 0; k < NCOEFF; k++) { + sk = sk1 = sk2 = 0; + for (n = 0; n < DTMF_NPOINTS; n++) { + sk = sample[n] + ((cos2pik[k] * sk1) >> 15) - sk2; + sk2 = sk1; + sk1 = sk; + } + /* Avoid overflows */ + sk >>= 1; + sk2 >>= 1; + /* compute |X(k)|**2 */ + /* report overflows. This should not happen. */ + /* Comment this out if desired */ + if (sk < -32768 || sk > 32767) + printk(KERN_DEBUG + "isdn_audio: dtmf goertzel overflow, sk=%d\n", sk); + if (sk2 < -32768 || sk2 > 32767) + printk(KERN_DEBUG + "isdn_audio: dtmf goertzel overflow, sk2=%d\n", sk2); + result[k] = + ((sk * sk) >> AMP_BITS) - + ((((cos2pik[k] * sk) >> 15) * sk2) >> AMP_BITS) + + ((sk2 * sk2) >> AMP_BITS); + } + skb_queue_tail(&info->dtmf_queue, skb); + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); +} + +void +isdn_audio_eval_dtmf(modem_info *info) +{ + struct sk_buff *skb; + int *result; + dtmf_state *s; + int silence; + int i; + int di; + int ch; + int grp[2]; + char what; + char *p; + int thresh; + + while ((skb = skb_dequeue(&info->dtmf_queue))) { + result = (int *) skb->data; + s = info->dtmf_state; + grp[LOGRP] = grp[HIGRP] = -1; + silence = 0; + thresh = 0; + for (i = 0; i < NCOEFF; i++) { + if (result[i] > DTMF_TRESH) { + if (result[i] > thresh) + thresh = result[i]; + } + else if (result[i] < SILENCE_TRESH) + silence++; + } + if (silence == NCOEFF) + what = ' '; + else { + if (thresh > 0) { + thresh = thresh >> 4; /* touchtones must match within 12 dB */ + for (i = 0; i < NCOEFF; i++) { + if (result[i] < thresh) + continue; /* ignore */ + /* good level found. This is allowed only one time per group */ + if (i < NCOEFF / 2) { + /* lowgroup*/ + if (grp[LOGRP] >= 0) { + // Bad. Another tone found. */ + grp[LOGRP] = -1; + break; + } + else + grp[LOGRP] = i; + } + else { /* higroup */ + if (grp[HIGRP] >= 0) { // Bad. Another tone found. */ + grp[HIGRP] = -1; + break; + } + else + grp[HIGRP] = i - NCOEFF/2; + } + } + if ((grp[LOGRP] >= 0) && (grp[HIGRP] >= 0)) { + what = dtmf_matrix[grp[LOGRP]][grp[HIGRP]]; + if (s->last != ' ' && s->last != '.') + s->last = what; /* min. 1 non-DTMF between DTMF */ + } else + what = '.'; + } + else + what = '.'; + } + if ((what != s->last) && (what != ' ') && (what != '.')) { + printk(KERN_DEBUG "dtmf: tt='%c'\n", what); + p = skb->data; + *p++ = 0x10; + *p = what; + skb_trim(skb, 2); + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + di = info->isdn_driver; + ch = info->isdn_channel; + __skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb); + dev->drv[di]->rcvcount[ch] += 2; + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + wake_up_interruptible(&dev->drv[di]->rcv_waitq[ch]); + } else + kfree_skb(skb); + s->last = what; + } +} + +/* + * Decode DTMF tones, queue result in separate sk_buf for + * later examination. + * Parameters: + * s = pointer to state-struct. + * buf = input audio data + * len = size of audio data. + * fmt = audio data format (0 = ulaw, 1 = alaw) + */ +void +isdn_audio_calc_dtmf(modem_info *info, unsigned char *buf, int len, int fmt) +{ + dtmf_state *s = info->dtmf_state; + int i; + int c; + + while (len) { + c = DTMF_NPOINTS - s->idx; + if (c > len) + c = len; + if (c <= 0) + break; + for (i = 0; i < c; i++) { + if (fmt) + s->buf[s->idx++] = + isdn_audio_alaw_to_s16[*buf++] >> (15 - AMP_BITS); + else + s->buf[s->idx++] = + isdn_audio_ulaw_to_s16[*buf++] >> (15 - AMP_BITS); + } + if (s->idx == DTMF_NPOINTS) { + isdn_audio_goertzel(s->buf, info); + s->idx = 0; + } + len -= c; + } +} + +silence_state * +isdn_audio_silence_init(silence_state *s) +{ + if (!s) + s = kmalloc(sizeof(silence_state), GFP_ATOMIC); + if (s) { + s->idx = 0; + s->state = 0; + } + return s; +} + +void +isdn_audio_calc_silence(modem_info *info, unsigned char *buf, int len, int fmt) +{ + silence_state *s = info->silence_state; + int i; + signed char c; + + if (!info->emu.vpar[1]) return; + + for (i = 0; i < len; i++) { + if (fmt) + c = isdn_audio_alaw_to_ulaw[*buf++]; + else + c = *buf++; + + if (c > 0) c -= 128; + c = abs(c); + + if (c > (info->emu.vpar[1] * 4)) { + s->idx = 0; + s->state = 1; + } else { + if (s->idx < 210000) s->idx++; + } + } +} + +void +isdn_audio_put_dle_code(modem_info *info, u_char code) +{ + struct sk_buff *skb; + int di; + int ch; + char *p; + + skb = dev_alloc_skb(2); + if (!skb) { + printk(KERN_WARNING + "isdn_audio: Could not alloc skb for ttyI%d\n", + info->line); + return; + } + p = (char *) skb_put(skb, 2); + p[0] = 0x10; + p[1] = code; + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + di = info->isdn_driver; + ch = info->isdn_channel; + __skb_queue_tail(&dev->drv[di]->rpqueue[ch], skb); + dev->drv[di]->rcvcount[ch] += 2; + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + wake_up_interruptible(&dev->drv[di]->rcv_waitq[ch]); +} + +void +isdn_audio_eval_silence(modem_info *info) +{ + silence_state *s = info->silence_state; + char what; + + what = ' '; + + if (s->idx > (info->emu.vpar[2] * 800)) { + s->idx = 0; + if (!s->state) { /* silence from beginning of rec */ + what = 's'; + } else { + what = 'q'; + } + } + if ((what == 's') || (what == 'q')) { + printk(KERN_DEBUG "ttyI%d: %s\n", info->line, + (what == 's') ? "silence" : "quiet"); + isdn_audio_put_dle_code(info, what); + } +} diff --git a/kernel/drivers/isdn/i4l/isdn_audio.h b/kernel/drivers/isdn/i4l/isdn_audio.h new file mode 100644 index 000000000..013c3582e --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_audio.h @@ -0,0 +1,44 @@ +/* $Id: isdn_audio.h,v 1.1.2.2 2004/01/12 22:37:18 keil Exp $ + * + * Linux ISDN subsystem, audio conversion and compression (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#define DTMF_NPOINTS 205 /* Number of samples for DTMF recognition */ +typedef struct adpcm_state { + int a; + int d; + int word; + int nleft; + int nbits; +} adpcm_state; + +typedef struct dtmf_state { + char last; + char llast; + int idx; + int buf[DTMF_NPOINTS]; +} dtmf_state; + +typedef struct silence_state { + int state; + unsigned int idx; +} silence_state; + +extern void isdn_audio_ulaw2alaw(unsigned char *, unsigned long); +extern void isdn_audio_alaw2ulaw(unsigned char *, unsigned long); +extern adpcm_state *isdn_audio_adpcm_init(adpcm_state *, int); +extern int isdn_audio_adpcm2xlaw(adpcm_state *, int, unsigned char *, unsigned char *, int); +extern int isdn_audio_xlaw2adpcm(adpcm_state *, int, unsigned char *, unsigned char *, int); +extern void isdn_audio_calc_dtmf(modem_info *, unsigned char *, int, int); +extern void isdn_audio_eval_dtmf(modem_info *); +dtmf_state *isdn_audio_dtmf_init(dtmf_state *); +extern void isdn_audio_calc_silence(modem_info *, unsigned char *, int, int); +extern void isdn_audio_eval_silence(modem_info *); +silence_state *isdn_audio_silence_init(silence_state *); +extern void isdn_audio_put_dle_code(modem_info *, u_char); diff --git a/kernel/drivers/isdn/i4l/isdn_bsdcomp.c b/kernel/drivers/isdn/i4l/isdn_bsdcomp.c new file mode 100644 index 000000000..8837ac5a4 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_bsdcomp.c @@ -0,0 +1,928 @@ +/* + * BSD compression module + * + * Patched version for ISDN syncPPP written 1997/1998 by Michael Hipp + * The whole module is now SKB based. + * + */ + +/* + * Update: The Berkeley copyright was changed, and the change + * is retroactive to all "true" BSD software (ie everything + * from UCB as opposed to other peoples code that just carried + * the same license). The new copyright doesn't clash with the + * GPL, so the module-only restriction has been removed.. + */ + +/* + * Original copyright notice: + * + * Copyright (c) 1985, 1986 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James A. Woods, derived from original work by Spencer Thomas + * and Joseph Orost. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ptrace.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/tty.h> +#include <linux/errno.h> +#include <linux/string.h> /* used in new tty drivers */ +#include <linux/signal.h> /* used in new tty drivers */ +#include <linux/bitops.h> + +#include <asm/byteorder.h> +#include <asm/types.h> + +#include <linux/if.h> + +#include <linux/if_ether.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> +#include <linux/inet.h> +#include <linux/ioctl.h> +#include <linux/vmalloc.h> + +#include <linux/ppp_defs.h> + +#include <linux/isdn.h> +#include <linux/isdn_ppp.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/if_arp.h> +#include <linux/ppp-comp.h> + +#include "isdn_ppp.h" + +MODULE_DESCRIPTION("ISDN4Linux: BSD Compression for PPP over ISDN"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define BSD_VERSION(x) ((x) >> 5) +#define BSD_NBITS(x) ((x) & 0x1F) + +#define BSD_CURRENT_VERSION 1 + +#define DEBUG 1 + +/* + * A dictionary for doing BSD compress. + */ + +struct bsd_dict { + u32 fcode; + u16 codem1; /* output of hash table -1 */ + u16 cptr; /* map code to hash table entry */ +}; + +struct bsd_db { + int totlen; /* length of this structure */ + unsigned int hsize; /* size of the hash table */ + unsigned char hshift; /* used in hash function */ + unsigned char n_bits; /* current bits/code */ + unsigned char maxbits; /* maximum bits/code */ + unsigned char debug; /* non-zero if debug desired */ + unsigned char unit; /* ppp unit number */ + u16 seqno; /* sequence # of next packet */ + unsigned int mru; /* size of receive (decompress) bufr */ + unsigned int maxmaxcode; /* largest valid code */ + unsigned int max_ent; /* largest code in use */ + unsigned int in_count; /* uncompressed bytes, aged */ + unsigned int bytes_out; /* compressed bytes, aged */ + unsigned int ratio; /* recent compression ratio */ + unsigned int checkpoint; /* when to next check the ratio */ + unsigned int clear_count; /* times dictionary cleared */ + unsigned int incomp_count; /* incompressible packets */ + unsigned int incomp_bytes; /* incompressible bytes */ + unsigned int uncomp_count; /* uncompressed packets */ + unsigned int uncomp_bytes; /* uncompressed bytes */ + unsigned int comp_count; /* compressed packets */ + unsigned int comp_bytes; /* compressed bytes */ + unsigned short *lens; /* array of lengths of codes */ + struct bsd_dict *dict; /* dictionary */ + int xmit; +}; + +#define BSD_OVHD 2 /* BSD compress overhead/packet */ +#define MIN_BSD_BITS 9 +#define BSD_INIT_BITS MIN_BSD_BITS +#define MAX_BSD_BITS 15 + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define CLEAR 256 /* table clear output code */ +#define FIRST 257 /* first free entry */ +#define LAST 255 + +#define MAXCODE(b) ((1 << (b)) - 1) +#define BADCODEM1 MAXCODE(MAX_BSD_BITS) + +#define BSD_HASH(prefix, suffix, hshift) ((((unsigned long)(suffix)) << (hshift)) \ + ^ (unsigned long)(prefix)) +#define BSD_KEY(prefix, suffix) ((((unsigned long)(suffix)) << 16) \ + + (unsigned long)(prefix)) + +#define CHECK_GAP 10000 /* Ratio check interval */ + +#define RATIO_SCALE_LOG 8 +#define RATIO_SCALE (1 << RATIO_SCALE_LOG) +#define RATIO_MAX (0x7fffffff >> RATIO_SCALE_LOG) + +/* + * clear the dictionary + */ + +static void bsd_clear(struct bsd_db *db) +{ + db->clear_count++; + db->max_ent = FIRST - 1; + db->n_bits = BSD_INIT_BITS; + db->bytes_out = 0; + db->in_count = 0; + db->incomp_count = 0; + db->ratio = 0; + db->checkpoint = CHECK_GAP; +} + +/* + * If the dictionary is full, then see if it is time to reset it. + * + * Compute the compression ratio using fixed-point arithmetic + * with 8 fractional bits. + * + * Since we have an infinite stream instead of a single file, + * watch only the local compression ratio. + * + * Since both peers must reset the dictionary at the same time even in + * the absence of CLEAR codes (while packets are incompressible), they + * must compute the same ratio. + */ +static int bsd_check(struct bsd_db *db) /* 1=output CLEAR */ +{ + unsigned int new_ratio; + + if (db->in_count >= db->checkpoint) + { + /* age the ratio by limiting the size of the counts */ + if (db->in_count >= RATIO_MAX || db->bytes_out >= RATIO_MAX) + { + db->in_count -= (db->in_count >> 2); + db->bytes_out -= (db->bytes_out >> 2); + } + + db->checkpoint = db->in_count + CHECK_GAP; + + if (db->max_ent >= db->maxmaxcode) + { + /* Reset the dictionary only if the ratio is worse, + * or if it looks as if it has been poisoned + * by incompressible data. + * + * This does not overflow, because + * db->in_count <= RATIO_MAX. + */ + + new_ratio = db->in_count << RATIO_SCALE_LOG; + if (db->bytes_out != 0) + { + new_ratio /= db->bytes_out; + } + + if (new_ratio < db->ratio || new_ratio < 1 * RATIO_SCALE) + { + bsd_clear(db); + return 1; + } + db->ratio = new_ratio; + } + } + return 0; +} + +/* + * Return statistics. + */ + +static void bsd_stats(void *state, struct compstat *stats) +{ + struct bsd_db *db = (struct bsd_db *) state; + + stats->unc_bytes = db->uncomp_bytes; + stats->unc_packets = db->uncomp_count; + stats->comp_bytes = db->comp_bytes; + stats->comp_packets = db->comp_count; + stats->inc_bytes = db->incomp_bytes; + stats->inc_packets = db->incomp_count; + stats->in_count = db->in_count; + stats->bytes_out = db->bytes_out; +} + +/* + * Reset state, as on a CCP ResetReq. + */ +static void bsd_reset(void *state, unsigned char code, unsigned char id, + unsigned char *data, unsigned len, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db = (struct bsd_db *) state; + + bsd_clear(db); + db->seqno = 0; + db->clear_count = 0; +} + +/* + * Release the compression structure + */ +static void bsd_free(void *state) +{ + struct bsd_db *db = (struct bsd_db *) state; + + if (db) { + /* + * Release the dictionary + */ + vfree(db->dict); + db->dict = NULL; + + /* + * Release the string buffer + */ + vfree(db->lens); + db->lens = NULL; + + /* + * Finally release the structure itself. + */ + kfree(db); + } +} + + +/* + * Allocate space for a (de) compressor. + */ +static void *bsd_alloc(struct isdn_ppp_comp_data *data) +{ + int bits; + unsigned int hsize, hshift, maxmaxcode; + struct bsd_db *db; + int decomp; + + static unsigned int htab[][2] = { + { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , { 5003 , 4 } , + { 9001 , 5 } , { 18013 , 6 } , { 35023 , 7 } , { 69001 , 8 } + }; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + return NULL; + + bits = BSD_NBITS(data->options[0]); + + if (bits < 9 || bits > 15) + return NULL; + + hsize = htab[bits - 9][0]; + hshift = htab[bits - 9][1]; + + /* + * Allocate the main control structure for this instance. + */ + maxmaxcode = MAXCODE(bits); + db = kzalloc(sizeof(struct bsd_db), GFP_KERNEL); + if (!db) + return NULL; + + db->xmit = data->flags & IPPP_COMP_FLAG_XMIT; + decomp = db->xmit ? 0 : 1; + + /* + * Allocate space for the dictionary. This may be more than one page in + * length. + */ + db->dict = vmalloc(hsize * sizeof(struct bsd_dict)); + if (!db->dict) { + bsd_free(db); + return NULL; + } + + /* + * If this is the compression buffer then there is no length data. + * For decompression, the length information is needed as well. + */ + if (!decomp) + db->lens = NULL; + else { + db->lens = vmalloc((maxmaxcode + 1) * sizeof(db->lens[0])); + if (!db->lens) { + bsd_free(db); + return (NULL); + } + } + + /* + * Initialize the data information for the compression code + */ + db->totlen = sizeof(struct bsd_db) + (sizeof(struct bsd_dict) * hsize); + db->hsize = hsize; + db->hshift = hshift; + db->maxmaxcode = maxmaxcode; + db->maxbits = bits; + + return (void *)db; +} + +/* + * Initialize the database. + */ +static int bsd_init(void *state, struct isdn_ppp_comp_data *data, int unit, int debug) +{ + struct bsd_db *db = state; + int indx; + int decomp; + + if (!state || !data) { + printk(KERN_ERR "isdn_bsd_init: [%d] ERR, state %lx data %lx\n", unit, (long)state, (long)data); + return 0; + } + + decomp = db->xmit ? 0 : 1; + + if (data->optlen != 1 || data->num != CI_BSD_COMPRESS + || (BSD_VERSION(data->options[0]) != BSD_CURRENT_VERSION) + || (BSD_NBITS(data->options[0]) != db->maxbits) + || (decomp && db->lens == NULL)) { + printk(KERN_ERR "isdn_bsd: %d %d %d %d %lx\n", data->optlen, data->num, data->options[0], decomp, (unsigned long)db->lens); + return 0; + } + + if (decomp) + for (indx = LAST; indx >= 0; indx--) + db->lens[indx] = 1; + + indx = db->hsize; + while (indx-- != 0) { + db->dict[indx].codem1 = BADCODEM1; + db->dict[indx].cptr = 0; + } + + db->unit = unit; + db->mru = 0; + + db->debug = 1; + + bsd_reset(db, 0, 0, NULL, 0, NULL); + + return 1; +} + +/* + * Obtain pointers to the various structures in the compression tables + */ + +#define dict_ptrx(p, idx) &(p->dict[idx]) +#define lens_ptrx(p, idx) &(p->lens[idx]) + +#ifdef DEBUG +static unsigned short *lens_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx > (unsigned int) db->maxmaxcode) { + printk(KERN_DEBUG "<9>ppp: lens_ptr(%d) > max\n", idx); + idx = 0; + } + return lens_ptrx(db, idx); +} + +static struct bsd_dict *dict_ptr(struct bsd_db *db, int idx) +{ + if ((unsigned int) idx >= (unsigned int) db->hsize) { + printk(KERN_DEBUG "<9>ppp: dict_ptr(%d) > max\n", idx); + idx = 0; + } + return dict_ptrx(db, idx); +} + +#else +#define lens_ptr(db, idx) lens_ptrx(db, idx) +#define dict_ptr(db, idx) dict_ptrx(db, idx) +#endif + +/* + * compress a packet + */ +static int bsd_compress(void *state, struct sk_buff *skb_in, struct sk_buff *skb_out, int proto) +{ + struct bsd_db *db; + int hshift; + unsigned int max_ent; + unsigned int n_bits; + unsigned int bitno; + unsigned long accm; + int ent; + unsigned long fcode; + struct bsd_dict *dictp; + unsigned char c; + int hval, disp, ilen, mxcode; + unsigned char *rptr = skb_in->data; + int isize = skb_in->len; + +#define OUTPUT(ent) \ + { \ + bitno -= n_bits; \ + accm |= ((ent) << bitno); \ + do { \ + if (skb_out && skb_tailroom(skb_out) > 0) \ + *(skb_put(skb_out, 1)) = (unsigned char)(accm >> 24); \ + accm <<= 8; \ + bitno += 8; \ + } while (bitno <= 24); \ + } + + /* + * If the protocol is not in the range we're interested in, + * just return without compressing the packet. If it is, + * the protocol becomes the first byte to compress. + */ + printk(KERN_DEBUG "bsd_compress called with %x\n", proto); + + ent = proto; + if (proto < 0x21 || proto > 0xf9 || !(proto & 0x1)) + return 0; + + db = (struct bsd_db *) state; + hshift = db->hshift; + max_ent = db->max_ent; + n_bits = db->n_bits; + bitno = 32; + accm = 0; + mxcode = MAXCODE(n_bits); + + /* This is the PPP header information */ + if (skb_out && skb_tailroom(skb_out) >= 2) { + char *v = skb_put(skb_out, 2); + /* we only push our own data on the header, + AC,PC and protos is pushed by caller */ + v[0] = db->seqno >> 8; + v[1] = db->seqno; + } + + ilen = ++isize; /* This is off by one, but that is what is in draft! */ + + while (--ilen > 0) { + c = *rptr++; + fcode = BSD_KEY(ent, c); + hval = BSD_HASH(ent, c, hshift); + dictp = dict_ptr(db, hval); + + /* Validate and then check the entry. */ + if (dictp->codem1 >= max_ent) + goto nomatch; + + if (dictp->fcode == fcode) { + ent = dictp->codem1 + 1; + continue; /* found (prefix,suffix) */ + } + + /* continue probing until a match or invalid entry */ + disp = (hval == 0) ? 1 : hval; + + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr(db, hval); + if (dictp->codem1 >= max_ent) + goto nomatch; + } while (dictp->fcode != fcode); + + ent = dictp->codem1 + 1; /* finally found (prefix,suffix) */ + continue; + + nomatch: + OUTPUT(ent); /* output the prefix */ + + /* code -> hashtable */ + if (max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2; + struct bsd_dict *dictp3; + int indx; + + /* expand code size if needed */ + if (max_ent >= mxcode) { + db->n_bits = ++n_bits; + mxcode = MAXCODE(n_bits); + } + + /* + * Invalidate old hash table entry using + * this code, and then take it over. + */ + dictp2 = dict_ptr(db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr(db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + if (db->lens) { + unsigned short *len1 = lens_ptr(db, max_ent); + unsigned short *len2 = lens_ptr(db, ent); + *len1 = *len2 + 1; + } + } + ent = c; + } + + OUTPUT(ent); /* output the last code */ + + if (skb_out) + db->bytes_out += skb_out->len; /* Do not count bytes from here */ + db->uncomp_bytes += isize; + db->in_count += isize; + ++db->uncomp_count; + ++db->seqno; + + if (bitno < 32) + ++db->bytes_out; /* must be set before calling bsd_check */ + + /* + * Generate the clear command if needed + */ + + if (bsd_check(db)) + OUTPUT(CLEAR); + + /* + * Pad dribble bits of last code with ones. + * Do not emit a completely useless byte of ones. + */ + if (bitno < 32 && skb_out && skb_tailroom(skb_out) > 0) + *(skb_put(skb_out, 1)) = (unsigned char)((accm | (0xff << (bitno - 8))) >> 24); + + /* + * Increase code size if we would have without the packet + * boundary because the decompressor will do so. + */ + if (max_ent >= mxcode && max_ent < db->maxmaxcode) + db->n_bits++; + + /* If output length is too large then this is an incompressible frame. */ + if (!skb_out || skb_out->len >= skb_in->len) { + ++db->incomp_count; + db->incomp_bytes += isize; + return 0; + } + + /* Count the number of compressed frames */ + ++db->comp_count; + db->comp_bytes += skb_out->len; + return skb_out->len; + +#undef OUTPUT +} + +/* + * Update the "BSD Compress" dictionary on the receiver for + * incompressible data by pretending to compress the incoming data. + */ +static void bsd_incomp(void *state, struct sk_buff *skb_in, int proto) +{ + bsd_compress(state, skb_in, NULL, proto); +} + +/* + * Decompress "BSD Compress". + */ +static int bsd_decompress(void *state, struct sk_buff *skb_in, struct sk_buff *skb_out, + struct isdn_ppp_resetparams *rsparm) +{ + struct bsd_db *db; + unsigned int max_ent; + unsigned long accm; + unsigned int bitno; /* 1st valid bit in accm */ + unsigned int n_bits; + unsigned int tgtbitno; /* bitno when we have a code */ + struct bsd_dict *dictp; + int seq; + unsigned int incode; + unsigned int oldcode; + unsigned int finchar; + unsigned char *p, *ibuf; + int ilen; + int codelen; + int extra; + + db = (struct bsd_db *) state; + max_ent = db->max_ent; + accm = 0; + bitno = 32; /* 1st valid bit in accm */ + n_bits = db->n_bits; + tgtbitno = 32 - n_bits; /* bitno when we have a code */ + + printk(KERN_DEBUG "bsd_decompress called\n"); + + if (!skb_in || !skb_out) { + printk(KERN_ERR "bsd_decompress called with NULL parameter\n"); + return DECOMP_ERROR; + } + + /* + * Get the sequence number. + */ + if ((p = skb_pull(skb_in, 2)) == NULL) { + return DECOMP_ERROR; + } + p -= 2; + seq = (p[0] << 8) + p[1]; + ilen = skb_in->len; + ibuf = skb_in->data; + + /* + * Check the sequence number and give up if it differs from + * the value we're expecting. + */ + if (seq != db->seqno) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad sequence # %d, expected %d\n", + db->unit, seq, db->seqno - 1); + } + return DECOMP_ERROR; + } + + ++db->seqno; + db->bytes_out += ilen; + + if (skb_tailroom(skb_out) > 0) + *(skb_put(skb_out, 1)) = 0; + else + return DECOMP_ERR_NOMEM; + + oldcode = CLEAR; + + /* + * Keep the checkpoint correctly so that incompressible packets + * clear the dictionary at the proper times. + */ + + for (;;) { + if (ilen-- <= 0) { + db->in_count += (skb_out->len - 1); /* don't count the header */ + break; + } + + /* + * Accumulate bytes until we have a complete code. + * Then get the next code, relying on the 32-bit, + * unsigned accm to mask the result. + */ + + bitno -= 8; + accm |= *ibuf++ << bitno; + if (tgtbitno < bitno) + continue; + + incode = accm >> tgtbitno; + accm <<= n_bits; + bitno += n_bits; + + /* + * The dictionary must only be cleared at the end of a packet. + */ + + if (incode == CLEAR) { + if (ilen > 0) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: bad CLEAR\n", db->unit); + return DECOMP_FATALERROR; /* probably a bug */ + } + bsd_clear(db); + break; + } + + if ((incode > max_ent + 2) || (incode > db->maxmaxcode) + || (incode > max_ent && oldcode == CLEAR)) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: bad code 0x%x oldcode=0x%x ", + db->unit, incode, oldcode); + printk(KERN_DEBUG "max_ent=0x%x skb->Len=%d seqno=%d\n", + max_ent, skb_out->len, db->seqno); + } + return DECOMP_FATALERROR; /* probably a bug */ + } + + /* Special case for KwKwK string. */ + if (incode > max_ent) { + finchar = oldcode; + extra = 1; + } else { + finchar = incode; + extra = 0; + } + + codelen = *(lens_ptr(db, finchar)); + if (skb_tailroom(skb_out) < codelen + extra) { + if (db->debug) { + printk(KERN_DEBUG "bsd_decomp%d: ran out of mru\n", db->unit); +#ifdef DEBUG + printk(KERN_DEBUG " len=%d, finchar=0x%x, codelen=%d,skblen=%d\n", + ilen, finchar, codelen, skb_out->len); +#endif + } + return DECOMP_FATALERROR; + } + + /* + * Decode this code and install it in the decompressed buffer. + */ + + p = skb_put(skb_out, codelen); + p += codelen; + while (finchar > LAST) { + struct bsd_dict *dictp2 = dict_ptr(db, finchar); + + dictp = dict_ptr(db, dictp2->cptr); + +#ifdef DEBUG + if (--codelen <= 0 || dictp->codem1 != finchar - 1) { + if (codelen <= 0) { + printk(KERN_ERR "bsd_decomp%d: fell off end of chain ", db->unit); + printk(KERN_ERR "0x%x at 0x%x by 0x%x, max_ent=0x%x\n", incode, finchar, dictp2->cptr, max_ent); + } else { + if (dictp->codem1 != finchar - 1) { + printk(KERN_ERR "bsd_decomp%d: bad code chain 0x%x finchar=0x%x ", db->unit, incode, finchar); + printk(KERN_ERR "oldcode=0x%x cptr=0x%x codem1=0x%x\n", oldcode, dictp2->cptr, dictp->codem1); + } + } + return DECOMP_FATALERROR; + } +#endif + + { + u32 fcode = dictp->fcode; + *--p = (fcode >> 16) & 0xff; + finchar = fcode & 0xffff; + } + } + *--p = finchar; + +#ifdef DEBUG + if (--codelen != 0) + printk(KERN_ERR "bsd_decomp%d: short by %d after code 0x%x, max_ent=0x%x\n", db->unit, codelen, incode, max_ent); +#endif + + if (extra) /* the KwKwK case again */ + *(skb_put(skb_out, 1)) = finchar; + + /* + * If not first code in a packet, and + * if not out of code space, then allocate a new code. + * + * Keep the hash table correct so it can be used + * with uncompressed packets. + */ + if (oldcode != CLEAR && max_ent < db->maxmaxcode) { + struct bsd_dict *dictp2, *dictp3; + u16 *lens1, *lens2; + unsigned long fcode; + int hval, disp, indx; + + fcode = BSD_KEY(oldcode, finchar); + hval = BSD_HASH(oldcode, finchar, db->hshift); + dictp = dict_ptr(db, hval); + + /* look for a free hash table entry */ + if (dictp->codem1 < max_ent) { + disp = (hval == 0) ? 1 : hval; + do { + hval += disp; + if (hval >= db->hsize) + hval -= db->hsize; + dictp = dict_ptr(db, hval); + } while (dictp->codem1 < max_ent); + } + + /* + * Invalidate previous hash table entry + * assigned this code, and then take it over + */ + + dictp2 = dict_ptr(db, max_ent + 1); + indx = dictp2->cptr; + dictp3 = dict_ptr(db, indx); + + if (dictp3->codem1 == max_ent) + dictp3->codem1 = BADCODEM1; + + dictp2->cptr = hval; + dictp->codem1 = max_ent; + dictp->fcode = fcode; + db->max_ent = ++max_ent; + + /* Update the length of this string. */ + lens1 = lens_ptr(db, max_ent); + lens2 = lens_ptr(db, oldcode); + *lens1 = *lens2 + 1; + + /* Expand code size if needed. */ + if (max_ent >= MAXCODE(n_bits) && max_ent < db->maxmaxcode) { + db->n_bits = ++n_bits; + tgtbitno = 32-n_bits; + } + } + oldcode = incode; + } + + ++db->comp_count; + ++db->uncomp_count; + db->comp_bytes += skb_in->len - BSD_OVHD; + db->uncomp_bytes += skb_out->len; + + if (bsd_check(db)) { + if (db->debug) + printk(KERN_DEBUG "bsd_decomp%d: peer should have cleared dictionary on %d\n", + db->unit, db->seqno - 1); + } + return skb_out->len; +} + +/************************************************************* + * Table of addresses for the BSD compression module + *************************************************************/ + +static struct isdn_ppp_compressor ippp_bsd_compress = { + .owner = THIS_MODULE, + .num = CI_BSD_COMPRESS, + .alloc = bsd_alloc, + .free = bsd_free, + .init = bsd_init, + .reset = bsd_reset, + .compress = bsd_compress, + .decompress = bsd_decompress, + .incomp = bsd_incomp, + .stat = bsd_stats, +}; + +/************************************************************* + * Module support routines + *************************************************************/ + +static int __init isdn_bsdcomp_init(void) +{ + int answer = isdn_ppp_register_compressor(&ippp_bsd_compress); + if (answer == 0) + printk(KERN_INFO "PPP BSD Compression module registered\n"); + return answer; +} + +static void __exit isdn_bsdcomp_exit(void) +{ + isdn_ppp_unregister_compressor(&ippp_bsd_compress); +} + +module_init(isdn_bsdcomp_init); +module_exit(isdn_bsdcomp_exit); diff --git a/kernel/drivers/isdn/i4l/isdn_common.c b/kernel/drivers/isdn/i4l/isdn_common.c new file mode 100644 index 000000000..9b856e189 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_common.c @@ -0,0 +1,2391 @@ +/* $Id: isdn_common.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $ + * + * Linux ISDN subsystem, common used functions (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/isdn.h> +#include <linux/mutex.h> +#include "isdn_common.h" +#include "isdn_tty.h" +#include "isdn_net.h" +#include "isdn_ppp.h" +#ifdef CONFIG_ISDN_AUDIO +#include "isdn_audio.h" +#endif +#ifdef CONFIG_ISDN_DIVERSION_MODULE +#define CONFIG_ISDN_DIVERSION +#endif +#ifdef CONFIG_ISDN_DIVERSION +#include <linux/isdn_divertif.h> +#endif /* CONFIG_ISDN_DIVERSION */ +#include "isdn_v110.h" + +/* Debugflags */ +#undef ISDN_DEBUG_STATCALLB + +MODULE_DESCRIPTION("ISDN4Linux: link layer"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); + +isdn_dev *dev; + +static DEFINE_MUTEX(isdn_mutex); +static char *isdn_revision = "$Revision: 1.1.2.3 $"; + +extern char *isdn_net_revision; +#ifdef CONFIG_ISDN_PPP +extern char *isdn_ppp_revision; +#else +static char *isdn_ppp_revision = ": none $"; +#endif +#ifdef CONFIG_ISDN_AUDIO +extern char *isdn_audio_revision; +#else +static char *isdn_audio_revision = ": none $"; +#endif +extern char *isdn_v110_revision; + +#ifdef CONFIG_ISDN_DIVERSION +static isdn_divert_if *divert_if; /* = NULL */ +#endif /* CONFIG_ISDN_DIVERSION */ + + +static int isdn_writebuf_stub(int, int, const u_char __user *, int); +static void set_global_features(void); +static int isdn_wildmat(char *s, char *p); +static int isdn_add_channels(isdn_driver_t *d, int drvidx, int n, int adding); + +static inline void +isdn_lock_driver(isdn_driver_t *drv) +{ + try_module_get(drv->interface->owner); + drv->locks++; +} + +void +isdn_lock_drivers(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (!dev->drv[i]) + continue; + isdn_lock_driver(dev->drv[i]); + } +} + +static inline void +isdn_unlock_driver(isdn_driver_t *drv) +{ + if (drv->locks > 0) { + drv->locks--; + module_put(drv->interface->owner); + } +} + +void +isdn_unlock_drivers(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (!dev->drv[i]) + continue; + isdn_unlock_driver(dev->drv[i]); + } +} + +#if defined(ISDN_DEBUG_NET_DUMP) || defined(ISDN_DEBUG_MODEM_DUMP) +void +isdn_dumppkt(char *s, u_char *p, int len, int dumplen) +{ + int dumpc; + + printk(KERN_DEBUG "%s(%d) ", s, len); + for (dumpc = 0; (dumpc < dumplen) && (len); len--, dumpc++) + printk(" %02x", *p++); + printk("\n"); +} +#endif + +/* + * I picked the pattern-matching-functions from an old GNU-tar version (1.10) + * It was originally written and put to PD by rs@mirror.TMC.COM (Rich Salz) + */ +static int +isdn_star(char *s, char *p) +{ + while (isdn_wildmat(s, p)) { + if (*++s == '\0') + return (2); + } + return (0); +} + +/* + * Shell-type Pattern-matching for incoming caller-Ids + * This function gets a string in s and checks, if it matches the pattern + * given in p. + * + * Return: + * 0 = match. + * 1 = no match. + * 2 = no match. Would eventually match, if s would be longer. + * + * Possible Patterns: + * + * '?' matches one character + * '*' matches zero or more characters + * [xyz] matches the set of characters in brackets. + * [^xyz] matches any single character not in the set of characters + */ + +static int +isdn_wildmat(char *s, char *p) +{ + register int last; + register int matched; + register int reverse; + register int nostar = 1; + + if (!(*s) && !(*p)) + return (1); + for (; *p; s++, p++) + switch (*p) { + case '\\': + /* + * Literal match with following character, + * fall through. + */ + p++; + default: + if (*s != *p) + return (*s == '\0') ? 2 : 1; + continue; + case '?': + /* Match anything. */ + if (*s == '\0') + return (2); + continue; + case '*': + nostar = 0; + /* Trailing star matches everything. */ + return (*++p ? isdn_star(s, p) : 0); + case '[': + /* [^....] means inverse character class. */ + if ((reverse = (p[1] == '^'))) + p++; + for (last = 0, matched = 0; *++p && (*p != ']'); last = *p) + /* This next line requires a good C compiler. */ + if (*p == '-' ? *s <= *++p && *s >= last : *s == *p) + matched = 1; + if (matched == reverse) + return (1); + continue; + } + return (*s == '\0') ? 0 : nostar; +} + +int isdn_msncmp(const char *msn1, const char *msn2) +{ + char TmpMsn1[ISDN_MSNLEN]; + char TmpMsn2[ISDN_MSNLEN]; + char *p; + + for (p = TmpMsn1; *msn1 && *msn1 != ':';) // Strip off a SPID + *p++ = *msn1++; + *p = '\0'; + + for (p = TmpMsn2; *msn2 && *msn2 != ':';) // Strip off a SPID + *p++ = *msn2++; + *p = '\0'; + + return isdn_wildmat(TmpMsn1, TmpMsn2); +} + +int +isdn_dc2minor(int di, int ch) +{ + int i; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->chanmap[i] == ch && dev->drvmap[i] == di) + return i; + return -1; +} + +static int isdn_timer_cnt1 = 0; +static int isdn_timer_cnt2 = 0; +static int isdn_timer_cnt3 = 0; + +static void +isdn_timer_funct(ulong dummy) +{ + int tf = dev->tflags; + if (tf & ISDN_TIMER_FAST) { + if (tf & ISDN_TIMER_MODEMREAD) + isdn_tty_readmodem(); + if (tf & ISDN_TIMER_MODEMPLUS) + isdn_tty_modem_escape(); + if (tf & ISDN_TIMER_MODEMXMIT) + isdn_tty_modem_xmit(); + } + if (tf & ISDN_TIMER_SLOW) { + if (++isdn_timer_cnt1 >= ISDN_TIMER_02SEC) { + isdn_timer_cnt1 = 0; + if (tf & ISDN_TIMER_NETDIAL) + isdn_net_dial(); + } + if (++isdn_timer_cnt2 >= ISDN_TIMER_1SEC) { + isdn_timer_cnt2 = 0; + if (tf & ISDN_TIMER_NETHANGUP) + isdn_net_autohup(); + if (++isdn_timer_cnt3 >= ISDN_TIMER_RINGING) { + isdn_timer_cnt3 = 0; + if (tf & ISDN_TIMER_MODEMRING) + isdn_tty_modem_ring(); + } + if (tf & ISDN_TIMER_CARRIER) + isdn_tty_carrier_timeout(); + } + } + if (tf) + mod_timer(&dev->timer, jiffies + ISDN_TIMER_RES); +} + +void +isdn_timer_ctrl(int tf, int onoff) +{ + unsigned long flags; + int old_tflags; + + spin_lock_irqsave(&dev->timerlock, flags); + if ((tf & ISDN_TIMER_SLOW) && (!(dev->tflags & ISDN_TIMER_SLOW))) { + /* If the slow-timer wasn't activated until now */ + isdn_timer_cnt1 = 0; + isdn_timer_cnt2 = 0; + } + old_tflags = dev->tflags; + if (onoff) + dev->tflags |= tf; + else + dev->tflags &= ~tf; + if (dev->tflags && !old_tflags) + mod_timer(&dev->timer, jiffies + ISDN_TIMER_RES); + spin_unlock_irqrestore(&dev->timerlock, flags); +} + +/* + * Receive a packet from B-Channel. (Called from low-level-module) + */ +static void +isdn_receive_skb_callback(int di, int channel, struct sk_buff *skb) +{ + int i; + + if ((i = isdn_dc2minor(di, channel)) == -1) { + dev_kfree_skb(skb); + return; + } + /* Update statistics */ + dev->ibytes[i] += skb->len; + + /* First, try to deliver data to network-device */ + if (isdn_net_rcv_skb(i, skb)) + return; + + /* V.110 handling + * makes sense for async streams only, so it is + * called after possible net-device delivery. + */ + if (dev->v110[i]) { + atomic_inc(&dev->v110use[i]); + skb = isdn_v110_decode(dev->v110[i], skb); + atomic_dec(&dev->v110use[i]); + if (!skb) + return; + } + + /* No network-device found, deliver to tty or raw-channel */ + if (skb->len) { + if (isdn_tty_rcv_skb(i, di, channel, skb)) + return; + wake_up_interruptible(&dev->drv[di]->rcv_waitq[channel]); + } else + dev_kfree_skb(skb); +} + +/* + * Intercept command from Linklevel to Lowlevel. + * If layer 2 protocol is V.110 and this is not supported by current + * lowlevel-driver, use driver's transparent mode and handle V.110 in + * linklevel instead. + */ +int +isdn_command(isdn_ctrl *cmd) +{ + if (cmd->driver == -1) { + printk(KERN_WARNING "isdn_command command(%x) driver -1\n", cmd->command); + return (1); + } + if (!dev->drv[cmd->driver]) { + printk(KERN_WARNING "isdn_command command(%x) dev->drv[%d] NULL\n", + cmd->command, cmd->driver); + return (1); + } + if (!dev->drv[cmd->driver]->interface) { + printk(KERN_WARNING "isdn_command command(%x) dev->drv[%d]->interface NULL\n", + cmd->command, cmd->driver); + return (1); + } + if (cmd->command == ISDN_CMD_SETL2) { + int idx = isdn_dc2minor(cmd->driver, cmd->arg & 255); + unsigned long l2prot = (cmd->arg >> 8) & 255; + unsigned long features = (dev->drv[cmd->driver]->interface->features + >> ISDN_FEATURE_L2_SHIFT) & + ISDN_FEATURE_L2_MASK; + unsigned long l2_feature = (1 << l2prot); + + switch (l2prot) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + /* If V.110 requested, but not supported by + * HL-driver, set emulator-flag and change + * Layer-2 to transparent + */ + if (!(features & l2_feature)) { + dev->v110emu[idx] = l2prot; + cmd->arg = (cmd->arg & 255) | + (ISDN_PROTO_L2_TRANS << 8); + } else + dev->v110emu[idx] = 0; + } + } + return dev->drv[cmd->driver]->interface->command(cmd); +} + +void +isdn_all_eaz(int di, int ch) +{ + isdn_ctrl cmd; + + if (di < 0) + return; + cmd.driver = di; + cmd.arg = ch; + cmd.command = ISDN_CMD_SETEAZ; + cmd.parm.num[0] = '\0'; + isdn_command(&cmd); +} + +/* + * Begin of a CAPI like LL<->HL interface, currently used only for + * supplementary service (CAPI 2.0 part III) + */ +#include <linux/isdn/capicmd.h> + +static int +isdn_capi_rec_hl_msg(capi_msg *cm) +{ + switch (cm->Command) { + case CAPI_FACILITY: + /* in the moment only handled in tty */ + return (isdn_tty_capi_facility(cm)); + default: + return (-1); + } +} + +static int +isdn_status_callback(isdn_ctrl *c) +{ + int di; + u_long flags; + int i; + int r; + int retval = 0; + isdn_ctrl cmd; + isdn_net_dev *p; + + di = c->driver; + i = isdn_dc2minor(di, c->arg); + switch (c->command) { + case ISDN_STAT_BSENT: + if (i < 0) + return -1; + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (isdn_net_stat_callback(i, c)) + return 0; + if (isdn_v110_stat_callback(i, c)) + return 0; + if (isdn_tty_stat_callback(i, c)) + return 0; + wake_up_interruptible(&dev->drv[di]->snd_waitq[c->arg]); + break; + case ISDN_STAT_STAVAIL: + dev->drv[di]->stavail += c->arg; + wake_up_interruptible(&dev->drv[di]->st_waitq); + break; + case ISDN_STAT_RUN: + dev->drv[di]->flags |= DRV_FLAG_RUNNING; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) + isdn_all_eaz(di, dev->chanmap[i]); + set_global_features(); + break; + case ISDN_STAT_STOP: + dev->drv[di]->flags &= ~DRV_FLAG_RUNNING; + break; + case ISDN_STAT_ICALL: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "ICALL (net): %d %ld %s\n", di, c->arg, c->parm.num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + return 0; + } + /* Try to find a network-interface which will accept incoming call */ + r = ((c->command == ISDN_STAT_ICALLW) ? 0 : isdn_net_find_icall(di, c->arg, i, &c->parm.setup)); + switch (r) { + case 0: + /* No network-device replies. + * Try ttyI's. + * These return 0 on no match, 1 on match and + * 3 on eventually match, if CID is longer. + */ + if (c->command == ISDN_STAT_ICALL) + if ((retval = isdn_tty_find_icall(di, c->arg, &c->parm.setup))) return (retval); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + if ((retval = divert_if->stat_callback(c))) + return (retval); /* processed */ +#endif /* CONFIG_ISDN_DIVERSION */ + if ((!retval) && (dev->drv[di]->flags & DRV_FLAG_REJBUS)) { + /* No tty responding */ + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + retval = 2; + } + break; + case 1: + /* Schedule connection-setup */ + isdn_net_dial(); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTD; + for (p = dev->netdev; p; p = p->next) + if (p->local->isdn_channel == cmd.arg) + { + strcpy(cmd.parm.setup.eazmsn, p->local->msn); + isdn_command(&cmd); + retval = 1; + break; + } + break; + + case 2: /* For calling back, first reject incoming call ... */ + case 3: /* Interface found, but down, reject call actively */ + retval = 2; + printk(KERN_INFO "isdn: Rejecting Call\n"); + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_HANGUP; + isdn_command(&cmd); + if (r == 3) + break; + /* Fall through */ + case 4: + /* ... then start callback. */ + isdn_net_dial(); + break; + case 5: + /* Number would eventually match, if longer */ + retval = 3; + break; + } +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "ICALL: ret=%d\n", retval); +#endif + return retval; + break; + case ISDN_STAT_CINF: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CINF: %ld %s\n", c->arg, c->parm.num); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (strcmp(c->parm.num, "0")) + isdn_net_stat_callback(i, c); + isdn_tty_stat_callback(i, c); + break; + case ISDN_STAT_CAUSE: +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "CAUSE: %ld %s\n", c->arg, c->parm.num); +#endif + printk(KERN_INFO "isdn: %s,ch%ld cause: %s\n", + dev->drvid[di], c->arg, c->parm.num); + isdn_tty_stat_callback(i, c); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + break; + case ISDN_STAT_DISPLAY: +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DISPLAY: %ld %s\n", c->arg, c->parm.display); +#endif + isdn_tty_stat_callback(i, c); +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + break; + case ISDN_STAT_DCONN: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DCONN: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + /* Find any net-device, waiting for D-channel setup */ + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + /* Find any ttyI, waiting for D-channel setup */ + if (isdn_tty_stat_callback(i, c)) { + cmd.driver = di; + cmd.arg = c->arg; + cmd.command = ISDN_CMD_ACCEPTB; + isdn_command(&cmd); + break; + } + break; + case ISDN_STAT_DHUP: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "DHUP: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->online &= ~(1 << (c->arg)); + isdn_info_update(); + /* Signal hangup to network-devices */ + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; +#ifdef CONFIG_ISDN_DIVERSION + if (divert_if) + divert_if->stat_callback(c); +#endif /* CONFIG_ISDN_DIVERSION */ + break; + break; + case ISDN_STAT_BCONN: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "BCONN: %ld\n", c->arg); +#endif + /* Signal B-channel-connect to network-devices */ + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->online |= (1 << (c->arg)); + isdn_info_update(); + if (isdn_net_stat_callback(i, c)) + break; + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; + break; + case ISDN_STAT_BHUP: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "BHUP: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + dev->drv[di]->online &= ~(1 << (c->arg)); + isdn_info_update(); +#ifdef CONFIG_ISDN_X25 + /* Signal hangup to network-devices */ + if (isdn_net_stat_callback(i, c)) + break; +#endif + isdn_v110_stat_callback(i, c); + if (isdn_tty_stat_callback(i, c)) + break; + break; + case ISDN_STAT_NODCH: + if (i < 0) + return -1; +#ifdef ISDN_DEBUG_STATCALLB + printk(KERN_DEBUG "NODCH: %ld\n", c->arg); +#endif + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + return 0; + if (isdn_net_stat_callback(i, c)) + break; + if (isdn_tty_stat_callback(i, c)) + break; + break; + case ISDN_STAT_ADDCH: + spin_lock_irqsave(&dev->lock, flags); + if (isdn_add_channels(dev->drv[di], di, c->arg, 1)) { + spin_unlock_irqrestore(&dev->lock, flags); + return -1; + } + spin_unlock_irqrestore(&dev->lock, flags); + isdn_info_update(); + break; + case ISDN_STAT_DISCH: + spin_lock_irqsave(&dev->lock, flags); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if ((dev->drvmap[i] == di) && + (dev->chanmap[i] == c->arg)) { + if (c->parm.num[0]) + dev->usage[i] &= ~ISDN_USAGE_DISABLED; + else + if (USG_NONE(dev->usage[i])) { + dev->usage[i] |= ISDN_USAGE_DISABLED; + } + else + retval = -1; + break; + } + spin_unlock_irqrestore(&dev->lock, flags); + isdn_info_update(); + break; + case ISDN_STAT_UNLOAD: + while (dev->drv[di]->locks > 0) { + isdn_unlock_driver(dev->drv[di]); + } + spin_lock_irqsave(&dev->lock, flags); + isdn_tty_stat_callback(i, c); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (dev->drvmap[i] == di) { + dev->drvmap[i] = -1; + dev->chanmap[i] = -1; + dev->usage[i] &= ~ISDN_USAGE_DISABLED; + } + dev->drivers--; + dev->channels -= dev->drv[di]->channels; + kfree(dev->drv[di]->rcverr); + kfree(dev->drv[di]->rcvcount); + for (i = 0; i < dev->drv[di]->channels; i++) + skb_queue_purge(&dev->drv[di]->rpqueue[i]); + kfree(dev->drv[di]->rpqueue); + kfree(dev->drv[di]->rcv_waitq); + kfree(dev->drv[di]); + dev->drv[di] = NULL; + dev->drvid[di][0] = '\0'; + isdn_info_update(); + set_global_features(); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + case ISDN_STAT_L1ERR: + break; + case CAPI_PUT_MESSAGE: + return (isdn_capi_rec_hl_msg(&c->parm.cmsg)); +#ifdef CONFIG_ISDN_TTY_FAX + case ISDN_STAT_FAXIND: + isdn_tty_stat_callback(i, c); + break; +#endif +#ifdef CONFIG_ISDN_AUDIO + case ISDN_STAT_AUDIO: + isdn_tty_stat_callback(i, c); + break; +#endif +#ifdef CONFIG_ISDN_DIVERSION + case ISDN_STAT_PROT: + case ISDN_STAT_REDIR: + if (divert_if) + return (divert_if->stat_callback(c)); +#endif /* CONFIG_ISDN_DIVERSION */ + default: + return -1; + } + return 0; +} + +/* + * Get integer from char-pointer, set pointer to end of number + */ +int +isdn_getnum(char **p) +{ + int v = -1; + + while (*p[0] >= '0' && *p[0] <= '9') + v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0'); + return v; +} + +#define DLE 0x10 + +/* + * isdn_readbchan() tries to get data from the read-queue. + * It MUST be called with interrupts off. + * + * Be aware that this is not an atomic operation when sleep != 0, even though + * interrupts are turned off! Well, like that we are currently only called + * on behalf of a read system call on raw device files (which are documented + * to be dangerous and for debugging purpose only). The inode semaphore + * takes care that this is not called for the same minor device number while + * we are sleeping, but access is not serialized against simultaneous read() + * from the corresponding ttyI device. Can other ugly events, like changes + * of the mapping (di,ch)<->minor, happen during the sleep? --he + */ +int +isdn_readbchan(int di, int channel, u_char *buf, u_char *fp, int len, wait_queue_head_t *sleep) +{ + int count; + int count_pull; + int count_put; + int dflag; + struct sk_buff *skb; + u_char *cp; + + if (!dev->drv[di]) + return 0; + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) { + if (sleep) + wait_event_interruptible(*sleep, + !skb_queue_empty(&dev->drv[di]->rpqueue[channel])); + else + return 0; + } + if (len > dev->drv[di]->rcvcount[channel]) + len = dev->drv[di]->rcvcount[channel]; + cp = buf; + count = 0; + while (len) { + if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel]))) + break; +#ifdef CONFIG_ISDN_AUDIO + if (ISDN_AUDIO_SKB_LOCK(skb)) + break; + ISDN_AUDIO_SKB_LOCK(skb) = 1; + if ((ISDN_AUDIO_SKB_DLECOUNT(skb)) || (dev->drv[di]->DLEflag & (1 << channel))) { + char *p = skb->data; + unsigned long DLEmask = (1 << channel); + + dflag = 0; + count_pull = count_put = 0; + while ((count_pull < skb->len) && (len > 0)) { + len--; + if (dev->drv[di]->DLEflag & DLEmask) { + *cp++ = DLE; + dev->drv[di]->DLEflag &= ~DLEmask; + } else { + *cp++ = *p; + if (*p == DLE) { + dev->drv[di]->DLEflag |= DLEmask; + (ISDN_AUDIO_SKB_DLECOUNT(skb))--; + } + p++; + count_pull++; + } + count_put++; + } + if (count_pull >= skb->len) + dflag = 1; + } else { +#endif + /* No DLE's in buff, so simply copy it */ + dflag = 1; + if ((count_pull = skb->len) > len) { + count_pull = len; + dflag = 0; + } + count_put = count_pull; + skb_copy_from_linear_data(skb, cp, count_put); + cp += count_put; + len -= count_put; +#ifdef CONFIG_ISDN_AUDIO + } +#endif + count += count_put; + if (fp) { + memset(fp, 0, count_put); + fp += count_put; + } + if (dflag) { + /* We got all the data in this buff. + * Now we can dequeue it. + */ + if (fp) + *(fp - 1) = 0xff; +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]); + dev_kfree_skb(skb); + } else { + /* Not yet emptied this buff, so it + * must stay in the queue, for further calls + * but we pull off the data we got until now. + */ + skb_pull(skb, count_pull); +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + } + dev->drv[di]->rcvcount[channel] -= count_put; + } + return count; +} + +/* + * isdn_readbchan_tty() tries to get data from the read-queue. + * It MUST be called with interrupts off. + * + * Be aware that this is not an atomic operation when sleep != 0, even though + * interrupts are turned off! Well, like that we are currently only called + * on behalf of a read system call on raw device files (which are documented + * to be dangerous and for debugging purpose only). The inode semaphore + * takes care that this is not called for the same minor device number while + * we are sleeping, but access is not serialized against simultaneous read() + * from the corresponding ttyI device. Can other ugly events, like changes + * of the mapping (di,ch)<->minor, happen during the sleep? --he + */ +int +isdn_readbchan_tty(int di, int channel, struct tty_port *port, int cisco_hack) +{ + int count; + int count_pull; + int count_put; + int dflag; + struct sk_buff *skb; + char last = 0; + int len; + + if (!dev->drv[di]) + return 0; + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) + return 0; + + len = tty_buffer_request_room(port, dev->drv[di]->rcvcount[channel]); + if (len == 0) + return len; + + count = 0; + while (len) { + if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel]))) + break; +#ifdef CONFIG_ISDN_AUDIO + if (ISDN_AUDIO_SKB_LOCK(skb)) + break; + ISDN_AUDIO_SKB_LOCK(skb) = 1; + if ((ISDN_AUDIO_SKB_DLECOUNT(skb)) || (dev->drv[di]->DLEflag & (1 << channel))) { + char *p = skb->data; + unsigned long DLEmask = (1 << channel); + + dflag = 0; + count_pull = count_put = 0; + while ((count_pull < skb->len) && (len > 0)) { + /* push every character but the last to the tty buffer directly */ + if (count_put) + tty_insert_flip_char(port, last, TTY_NORMAL); + len--; + if (dev->drv[di]->DLEflag & DLEmask) { + last = DLE; + dev->drv[di]->DLEflag &= ~DLEmask; + } else { + last = *p; + if (last == DLE) { + dev->drv[di]->DLEflag |= DLEmask; + (ISDN_AUDIO_SKB_DLECOUNT(skb))--; + } + p++; + count_pull++; + } + count_put++; + } + if (count_pull >= skb->len) + dflag = 1; + } else { +#endif + /* No DLE's in buff, so simply copy it */ + dflag = 1; + if ((count_pull = skb->len) > len) { + count_pull = len; + dflag = 0; + } + count_put = count_pull; + if (count_put > 1) + tty_insert_flip_string(port, skb->data, count_put - 1); + last = skb->data[count_put - 1]; + len -= count_put; +#ifdef CONFIG_ISDN_AUDIO + } +#endif + count += count_put; + if (dflag) { + /* We got all the data in this buff. + * Now we can dequeue it. + */ + if (cisco_hack) + tty_insert_flip_char(port, last, 0xFF); + else + tty_insert_flip_char(port, last, TTY_NORMAL); +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]); + dev_kfree_skb(skb); + } else { + tty_insert_flip_char(port, last, TTY_NORMAL); + /* Not yet emptied this buff, so it + * must stay in the queue, for further calls + * but we pull off the data we got until now. + */ + skb_pull(skb, count_pull); +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_LOCK(skb) = 0; +#endif + } + dev->drv[di]->rcvcount[channel] -= count_put; + } + return count; +} + + +static inline int +isdn_minor2drv(int minor) +{ + return (dev->drvmap[minor]); +} + +static inline int +isdn_minor2chan(int minor) +{ + return (dev->chanmap[minor]); +} + +static char * +isdn_statstr(void) +{ + static char istatbuf[2048]; + char *p; + int i; + + sprintf(istatbuf, "idmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%s ", (dev->drvmap[i] < 0) ? "-" : dev->drvid[dev->drvmap[i]]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nchmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->chanmap[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\ndrmap:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->drvmap[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nusage:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%d ", dev->usage[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\nflags:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_DRIVERS; i++) { + if (dev->drv[i]) { + sprintf(p, "%ld ", dev->drv[i]->online); + p = istatbuf + strlen(istatbuf); + } else { + sprintf(p, "? "); + p = istatbuf + strlen(istatbuf); + } + } + sprintf(p, "\nphone:\t"); + p = istatbuf + strlen(istatbuf); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + sprintf(p, "%s ", dev->num[i]); + p = istatbuf + strlen(istatbuf); + } + sprintf(p, "\n"); + return istatbuf; +} + +/* Module interface-code */ + +void +isdn_info_update(void) +{ + infostruct *p = dev->infochain; + + while (p) { + *(p->private) = 1; + p = (infostruct *) p->next; + } + wake_up_interruptible(&(dev->info_waitq)); +} + +static ssize_t +isdn_read(struct file *file, char __user *buf, size_t count, loff_t *off) +{ + uint minor = iminor(file_inode(file)); + int len = 0; + int drvidx; + int chidx; + int retval; + char *p; + + mutex_lock(&isdn_mutex); + if (minor == ISDN_MINOR_STATUS) { + if (!file->private_data) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + wait_event_interruptible(dev->info_waitq, + file->private_data); + } + p = isdn_statstr(); + file->private_data = NULL; + if ((len = strlen(p)) <= count) { + if (copy_to_user(buf, p, len)) { + retval = -EFAULT; + goto out; + } + *off += len; + retval = len; + goto out; + } + retval = 0; + goto out; + } + if (!dev->drivers) { + retval = -ENODEV; + goto out; + } + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_read minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) { + retval = -ENODEV; + goto out; + } + chidx = isdn_minor2chan(minor); + if (!(p = kmalloc(count, GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + len = isdn_readbchan(drvidx, chidx, p, NULL, count, + &dev->drv[drvidx]->rcv_waitq[chidx]); + *off += len; + if (copy_to_user(buf, p, len)) + len = -EFAULT; + kfree(p); + retval = len; + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!dev->drv[drvidx]->stavail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + wait_event_interruptible(dev->drv[drvidx]->st_waitq, + dev->drv[drvidx]->stavail); + } + if (dev->drv[drvidx]->interface->readstat) { + if (count > dev->drv[drvidx]->stavail) + count = dev->drv[drvidx]->stavail; + len = dev->drv[drvidx]->interface->readstat(buf, count, + drvidx, isdn_minor2chan(minor - ISDN_MINOR_CTRL)); + if (len < 0) { + retval = len; + goto out; + } + } else { + len = 0; + } + if (len) + dev->drv[drvidx]->stavail -= len; + else + dev->drv[drvidx]->stavail = 0; + *off += len; + retval = len; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_read(minor - ISDN_MINOR_PPP, file, buf, count); + goto out; + } +#endif + retval = -ENODEV; +out: + mutex_unlock(&isdn_mutex); + return retval; +} + +static ssize_t +isdn_write(struct file *file, const char __user *buf, size_t count, loff_t *off) +{ + uint minor = iminor(file_inode(file)); + int drvidx; + int chidx; + int retval; + + if (minor == ISDN_MINOR_STATUS) + return -EPERM; + if (!dev->drivers) + return -ENODEV; + + mutex_lock(&isdn_mutex); + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_write minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) { + retval = -ENODEV; + goto out; + } + chidx = isdn_minor2chan(minor); + wait_event_interruptible(dev->drv[drvidx]->snd_waitq[chidx], + (retval = isdn_writebuf_stub(drvidx, chidx, buf, count))); + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) { + retval = -ENODEV; + goto out; + } + /* + * We want to use the isdnctrl device to load the firmware + * + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + return -ENODEV; + */ + if (dev->drv[drvidx]->interface->writecmd) + retval = dev->drv[drvidx]->interface-> + writecmd(buf, count, drvidx, + isdn_minor2chan(minor - ISDN_MINOR_CTRL)); + else + retval = count; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_write(minor - ISDN_MINOR_PPP, file, buf, count); + goto out; + } +#endif + retval = -ENODEV; +out: + mutex_unlock(&isdn_mutex); + return retval; +} + +static unsigned int +isdn_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + unsigned int minor = iminor(file_inode(file)); + int drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + + mutex_lock(&isdn_mutex); + if (minor == ISDN_MINOR_STATUS) { + poll_wait(file, &(dev->info_waitq), wait); + /* mask = POLLOUT | POLLWRNORM; */ + if (file->private_data) { + mask |= POLLIN | POLLRDNORM; + } + goto out; + } + if (minor >= ISDN_MINOR_CTRL && minor <= ISDN_MINOR_CTRLMAX) { + if (drvidx < 0) { + /* driver deregistered while file open */ + mask = POLLHUP; + goto out; + } + poll_wait(file, &(dev->drv[drvidx]->st_waitq), wait); + mask = POLLOUT | POLLWRNORM; + if (dev->drv[drvidx]->stavail) { + mask |= POLLIN | POLLRDNORM; + } + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + mask = isdn_ppp_poll(file, wait); + goto out; + } +#endif + mask = POLLERR; +out: + mutex_unlock(&isdn_mutex); + return mask; +} + + +static int +isdn_ioctl(struct file *file, uint cmd, ulong arg) +{ + uint minor = iminor(file_inode(file)); + isdn_ctrl c; + int drvidx; + int ret; + int i; + char __user *p; + char *s; + union iocpar { + char name[10]; + char bname[22]; + isdn_ioctl_struct iocts; + isdn_net_ioctl_phone phone; + isdn_net_ioctl_cfg cfg; + } iocpar; + void __user *argp = (void __user *)arg; + +#define name iocpar.name +#define bname iocpar.bname +#define iocts iocpar.iocts +#define phone iocpar.phone +#define cfg iocpar.cfg + + if (minor == ISDN_MINOR_STATUS) { + switch (cmd) { + case IIOCGETDVR: + return (TTY_DV + + (NET_DV << 8) + + (INF_DV << 16)); + case IIOCGETCPS: + if (arg) { + ulong __user *p = argp; + int i; + if (!access_ok(VERIFY_WRITE, p, + sizeof(ulong) * ISDN_MAX_CHANNELS * 2)) + return -EFAULT; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + put_user(dev->ibytes[i], p++); + put_user(dev->obytes[i], p++); + } + return 0; + } else + return -EINVAL; + break; + case IIOCNETGPN: + /* Get peer phone number of a connected + * isdn network interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + return isdn_net_getpeer(&phone, argp); + } else + return -EINVAL; + default: + return -EINVAL; + } + } + if (!dev->drivers) + return -ENODEV; + if (minor <= ISDN_MINOR_BMAX) { + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + return -ENODEV; + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + return -ENODEV; + return 0; + } + if (minor <= ISDN_MINOR_CTRLMAX) { +/* + * isdn net devices manage lots of configuration variables as linked lists. + * Those lists must only be manipulated from user space. Some of the ioctl's + * service routines access user space and are not atomic. Therefore, ioctl's + * manipulating the lists and ioctl's sleeping while accessing the lists + * are serialized by means of a semaphore. + */ + switch (cmd) { + case IIOCNETDWRSET: + printk(KERN_INFO "INFO: ISDN_DW_ABC_EXTENSION not enabled\n"); + return (-EINVAL); + case IIOCNETLCR: + printk(KERN_INFO "INFO: ISDN_ABC_LCR_SUPPORT not enabled\n"); + return -ENODEV; + case IIOCNETAIF: + /* Add a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + s = name; + } else { + s = NULL; + } + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + if ((s = isdn_net_new(s, NULL))) { + if (copy_to_user(argp, s, strlen(s) + 1)) { + ret = -EFAULT; + } else { + ret = 0; + } + } else + ret = -ENODEV; + mutex_unlock(&dev->mtx); + return ret; + case IIOCNETASL: + /* Add a slave to a network-interface */ + if (arg) { + if (copy_from_user(bname, argp, sizeof(bname) - 1)) + return -EFAULT; + } else + return -EINVAL; + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + if ((s = isdn_net_newslave(bname))) { + if (copy_to_user(argp, s, strlen(s) + 1)) { + ret = -EFAULT; + } else { + ret = 0; + } + } else + ret = -ENODEV; + mutex_unlock(&dev->mtx); + return ret; + case IIOCNETDIF: + /* Delete a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + ret = isdn_net_rm(name); + mutex_unlock(&dev->mtx); + return ret; + } else + return -EINVAL; + case IIOCNETSCF: + /* Set configurable parameters of a network-interface */ + if (arg) { + if (copy_from_user(&cfg, argp, sizeof(cfg))) + return -EFAULT; + return isdn_net_setcfg(&cfg); + } else + return -EINVAL; + case IIOCNETGCF: + /* Get configurable parameters of a network-interface */ + if (arg) { + if (copy_from_user(&cfg, argp, sizeof(cfg))) + return -EFAULT; + if (!(ret = isdn_net_getcfg(&cfg))) { + if (copy_to_user(argp, &cfg, sizeof(cfg))) + return -EFAULT; + } + return ret; + } else + return -EINVAL; + case IIOCNETANM: + /* Add a phone-number to a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + ret = isdn_net_addphone(&phone); + mutex_unlock(&dev->mtx); + return ret; + } else + return -EINVAL; + case IIOCNETGNM: + /* Get list of phone-numbers of a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + ret = isdn_net_getphones(&phone, argp); + mutex_unlock(&dev->mtx); + return ret; + } else + return -EINVAL; + case IIOCNETDNM: + /* Delete a phone-number of a network-interface */ + if (arg) { + if (copy_from_user(&phone, argp, sizeof(phone))) + return -EFAULT; + ret = mutex_lock_interruptible(&dev->mtx); + if (ret) return ret; + ret = isdn_net_delphone(&phone); + mutex_unlock(&dev->mtx); + return ret; + } else + return -EINVAL; + case IIOCNETDIL: + /* Force dialing of a network-interface */ + if (arg) { + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_net_force_dial(name); + } else + return -EINVAL; +#ifdef CONFIG_ISDN_PPP + case IIOCNETALN: + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_ppp_dial_slave(name); + case IIOCNETDLN: + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_ppp_hangup_slave(name); +#endif + case IIOCNETHUP: + /* Force hangup of a network-interface */ + if (!arg) + return -EINVAL; + if (copy_from_user(name, argp, sizeof(name))) + return -EFAULT; + return isdn_net_force_hangup(name); + break; + case IIOCSETVER: + dev->net_verbose = arg; + printk(KERN_INFO "isdn: Verbose-Level is %d\n", dev->net_verbose); + return 0; + case IIOCSETGST: + if (arg) + dev->global_flags |= ISDN_GLOBAL_STOPPED; + else + dev->global_flags &= ~ISDN_GLOBAL_STOPPED; + printk(KERN_INFO "isdn: Global Mode %s\n", + (dev->global_flags & ISDN_GLOBAL_STOPPED) ? "stopped" : "running"); + return 0; + case IIOCSETBRJ: + drvidx = -1; + if (arg) { + int i; + char *p; + if (copy_from_user(&iocts, argp, + sizeof(isdn_ioctl_struct))) + return -EFAULT; + iocts.drvid[sizeof(iocts.drvid) - 1] = 0; + if (strlen(iocts.drvid)) { + if ((p = strchr(iocts.drvid, ','))) + *p = 0; + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } + } + if (drvidx == -1) + return -ENODEV; + if (iocts.arg) + dev->drv[drvidx]->flags |= DRV_FLAG_REJBUS; + else + dev->drv[drvidx]->flags &= ~DRV_FLAG_REJBUS; + return 0; + case IIOCSIGPRF: + dev->profd = current; + return 0; + break; + case IIOCGETPRF: + /* Get all Modem-Profiles */ + if (arg) { + char __user *p = argp; + int i; + + if (!access_ok(VERIFY_WRITE, argp, + (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) + * ISDN_MAX_CHANNELS)) + return -EFAULT; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (copy_to_user(p, dev->mdm.info[i].emu.profile, + ISDN_MODEM_NUMREG)) + return -EFAULT; + p += ISDN_MODEM_NUMREG; + if (copy_to_user(p, dev->mdm.info[i].emu.pmsn, ISDN_MSNLEN)) + return -EFAULT; + p += ISDN_MSNLEN; + if (copy_to_user(p, dev->mdm.info[i].emu.plmsn, ISDN_LMSNLEN)) + return -EFAULT; + p += ISDN_LMSNLEN; + } + return (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) * ISDN_MAX_CHANNELS; + } else + return -EINVAL; + break; + case IIOCSETPRF: + /* Set all Modem-Profiles */ + if (arg) { + char __user *p = argp; + int i; + + if (!access_ok(VERIFY_READ, argp, + (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) + * ISDN_MAX_CHANNELS)) + return -EFAULT; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (copy_from_user(dev->mdm.info[i].emu.profile, p, + ISDN_MODEM_NUMREG)) + return -EFAULT; + p += ISDN_MODEM_NUMREG; + if (copy_from_user(dev->mdm.info[i].emu.plmsn, p, ISDN_LMSNLEN)) + return -EFAULT; + p += ISDN_LMSNLEN; + if (copy_from_user(dev->mdm.info[i].emu.pmsn, p, ISDN_MSNLEN)) + return -EFAULT; + p += ISDN_MSNLEN; + } + return 0; + } else + return -EINVAL; + break; + case IIOCSETMAP: + case IIOCGETMAP: + /* Set/Get MSN->EAZ-Mapping for a driver */ + if (arg) { + + if (copy_from_user(&iocts, argp, + sizeof(isdn_ioctl_struct))) + return -EFAULT; + iocts.drvid[sizeof(iocts.drvid) - 1] = 0; + if (strlen(iocts.drvid)) { + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } else + drvidx = 0; + if (drvidx == -1) + return -ENODEV; + if (cmd == IIOCSETMAP) { + int loop = 1; + + p = (char __user *) iocts.arg; + i = 0; + while (loop) { + int j = 0; + + while (1) { + if (!access_ok(VERIFY_READ, p, 1)) + return -EFAULT; + get_user(bname[j], p++); + switch (bname[j]) { + case '\0': + loop = 0; + /* Fall through */ + case ',': + bname[j] = '\0'; + strcpy(dev->drv[drvidx]->msn2eaz[i], bname); + j = ISDN_MSNLEN; + break; + default: + j++; + } + if (j >= ISDN_MSNLEN) + break; + } + if (++i > 9) + break; + } + } else { + p = (char __user *) iocts.arg; + for (i = 0; i < 10; i++) { + snprintf(bname, sizeof(bname), "%s%s", + strlen(dev->drv[drvidx]->msn2eaz[i]) ? + dev->drv[drvidx]->msn2eaz[i] : "_", + (i < 9) ? "," : "\0"); + if (copy_to_user(p, bname, strlen(bname) + 1)) + return -EFAULT; + p += strlen(bname); + } + } + return 0; + } else + return -EINVAL; + case IIOCDBGVAR: + if (arg) { + if (copy_to_user(argp, &dev, sizeof(ulong))) + return -EFAULT; + return 0; + } else + return -EINVAL; + break; + default: + if ((cmd & IIOCDRVCTL) == IIOCDRVCTL) + cmd = ((cmd >> _IOC_NRSHIFT) & _IOC_NRMASK) & ISDN_DRVIOCTL_MASK; + else + return -EINVAL; + if (arg) { + int i; + char *p; + if (copy_from_user(&iocts, argp, sizeof(isdn_ioctl_struct))) + return -EFAULT; + iocts.drvid[sizeof(iocts.drvid) - 1] = 0; + if (strlen(iocts.drvid)) { + if ((p = strchr(iocts.drvid, ','))) + *p = 0; + drvidx = -1; + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (!(strcmp(dev->drvid[i], iocts.drvid))) { + drvidx = i; + break; + } + } else + drvidx = 0; + if (drvidx == -1) + return -ENODEV; + if (!access_ok(VERIFY_WRITE, argp, + sizeof(isdn_ioctl_struct))) + return -EFAULT; + c.driver = drvidx; + c.command = ISDN_CMD_IOCTL; + c.arg = cmd; + memcpy(c.parm.num, &iocts.arg, sizeof(ulong)); + ret = isdn_command(&c); + memcpy(&iocts.arg, c.parm.num, sizeof(ulong)); + if (copy_to_user(argp, &iocts, sizeof(isdn_ioctl_struct))) + return -EFAULT; + return ret; + } else + return -EINVAL; + } + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + return (isdn_ppp_ioctl(minor - ISDN_MINOR_PPP, file, cmd, arg)); +#endif + return -ENODEV; + +#undef name +#undef bname +#undef iocts +#undef phone +#undef cfg +} + +static long +isdn_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret; + + mutex_lock(&isdn_mutex); + ret = isdn_ioctl(file, cmd, arg); + mutex_unlock(&isdn_mutex); + + return ret; +} + +/* + * Open the device code. + */ +static int +isdn_open(struct inode *ino, struct file *filep) +{ + uint minor = iminor(ino); + int drvidx; + int chidx; + int retval = -ENODEV; + + mutex_lock(&isdn_mutex); + if (minor == ISDN_MINOR_STATUS) { + infostruct *p; + + if ((p = kmalloc(sizeof(infostruct), GFP_KERNEL))) { + p->next = (char *) dev->infochain; + p->private = (char *) &(filep->private_data); + dev->infochain = p; + /* At opening we allow a single update */ + filep->private_data = (char *) 1; + retval = 0; + goto out; + } else { + retval = -ENOMEM; + goto out; + } + } + if (!dev->channels) + goto out; + if (minor <= ISDN_MINOR_BMAX) { + printk(KERN_WARNING "isdn_open minor %d obsolete!\n", minor); + drvidx = isdn_minor2drv(minor); + if (drvidx < 0) + goto out; + chidx = isdn_minor2chan(minor); + if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) + goto out; + if (!(dev->drv[drvidx]->online & (1 << chidx))) + goto out; + isdn_lock_drivers(); + retval = 0; + goto out; + } + if (minor <= ISDN_MINOR_CTRLMAX) { + drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL); + if (drvidx < 0) + goto out; + isdn_lock_drivers(); + retval = 0; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) { + retval = isdn_ppp_open(minor - ISDN_MINOR_PPP, filep); + if (retval == 0) + isdn_lock_drivers(); + goto out; + } +#endif +out: + nonseekable_open(ino, filep); + mutex_unlock(&isdn_mutex); + return retval; +} + +static int +isdn_close(struct inode *ino, struct file *filep) +{ + uint minor = iminor(ino); + + mutex_lock(&isdn_mutex); + if (minor == ISDN_MINOR_STATUS) { + infostruct *p = dev->infochain; + infostruct *q = NULL; + + while (p) { + if (p->private == (char *) &(filep->private_data)) { + if (q) + q->next = p->next; + else + dev->infochain = (infostruct *) (p->next); + kfree(p); + goto out; + } + q = p; + p = (infostruct *) (p->next); + } + printk(KERN_WARNING "isdn: No private data while closing isdnctrl\n"); + goto out; + } + isdn_unlock_drivers(); + if (minor <= ISDN_MINOR_BMAX) + goto out; + if (minor <= ISDN_MINOR_CTRLMAX) { + if (dev->profd == current) + dev->profd = NULL; + goto out; + } +#ifdef CONFIG_ISDN_PPP + if (minor <= ISDN_MINOR_PPPMAX) + isdn_ppp_release(minor - ISDN_MINOR_PPP, filep); +#endif + +out: + mutex_unlock(&isdn_mutex); + return 0; +} + +static const struct file_operations isdn_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = isdn_read, + .write = isdn_write, + .poll = isdn_poll, + .unlocked_ioctl = isdn_unlocked_ioctl, + .open = isdn_open, + .release = isdn_close, +}; + +char * +isdn_map_eaz2msn(char *msn, int di) +{ + isdn_driver_t *this = dev->drv[di]; + int i; + + if (strlen(msn) == 1) { + i = msn[0] - '0'; + if ((i >= 0) && (i <= 9)) + if (strlen(this->msn2eaz[i])) + return (this->msn2eaz[i]); + } + return (msn); +} + +/* + * Find an unused ISDN-channel, whose feature-flags match the + * given L2- and L3-protocols. + */ +#define L2V (~(ISDN_FEATURE_L2_V11096 | ISDN_FEATURE_L2_V11019 | ISDN_FEATURE_L2_V11038)) + +/* + * This function must be called with holding the dev->lock. + */ +int +isdn_get_free_channel(int usage, int l2_proto, int l3_proto, int pre_dev + , int pre_chan, char *msn) +{ + int i; + ulong features; + ulong vfeatures; + + features = ((1 << l2_proto) | (0x10000 << l3_proto)); + vfeatures = (((1 << l2_proto) | (0x10000 << l3_proto)) & + ~(ISDN_FEATURE_L2_V11096 | ISDN_FEATURE_L2_V11019 | ISDN_FEATURE_L2_V11038)); + /* If Layer-2 protocol is V.110, accept drivers with + * transparent feature even if these don't support V.110 + * because we can emulate this in linklevel. + */ + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (USG_NONE(dev->usage[i]) && + (dev->drvmap[i] != -1)) { + int d = dev->drvmap[i]; + if ((dev->usage[i] & ISDN_USAGE_EXCLUSIVE) && + ((pre_dev != d) || (pre_chan != dev->chanmap[i]))) + continue; + if (!strcmp(isdn_map_eaz2msn(msn, d), "-")) + continue; + if (dev->usage[i] & ISDN_USAGE_DISABLED) + continue; /* usage not allowed */ + if (dev->drv[d]->flags & DRV_FLAG_RUNNING) { + if (((dev->drv[d]->interface->features & features) == features) || + (((dev->drv[d]->interface->features & vfeatures) == vfeatures) && + (dev->drv[d]->interface->features & ISDN_FEATURE_L2_TRANS))) { + if ((pre_dev < 0) || (pre_chan < 0)) { + dev->usage[i] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[i] |= usage; + isdn_info_update(); + return i; + } else { + if ((pre_dev == d) && (pre_chan == dev->chanmap[i])) { + dev->usage[i] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[i] |= usage; + isdn_info_update(); + return i; + } + } + } + } + } + return -1; +} + +/* + * Set state of ISDN-channel to 'unused' + */ +void +isdn_free_channel(int di, int ch, int usage) +{ + int i; + + if ((di < 0) || (ch < 0)) { + printk(KERN_WARNING "%s: called with invalid drv(%d) or channel(%d)\n", + __func__, di, ch); + return; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (((!usage) || ((dev->usage[i] & ISDN_USAGE_MASK) == usage)) && + (dev->drvmap[i] == di) && + (dev->chanmap[i] == ch)) { + dev->usage[i] &= (ISDN_USAGE_NONE | ISDN_USAGE_EXCLUSIVE); + strcpy(dev->num[i], "???"); + dev->ibytes[i] = 0; + dev->obytes[i] = 0; +// 20.10.99 JIM, try to reinitialize v110 ! + dev->v110emu[i] = 0; + atomic_set(&(dev->v110use[i]), 0); + isdn_v110_close(dev->v110[i]); + dev->v110[i] = NULL; +// 20.10.99 JIM, try to reinitialize v110 ! + isdn_info_update(); + if (dev->drv[di]) + skb_queue_purge(&dev->drv[di]->rpqueue[ch]); + } +} + +/* + * Cancel Exclusive-Flag for ISDN-channel + */ +void +isdn_unexclusive_channel(int di, int ch) +{ + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if ((dev->drvmap[i] == di) && + (dev->chanmap[i] == ch)) { + dev->usage[i] &= ~ISDN_USAGE_EXCLUSIVE; + isdn_info_update(); + return; + } +} + +/* + * writebuf replacement for SKB_ABLE drivers + */ +static int +isdn_writebuf_stub(int drvidx, int chan, const u_char __user *buf, int len) +{ + int ret; + int hl = dev->drv[drvidx]->interface->hl_hdrlen; + struct sk_buff *skb = alloc_skb(hl + len, GFP_ATOMIC); + + if (!skb) + return -ENOMEM; + skb_reserve(skb, hl); + if (copy_from_user(skb_put(skb, len), buf, len)) { + dev_kfree_skb(skb); + return -EFAULT; + } + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, 1, skb); + if (ret <= 0) + dev_kfree_skb(skb); + if (ret > 0) + dev->obytes[isdn_dc2minor(drvidx, chan)] += ret; + return ret; +} + +/* + * Return: length of data on success, -ERRcode on failure. + */ +int +isdn_writebuf_skb_stub(int drvidx, int chan, int ack, struct sk_buff *skb) +{ + int ret; + struct sk_buff *nskb = NULL; + int v110_ret = skb->len; + int idx = isdn_dc2minor(drvidx, chan); + + if (dev->v110[idx]) { + atomic_inc(&dev->v110use[idx]); + nskb = isdn_v110_encode(dev->v110[idx], skb); + atomic_dec(&dev->v110use[idx]); + if (!nskb) + return 0; + v110_ret = *((int *)nskb->data); + skb_pull(nskb, sizeof(int)); + if (!nskb->len) { + dev_kfree_skb(nskb); + return v110_ret; + } + /* V.110 must always be acknowledged */ + ack = 1; + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, nskb); + } else { + int hl = dev->drv[drvidx]->interface->hl_hdrlen; + + if (skb_headroom(skb) < hl) { + /* + * This should only occur when new HL driver with + * increased hl_hdrlen was loaded after netdevice + * was created and connected to the new driver. + * + * The V.110 branch (re-allocates on its own) does + * not need this + */ + struct sk_buff *skb_tmp; + + skb_tmp = skb_realloc_headroom(skb, hl); + printk(KERN_DEBUG "isdn_writebuf_skb_stub: reallocating headroom%s\n", skb_tmp ? "" : " failed"); + if (!skb_tmp) return -ENOMEM; /* 0 better? */ + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb_tmp); + if (ret > 0) { + dev_kfree_skb(skb); + } else { + dev_kfree_skb(skb_tmp); + } + } else { + ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb); + } + } + if (ret > 0) { + dev->obytes[idx] += ret; + if (dev->v110[idx]) { + atomic_inc(&dev->v110use[idx]); + dev->v110[idx]->skbuser++; + atomic_dec(&dev->v110use[idx]); + /* For V.110 return unencoded data length */ + ret = v110_ret; + /* if the complete frame was send we free the skb; + if not upper function will requeue the skb */ + if (ret == skb->len) + dev_kfree_skb(skb); + } + } else + if (dev->v110[idx]) + dev_kfree_skb(nskb); + return ret; +} + +static int +isdn_add_channels(isdn_driver_t *d, int drvidx, int n, int adding) +{ + int j, k, m; + + init_waitqueue_head(&d->st_waitq); + if (d->flags & DRV_FLAG_RUNNING) + return -1; + if (n < 1) return 0; + + m = (adding) ? d->channels + n : n; + + if (dev->channels + n > ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "register_isdn: Max. %d channels supported\n", + ISDN_MAX_CHANNELS); + return -1; + } + + if ((adding) && (d->rcverr)) + kfree(d->rcverr); + if (!(d->rcverr = kzalloc(sizeof(int) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcverr\n"); + return -1; + } + + if ((adding) && (d->rcvcount)) + kfree(d->rcvcount); + if (!(d->rcvcount = kzalloc(sizeof(int) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rcvcount\n"); + if (!adding) + kfree(d->rcverr); + return -1; + } + + if ((adding) && (d->rpqueue)) { + for (j = 0; j < d->channels; j++) + skb_queue_purge(&d->rpqueue[j]); + kfree(d->rpqueue); + } + if (!(d->rpqueue = kmalloc(sizeof(struct sk_buff_head) * m, GFP_ATOMIC))) { + printk(KERN_WARNING "register_isdn: Could not alloc rpqueue\n"); + if (!adding) { + kfree(d->rcvcount); + kfree(d->rcverr); + } + return -1; + } + for (j = 0; j < m; j++) { + skb_queue_head_init(&d->rpqueue[j]); + } + + if ((adding) && (d->rcv_waitq)) + kfree(d->rcv_waitq); + d->rcv_waitq = kmalloc(sizeof(wait_queue_head_t) * 2 * m, GFP_ATOMIC); + if (!d->rcv_waitq) { + printk(KERN_WARNING "register_isdn: Could not alloc rcv_waitq\n"); + if (!adding) { + kfree(d->rpqueue); + kfree(d->rcvcount); + kfree(d->rcverr); + } + return -1; + } + d->snd_waitq = d->rcv_waitq + m; + for (j = 0; j < m; j++) { + init_waitqueue_head(&d->rcv_waitq[j]); + init_waitqueue_head(&d->snd_waitq[j]); + } + + dev->channels += n; + for (j = d->channels; j < m; j++) + for (k = 0; k < ISDN_MAX_CHANNELS; k++) + if (dev->chanmap[k] < 0) { + dev->chanmap[k] = j; + dev->drvmap[k] = drvidx; + break; + } + d->channels = m; + return 0; +} + +/* + * Low-level-driver registration + */ + +static void +set_global_features(void) +{ + int drvidx; + + dev->global_features = 0; + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) { + if (!dev->drv[drvidx]) + continue; + if (dev->drv[drvidx]->interface) + dev->global_features |= dev->drv[drvidx]->interface->features; + } +} + +#ifdef CONFIG_ISDN_DIVERSION + +static char *map_drvname(int di) +{ + if ((di < 0) || (di >= ISDN_MAX_DRIVERS)) + return (NULL); + return (dev->drvid[di]); /* driver name */ +} /* map_drvname */ + +static int map_namedrv(char *id) +{ int i; + + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + { if (!strcmp(dev->drvid[i], id)) + return (i); + } + return (-1); +} /* map_namedrv */ + +int DIVERT_REG_NAME(isdn_divert_if *i_div) +{ + if (i_div->if_magic != DIVERT_IF_MAGIC) + return (DIVERT_VER_ERR); + switch (i_div->cmd) + { + case DIVERT_CMD_REL: + if (divert_if != i_div) + return (DIVERT_REL_ERR); + divert_if = NULL; /* free interface */ + return (DIVERT_NO_ERR); + + case DIVERT_CMD_REG: + if (divert_if) + return (DIVERT_REG_ERR); + i_div->ll_cmd = isdn_command; /* set command function */ + i_div->drv_to_name = map_drvname; + i_div->name_to_drv = map_namedrv; + divert_if = i_div; /* remember interface */ + return (DIVERT_NO_ERR); + + default: + return (DIVERT_CMD_ERR); + } +} /* DIVERT_REG_NAME */ + +EXPORT_SYMBOL(DIVERT_REG_NAME); + +#endif /* CONFIG_ISDN_DIVERSION */ + + +EXPORT_SYMBOL(register_isdn); +#ifdef CONFIG_ISDN_PPP +EXPORT_SYMBOL(isdn_ppp_register_compressor); +EXPORT_SYMBOL(isdn_ppp_unregister_compressor); +#endif + +int +register_isdn(isdn_if *i) +{ + isdn_driver_t *d; + int j; + ulong flags; + int drvidx; + + if (dev->drivers >= ISDN_MAX_DRIVERS) { + printk(KERN_WARNING "register_isdn: Max. %d drivers supported\n", + ISDN_MAX_DRIVERS); + return 0; + } + if (!i->writebuf_skb) { + printk(KERN_WARNING "register_isdn: No write routine given.\n"); + return 0; + } + if (!(d = kzalloc(sizeof(isdn_driver_t), GFP_KERNEL))) { + printk(KERN_WARNING "register_isdn: Could not alloc driver-struct\n"); + return 0; + } + + d->maxbufsize = i->maxbufsize; + d->pktcount = 0; + d->stavail = 0; + d->flags = DRV_FLAG_LOADED; + d->online = 0; + d->interface = i; + d->channels = 0; + spin_lock_irqsave(&dev->lock, flags); + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) + if (!dev->drv[drvidx]) + break; + if (isdn_add_channels(d, drvidx, i->channels, 0)) { + spin_unlock_irqrestore(&dev->lock, flags); + kfree(d); + return 0; + } + i->channels = drvidx; + i->rcvcallb_skb = isdn_receive_skb_callback; + i->statcallb = isdn_status_callback; + if (!strlen(i->id)) + sprintf(i->id, "line%d", drvidx); + for (j = 0; j < drvidx; j++) + if (!strcmp(i->id, dev->drvid[j])) + sprintf(i->id, "line%d", drvidx); + dev->drv[drvidx] = d; + strcpy(dev->drvid[drvidx], i->id); + isdn_info_update(); + dev->drivers++; + set_global_features(); + spin_unlock_irqrestore(&dev->lock, flags); + return 1; +} + +/* +***************************************************************************** +* And now the modules code. +***************************************************************************** +*/ + +static char * +isdn_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + +/* + * Allocate and initialize all data, register modem-devices + */ +static int __init isdn_init(void) +{ + int i; + char tmprev[50]; + + dev = vzalloc(sizeof(isdn_dev)); + if (!dev) { + printk(KERN_WARNING "isdn: Could not allocate device-struct.\n"); + return -EIO; + } + init_timer(&dev->timer); + dev->timer.function = isdn_timer_funct; + spin_lock_init(&dev->lock); + spin_lock_init(&dev->timerlock); +#ifdef MODULE + dev->owner = THIS_MODULE; +#endif + mutex_init(&dev->mtx); + init_waitqueue_head(&dev->info_waitq); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + dev->drvmap[i] = -1; + dev->chanmap[i] = -1; + dev->m_idx[i] = -1; + strcpy(dev->num[i], "???"); + } + if (register_chrdev(ISDN_MAJOR, "isdn", &isdn_fops)) { + printk(KERN_WARNING "isdn: Could not register control devices\n"); + vfree(dev); + return -EIO; + } + if ((isdn_tty_modem_init()) < 0) { + printk(KERN_WARNING "isdn: Could not register tty devices\n"); + vfree(dev); + unregister_chrdev(ISDN_MAJOR, "isdn"); + return -EIO; + } +#ifdef CONFIG_ISDN_PPP + if (isdn_ppp_init() < 0) { + printk(KERN_WARNING "isdn: Could not create PPP-device-structs\n"); + isdn_tty_exit(); + unregister_chrdev(ISDN_MAJOR, "isdn"); + vfree(dev); + return -EIO; + } +#endif /* CONFIG_ISDN_PPP */ + + strcpy(tmprev, isdn_revision); + printk(KERN_NOTICE "ISDN subsystem Rev: %s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_net_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_ppp_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_audio_revision); + printk("%s/", isdn_getrev(tmprev)); + strcpy(tmprev, isdn_v110_revision); + printk("%s", isdn_getrev(tmprev)); + +#ifdef MODULE + printk(" loaded\n"); +#else + printk("\n"); +#endif + isdn_info_update(); + return 0; +} + +/* + * Unload module + */ +static void __exit isdn_exit(void) +{ +#ifdef CONFIG_ISDN_PPP + isdn_ppp_cleanup(); +#endif + if (isdn_net_rmall() < 0) { + printk(KERN_WARNING "isdn: net-device busy, remove cancelled\n"); + return; + } + isdn_tty_exit(); + unregister_chrdev(ISDN_MAJOR, "isdn"); + del_timer_sync(&dev->timer); + /* call vfree with interrupts enabled, else it will hang */ + vfree(dev); + printk(KERN_NOTICE "ISDN-subsystem unloaded\n"); +} + +module_init(isdn_init); +module_exit(isdn_exit); diff --git a/kernel/drivers/isdn/i4l/isdn_common.h b/kernel/drivers/isdn/i4l/isdn_common.h new file mode 100644 index 000000000..2260ef07a --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_common.h @@ -0,0 +1,47 @@ +/* $Id: isdn_common.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem + * common used functions and debugging-switches (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#undef ISDN_DEBUG_MODEM_OPEN +#undef ISDN_DEBUG_MODEM_IOCTL +#undef ISDN_DEBUG_MODEM_WAITSENT +#undef ISDN_DEBUG_MODEM_HUP +#undef ISDN_DEBUG_MODEM_ICALL +#undef ISDN_DEBUG_MODEM_DUMP +#undef ISDN_DEBUG_MODEM_VOICE +#undef ISDN_DEBUG_AT +#undef ISDN_DEBUG_NET_DUMP +#undef ISDN_DEBUG_NET_DIAL +#undef ISDN_DEBUG_NET_ICALL + +/* Prototypes */ +extern void isdn_lock_drivers(void); +extern void isdn_unlock_drivers(void); +extern void isdn_free_channel(int di, int ch, int usage); +extern void isdn_all_eaz(int di, int ch); +extern int isdn_command(isdn_ctrl *); +extern int isdn_dc2minor(int di, int ch); +extern void isdn_info_update(void); +extern char *isdn_map_eaz2msn(char *msn, int di); +extern void isdn_timer_ctrl(int tf, int onoff); +extern void isdn_unexclusive_channel(int di, int ch); +extern int isdn_getnum(char **); +extern int isdn_readbchan(int, int, u_char *, u_char *, int, wait_queue_head_t *); +extern int isdn_readbchan_tty(int, int, struct tty_port *, int); +extern int isdn_get_free_channel(int, int, int, int, int, char *); +extern int isdn_writebuf_skb_stub(int, int, int, struct sk_buff *); +extern int register_isdn(isdn_if *i); +extern int isdn_msncmp(const char *, const char *); +#if defined(ISDN_DEBUG_NET_DUMP) || defined(ISDN_DEBUG_MODEM_DUMP) +extern void isdn_dumppkt(char *, u_char *, int, int); +#endif diff --git a/kernel/drivers/isdn/i4l/isdn_concap.c b/kernel/drivers/isdn/i4l/isdn_concap.c new file mode 100644 index 000000000..91d57304d --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_concap.c @@ -0,0 +1,99 @@ +/* $Id: isdn_concap.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, protocol encapsulation + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* Stuff to support the concap_proto by isdn4linux. isdn4linux - specific + * stuff goes here. Stuff that depends only on the concap protocol goes to + * another -- protocol specific -- source file. + * + */ + + +#include <linux/isdn.h> +#include "isdn_x25iface.h" +#include "isdn_net.h" +#include <linux/concap.h> +#include "isdn_concap.h" + + +/* The following set of device service operations are for encapsulation + protocols that require for reliable datalink semantics. That means: + + - before any data is to be submitted the connection must explicitly + be set up. + - after the successful set up of the connection is signalled the + connection is considered to be reliably up. + + Auto-dialing ist not compatible with this requirements. Thus, auto-dialing + is completely bypassed. + + It might be possible to implement a (non standardized) datalink protocol + that provides a reliable data link service while using some auto dialing + mechanism. Such a protocol would need an auxiliary channel (i.e. user-user- + signaling on the D-channel) while the B-channel is down. +*/ + + +static int isdn_concap_dl_data_req(struct concap_proto *concap, struct sk_buff *skb) +{ + struct net_device *ndev = concap->net_dev; + isdn_net_dev *nd = ((isdn_net_local *) netdev_priv(ndev))->netdev; + isdn_net_local *lp = isdn_net_get_locked_lp(nd); + + IX25DEBUG("isdn_concap_dl_data_req: %s \n", concap->net_dev->name); + if (!lp) { + IX25DEBUG("isdn_concap_dl_data_req: %s : isdn_net_send_skb returned %d\n", concap->net_dev->name, 1); + return 1; + } + lp->huptimer = 0; + isdn_net_writebuf_skb(lp, skb); + spin_unlock_bh(&lp->xmit_lock); + IX25DEBUG("isdn_concap_dl_data_req: %s : isdn_net_send_skb returned %d\n", concap->net_dev->name, 0); + return 0; +} + + +static int isdn_concap_dl_connect_req(struct concap_proto *concap) +{ + struct net_device *ndev = concap->net_dev; + isdn_net_local *lp = netdev_priv(ndev); + int ret; + IX25DEBUG("isdn_concap_dl_connect_req: %s \n", ndev->name); + + /* dial ... */ + ret = isdn_net_dial_req(lp); + if (ret) IX25DEBUG("dialing failed\n"); + return ret; +} + +static int isdn_concap_dl_disconn_req(struct concap_proto *concap) +{ + IX25DEBUG("isdn_concap_dl_disconn_req: %s \n", concap->net_dev->name); + + isdn_net_hangup(concap->net_dev); + return 0; +} + +struct concap_device_ops isdn_concap_reliable_dl_dops = { + &isdn_concap_dl_data_req, + &isdn_concap_dl_connect_req, + &isdn_concap_dl_disconn_req +}; + +/* The following should better go into a dedicated source file such that + this sourcefile does not need to include any protocol specific header + files. For now: +*/ +struct concap_proto *isdn_concap_new(int encap) +{ + switch (encap) { + case ISDN_NET_ENCAP_X25IFACE: + return isdn_x25iface_proto_new(); + } + return NULL; +} diff --git a/kernel/drivers/isdn/i4l/isdn_concap.h b/kernel/drivers/isdn/i4l/isdn_concap.h new file mode 100644 index 000000000..cd7e3ba74 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_concap.h @@ -0,0 +1,11 @@ +/* $Id: isdn_concap.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, protocol encapsulation + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +extern struct concap_device_ops isdn_concap_reliable_dl_dops; +extern struct concap_proto *isdn_concap_new(int); diff --git a/kernel/drivers/isdn/i4l/isdn_net.c b/kernel/drivers/isdn/i4l/isdn_net.c new file mode 100644 index 000000000..546b7e811 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_net.c @@ -0,0 +1,3200 @@ +/* $Id: isdn_net.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, network interfaces and related functions (linklevel). + * + * Copyright 1994-1998 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Data Over Voice (DOV) support added - Guy Ellis 23-Mar-02 + * guy@traverse.com.au + * Outgoing calls - looks for a 'V' in first char of dialed number + * Incoming calls - checks first character of eaz as follows: + * Numeric - accept DATA only - original functionality + * 'V' - accept VOICE (DOV) only + * 'B' - accept BOTH DATA and DOV types + * + * Jan 2001: fix CISCO HDLC Bjoern A. Zeeb <i4l@zabbadoz.net> + * for info on the protocol, see + * http://i4l.zabbadoz.net/i4l/cisco-hdlc.txt + */ + +#include <linux/isdn.h> +#include <linux/slab.h> +#include <net/arp.h> +#include <net/dst.h> +#include <net/pkt_sched.h> +#include <linux/inetdevice.h> +#include "isdn_common.h" +#include "isdn_net.h" +#ifdef CONFIG_ISDN_PPP +#include "isdn_ppp.h" +#endif +#ifdef CONFIG_ISDN_X25 +#include <linux/concap.h> +#include "isdn_concap.h" +#endif + + +/* + * Outline of new tbusy handling: + * + * Old method, roughly spoken, consisted of setting tbusy when entering + * isdn_net_start_xmit() and at several other locations and clearing + * it from isdn_net_start_xmit() thread when sending was successful. + * + * With 2.3.x multithreaded network core, to prevent problems, tbusy should + * only be set by the isdn_net_start_xmit() thread and only when a tx-busy + * condition is detected. Other threads (in particular isdn_net_stat_callb()) + * are only allowed to clear tbusy. + * + * -HE + */ + +/* + * About SOFTNET: + * Most of the changes were pretty obvious and basically done by HE already. + * + * One problem of the isdn net device code is that is uses struct net_device + * for masters and slaves. However, only master interface are registered to + * the network layer, and therefore, it only makes sense to call netif_* + * functions on them. + * + * --KG + */ + +/* + * Find out if the netdevice has been ifup-ed yet. + * For slaves, look at the corresponding master. + */ +static __inline__ int isdn_net_device_started(isdn_net_dev *n) +{ + isdn_net_local *lp = n->local; + struct net_device *dev; + + if (lp->master) + dev = lp->master; + else + dev = n->dev; + return netif_running(dev); +} + +/* + * wake up the network -> net_device queue. + * For slaves, wake the corresponding master interface. + */ +static __inline__ void isdn_net_device_wake_queue(isdn_net_local *lp) +{ + if (lp->master) + netif_wake_queue(lp->master); + else + netif_wake_queue(lp->netdev->dev); +} + +/* + * stop the network -> net_device queue. + * For slaves, stop the corresponding master interface. + */ +static __inline__ void isdn_net_device_stop_queue(isdn_net_local *lp) +{ + if (lp->master) + netif_stop_queue(lp->master); + else + netif_stop_queue(lp->netdev->dev); +} + +/* + * find out if the net_device which this lp belongs to (lp can be + * master or slave) is busy. It's busy iff all (master and slave) + * queues are busy + */ +static __inline__ int isdn_net_device_busy(isdn_net_local *lp) +{ + isdn_net_local *nlp; + isdn_net_dev *nd; + unsigned long flags; + + if (!isdn_net_lp_busy(lp)) + return 0; + + if (lp->master) + nd = ISDN_MASTER_PRIV(lp)->netdev; + else + nd = lp->netdev; + + spin_lock_irqsave(&nd->queue_lock, flags); + nlp = lp->next; + while (nlp != lp) { + if (!isdn_net_lp_busy(nlp)) { + spin_unlock_irqrestore(&nd->queue_lock, flags); + return 0; + } + nlp = nlp->next; + } + spin_unlock_irqrestore(&nd->queue_lock, flags); + return 1; +} + +static __inline__ void isdn_net_inc_frame_cnt(isdn_net_local *lp) +{ + atomic_inc(&lp->frame_cnt); + if (isdn_net_device_busy(lp)) + isdn_net_device_stop_queue(lp); +} + +static __inline__ void isdn_net_dec_frame_cnt(isdn_net_local *lp) +{ + atomic_dec(&lp->frame_cnt); + + if (!(isdn_net_device_busy(lp))) { + if (!skb_queue_empty(&lp->super_tx_queue)) { + schedule_work(&lp->tqueue); + } else { + isdn_net_device_wake_queue(lp); + } + } +} + +static __inline__ void isdn_net_zero_frame_cnt(isdn_net_local *lp) +{ + atomic_set(&lp->frame_cnt, 0); +} + +/* For 2.2.x we leave the transmitter busy timeout at 2 secs, just + * to be safe. + * For 2.3.x we push it up to 20 secs, because call establishment + * (in particular callback) may take such a long time, and we + * don't want confusing messages in the log. However, there is a slight + * possibility that this large timeout will break other things like MPPP, + * which might rely on the tx timeout. If so, we'll find out this way... + */ + +#define ISDN_NET_TX_TIMEOUT (20 * HZ) + +/* Prototypes */ + +static int isdn_net_force_dial_lp(isdn_net_local *); +static netdev_tx_t isdn_net_start_xmit(struct sk_buff *, + struct net_device *); + +static void isdn_net_ciscohdlck_connected(isdn_net_local *lp); +static void isdn_net_ciscohdlck_disconnected(isdn_net_local *lp); + +char *isdn_net_revision = "$Revision: 1.1.2.2 $"; + +/* + * Code for raw-networking over ISDN + */ + +static void +isdn_net_unreachable(struct net_device *dev, struct sk_buff *skb, char *reason) +{ + if (skb) { + + u_short proto = ntohs(skb->protocol); + + printk(KERN_DEBUG "isdn_net: %s: %s, signalling dst_link_failure %s\n", + dev->name, + (reason != NULL) ? reason : "unknown", + (proto != ETH_P_IP) ? "Protocol != ETH_P_IP" : ""); + + dst_link_failure(skb); + } + else { /* dial not triggered by rawIP packet */ + printk(KERN_DEBUG "isdn_net: %s: %s\n", + dev->name, + (reason != NULL) ? reason : "reason unknown"); + } +} + +static void +isdn_net_reset(struct net_device *dev) +{ +#ifdef CONFIG_ISDN_X25 + struct concap_device_ops *dops = + ((isdn_net_local *)netdev_priv(dev))->dops; + struct concap_proto *cprot = + ((isdn_net_local *)netdev_priv(dev))->netdev->cprot; +#endif +#ifdef CONFIG_ISDN_X25 + if (cprot && cprot->pops && dops) + cprot->pops->restart(cprot, dev, dops); +#endif +} + +/* Open/initialize the board. */ +static int +isdn_net_open(struct net_device *dev) +{ + int i; + struct net_device *p; + struct in_device *in_dev; + + /* moved here from isdn_net_reset, because only the master has an + interface associated which is supposed to be started. BTW: + we need to call netif_start_queue, not netif_wake_queue here */ + netif_start_queue(dev); + + isdn_net_reset(dev); + /* Fill in the MAC-level header (not needed, but for compatibility... */ + for (i = 0; i < ETH_ALEN - sizeof(u32); i++) + dev->dev_addr[i] = 0xfc; + if ((in_dev = dev->ip_ptr) != NULL) { + /* + * Any address will do - we take the first + */ + struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa != NULL) + memcpy(dev->dev_addr + 2, &ifa->ifa_local, 4); + } + + /* If this interface has slaves, start them also */ + p = MASTER_TO_SLAVE(dev); + if (p) { + while (p) { + isdn_net_reset(p); + p = MASTER_TO_SLAVE(p); + } + } + isdn_lock_drivers(); + return 0; +} + +/* + * Assign an ISDN-channel to a net-interface + */ +static void +isdn_net_bind_channel(isdn_net_local *lp, int idx) +{ + lp->flags |= ISDN_NET_CONNECTED; + lp->isdn_device = dev->drvmap[idx]; + lp->isdn_channel = dev->chanmap[idx]; + dev->rx_netdev[idx] = lp->netdev; + dev->st_netdev[idx] = lp->netdev; +} + +/* + * unbind a net-interface (resets interface after an error) + */ +static void +isdn_net_unbind_channel(isdn_net_local *lp) +{ + skb_queue_purge(&lp->super_tx_queue); + + if (!lp->master) { /* reset only master device */ + /* Moral equivalent of dev_purge_queues(): + BEWARE! This chunk of code cannot be called from hardware + interrupt handler. I hope it is true. --ANK + */ + qdisc_reset_all_tx(lp->netdev->dev); + } + lp->dialstate = 0; + dev->rx_netdev[isdn_dc2minor(lp->isdn_device, lp->isdn_channel)] = NULL; + dev->st_netdev[isdn_dc2minor(lp->isdn_device, lp->isdn_channel)] = NULL; + if (lp->isdn_device != -1 && lp->isdn_channel != -1) + isdn_free_channel(lp->isdn_device, lp->isdn_channel, + ISDN_USAGE_NET); + lp->flags &= ~ISDN_NET_CONNECTED; + lp->isdn_device = -1; + lp->isdn_channel = -1; +} + +/* + * Perform auto-hangup and cps-calculation for net-interfaces. + * + * auto-hangup: + * Increment idle-counter (this counter is reset on any incoming or + * outgoing packet), if counter exceeds configured limit either do a + * hangup immediately or - if configured - wait until just before the next + * charge-info. + * + * cps-calculation (needed for dynamic channel-bundling): + * Since this function is called every second, simply reset the + * byte-counter of the interface after copying it to the cps-variable. + */ +static unsigned long last_jiffies = -HZ; + +void +isdn_net_autohup(void) +{ + isdn_net_dev *p = dev->netdev; + int anymore; + + anymore = 0; + while (p) { + isdn_net_local *l = p->local; + if (jiffies == last_jiffies) + l->cps = l->transcount; + else + l->cps = (l->transcount * HZ) / (jiffies - last_jiffies); + l->transcount = 0; + if (dev->net_verbose > 3) + printk(KERN_DEBUG "%s: %d bogocps\n", p->dev->name, l->cps); + if ((l->flags & ISDN_NET_CONNECTED) && (!l->dialstate)) { + anymore = 1; + l->huptimer++; + /* + * if there is some dialmode where timeout-hangup + * should _not_ be done, check for that here + */ + if ((l->onhtime) && + (l->huptimer > l->onhtime)) + { + if (l->hupflags & ISDN_MANCHARGE && + l->hupflags & ISDN_CHARGEHUP) { + while (time_after(jiffies, l->chargetime + l->chargeint)) + l->chargetime += l->chargeint; + if (time_after(jiffies, l->chargetime + l->chargeint - 2 * HZ)) + if (l->outgoing || l->hupflags & ISDN_INHUP) + isdn_net_hangup(p->dev); + } else if (l->outgoing) { + if (l->hupflags & ISDN_CHARGEHUP) { + if (l->hupflags & ISDN_WAITCHARGE) { + printk(KERN_DEBUG "isdn_net: Hupflags of %s are %X\n", + p->dev->name, l->hupflags); + isdn_net_hangup(p->dev); + } else if (time_after(jiffies, l->chargetime + l->chargeint)) { + printk(KERN_DEBUG + "isdn_net: %s: chtime = %lu, chint = %d\n", + p->dev->name, l->chargetime, l->chargeint); + isdn_net_hangup(p->dev); + } + } else + isdn_net_hangup(p->dev); + } else if (l->hupflags & ISDN_INHUP) + isdn_net_hangup(p->dev); + } + + if (dev->global_flags & ISDN_GLOBAL_STOPPED || (ISDN_NET_DIALMODE(*l) == ISDN_NET_DM_OFF)) { + isdn_net_hangup(p->dev); + break; + } + } + p = (isdn_net_dev *) p->next; + } + last_jiffies = jiffies; + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, anymore); +} + +static void isdn_net_lp_disconnected(isdn_net_local *lp) +{ + isdn_net_rm_from_bundle(lp); +} + +/* + * Handle status-messages from ISDN-interfacecard. + * This function is called from within the main-status-dispatcher + * isdn_status_callback, which itself is called from the low-level driver. + * Return: 1 = Event handled, 0 = not for us or unknown Event. + */ +int +isdn_net_stat_callback(int idx, isdn_ctrl *c) +{ + isdn_net_dev *p = dev->st_netdev[idx]; + int cmd = c->command; + + if (p) { + isdn_net_local *lp = p->local; +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; + struct concap_proto_ops *pops = cprot ? cprot->pops : NULL; +#endif + switch (cmd) { + case ISDN_STAT_BSENT: + /* A packet has successfully been sent out */ + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + isdn_net_dec_frame_cnt(lp); + lp->stats.tx_packets++; + lp->stats.tx_bytes += c->parm.length; + } + return 1; + case ISDN_STAT_DCONN: + /* D-Channel is up */ + switch (lp->dialstate) { + case 4: + case 7: + case 8: + lp->dialstate++; + return 1; + case 12: + lp->dialstate = 5; + return 1; + } + break; + case ISDN_STAT_DHUP: + /* Either D-Channel-hangup or error during dialout */ +#ifdef CONFIG_ISDN_X25 + /* If we are not connencted then dialing had + failed. If there are generic encap protocol + receiver routines signal the closure of + the link*/ + + if (!(lp->flags & ISDN_NET_CONNECTED) + && pops && pops->disconn_ind) + pops->disconn_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + if ((!lp->dialstate) && (lp->flags & ISDN_NET_CONNECTED)) { + if (lp->p_encap == ISDN_NET_ENCAP_CISCOHDLCK) + isdn_net_ciscohdlck_disconnected(lp); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + printk(KERN_INFO "%s: remote hangup\n", p->dev->name); + printk(KERN_INFO "%s: Chargesum is %d\n", p->dev->name, + lp->charge); + isdn_net_unbind_channel(lp); + return 1; + } + break; +#ifdef CONFIG_ISDN_X25 + case ISDN_STAT_BHUP: + /* B-Channel-hangup */ + /* try if there are generic encap protocol + receiver routines and signal the closure of + the link */ + if (pops && pops->disconn_ind) { + pops->disconn_ind(cprot); + return 1; + } + break; +#endif /* CONFIG_ISDN_X25 */ + case ISDN_STAT_BCONN: + /* B-Channel is up */ + isdn_net_zero_frame_cnt(lp); + switch (lp->dialstate) { + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 12: + if (lp->dialstate <= 6) { + dev->usage[idx] |= ISDN_USAGE_OUTGOING; + isdn_info_update(); + } else + dev->rx_netdev[idx] = p; + lp->dialstate = 0; + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 1); + if (lp->p_encap == ISDN_NET_ENCAP_CISCOHDLCK) + isdn_net_ciscohdlck_connected(lp); + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) { + if (lp->master) { /* is lp a slave? */ + isdn_net_dev *nd = ISDN_MASTER_PRIV(lp)->netdev; + isdn_net_add_to_bundle(nd, lp); + } + } + printk(KERN_INFO "isdn_net: %s connected\n", p->dev->name); + /* If first Chargeinfo comes before B-Channel connect, + * we correct the timestamp here. + */ + lp->chargetime = jiffies; + + /* reset dial-timeout */ + lp->dialstarted = 0; + lp->dialwait_timer = 0; + +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_wakeup_daemon(lp); +#endif +#ifdef CONFIG_ISDN_X25 + /* try if there are generic concap receiver routines */ + if (pops) + if (pops->connect_ind) + pops->connect_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + /* ppp needs to do negotiations first */ + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + isdn_net_device_wake_queue(lp); + return 1; + } + break; + case ISDN_STAT_NODCH: + /* No D-Channel avail. */ + if (lp->dialstate == 4) { + lp->dialstate--; + return 1; + } + break; + case ISDN_STAT_CINF: + /* Charge-info from TelCo. Calculate interval between + * charge-infos and set timestamp for last info for + * usage by isdn_net_autohup() + */ + lp->charge++; + if (lp->hupflags & ISDN_HAVECHARGE) { + lp->hupflags &= ~ISDN_WAITCHARGE; + lp->chargeint = jiffies - lp->chargetime - (2 * HZ); + } + if (lp->hupflags & ISDN_WAITCHARGE) + lp->hupflags |= ISDN_HAVECHARGE; + lp->chargetime = jiffies; + printk(KERN_DEBUG "isdn_net: Got CINF chargetime of %s now %lu\n", + p->dev->name, lp->chargetime); + return 1; + } + } + return 0; +} + +/* + * Perform dialout for net-interfaces and timeout-handling for + * D-Channel-up and B-Channel-up Messages. + * This function is initially called from within isdn_net_start_xmit() or + * or isdn_net_find_icall() after initializing the dialstate for an + * interface. If further calls are needed, the function schedules itself + * for a timer-callback via isdn_timer_function(). + * The dialstate is also affected by incoming status-messages from + * the ISDN-Channel which are handled in isdn_net_stat_callback() above. + */ +void +isdn_net_dial(void) +{ + isdn_net_dev *p = dev->netdev; + int anymore = 0; + int i; + isdn_ctrl cmd; + u_char *phone_number; + + while (p) { + isdn_net_local *lp = p->local; + +#ifdef ISDN_DEBUG_NET_DIAL + if (lp->dialstate) + printk(KERN_DEBUG "%s: dialstate=%d\n", p->dev->name, lp->dialstate); +#endif + switch (lp->dialstate) { + case 0: + /* Nothing to do for this interface */ + break; + case 1: + /* Initiate dialout. Set phone-number-pointer to first number + * of interface. + */ + lp->dial = lp->phone[1]; + if (!lp->dial) { + printk(KERN_WARNING "%s: phone number deleted?\n", + p->dev->name); + isdn_net_hangup(p->dev); + break; + } + anymore = 1; + + if (lp->dialtimeout > 0) + if (lp->dialstarted == 0 || time_after(jiffies, lp->dialstarted + lp->dialtimeout + lp->dialwait)) { + lp->dialstarted = jiffies; + lp->dialwait_timer = 0; + } + + lp->dialstate++; + /* Fall through */ + case 2: + /* Prepare dialing. Clear EAZ, then set EAZ. */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + sprintf(cmd.parm.num, "%s", isdn_map_eaz2msn(lp->msn, cmd.driver)); + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + lp->dialretry = 0; + anymore = 1; + lp->dialstate++; + /* Fall through */ + case 3: + /* Setup interface, dial current phone-number, switch to next number. + * If list of phone-numbers is exhausted, increment + * retry-counter. + */ + if (dev->global_flags & ISDN_GLOBAL_STOPPED || (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF)) { + char *s; + if (dev->global_flags & ISDN_GLOBAL_STOPPED) + s = "dial suppressed: isdn system stopped"; + else + s = "dial suppressed: dialmode `off'"; + isdn_net_unreachable(p->dev, NULL, s); + isdn_net_hangup(p->dev); + break; + } + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = lp->isdn_channel + (lp->l2_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = lp->isdn_channel + (lp->l3_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + if (!lp->dial) { + printk(KERN_WARNING "%s: phone number deleted?\n", + p->dev->name); + isdn_net_hangup(p->dev); + break; + } + if (!strncmp(lp->dial->num, "LEASED", strlen("LEASED"))) { + lp->dialstate = 4; + printk(KERN_INFO "%s: Open leased line ...\n", p->dev->name); + } else { + if (lp->dialtimeout > 0) + if (time_after(jiffies, lp->dialstarted + lp->dialtimeout)) { + lp->dialwait_timer = jiffies + lp->dialwait; + lp->dialstarted = 0; + isdn_net_unreachable(p->dev, NULL, "dial: timed out"); + isdn_net_hangup(p->dev); + break; + } + + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_DIAL; + cmd.parm.setup.si2 = 0; + + /* check for DOV */ + phone_number = lp->dial->num; + if ((*phone_number == 'v') || + (*phone_number == 'V')) { /* DOV call */ + cmd.parm.setup.si1 = 1; + } else { /* DATA call */ + cmd.parm.setup.si1 = 7; + } + + strcpy(cmd.parm.setup.phone, phone_number); + /* + * Switch to next number or back to start if at end of list. + */ + if (!(lp->dial = (isdn_net_phone *) lp->dial->next)) { + lp->dial = lp->phone[1]; + lp->dialretry++; + + if (lp->dialretry > lp->dialmax) { + if (lp->dialtimeout == 0) { + lp->dialwait_timer = jiffies + lp->dialwait; + lp->dialstarted = 0; + isdn_net_unreachable(p->dev, NULL, "dial: tried all numbers dialmax times"); + } + isdn_net_hangup(p->dev); + break; + } + } + sprintf(cmd.parm.setup.eazmsn, "%s", + isdn_map_eaz2msn(lp->msn, cmd.driver)); + i = isdn_dc2minor(lp->isdn_device, lp->isdn_channel); + if (i >= 0) { + strcpy(dev->num[i], cmd.parm.setup.phone); + dev->usage[i] |= ISDN_USAGE_OUTGOING; + isdn_info_update(); + } + printk(KERN_INFO "%s: dialing %d %s... %s\n", p->dev->name, + lp->dialretry, cmd.parm.setup.phone, + (cmd.parm.setup.si1 == 1) ? "DOV" : ""); + lp->dtimer = 0; +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dial: d=%d c=%d\n", lp->isdn_device, + lp->isdn_channel); +#endif + isdn_command(&cmd); + } + lp->huptimer = 0; + lp->outgoing = 1; + if (lp->chargeint) { + lp->hupflags |= ISDN_HAVECHARGE; + lp->hupflags &= ~ISDN_WAITCHARGE; + } else { + lp->hupflags |= ISDN_WAITCHARGE; + lp->hupflags &= ~ISDN_HAVECHARGE; + } + anymore = 1; + lp->dialstate = + (lp->cbdelay && + (lp->flags & ISDN_NET_CBOUT)) ? 12 : 4; + break; + case 4: + /* Wait for D-Channel-connect. + * If timeout, switch back to state 3. + * Dialmax-handling moved to state 3. + */ + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + lp->dialstate = 3; + anymore = 1; + break; + case 5: + /* Got D-Channel-Connect, send B-Channel-request */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + anymore = 1; + lp->dtimer = 0; + lp->dialstate++; + isdn_command(&cmd); + break; + case 6: + /* Wait for B- or D-Channel-connect. If timeout, + * switch back to state 3. + */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer2: %d\n", lp->dtimer); +#endif + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + lp->dialstate = 3; + anymore = 1; + break; + case 7: + /* Got incoming Call, setup L2 and L3 protocols, + * then wait for D-Channel-connect + */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer4: %d\n", lp->dtimer); +#endif + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = lp->isdn_channel + (lp->l2_proto << 8); + isdn_command(&cmd); + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = lp->isdn_channel + (lp->l3_proto << 8); + isdn_command(&cmd); + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT15) + isdn_net_hangup(p->dev); + else { + anymore = 1; + lp->dialstate++; + } + break; + case 9: + /* Got incoming D-Channel-Connect, send B-Channel-request */ + cmd.driver = lp->isdn_device; + cmd.arg = lp->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTB; + isdn_command(&cmd); + anymore = 1; + lp->dtimer = 0; + lp->dialstate++; + break; + case 8: + case 10: + /* Wait for B- or D-channel-connect */ +#ifdef ISDN_DEBUG_NET_DIAL + printk(KERN_DEBUG "dialtimer4: %d\n", lp->dtimer); +#endif + if (lp->dtimer++ > ISDN_TIMER_DTIMEOUT10) + isdn_net_hangup(p->dev); + else + anymore = 1; + break; + case 11: + /* Callback Delay */ + if (lp->dtimer++ > lp->cbdelay) + lp->dialstate = 1; + anymore = 1; + break; + case 12: + /* Remote does callback. Hangup after cbdelay, then wait for incoming + * call (in state 4). + */ + if (lp->dtimer++ > lp->cbdelay) + { + printk(KERN_INFO "%s: hangup waiting for callback ...\n", p->dev->name); + lp->dtimer = 0; + lp->dialstate = 4; + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = lp->isdn_channel; + isdn_command(&cmd); + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + } + anymore = 1; + break; + default: + printk(KERN_WARNING "isdn_net: Illegal dialstate %d for device %s\n", + lp->dialstate, p->dev->name); + } + p = (isdn_net_dev *) p->next; + } + isdn_timer_ctrl(ISDN_TIMER_NETDIAL, anymore); +} + +/* + * Perform hangup for a net-interface. + */ +void +isdn_net_hangup(struct net_device *d) +{ + isdn_net_local *lp = netdev_priv(d); + isdn_ctrl cmd; +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; + struct concap_proto_ops *pops = cprot ? cprot->pops : NULL; +#endif + + if (lp->flags & ISDN_NET_CONNECTED) { + if (lp->slave != NULL) { + isdn_net_local *slp = ISDN_SLAVE_PRIV(lp); + if (slp->flags & ISDN_NET_CONNECTED) { + printk(KERN_INFO + "isdn_net: hang up slave %s before %s\n", + lp->slave->name, d->name); + isdn_net_hangup(lp->slave); + } + } + printk(KERN_INFO "isdn_net: local hangup %s\n", d->name); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); +#ifdef CONFIG_ISDN_X25 + /* try if there are generic encap protocol + receiver routines and signal the closure of + the link */ + if (pops && pops->disconn_ind) + pops->disconn_ind(cprot); +#endif /* CONFIG_ISDN_X25 */ + + cmd.driver = lp->isdn_device; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = lp->isdn_channel; + isdn_command(&cmd); + printk(KERN_INFO "%s: Chargesum is %d\n", d->name, lp->charge); + isdn_all_eaz(lp->isdn_device, lp->isdn_channel); + } + isdn_net_unbind_channel(lp); +} + +typedef struct { + __be16 source; + __be16 dest; +} ip_ports; + +static void +isdn_net_log_skb(struct sk_buff *skb, isdn_net_local *lp) +{ + /* hopefully, this was set correctly */ + const u_char *p = skb_network_header(skb); + unsigned short proto = ntohs(skb->protocol); + int data_ofs; + ip_ports *ipp; + char addinfo[100]; + + addinfo[0] = '\0'; + /* This check stolen from 2.1.72 dev_queue_xmit_nit() */ + if (p < skb->data || skb_network_header(skb) >= skb_tail_pointer(skb)) { + /* fall back to old isdn_net_log_packet method() */ + char *buf = skb->data; + + printk(KERN_DEBUG "isdn_net: protocol %04x is buggy, dev %s\n", skb->protocol, lp->netdev->dev->name); + p = buf; + proto = ETH_P_IP; + switch (lp->p_encap) { + case ISDN_NET_ENCAP_IPTYP: + proto = ntohs(*(__be16 *)&buf[0]); + p = &buf[2]; + break; + case ISDN_NET_ENCAP_ETHER: + proto = ntohs(*(__be16 *)&buf[12]); + p = &buf[14]; + break; + case ISDN_NET_ENCAP_CISCOHDLC: + proto = ntohs(*(__be16 *)&buf[2]); + p = &buf[4]; + break; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + proto = ntohs(skb->protocol); + p = &buf[IPPP_MAX_HEADER]; + break; +#endif + } + } + data_ofs = ((p[0] & 15) * 4); + switch (proto) { + case ETH_P_IP: + switch (p[9]) { + case 1: + strcpy(addinfo, " ICMP"); + break; + case 2: + strcpy(addinfo, " IGMP"); + break; + case 4: + strcpy(addinfo, " IPIP"); + break; + case 6: + ipp = (ip_ports *) (&p[data_ofs]); + sprintf(addinfo, " TCP, port: %d -> %d", ntohs(ipp->source), + ntohs(ipp->dest)); + break; + case 8: + strcpy(addinfo, " EGP"); + break; + case 12: + strcpy(addinfo, " PUP"); + break; + case 17: + ipp = (ip_ports *) (&p[data_ofs]); + sprintf(addinfo, " UDP, port: %d -> %d", ntohs(ipp->source), + ntohs(ipp->dest)); + break; + case 22: + strcpy(addinfo, " IDP"); + break; + } + printk(KERN_INFO "OPEN: %pI4 -> %pI4%s\n", + p + 12, p + 16, addinfo); + break; + case ETH_P_ARP: + printk(KERN_INFO "OPEN: ARP %pI4 -> *.*.*.* ?%pI4\n", + p + 14, p + 24); + break; + } +} + +/* + * this function is used to send supervisory data, i.e. data which was + * not received from the network layer, but e.g. frames from ipppd, CCP + * reset frames etc. + */ +void isdn_net_write_super(isdn_net_local *lp, struct sk_buff *skb) +{ + if (in_irq()) { + // we can't grab the lock from irq context, + // so we just queue the packet + skb_queue_tail(&lp->super_tx_queue, skb); + schedule_work(&lp->tqueue); + return; + } + + spin_lock_bh(&lp->xmit_lock); + if (!isdn_net_lp_busy(lp)) { + isdn_net_writebuf_skb(lp, skb); + } else { + skb_queue_tail(&lp->super_tx_queue, skb); + } + spin_unlock_bh(&lp->xmit_lock); +} + +/* + * called from tq_immediate + */ +static void isdn_net_softint(struct work_struct *work) +{ + isdn_net_local *lp = container_of(work, isdn_net_local, tqueue); + struct sk_buff *skb; + + spin_lock_bh(&lp->xmit_lock); + while (!isdn_net_lp_busy(lp)) { + skb = skb_dequeue(&lp->super_tx_queue); + if (!skb) + break; + isdn_net_writebuf_skb(lp, skb); + } + spin_unlock_bh(&lp->xmit_lock); +} + +/* + * all frames sent from the (net) LL to a HL driver should go via this function + * it's serialized by the caller holding the lp->xmit_lock spinlock + */ +void isdn_net_writebuf_skb(isdn_net_local *lp, struct sk_buff *skb) +{ + int ret; + int len = skb->len; /* save len */ + + /* before obtaining the lock the caller should have checked that + the lp isn't busy */ + if (isdn_net_lp_busy(lp)) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + goto error; + } + + if (!(lp->flags & ISDN_NET_CONNECTED)) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + goto error; + } + ret = isdn_writebuf_skb_stub(lp->isdn_device, lp->isdn_channel, 1, skb); + if (ret != len) { + /* we should never get here */ + printk(KERN_WARNING "%s: HL driver queue full\n", lp->netdev->dev->name); + goto error; + } + + lp->transcount += len; + isdn_net_inc_frame_cnt(lp); + return; + +error: + dev_kfree_skb(skb); + lp->stats.tx_errors++; + +} + + +/* + * Helper function for isdn_net_start_xmit. + * When called, the connection is already established. + * Based on cps-calculation, check if device is overloaded. + * If so, and if a slave exists, trigger dialing for it. + * If any slave is online, deliver packets using a simple round robin + * scheme. + * + * Return: 0 on success, !0 on failure. + */ + +static int +isdn_net_xmit(struct net_device *ndev, struct sk_buff *skb) +{ + isdn_net_dev *nd; + isdn_net_local *slp; + isdn_net_local *lp = netdev_priv(ndev); + int retv = NETDEV_TX_OK; + + if (((isdn_net_local *) netdev_priv(ndev))->master) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + + /* For the other encaps the header has already been built */ +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + return isdn_ppp_xmit(skb, ndev); + } +#endif + nd = ((isdn_net_local *) netdev_priv(ndev))->netdev; + lp = isdn_net_get_locked_lp(nd); + if (!lp) { + printk(KERN_WARNING "%s: all channels busy - requeuing!\n", ndev->name); + return NETDEV_TX_BUSY; + } + /* we have our lp locked from now on */ + + /* Reset hangup-timeout */ + lp->huptimer = 0; // FIXME? + isdn_net_writebuf_skb(lp, skb); + spin_unlock_bh(&lp->xmit_lock); + + /* the following stuff is here for backwards compatibility. + * in future, start-up and hangup of slaves (based on current load) + * should move to userspace and get based on an overall cps + * calculation + */ + if (lp->cps > lp->triggercps) { + if (lp->slave) { + if (!lp->sqfull) { + /* First time overload: set timestamp only */ + lp->sqfull = 1; + lp->sqfull_stamp = jiffies; + } else { + /* subsequent overload: if slavedelay exceeded, start dialing */ + if (time_after(jiffies, lp->sqfull_stamp + lp->slavedelay)) { + slp = ISDN_SLAVE_PRIV(lp); + if (!(slp->flags & ISDN_NET_CONNECTED)) { + isdn_net_force_dial_lp(ISDN_SLAVE_PRIV(lp)); + } + } + } + } + } else { + if (lp->sqfull && time_after(jiffies, lp->sqfull_stamp + lp->slavedelay + (10 * HZ))) { + lp->sqfull = 0; + } + /* this is a hack to allow auto-hangup for slaves on moderate loads */ + nd->queue = nd->local; + } + + return retv; + +} + +static void +isdn_net_adjust_hdr(struct sk_buff *skb, struct net_device *dev) +{ + isdn_net_local *lp = netdev_priv(dev); + if (!skb) + return; + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) { + const int pullsize = skb_network_offset(skb) - ETH_HLEN; + if (pullsize > 0) { + printk(KERN_DEBUG "isdn_net: Pull junk %d\n", pullsize); + skb_pull(skb, pullsize); + } + } +} + + +static void isdn_net_tx_timeout(struct net_device *ndev) +{ + isdn_net_local *lp = netdev_priv(ndev); + + printk(KERN_WARNING "isdn_tx_timeout dev %s dialstate %d\n", ndev->name, lp->dialstate); + if (!lp->dialstate) { + lp->stats.tx_errors++; + /* + * There is a certain probability that this currently + * works at all because if we always wake up the interface, + * then upper layer will try to send the next packet + * immediately. And then, the old clean_up logic in the + * driver will hopefully continue to work as it used to do. + * + * This is rather primitive right know, we better should + * clean internal queues here, in particular for multilink and + * ppp, and reset HL driver's channel, too. --HE + * + * actually, this may not matter at all, because ISDN hardware + * should not see transmitter hangs at all IMO + * changed KERN_DEBUG to KERN_WARNING to find out if this is + * ever called --KG + */ + } + ndev->trans_start = jiffies; + netif_wake_queue(ndev); +} + +/* + * Try sending a packet. + * If this interface isn't connected to a ISDN-Channel, find a free channel, + * and start dialing. + */ +static netdev_tx_t +isdn_net_start_xmit(struct sk_buff *skb, struct net_device *ndev) +{ + isdn_net_local *lp = netdev_priv(ndev); +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; +/* At this point hard_start_xmit() passes control to the encapsulation + protocol (if present). + For X.25 auto-dialing is completly bypassed because: + - It does not conform with the semantics of a reliable datalink + service as needed by X.25 PLP. + - I don't want that the interface starts dialing when the network layer + sends a message which requests to disconnect the lapb link (or if it + sends any other message not resulting in data transmission). + Instead, dialing will be initiated by the encapsulation protocol entity + when a dl_establish request is received from the upper layer. +*/ + if (cprot && cprot->pops) { + int ret = cprot->pops->encap_and_xmit(cprot, skb); + + if (ret) + netif_stop_queue(ndev); + return ret; + } else +#endif + /* auto-dialing xmit function */ + { +#ifdef ISDN_DEBUG_NET_DUMP + u_char *buf; +#endif + isdn_net_adjust_hdr(skb, ndev); +#ifdef ISDN_DEBUG_NET_DUMP + buf = skb->data; + isdn_dumppkt("S:", buf, skb->len, 40); +#endif + + if (!(lp->flags & ISDN_NET_CONNECTED)) { + int chi; + /* only do autodial if allowed by config */ + if (!(ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_AUTO)) { + isdn_net_unreachable(ndev, skb, "dial rejected: interface not in dialmode `auto'"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + if (lp->phone[1]) { + ulong flags; + + if (lp->dialwait_timer <= 0) + if (lp->dialstarted > 0 && lp->dialtimeout > 0 && time_before(jiffies, lp->dialstarted + lp->dialtimeout + lp->dialwait)) + lp->dialwait_timer = lp->dialstarted + lp->dialtimeout + lp->dialwait; + + if (lp->dialwait_timer > 0) { + if (time_before(jiffies, lp->dialwait_timer)) { + isdn_net_unreachable(ndev, skb, "dial rejected: retry-time not reached"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } else + lp->dialwait_timer = 0; + } + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if (((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn) + ) < 0) && + ((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel^1, + lp->msn) + ) < 0)) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unreachable(ndev, skb, + "No channel"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + /* Log packet, which triggered dialing */ + if (dev->net_verbose) + isdn_net_log_skb(skb, lp); + lp->dialstate = 1; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + /* no 'first_skb' handling for syncPPP */ + if (isdn_ppp_bind(lp) < 0) { + dev_kfree_skb(skb); + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + return NETDEV_TX_OK; /* STN (skb to nirvana) ;) */ + } +#ifdef CONFIG_IPPP_FILTER + if (isdn_ppp_autodial_filter(skb, lp)) { + isdn_ppp_free(lp); + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unreachable(ndev, skb, "dial rejected: packet filtered"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } +#endif + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); /* Initiate dialing */ + netif_stop_queue(ndev); + return NETDEV_TX_BUSY; /* let upper layer requeue skb packet */ + } +#endif + /* Initiate dialing */ + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); + isdn_net_device_stop_queue(lp); + return NETDEV_TX_BUSY; + } else { + isdn_net_unreachable(ndev, skb, + "No phone number"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + } + } else { + /* Device is connected to an ISDN channel */ + ndev->trans_start = jiffies; + if (!lp->dialstate) { + /* ISDN connection is established, try sending */ + int ret; + ret = (isdn_net_xmit(ndev, skb)); + if (ret) netif_stop_queue(ndev); + return ret; + } else + netif_stop_queue(ndev); + } + } + return NETDEV_TX_BUSY; +} + +/* + * Shutdown a net-interface. + */ +static int +isdn_net_close(struct net_device *dev) +{ + struct net_device *p; +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = + ((isdn_net_local *)netdev_priv(dev))->netdev->cprot; + /* printk(KERN_DEBUG "isdn_net_close %s\n" , dev-> name); */ +#endif + +#ifdef CONFIG_ISDN_X25 + if (cprot && cprot->pops) cprot->pops->close(cprot); +#endif + netif_stop_queue(dev); + p = MASTER_TO_SLAVE(dev); + if (p) { + /* If this interface has slaves, stop them also */ + while (p) { +#ifdef CONFIG_ISDN_X25 + cprot = ((isdn_net_local *)netdev_priv(p)) + ->netdev->cprot; + if (cprot && cprot->pops) + cprot->pops->close(cprot); +#endif + isdn_net_hangup(p); + p = MASTER_TO_SLAVE(p); + } + } + isdn_net_hangup(dev); + isdn_unlock_drivers(); + return 0; +} + +/* + * Get statistics + */ +static struct net_device_stats * +isdn_net_get_stats(struct net_device *dev) +{ + isdn_net_local *lp = netdev_priv(dev); + return &lp->stats; +} + +/* This is simply a copy from std. eth.c EXCEPT we pull ETH_HLEN + * instead of dev->hard_header_len off. This is done because the + * lowlevel-driver has already pulled off its stuff when we get + * here and this routine only gets called with p_encap == ETHER. + * Determine the packet's protocol ID. The rule here is that we + * assume 802.3 if the type field is short enough to be a length. + * This is normal practice and works for any 'now in use' protocol. + */ + +static __be16 +isdn_net_type_trans(struct sk_buff *skb, struct net_device *dev) +{ + struct ethhdr *eth; + unsigned char *rawp; + + skb_reset_mac_header(skb); + skb_pull(skb, ETH_HLEN); + eth = eth_hdr(skb); + + if (*eth->h_dest & 1) { + if (ether_addr_equal(eth->h_dest, dev->broadcast)) + skb->pkt_type = PACKET_BROADCAST; + else + skb->pkt_type = PACKET_MULTICAST; + } + /* + * This ALLMULTI check should be redundant by 1.4 + * so don't forget to remove it. + */ + + else if (dev->flags & (IFF_PROMISC /*| IFF_ALLMULTI*/)) { + if (!ether_addr_equal(eth->h_dest, dev->dev_addr)) + skb->pkt_type = PACKET_OTHERHOST; + } + if (ntohs(eth->h_proto) >= ETH_P_802_3_MIN) + return eth->h_proto; + + rawp = skb->data; + + /* + * This is a magic hack to spot IPX packets. Older Novell breaks + * the protocol design and runs IPX over 802.3 without an 802.2 LLC + * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This + * won't work for fault tolerant netware but does for the rest. + */ + if (*(unsigned short *) rawp == 0xFFFF) + return htons(ETH_P_802_3); + /* + * Real 802.2 LLC + */ + return htons(ETH_P_802_2); +} + + +/* + * CISCO HDLC keepalive specific stuff + */ +static struct sk_buff* +isdn_net_ciscohdlck_alloc_skb(isdn_net_local *lp, int len) +{ + unsigned short hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + struct sk_buff *skb; + + skb = alloc_skb(hl + len, GFP_ATOMIC); + if (skb) + skb_reserve(skb, hl); + else + printk("isdn out of mem at %s:%d!\n", __FILE__, __LINE__); + return skb; +} + +/* cisco hdlck device private ioctls */ +static int +isdn_ciscohdlck_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + isdn_net_local *lp = netdev_priv(dev); + unsigned long len = 0; + unsigned long expires = 0; + int tmp = 0; + int period = lp->cisco_keepalive_period; + s8 debserint = lp->cisco_debserint; + int rc = 0; + + if (lp->p_encap != ISDN_NET_ENCAP_CISCOHDLCK) + return -EINVAL; + + switch (cmd) { + /* get/set keepalive period */ + case SIOCGKEEPPERIOD: + len = (unsigned long)sizeof(lp->cisco_keepalive_period); + if (copy_to_user(ifr->ifr_data, + &lp->cisco_keepalive_period, len)) + rc = -EFAULT; + break; + case SIOCSKEEPPERIOD: + tmp = lp->cisco_keepalive_period; + len = (unsigned long)sizeof(lp->cisco_keepalive_period); + if (copy_from_user(&period, ifr->ifr_data, len)) + rc = -EFAULT; + if ((period > 0) && (period <= 32767)) + lp->cisco_keepalive_period = period; + else + rc = -EINVAL; + if (!rc && (tmp != lp->cisco_keepalive_period)) { + expires = (unsigned long)(jiffies + + lp->cisco_keepalive_period * HZ); + mod_timer(&lp->cisco_timer, expires); + printk(KERN_INFO "%s: Keepalive period set " + "to %d seconds.\n", + dev->name, lp->cisco_keepalive_period); + } + break; + + /* get/set debugging */ + case SIOCGDEBSERINT: + len = (unsigned long)sizeof(lp->cisco_debserint); + if (copy_to_user(ifr->ifr_data, + &lp->cisco_debserint, len)) + rc = -EFAULT; + break; + case SIOCSDEBSERINT: + len = (unsigned long)sizeof(lp->cisco_debserint); + if (copy_from_user(&debserint, + ifr->ifr_data, len)) + rc = -EFAULT; + if ((debserint >= 0) && (debserint <= 64)) + lp->cisco_debserint = debserint; + else + rc = -EINVAL; + break; + + default: + rc = -EINVAL; + break; + } + return (rc); +} + + +static int isdn_net_ioctl(struct net_device *dev, + struct ifreq *ifr, int cmd) +{ + isdn_net_local *lp = netdev_priv(dev); + + switch (lp->p_encap) { +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + return isdn_ppp_dev_ioctl(dev, ifr, cmd); +#endif + case ISDN_NET_ENCAP_CISCOHDLCK: + return isdn_ciscohdlck_dev_ioctl(dev, ifr, cmd); + default: + return -EINVAL; + } +} + +/* called via cisco_timer.function */ +static void +isdn_net_ciscohdlck_slarp_send_keepalive(unsigned long data) +{ + isdn_net_local *lp = (isdn_net_local *) data; + struct sk_buff *skb; + unsigned char *p; + unsigned long last_cisco_myseq = lp->cisco_myseq; + int myseq_diff = 0; + + if (!(lp->flags & ISDN_NET_CONNECTED) || lp->dialstate) { + printk("isdn BUG at %s:%d!\n", __FILE__, __LINE__); + return; + } + lp->cisco_myseq++; + + myseq_diff = (lp->cisco_myseq - lp->cisco_mineseen); + if ((lp->cisco_line_state) && ((myseq_diff >= 3) || (myseq_diff <= -3))) { + /* line up -> down */ + lp->cisco_line_state = 0; + printk(KERN_WARNING + "UPDOWN: Line protocol on Interface %s," + " changed state to down\n", lp->netdev->dev->name); + /* should stop routing higher-level data across */ + } else if ((!lp->cisco_line_state) && + (myseq_diff >= 0) && (myseq_diff <= 2)) { + /* line down -> up */ + lp->cisco_line_state = 1; + printk(KERN_WARNING + "UPDOWN: Line protocol on Interface %s," + " changed state to up\n", lp->netdev->dev->name); + /* restart routing higher-level data across */ + } + + if (lp->cisco_debserint) + printk(KERN_DEBUG "%s: HDLC " + "myseq %lu, mineseen %lu%c, yourseen %lu, %s\n", + lp->netdev->dev->name, last_cisco_myseq, lp->cisco_mineseen, + ((last_cisco_myseq == lp->cisco_mineseen) ? '*' : 040), + lp->cisco_yourseq, + ((lp->cisco_line_state) ? "line up" : "line down")); + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + *(u8 *)(p + 0) = CISCO_ADDR_UNICAST; + *(u8 *)(p + 1) = CISCO_CTRL; + *(__be16 *)(p + 2) = cpu_to_be16(CISCO_TYPE_SLARP); + + /* slarp keepalive */ + *(__be32 *)(p + 4) = cpu_to_be32(CISCO_SLARP_KEEPALIVE); + *(__be32 *)(p + 8) = cpu_to_be32(lp->cisco_myseq); + *(__be32 *)(p + 12) = cpu_to_be32(lp->cisco_yourseq); + *(__be16 *)(p + 16) = cpu_to_be16(0xffff); // reliability, always 0xffff + p += 18; + + isdn_net_write_super(lp, skb); + + lp->cisco_timer.expires = jiffies + lp->cisco_keepalive_period * HZ; + + add_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_slarp_send_request(isdn_net_local *lp) +{ + struct sk_buff *skb; + unsigned char *p; + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + *(u8 *)(p + 0) = CISCO_ADDR_UNICAST; + *(u8 *)(p + 1) = CISCO_CTRL; + *(__be16 *)(p + 2) = cpu_to_be16(CISCO_TYPE_SLARP); + + /* slarp request */ + *(__be32 *)(p + 4) = cpu_to_be32(CISCO_SLARP_REQUEST); + *(__be32 *)(p + 8) = cpu_to_be32(0); // address + *(__be32 *)(p + 12) = cpu_to_be32(0); // netmask + *(__be16 *)(p + 16) = cpu_to_be16(0); // unused + p += 18; + + isdn_net_write_super(lp, skb); +} + +static void +isdn_net_ciscohdlck_connected(isdn_net_local *lp) +{ + lp->cisco_myseq = 0; + lp->cisco_mineseen = 0; + lp->cisco_yourseq = 0; + lp->cisco_keepalive_period = ISDN_TIMER_KEEPINT; + lp->cisco_last_slarp_in = 0; + lp->cisco_line_state = 0; + lp->cisco_debserint = 0; + + /* send slarp request because interface/seq.no.s reset */ + isdn_net_ciscohdlck_slarp_send_request(lp); + + init_timer(&lp->cisco_timer); + lp->cisco_timer.data = (unsigned long) lp; + lp->cisco_timer.function = isdn_net_ciscohdlck_slarp_send_keepalive; + lp->cisco_timer.expires = jiffies + lp->cisco_keepalive_period * HZ; + add_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_disconnected(isdn_net_local *lp) +{ + del_timer(&lp->cisco_timer); +} + +static void +isdn_net_ciscohdlck_slarp_send_reply(isdn_net_local *lp) +{ + struct sk_buff *skb; + unsigned char *p; + struct in_device *in_dev = NULL; + __be32 addr = 0; /* local ipv4 address */ + __be32 mask = 0; /* local netmask */ + + if ((in_dev = lp->netdev->dev->ip_ptr) != NULL) { + /* take primary(first) address of interface */ + struct in_ifaddr *ifa = in_dev->ifa_list; + if (ifa != NULL) { + addr = ifa->ifa_local; + mask = ifa->ifa_mask; + } + } + + skb = isdn_net_ciscohdlck_alloc_skb(lp, 4 + 14); + if (!skb) + return; + + p = skb_put(skb, 4 + 14); + + /* cisco header */ + *(u8 *)(p + 0) = CISCO_ADDR_UNICAST; + *(u8 *)(p + 1) = CISCO_CTRL; + *(__be16 *)(p + 2) = cpu_to_be16(CISCO_TYPE_SLARP); + + /* slarp reply, send own ip/netmask; if values are nonsense remote + * should think we are unable to provide it with an address via SLARP */ + *(__be32 *)(p + 4) = cpu_to_be32(CISCO_SLARP_REPLY); + *(__be32 *)(p + 8) = addr; // address + *(__be32 *)(p + 12) = mask; // netmask + *(__be16 *)(p + 16) = cpu_to_be16(0); // unused + p += 18; + + isdn_net_write_super(lp, skb); +} + +static void +isdn_net_ciscohdlck_slarp_in(isdn_net_local *lp, struct sk_buff *skb) +{ + unsigned char *p; + int period; + u32 code; + u32 my_seq; + u32 your_seq; + __be32 local; + __be32 *addr, *mask; + + if (skb->len < 14) + return; + + p = skb->data; + code = be32_to_cpup((__be32 *)p); + p += 4; + + switch (code) { + case CISCO_SLARP_REQUEST: + lp->cisco_yourseq = 0; + isdn_net_ciscohdlck_slarp_send_reply(lp); + break; + case CISCO_SLARP_REPLY: + addr = (__be32 *)p; + mask = (__be32 *)(p + 4); + if (*mask != cpu_to_be32(0xfffffffc)) + goto slarp_reply_out; + if ((*addr & cpu_to_be32(3)) == cpu_to_be32(0) || + (*addr & cpu_to_be32(3)) == cpu_to_be32(3)) + goto slarp_reply_out; + local = *addr ^ cpu_to_be32(3); + printk(KERN_INFO "%s: got slarp reply: remote ip: %pI4, local ip: %pI4 mask: %pI4\n", + lp->netdev->dev->name, addr, &local, mask); + break; + slarp_reply_out: + printk(KERN_INFO "%s: got invalid slarp reply (%pI4/%pI4) - ignored\n", + lp->netdev->dev->name, addr, mask); + break; + case CISCO_SLARP_KEEPALIVE: + period = (int)((jiffies - lp->cisco_last_slarp_in + + HZ / 2 - 1) / HZ); + if (lp->cisco_debserint && + (period != lp->cisco_keepalive_period) && + lp->cisco_last_slarp_in) { + printk(KERN_DEBUG "%s: Keepalive period mismatch - " + "is %d but should be %d.\n", + lp->netdev->dev->name, period, + lp->cisco_keepalive_period); + } + lp->cisco_last_slarp_in = jiffies; + my_seq = be32_to_cpup((__be32 *)(p + 0)); + your_seq = be32_to_cpup((__be32 *)(p + 4)); + p += 10; + lp->cisco_yourseq = my_seq; + lp->cisco_mineseen = your_seq; + break; + } +} + +static void +isdn_net_ciscohdlck_receive(isdn_net_local *lp, struct sk_buff *skb) +{ + unsigned char *p; + u8 addr; + u8 ctrl; + u16 type; + + if (skb->len < 4) + goto out_free; + + p = skb->data; + addr = *(u8 *)(p + 0); + ctrl = *(u8 *)(p + 1); + type = be16_to_cpup((__be16 *)(p + 2)); + p += 4; + skb_pull(skb, 4); + + if (addr != CISCO_ADDR_UNICAST && addr != CISCO_ADDR_BROADCAST) { + printk(KERN_WARNING "%s: Unknown Cisco addr 0x%02x\n", + lp->netdev->dev->name, addr); + goto out_free; + } + if (ctrl != CISCO_CTRL) { + printk(KERN_WARNING "%s: Unknown Cisco ctrl 0x%02x\n", + lp->netdev->dev->name, ctrl); + goto out_free; + } + + switch (type) { + case CISCO_TYPE_SLARP: + isdn_net_ciscohdlck_slarp_in(lp, skb); + goto out_free; + case CISCO_TYPE_CDP: + if (lp->cisco_debserint) + printk(KERN_DEBUG "%s: Received CDP packet. use " + "\"no cdp enable\" on cisco.\n", + lp->netdev->dev->name); + goto out_free; + default: + /* no special cisco protocol */ + skb->protocol = htons(type); + netif_rx(skb); + return; + } + +out_free: + kfree_skb(skb); +} + +/* + * Got a packet from ISDN-Channel. + */ +static void +isdn_net_receive(struct net_device *ndev, struct sk_buff *skb) +{ + isdn_net_local *lp = netdev_priv(ndev); + isdn_net_local *olp = lp; /* original 'lp' */ +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = lp->netdev->cprot; +#endif + lp->transcount += skb->len; + + lp->stats.rx_packets++; + lp->stats.rx_bytes += skb->len; + if (lp->master) { + /* Bundling: If device is a slave-device, deliver to master, also + * handle master's statistics and hangup-timeout + */ + ndev = lp->master; + lp = netdev_priv(ndev); + lp->stats.rx_packets++; + lp->stats.rx_bytes += skb->len; + } + skb->dev = ndev; + skb->pkt_type = PACKET_HOST; + skb_reset_mac_header(skb); +#ifdef ISDN_DEBUG_NET_DUMP + isdn_dumppkt("R:", skb->data, skb->len, 40); +#endif + switch (lp->p_encap) { + case ISDN_NET_ENCAP_ETHER: + /* Ethernet over ISDN */ + olp->huptimer = 0; + lp->huptimer = 0; + skb->protocol = isdn_net_type_trans(skb, ndev); + break; + case ISDN_NET_ENCAP_UIHDLC: + /* HDLC with UI-frame (for ispa with -h1 option) */ + olp->huptimer = 0; + lp->huptimer = 0; + skb_pull(skb, 2); + /* Fall through */ + case ISDN_NET_ENCAP_RAWIP: + /* RAW-IP without MAC-Header */ + olp->huptimer = 0; + lp->huptimer = 0; + skb->protocol = htons(ETH_P_IP); + break; + case ISDN_NET_ENCAP_CISCOHDLCK: + isdn_net_ciscohdlck_receive(lp, skb); + return; + case ISDN_NET_ENCAP_CISCOHDLC: + /* CISCO-HDLC IP with type field and fake I-frame-header */ + skb_pull(skb, 2); + /* Fall through */ + case ISDN_NET_ENCAP_IPTYP: + /* IP with type field */ + olp->huptimer = 0; + lp->huptimer = 0; + skb->protocol = *(__be16 *)&(skb->data[0]); + skb_pull(skb, 2); + if (*(unsigned short *) skb->data == 0xFFFF) + skb->protocol = htons(ETH_P_802_3); + break; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + /* huptimer is done in isdn_ppp_push_higher */ + isdn_ppp_receive(lp->netdev, olp, skb); + return; +#endif + + default: +#ifdef CONFIG_ISDN_X25 + /* try if there are generic sync_device receiver routines */ + if (cprot) if (cprot->pops) + if (cprot->pops->data_ind) { + cprot->pops->data_ind(cprot, skb); + return; + }; +#endif /* CONFIG_ISDN_X25 */ + printk(KERN_WARNING "%s: unknown encapsulation, dropping\n", + lp->netdev->dev->name); + kfree_skb(skb); + return; + } + + netif_rx(skb); + return; +} + +/* + * A packet arrived via ISDN. Search interface-chain for a corresponding + * interface. If found, deliver packet to receiver-function and return 1, + * else return 0. + */ +int +isdn_net_rcv_skb(int idx, struct sk_buff *skb) +{ + isdn_net_dev *p = dev->rx_netdev[idx]; + + if (p) { + isdn_net_local *lp = p->local; + if ((lp->flags & ISDN_NET_CONNECTED) && + (!lp->dialstate)) { + isdn_net_receive(p->dev, skb); + return 1; + } + } + return 0; +} + +/* + * build an header + * depends on encaps that is being used. + */ + +static int isdn_net_header(struct sk_buff *skb, struct net_device *dev, + unsigned short type, + const void *daddr, const void *saddr, unsigned plen) +{ + isdn_net_local *lp = netdev_priv(dev); + unsigned char *p; + int len = 0; + + switch (lp->p_encap) { + case ISDN_NET_ENCAP_ETHER: + len = eth_header(skb, dev, type, daddr, saddr, plen); + break; +#ifdef CONFIG_ISDN_PPP + case ISDN_NET_ENCAP_SYNCPPP: + /* stick on a fake header to keep fragmentation code happy. */ + len = IPPP_MAX_HEADER; + skb_push(skb, len); + break; +#endif + case ISDN_NET_ENCAP_RAWIP: + printk(KERN_WARNING "isdn_net_header called with RAW_IP!\n"); + len = 0; + break; + case ISDN_NET_ENCAP_IPTYP: + /* ethernet type field */ + *((__be16 *)skb_push(skb, 2)) = htons(type); + len = 2; + break; + case ISDN_NET_ENCAP_UIHDLC: + /* HDLC with UI-Frames (for ispa with -h1 option) */ + *((__be16 *)skb_push(skb, 2)) = htons(0x0103); + len = 2; + break; + case ISDN_NET_ENCAP_CISCOHDLC: + case ISDN_NET_ENCAP_CISCOHDLCK: + p = skb_push(skb, 4); + *(u8 *)(p + 0) = CISCO_ADDR_UNICAST; + *(u8 *)(p + 1) = CISCO_CTRL; + *(__be16 *)(p + 2) = cpu_to_be16(type); + p += 4; + len = 4; + break; +#ifdef CONFIG_ISDN_X25 + default: + /* try if there are generic concap protocol routines */ + if (lp->netdev->cprot) { + printk(KERN_WARNING "isdn_net_header called with concap_proto!\n"); + len = 0; + break; + } + break; +#endif /* CONFIG_ISDN_X25 */ + } + return len; +} + +static int isdn_header_cache(const struct neighbour *neigh, struct hh_cache *hh, + __be16 type) +{ + const struct net_device *dev = neigh->dev; + isdn_net_local *lp = netdev_priv(dev); + + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) + return eth_header_cache(neigh, hh, type); + return -1; +} + +static void isdn_header_cache_update(struct hh_cache *hh, + const struct net_device *dev, + const unsigned char *haddr) +{ + isdn_net_local *lp = netdev_priv(dev); + if (lp->p_encap == ISDN_NET_ENCAP_ETHER) + eth_header_cache_update(hh, dev, haddr); +} + +static const struct header_ops isdn_header_ops = { + .create = isdn_net_header, + .cache = isdn_header_cache, + .cache_update = isdn_header_cache_update, +}; + +/* + * Interface-setup. (just after registering a new interface) + */ +static int +isdn_net_init(struct net_device *ndev) +{ + ushort max_hlhdr_len = 0; + int drvidx; + + /* + * up till binding we ask the protocol layer to reserve as much + * as we might need for HL layer + */ + + for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) + if (dev->drv[drvidx]) + if (max_hlhdr_len < dev->drv[drvidx]->interface->hl_hdrlen) + max_hlhdr_len = dev->drv[drvidx]->interface->hl_hdrlen; + + ndev->hard_header_len = ETH_HLEN + max_hlhdr_len; + return 0; +} + +static void +isdn_net_swapbind(int drvidx) +{ + isdn_net_dev *p; + +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: swapping ch of %d\n", drvidx); +#endif + p = dev->netdev; + while (p) { + if (p->local->pre_device == drvidx) + switch (p->local->pre_channel) { + case 0: + p->local->pre_channel = 1; + break; + case 1: + p->local->pre_channel = 0; + break; + } + p = (isdn_net_dev *) p->next; + } +} + +static void +isdn_net_swap_usage(int i1, int i2) +{ + int u1 = dev->usage[i1] & ISDN_USAGE_EXCLUSIVE; + int u2 = dev->usage[i2] & ISDN_USAGE_EXCLUSIVE; + +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: usage of %d and %d\n", i1, i2); +#endif + dev->usage[i1] &= ~ISDN_USAGE_EXCLUSIVE; + dev->usage[i1] |= u2; + dev->usage[i2] &= ~ISDN_USAGE_EXCLUSIVE; + dev->usage[i2] |= u1; + isdn_info_update(); +} + +/* + * An incoming call-request has arrived. + * Search the interface-chain for an appropriate interface. + * If found, connect the interface to the ISDN-channel and initiate + * D- and B-Channel-setup. If secure-flag is set, accept only + * configured phone-numbers. If callback-flag is set, initiate + * callback-dialing. + * + * Return-Value: 0 = No appropriate interface for this call. + * 1 = Call accepted + * 2 = Reject call, wait cbdelay, then call back + * 3 = Reject call + * 4 = Wait cbdelay, then call back + * 5 = No appropriate interface for this call, + * would eventually match if CID was longer. + */ + +int +isdn_net_find_icall(int di, int ch, int idx, setup_parm *setup) +{ + char *eaz; + int si1; + int si2; + int ematch; + int wret; + int swapped; + int sidx = 0; + u_long flags; + isdn_net_dev *p; + isdn_net_phone *n; + char nr[ISDN_MSNLEN]; + char *my_eaz; + + /* Search name in netdev-chain */ + if (!setup->phone[0]) { + nr[0] = '0'; + nr[1] = '\0'; + printk(KERN_INFO "isdn_net: Incoming call without OAD, assuming '0'\n"); + } else + strlcpy(nr, setup->phone, ISDN_MSNLEN); + si1 = (int) setup->si1; + si2 = (int) setup->si2; + if (!setup->eazmsn[0]) { + printk(KERN_WARNING "isdn_net: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } else + eaz = setup->eazmsn; + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: call from %s,%d,%d -> %s\n", nr, si1, si2, eaz); + /* Accept DATA and VOICE calls at this stage + * local eaz is checked later for allowed call types + */ + if ((si1 != 7) && (si1 != 1)) { + if (dev->net_verbose > 1) + printk(KERN_INFO "isdn_net: Service-Indicator not 1 or 7, ignored\n"); + return 0; + } + n = (isdn_net_phone *) 0; + p = dev->netdev; + ematch = wret = swapped = 0; +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: di=%d ch=%d idx=%d usg=%d\n", di, ch, idx, + dev->usage[idx]); +#endif + while (p) { + int matchret; + isdn_net_local *lp = p->local; + + /* If last check has triggered as binding-swap, revert it */ + switch (swapped) { + case 2: + isdn_net_swap_usage(idx, sidx); + /* fall through */ + case 1: + isdn_net_swapbind(di); + break; + } + swapped = 0; + /* check acceptable call types for DOV */ + my_eaz = isdn_map_eaz2msn(lp->msn, di); + if (si1 == 1) { /* it's a DOV call, check if we allow it */ + if (*my_eaz == 'v' || *my_eaz == 'V' || + *my_eaz == 'b' || *my_eaz == 'B') + my_eaz++; /* skip to allow a match */ + else + my_eaz = NULL; /* force non match */ + } else { /* it's a DATA call, check if we allow it */ + if (*my_eaz == 'b' || *my_eaz == 'B') + my_eaz++; /* skip to allow a match */ + } + if (my_eaz) + matchret = isdn_msncmp(eaz, my_eaz); + else + matchret = 1; + if (!matchret) + ematch = 1; + + /* Remember if more numbers eventually can match */ + if (matchret > wret) + wret = matchret; +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: if='%s', l.msn=%s, l.flags=%d, l.dstate=%d\n", + p->dev->name, lp->msn, lp->flags, lp->dialstate); +#endif + if ((!matchret) && /* EAZ is matching */ + (((!(lp->flags & ISDN_NET_CONNECTED)) && /* but not connected */ + (USG_NONE(dev->usage[idx]))) || /* and ch. unused or */ + ((((lp->dialstate == 4) || (lp->dialstate == 12)) && /* if dialing */ + (!(lp->flags & ISDN_NET_CALLBACK))) /* but no callback */ + ))) + { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match1, pdev=%d pch=%d\n", + lp->pre_device, lp->pre_channel); +#endif + if (dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) { + if ((lp->pre_channel != ch) || + (lp->pre_device != di)) { + /* Here we got a problem: + * If using an ICN-Card, an incoming call is always signaled on + * on the first channel of the card, if both channels are + * down. However this channel may be bound exclusive. If the + * second channel is free, this call should be accepted. + * The solution is horribly but it runs, so what: + * We exchange the exclusive bindings of the two channels, the + * corresponding variables in the interface-structs. + */ + if (ch == 0) { + sidx = isdn_dc2minor(di, 1); +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: ch is 0\n"); +#endif + if (USG_NONE(dev->usage[sidx])) { + /* Second Channel is free, now see if it is bound + * exclusive too. */ + if (dev->usage[sidx] & ISDN_USAGE_EXCLUSIVE) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: 2nd channel is down and bound\n"); +#endif + /* Yes, swap bindings only, if the original + * binding is bound to channel 1 of this driver */ + if ((lp->pre_device == di) && + (lp->pre_channel == 1)) { + isdn_net_swapbind(di); + swapped = 1; + } else { + /* ... else iterate next device */ + p = (isdn_net_dev *) p->next; + continue; + } + } else { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: 2nd channel is down and unbound\n"); +#endif + /* No, swap always and swap excl-usage also */ + isdn_net_swap_usage(idx, sidx); + isdn_net_swapbind(di); + swapped = 2; + } + /* Now check for exclusive binding again */ +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: final check\n"); +#endif + if ((dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) && + ((lp->pre_channel != ch) || + (lp->pre_device != di))) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: final check failed\n"); +#endif + p = (isdn_net_dev *) p->next; + continue; + } + } + } else { + /* We are already on the second channel, so nothing to do */ +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: already on 2nd channel\n"); +#endif + } + } + } +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match2\n"); +#endif + n = lp->phone[0]; + if (lp->flags & ISDN_NET_SECURE) { + while (n) { + if (!isdn_msncmp(nr, n->num)) + break; + n = (isdn_net_phone *) n->next; + } + } + if (n || (!(lp->flags & ISDN_NET_SECURE))) { +#ifdef ISDN_DEBUG_NET_ICALL + printk(KERN_DEBUG "n_fi: match3\n"); +#endif + /* matching interface found */ + + /* + * Is the state STOPPED? + * If so, no dialin is allowed, + * so reject actively. + * */ + if (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF) { + printk(KERN_INFO "incoming call, interface %s `stopped' -> rejected\n", + p->dev->name); + return 3; + } + /* + * Is the interface up? + * If not, reject the call actively. + */ + if (!isdn_net_device_started(p)) { + printk(KERN_INFO "%s: incoming call, interface down -> rejected\n", + p->dev->name); + return 3; + } + /* Interface is up, now see if it's a slave. If so, see if + * it's master and parent slave is online. If not, reject the call. + */ + if (lp->master) { + isdn_net_local *mlp = ISDN_MASTER_PRIV(lp); + printk(KERN_DEBUG "ICALLslv: %s\n", p->dev->name); + printk(KERN_DEBUG "master=%s\n", lp->master->name); + if (mlp->flags & ISDN_NET_CONNECTED) { + printk(KERN_DEBUG "master online\n"); + /* Master is online, find parent-slave (master if first slave) */ + while (mlp->slave) { + if (ISDN_SLAVE_PRIV(mlp) == lp) + break; + mlp = ISDN_SLAVE_PRIV(mlp); + } + } else + printk(KERN_DEBUG "master offline\n"); + /* Found parent, if it's offline iterate next device */ + printk(KERN_DEBUG "mlpf: %d\n", mlp->flags & ISDN_NET_CONNECTED); + if (!(mlp->flags & ISDN_NET_CONNECTED)) { + p = (isdn_net_dev *) p->next; + continue; + } + } + if (lp->flags & ISDN_NET_CALLBACK) { + int chi; + /* + * Is the state MANUAL? + * If so, no callback can be made, + * so reject actively. + * */ + if (ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_OFF) { + printk(KERN_INFO "incoming call for callback, interface %s `off' -> rejected\n", + p->dev->name); + return 3; + } + printk(KERN_DEBUG "%s: call from %s -> %s, start callback\n", + p->dev->name, nr, eaz); + if (lp->phone[1]) { + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((chi = + isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn) + ) < 0) { + + printk(KERN_WARNING "isdn_net_find_icall: No channel for %s\n", + p->dev->name); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + /* Setup dialstate. */ + lp->dtimer = 0; + lp->dialstate = 11; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + if (isdn_ppp_bind(lp) < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_unbind_channel(lp); + return 0; + } +#endif + spin_unlock_irqrestore(&dev->lock, flags); + /* Initiate dialing by returning 2 or 4 */ + return (lp->flags & ISDN_NET_CBHUP) ? 2 : 4; + } else + printk(KERN_WARNING "isdn_net: %s: No phone number\n", + p->dev->name); + return 0; + } else { + printk(KERN_DEBUG "%s: call from %s -> %s accepted\n", + p->dev->name, nr, eaz); + /* if this interface is dialing, it does it probably on a different + device, so free this device */ + if ((lp->dialstate == 4) || (lp->dialstate == 12)) { +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + isdn_ppp_free(lp); +#endif + isdn_net_lp_disconnected(lp); + isdn_free_channel(lp->isdn_device, lp->isdn_channel, + ISDN_USAGE_NET); + } + spin_lock_irqsave(&dev->lock, flags); + dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE; + dev->usage[idx] |= ISDN_USAGE_NET; + strcpy(dev->num[idx], nr); + isdn_info_update(); + dev->st_netdev[idx] = lp->netdev; + lp->isdn_device = di; + lp->isdn_channel = ch; + lp->ppp_slot = -1; + lp->flags |= ISDN_NET_CONNECTED; + lp->dialstate = 7; + lp->dtimer = 0; + lp->outgoing = 0; + lp->huptimer = 0; + lp->hupflags |= ISDN_WAITCHARGE; + lp->hupflags &= ~ISDN_HAVECHARGE; +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) { + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + return 0; + } + } +#endif + spin_unlock_irqrestore(&dev->lock, flags); + return 1; + } + } + } + p = (isdn_net_dev *) p->next; + } + /* If none of configured EAZ/MSN matched and not verbose, be silent */ + if (!ematch || dev->net_verbose) + printk(KERN_INFO "isdn_net: call from %s -> %d %s ignored\n", nr, di, eaz); + return (wret == 2) ? 5 : 0; +} + +/* + * Search list of net-interfaces for an interface with given name. + */ +isdn_net_dev * +isdn_net_findif(char *name) +{ + isdn_net_dev *p = dev->netdev; + + while (p) { + if (!strcmp(p->dev->name, name)) + return p; + p = (isdn_net_dev *) p->next; + } + return (isdn_net_dev *) NULL; +} + +/* + * Force a net-interface to dial out. + * This is called from the userlevel-routine below or + * from isdn_net_start_xmit(). + */ +static int +isdn_net_force_dial_lp(isdn_net_local *lp) +{ + if ((!(lp->flags & ISDN_NET_CONNECTED)) && !lp->dialstate) { + int chi; + if (lp->phone[1]) { + ulong flags; + + /* Grab a free ISDN-Channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((chi = isdn_get_free_channel( + ISDN_USAGE_NET, + lp->l2_proto, + lp->l3_proto, + lp->pre_device, + lp->pre_channel, + lp->msn)) < 0) { + printk(KERN_WARNING "isdn_net_force_dial: No channel for %s\n", + lp->netdev->dev->name); + spin_unlock_irqrestore(&dev->lock, flags); + return -EAGAIN; + } + lp->dialstate = 1; + /* Connect interface with channel */ + isdn_net_bind_channel(lp, chi); +#ifdef CONFIG_ISDN_PPP + if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP) + if (isdn_ppp_bind(lp) < 0) { + isdn_net_unbind_channel(lp); + spin_unlock_irqrestore(&dev->lock, flags); + return -EAGAIN; + } +#endif + /* Initiate dialing */ + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_dial(); + return 0; + } else + return -EINVAL; + } else + return -EBUSY; +} + +/* + * This is called from certain upper protocol layers (multilink ppp + * and x25iface encapsulation module) that want to initiate dialing + * themselves. + */ +int +isdn_net_dial_req(isdn_net_local *lp) +{ + /* is there a better error code? */ + if (!(ISDN_NET_DIALMODE(*lp) == ISDN_NET_DM_AUTO)) return -EBUSY; + + return isdn_net_force_dial_lp(lp); +} + +/* + * Force a net-interface to dial out. + * This is always called from within userspace (ISDN_IOCTL_NET_DIAL). + */ +int +isdn_net_force_dial(char *name) +{ + isdn_net_dev *p = isdn_net_findif(name); + + if (!p) + return -ENODEV; + return (isdn_net_force_dial_lp(p->local)); +} + +/* The ISDN-specific entries in the device structure. */ +static const struct net_device_ops isdn_netdev_ops = { + .ndo_init = isdn_net_init, + .ndo_open = isdn_net_open, + .ndo_stop = isdn_net_close, + .ndo_do_ioctl = isdn_net_ioctl, + + .ndo_start_xmit = isdn_net_start_xmit, + .ndo_get_stats = isdn_net_get_stats, + .ndo_tx_timeout = isdn_net_tx_timeout, +}; + +/* + * Helper for alloc_netdev() + */ +static void _isdn_setup(struct net_device *dev) +{ + isdn_net_local *lp = netdev_priv(dev); + + ether_setup(dev); + + /* Setup the generic properties */ + dev->flags = IFF_NOARP | IFF_POINTOPOINT; + + /* isdn prepends a header in the tx path, can't share skbs */ + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + dev->header_ops = NULL; + dev->netdev_ops = &isdn_netdev_ops; + + /* for clients with MPPP maybe higher values better */ + dev->tx_queue_len = 30; + + lp->p_encap = ISDN_NET_ENCAP_RAWIP; + lp->magic = ISDN_NET_MAGIC; + lp->last = lp; + lp->next = lp; + lp->isdn_device = -1; + lp->isdn_channel = -1; + lp->pre_device = -1; + lp->pre_channel = -1; + lp->exclusive = -1; + lp->ppp_slot = -1; + lp->pppbind = -1; + skb_queue_head_init(&lp->super_tx_queue); + lp->l2_proto = ISDN_PROTO_L2_X75I; + lp->l3_proto = ISDN_PROTO_L3_TRANS; + lp->triggercps = 6000; + lp->slavedelay = 10 * HZ; + lp->hupflags = ISDN_INHUP; /* Do hangup even on incoming calls */ + lp->onhtime = 10; /* Default hangup-time for saving costs */ + lp->dialmax = 1; + /* Hangup before Callback, manual dial */ + lp->flags = ISDN_NET_CBHUP | ISDN_NET_DM_MANUAL; + lp->cbdelay = 25; /* Wait 5 secs before Callback */ + lp->dialtimeout = -1; /* Infinite Dial-Timeout */ + lp->dialwait = 5 * HZ; /* Wait 5 sec. after failed dial */ + lp->dialstarted = 0; /* Jiffies of last dial-start */ + lp->dialwait_timer = 0; /* Jiffies of earliest next dial-start */ +} + +/* + * Allocate a new network-interface and initialize its data structures. + */ +char * +isdn_net_new(char *name, struct net_device *master) +{ + isdn_net_dev *netdev; + + /* Avoid creating an existing interface */ + if (isdn_net_findif(name)) { + printk(KERN_WARNING "isdn_net: interface %s already exists\n", name); + return NULL; + } + if (name == NULL) + return NULL; + if (!(netdev = kzalloc(sizeof(isdn_net_dev), GFP_KERNEL))) { + printk(KERN_WARNING "isdn_net: Could not allocate net-device\n"); + return NULL; + } + netdev->dev = alloc_netdev(sizeof(isdn_net_local), name, + NET_NAME_UNKNOWN, _isdn_setup); + if (!netdev->dev) { + printk(KERN_WARNING "isdn_net: Could not allocate network device\n"); + kfree(netdev); + return NULL; + } + netdev->local = netdev_priv(netdev->dev); + + if (master) { + /* Device shall be a slave */ + struct net_device *p = MASTER_TO_SLAVE(master); + struct net_device *q = master; + + netdev->local->master = master; + /* Put device at end of slave-chain */ + while (p) { + q = p; + p = MASTER_TO_SLAVE(p); + } + MASTER_TO_SLAVE(q) = netdev->dev; + } else { + /* Device shall be a master */ + /* + * Watchdog timer (currently) for master only. + */ + netdev->dev->watchdog_timeo = ISDN_NET_TX_TIMEOUT; + if (register_netdev(netdev->dev) != 0) { + printk(KERN_WARNING "isdn_net: Could not register net-device\n"); + free_netdev(netdev->dev); + kfree(netdev); + return NULL; + } + } + netdev->queue = netdev->local; + spin_lock_init(&netdev->queue_lock); + + netdev->local->netdev = netdev; + + INIT_WORK(&netdev->local->tqueue, isdn_net_softint); + spin_lock_init(&netdev->local->xmit_lock); + + /* Put into to netdev-chain */ + netdev->next = (void *) dev->netdev; + dev->netdev = netdev; + return netdev->dev->name; +} + +char * +isdn_net_newslave(char *parm) +{ + char *p = strchr(parm, ','); + isdn_net_dev *n; + char newname[10]; + + if (p) { + /* Slave-Name MUST not be empty */ + if (!strlen(p + 1)) + return NULL; + strcpy(newname, p + 1); + *p = 0; + /* Master must already exist */ + if (!(n = isdn_net_findif(parm))) + return NULL; + /* Master must be a real interface, not a slave */ + if (n->local->master) + return NULL; + /* Master must not be started yet */ + if (isdn_net_device_started(n)) + return NULL; + return (isdn_net_new(newname, n->dev)); + } + return NULL; +} + +/* + * Set interface-parameters. + * Always set all parameters, so the user-level application is responsible + * for not overwriting existing setups. It has to get the current + * setup first, if only selected parameters are to be changed. + */ +int +isdn_net_setcfg(isdn_net_ioctl_cfg *cfg) +{ + isdn_net_dev *p = isdn_net_findif(cfg->name); + ulong features; + int i; + int drvidx; + int chidx; + char drvid[25]; + + if (p) { + isdn_net_local *lp = p->local; + + /* See if any registered driver supports the features we want */ + features = ((1 << cfg->l2_proto) << ISDN_FEATURE_L2_SHIFT) | + ((1 << cfg->l3_proto) << ISDN_FEATURE_L3_SHIFT); + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + if (dev->drv[i]) + if ((dev->drv[i]->interface->features & features) == features) + break; + if (i == ISDN_MAX_DRIVERS) { + printk(KERN_WARNING "isdn_net: No driver with selected features\n"); + return -ENODEV; + } + if (lp->p_encap != cfg->p_encap) { +#ifdef CONFIG_ISDN_X25 + struct concap_proto *cprot = p->cprot; +#endif + if (isdn_net_device_started(p)) { + printk(KERN_WARNING "%s: cannot change encap when if is up\n", + p->dev->name); + return -EBUSY; + } +#ifdef CONFIG_ISDN_X25 + if (cprot && cprot->pops) + cprot->pops->proto_del(cprot); + p->cprot = NULL; + lp->dops = NULL; + /* ... , prepare for configuration of new one ... */ + switch (cfg->p_encap) { + case ISDN_NET_ENCAP_X25IFACE: + lp->dops = &isdn_concap_reliable_dl_dops; + } + /* ... and allocate new one ... */ + p->cprot = isdn_concap_new(cfg->p_encap); + /* p -> cprot == NULL now if p_encap is not supported + by means of the concap_proto mechanism */ + /* the protocol is not configured yet; this will + happen later when isdn_net_reset() is called */ +#endif + } + switch (cfg->p_encap) { + case ISDN_NET_ENCAP_SYNCPPP: +#ifndef CONFIG_ISDN_PPP + printk(KERN_WARNING "%s: SyncPPP support not configured\n", + p->dev->name); + return -EINVAL; +#else + p->dev->type = ARPHRD_PPP; /* change ARP type */ + p->dev->addr_len = 0; +#endif + break; + case ISDN_NET_ENCAP_X25IFACE: +#ifndef CONFIG_ISDN_X25 + printk(KERN_WARNING "%s: isdn-x25 support not configured\n", + p->dev->name); + return -EINVAL; +#else + p->dev->type = ARPHRD_X25; /* change ARP type */ + p->dev->addr_len = 0; +#endif + break; + case ISDN_NET_ENCAP_CISCOHDLCK: + break; + default: + if (cfg->p_encap >= 0 && + cfg->p_encap <= ISDN_NET_ENCAP_MAX_ENCAP) + break; + printk(KERN_WARNING + "%s: encapsulation protocol %d not supported\n", + p->dev->name, cfg->p_encap); + return -EINVAL; + } + if (strlen(cfg->drvid)) { + /* A bind has been requested ... */ + char *c, + *e; + + if (strnlen(cfg->drvid, sizeof(cfg->drvid)) == + sizeof(cfg->drvid)) + return -EINVAL; + drvidx = -1; + chidx = -1; + strcpy(drvid, cfg->drvid); + if ((c = strchr(drvid, ','))) { + /* The channel-number is appended to the driver-Id with a comma */ + chidx = (int) simple_strtoul(c + 1, &e, 10); + if (e == c) + chidx = -1; + *c = '\0'; + } + for (i = 0; i < ISDN_MAX_DRIVERS; i++) + /* Lookup driver-Id in array */ + if (!(strcmp(dev->drvid[i], drvid))) { + drvidx = i; + break; + } + if ((drvidx == -1) || (chidx == -1)) + /* Either driver-Id or channel-number invalid */ + return -ENODEV; + } else { + /* Parameters are valid, so get them */ + drvidx = lp->pre_device; + chidx = lp->pre_channel; + } + if (cfg->exclusive > 0) { + unsigned long flags; + + /* If binding is exclusive, try to grab the channel */ + spin_lock_irqsave(&dev->lock, flags); + if ((i = isdn_get_free_channel(ISDN_USAGE_NET, + lp->l2_proto, lp->l3_proto, drvidx, + chidx, lp->msn)) < 0) { + /* Grab failed, because desired channel is in use */ + lp->exclusive = -1; + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + /* All went ok, so update isdninfo */ + dev->usage[i] = ISDN_USAGE_EXCLUSIVE; + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + lp->exclusive = i; + } else { + /* Non-exclusive binding or unbind. */ + lp->exclusive = -1; + if ((lp->pre_device != -1) && (cfg->exclusive == -1)) { + isdn_unexclusive_channel(lp->pre_device, lp->pre_channel); + isdn_free_channel(lp->pre_device, lp->pre_channel, ISDN_USAGE_NET); + drvidx = -1; + chidx = -1; + } + } + strlcpy(lp->msn, cfg->eaz, sizeof(lp->msn)); + lp->pre_device = drvidx; + lp->pre_channel = chidx; + lp->onhtime = cfg->onhtime; + lp->charge = cfg->charge; + lp->l2_proto = cfg->l2_proto; + lp->l3_proto = cfg->l3_proto; + lp->cbdelay = cfg->cbdelay; + lp->dialmax = cfg->dialmax; + lp->triggercps = cfg->triggercps; + lp->slavedelay = cfg->slavedelay * HZ; + lp->pppbind = cfg->pppbind; + lp->dialtimeout = cfg->dialtimeout >= 0 ? cfg->dialtimeout * HZ : -1; + lp->dialwait = cfg->dialwait * HZ; + if (cfg->secure) + lp->flags |= ISDN_NET_SECURE; + else + lp->flags &= ~ISDN_NET_SECURE; + if (cfg->cbhup) + lp->flags |= ISDN_NET_CBHUP; + else + lp->flags &= ~ISDN_NET_CBHUP; + switch (cfg->callback) { + case 0: + lp->flags &= ~(ISDN_NET_CALLBACK | ISDN_NET_CBOUT); + break; + case 1: + lp->flags |= ISDN_NET_CALLBACK; + lp->flags &= ~ISDN_NET_CBOUT; + break; + case 2: + lp->flags |= ISDN_NET_CBOUT; + lp->flags &= ~ISDN_NET_CALLBACK; + break; + } + lp->flags &= ~ISDN_NET_DIALMODE_MASK; /* first all bits off */ + if (cfg->dialmode && !(cfg->dialmode & ISDN_NET_DIALMODE_MASK)) { + /* old isdnctrl version, where only 0 or 1 is given */ + printk(KERN_WARNING + "Old isdnctrl version detected! Please update.\n"); + lp->flags |= ISDN_NET_DM_OFF; /* turn on `off' bit */ + } + else { + lp->flags |= cfg->dialmode; /* turn on selected bits */ + } + if (cfg->chargehup) + lp->hupflags |= ISDN_CHARGEHUP; + else + lp->hupflags &= ~ISDN_CHARGEHUP; + if (cfg->ihup) + lp->hupflags |= ISDN_INHUP; + else + lp->hupflags &= ~ISDN_INHUP; + if (cfg->chargeint > 10) { + lp->hupflags |= ISDN_CHARGEHUP | ISDN_HAVECHARGE | ISDN_MANCHARGE; + lp->chargeint = cfg->chargeint * HZ; + } + if (cfg->p_encap != lp->p_encap) { + if (cfg->p_encap == ISDN_NET_ENCAP_RAWIP) { + p->dev->header_ops = NULL; + p->dev->flags = IFF_NOARP | IFF_POINTOPOINT; + } else { + p->dev->header_ops = &isdn_header_ops; + if (cfg->p_encap == ISDN_NET_ENCAP_ETHER) + p->dev->flags = IFF_BROADCAST | IFF_MULTICAST; + else + p->dev->flags = IFF_NOARP | IFF_POINTOPOINT; + } + } + lp->p_encap = cfg->p_encap; + return 0; + } + return -ENODEV; +} + +/* + * Perform get-interface-parameters.ioctl + */ +int +isdn_net_getcfg(isdn_net_ioctl_cfg *cfg) +{ + isdn_net_dev *p = isdn_net_findif(cfg->name); + + if (p) { + isdn_net_local *lp = p->local; + + strcpy(cfg->eaz, lp->msn); + cfg->exclusive = lp->exclusive; + if (lp->pre_device >= 0) { + sprintf(cfg->drvid, "%s,%d", dev->drvid[lp->pre_device], + lp->pre_channel); + } else + cfg->drvid[0] = '\0'; + cfg->onhtime = lp->onhtime; + cfg->charge = lp->charge; + cfg->l2_proto = lp->l2_proto; + cfg->l3_proto = lp->l3_proto; + cfg->p_encap = lp->p_encap; + cfg->secure = (lp->flags & ISDN_NET_SECURE) ? 1 : 0; + cfg->callback = 0; + if (lp->flags & ISDN_NET_CALLBACK) + cfg->callback = 1; + if (lp->flags & ISDN_NET_CBOUT) + cfg->callback = 2; + cfg->cbhup = (lp->flags & ISDN_NET_CBHUP) ? 1 : 0; + cfg->dialmode = lp->flags & ISDN_NET_DIALMODE_MASK; + cfg->chargehup = (lp->hupflags & ISDN_CHARGEHUP) ? 1 : 0; + cfg->ihup = (lp->hupflags & ISDN_INHUP) ? 1 : 0; + cfg->cbdelay = lp->cbdelay; + cfg->dialmax = lp->dialmax; + cfg->triggercps = lp->triggercps; + cfg->slavedelay = lp->slavedelay / HZ; + cfg->chargeint = (lp->hupflags & ISDN_CHARGEHUP) ? + (lp->chargeint / HZ) : 0; + cfg->pppbind = lp->pppbind; + cfg->dialtimeout = lp->dialtimeout >= 0 ? lp->dialtimeout / HZ : -1; + cfg->dialwait = lp->dialwait / HZ; + if (lp->slave) { + if (strlen(lp->slave->name) >= 10) + strcpy(cfg->slave, "too-long"); + else + strcpy(cfg->slave, lp->slave->name); + } else + cfg->slave[0] = '\0'; + if (lp->master) { + if (strlen(lp->master->name) >= 10) + strcpy(cfg->master, "too-long"); + else + strcpy(cfg->master, lp->master->name); + } else + cfg->master[0] = '\0'; + return 0; + } + return -ENODEV; +} + +/* + * Add a phone-number to an interface. + */ +int +isdn_net_addphone(isdn_net_ioctl_phone *phone) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + isdn_net_phone *n; + + if (p) { + if (!(n = kmalloc(sizeof(isdn_net_phone), GFP_KERNEL))) + return -ENOMEM; + strlcpy(n->num, phone->phone, sizeof(n->num)); + n->next = p->local->phone[phone->outgoing & 1]; + p->local->phone[phone->outgoing & 1] = n; + return 0; + } + return -ENODEV; +} + +/* + * Copy a string of all phone-numbers of an interface to user space. + * This might sleep and must be called with the isdn semaphore down. + */ +int +isdn_net_getphones(isdn_net_ioctl_phone *phone, char __user *phones) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int inout = phone->outgoing & 1; + int more = 0; + int count = 0; + isdn_net_phone *n; + + if (!p) + return -ENODEV; + inout &= 1; + for (n = p->local->phone[inout]; n; n = n->next) { + if (more) { + put_user(' ', phones++); + count++; + } + if (copy_to_user(phones, n->num, strlen(n->num) + 1)) { + return -EFAULT; + } + phones += strlen(n->num); + count += strlen(n->num); + more = 1; + } + put_user(0, phones); + count++; + return count; +} + +/* + * Copy a string containing the peer's phone number of a connected interface + * to user space. + */ +int +isdn_net_getpeer(isdn_net_ioctl_phone *phone, isdn_net_ioctl_phone __user *peer) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int ch, dv, idx; + + if (!p) + return -ENODEV; + /* + * Theoretical race: while this executes, the remote number might + * become invalid (hang up) or change (new connection), resulting + * in (partially) wrong number copied to user. This race + * currently ignored. + */ + ch = p->local->isdn_channel; + dv = p->local->isdn_device; + if (ch < 0 && dv < 0) + return -ENOTCONN; + idx = isdn_dc2minor(dv, ch); + if (idx < 0) + return -ENODEV; + /* for pre-bound channels, we need this extra check */ + if (strncmp(dev->num[idx], "???", 3) == 0) + return -ENOTCONN; + strncpy(phone->phone, dev->num[idx], ISDN_MSNLEN); + phone->outgoing = USG_OUTGOING(dev->usage[idx]); + if (copy_to_user(peer, phone, sizeof(*peer))) + return -EFAULT; + return 0; +} +/* + * Delete a phone-number from an interface. + */ +int +isdn_net_delphone(isdn_net_ioctl_phone *phone) +{ + isdn_net_dev *p = isdn_net_findif(phone->name); + int inout = phone->outgoing & 1; + isdn_net_phone *n; + isdn_net_phone *m; + + if (p) { + n = p->local->phone[inout]; + m = NULL; + while (n) { + if (!strcmp(n->num, phone->phone)) { + if (p->local->dial == n) + p->local->dial = n->next; + if (m) + m->next = n->next; + else + p->local->phone[inout] = n->next; + kfree(n); + return 0; + } + m = n; + n = (isdn_net_phone *) n->next; + } + return -EINVAL; + } + return -ENODEV; +} + +/* + * Delete all phone-numbers of an interface. + */ +static int +isdn_net_rmallphone(isdn_net_dev *p) +{ + isdn_net_phone *n; + isdn_net_phone *m; + int i; + + for (i = 0; i < 2; i++) { + n = p->local->phone[i]; + while (n) { + m = n->next; + kfree(n); + n = m; + } + p->local->phone[i] = NULL; + } + p->local->dial = NULL; + return 0; +} + +/* + * Force a hangup of a network-interface. + */ +int +isdn_net_force_hangup(char *name) +{ + isdn_net_dev *p = isdn_net_findif(name); + struct net_device *q; + + if (p) { + if (p->local->isdn_device < 0) + return 1; + q = p->local->slave; + /* If this interface has slaves, do a hangup for them also. */ + while (q) { + isdn_net_hangup(q); + q = MASTER_TO_SLAVE(q); + } + isdn_net_hangup(p->dev); + return 0; + } + return -ENODEV; +} + +/* + * Helper-function for isdn_net_rm: Do the real work. + */ +static int +isdn_net_realrm(isdn_net_dev *p, isdn_net_dev *q) +{ + u_long flags; + + if (isdn_net_device_started(p)) { + return -EBUSY; + } +#ifdef CONFIG_ISDN_X25 + if (p->cprot && p->cprot->pops) + p->cprot->pops->proto_del(p->cprot); +#endif + /* Free all phone-entries */ + isdn_net_rmallphone(p); + /* If interface is bound exclusive, free channel-usage */ + if (p->local->exclusive != -1) + isdn_unexclusive_channel(p->local->pre_device, p->local->pre_channel); + if (p->local->master) { + /* It's a slave-device, so update master's slave-pointer if necessary */ + if (((isdn_net_local *) ISDN_MASTER_PRIV(p->local))->slave == + p->dev) + ((isdn_net_local *)ISDN_MASTER_PRIV(p->local))->slave = + p->local->slave; + } else { + /* Unregister only if it's a master-device */ + unregister_netdev(p->dev); + } + /* Unlink device from chain */ + spin_lock_irqsave(&dev->lock, flags); + if (q) + q->next = p->next; + else + dev->netdev = p->next; + if (p->local->slave) { + /* If this interface has a slave, remove it also */ + char *slavename = p->local->slave->name; + isdn_net_dev *n = dev->netdev; + q = NULL; + while (n) { + if (!strcmp(n->dev->name, slavename)) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_net_realrm(n, q); + spin_lock_irqsave(&dev->lock, flags); + break; + } + q = n; + n = (isdn_net_dev *)n->next; + } + } + spin_unlock_irqrestore(&dev->lock, flags); + /* If no more net-devices remain, disable auto-hangup timer */ + if (dev->netdev == NULL) + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 0); + free_netdev(p->dev); + kfree(p); + + return 0; +} + +/* + * Remove a single network-interface. + */ +int +isdn_net_rm(char *name) +{ + u_long flags; + isdn_net_dev *p; + isdn_net_dev *q; + + /* Search name in netdev-chain */ + spin_lock_irqsave(&dev->lock, flags); + p = dev->netdev; + q = NULL; + while (p) { + if (!strcmp(p->dev->name, name)) { + spin_unlock_irqrestore(&dev->lock, flags); + return (isdn_net_realrm(p, q)); + } + q = p; + p = (isdn_net_dev *) p->next; + } + spin_unlock_irqrestore(&dev->lock, flags); + /* If no more net-devices remain, disable auto-hangup timer */ + if (dev->netdev == NULL) + isdn_timer_ctrl(ISDN_TIMER_NETHANGUP, 0); + return -ENODEV; +} + +/* + * Remove all network-interfaces + */ +int +isdn_net_rmall(void) +{ + u_long flags; + int ret; + + /* Walk through netdev-chain */ + spin_lock_irqsave(&dev->lock, flags); + while (dev->netdev) { + if (!dev->netdev->local->master) { + /* Remove master-devices only, slaves get removed with their master */ + spin_unlock_irqrestore(&dev->lock, flags); + if ((ret = isdn_net_realrm(dev->netdev, NULL))) { + return ret; + } + spin_lock_irqsave(&dev->lock, flags); + } + } + dev->netdev = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + return 0; +} diff --git a/kernel/drivers/isdn/i4l/isdn_net.h b/kernel/drivers/isdn/i4l/isdn_net.h new file mode 100644 index 000000000..cca6d68da --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_net.h @@ -0,0 +1,151 @@ +/* $Id: isdn_net.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, network related functions (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +/* Definitions for hupflags: */ +#define ISDN_WAITCHARGE 1 /* did not get a charge info yet */ +#define ISDN_HAVECHARGE 2 /* We know a charge info */ +#define ISDN_CHARGEHUP 4 /* We want to use the charge mechanism */ +#define ISDN_INHUP 8 /* Even if incoming, close after huptimeout */ +#define ISDN_MANCHARGE 16 /* Charge Interval manually set */ + +/* + * Definitions for Cisco-HDLC header. + */ + +#define CISCO_ADDR_UNICAST 0x0f +#define CISCO_ADDR_BROADCAST 0x8f +#define CISCO_CTRL 0x00 +#define CISCO_TYPE_CDP 0x2000 +#define CISCO_TYPE_SLARP 0x8035 +#define CISCO_SLARP_REQUEST 0 +#define CISCO_SLARP_REPLY 1 +#define CISCO_SLARP_KEEPALIVE 2 + +extern char *isdn_net_new(char *, struct net_device *); +extern char *isdn_net_newslave(char *); +extern int isdn_net_rm(char *); +extern int isdn_net_rmall(void); +extern int isdn_net_stat_callback(int, isdn_ctrl *); +extern int isdn_net_setcfg(isdn_net_ioctl_cfg *); +extern int isdn_net_getcfg(isdn_net_ioctl_cfg *); +extern int isdn_net_addphone(isdn_net_ioctl_phone *); +extern int isdn_net_getphones(isdn_net_ioctl_phone *, char __user *); +extern int isdn_net_getpeer(isdn_net_ioctl_phone *, isdn_net_ioctl_phone __user *); +extern int isdn_net_delphone(isdn_net_ioctl_phone *); +extern int isdn_net_find_icall(int, int, int, setup_parm *); +extern void isdn_net_hangup(struct net_device *); +extern void isdn_net_dial(void); +extern void isdn_net_autohup(void); +extern int isdn_net_force_hangup(char *); +extern int isdn_net_force_dial(char *); +extern isdn_net_dev *isdn_net_findif(char *); +extern int isdn_net_rcv_skb(int, struct sk_buff *); +extern int isdn_net_dial_req(isdn_net_local *); +extern void isdn_net_writebuf_skb(isdn_net_local *lp, struct sk_buff *skb); +extern void isdn_net_write_super(isdn_net_local *lp, struct sk_buff *skb); + +#define ISDN_NET_MAX_QUEUE_LENGTH 2 + +#define ISDN_MASTER_PRIV(lp) ((isdn_net_local *) netdev_priv(lp->master)) +#define ISDN_SLAVE_PRIV(lp) ((isdn_net_local *) netdev_priv(lp->slave)) +#define MASTER_TO_SLAVE(master) \ + (((isdn_net_local *) netdev_priv(master))->slave) + +/* + * is this particular channel busy? + */ +static __inline__ int isdn_net_lp_busy(isdn_net_local *lp) +{ + if (atomic_read(&lp->frame_cnt) < ISDN_NET_MAX_QUEUE_LENGTH) + return 0; + else + return 1; +} + +/* + * For the given net device, this will get a non-busy channel out of the + * corresponding bundle. The returned channel is locked. + */ +static __inline__ isdn_net_local *isdn_net_get_locked_lp(isdn_net_dev *nd) +{ + unsigned long flags; + isdn_net_local *lp; + + spin_lock_irqsave(&nd->queue_lock, flags); + lp = nd->queue; /* get lp on top of queue */ + while (isdn_net_lp_busy(nd->queue)) { + nd->queue = nd->queue->next; + if (nd->queue == lp) { /* not found -- should never happen */ + lp = NULL; + goto errout; + } + } + lp = nd->queue; + nd->queue = nd->queue->next; + spin_unlock_irqrestore(&nd->queue_lock, flags); + spin_lock(&lp->xmit_lock); + local_bh_disable(); + return lp; +errout: + spin_unlock_irqrestore(&nd->queue_lock, flags); + return lp; +} + +/* + * add a channel to a bundle + */ +static __inline__ void isdn_net_add_to_bundle(isdn_net_dev *nd, isdn_net_local *nlp) +{ + isdn_net_local *lp; + unsigned long flags; + + spin_lock_irqsave(&nd->queue_lock, flags); + + lp = nd->queue; +// printk(KERN_DEBUG "%s: lp:%s(%p) nlp:%s(%p) last(%p)\n", +// __func__, lp->name, lp, nlp->name, nlp, lp->last); + nlp->last = lp->last; + lp->last->next = nlp; + lp->last = nlp; + nlp->next = lp; + nd->queue = nlp; + + spin_unlock_irqrestore(&nd->queue_lock, flags); +} +/* + * remove a channel from the bundle it belongs to + */ +static __inline__ void isdn_net_rm_from_bundle(isdn_net_local *lp) +{ + isdn_net_local *master_lp = lp; + unsigned long flags; + + if (lp->master) + master_lp = ISDN_MASTER_PRIV(lp); + +// printk(KERN_DEBUG "%s: lp:%s(%p) mlp:%s(%p) last(%p) next(%p) mndq(%p)\n", +// __func__, lp->name, lp, master_lp->name, master_lp, lp->last, lp->next, master_lp->netdev->queue); + spin_lock_irqsave(&master_lp->netdev->queue_lock, flags); + lp->last->next = lp->next; + lp->next->last = lp->last; + if (master_lp->netdev->queue == lp) { + master_lp->netdev->queue = lp->next; + if (lp->next == lp) { /* last in queue */ + master_lp->netdev->queue = master_lp->netdev->local; + } + } + lp->next = lp->last = lp; /* (re)set own pointers */ +// printk(KERN_DEBUG "%s: mndq(%p)\n", +// __func__, master_lp->netdev->queue); + spin_unlock_irqrestore(&master_lp->netdev->queue_lock, flags); +} diff --git a/kernel/drivers/isdn/i4l/isdn_ppp.c b/kernel/drivers/isdn/i4l/isdn_ppp.c new file mode 100644 index 000000000..c4198fa49 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_ppp.c @@ -0,0 +1,3037 @@ +/* $Id: isdn_ppp.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $ + * + * Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/isdn.h> +#include <linux/poll.h> +#include <linux/ppp-comp.h> +#include <linux/slab.h> +#ifdef CONFIG_IPPP_FILTER +#include <linux/filter.h> +#endif + +#include "isdn_common.h" +#include "isdn_ppp.h" +#include "isdn_net.h" + +#ifndef PPP_IPX +#define PPP_IPX 0x002b +#endif + +/* Prototypes */ +static int isdn_ppp_fill_rq(unsigned char *buf, int len, int proto, int slot); +static int isdn_ppp_closewait(int slot); +static void isdn_ppp_push_higher(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb, int proto); +static int isdn_ppp_if_get_unit(char *namebuf); +static int isdn_ppp_set_compressor(struct ippp_struct *is, struct isdn_ppp_comp_data *); +static struct sk_buff *isdn_ppp_decompress(struct sk_buff *, + struct ippp_struct *, struct ippp_struct *, int *proto); +static void isdn_ppp_receive_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb, int proto); +static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in, int *proto, + struct ippp_struct *is, struct ippp_struct *master, int type); +static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb); + +/* New CCP stuff */ +static void isdn_ppp_ccp_kickup(struct ippp_struct *is); +static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto, + unsigned char code, unsigned char id, + unsigned char *data, int len); +static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is); +static void isdn_ppp_ccp_reset_free(struct ippp_struct *is); +static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is, + unsigned char id); +static void isdn_ppp_ccp_timer_callback(unsigned long closure); +static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is, + unsigned char id); +static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is, + struct isdn_ppp_resetparams *rp); +static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is, + unsigned char id); + + + +#ifdef CONFIG_ISDN_MPP +static ippp_bundle *isdn_ppp_bundle_arr = NULL; + +static int isdn_ppp_mp_bundle_array_init(void); +static int isdn_ppp_mp_init(isdn_net_local *lp, ippp_bundle *add_to); +static void isdn_ppp_mp_receive(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb); +static void isdn_ppp_mp_cleanup(isdn_net_local *lp); + +static int isdn_ppp_bundle(struct ippp_struct *, int unit); +#endif /* CONFIG_ISDN_MPP */ + +char *isdn_ppp_revision = "$Revision: 1.1.2.3 $"; + +static struct ippp_struct *ippp_table[ISDN_MAX_CHANNELS]; + +static struct isdn_ppp_compressor *ipc_head = NULL; + +/* + * frame log (debug) + */ +static void +isdn_ppp_frame_log(char *info, char *data, int len, int maxlen, int unit, int slot) +{ + int cnt, + j, + i; + char buf[80]; + + if (len < maxlen) + maxlen = len; + + for (i = 0, cnt = 0; cnt < maxlen; i++) { + for (j = 0; j < 16 && cnt < maxlen; j++, cnt++) + sprintf(buf + j * 3, "%02x ", (unsigned char)data[cnt]); + printk(KERN_DEBUG "[%d/%d].%s[%d]: %s\n", unit, slot, info, i, buf); + } +} + +/* + * unbind isdn_net_local <=> ippp-device + * note: it can happen, that we hangup/free the master before the slaves + * in this case we bind another lp to the master device + */ +int +isdn_ppp_free(isdn_net_local *lp) +{ + struct ippp_struct *is; + + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) out of range\n", + __func__, lp->ppp_slot); + return 0; + } + +#ifdef CONFIG_ISDN_MPP + spin_lock(&lp->netdev->pb->lock); +#endif + isdn_net_rm_from_bundle(lp); +#ifdef CONFIG_ISDN_MPP + if (lp->netdev->pb->ref_ct == 1) /* last link in queue? */ + isdn_ppp_mp_cleanup(lp); + + lp->netdev->pb->ref_ct--; + spin_unlock(&lp->netdev->pb->lock); +#endif /* CONFIG_ISDN_MPP */ + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) now invalid\n", + __func__, lp->ppp_slot); + return 0; + } + is = ippp_table[lp->ppp_slot]; + if ((is->state & IPPP_CONNECT)) + isdn_ppp_closewait(lp->ppp_slot); /* force wakeup on ippp device */ + else if (is->state & IPPP_ASSIGNED) + is->state = IPPP_OPEN; /* fallback to 'OPEN but not ASSIGNED' state */ + + if (is->debug & 0x1) + printk(KERN_DEBUG "isdn_ppp_free %d %lx %lx\n", lp->ppp_slot, (long) lp, (long) is->lp); + + is->lp = NULL; /* link is down .. set lp to NULL */ + lp->ppp_slot = -1; /* is this OK ?? */ + + return 0; +} + +/* + * bind isdn_net_local <=> ippp-device + * + * This function is allways called with holding dev->lock so + * no additional lock is needed + */ +int +isdn_ppp_bind(isdn_net_local *lp) +{ + int i; + int unit = 0; + struct ippp_struct *is; + int retval; + + if (lp->pppbind < 0) { /* device bounded to ippp device ? */ + isdn_net_dev *net_dev = dev->netdev; + char exclusive[ISDN_MAX_CHANNELS]; /* exclusive flags */ + memset(exclusive, 0, ISDN_MAX_CHANNELS); + while (net_dev) { /* step through net devices to find exclusive minors */ + isdn_net_local *lp = net_dev->local; + if (lp->pppbind >= 0) + exclusive[lp->pppbind] = 1; + net_dev = net_dev->next; + } + /* + * search a free device / slot + */ + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (ippp_table[i]->state == IPPP_OPEN && !exclusive[ippp_table[i]->minor]) { /* OPEN, but not connected! */ + break; + } + } + } else { + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (ippp_table[i]->minor == lp->pppbind && + (ippp_table[i]->state & IPPP_OPEN) == IPPP_OPEN) + break; + } + } + + if (i >= ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "isdn_ppp_bind: Can't find a (free) connection to the ipppd daemon.\n"); + retval = -1; + goto out; + } + /* get unit number from interface name .. ugly! */ + unit = isdn_ppp_if_get_unit(lp->netdev->dev->name); + if (unit < 0) { + printk(KERN_ERR "isdn_ppp_bind: illegal interface name %s.\n", + lp->netdev->dev->name); + retval = -1; + goto out; + } + + lp->ppp_slot = i; + is = ippp_table[i]; + is->lp = lp; + is->unit = unit; + is->state = IPPP_OPEN | IPPP_ASSIGNED; /* assigned to a netdevice but not connected */ +#ifdef CONFIG_ISDN_MPP + retval = isdn_ppp_mp_init(lp, NULL); + if (retval < 0) + goto out; +#endif /* CONFIG_ISDN_MPP */ + + retval = lp->ppp_slot; + +out: + return retval; +} + +/* + * kick the ipppd on the device + * (wakes up daemon after B-channel connect) + */ + +void +isdn_ppp_wakeup_daemon(isdn_net_local *lp) +{ + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: ppp_slot(%d) out of range\n", + __func__, lp->ppp_slot); + return; + } + ippp_table[lp->ppp_slot]->state = IPPP_OPEN | IPPP_CONNECT | IPPP_NOBLOCK; + wake_up_interruptible(&ippp_table[lp->ppp_slot]->wq); +} + +/* + * there was a hangup on the netdevice + * force wakeup of the ippp device + * go into 'device waits for release' state + */ +static int +isdn_ppp_closewait(int slot) +{ + struct ippp_struct *is; + + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __func__, slot); + return 0; + } + is = ippp_table[slot]; + if (is->state) + wake_up_interruptible(&is->wq); + is->state = IPPP_CLOSEWAIT; + return 1; +} + +/* + * isdn_ppp_find_slot / isdn_ppp_free_slot + */ + +static int +isdn_ppp_get_slot(void) +{ + int i; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (!ippp_table[i]->state) + return i; + } + return -1; +} + +/* + * isdn_ppp_open + */ + +int +isdn_ppp_open(int min, struct file *file) +{ + int slot; + struct ippp_struct *is; + + if (min < 0 || min >= ISDN_MAX_CHANNELS) + return -ENODEV; + + slot = isdn_ppp_get_slot(); + if (slot < 0) { + return -EBUSY; + } + is = file->private_data = ippp_table[slot]; + + printk(KERN_DEBUG "ippp, open, slot: %d, minor: %d, state: %04x\n", + slot, min, is->state); + + /* compression stuff */ + is->link_compressor = is->compressor = NULL; + is->link_decompressor = is->decompressor = NULL; + is->link_comp_stat = is->comp_stat = NULL; + is->link_decomp_stat = is->decomp_stat = NULL; + is->compflags = 0; + + is->reset = isdn_ppp_ccp_reset_alloc(is); + + is->lp = NULL; + is->mp_seqno = 0; /* MP sequence number */ + is->pppcfg = 0; /* ppp configuration */ + is->mpppcfg = 0; /* mppp configuration */ + is->last_link_seqno = -1; /* MP: maybe set to Bundle-MIN, when joining a bundle ?? */ + is->unit = -1; /* set, when we have our interface */ + is->mru = 1524; /* MRU, default 1524 */ + is->maxcid = 16; /* VJ: maxcid */ + is->tk = current; + init_waitqueue_head(&is->wq); + is->first = is->rq + NUM_RCV_BUFFS - 1; /* receive queue */ + is->last = is->rq; + is->minor = min; +#ifdef CONFIG_ISDN_PPP_VJ + /* + * VJ header compression init + */ + is->slcomp = slhc_init(16, 16); /* not necessary for 2. link in bundle */ +#endif +#ifdef CONFIG_IPPP_FILTER + is->pass_filter = NULL; + is->active_filter = NULL; +#endif + is->state = IPPP_OPEN; + + return 0; +} + +/* + * release ippp device + */ +void +isdn_ppp_release(int min, struct file *file) +{ + int i; + struct ippp_struct *is; + + if (min < 0 || min >= ISDN_MAX_CHANNELS) + return; + is = file->private_data; + + if (!is) { + printk(KERN_ERR "%s: no file->private_data\n", __func__); + return; + } + if (is->debug & 0x1) + printk(KERN_DEBUG "ippp: release, minor: %d %lx\n", min, (long) is->lp); + + if (is->lp) { /* a lp address says: this link is still up */ + isdn_net_dev *p = is->lp->netdev; + + if (!p) { + printk(KERN_ERR "%s: no lp->netdev\n", __func__); + return; + } + is->state &= ~IPPP_CONNECT; /* -> effect: no call of wakeup */ + /* + * isdn_net_hangup() calls isdn_ppp_free() + * isdn_ppp_free() sets is->lp to NULL and lp->ppp_slot to -1 + * removing the IPPP_CONNECT flag omits calling of isdn_ppp_wakeup_daemon() + */ + isdn_net_hangup(p->dev); + } + for (i = 0; i < NUM_RCV_BUFFS; i++) { + kfree(is->rq[i].buf); + is->rq[i].buf = NULL; + } + is->first = is->rq + NUM_RCV_BUFFS - 1; /* receive queue */ + is->last = is->rq; + +#ifdef CONFIG_ISDN_PPP_VJ +/* TODO: if this was the previous master: link the slcomp to the new master */ + slhc_free(is->slcomp); + is->slcomp = NULL; +#endif +#ifdef CONFIG_IPPP_FILTER + if (is->pass_filter) { + bpf_prog_destroy(is->pass_filter); + is->pass_filter = NULL; + } + + if (is->active_filter) { + bpf_prog_destroy(is->active_filter); + is->active_filter = NULL; + } +#endif + +/* TODO: if this was the previous master: link the stuff to the new master */ + if (is->comp_stat) + is->compressor->free(is->comp_stat); + if (is->link_comp_stat) + is->link_compressor->free(is->link_comp_stat); + if (is->link_decomp_stat) + is->link_decompressor->free(is->link_decomp_stat); + if (is->decomp_stat) + is->decompressor->free(is->decomp_stat); + is->compressor = is->link_compressor = NULL; + is->decompressor = is->link_decompressor = NULL; + is->comp_stat = is->link_comp_stat = NULL; + is->decomp_stat = is->link_decomp_stat = NULL; + + /* Clean up if necessary */ + if (is->reset) + isdn_ppp_ccp_reset_free(is); + + /* this slot is ready for new connections */ + is->state = 0; +} + +/* + * get_arg .. ioctl helper + */ +static int +get_arg(void __user *b, void *val, int len) +{ + if (len <= 0) + len = sizeof(void *); + if (copy_from_user(val, b, len)) + return -EFAULT; + return 0; +} + +/* + * set arg .. ioctl helper + */ +static int +set_arg(void __user *b, void *val, int len) +{ + if (len <= 0) + len = sizeof(void *); + if (copy_to_user(b, val, len)) + return -EFAULT; + return 0; +} + +#ifdef CONFIG_IPPP_FILTER +static int get_filter(void __user *arg, struct sock_filter **p) +{ + struct sock_fprog uprog; + struct sock_filter *code = NULL; + int len; + + if (copy_from_user(&uprog, arg, sizeof(uprog))) + return -EFAULT; + + if (!uprog.len) { + *p = NULL; + return 0; + } + + /* uprog.len is unsigned short, so no overflow here */ + len = uprog.len * sizeof(struct sock_filter); + code = memdup_user(uprog.filter, len); + if (IS_ERR(code)) + return PTR_ERR(code); + + *p = code; + return uprog.len; +} +#endif /* CONFIG_IPPP_FILTER */ + +/* + * ippp device ioctl + */ +int +isdn_ppp_ioctl(int min, struct file *file, unsigned int cmd, unsigned long arg) +{ + unsigned long val; + int r, i, j; + struct ippp_struct *is; + isdn_net_local *lp; + struct isdn_ppp_comp_data data; + void __user *argp = (void __user *)arg; + + is = file->private_data; + lp = is->lp; + + if (is->debug & 0x1) + printk(KERN_DEBUG "isdn_ppp_ioctl: minor: %d cmd: %x state: %x\n", min, cmd, is->state); + + if (!(is->state & IPPP_OPEN)) + return -EINVAL; + + switch (cmd) { + case PPPIOCBUNDLE: +#ifdef CONFIG_ISDN_MPP + if (!(is->state & IPPP_CONNECT)) + return -EINVAL; + if ((r = get_arg(argp, &val, sizeof(val)))) + return r; + printk(KERN_DEBUG "iPPP-bundle: minor: %d, slave unit: %d, master unit: %d\n", + (int) min, (int) is->unit, (int) val); + return isdn_ppp_bundle(is, val); +#else + return -1; +#endif + break; + case PPPIOCGUNIT: /* get ppp/isdn unit number */ + if ((r = set_arg(argp, &is->unit, sizeof(is->unit)))) + return r; + break; + case PPPIOCGIFNAME: + if (!lp) + return -EINVAL; + if ((r = set_arg(argp, lp->netdev->dev->name, + strlen(lp->netdev->dev->name)))) + return r; + break; + case PPPIOCGMPFLAGS: /* get configuration flags */ + if ((r = set_arg(argp, &is->mpppcfg, sizeof(is->mpppcfg)))) + return r; + break; + case PPPIOCSMPFLAGS: /* set configuration flags */ + if ((r = get_arg(argp, &val, sizeof(val)))) + return r; + is->mpppcfg = val; + break; + case PPPIOCGFLAGS: /* get configuration flags */ + if ((r = set_arg(argp, &is->pppcfg, sizeof(is->pppcfg)))) + return r; + break; + case PPPIOCSFLAGS: /* set configuration flags */ + if ((r = get_arg(argp, &val, sizeof(val)))) { + return r; + } + if (val & SC_ENABLE_IP && !(is->pppcfg & SC_ENABLE_IP) && (is->state & IPPP_CONNECT)) { + if (lp) { + /* OK .. we are ready to send buffers */ + is->pppcfg = val; /* isdn_ppp_xmit test for SC_ENABLE_IP !!! */ + netif_wake_queue(lp->netdev->dev); + break; + } + } + is->pppcfg = val; + break; + case PPPIOCGIDLE: /* get idle time information */ + if (lp) { + struct ppp_idle pidle; + pidle.xmit_idle = pidle.recv_idle = lp->huptimer; + if ((r = set_arg(argp, &pidle, sizeof(struct ppp_idle)))) + return r; + } + break; + case PPPIOCSMRU: /* set receive unit size for PPP */ + if ((r = get_arg(argp, &val, sizeof(val)))) + return r; + is->mru = val; + break; + case PPPIOCSMPMRU: + break; + case PPPIOCSMPMTU: + break; + case PPPIOCSMAXCID: /* set the maximum compression slot id */ + if ((r = get_arg(argp, &val, sizeof(val)))) + return r; + val++; + if (is->maxcid != val) { +#ifdef CONFIG_ISDN_PPP_VJ + struct slcompress *sltmp; +#endif + if (is->debug & 0x1) + printk(KERN_DEBUG "ippp, ioctl: changed MAXCID to %ld\n", val); + is->maxcid = val; +#ifdef CONFIG_ISDN_PPP_VJ + sltmp = slhc_init(16, val); + if (!sltmp) { + printk(KERN_ERR "ippp, can't realloc slhc struct\n"); + return -ENOMEM; + } + if (is->slcomp) + slhc_free(is->slcomp); + is->slcomp = sltmp; +#endif + } + break; + case PPPIOCGDEBUG: + if ((r = set_arg(argp, &is->debug, sizeof(is->debug)))) + return r; + break; + case PPPIOCSDEBUG: + if ((r = get_arg(argp, &val, sizeof(val)))) + return r; + is->debug = val; + break; + case PPPIOCGCOMPRESSORS: + { + unsigned long protos[8] = {0,}; + struct isdn_ppp_compressor *ipc = ipc_head; + while (ipc) { + j = ipc->num / (sizeof(long) * 8); + i = ipc->num % (sizeof(long) * 8); + if (j < 8) + protos[j] |= (1UL << i); + ipc = ipc->next; + } + if ((r = set_arg(argp, protos, 8 * sizeof(long)))) + return r; + } + break; + case PPPIOCSCOMPRESSOR: + if ((r = get_arg(argp, &data, sizeof(struct isdn_ppp_comp_data)))) + return r; + return isdn_ppp_set_compressor(is, &data); + case PPPIOCGCALLINFO: + { + struct pppcallinfo pci; + memset((char *)&pci, 0, sizeof(struct pppcallinfo)); + if (lp) + { + strncpy(pci.local_num, lp->msn, 63); + if (lp->dial) { + strncpy(pci.remote_num, lp->dial->num, 63); + } + pci.charge_units = lp->charge; + if (lp->outgoing) + pci.calltype = CALLTYPE_OUTGOING; + else + pci.calltype = CALLTYPE_INCOMING; + if (lp->flags & ISDN_NET_CALLBACK) + pci.calltype |= CALLTYPE_CALLBACK; + } + return set_arg(argp, &pci, sizeof(struct pppcallinfo)); + } +#ifdef CONFIG_IPPP_FILTER + case PPPIOCSPASS: + { + struct sock_fprog_kern fprog; + struct sock_filter *code; + int err, len = get_filter(argp, &code); + + if (len < 0) + return len; + + fprog.len = len; + fprog.filter = code; + + if (is->pass_filter) { + bpf_prog_destroy(is->pass_filter); + is->pass_filter = NULL; + } + if (fprog.filter != NULL) + err = bpf_prog_create(&is->pass_filter, &fprog); + else + err = 0; + kfree(code); + + return err; + } + case PPPIOCSACTIVE: + { + struct sock_fprog_kern fprog; + struct sock_filter *code; + int err, len = get_filter(argp, &code); + + if (len < 0) + return len; + + fprog.len = len; + fprog.filter = code; + + if (is->active_filter) { + bpf_prog_destroy(is->active_filter); + is->active_filter = NULL; + } + if (fprog.filter != NULL) + err = bpf_prog_create(&is->active_filter, &fprog); + else + err = 0; + kfree(code); + + return err; + } +#endif /* CONFIG_IPPP_FILTER */ + default: + break; + } + return 0; +} + +unsigned int +isdn_ppp_poll(struct file *file, poll_table *wait) +{ + u_int mask; + struct ippp_buf_queue *bf, *bl; + u_long flags; + struct ippp_struct *is; + + is = file->private_data; + + if (is->debug & 0x2) + printk(KERN_DEBUG "isdn_ppp_poll: minor: %d\n", + iminor(file_inode(file))); + + /* just registers wait_queue hook. This doesn't really wait. */ + poll_wait(file, &is->wq, wait); + + if (!(is->state & IPPP_OPEN)) { + if (is->state == IPPP_CLOSEWAIT) + return POLLHUP; + printk(KERN_DEBUG "isdn_ppp: device not open\n"); + return POLLERR; + } + /* we're always ready to send .. */ + mask = POLLOUT | POLLWRNORM; + + spin_lock_irqsave(&is->buflock, flags); + bl = is->last; + bf = is->first; + /* + * if IPPP_NOBLOCK is set we return even if we have nothing to read + */ + if (bf->next != bl || (is->state & IPPP_NOBLOCK)) { + is->state &= ~IPPP_NOBLOCK; + mask |= POLLIN | POLLRDNORM; + } + spin_unlock_irqrestore(&is->buflock, flags); + return mask; +} + +/* + * fill up isdn_ppp_read() queue .. + */ + +static int +isdn_ppp_fill_rq(unsigned char *buf, int len, int proto, int slot) +{ + struct ippp_buf_queue *bf, *bl; + u_long flags; + u_char *nbuf; + struct ippp_struct *is; + + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_WARNING "ippp: illegal slot(%d).\n", slot); + return 0; + } + is = ippp_table[slot]; + + if (!(is->state & IPPP_CONNECT)) { + printk(KERN_DEBUG "ippp: device not activated.\n"); + return 0; + } + nbuf = kmalloc(len + 4, GFP_ATOMIC); + if (!nbuf) { + printk(KERN_WARNING "ippp: Can't alloc buf\n"); + return 0; + } + nbuf[0] = PPP_ALLSTATIONS; + nbuf[1] = PPP_UI; + nbuf[2] = proto >> 8; + nbuf[3] = proto & 0xff; + memcpy(nbuf + 4, buf, len); + + spin_lock_irqsave(&is->buflock, flags); + bf = is->first; + bl = is->last; + + if (bf == bl) { + printk(KERN_WARNING "ippp: Queue is full; discarding first buffer\n"); + bf = bf->next; + kfree(bf->buf); + is->first = bf; + } + bl->buf = (char *) nbuf; + bl->len = len + 4; + + is->last = bl->next; + spin_unlock_irqrestore(&is->buflock, flags); + wake_up_interruptible(&is->wq); + return len; +} + +/* + * read() .. non-blocking: ipppd calls it only after select() + * reports, that there is data + */ + +int +isdn_ppp_read(int min, struct file *file, char __user *buf, int count) +{ + struct ippp_struct *is; + struct ippp_buf_queue *b; + u_long flags; + u_char *save_buf; + + is = file->private_data; + + if (!(is->state & IPPP_OPEN)) + return 0; + + if (!access_ok(VERIFY_WRITE, buf, count)) + return -EFAULT; + + spin_lock_irqsave(&is->buflock, flags); + b = is->first->next; + save_buf = b->buf; + if (!save_buf) { + spin_unlock_irqrestore(&is->buflock, flags); + return -EAGAIN; + } + if (b->len < count) + count = b->len; + b->buf = NULL; + is->first = b; + + spin_unlock_irqrestore(&is->buflock, flags); + if (copy_to_user(buf, save_buf, count)) + count = -EFAULT; + kfree(save_buf); + + return count; +} + +/* + * ipppd wanna write a packet to the card .. non-blocking + */ + +int +isdn_ppp_write(int min, struct file *file, const char __user *buf, int count) +{ + isdn_net_local *lp; + struct ippp_struct *is; + int proto; + unsigned char protobuf[4]; + + is = file->private_data; + + if (!(is->state & IPPP_CONNECT)) + return 0; + + lp = is->lp; + + /* -> push it directly to the lowlevel interface */ + + if (!lp) + printk(KERN_DEBUG "isdn_ppp_write: lp == NULL\n"); + else { + /* + * Don't reset huptimer for + * LCP packets. (Echo requests). + */ + if (copy_from_user(protobuf, buf, 4)) + return -EFAULT; + proto = PPP_PROTOCOL(protobuf); + if (proto != PPP_LCP) + lp->huptimer = 0; + + if (lp->isdn_device < 0 || lp->isdn_channel < 0) + return 0; + + if ((dev->drv[lp->isdn_device]->flags & DRV_FLAG_RUNNING) && + lp->dialstate == 0 && + (lp->flags & ISDN_NET_CONNECTED)) { + unsigned short hl; + struct sk_buff *skb; + /* + * we need to reserve enough space in front of + * sk_buff. old call to dev_alloc_skb only reserved + * 16 bytes, now we are looking what the driver want + */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + skb = alloc_skb(hl + count, GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING "isdn_ppp_write: out of memory!\n"); + return count; + } + skb_reserve(skb, hl); + if (copy_from_user(skb_put(skb, count), buf, count)) + { + kfree_skb(skb); + return -EFAULT; + } + if (is->debug & 0x40) { + printk(KERN_DEBUG "ppp xmit: len %d\n", (int) skb->len); + isdn_ppp_frame_log("xmit", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + } + + isdn_ppp_send_ccp(lp->netdev, lp, skb); /* keeps CCP/compression states in sync */ + + isdn_net_write_super(lp, skb); + } + } + return count; +} + +/* + * init memory, structures etc. + */ + +int +isdn_ppp_init(void) +{ + int i, + j; + +#ifdef CONFIG_ISDN_MPP + if (isdn_ppp_mp_bundle_array_init() < 0) + return -ENOMEM; +#endif /* CONFIG_ISDN_MPP */ + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + if (!(ippp_table[i] = kzalloc(sizeof(struct ippp_struct), GFP_KERNEL))) { + printk(KERN_WARNING "isdn_ppp_init: Could not alloc ippp_table\n"); + for (j = 0; j < i; j++) + kfree(ippp_table[j]); + return -1; + } + spin_lock_init(&ippp_table[i]->buflock); + ippp_table[i]->state = 0; + ippp_table[i]->first = ippp_table[i]->rq + NUM_RCV_BUFFS - 1; + ippp_table[i]->last = ippp_table[i]->rq; + + for (j = 0; j < NUM_RCV_BUFFS; j++) { + ippp_table[i]->rq[j].buf = NULL; + ippp_table[i]->rq[j].last = ippp_table[i]->rq + + (NUM_RCV_BUFFS + j - 1) % NUM_RCV_BUFFS; + ippp_table[i]->rq[j].next = ippp_table[i]->rq + (j + 1) % NUM_RCV_BUFFS; + } + } + return 0; +} + +void +isdn_ppp_cleanup(void) +{ + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + kfree(ippp_table[i]); + +#ifdef CONFIG_ISDN_MPP + kfree(isdn_ppp_bundle_arr); +#endif /* CONFIG_ISDN_MPP */ + +} + +/* + * check for address/control field and skip if allowed + * retval != 0 -> discard packet silently + */ +static int isdn_ppp_skip_ac(struct ippp_struct *is, struct sk_buff *skb) +{ + if (skb->len < 1) + return -1; + + if (skb->data[0] == 0xff) { + if (skb->len < 2) + return -1; + + if (skb->data[1] != 0x03) + return -1; + + // skip address/control (AC) field + skb_pull(skb, 2); + } else { + if (is->pppcfg & SC_REJ_COMP_AC) + // if AC compression was not negotiated, but used, discard packet + return -1; + } + return 0; +} + +/* + * get the PPP protocol header and pull skb + * retval < 0 -> discard packet silently + */ +static int isdn_ppp_strip_proto(struct sk_buff *skb) +{ + int proto; + + if (skb->len < 1) + return -1; + + if (skb->data[0] & 0x1) { + // protocol field is compressed + proto = skb->data[0]; + skb_pull(skb, 1); + } else { + if (skb->len < 2) + return -1; + proto = ((int) skb->data[0] << 8) + skb->data[1]; + skb_pull(skb, 2); + } + return proto; +} + + +/* + * handler for incoming packets on a syncPPP interface + */ +void isdn_ppp_receive(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb) +{ + struct ippp_struct *is; + int slot; + int proto; + + BUG_ON(net_dev->local->master); // we're called with the master device always + + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_receive: lp->ppp_slot(%d)\n", + lp->ppp_slot); + kfree_skb(skb); + return; + } + is = ippp_table[slot]; + + if (is->debug & 0x4) { + printk(KERN_DEBUG "ippp_receive: is:%08lx lp:%08lx slot:%d unit:%d len:%d\n", + (long)is, (long)lp, lp->ppp_slot, is->unit, (int)skb->len); + isdn_ppp_frame_log("receive", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + } + + if (isdn_ppp_skip_ac(is, skb) < 0) { + kfree_skb(skb); + return; + } + proto = isdn_ppp_strip_proto(skb); + if (proto < 0) { + kfree_skb(skb); + return; + } + +#ifdef CONFIG_ISDN_MPP + if (is->compflags & SC_LINK_DECOMP_ON) { + skb = isdn_ppp_decompress(skb, is, NULL, &proto); + if (!skb) // decompression error + return; + } + + if (!(is->mpppcfg & SC_REJ_MP_PROT)) { // we agreed to receive MPPP + if (proto == PPP_MP) { + isdn_ppp_mp_receive(net_dev, lp, skb); + return; + } + } +#endif + isdn_ppp_push_higher(net_dev, lp, skb, proto); +} + +/* + * we receive a reassembled frame, MPPP has been taken care of before. + * address/control and protocol have been stripped from the skb + * note: net_dev has to be master net_dev + */ +static void +isdn_ppp_push_higher(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb, int proto) +{ + struct net_device *dev = net_dev->dev; + struct ippp_struct *is, *mis; + isdn_net_local *mlp = NULL; + int slot; + + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_push_higher: lp->ppp_slot(%d)\n", + lp->ppp_slot); + goto drop_packet; + } + is = ippp_table[slot]; + + if (lp->master) { // FIXME? + mlp = ISDN_MASTER_PRIV(lp); + slot = mlp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_push_higher: master->ppp_slot(%d)\n", + lp->ppp_slot); + goto drop_packet; + } + } + mis = ippp_table[slot]; + + if (is->debug & 0x10) { + printk(KERN_DEBUG "push, skb %d %04x\n", (int) skb->len, proto); + isdn_ppp_frame_log("rpush", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + } + if (mis->compflags & SC_DECOMP_ON) { + skb = isdn_ppp_decompress(skb, is, mis, &proto); + if (!skb) // decompression error + return; + } + switch (proto) { + case PPP_IPX: /* untested */ + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: IPX\n"); + skb->protocol = htons(ETH_P_IPX); + break; + case PPP_IP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: IP\n"); + skb->protocol = htons(ETH_P_IP); + break; + case PPP_COMP: + case PPP_COMPFRAG: + printk(KERN_INFO "isdn_ppp: unexpected compressed frame dropped\n"); + goto drop_packet; +#ifdef CONFIG_ISDN_PPP_VJ + case PPP_VJC_UNCOMP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_UNCOMP\n"); + if (net_dev->local->ppp_slot < 0) { + printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n", + __func__, net_dev->local->ppp_slot); + goto drop_packet; + } + if (slhc_remember(ippp_table[net_dev->local->ppp_slot]->slcomp, skb->data, skb->len) <= 0) { + printk(KERN_WARNING "isdn_ppp: received illegal VJC_UNCOMP frame!\n"); + goto drop_packet; + } + skb->protocol = htons(ETH_P_IP); + break; + case PPP_VJC_COMP: + if (is->debug & 0x20) + printk(KERN_DEBUG "isdn_ppp: VJC_COMP\n"); + { + struct sk_buff *skb_old = skb; + int pkt_len; + skb = dev_alloc_skb(skb_old->len + 128); + + if (!skb) { + printk(KERN_WARNING "%s: Memory squeeze, dropping packet.\n", dev->name); + skb = skb_old; + goto drop_packet; + } + skb_put(skb, skb_old->len + 128); + skb_copy_from_linear_data(skb_old, skb->data, + skb_old->len); + if (net_dev->local->ppp_slot < 0) { + printk(KERN_ERR "%s: net_dev->local->ppp_slot(%d) out of range\n", + __func__, net_dev->local->ppp_slot); + goto drop_packet; + } + pkt_len = slhc_uncompress(ippp_table[net_dev->local->ppp_slot]->slcomp, + skb->data, skb_old->len); + kfree_skb(skb_old); + if (pkt_len < 0) + goto drop_packet; + + skb_trim(skb, pkt_len); + skb->protocol = htons(ETH_P_IP); + } + break; +#endif + case PPP_CCP: + case PPP_CCPFRAG: + isdn_ppp_receive_ccp(net_dev, lp, skb, proto); + /* Dont pop up ResetReq/Ack stuff to the daemon any + longer - the job is done already */ + if (skb->data[0] == CCP_RESETREQ || + skb->data[0] == CCP_RESETACK) + break; + /* fall through */ + default: + isdn_ppp_fill_rq(skb->data, skb->len, proto, lp->ppp_slot); /* push data to pppd device */ + kfree_skb(skb); + return; + } + +#ifdef CONFIG_IPPP_FILTER + /* check if the packet passes the pass and active filters + * the filter instructions are constructed assuming + * a four-byte PPP header on each packet (which is still present) */ + skb_push(skb, 4); + + { + u_int16_t *p = (u_int16_t *) skb->data; + + *p = 0; /* indicate inbound */ + } + + if (is->pass_filter + && BPF_PROG_RUN(is->pass_filter, skb) == 0) { + if (is->debug & 0x2) + printk(KERN_DEBUG "IPPP: inbound frame filtered.\n"); + kfree_skb(skb); + return; + } + if (!(is->active_filter + && BPF_PROG_RUN(is->active_filter, skb) == 0)) { + if (is->debug & 0x2) + printk(KERN_DEBUG "IPPP: link-active filter: resetting huptimer.\n"); + lp->huptimer = 0; + if (mlp) + mlp->huptimer = 0; + } + skb_pull(skb, 4); +#else /* CONFIG_IPPP_FILTER */ + lp->huptimer = 0; + if (mlp) + mlp->huptimer = 0; +#endif /* CONFIG_IPPP_FILTER */ + skb->dev = dev; + skb_reset_mac_header(skb); + netif_rx(skb); + /* net_dev->local->stats.rx_packets++; done in isdn_net.c */ + return; + +drop_packet: + net_dev->local->stats.rx_dropped++; + kfree_skb(skb); +} + +/* + * isdn_ppp_skb_push .. + * checks whether we have enough space at the beginning of the skb + * and allocs a new SKB if necessary + */ +static unsigned char *isdn_ppp_skb_push(struct sk_buff **skb_p, int len) +{ + struct sk_buff *skb = *skb_p; + + if (skb_headroom(skb) < len) { + struct sk_buff *nskb = skb_realloc_headroom(skb, len); + + if (!nskb) { + printk(KERN_ERR "isdn_ppp_skb_push: can't realloc headroom!\n"); + dev_kfree_skb(skb); + return NULL; + } + printk(KERN_DEBUG "isdn_ppp_skb_push:under %d %d\n", skb_headroom(skb), len); + dev_kfree_skb(skb); + *skb_p = nskb; + return skb_push(nskb, len); + } + return skb_push(skb, len); +} + +/* + * send ppp frame .. we expect a PIDCOMPressable proto -- + * (here: currently always PPP_IP,PPP_VJC_COMP,PPP_VJC_UNCOMP) + * + * VJ compression may change skb pointer!!! .. requeue with old + * skb isn't allowed!! + */ + +int +isdn_ppp_xmit(struct sk_buff *skb, struct net_device *netdev) +{ + isdn_net_local *lp, *mlp; + isdn_net_dev *nd; + unsigned int proto = PPP_IP; /* 0x21 */ + struct ippp_struct *ipt, *ipts; + int slot, retval = NETDEV_TX_OK; + + mlp = netdev_priv(netdev); + nd = mlp->netdev; /* get master lp */ + + slot = mlp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n", + mlp->ppp_slot); + kfree_skb(skb); + goto out; + } + ipts = ippp_table[slot]; + + if (!(ipts->pppcfg & SC_ENABLE_IP)) { /* PPP connected ? */ + if (ipts->debug & 0x1) + printk(KERN_INFO "%s: IP frame delayed.\n", netdev->name); + retval = NETDEV_TX_BUSY; + goto out; + } + + switch (ntohs(skb->protocol)) { + case ETH_P_IP: + proto = PPP_IP; + break; + case ETH_P_IPX: + proto = PPP_IPX; /* untested */ + break; + default: + printk(KERN_ERR "isdn_ppp: skipped unsupported protocol: %#x.\n", + skb->protocol); + dev_kfree_skb(skb); + goto out; + } + + lp = isdn_net_get_locked_lp(nd); + if (!lp) { + printk(KERN_WARNING "%s: all channels busy - requeuing!\n", netdev->name); + retval = NETDEV_TX_BUSY; + goto out; + } + /* we have our lp locked from now on */ + + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "isdn_ppp_xmit: lp->ppp_slot(%d)\n", + lp->ppp_slot); + kfree_skb(skb); + goto unlock; + } + ipt = ippp_table[slot]; + + /* + * after this line .. requeueing in the device queue is no longer allowed!!! + */ + + /* Pull off the fake header we stuck on earlier to keep + * the fragmentation code happy. + */ + skb_pull(skb, IPPP_MAX_HEADER); + +#ifdef CONFIG_IPPP_FILTER + /* check if we should pass this packet + * the filter instructions are constructed assuming + * a four-byte PPP header on each packet */ + *skb_push(skb, 4) = 1; /* indicate outbound */ + + { + __be16 *p = (__be16 *)skb->data; + + p++; + *p = htons(proto); + } + + if (ipt->pass_filter + && BPF_PROG_RUN(ipt->pass_filter, skb) == 0) { + if (ipt->debug & 0x4) + printk(KERN_DEBUG "IPPP: outbound frame filtered.\n"); + kfree_skb(skb); + goto unlock; + } + if (!(ipt->active_filter + && BPF_PROG_RUN(ipt->active_filter, skb) == 0)) { + if (ipt->debug & 0x4) + printk(KERN_DEBUG "IPPP: link-active filter: resetting huptimer.\n"); + lp->huptimer = 0; + } + skb_pull(skb, 4); +#else /* CONFIG_IPPP_FILTER */ + lp->huptimer = 0; +#endif /* CONFIG_IPPP_FILTER */ + + if (ipt->debug & 0x4) + printk(KERN_DEBUG "xmit skb, len %d\n", (int) skb->len); + if (ipts->debug & 0x40) + isdn_ppp_frame_log("xmit0", skb->data, skb->len, 32, ipts->unit, lp->ppp_slot); + +#ifdef CONFIG_ISDN_PPP_VJ + if (proto == PPP_IP && ipts->pppcfg & SC_COMP_TCP) { /* ipts here? probably yes, but check this again */ + struct sk_buff *new_skb; + unsigned short hl; + /* + * we need to reserve enough space in front of + * sk_buff. old call to dev_alloc_skb only reserved + * 16 bytes, now we are looking what the driver want. + */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen + IPPP_MAX_HEADER; + /* + * Note: hl might still be insufficient because the method + * above does not account for a possibible MPPP slave channel + * which had larger HL header space requirements than the + * master. + */ + new_skb = alloc_skb(hl + skb->len, GFP_ATOMIC); + if (new_skb) { + u_char *buf; + int pktlen; + + skb_reserve(new_skb, hl); + new_skb->dev = skb->dev; + skb_put(new_skb, skb->len); + buf = skb->data; + + pktlen = slhc_compress(ipts->slcomp, skb->data, skb->len, new_skb->data, + &buf, !(ipts->pppcfg & SC_NO_TCP_CCID)); + + if (buf != skb->data) { + if (new_skb->data != buf) + printk(KERN_ERR "isdn_ppp: FATAL error after slhc_compress!!\n"); + dev_kfree_skb(skb); + skb = new_skb; + } else { + dev_kfree_skb(new_skb); + } + + skb_trim(skb, pktlen); + if (skb->data[0] & SL_TYPE_COMPRESSED_TCP) { /* cslip? style -> PPP */ + proto = PPP_VJC_COMP; + skb->data[0] ^= SL_TYPE_COMPRESSED_TCP; + } else { + if (skb->data[0] >= SL_TYPE_UNCOMPRESSED_TCP) + proto = PPP_VJC_UNCOMP; + skb->data[0] = (skb->data[0] & 0x0f) | 0x40; + } + } + } +#endif + + /* + * normal (single link) or bundle compression + */ + if (ipts->compflags & SC_COMP_ON) { + /* We send compressed only if both down- und upstream + compression is negotiated, that means, CCP is up */ + if (ipts->compflags & SC_DECOMP_ON) { + skb = isdn_ppp_compress(skb, &proto, ipt, ipts, 0); + } else { + printk(KERN_DEBUG "isdn_ppp: CCP not yet up - sending as-is\n"); + } + } + + if (ipt->debug & 0x24) + printk(KERN_DEBUG "xmit2 skb, len %d, proto %04x\n", (int) skb->len, proto); + +#ifdef CONFIG_ISDN_MPP + if (ipt->mpppcfg & SC_MP_PROT) { + /* we get mp_seqno from static isdn_net_local */ + long mp_seqno = ipts->mp_seqno; + ipts->mp_seqno++; + if (ipt->mpppcfg & SC_OUT_SHORT_SEQ) { + unsigned char *data = isdn_ppp_skb_push(&skb, 3); + if (!data) + goto unlock; + mp_seqno &= 0xfff; + data[0] = MP_BEGIN_FRAG | MP_END_FRAG | ((mp_seqno >> 8) & 0xf); /* (B)egin & (E)ndbit .. */ + data[1] = mp_seqno & 0xff; + data[2] = proto; /* PID compression */ + } else { + unsigned char *data = isdn_ppp_skb_push(&skb, 5); + if (!data) + goto unlock; + data[0] = MP_BEGIN_FRAG | MP_END_FRAG; /* (B)egin & (E)ndbit .. */ + data[1] = (mp_seqno >> 16) & 0xff; /* sequence number: 24bit */ + data[2] = (mp_seqno >> 8) & 0xff; + data[3] = (mp_seqno >> 0) & 0xff; + data[4] = proto; /* PID compression */ + } + proto = PPP_MP; /* MP Protocol, 0x003d */ + } +#endif + + /* + * 'link in bundle' compression ... + */ + if (ipt->compflags & SC_LINK_COMP_ON) + skb = isdn_ppp_compress(skb, &proto, ipt, ipts, 1); + + if ((ipt->pppcfg & SC_COMP_PROT) && (proto <= 0xff)) { + unsigned char *data = isdn_ppp_skb_push(&skb, 1); + if (!data) + goto unlock; + data[0] = proto & 0xff; + } + else { + unsigned char *data = isdn_ppp_skb_push(&skb, 2); + if (!data) + goto unlock; + data[0] = (proto >> 8) & 0xff; + data[1] = proto & 0xff; + } + if (!(ipt->pppcfg & SC_COMP_AC)) { + unsigned char *data = isdn_ppp_skb_push(&skb, 2); + if (!data) + goto unlock; + data[0] = 0xff; /* All Stations */ + data[1] = 0x03; /* Unnumbered information */ + } + + /* tx-stats are now updated via BSENT-callback */ + + if (ipts->debug & 0x40) { + printk(KERN_DEBUG "skb xmit: len: %d\n", (int) skb->len); + isdn_ppp_frame_log("xmit", skb->data, skb->len, 32, ipt->unit, lp->ppp_slot); + } + + isdn_net_writebuf_skb(lp, skb); + +unlock: + spin_unlock_bh(&lp->xmit_lock); +out: + return retval; +} + +#ifdef CONFIG_IPPP_FILTER +/* + * check if this packet may trigger auto-dial. + */ + +int isdn_ppp_autodial_filter(struct sk_buff *skb, isdn_net_local *lp) +{ + struct ippp_struct *is = ippp_table[lp->ppp_slot]; + u_int16_t proto; + int drop = 0; + + switch (ntohs(skb->protocol)) { + case ETH_P_IP: + proto = PPP_IP; + break; + case ETH_P_IPX: + proto = PPP_IPX; + break; + default: + printk(KERN_ERR "isdn_ppp_autodial_filter: unsupported protocol 0x%x.\n", + skb->protocol); + return 1; + } + + /* the filter instructions are constructed assuming + * a four-byte PPP header on each packet. we have to + * temporarily remove part of the fake header stuck on + * earlier. + */ + *skb_pull(skb, IPPP_MAX_HEADER - 4) = 1; /* indicate outbound */ + + { + __be16 *p = (__be16 *)skb->data; + + p++; + *p = htons(proto); + } + + drop |= is->pass_filter + && BPF_PROG_RUN(is->pass_filter, skb) == 0; + drop |= is->active_filter + && BPF_PROG_RUN(is->active_filter, skb) == 0; + + skb_push(skb, IPPP_MAX_HEADER - 4); + return drop; +} +#endif +#ifdef CONFIG_ISDN_MPP + +/* this is _not_ rfc1990 header, but something we convert both short and long + * headers to for convinience's sake: + * byte 0 is flags as in rfc1990 + * bytes 1...4 is 24-bit seqence number converted to host byte order + */ +#define MP_HEADER_LEN 5 + +#define MP_LONGSEQ_MASK 0x00ffffff +#define MP_SHORTSEQ_MASK 0x00000fff +#define MP_LONGSEQ_MAX MP_LONGSEQ_MASK +#define MP_SHORTSEQ_MAX MP_SHORTSEQ_MASK +#define MP_LONGSEQ_MAXBIT ((MP_LONGSEQ_MASK + 1) >> 1) +#define MP_SHORTSEQ_MAXBIT ((MP_SHORTSEQ_MASK + 1) >> 1) + +/* sequence-wrap safe comparisons (for long sequence)*/ +#define MP_LT(a, b) ((a - b) & MP_LONGSEQ_MAXBIT) +#define MP_LE(a, b) !((b - a) & MP_LONGSEQ_MAXBIT) +#define MP_GT(a, b) ((b - a) & MP_LONGSEQ_MAXBIT) +#define MP_GE(a, b) !((a - b) & MP_LONGSEQ_MAXBIT) + +#define MP_SEQ(f) ((*(u32 *)(f->data + 1))) +#define MP_FLAGS(f) (f->data[0]) + +static int isdn_ppp_mp_bundle_array_init(void) +{ + int i; + int sz = ISDN_MAX_CHANNELS * sizeof(ippp_bundle); + if ((isdn_ppp_bundle_arr = kzalloc(sz, GFP_KERNEL)) == NULL) + return -ENOMEM; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + spin_lock_init(&isdn_ppp_bundle_arr[i].lock); + return 0; +} + +static ippp_bundle *isdn_ppp_mp_bundle_alloc(void) +{ + int i; + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (isdn_ppp_bundle_arr[i].ref_ct <= 0) + return (isdn_ppp_bundle_arr + i); + return NULL; +} + +static int isdn_ppp_mp_init(isdn_net_local *lp, ippp_bundle *add_to) +{ + struct ippp_struct *is; + + if (lp->ppp_slot < 0) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __func__, lp->ppp_slot); + return (-EINVAL); + } + + is = ippp_table[lp->ppp_slot]; + if (add_to) { + if (lp->netdev->pb) + lp->netdev->pb->ref_ct--; + lp->netdev->pb = add_to; + } else { /* first link in a bundle */ + is->mp_seqno = 0; + if ((lp->netdev->pb = isdn_ppp_mp_bundle_alloc()) == NULL) + return -ENOMEM; + lp->next = lp->last = lp; /* nobody else in a queue */ + lp->netdev->pb->frags = NULL; + lp->netdev->pb->frames = 0; + lp->netdev->pb->seq = UINT_MAX; + } + lp->netdev->pb->ref_ct++; + + is->last_link_seqno = 0; + return 0; +} + +static u32 isdn_ppp_mp_get_seq(int short_seq, + struct sk_buff *skb, u32 last_seq); +static struct sk_buff *isdn_ppp_mp_discard(ippp_bundle *mp, + struct sk_buff *from, struct sk_buff *to); +static void isdn_ppp_mp_reassembly(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *from, struct sk_buff *to); +static void isdn_ppp_mp_free_skb(ippp_bundle *mp, struct sk_buff *skb); +static void isdn_ppp_mp_print_recv_pkt(int slot, struct sk_buff *skb); + +static void isdn_ppp_mp_receive(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb) +{ + struct ippp_struct *is; + isdn_net_local *lpq; + ippp_bundle *mp; + isdn_mppp_stats *stats; + struct sk_buff *newfrag, *frag, *start, *nextf; + u32 newseq, minseq, thisseq; + unsigned long flags; + int slot; + + spin_lock_irqsave(&net_dev->pb->lock, flags); + mp = net_dev->pb; + stats = &mp->stats; + slot = lp->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d)\n", + __func__, lp->ppp_slot); + stats->frame_drops++; + dev_kfree_skb(skb); + spin_unlock_irqrestore(&mp->lock, flags); + return; + } + is = ippp_table[slot]; + if (++mp->frames > stats->max_queue_len) + stats->max_queue_len = mp->frames; + + if (is->debug & 0x8) + isdn_ppp_mp_print_recv_pkt(lp->ppp_slot, skb); + + newseq = isdn_ppp_mp_get_seq(is->mpppcfg & SC_IN_SHORT_SEQ, + skb, is->last_link_seqno); + + + /* if this packet seq # is less than last already processed one, + * toss it right away, but check for sequence start case first + */ + if (mp->seq > MP_LONGSEQ_MAX && (newseq & MP_LONGSEQ_MAXBIT)) { + mp->seq = newseq; /* the first packet: required for + * rfc1990 non-compliant clients -- + * prevents constant packet toss */ + } else if (MP_LT(newseq, mp->seq)) { + stats->frame_drops++; + isdn_ppp_mp_free_skb(mp, skb); + spin_unlock_irqrestore(&mp->lock, flags); + return; + } + + /* find the minimum received sequence number over all links */ + is->last_link_seqno = minseq = newseq; + for (lpq = net_dev->queue;;) { + slot = lpq->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lpq->ppp_slot(%d)\n", + __func__, lpq->ppp_slot); + } else { + u32 lls = ippp_table[slot]->last_link_seqno; + if (MP_LT(lls, minseq)) + minseq = lls; + } + if ((lpq = lpq->next) == net_dev->queue) + break; + } + if (MP_LT(minseq, mp->seq)) + minseq = mp->seq; /* can't go beyond already processed + * packets */ + newfrag = skb; + + /* if this new fragment is before the first one, then enqueue it now. */ + if ((frag = mp->frags) == NULL || MP_LT(newseq, MP_SEQ(frag))) { + newfrag->next = frag; + mp->frags = frag = newfrag; + newfrag = NULL; + } + + start = MP_FLAGS(frag) & MP_BEGIN_FRAG && + MP_SEQ(frag) == mp->seq ? frag : NULL; + + /* + * main fragment traversing loop + * + * try to accomplish several tasks: + * - insert new fragment into the proper sequence slot (once that's done + * newfrag will be set to NULL) + * - reassemble any complete fragment sequence (non-null 'start' + * indicates there is a contiguous sequence present) + * - discard any incomplete sequences that are below minseq -- due + * to the fact that sender always increment sequence number, if there + * is an incomplete sequence below minseq, no new fragments would + * come to complete such sequence and it should be discarded + * + * loop completes when we accomplished the following tasks: + * - new fragment is inserted in the proper sequence ('newfrag' is + * set to NULL) + * - we hit a gap in the sequence, so no reassembly/processing is + * possible ('start' would be set to NULL) + * + * algorithm for this code is derived from code in the book + * 'PPP Design And Debugging' by James Carlson (Addison-Wesley) + */ + while (start != NULL || newfrag != NULL) { + + thisseq = MP_SEQ(frag); + nextf = frag->next; + + /* drop any duplicate fragments */ + if (newfrag != NULL && thisseq == newseq) { + isdn_ppp_mp_free_skb(mp, newfrag); + newfrag = NULL; + } + + /* insert new fragment before next element if possible. */ + if (newfrag != NULL && (nextf == NULL || + MP_LT(newseq, MP_SEQ(nextf)))) { + newfrag->next = nextf; + frag->next = nextf = newfrag; + newfrag = NULL; + } + + if (start != NULL) { + /* check for misplaced start */ + if (start != frag && (MP_FLAGS(frag) & MP_BEGIN_FRAG)) { + printk(KERN_WARNING"isdn_mppp(seq %d): new " + "BEGIN flag with no prior END", thisseq); + stats->seqerrs++; + stats->frame_drops++; + start = isdn_ppp_mp_discard(mp, start, frag); + nextf = frag->next; + } + } else if (MP_LE(thisseq, minseq)) { + if (MP_FLAGS(frag) & MP_BEGIN_FRAG) + start = frag; + else { + if (MP_FLAGS(frag) & MP_END_FRAG) + stats->frame_drops++; + if (mp->frags == frag) + mp->frags = nextf; + isdn_ppp_mp_free_skb(mp, frag); + frag = nextf; + continue; + } + } + + /* if start is non-null and we have end fragment, then + * we have full reassembly sequence -- reassemble + * and process packet now + */ + if (start != NULL && (MP_FLAGS(frag) & MP_END_FRAG)) { + minseq = mp->seq = (thisseq + 1) & MP_LONGSEQ_MASK; + /* Reassemble the packet then dispatch it */ + isdn_ppp_mp_reassembly(net_dev, lp, start, nextf); + + start = NULL; + frag = NULL; + + mp->frags = nextf; + } + + /* check if need to update start pointer: if we just + * reassembled the packet and sequence is contiguous + * then next fragment should be the start of new reassembly + * if sequence is contiguous, but we haven't reassembled yet, + * keep going. + * if sequence is not contiguous, either clear everything + * below low watermark and set start to the next frag or + * clear start ptr. + */ + if (nextf != NULL && + ((thisseq + 1) & MP_LONGSEQ_MASK) == MP_SEQ(nextf)) { + /* if we just reassembled and the next one is here, + * then start another reassembly. */ + + if (frag == NULL) { + if (MP_FLAGS(nextf) & MP_BEGIN_FRAG) + start = nextf; + else + { + printk(KERN_WARNING"isdn_mppp(seq %d):" + " END flag with no following " + "BEGIN", thisseq); + stats->seqerrs++; + } + } + + } else { + if (nextf != NULL && frag != NULL && + MP_LT(thisseq, minseq)) { + /* we've got a break in the sequence + * and we not at the end yet + * and we did not just reassembled + *(if we did, there wouldn't be anything before) + * and we below the low watermark + * discard all the frames below low watermark + * and start over */ + stats->frame_drops++; + mp->frags = isdn_ppp_mp_discard(mp, start, nextf); + } + /* break in the sequence, no reassembly */ + start = NULL; + } + + frag = nextf; + } /* while -- main loop */ + + if (mp->frags == NULL) + mp->frags = frag; + + /* rather straighforward way to deal with (not very) possible + * queue overflow */ + if (mp->frames > MP_MAX_QUEUE_LEN) { + stats->overflows++; + while (mp->frames > MP_MAX_QUEUE_LEN) { + frag = mp->frags->next; + isdn_ppp_mp_free_skb(mp, mp->frags); + mp->frags = frag; + } + } + spin_unlock_irqrestore(&mp->lock, flags); +} + +static void isdn_ppp_mp_cleanup(isdn_net_local *lp) +{ + struct sk_buff *frag = lp->netdev->pb->frags; + struct sk_buff *nextfrag; + while (frag) { + nextfrag = frag->next; + isdn_ppp_mp_free_skb(lp->netdev->pb, frag); + frag = nextfrag; + } + lp->netdev->pb->frags = NULL; +} + +static u32 isdn_ppp_mp_get_seq(int short_seq, + struct sk_buff *skb, u32 last_seq) +{ + u32 seq; + int flags = skb->data[0] & (MP_BEGIN_FRAG | MP_END_FRAG); + + if (!short_seq) + { + seq = ntohl(*(__be32 *)skb->data) & MP_LONGSEQ_MASK; + skb_push(skb, 1); + } + else + { + /* convert 12-bit short seq number to 24-bit long one + */ + seq = ntohs(*(__be16 *)skb->data) & MP_SHORTSEQ_MASK; + + /* check for seqence wrap */ + if (!(seq & MP_SHORTSEQ_MAXBIT) && + (last_seq & MP_SHORTSEQ_MAXBIT) && + (unsigned long)last_seq <= MP_LONGSEQ_MAX) + seq |= (last_seq + MP_SHORTSEQ_MAX + 1) & + (~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK); + else + seq |= last_seq & (~MP_SHORTSEQ_MASK & MP_LONGSEQ_MASK); + + skb_push(skb, 3); /* put converted seqence back in skb */ + } + *(u32 *)(skb->data + 1) = seq; /* put seqence back in _host_ byte + * order */ + skb->data[0] = flags; /* restore flags */ + return seq; +} + +struct sk_buff *isdn_ppp_mp_discard(ippp_bundle *mp, + struct sk_buff *from, struct sk_buff *to) +{ + if (from) + while (from != to) { + struct sk_buff *next = from->next; + isdn_ppp_mp_free_skb(mp, from); + from = next; + } + return from; +} + +void isdn_ppp_mp_reassembly(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *from, struct sk_buff *to) +{ + ippp_bundle *mp = net_dev->pb; + int proto; + struct sk_buff *skb; + unsigned int tot_len; + + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __func__, lp->ppp_slot); + return; + } + if (MP_FLAGS(from) == (MP_BEGIN_FRAG | MP_END_FRAG)) { + if (ippp_table[lp->ppp_slot]->debug & 0x40) + printk(KERN_DEBUG "isdn_mppp: reassembly: frame %d, " + "len %d\n", MP_SEQ(from), from->len); + skb = from; + skb_pull(skb, MP_HEADER_LEN); + mp->frames--; + } else { + struct sk_buff *frag; + int n; + + for (tot_len = n = 0, frag = from; frag != to; frag = frag->next, n++) + tot_len += frag->len - MP_HEADER_LEN; + + if (ippp_table[lp->ppp_slot]->debug & 0x40) + printk(KERN_DEBUG"isdn_mppp: reassembling frames %d " + "to %d, len %d\n", MP_SEQ(from), + (MP_SEQ(from) + n - 1) & MP_LONGSEQ_MASK, tot_len); + if ((skb = dev_alloc_skb(tot_len)) == NULL) { + printk(KERN_ERR "isdn_mppp: cannot allocate sk buff " + "of size %d\n", tot_len); + isdn_ppp_mp_discard(mp, from, to); + return; + } + + while (from != to) { + unsigned int len = from->len - MP_HEADER_LEN; + + skb_copy_from_linear_data_offset(from, MP_HEADER_LEN, + skb_put(skb, len), + len); + frag = from->next; + isdn_ppp_mp_free_skb(mp, from); + from = frag; + } + } + proto = isdn_ppp_strip_proto(skb); + isdn_ppp_push_higher(net_dev, lp, skb, proto); +} + +static void isdn_ppp_mp_free_skb(ippp_bundle *mp, struct sk_buff *skb) +{ + dev_kfree_skb(skb); + mp->frames--; +} + +static void isdn_ppp_mp_print_recv_pkt(int slot, struct sk_buff *skb) +{ + printk(KERN_DEBUG "mp_recv: %d/%d -> %02x %02x %02x %02x %02x %02x\n", + slot, (int) skb->len, + (int) skb->data[0], (int) skb->data[1], (int) skb->data[2], + (int) skb->data[3], (int) skb->data[4], (int) skb->data[5]); +} + +static int +isdn_ppp_bundle(struct ippp_struct *is, int unit) +{ + char ifn[IFNAMSIZ + 1]; + isdn_net_dev *p; + isdn_net_local *lp, *nlp; + int rc; + unsigned long flags; + + sprintf(ifn, "ippp%d", unit); + p = isdn_net_findif(ifn); + if (!p) { + printk(KERN_ERR "ippp_bundle: cannot find %s\n", ifn); + return -EINVAL; + } + + spin_lock_irqsave(&p->pb->lock, flags); + + nlp = is->lp; + lp = p->queue; + if (nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS || + lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "ippp_bundle: binding to invalid slot %d\n", + nlp->ppp_slot < 0 || nlp->ppp_slot >= ISDN_MAX_CHANNELS ? + nlp->ppp_slot : lp->ppp_slot); + rc = -EINVAL; + goto out; + } + + isdn_net_add_to_bundle(p, nlp); + + ippp_table[nlp->ppp_slot]->unit = ippp_table[lp->ppp_slot]->unit; + + /* maybe also SC_CCP stuff */ + ippp_table[nlp->ppp_slot]->pppcfg |= ippp_table[lp->ppp_slot]->pppcfg & + (SC_ENABLE_IP | SC_NO_TCP_CCID | SC_REJ_COMP_TCP); + ippp_table[nlp->ppp_slot]->mpppcfg |= ippp_table[lp->ppp_slot]->mpppcfg & + (SC_MP_PROT | SC_REJ_MP_PROT | SC_OUT_SHORT_SEQ | SC_IN_SHORT_SEQ); + rc = isdn_ppp_mp_init(nlp, p->pb); +out: + spin_unlock_irqrestore(&p->pb->lock, flags); + return rc; +} + +#endif /* CONFIG_ISDN_MPP */ + +/* + * network device ioctl handlers + */ + +static int +isdn_ppp_dev_ioctl_stats(int slot, struct ifreq *ifr, struct net_device *dev) +{ + struct ppp_stats __user *res = ifr->ifr_data; + struct ppp_stats t; + isdn_net_local *lp = netdev_priv(dev); + + if (!access_ok(VERIFY_WRITE, res, sizeof(struct ppp_stats))) + return -EFAULT; + + /* build a temporary stat struct and copy it to user space */ + + memset(&t, 0, sizeof(struct ppp_stats)); + if (dev->flags & IFF_UP) { + t.p.ppp_ipackets = lp->stats.rx_packets; + t.p.ppp_ibytes = lp->stats.rx_bytes; + t.p.ppp_ierrors = lp->stats.rx_errors; + t.p.ppp_opackets = lp->stats.tx_packets; + t.p.ppp_obytes = lp->stats.tx_bytes; + t.p.ppp_oerrors = lp->stats.tx_errors; +#ifdef CONFIG_ISDN_PPP_VJ + if (slot >= 0 && ippp_table[slot]->slcomp) { + struct slcompress *slcomp = ippp_table[slot]->slcomp; + t.vj.vjs_packets = slcomp->sls_o_compressed + slcomp->sls_o_uncompressed; + t.vj.vjs_compressed = slcomp->sls_o_compressed; + t.vj.vjs_searches = slcomp->sls_o_searches; + t.vj.vjs_misses = slcomp->sls_o_misses; + t.vj.vjs_errorin = slcomp->sls_i_error; + t.vj.vjs_tossed = slcomp->sls_i_tossed; + t.vj.vjs_uncompressedin = slcomp->sls_i_uncompressed; + t.vj.vjs_compressedin = slcomp->sls_i_compressed; + } +#endif + } + if (copy_to_user(res, &t, sizeof(struct ppp_stats))) + return -EFAULT; + return 0; +} + +int +isdn_ppp_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int error = 0; + int len; + isdn_net_local *lp = netdev_priv(dev); + + + if (lp->p_encap != ISDN_NET_ENCAP_SYNCPPP) + return -EINVAL; + + switch (cmd) { +#define PPP_VERSION "2.3.7" + case SIOCGPPPVER: + len = strlen(PPP_VERSION) + 1; + if (copy_to_user(ifr->ifr_data, PPP_VERSION, len)) + error = -EFAULT; + break; + + case SIOCGPPPSTATS: + error = isdn_ppp_dev_ioctl_stats(lp->ppp_slot, ifr, dev); + break; + default: + error = -EINVAL; + break; + } + return error; +} + +static int +isdn_ppp_if_get_unit(char *name) +{ + int len, + i, + unit = 0, + deci; + + len = strlen(name); + + if (strncmp("ippp", name, 4) || len > 8) + return -1; + + for (i = 0, deci = 1; i < len; i++, deci *= 10) { + char a = name[len - i - 1]; + if (a >= '0' && a <= '9') + unit += (a - '0') * deci; + else + break; + } + if (!i || len - i != 4) + unit = -1; + + return unit; +} + + +int +isdn_ppp_dial_slave(char *name) +{ +#ifdef CONFIG_ISDN_MPP + isdn_net_dev *ndev; + isdn_net_local *lp; + struct net_device *sdev; + + if (!(ndev = isdn_net_findif(name))) + return 1; + lp = ndev->local; + if (!(lp->flags & ISDN_NET_CONNECTED)) + return 5; + + sdev = lp->slave; + while (sdev) { + isdn_net_local *mlp = netdev_priv(sdev); + if (!(mlp->flags & ISDN_NET_CONNECTED)) + break; + sdev = mlp->slave; + } + if (!sdev) + return 2; + + isdn_net_dial_req(netdev_priv(sdev)); + return 0; +#else + return -1; +#endif +} + +int +isdn_ppp_hangup_slave(char *name) +{ +#ifdef CONFIG_ISDN_MPP + isdn_net_dev *ndev; + isdn_net_local *lp; + struct net_device *sdev; + + if (!(ndev = isdn_net_findif(name))) + return 1; + lp = ndev->local; + if (!(lp->flags & ISDN_NET_CONNECTED)) + return 5; + + sdev = lp->slave; + while (sdev) { + isdn_net_local *mlp = netdev_priv(sdev); + + if (mlp->slave) { /* find last connected link in chain */ + isdn_net_local *nlp = ISDN_SLAVE_PRIV(mlp); + + if (!(nlp->flags & ISDN_NET_CONNECTED)) + break; + } else if (mlp->flags & ISDN_NET_CONNECTED) + break; + + sdev = mlp->slave; + } + if (!sdev) + return 2; + + isdn_net_hangup(sdev); + return 0; +#else + return -1; +#endif +} + +/* + * PPP compression stuff + */ + + +/* Push an empty CCP Data Frame up to the daemon to wake it up and let it + generate a CCP Reset-Request or tear down CCP altogether */ + +static void isdn_ppp_ccp_kickup(struct ippp_struct *is) +{ + isdn_ppp_fill_rq(NULL, 0, PPP_COMP, is->lp->ppp_slot); +} + +/* In-kernel handling of CCP Reset-Request and Reset-Ack is necessary, + but absolutely nontrivial. The most abstruse problem we are facing is + that the generation, reception and all the handling of timeouts and + resends including proper request id management should be entirely left + to the (de)compressor, but indeed is not covered by the current API to + the (de)compressor. The API is a prototype version from PPP where only + some (de)compressors have yet been implemented and all of them are + rather simple in their reset handling. Especially, their is only one + outstanding ResetAck at a time with all of them and ResetReq/-Acks do + not have parameters. For this very special case it was sufficient to + just return an error code from the decompressor and have a single + reset() entry to communicate all the necessary information between + the framework and the (de)compressor. Bad enough, LZS is different + (and any other compressor may be different, too). It has multiple + histories (eventually) and needs to Reset each of them independently + and thus uses multiple outstanding Acks and history numbers as an + additional parameter to Reqs/Acks. + All that makes it harder to port the reset state engine into the + kernel because it is not just the same simple one as in (i)pppd but + it must be able to pass additional parameters and have multiple out- + standing Acks. We are trying to achieve the impossible by handling + reset transactions independent by their id. The id MUST change when + the data portion changes, thus any (de)compressor who uses more than + one resettable state must provide and recognize individual ids for + each individual reset transaction. The framework itself does _only_ + differentiate them by id, because it has no other semantics like the + (de)compressor might. + This looks like a major redesign of the interface would be nice, + but I don't have an idea how to do it better. */ + +/* Send a CCP Reset-Request or Reset-Ack directly from the kernel. This is + getting that lengthy because there is no simple "send-this-frame-out" + function above but every wrapper does a bit different. Hope I guess + correct in this hack... */ + +static void isdn_ppp_ccp_xmit_reset(struct ippp_struct *is, int proto, + unsigned char code, unsigned char id, + unsigned char *data, int len) +{ + struct sk_buff *skb; + unsigned char *p; + int hl; + int cnt = 0; + isdn_net_local *lp = is->lp; + + /* Alloc large enough skb */ + hl = dev->drv[lp->isdn_device]->interface->hl_hdrlen; + skb = alloc_skb(len + hl + 16, GFP_ATOMIC); + if (!skb) { + printk(KERN_WARNING + "ippp: CCP cannot send reset - out of memory\n"); + return; + } + skb_reserve(skb, hl); + + /* We may need to stuff an address and control field first */ + if (!(is->pppcfg & SC_COMP_AC)) { + p = skb_put(skb, 2); + *p++ = 0xff; + *p++ = 0x03; + } + + /* Stuff proto, code, id and length */ + p = skb_put(skb, 6); + *p++ = (proto >> 8); + *p++ = (proto & 0xff); + *p++ = code; + *p++ = id; + cnt = 4 + len; + *p++ = (cnt >> 8); + *p++ = (cnt & 0xff); + + /* Now stuff remaining bytes */ + if (len) { + p = skb_put(skb, len); + memcpy(p, data, len); + } + + /* skb is now ready for xmit */ + printk(KERN_DEBUG "Sending CCP Frame:\n"); + isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + + isdn_net_write_super(lp, skb); +} + +/* Allocate the reset state vector */ +static struct ippp_ccp_reset *isdn_ppp_ccp_reset_alloc(struct ippp_struct *is) +{ + struct ippp_ccp_reset *r; + r = kzalloc(sizeof(struct ippp_ccp_reset), GFP_KERNEL); + if (!r) { + printk(KERN_ERR "ippp_ccp: failed to allocate reset data" + " structure - no mem\n"); + return NULL; + } + printk(KERN_DEBUG "ippp_ccp: allocated reset data structure %p\n", r); + is->reset = r; + return r; +} + +/* Destroy the reset state vector. Kill all pending timers first. */ +static void isdn_ppp_ccp_reset_free(struct ippp_struct *is) +{ + unsigned int id; + + printk(KERN_DEBUG "ippp_ccp: freeing reset data structure %p\n", + is->reset); + for (id = 0; id < 256; id++) { + if (is->reset->rs[id]) { + isdn_ppp_ccp_reset_free_state(is, (unsigned char)id); + } + } + kfree(is->reset); + is->reset = NULL; +} + +/* Free a given state and clear everything up for later reallocation */ +static void isdn_ppp_ccp_reset_free_state(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs; + + if (is->reset->rs[id]) { + printk(KERN_DEBUG "ippp_ccp: freeing state for id %d\n", id); + rs = is->reset->rs[id]; + /* Make sure the kernel will not call back later */ + if (rs->ta) + del_timer(&rs->timer); + is->reset->rs[id] = NULL; + kfree(rs); + } else { + printk(KERN_WARNING "ippp_ccp: id %d is not allocated\n", id); + } +} + +/* The timer callback function which is called when a ResetReq has timed out, + aka has never been answered by a ResetAck */ +static void isdn_ppp_ccp_timer_callback(unsigned long closure) +{ + struct ippp_ccp_reset_state *rs = + (struct ippp_ccp_reset_state *)closure; + + if (!rs) { + printk(KERN_ERR "ippp_ccp: timer cb with zero closure.\n"); + return; + } + if (rs->ta && rs->state == CCPResetSentReq) { + /* We are correct here */ + if (!rs->expra) { + /* Hmm, there is no Ack really expected. We can clean + up the state now, it will be reallocated if the + decompressor insists on another reset */ + rs->ta = 0; + isdn_ppp_ccp_reset_free_state(rs->is, rs->id); + return; + } + printk(KERN_DEBUG "ippp_ccp: CCP Reset timed out for id %d\n", + rs->id); + /* Push it again */ + isdn_ppp_ccp_xmit_reset(rs->is, PPP_CCP, CCP_RESETREQ, rs->id, + rs->data, rs->dlen); + /* Restart timer */ + rs->timer.expires = jiffies + HZ * 5; + add_timer(&rs->timer); + } else { + printk(KERN_WARNING "ippp_ccp: timer cb in wrong state %d\n", + rs->state); + } +} + +/* Allocate a new reset transaction state */ +static struct ippp_ccp_reset_state *isdn_ppp_ccp_reset_alloc_state(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs; + if (is->reset->rs[id]) { + printk(KERN_WARNING "ippp_ccp: old state exists for id %d\n", + id); + return NULL; + } else { + rs = kzalloc(sizeof(struct ippp_ccp_reset_state), GFP_KERNEL); + if (!rs) + return NULL; + rs->state = CCPResetIdle; + rs->is = is; + rs->id = id; + init_timer(&rs->timer); + rs->timer.data = (unsigned long)rs; + rs->timer.function = isdn_ppp_ccp_timer_callback; + is->reset->rs[id] = rs; + } + return rs; +} + + +/* A decompressor wants a reset with a set of parameters - do what is + necessary to fulfill it */ +static void isdn_ppp_ccp_reset_trans(struct ippp_struct *is, + struct isdn_ppp_resetparams *rp) +{ + struct ippp_ccp_reset_state *rs; + + if (rp->valid) { + /* The decompressor defines parameters by itself */ + if (rp->rsend) { + /* And he wants us to send a request */ + if (!(rp->idval)) { + printk(KERN_ERR "ippp_ccp: decompressor must" + " specify reset id\n"); + return; + } + if (is->reset->rs[rp->id]) { + /* There is already a transaction in existence + for this id. May be still waiting for a + Ack or may be wrong. */ + rs = is->reset->rs[rp->id]; + if (rs->state == CCPResetSentReq && rs->ta) { + printk(KERN_DEBUG "ippp_ccp: reset" + " trans still in progress" + " for id %d\n", rp->id); + } else { + printk(KERN_WARNING "ippp_ccp: reset" + " trans in wrong state %d for" + " id %d\n", rs->state, rp->id); + } + } else { + /* Ok, this is a new transaction */ + printk(KERN_DEBUG "ippp_ccp: new trans for id" + " %d to be started\n", rp->id); + rs = isdn_ppp_ccp_reset_alloc_state(is, rp->id); + if (!rs) { + printk(KERN_ERR "ippp_ccp: out of mem" + " allocing ccp trans\n"); + return; + } + rs->state = CCPResetSentReq; + rs->expra = rp->expra; + if (rp->dtval) { + rs->dlen = rp->dlen; + memcpy(rs->data, rp->data, rp->dlen); + } + /* HACK TODO - add link comp here */ + isdn_ppp_ccp_xmit_reset(is, PPP_CCP, + CCP_RESETREQ, rs->id, + rs->data, rs->dlen); + /* Start the timer */ + rs->timer.expires = jiffies + 5 * HZ; + add_timer(&rs->timer); + rs->ta = 1; + } + } else { + printk(KERN_DEBUG "ippp_ccp: no reset sent\n"); + } + } else { + /* The reset params are invalid. The decompressor does not + care about them, so we just send the minimal requests + and increase ids only when an Ack is received for a + given id */ + if (is->reset->rs[is->reset->lastid]) { + /* There is already a transaction in existence + for this id. May be still waiting for a + Ack or may be wrong. */ + rs = is->reset->rs[is->reset->lastid]; + if (rs->state == CCPResetSentReq && rs->ta) { + printk(KERN_DEBUG "ippp_ccp: reset" + " trans still in progress" + " for id %d\n", rp->id); + } else { + printk(KERN_WARNING "ippp_ccp: reset" + " trans in wrong state %d for" + " id %d\n", rs->state, rp->id); + } + } else { + printk(KERN_DEBUG "ippp_ccp: new trans for id" + " %d to be started\n", is->reset->lastid); + rs = isdn_ppp_ccp_reset_alloc_state(is, + is->reset->lastid); + if (!rs) { + printk(KERN_ERR "ippp_ccp: out of mem" + " allocing ccp trans\n"); + return; + } + rs->state = CCPResetSentReq; + /* We always expect an Ack if the decompressor doesn't + know better */ + rs->expra = 1; + rs->dlen = 0; + /* HACK TODO - add link comp here */ + isdn_ppp_ccp_xmit_reset(is, PPP_CCP, CCP_RESETREQ, + rs->id, NULL, 0); + /* Start the timer */ + rs->timer.expires = jiffies + 5 * HZ; + add_timer(&rs->timer); + rs->ta = 1; + } + } +} + +/* An Ack was received for this id. This means we stop the timer and clean + up the state prior to calling the decompressors reset routine. */ +static void isdn_ppp_ccp_reset_ack_rcvd(struct ippp_struct *is, + unsigned char id) +{ + struct ippp_ccp_reset_state *rs = is->reset->rs[id]; + + if (rs) { + if (rs->ta && rs->state == CCPResetSentReq) { + /* Great, we are correct */ + if (!rs->expra) + printk(KERN_DEBUG "ippp_ccp: ResetAck received" + " for id %d but not expected\n", id); + } else { + printk(KERN_INFO "ippp_ccp: ResetAck received out of" + "sync for id %d\n", id); + } + if (rs->ta) { + rs->ta = 0; + del_timer(&rs->timer); + } + isdn_ppp_ccp_reset_free_state(is, id); + } else { + printk(KERN_INFO "ippp_ccp: ResetAck received for unknown id" + " %d\n", id); + } + /* Make sure the simple reset stuff uses a new id next time */ + is->reset->lastid++; +} + +/* + * decompress packet + * + * if master = 0, we're trying to uncompress an per-link compressed packet, + * as opposed to an compressed reconstructed-from-MPPP packet. + * proto is updated to protocol field of uncompressed packet. + * + * retval: decompressed packet, + * same packet if uncompressed, + * NULL if decompression error + */ + +static struct sk_buff *isdn_ppp_decompress(struct sk_buff *skb, struct ippp_struct *is, struct ippp_struct *master, + int *proto) +{ + void *stat = NULL; + struct isdn_ppp_compressor *ipc = NULL; + struct sk_buff *skb_out; + int len; + struct ippp_struct *ri; + struct isdn_ppp_resetparams rsparm; + unsigned char rsdata[IPPP_RESET_MAXDATABYTES]; + + if (!master) { + // per-link decompression + stat = is->link_decomp_stat; + ipc = is->link_decompressor; + ri = is; + } else { + stat = master->decomp_stat; + ipc = master->decompressor; + ri = master; + } + + if (!ipc) { + // no decompressor -> we can't decompress. + printk(KERN_DEBUG "ippp: no decompressor defined!\n"); + return skb; + } + BUG_ON(!stat); // if we have a compressor, stat has been set as well + + if ((master && *proto == PPP_COMP) || (!master && *proto == PPP_COMPFRAG)) { + // compressed packets are compressed by their protocol type + + // Set up reset params for the decompressor + memset(&rsparm, 0, sizeof(rsparm)); + rsparm.data = rsdata; + rsparm.maxdlen = IPPP_RESET_MAXDATABYTES; + + skb_out = dev_alloc_skb(is->mru + PPP_HDRLEN); + if (!skb_out) { + kfree_skb(skb); + printk(KERN_ERR "ippp: decomp memory allocation failure\n"); + return NULL; + } + len = ipc->decompress(stat, skb, skb_out, &rsparm); + kfree_skb(skb); + if (len <= 0) { + switch (len) { + case DECOMP_ERROR: + printk(KERN_INFO "ippp: decomp wants reset %s params\n", + rsparm.valid ? "with" : "without"); + + isdn_ppp_ccp_reset_trans(ri, &rsparm); + break; + case DECOMP_FATALERROR: + ri->pppcfg |= SC_DC_FERROR; + /* Kick ipppd to recognize the error */ + isdn_ppp_ccp_kickup(ri); + break; + } + kfree_skb(skb_out); + return NULL; + } + *proto = isdn_ppp_strip_proto(skb_out); + if (*proto < 0) { + kfree_skb(skb_out); + return NULL; + } + return skb_out; + } else { + // uncompressed packets are fed through the decompressor to + // update the decompressor state + ipc->incomp(stat, skb, *proto); + return skb; + } +} + +/* + * compress a frame + * type=0: normal/bundle compression + * =1: link compression + * returns original skb if we haven't compressed the frame + * and a new skb pointer if we've done it + */ +static struct sk_buff *isdn_ppp_compress(struct sk_buff *skb_in, int *proto, + struct ippp_struct *is, struct ippp_struct *master, int type) +{ + int ret; + int new_proto; + struct isdn_ppp_compressor *compressor; + void *stat; + struct sk_buff *skb_out; + + /* we do not compress control protocols */ + if (*proto < 0 || *proto > 0x3fff) { + return skb_in; + } + + if (type) { /* type=1 => Link compression */ + return skb_in; + } + else { + if (!master) { + compressor = is->compressor; + stat = is->comp_stat; + } + else { + compressor = master->compressor; + stat = master->comp_stat; + } + new_proto = PPP_COMP; + } + + if (!compressor) { + printk(KERN_ERR "isdn_ppp: No compressor set!\n"); + return skb_in; + } + if (!stat) { + printk(KERN_ERR "isdn_ppp: Compressor not initialized?\n"); + return skb_in; + } + + /* Allow for at least 150 % expansion (for now) */ + skb_out = alloc_skb(skb_in->len + skb_in->len / 2 + 32 + + skb_headroom(skb_in), GFP_ATOMIC); + if (!skb_out) + return skb_in; + skb_reserve(skb_out, skb_headroom(skb_in)); + + ret = (compressor->compress)(stat, skb_in, skb_out, *proto); + if (!ret) { + dev_kfree_skb(skb_out); + return skb_in; + } + + dev_kfree_skb(skb_in); + *proto = new_proto; + return skb_out; +} + +/* + * we received a CCP frame .. + * not a clean solution, but we MUST handle a few cases in the kernel + */ +static void isdn_ppp_receive_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, + struct sk_buff *skb, int proto) +{ + struct ippp_struct *is; + struct ippp_struct *mis; + int len; + struct isdn_ppp_resetparams rsparm; + unsigned char rsdata[IPPP_RESET_MAXDATABYTES]; + + printk(KERN_DEBUG "Received CCP frame from peer slot(%d)\n", + lp->ppp_slot); + if (lp->ppp_slot < 0 || lp->ppp_slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __func__, lp->ppp_slot); + return; + } + is = ippp_table[lp->ppp_slot]; + isdn_ppp_frame_log("ccp-rcv", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + + if (lp->master) { + int slot = ISDN_MASTER_PRIV(lp)->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __func__, slot); + return; + } + mis = ippp_table[slot]; + } else + mis = is; + + switch (skb->data[0]) { + case CCP_CONFREQ: + if (is->debug & 0x10) + printk(KERN_DEBUG "Disable compression here!\n"); + if (proto == PPP_CCP) + mis->compflags &= ~SC_COMP_ON; + else + is->compflags &= ~SC_LINK_COMP_ON; + break; + case CCP_TERMREQ: + case CCP_TERMACK: + if (is->debug & 0x10) + printk(KERN_DEBUG "Disable (de)compression here!\n"); + if (proto == PPP_CCP) + mis->compflags &= ~(SC_DECOMP_ON | SC_COMP_ON); + else + is->compflags &= ~(SC_LINK_DECOMP_ON | SC_LINK_COMP_ON); + break; + case CCP_CONFACK: + /* if we RECEIVE an ackowledge we enable the decompressor */ + if (is->debug & 0x10) + printk(KERN_DEBUG "Enable decompression here!\n"); + if (proto == PPP_CCP) { + if (!mis->decompressor) + break; + mis->compflags |= SC_DECOMP_ON; + } else { + if (!is->decompressor) + break; + is->compflags |= SC_LINK_DECOMP_ON; + } + break; + + case CCP_RESETACK: + printk(KERN_DEBUG "Received ResetAck from peer\n"); + len = (skb->data[2] << 8) | skb->data[3]; + len -= 4; + + if (proto == PPP_CCP) { + /* If a reset Ack was outstanding for this id, then + clean up the state engine */ + isdn_ppp_ccp_reset_ack_rcvd(mis, skb->data[1]); + if (mis->decompressor && mis->decomp_stat) + mis->decompressor-> + reset(mis->decomp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, NULL); + /* TODO: This is not easy to decide here */ + mis->compflags &= ~SC_DECOMP_DISCARD; + } + else { + isdn_ppp_ccp_reset_ack_rcvd(is, skb->data[1]); + if (is->link_decompressor && is->link_decomp_stat) + is->link_decompressor-> + reset(is->link_decomp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, NULL); + /* TODO: neither here */ + is->compflags &= ~SC_LINK_DECOMP_DISCARD; + } + break; + + case CCP_RESETREQ: + printk(KERN_DEBUG "Received ResetReq from peer\n"); + /* Receiving a ResetReq means we must reset our compressor */ + /* Set up reset params for the reset entry */ + memset(&rsparm, 0, sizeof(rsparm)); + rsparm.data = rsdata; + rsparm.maxdlen = IPPP_RESET_MAXDATABYTES; + /* Isolate data length */ + len = (skb->data[2] << 8) | skb->data[3]; + len -= 4; + if (proto == PPP_CCP) { + if (mis->compressor && mis->comp_stat) + mis->compressor-> + reset(mis->comp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, &rsparm); + } + else { + if (is->link_compressor && is->link_comp_stat) + is->link_compressor-> + reset(is->link_comp_stat, + skb->data[0], + skb->data[1], + len ? &skb->data[4] : NULL, + len, &rsparm); + } + /* Ack the Req as specified by rsparm */ + if (rsparm.valid) { + /* Compressor reset handler decided how to answer */ + if (rsparm.rsend) { + /* We should send a Frame */ + isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK, + rsparm.idval ? rsparm.id + : skb->data[1], + rsparm.dtval ? + rsparm.data : NULL, + rsparm.dtval ? + rsparm.dlen : 0); + } else { + printk(KERN_DEBUG "ResetAck suppressed\n"); + } + } else { + /* We answer with a straight reflected Ack */ + isdn_ppp_ccp_xmit_reset(is, proto, CCP_RESETACK, + skb->data[1], + len ? &skb->data[4] : NULL, + len); + } + break; + } +} + + +/* + * Daemon sends a CCP frame ... + */ + +/* TODO: Clean this up with new Reset semantics */ + +/* I believe the CCP handling as-is is done wrong. Compressed frames + * should only be sent/received after CCP reaches UP state, which means + * both sides have sent CONF_ACK. Currently, we handle both directions + * independently, which means we may accept compressed frames too early + * (supposedly not a problem), but may also mean we send compressed frames + * too early, which may turn out to be a problem. + * This part of state machine should actually be handled by (i)pppd, but + * that's too big of a change now. --kai + */ + +/* Actually, we might turn this into an advantage: deal with the RFC in + * the old tradition of beeing generous on what we accept, but beeing + * strict on what we send. Thus we should just + * - accept compressed frames as soon as decompression is negotiated + * - send compressed frames only when decomp *and* comp are negotiated + * - drop rx compressed frames if we cannot decomp (instead of pushing them + * up to ipppd) + * and I tried to modify this file according to that. --abp + */ + +static void isdn_ppp_send_ccp(isdn_net_dev *net_dev, isdn_net_local *lp, struct sk_buff *skb) +{ + struct ippp_struct *mis, *is; + int proto, slot = lp->ppp_slot; + unsigned char *data; + + if (!skb || skb->len < 3) + return; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: lp->ppp_slot(%d) out of range\n", + __func__, slot); + return; + } + is = ippp_table[slot]; + /* Daemon may send with or without address and control field comp */ + data = skb->data; + if (!(is->pppcfg & SC_COMP_AC) && data[0] == 0xff && data[1] == 0x03) { + data += 2; + if (skb->len < 5) + return; + } + + proto = ((int)data[0]<<8) + data[1]; + if (proto != PPP_CCP && proto != PPP_CCPFRAG) + return; + + printk(KERN_DEBUG "Received CCP frame from daemon:\n"); + isdn_ppp_frame_log("ccp-xmit", skb->data, skb->len, 32, is->unit, lp->ppp_slot); + + if (lp->master) { + slot = ISDN_MASTER_PRIV(lp)->ppp_slot; + if (slot < 0 || slot >= ISDN_MAX_CHANNELS) { + printk(KERN_ERR "%s: slot(%d) out of range\n", + __func__, slot); + return; + } + mis = ippp_table[slot]; + } else + mis = is; + if (mis != is) + printk(KERN_DEBUG "isdn_ppp: Ouch! Master CCP sends on slave slot!\n"); + + switch (data[2]) { + case CCP_CONFREQ: + if (is->debug & 0x10) + printk(KERN_DEBUG "Disable decompression here!\n"); + if (proto == PPP_CCP) + is->compflags &= ~SC_DECOMP_ON; + else + is->compflags &= ~SC_LINK_DECOMP_ON; + break; + case CCP_TERMREQ: + case CCP_TERMACK: + if (is->debug & 0x10) + printk(KERN_DEBUG "Disable (de)compression here!\n"); + if (proto == PPP_CCP) + is->compflags &= ~(SC_DECOMP_ON | SC_COMP_ON); + else + is->compflags &= ~(SC_LINK_DECOMP_ON | SC_LINK_COMP_ON); + break; + case CCP_CONFACK: + /* if we SEND an ackowledge we can/must enable the compressor */ + if (is->debug & 0x10) + printk(KERN_DEBUG "Enable compression here!\n"); + if (proto == PPP_CCP) { + if (!is->compressor) + break; + is->compflags |= SC_COMP_ON; + } else { + if (!is->compressor) + break; + is->compflags |= SC_LINK_COMP_ON; + } + break; + case CCP_RESETACK: + /* If we send a ACK we should reset our compressor */ + if (is->debug & 0x10) + printk(KERN_DEBUG "Reset decompression state here!\n"); + printk(KERN_DEBUG "ResetAck from daemon passed by\n"); + if (proto == PPP_CCP) { + /* link to master? */ + if (is->compressor && is->comp_stat) + is->compressor->reset(is->comp_stat, 0, 0, + NULL, 0, NULL); + is->compflags &= ~SC_COMP_DISCARD; + } + else { + if (is->link_compressor && is->link_comp_stat) + is->link_compressor->reset(is->link_comp_stat, + 0, 0, NULL, 0, NULL); + is->compflags &= ~SC_LINK_COMP_DISCARD; + } + break; + case CCP_RESETREQ: + /* Just let it pass by */ + printk(KERN_DEBUG "ResetReq from daemon passed by\n"); + break; + } +} + +int isdn_ppp_register_compressor(struct isdn_ppp_compressor *ipc) +{ + ipc->next = ipc_head; + ipc->prev = NULL; + if (ipc_head) { + ipc_head->prev = ipc; + } + ipc_head = ipc; + return 0; +} + +int isdn_ppp_unregister_compressor(struct isdn_ppp_compressor *ipc) +{ + if (ipc->prev) + ipc->prev->next = ipc->next; + else + ipc_head = ipc->next; + if (ipc->next) + ipc->next->prev = ipc->prev; + ipc->prev = ipc->next = NULL; + return 0; +} + +static int isdn_ppp_set_compressor(struct ippp_struct *is, struct isdn_ppp_comp_data *data) +{ + struct isdn_ppp_compressor *ipc = ipc_head; + int ret; + void *stat; + int num = data->num; + + if (is->debug & 0x10) + printk(KERN_DEBUG "[%d] Set %s type %d\n", is->unit, + (data->flags & IPPP_COMP_FLAG_XMIT) ? "compressor" : "decompressor", num); + + /* If is has no valid reset state vector, we cannot allocate a + decompressor. The decompressor would cause reset transactions + sooner or later, and they need that vector. */ + + if (!(data->flags & IPPP_COMP_FLAG_XMIT) && !is->reset) { + printk(KERN_ERR "ippp_ccp: no reset data structure - can't" + " allow decompression.\n"); + return -ENOMEM; + } + + while (ipc) { + if (ipc->num == num) { + stat = ipc->alloc(data); + if (stat) { + ret = ipc->init(stat, data, is->unit, 0); + if (!ret) { + printk(KERN_ERR "Can't init (de)compression!\n"); + ipc->free(stat); + stat = NULL; + break; + } + } + else { + printk(KERN_ERR "Can't alloc (de)compression!\n"); + break; + } + + if (data->flags & IPPP_COMP_FLAG_XMIT) { + if (data->flags & IPPP_COMP_FLAG_LINK) { + if (is->link_comp_stat) + is->link_compressor->free(is->link_comp_stat); + is->link_comp_stat = stat; + is->link_compressor = ipc; + } + else { + if (is->comp_stat) + is->compressor->free(is->comp_stat); + is->comp_stat = stat; + is->compressor = ipc; + } + } + else { + if (data->flags & IPPP_COMP_FLAG_LINK) { + if (is->link_decomp_stat) + is->link_decompressor->free(is->link_decomp_stat); + is->link_decomp_stat = stat; + is->link_decompressor = ipc; + } + else { + if (is->decomp_stat) + is->decompressor->free(is->decomp_stat); + is->decomp_stat = stat; + is->decompressor = ipc; + } + } + return 0; + } + ipc = ipc->next; + } + return -EINVAL; +} diff --git a/kernel/drivers/isdn/i4l/isdn_ppp.h b/kernel/drivers/isdn/i4l/isdn_ppp.h new file mode 100644 index 000000000..4e9b8935a --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_ppp.h @@ -0,0 +1,41 @@ +/* $Id: isdn_ppp.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, functions for synchronous PPP (linklevel). + * + * Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/ppp_defs.h> /* for PPP_PROTOCOL */ +#include <linux/isdn_ppp.h> /* for isdn_ppp info */ + +extern int isdn_ppp_read(int, struct file *, char __user *, int); +extern int isdn_ppp_write(int, struct file *, const char __user *, int); +extern int isdn_ppp_open(int, struct file *); +extern int isdn_ppp_init(void); +extern void isdn_ppp_cleanup(void); +extern int isdn_ppp_free(isdn_net_local *); +extern int isdn_ppp_bind(isdn_net_local *); +extern int isdn_ppp_autodial_filter(struct sk_buff *, isdn_net_local *); +extern int isdn_ppp_xmit(struct sk_buff *, struct net_device *); +extern void isdn_ppp_receive(isdn_net_dev *, isdn_net_local *, struct sk_buff *); +extern int isdn_ppp_dev_ioctl(struct net_device *, struct ifreq *, int); +extern unsigned int isdn_ppp_poll(struct file *, struct poll_table_struct *); +extern int isdn_ppp_ioctl(int, struct file *, unsigned int, unsigned long); +extern void isdn_ppp_release(int, struct file *); +extern int isdn_ppp_dial_slave(char *); +extern void isdn_ppp_wakeup_daemon(isdn_net_local *); + +extern int isdn_ppp_register_compressor(struct isdn_ppp_compressor *ipc); +extern int isdn_ppp_unregister_compressor(struct isdn_ppp_compressor *ipc); + +#define IPPP_OPEN 0x01 +#define IPPP_CONNECT 0x02 +#define IPPP_CLOSEWAIT 0x04 +#define IPPP_NOBLOCK 0x08 +#define IPPP_ASSIGNED 0x10 + +#define IPPP_MAX_HEADER 10 diff --git a/kernel/drivers/isdn/i4l/isdn_tty.c b/kernel/drivers/isdn/i4l/isdn_tty.c new file mode 100644 index 000000000..bc912611f --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_tty.c @@ -0,0 +1,3778 @@ +/* + * Linux ISDN subsystem, tty functions and AT-command emulator (linklevel). + * + * Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de) + * Copyright 1995,96 by Thinking Objects Software GmbH Wuerzburg + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ +#undef ISDN_TTY_STAT_DEBUG + +#include <linux/isdn.h> +#include <linux/serial.h> /* ASYNC_* flags */ +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include "isdn_common.h" +#include "isdn_tty.h" +#ifdef CONFIG_ISDN_AUDIO +#include "isdn_audio.h" +#define VBUF 0x3e0 +#define VBUFX (VBUF/16) +#endif + +#define FIX_FILE_TRANSFER +#define DUMMY_HAYES_AT + +/* Prototypes */ + +static DEFINE_MUTEX(modem_info_mutex); +static int isdn_tty_edit_at(const char *, int, modem_info *); +static void isdn_tty_check_esc(const u_char *, u_char, int, int *, u_long *); +static void isdn_tty_modem_reset_regs(modem_info *, int); +static void isdn_tty_cmd_ATA(modem_info *); +static void isdn_tty_flush_buffer(struct tty_struct *); +static void isdn_tty_modem_result(int, modem_info *); +#ifdef CONFIG_ISDN_AUDIO +static int isdn_tty_countDLE(unsigned char *, int); +#endif + +/* Leave this unchanged unless you know what you do! */ +#define MODEM_PARANOIA_CHECK +#define MODEM_DO_RESTART + +static int bit2si[8] = +{1, 5, 7, 7, 7, 7, 7, 7}; +static int si2bit[8] = +{4, 1, 4, 4, 4, 4, 4, 4}; + +/* isdn_tty_try_read() is called from within isdn_tty_rcv_skb() + * to stuff incoming data directly into a tty's flip-buffer. This + * is done to speed up tty-receiving if the receive-queue is empty. + * This routine MUST be called with interrupts off. + * Return: + * 1 = Success + * 0 = Failure, data has to be buffered and later processed by + * isdn_tty_readmodem(). + */ +static int +isdn_tty_try_read(modem_info *info, struct sk_buff *skb) +{ + struct tty_port *port = &info->port; + int c; + int len; + char last; + + if (!info->online) + return 0; + + if (!(info->mcr & UART_MCR_RTS)) + return 0; + + len = skb->len +#ifdef CONFIG_ISDN_AUDIO + + ISDN_AUDIO_SKB_DLECOUNT(skb) +#endif + ; + + c = tty_buffer_request_room(port, len); + if (c < len) + return 0; + +#ifdef CONFIG_ISDN_AUDIO + if (ISDN_AUDIO_SKB_DLECOUNT(skb)) { + int l = skb->len; + unsigned char *dp = skb->data; + while (--l) { + if (*dp == DLE) + tty_insert_flip_char(port, DLE, 0); + tty_insert_flip_char(port, *dp++, 0); + } + if (*dp == DLE) + tty_insert_flip_char(port, DLE, 0); + last = *dp; + } else { +#endif + if (len > 1) + tty_insert_flip_string(port, skb->data, len - 1); + last = skb->data[len - 1]; +#ifdef CONFIG_ISDN_AUDIO + } +#endif + if (info->emu.mdmreg[REG_CPPP] & BIT_CPPP) + tty_insert_flip_char(port, last, 0xFF); + else + tty_insert_flip_char(port, last, TTY_NORMAL); + tty_flip_buffer_push(port); + kfree_skb(skb); + + return 1; +} + +/* isdn_tty_readmodem() is called periodically from within timer-interrupt. + * It tries getting received data from the receive queue an stuff it into + * the tty's flip-buffer. + */ +void +isdn_tty_readmodem(void) +{ + int resched = 0; + int midx; + int i; + int r; + modem_info *info; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + midx = dev->m_idx[i]; + if (midx < 0) + continue; + + info = &dev->mdm.info[midx]; + if (!info->online) + continue; + + r = 0; +#ifdef CONFIG_ISDN_AUDIO + isdn_audio_eval_dtmf(info); + if ((info->vonline & 1) && (info->emu.vpar[1])) + isdn_audio_eval_silence(info); +#endif + if (info->mcr & UART_MCR_RTS) { + /* CISCO AsyncPPP Hack */ + if (!(info->emu.mdmreg[REG_CPPP] & BIT_CPPP)) + r = isdn_readbchan_tty(info->isdn_driver, + info->isdn_channel, + &info->port, 0); + else + r = isdn_readbchan_tty(info->isdn_driver, + info->isdn_channel, + &info->port, 1); + if (r) + tty_flip_buffer_push(&info->port); + } else + r = 1; + + if (r) { + info->rcvsched = 0; + resched = 1; + } else + info->rcvsched = 1; + } + if (!resched) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 0); +} + +int +isdn_tty_rcv_skb(int i, int di, int channel, struct sk_buff *skb) +{ + ulong flags; + int midx; +#ifdef CONFIG_ISDN_AUDIO + int ifmt; +#endif + modem_info *info; + + if ((midx = dev->m_idx[i]) < 0) { + /* if midx is invalid, packet is not for tty */ + return 0; + } + info = &dev->mdm.info[midx]; +#ifdef CONFIG_ISDN_AUDIO + ifmt = 1; + + if ((info->vonline) && (!info->emu.vpar[4])) + isdn_audio_calc_dtmf(info, skb->data, skb->len, ifmt); + if ((info->vonline & 1) && (info->emu.vpar[1])) + isdn_audio_calc_silence(info, skb->data, skb->len, ifmt); +#endif + if ((info->online < 2) +#ifdef CONFIG_ISDN_AUDIO + && (!(info->vonline & 1)) +#endif + ) { + /* If Modem not listening, drop data */ + kfree_skb(skb); + return 1; + } + if (info->emu.mdmreg[REG_T70] & BIT_T70) { + if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT) { + /* T.70 decoding: throw away the T.70 header (2 or 4 bytes) */ + if (skb->data[0] == 3) /* pure data packet -> 4 byte headers */ + skb_pull(skb, 4); + else + if (skb->data[0] == 1) /* keepalive packet -> 2 byte hdr */ + skb_pull(skb, 2); + } else + /* T.70 decoding: Simply throw away the T.70 header (4 bytes) */ + if ((skb->data[0] == 1) && ((skb->data[1] == 0) || (skb->data[1] == 1))) + skb_pull(skb, 4); + } +#ifdef CONFIG_ISDN_AUDIO + ISDN_AUDIO_SKB_DLECOUNT(skb) = 0; + ISDN_AUDIO_SKB_LOCK(skb) = 0; + if (info->vonline & 1) { + /* voice conversion/compression */ + switch (info->emu.vpar[3]) { + case 2: + case 3: + case 4: + /* adpcm + * Since compressed data takes less + * space, we can overwrite the buffer. + */ + skb_trim(skb, isdn_audio_xlaw2adpcm(info->adpcmr, + ifmt, + skb->data, + skb->data, + skb->len)); + break; + case 5: + /* a-law */ + if (!ifmt) + isdn_audio_ulaw2alaw(skb->data, skb->len); + break; + case 6: + /* u-law */ + if (ifmt) + isdn_audio_alaw2ulaw(skb->data, skb->len); + break; + } + ISDN_AUDIO_SKB_DLECOUNT(skb) = + isdn_tty_countDLE(skb->data, skb->len); + } +#ifdef CONFIG_ISDN_TTY_FAX + else { + if (info->faxonline & 2) { + isdn_tty_fax_bitorder(info, skb); + ISDN_AUDIO_SKB_DLECOUNT(skb) = + isdn_tty_countDLE(skb->data, skb->len); + } + } +#endif +#endif + /* Try to deliver directly via tty-buf if queue is empty */ + spin_lock_irqsave(&info->readlock, flags); + if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) + if (isdn_tty_try_read(info, skb)) { + spin_unlock_irqrestore(&info->readlock, flags); + return 1; + } + /* Direct deliver failed or queue wasn't empty. + * Queue up for later dequeueing via timer-irq. + */ + __skb_queue_tail(&dev->drv[di]->rpqueue[channel], skb); + dev->drv[di]->rcvcount[channel] += + (skb->len +#ifdef CONFIG_ISDN_AUDIO + + ISDN_AUDIO_SKB_DLECOUNT(skb) +#endif + ); + spin_unlock_irqrestore(&info->readlock, flags); + /* Schedule dequeuing */ + if ((dev->modempoll) && (info->rcvsched)) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + return 1; +} + +static void +isdn_tty_cleanup_xmit(modem_info *info) +{ + skb_queue_purge(&info->xmit_queue); +#ifdef CONFIG_ISDN_AUDIO + skb_queue_purge(&info->dtmf_queue); +#endif +} + +static void +isdn_tty_tint(modem_info *info) +{ + struct sk_buff *skb = skb_dequeue(&info->xmit_queue); + int len, slen; + + if (!skb) + return; + len = skb->len; + if ((slen = isdn_writebuf_skb_stub(info->isdn_driver, + info->isdn_channel, 1, skb)) == len) { + struct tty_struct *tty = info->port.tty; + info->send_outstanding++; + info->msr &= ~UART_MSR_CTS; + info->lsr &= ~UART_LSR_TEMT; + tty_wakeup(tty); + return; + } + if (slen < 0) { + /* Error: no channel, already shutdown, or wrong parameter */ + dev_kfree_skb(skb); + return; + } + skb_queue_head(&info->xmit_queue, skb); +} + +#ifdef CONFIG_ISDN_AUDIO +static int +isdn_tty_countDLE(unsigned char *buf, int len) +{ + int count = 0; + + while (len--) + if (*buf++ == DLE) + count++; + return count; +} + +/* This routine is called from within isdn_tty_write() to perform + * DLE-decoding when sending audio-data. + */ +static int +isdn_tty_handleDLEdown(modem_info *info, atemu *m, int len) +{ + unsigned char *p = &info->port.xmit_buf[info->xmit_count]; + int count = 0; + + while (len > 0) { + if (m->lastDLE) { + m->lastDLE = 0; + switch (*p) { + case DLE: + /* Escape code */ + if (len > 1) + memmove(p, p + 1, len - 1); + p--; + count++; + break; + case ETX: + /* End of data */ + info->vonline |= 4; + return count; + case DC4: + /* Abort RX */ + info->vonline &= ~1; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "DLEdown: got DLE-DC4, send DLE-ETX on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\020\003", info); + if (!info->vonline) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "DLEdown: send VCON on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\r\nVCON\r\n", info); + } + /* Fall through */ + case 'q': + case 's': + /* Silence */ + if (len > 1) + memmove(p, p + 1, len - 1); + p--; + break; + } + } else { + if (*p == DLE) + m->lastDLE = 1; + else + count++; + } + p++; + len--; + } + if (len < 0) { + printk(KERN_WARNING "isdn_tty: len<0 in DLEdown\n"); + return 0; + } + return count; +} + +/* This routine is called from within isdn_tty_write() when receiving + * audio-data. It interrupts receiving, if an character other than + * ^S or ^Q is sent. + */ +static int +isdn_tty_end_vrx(const char *buf, int c) +{ + char ch; + + while (c--) { + ch = *buf; + if ((ch != 0x11) && (ch != 0x13)) + return 1; + buf++; + } + return 0; +} + +static int voice_cf[7] = +{0, 0, 4, 3, 2, 0, 0}; + +#endif /* CONFIG_ISDN_AUDIO */ + +/* isdn_tty_senddown() is called either directly from within isdn_tty_write() + * or via timer-interrupt from within isdn_tty_modem_xmit(). It pulls + * outgoing data from the tty's xmit-buffer, handles voice-decompression or + * T.70 if necessary, and finally queues it up for sending via isdn_tty_tint. + */ +static void +isdn_tty_senddown(modem_info *info) +{ + int buflen; + int skb_res; +#ifdef CONFIG_ISDN_AUDIO + int audio_len; +#endif + struct sk_buff *skb; + +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 4) { + info->vonline &= ~6; + if (!info->vonline) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "senddown: send VCON on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\r\nVCON\r\n", info); + } + } +#endif + if (!(buflen = info->xmit_count)) + return; + if ((info->emu.mdmreg[REG_CTS] & BIT_CTS) != 0) + info->msr &= ~UART_MSR_CTS; + info->lsr &= ~UART_LSR_TEMT; + /* info->xmit_count is modified here and in isdn_tty_write(). + * So we return here if isdn_tty_write() is in the + * critical section. + */ + atomic_inc(&info->xmit_lock); + if (!(atomic_dec_and_test(&info->xmit_lock))) + return; + if (info->isdn_driver < 0) { + info->xmit_count = 0; + return; + } + skb_res = dev->drv[info->isdn_driver]->interface->hl_hdrlen + 4; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 2) + audio_len = buflen * voice_cf[info->emu.vpar[3]]; + else + audio_len = 0; + skb = dev_alloc_skb(skb_res + buflen + audio_len); +#else + skb = dev_alloc_skb(skb_res + buflen); +#endif + if (!skb) { + printk(KERN_WARNING + "isdn_tty: Out of memory in ttyI%d senddown\n", + info->line); + return; + } + skb_reserve(skb, skb_res); + memcpy(skb_put(skb, buflen), info->port.xmit_buf, buflen); + info->xmit_count = 0; +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 2) { + /* For now, ifmt is fixed to 1 (alaw), since this + * is used with ISDN everywhere in the world, except + * US, Canada and Japan. + * Later, when US-ISDN protocols are implemented, + * this setting will depend on the D-channel protocol. + */ + int ifmt = 1; + + /* voice conversion/decompression */ + switch (info->emu.vpar[3]) { + case 2: + case 3: + case 4: + /* adpcm, compatible to ZyXel 1496 modem + * with ROM revision 6.01 + */ + audio_len = isdn_audio_adpcm2xlaw(info->adpcms, + ifmt, + skb->data, + skb_put(skb, audio_len), + buflen); + skb_pull(skb, buflen); + skb_trim(skb, audio_len); + break; + case 5: + /* a-law */ + if (!ifmt) + isdn_audio_alaw2ulaw(skb->data, + buflen); + break; + case 6: + /* u-law */ + if (ifmt) + isdn_audio_ulaw2alaw(skb->data, + buflen); + break; + } + } +#endif /* CONFIG_ISDN_AUDIO */ + if (info->emu.mdmreg[REG_T70] & BIT_T70) { + /* Add T.70 simplified header */ + if (info->emu.mdmreg[REG_T70] & BIT_T70_EXT) + memcpy(skb_push(skb, 2), "\1\0", 2); + else + memcpy(skb_push(skb, 4), "\1\0\1\0", 4); + } + skb_queue_tail(&info->xmit_queue, skb); +} + +/************************************************************ + * + * Modem-functions + * + * mostly "stolen" from original Linux-serial.c and friends. + * + ************************************************************/ + +/* The next routine is called once from within timer-interrupt + * triggered within isdn_tty_modem_ncarrier(). It calls + * isdn_tty_modem_result() to stuff a "NO CARRIER" Message + * into the tty's buffer. + */ +static void +isdn_tty_modem_do_ncarrier(unsigned long data) +{ + modem_info *info = (modem_info *) data; + isdn_tty_modem_result(RESULT_NO_CARRIER, info); +} + +/* Next routine is called, whenever the DTR-signal is raised. + * It checks the ncarrier-flag, and triggers the above routine + * when necessary. The ncarrier-flag is set, whenever DTR goes + * low. + */ +static void +isdn_tty_modem_ncarrier(modem_info *info) +{ + if (info->ncarrier) { + info->nc_timer.expires = jiffies + HZ; + add_timer(&info->nc_timer); + } +} + +/* + * return the usage calculated by si and layer 2 protocol + */ +static int +isdn_calc_usage(int si, int l2) +{ + int usg = ISDN_USAGE_MODEM; + +#ifdef CONFIG_ISDN_AUDIO + if (si == 1) { + switch (l2) { + case ISDN_PROTO_L2_MODEM: + usg = ISDN_USAGE_MODEM; + break; +#ifdef CONFIG_ISDN_TTY_FAX + case ISDN_PROTO_L2_FAX: + usg = ISDN_USAGE_FAX; + break; +#endif + case ISDN_PROTO_L2_TRANS: + default: + usg = ISDN_USAGE_VOICE; + break; + } + } +#endif + return (usg); +} + +/* isdn_tty_dial() performs dialing of a tty an the necessary + * setup of the lower levels before that. + */ +static void +isdn_tty_dial(char *n, modem_info *info, atemu *m) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[REG_L2PROT]; + u_long flags; + isdn_ctrl cmd; + int i; + int j; + + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, info); + } else { + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + dev->usage[i] |= ISDN_USAGE_OUTGOING; + info->last_dir = 1; + strcpy(info->last_num, n); + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); +#ifdef CONFIG_ISDN_TTY_FAX + if (l2 == ISDN_PROTO_L2_FAX) { + cmd.parm.fax = info->fax; + info->fax->direction = ISDN_TTY_FAX_CONN_OUT; + } +#endif + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + sprintf(cmd.parm.setup.phone, "%s", n); + sprintf(cmd.parm.setup.eazmsn, "%s", + isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.parm.setup.si1 = si; + cmd.parm.setup.si2 = m->mdmreg[REG_SI2]; + cmd.command = ISDN_CMD_DIAL; + info->dialing = 1; + info->emu.carrierwait = 0; + strcpy(dev->num[i], n); + isdn_info_update(); + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } +} + +/* isdn_tty_hangup() disassociates a tty from the real + * ISDN-line (hangup). The usage-status is cleared + * and some cleanup is done also. + */ +void +isdn_tty_modem_hup(modem_info *info, int local) +{ + isdn_ctrl cmd; + int di, ch; + + if (!info) + return; + + di = info->isdn_driver; + ch = info->isdn_channel; + if (di < 0 || ch < 0) + return; + + info->isdn_driver = -1; + info->isdn_channel = -1; + +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup ttyI%d\n", info->line); +#endif + info->rcvsched = 0; + isdn_tty_flush_buffer(info->port.tty); + if (info->online) { + info->last_lhup = local; + info->online = 0; + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + } +#ifdef CONFIG_ISDN_AUDIO + info->vonline = 0; +#ifdef CONFIG_ISDN_TTY_FAX + info->faxonline = 0; + info->fax->phase = ISDN_FAX_PHASE_IDLE; +#endif + info->emu.vpar[4] = 0; + info->emu.vpar[5] = 8; + kfree(info->dtmf_state); + info->dtmf_state = NULL; + kfree(info->silence_state); + info->silence_state = NULL; + kfree(info->adpcms); + info->adpcms = NULL; + kfree(info->adpcmr); + info->adpcmr = NULL; +#endif + if ((info->msr & UART_MSR_RI) && + (info->emu.mdmreg[REG_RUNG] & BIT_RUNG)) + isdn_tty_modem_result(RESULT_RUNG, info); + info->msr &= ~(UART_MSR_DCD | UART_MSR_RI); + info->lsr |= UART_LSR_TEMT; + + if (local) { + cmd.driver = di; + cmd.command = ISDN_CMD_HANGUP; + cmd.arg = ch; + isdn_command(&cmd); + } + + isdn_all_eaz(di, ch); + info->emu.mdmreg[REG_RINGCNT] = 0; + isdn_free_channel(di, ch, 0); + + if (info->drv_index >= 0) { + dev->m_idx[info->drv_index] = -1; + info->drv_index = -1; + } +} + +/* + * Begin of a CAPI like interface, currently used only for + * supplementary service (CAPI 2.0 part III) + */ +#include <linux/isdn/capicmd.h> +#include <linux/module.h> + +int +isdn_tty_capi_facility(capi_msg *cm) { + return (-1); /* dummy */ +} + +/* isdn_tty_suspend() tries to suspend the current tty connection + */ +static void +isdn_tty_suspend(char *id, modem_info *info, atemu *m) +{ + isdn_ctrl cmd; + + int l; + + if (!info) + return; + +#ifdef ISDN_DEBUG_MODEM_SERVICES + printk(KERN_DEBUG "Msusp ttyI%d\n", info->line); +#endif + l = strlen(id); + if ((info->isdn_driver >= 0)) { + cmd.parm.cmsg.Length = l + 18; + cmd.parm.cmsg.Command = CAPI_FACILITY; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */ + cmd.parm.cmsg.para[1] = 0; + cmd.parm.cmsg.para[2] = l + 3; + cmd.parm.cmsg.para[3] = 4; /* 16 bit 0x0004 Suspend */ + cmd.parm.cmsg.para[4] = 0; + cmd.parm.cmsg.para[5] = l; + strncpy(&cmd.parm.cmsg.para[6], id, l); + cmd.command = CAPI_PUT_MESSAGE; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + isdn_command(&cmd); + } +} + +/* isdn_tty_resume() tries to resume a suspended call + * setup of the lower levels before that. unfortunately here is no + * checking for compatibility of used protocols implemented by Q931 + * It does the same things like isdn_tty_dial, the last command + * is different, may be we can merge it. + */ + +static void +isdn_tty_resume(char *id, modem_info *info, atemu *m) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[REG_L2PROT]; + isdn_ctrl cmd; + ulong flags; + int i; + int j; + int l; + + l = strlen(id); + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, info); + } else { + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + dev->usage[i] |= ISDN_USAGE_OUTGOING; + info->last_dir = 1; +// strcpy(info->last_num, n); + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.parm.cmsg.Length = l + 18; + cmd.parm.cmsg.Command = CAPI_FACILITY; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = 3; /* 16 bit 0x0003 suplementary service */ + cmd.parm.cmsg.para[1] = 0; + cmd.parm.cmsg.para[2] = l + 3; + cmd.parm.cmsg.para[3] = 5; /* 16 bit 0x0005 Resume */ + cmd.parm.cmsg.para[4] = 0; + cmd.parm.cmsg.para[5] = l; + strncpy(&cmd.parm.cmsg.para[6], id, l); + cmd.command = CAPI_PUT_MESSAGE; + info->dialing = 1; +// strcpy(dev->num[i], n); + isdn_info_update(); + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } +} + +/* isdn_tty_send_msg() sends a message to a HL driver + * This is used for hybrid modem cards to send AT commands to it + */ + +static void +isdn_tty_send_msg(modem_info *info, atemu *m, char *msg) +{ + int usg = ISDN_USAGE_MODEM; + int si = 7; + int l2 = m->mdmreg[REG_L2PROT]; + isdn_ctrl cmd; + ulong flags; + int i; + int j; + int l; + + l = min(strlen(msg), sizeof(cmd.parm) - sizeof(cmd.parm.cmsg) + + sizeof(cmd.parm.cmsg.para) - 2); + + if (!l) { + isdn_tty_modem_result(RESULT_ERROR, info); + return; + } + for (j = 7; j >= 0; j--) + if (m->mdmreg[REG_SI1] & (1 << j)) { + si = bit2si[j]; + break; + } + usg = isdn_calc_usage(si, l2); +#ifdef CONFIG_ISDN_AUDIO + if ((si == 1) && + (l2 != ISDN_PROTO_L2_MODEM) +#ifdef CONFIG_ISDN_TTY_FAX + && (l2 != ISDN_PROTO_L2_FAX) +#endif + ) { + l2 = ISDN_PROTO_L2_TRANS; + usg = ISDN_USAGE_VOICE; + } +#endif + m->mdmreg[REG_SI1I] = si2bit[si]; + spin_lock_irqsave(&dev->lock, flags); + i = isdn_get_free_channel(usg, l2, m->mdmreg[REG_L3PROT], -1, -1, m->msn); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + isdn_tty_modem_result(RESULT_NO_DIALTONE, info); + } else { + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + dev->usage[i] |= ISDN_USAGE_OUTGOING; + info->last_dir = 1; + isdn_info_update(); + spin_unlock_irqrestore(&dev->lock, flags); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_CLREAZ; + isdn_command(&cmd); + strcpy(cmd.parm.num, isdn_map_eaz2msn(m->msn, info->isdn_driver)); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETEAZ; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + info->last_l2 = l2; + cmd.arg = info->isdn_channel + (l2 << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.parm.cmsg.Length = l + 14; + cmd.parm.cmsg.Command = CAPI_MANUFACTURER; + cmd.parm.cmsg.Subcommand = CAPI_REQ; + cmd.parm.cmsg.adr.Controller = info->isdn_driver + 1; + cmd.parm.cmsg.para[0] = l + 1; + strncpy(&cmd.parm.cmsg.para[1], msg, l); + cmd.parm.cmsg.para[l + 1] = 0xd; + cmd.command = CAPI_PUT_MESSAGE; +/* info->dialing = 1; + strcpy(dev->num[i], n); + isdn_info_update(); +*/ + isdn_command(&cmd); + } +} + +static inline int +isdn_tty_paranoia_check(modem_info *info, char *name, const char *routine) +{ +#ifdef MODEM_PARANOIA_CHECK + if (!info) { + printk(KERN_WARNING "isdn_tty: null info_struct for %s in %s\n", + name, routine); + return 1; + } + if (info->magic != ISDN_ASYNC_MAGIC) { + printk(KERN_WARNING "isdn_tty: bad magic for modem struct %s in %s\n", + name, routine); + return 1; + } +#endif + return 0; +} + +/* + * This routine is called to set the UART divisor registers to match + * the specified baud rate for a serial port. + */ +static void +isdn_tty_change_speed(modem_info *info) +{ + struct tty_port *port = &info->port; + uint cflag, + cval, + quot; + int i; + + if (!port->tty) + return; + cflag = port->tty->termios.c_cflag; + + quot = i = cflag & CBAUD; + if (i & CBAUDEX) { + i &= ~CBAUDEX; + if (i < 1 || i > 2) + port->tty->termios.c_cflag &= ~CBAUDEX; + else + i += 15; + } + if (quot) { + info->mcr |= UART_MCR_DTR; + isdn_tty_modem_ncarrier(info); + } else { + info->mcr &= ~UART_MCR_DTR; + if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) { +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in changespeed\n"); +#endif + if (info->online) + info->ncarrier = 1; + isdn_tty_modem_reset_regs(info, 0); + isdn_tty_modem_hup(info, 1); + } + return; + } + /* byte size and parity */ + cval = cflag & (CSIZE | CSTOPB); + cval >>= 4; + if (cflag & PARENB) + cval |= UART_LCR_PARITY; + if (!(cflag & PARODD)) + cval |= UART_LCR_EPAR; + + if (cflag & CLOCAL) + port->flags &= ~ASYNC_CHECK_CD; + else { + port->flags |= ASYNC_CHECK_CD; + } +} + +static int +isdn_tty_startup(modem_info *info) +{ + if (info->port.flags & 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->port.tty) + clear_bit(TTY_IO_ERROR, &info->port.tty->flags); + /* + * and set the speed of the serial port + */ + isdn_tty_change_speed(info); + + info->port.flags |= 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->port.flags & 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->port.tty || (info->port.tty->termios.c_cflag & HUPCL)) { + info->mcr &= ~(UART_MCR_DTR | UART_MCR_RTS); + if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) { + isdn_tty_modem_reset_regs(info, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_shutdown\n"); +#endif + isdn_tty_modem_hup(info, 1); + } + } + if (info->port.tty) + set_bit(TTY_IO_ERROR, &info->port.tty->flags); + + info->port.flags &= ~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->port.xmit_buf[info->xmit_count], buf, c); +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline) { + int cc = isdn_tty_handleDLEdown(info, m, c); + if (info->vonline & 2) { + if (!cc) { + /* If DLE decoding results in zero-transmit, but + * c originally was non-zero, do a wakeup. + */ + tty_wakeup(tty); + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + } + info->xmit_count += cc; + } + if ((info->vonline & 3) == 1) { + /* Do NOT handle Ctrl-Q or Ctrl-S + * when in full-duplex audio mode. + */ + if (isdn_tty_end_vrx(buf, c)) { + info->vonline &= ~1; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG + "got !^Q/^S, send DLE-ETX,VCON on ttyI%d\n", + info->line); +#endif + isdn_tty_at_cout("\020\003\r\nVCON\r\n", info); + } + } + } else + if (TTY_IS_FCLASS1(info)) { + int cc = isdn_tty_handleDLEdown(info, m, c); + + if (info->vonline & 4) { /* ETX seen */ + isdn_ctrl c; + + c.command = ISDN_CMD_FAXCMD; + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + c.parm.aux.cmd = ISDN_FAX_CLASS1_CTRL; + c.parm.aux.subcmd = ETX; + isdn_command(&c); + } + info->vonline = 0; +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "fax dle cc/c %d/%d\n", cc, c); +#endif + info->xmit_count += cc; + } else +#endif + info->xmit_count += c; + } else { + info->msr |= UART_MSR_CTS; + info->lsr |= UART_LSR_TEMT; + if (info->dialing) { + info->dialing = 0; +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_write\n"); +#endif + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + isdn_tty_modem_hup(info, 1); + } else + c = isdn_tty_edit_at(buf, c, info); + } + buf += c; + count -= c; + total += c; + } + atomic_dec(&info->xmit_lock); + if ((info->xmit_count) || !skb_queue_empty(&info->xmit_queue)) { + if (m->mdmreg[REG_DXMT] & BIT_DXMT) { + isdn_tty_senddown(info); + isdn_tty_tint(info); + } + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1); + } + return total; +} + +static int +isdn_tty_write_room(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + int ret; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_write_room")) + return 0; + if (!info->online) + return info->xmit_size; + ret = info->xmit_size - info->xmit_count; + return (ret < 0) ? 0 : ret; +} + +static int +isdn_tty_chars_in_buffer(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_chars_in_buffer")) + return 0; + if (!info->online) + return 0; + return (info->xmit_count); +} + +static void +isdn_tty_flush_buffer(struct tty_struct *tty) +{ + modem_info *info; + + if (!tty) { + return; + } + info = (modem_info *) tty->driver_data; + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_flush_buffer")) { + return; + } + isdn_tty_cleanup_xmit(info); + info->xmit_count = 0; + tty_wakeup(tty); +} + +static void +isdn_tty_flush_chars(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_flush_chars")) + return; + if ((info->xmit_count) || !skb_queue_empty(&info->xmit_queue)) + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, 1); +} + +/* + * ------------------------------------------------------------ + * isdn_tty_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void +isdn_tty_throttle(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_throttle")) + return; + if (I_IXOFF(tty)) + info->x_char = STOP_CHAR(tty); + info->mcr &= ~UART_MCR_RTS; +} + +static void +isdn_tty_unthrottle(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_unthrottle")) + return; + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + info->x_char = START_CHAR(tty); + } + info->mcr |= UART_MCR_RTS; +} + +/* + * ------------------------------------------------------------ + * isdn_tty_ioctl() and friends + * ------------------------------------------------------------ + */ + +/* + * isdn_tty_get_lsr_info - get line status register info + * + * Purpose: Let user call ioctl() to get info when the UART physically + * is emptied. On bus types like RS485, the transmitter must + * release the bus after transmitting. This must be done when + * the transmit shift register is empty, not be done when the + * transmit holding register is empty. This functionality + * allows RS485 driver to be written in user space. + */ +static int +isdn_tty_get_lsr_info(modem_info *info, uint __user *value) +{ + u_char status; + uint result; + + status = info->lsr; + result = ((status & UART_LSR_TEMT) ? TIOCSER_TEMT : 0); + return put_user(result, value); +} + + +static int +isdn_tty_tiocmget(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + u_char control, status; + + if (isdn_tty_paranoia_check(info, tty->name, __func__)) + return -ENODEV; + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + + mutex_lock(&modem_info_mutex); +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCMGET\n", info->line); +#endif + + control = info->mcr; + status = info->msr; + mutex_unlock(&modem_info_mutex); + return ((control & UART_MCR_RTS) ? TIOCM_RTS : 0) + | ((control & UART_MCR_DTR) ? TIOCM_DTR : 0) + | ((status & UART_MSR_DCD) ? TIOCM_CAR : 0) + | ((status & UART_MSR_RI) ? TIOCM_RNG : 0) + | ((status & UART_MSR_DSR) ? TIOCM_DSR : 0) + | ((status & UART_MSR_CTS) ? TIOCM_CTS : 0); +} + +static int +isdn_tty_tiocmset(struct tty_struct *tty, + unsigned int set, unsigned int clear) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (isdn_tty_paranoia_check(info, tty->name, __func__)) + return -ENODEV; + if (tty->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 + + mutex_lock(&modem_info_mutex); + if (set & TIOCM_RTS) + info->mcr |= UART_MCR_RTS; + if (set & TIOCM_DTR) { + info->mcr |= UART_MCR_DTR; + isdn_tty_modem_ncarrier(info); + } + + if (clear & TIOCM_RTS) + info->mcr &= ~UART_MCR_RTS; + if (clear & TIOCM_DTR) { + info->mcr &= ~UART_MCR_DTR; + if (info->emu.mdmreg[REG_DTRHUP] & BIT_DTRHUP) { + isdn_tty_modem_reset_regs(info, 0); +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in TIOCMSET\n"); +#endif + if (info->online) + info->ncarrier = 1; + isdn_tty_modem_hup(info, 1); + } + } + mutex_unlock(&modem_info_mutex); + return 0; +} + +static int +isdn_tty_ioctl(struct tty_struct *tty, uint cmd, ulong arg) +{ + modem_info *info = (modem_info *) tty->driver_data; + int retval; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_ioctl")) + return -ENODEV; + if (tty->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 TIOCSERGETLSR: /* Get line status register */ +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "ttyI%d ioctl TIOCSERGETLSR\n", info->line); +#endif + return isdn_tty_get_lsr_info(info, (uint __user *) arg); + default: +#ifdef ISDN_DEBUG_MODEM_IOCTL + printk(KERN_DEBUG "UNKNOWN ioctl 0x%08x on ttyi%d\n", cmd, info->line); +#endif + return -ENOIOCTLCMD; + } + return 0; +} + +static void +isdn_tty_set_termios(struct tty_struct *tty, struct ktermios *old_termios) +{ + modem_info *info = (modem_info *) tty->driver_data; + + if (!old_termios) + isdn_tty_change_speed(info); + else { + if (tty->termios.c_cflag == old_termios->c_cflag && + tty->termios.c_ispeed == old_termios->c_ispeed && + tty->termios.c_ospeed == old_termios->c_ospeed) + return; + isdn_tty_change_speed(info); + } +} + +/* + * ------------------------------------------------------------ + * isdn_tty_open() and friends + * ------------------------------------------------------------ + */ + +static int isdn_tty_install(struct tty_driver *driver, struct tty_struct *tty) +{ + modem_info *info = &dev->mdm.info[tty->index]; + + if (isdn_tty_paranoia_check(info, tty->name, __func__)) + return -ENODEV; + + tty->driver_data = info; + + return tty_port_install(&info->port, driver, tty); +} + +/* + * This routine is called whenever a serial port is opened. It + * enables interrupts for a serial port, linking in its async structure into + * the IRQ chain. It also performs the serial-specific + * initialization for the tty structure. + */ +static int +isdn_tty_open(struct tty_struct *tty, struct file *filp) +{ + modem_info *info = tty->driver_data; + struct tty_port *port = &info->port; + int retval; + +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open %s, count = %d\n", tty->name, + port->count); +#endif + port->count++; + port->tty = tty; + /* + * Start up serial port + */ + retval = isdn_tty_startup(info); + if (retval) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open return after startup\n"); +#endif + return retval; + } + retval = tty_port_block_til_ready(port, tty, filp); + if (retval) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open return after isdn_tty_block_til_ready \n"); +#endif + return retval; + } +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open ttyi%d successful...\n", info->line); +#endif + dev->modempoll++; +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_open normal exit\n"); +#endif + return 0; +} + +static void +isdn_tty_close(struct tty_struct *tty, struct file *filp) +{ + modem_info *info = (modem_info *) tty->driver_data; + struct tty_port *port = &info->port; + ulong timeout; + + if (!info || isdn_tty_paranoia_check(info, tty->name, "isdn_tty_close")) + return; + if (tty_hung_up_p(filp)) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close return after tty_hung_up_p\n"); +#endif + return; + } + if ((tty->count == 1) && (port->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. Info->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk(KERN_ERR "isdn_tty_close: bad port count; tty->count is 1, " + "info->count is %d\n", port->count); + port->count = 1; + } + if (--port->count < 0) { + printk(KERN_ERR "isdn_tty_close: bad port count for ttyi%d: %d\n", + info->line, port->count); + port->count = 0; + } + if (port->count) { +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close after info->count != 0\n"); +#endif + return; + } + port->flags |= ASYNC_CLOSING; + + tty->closing = 1; + /* + * At this point we stop accepting input. To do this, we + * disable the receive line status interrupts, and tell the + * interrupt driver to stop checking the data ready bit in the + * line status register. + */ + if (port->flags & ASYNC_INITIALIZED) { + tty_wait_until_sent_from_close(tty, 3000); /* 30 seconds timeout */ + /* + * Before we drop DTR, make sure the UART transmitter + * has completely drained; this is especially + * important if there is a transmit FIFO! + */ + timeout = jiffies + HZ; + while (!(info->lsr & UART_LSR_TEMT)) { + schedule_timeout_interruptible(20); + if (time_after(jiffies, timeout)) + break; + } + } + dev->modempoll--; + isdn_tty_shutdown(info); + isdn_tty_flush_buffer(tty); + tty_ldisc_flush(tty); + port->tty = NULL; + info->ncarrier = 0; + + tty_port_close_end(port, tty); +#ifdef ISDN_DEBUG_MODEM_OPEN + printk(KERN_DEBUG "isdn_tty_close normal exit\n"); +#endif +} + +/* + * isdn_tty_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void +isdn_tty_hangup(struct tty_struct *tty) +{ + modem_info *info = (modem_info *) tty->driver_data; + struct tty_port *port = &info->port; + + if (isdn_tty_paranoia_check(info, tty->name, "isdn_tty_hangup")) + return; + isdn_tty_shutdown(info); + port->count = 0; + port->flags &= ~ASYNC_NORMAL_ACTIVE; + port->tty = NULL; + wake_up_interruptible(&port->open_wait); +} + +/* This routine initializes all emulator-data. + */ +static void +isdn_tty_reset_profile(atemu *m) +{ + m->profile[0] = 0; + m->profile[1] = 0; + m->profile[2] = 43; + m->profile[3] = 13; + m->profile[4] = 10; + m->profile[5] = 8; + m->profile[6] = 3; + m->profile[7] = 60; + m->profile[8] = 2; + m->profile[9] = 6; + m->profile[10] = 7; + m->profile[11] = 70; + m->profile[12] = 0x45; + m->profile[13] = 4; + m->profile[14] = ISDN_PROTO_L2_X75I; + m->profile[15] = ISDN_PROTO_L3_TRANS; + m->profile[16] = ISDN_SERIAL_XMIT_SIZE / 16; + m->profile[17] = ISDN_MODEM_WINSIZE; + m->profile[18] = 4; + m->profile[19] = 0; + m->profile[20] = 0; + m->profile[23] = 0; + m->pmsn[0] = '\0'; + m->plmsn[0] = '\0'; +} + +#ifdef CONFIG_ISDN_AUDIO +static void +isdn_tty_modem_reset_vpar(atemu *m) +{ + m->vpar[0] = 2; /* Voice-device (2 = phone line) */ + m->vpar[1] = 0; /* Silence detection level (0 = none ) */ + m->vpar[2] = 70; /* Silence interval (7 sec. ) */ + m->vpar[3] = 2; /* Compression type (1 = ADPCM-2 ) */ + m->vpar[4] = 0; /* DTMF detection level (0 = softcode ) */ + m->vpar[5] = 8; /* DTMF interval (8 * 5 ms. ) */ +} +#endif + +#ifdef CONFIG_ISDN_TTY_FAX +static void +isdn_tty_modem_reset_faxpar(modem_info *info) +{ + T30_s *f = info->fax; + + f->code = 0; + f->phase = ISDN_FAX_PHASE_IDLE; + f->direction = 0; + f->resolution = 1; /* fine */ + f->rate = 5; /* 14400 bit/s */ + f->width = 0; + f->length = 0; + f->compression = 0; + f->ecm = 0; + f->binary = 0; + f->scantime = 0; + memset(&f->id[0], 32, FAXIDLEN - 1); + f->id[FAXIDLEN - 1] = 0; + f->badlin = 0; + f->badmul = 0; + f->bor = 0; + f->nbc = 0; + f->cq = 0; + f->cr = 0; + f->ctcrty = 0; + f->minsp = 0; + f->phcto = 30; + f->rel = 0; + memset(&f->pollid[0], 32, FAXIDLEN - 1); + f->pollid[FAXIDLEN - 1] = 0; +} +#endif + +static void +isdn_tty_modem_reset_regs(modem_info *info, int force) +{ + atemu *m = &info->emu; + if ((m->mdmreg[REG_DTRR] & BIT_DTRR) || force) { + memcpy(m->mdmreg, m->profile, ISDN_MODEM_NUMREG); + memcpy(m->msn, m->pmsn, ISDN_MSNLEN); + memcpy(m->lmsn, m->plmsn, ISDN_LMSNLEN); + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + } +#ifdef CONFIG_ISDN_AUDIO + isdn_tty_modem_reset_vpar(m); +#endif +#ifdef CONFIG_ISDN_TTY_FAX + isdn_tty_modem_reset_faxpar(info); +#endif + m->mdmcmdl = 0; +} + +static void +modem_write_profile(atemu *m) +{ + memcpy(m->profile, m->mdmreg, ISDN_MODEM_NUMREG); + memcpy(m->pmsn, m->msn, ISDN_MSNLEN); + memcpy(m->plmsn, m->lmsn, ISDN_LMSNLEN); + if (dev->profd) + send_sig(SIGIO, dev->profd, 1); +} + +static const struct tty_operations modem_ops = { + .install = isdn_tty_install, + .open = isdn_tty_open, + .close = isdn_tty_close, + .write = isdn_tty_write, + .flush_chars = isdn_tty_flush_chars, + .write_room = isdn_tty_write_room, + .chars_in_buffer = isdn_tty_chars_in_buffer, + .flush_buffer = isdn_tty_flush_buffer, + .ioctl = isdn_tty_ioctl, + .throttle = isdn_tty_throttle, + .unthrottle = isdn_tty_unthrottle, + .set_termios = isdn_tty_set_termios, + .hangup = isdn_tty_hangup, + .tiocmget = isdn_tty_tiocmget, + .tiocmset = isdn_tty_tiocmset, +}; + +static int isdn_tty_carrier_raised(struct tty_port *port) +{ + modem_info *info = container_of(port, modem_info, port); + return info->msr & UART_MSR_DCD; +} + +static const struct tty_port_operations isdn_tty_port_ops = { + .carrier_raised = isdn_tty_carrier_raised, +}; + +int +isdn_tty_modem_init(void) +{ + isdn_modem_t *m; + int i, retval; + modem_info *info; + + m = &dev->mdm; + m->tty_modem = alloc_tty_driver(ISDN_MAX_CHANNELS); + if (!m->tty_modem) + return -ENOMEM; + m->tty_modem->name = "ttyI"; + m->tty_modem->major = ISDN_TTY_MAJOR; + m->tty_modem->minor_start = 0; + m->tty_modem->type = TTY_DRIVER_TYPE_SERIAL; + m->tty_modem->subtype = SERIAL_TYPE_NORMAL; + m->tty_modem->init_termios = tty_std_termios; + m->tty_modem->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + m->tty_modem->flags = TTY_DRIVER_REAL_RAW; + m->tty_modem->driver_name = "isdn_tty"; + tty_set_operations(m->tty_modem, &modem_ops); + retval = tty_register_driver(m->tty_modem); + if (retval) { + printk(KERN_WARNING "isdn_tty: Couldn't register modem-device\n"); + goto err; + } + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &m->info[i]; +#ifdef CONFIG_ISDN_TTY_FAX + if (!(info->fax = kmalloc(sizeof(T30_s), GFP_KERNEL))) { + printk(KERN_ERR "Could not allocate fax t30-buffer\n"); + retval = -ENOMEM; + goto err_unregister; + } +#endif + tty_port_init(&info->port); + info->port.ops = &isdn_tty_port_ops; + spin_lock_init(&info->readlock); + sprintf(info->last_cause, "0000"); + sprintf(info->last_num, "none"); + info->last_dir = 0; + info->last_lhup = 1; + info->last_l2 = -1; + info->last_si = 0; + isdn_tty_reset_profile(&info->emu); + isdn_tty_modem_reset_regs(info, 1); + info->magic = ISDN_ASYNC_MAGIC; + info->line = i; + info->x_char = 0; + info->isdn_driver = -1; + info->isdn_channel = -1; + info->drv_index = -1; + info->xmit_size = ISDN_SERIAL_XMIT_SIZE; + init_timer(&info->nc_timer); + info->nc_timer.function = isdn_tty_modem_do_ncarrier; + info->nc_timer.data = (unsigned long) info; + skb_queue_head_init(&info->xmit_queue); +#ifdef CONFIG_ISDN_AUDIO + skb_queue_head_init(&info->dtmf_queue); +#endif + info->port.xmit_buf = kmalloc(ISDN_SERIAL_XMIT_MAX + 5, + GFP_KERNEL); + if (!info->port.xmit_buf) { + printk(KERN_ERR "Could not allocate modem xmit-buffer\n"); + retval = -ENOMEM; + goto err_unregister; + } + /* Make room for T.70 header */ + info->port.xmit_buf += 4; + } + return 0; +err_unregister: + for (i--; i >= 0; i--) { + info = &m->info[i]; +#ifdef CONFIG_ISDN_TTY_FAX + kfree(info->fax); +#endif + kfree(info->port.xmit_buf - 4); + info->port.xmit_buf = NULL; + tty_port_destroy(&info->port); + } + tty_unregister_driver(m->tty_modem); +err: + put_tty_driver(m->tty_modem); + m->tty_modem = NULL; + return retval; +} + +void +isdn_tty_exit(void) +{ + modem_info *info; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + info = &dev->mdm.info[i]; + isdn_tty_cleanup_xmit(info); +#ifdef CONFIG_ISDN_TTY_FAX + kfree(info->fax); +#endif + kfree(info->port.xmit_buf - 4); + info->port.xmit_buf = NULL; + tty_port_destroy(&info->port); + } + tty_unregister_driver(dev->mdm.tty_modem); + put_tty_driver(dev->mdm.tty_modem); + dev->mdm.tty_modem = NULL; +} + + +/* + * isdn_tty_match_icall(char *MSN, atemu *tty_emulator, int dev_idx) + * match the MSN against the MSNs (glob patterns) defined for tty_emulator, + * and return 0 for match, 1 for no match, 2 if MSN could match if longer. + */ + +static int +isdn_tty_match_icall(char *cid, atemu *emu, int di) +{ +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: msn=%s lmsn=%s mmsn=%s mreg[SI1]=%d mreg[SI2]=%d\n", + emu->msn, emu->lmsn, isdn_map_eaz2msn(emu->msn, di), + emu->mdmreg[REG_SI1], emu->mdmreg[REG_SI2]); +#endif + if (strlen(emu->lmsn)) { + char *p = emu->lmsn; + char *q; + int tmp; + int ret = 0; + + while (1) { + if ((q = strchr(p, ';'))) + *q = '\0'; + if ((tmp = isdn_msncmp(cid, isdn_map_eaz2msn(p, di))) > ret) + ret = tmp; +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: lmsnX=%s mmsn=%s -> tmp=%d\n", + p, isdn_map_eaz2msn(emu->msn, di), tmp); +#endif + if (q) { + *q = ';'; + p = q; + p++; + } + if (!tmp) + return 0; + if (!q) + break; + } + return ret; + } else { + int tmp; + tmp = isdn_msncmp(cid, isdn_map_eaz2msn(emu->msn, di)); +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: mmsn=%s -> tmp=%d\n", + isdn_map_eaz2msn(emu->msn, di), tmp); +#endif + return tmp; + } +} + +/* + * An incoming call-request has arrived. + * Search the tty-devices for an appropriate device and bind + * it to the ISDN-Channel. + * Return: + * + * 0 = No matching device found. + * 1 = A matching device found. + * 3 = No match found, but eventually would match, if + * CID is longer. + */ +int +isdn_tty_find_icall(int di, int ch, setup_parm *setup) +{ + char *eaz; + int i; + int wret; + int idx; + int si1; + int si2; + char *nr; + ulong flags; + + if (!setup->phone[0]) { + nr = "0"; + printk(KERN_INFO "isdn_tty: Incoming call without OAD, assuming '0'\n"); + } else + nr = setup->phone; + si1 = (int) setup->si1; + si2 = (int) setup->si2; + if (!setup->eazmsn[0]) { + printk(KERN_WARNING "isdn_tty: Incoming call without CPN, assuming '0'\n"); + eaz = "0"; + } else + eaz = setup->eazmsn; +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: eaz=%s si1=%d si2=%d\n", eaz, si1, si2); +#endif + wret = 0; + spin_lock_irqsave(&dev->lock, flags); + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + + if (info->port.count == 0) + continue; + if ((info->emu.mdmreg[REG_SI1] & si2bit[si1]) && /* SI1 is matching */ + (info->emu.mdmreg[REG_SI2] == si2)) { /* SI2 is matching */ + idx = isdn_dc2minor(di, ch); +#ifdef ISDN_DEBUG_MODEM_ICALL + printk(KERN_DEBUG "m_fi: match1 wret=%d\n", wret); + printk(KERN_DEBUG "m_fi: idx=%d flags=%08lx drv=%d ch=%d usg=%d\n", idx, + info->port.flags, info->isdn_driver, + info->isdn_channel, dev->usage[idx]); +#endif + if ( +#ifndef FIX_FILE_TRANSFER + (info->port.flags & 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->port.flags & ASYNC_NORMAL_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->port.blocked_open && + (info->emu.mdmreg[REG_DCD] & BIT_DCD)) { + wake_up_interruptible(&info->port.open_wait); + } + + /* Schedule CONNECT-Message to any tty + * waiting for it and + * set DCD-bit of its modem-status. + */ + if (TTY_IS_ACTIVE(info) || + (info->port.blocked_open && + (info->emu.mdmreg[REG_DCD] & BIT_DCD))) { + info->msr |= UART_MSR_DCD; + info->emu.charge = 0; + if (info->dialing & 0xf) + info->last_dir = 1; + else + info->last_dir = 0; + info->dialing = 0; + info->rcvsched = 1; + if (USG_MODEM(dev->usage[i])) { + if (info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) { + strcpy(info->emu.connmsg, c->parm.num); + isdn_tty_modem_result(RESULT_CONNECT, info); + } else + isdn_tty_modem_result(RESULT_CONNECT64000, info); + } + if (USG_VOICE(dev->usage[i])) + isdn_tty_modem_result(RESULT_VCON, info); + return 1; + } + break; + case ISDN_STAT_BHUP: +#ifdef ISDN_TTY_STAT_DEBUG + printk(KERN_DEBUG "tty_STAT_BHUP ttyI%d\n", info->line); +#endif + if (TTY_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_port *port = &info->port; + atemu *m = &info->emu; + char *p; + char c; + u_long flags; + struct sk_buff *skb = NULL; + char *sp = NULL; + int l; + + if (!msg) { + printk(KERN_WARNING "isdn_tty: Null-Message in isdn_tty_at_cout\n"); + return; + } + + l = strlen(msg); + + spin_lock_irqsave(&info->readlock, flags); + if (port->flags & ASYNC_CLOSING) { + 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(port, 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(port, c, TTY_NORMAL) == 0) + break; + } + } + if (skb) { + __skb_queue_tail(&dev->drv[info->isdn_driver]->rpqueue[info->isdn_channel], skb); + dev->drv[info->isdn_driver]->rcvcount[info->isdn_channel] += skb->len; + spin_unlock_irqrestore(&info->readlock, flags); + /* Schedule dequeuing */ + if (dev->modempoll && info->rcvsched) + isdn_timer_ctrl(ISDN_TIMER_MODEMREAD, 1); + + } else { + spin_unlock_irqrestore(&info->readlock, flags); + tty_flip_buffer_push(port); + } +} + +/* + * Perform ATH Hangup + */ +static void +isdn_tty_on_hook(modem_info *info) +{ + if (info->isdn_channel >= 0) { +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "Mhup in isdn_tty_on_hook\n"); +#endif + isdn_tty_modem_hup(info, 1); + } +} + +static void +isdn_tty_off_hook(void) +{ + printk(KERN_DEBUG "isdn_tty_off_hook\n"); +} + +#define PLUSWAIT1 (HZ / 2) /* 0.5 sec. */ +#define PLUSWAIT2 (HZ * 3 / 2) /* 1.5 sec */ + +/* + * Check Buffer for Modem-escape-sequence, activate timer-callback to + * isdn_tty_modem_escape() if sequence found. + * + * Parameters: + * p pointer to databuffer + * plus escape-character + * count length of buffer + * pluscount count of valid escape-characters so far + * lastplus timestamp of last character + */ +static void +isdn_tty_check_esc(const u_char *p, u_char plus, int count, int *pluscount, + u_long *lastplus) +{ + if (plus > 127) + return; + if (count > 3) { + p += count - 3; + count = 3; + *pluscount = 0; + } + while (count > 0) { + if (*(p++) == plus) { + if ((*pluscount)++) { + /* Time since last '+' > 0.5 sec. ? */ + if (time_after(jiffies, *lastplus + PLUSWAIT1)) + *pluscount = 1; + } else { + /* Time since last non-'+' < 1.5 sec. ? */ + if (time_before(jiffies, *lastplus + PLUSWAIT2)) + *pluscount = 0; + } + if ((*pluscount == 3) && (count == 1)) + isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, 1); + if (*pluscount > 3) + *pluscount = 1; + } else + *pluscount = 0; + *lastplus = jiffies; + count--; + } +} + +/* + * Return result of AT-emulator to tty-receive-buffer, depending on + * modem-register 12, bit 0 and 1. + * For CONNECT-messages also switch to online-mode. + * For RING-message handle auto-ATA if register 0 != 0 + */ + +static void +isdn_tty_modem_result(int code, modem_info *info) +{ + atemu *m = &info->emu; + static char *msg[] = + {"OK", "CONNECT", "RING", "NO CARRIER", "ERROR", + "CONNECT 64000", "NO DIALTONE", "BUSY", "NO ANSWER", + "RINGING", "NO MSN/EAZ", "VCON", "RUNG"}; + char s[ISDN_MSNLEN + 10]; + + switch (code) { + case RESULT_RING: + m->mdmreg[REG_RINGCNT]++; + if (m->mdmreg[REG_RINGCNT] == m->mdmreg[REG_RINGATA]) + /* Automatically accept incoming call */ + isdn_tty_cmd_ATA(info); + break; + case RESULT_NO_CARRIER: +#ifdef ISDN_DEBUG_MODEM_HUP + printk(KERN_DEBUG "modem_result: NO CARRIER %d %d\n", + (info->port.flags & ASYNC_CLOSING), + (!info->port.tty)); +#endif + m->mdmreg[REG_RINGCNT] = 0; + del_timer(&info->nc_timer); + info->ncarrier = 0; + if ((info->port.flags & ASYNC_CLOSING) || (!info->port.tty)) + return; + +#ifdef CONFIG_ISDN_AUDIO + if (info->vonline & 1) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send DLE-ETX on ttyI%d\n", + info->line); +#endif + /* voice-recording, add DLE-ETX */ + isdn_tty_at_cout("\020\003", info); + } + if (info->vonline & 2) { +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send DLE-DC4 on ttyI%d\n", + info->line); +#endif + /* voice-playing, add DLE-DC4 */ + isdn_tty_at_cout("\020\024", info); + } +#endif + break; + case RESULT_CONNECT: + case RESULT_CONNECT64000: + sprintf(info->last_cause, "0000"); + if (!info->online) + info->online = 2; + break; + case RESULT_VCON: +#ifdef ISDN_DEBUG_MODEM_VOICE + printk(KERN_DEBUG "res3: send VCON on ttyI%d\n", + info->line); +#endif + sprintf(info->last_cause, "0000"); + if (!info->online) + info->online = 1; + break; + } /* switch (code) */ + + if (m->mdmreg[REG_RESP] & BIT_RESP) { + /* Show results */ + if (m->mdmreg[REG_RESPNUM] & BIT_RESPNUM) { + /* Show numeric results only */ + sprintf(s, "\r\n%d\r\n", code); + isdn_tty_at_cout(s, info); + } else { + if (code == RESULT_RING) { + /* return if "show RUNG" and ringcounter>1 */ + if ((m->mdmreg[REG_RUNG] & BIT_RUNG) && + (m->mdmreg[REG_RINGCNT] > 1)) + return; + /* print CID, _before_ _every_ ring */ + if (!(m->mdmreg[REG_CIDONCE] & BIT_CIDONCE)) { + isdn_tty_at_cout("\r\nCALLER NUMBER: ", info); + isdn_tty_at_cout(dev->num[info->drv_index], info); + if (m->mdmreg[REG_CDN] & BIT_CDN) { + isdn_tty_at_cout("\r\nCALLED NUMBER: ", info); + isdn_tty_at_cout(info->emu.cpn, info); + } + } + } + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[code], info); + switch (code) { + case RESULT_CONNECT: + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_MODEM: + isdn_tty_at_cout(" ", info); + isdn_tty_at_cout(m->connmsg, info); + break; + } + break; + case RESULT_RING: + /* Append CPN, if enabled */ + if ((m->mdmreg[REG_CPN] & BIT_CPN)) { + sprintf(s, "/%s", m->cpn); + isdn_tty_at_cout(s, info); + } + /* Print CID only once, _after_ 1st RING */ + if ((m->mdmreg[REG_CIDONCE] & BIT_CIDONCE) && + (m->mdmreg[REG_RINGCNT] == 1)) { + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout("CALLER NUMBER: ", info); + isdn_tty_at_cout(dev->num[info->drv_index], info); + if (m->mdmreg[REG_CDN] & BIT_CDN) { + isdn_tty_at_cout("\r\nCALLED NUMBER: ", info); + isdn_tty_at_cout(info->emu.cpn, info); + } + } + break; + case RESULT_NO_CARRIER: + case RESULT_NO_DIALTONE: + case RESULT_BUSY: + case RESULT_NO_ANSWER: + m->mdmreg[REG_RINGCNT] = 0; + /* Append Cause-Message if enabled */ + if (m->mdmreg[REG_RESPXT] & BIT_RESPXT) { + sprintf(s, "/%s", info->last_cause); + isdn_tty_at_cout(s, info); + } + break; + case RESULT_CONNECT64000: + /* Append Protocol to CONNECT message */ + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + isdn_tty_at_cout("/X.75", info); + break; + case ISDN_PROTO_L2_HDLC: + isdn_tty_at_cout("/HDLC", info); + break; + case ISDN_PROTO_L2_V11096: + isdn_tty_at_cout("/V110/9600", info); + break; + case ISDN_PROTO_L2_V11019: + isdn_tty_at_cout("/V110/19200", info); + break; + case ISDN_PROTO_L2_V11038: + isdn_tty_at_cout("/V110/38400", info); + break; + } + if (m->mdmreg[REG_T70] & BIT_T70) { + isdn_tty_at_cout("/T.70", info); + if (m->mdmreg[REG_T70] & BIT_T70_EXT) + isdn_tty_at_cout("+", info); + } + break; + } + isdn_tty_at_cout("\r\n", info); + } + } + if (code == RESULT_NO_CARRIER) { + if ((info->port.flags & ASYNC_CLOSING) || (!info->port.tty)) + return; + + if (info->port.flags & ASYNC_CHECK_CD) + tty_hangup(info->port.tty); + } +} + + +/* + * Display a modem-register-value. + */ +static void +isdn_tty_show_profile(int ridx, modem_info *info) +{ + char v[6]; + + sprintf(v, "\r\n%d", info->emu.mdmreg[ridx]); + isdn_tty_at_cout(v, info); +} + +/* + * Get MSN-string from char-pointer, set pointer to end of number + */ +static void +isdn_tty_get_msnstr(char *n, char **p) +{ + int limit = ISDN_MSNLEN - 1; + + while (((*p[0] >= '0' && *p[0] <= '9') || + /* Why a comma ??? */ + (*p[0] == ',') || (*p[0] == ':')) && + (limit--)) + *n++ = *p[0]++; + *n = '\0'; +} + +/* + * Get phone-number from modem-commandbuffer + */ +static void +isdn_tty_getdial(char *p, char *q, int cnt) +{ + int first = 1; + int limit = ISDN_MSNLEN - 1; /* MUST match the size of interface var to avoid + buffer overflow */ + + while (strchr(" 0123456789,#.*WPTSR-", *p) && *p && --cnt > 0) { + if ((*p >= '0' && *p <= '9') || ((*p == 'S') && first) || + ((*p == 'R') && first) || + (*p == '*') || (*p == '#')) { + *q++ = *p; + limit--; + } + if (!limit) + break; + p++; + first = 0; + } + *q = 0; +} + +#define PARSE_ERROR { isdn_tty_modem_result(RESULT_ERROR, info); return; } +#define PARSE_ERROR1 { isdn_tty_modem_result(RESULT_ERROR, info); return 1; } + +static void +isdn_tty_report(modem_info *info) +{ + atemu *m = &info->emu; + char s[80]; + + isdn_tty_at_cout("\r\nStatistics of last connection:\r\n\r\n", info); + sprintf(s, " Remote Number: %s\r\n", info->last_num); + isdn_tty_at_cout(s, info); + sprintf(s, " Direction: %s\r\n", info->last_dir ? "outgoing" : "incoming"); + isdn_tty_at_cout(s, info); + isdn_tty_at_cout(" Layer-2 Protocol: ", info); + switch (info->last_l2) { + case ISDN_PROTO_L2_X75I: + isdn_tty_at_cout("X.75i", info); + break; + case ISDN_PROTO_L2_X75UI: + isdn_tty_at_cout("X.75ui", info); + break; + case ISDN_PROTO_L2_X75BUI: + isdn_tty_at_cout("X.75bui", info); + break; + case ISDN_PROTO_L2_HDLC: + isdn_tty_at_cout("HDLC", info); + break; + case ISDN_PROTO_L2_V11096: + isdn_tty_at_cout("V.110 9600 Baud", info); + break; + case ISDN_PROTO_L2_V11019: + isdn_tty_at_cout("V.110 19200 Baud", info); + break; + case ISDN_PROTO_L2_V11038: + isdn_tty_at_cout("V.110 38400 Baud", info); + break; + case ISDN_PROTO_L2_TRANS: + isdn_tty_at_cout("transparent", info); + break; + case ISDN_PROTO_L2_MODEM: + isdn_tty_at_cout("modem", info); + break; + case ISDN_PROTO_L2_FAX: + isdn_tty_at_cout("fax", info); + break; + default: + isdn_tty_at_cout("unknown", info); + break; + } + if (m->mdmreg[REG_T70] & BIT_T70) { + isdn_tty_at_cout("/T.70", info); + if (m->mdmreg[REG_T70] & BIT_T70_EXT) + isdn_tty_at_cout("+", info); + } + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(" Service: ", info); + switch (info->last_si) { + case 1: + isdn_tty_at_cout("audio\r\n", info); + break; + case 5: + isdn_tty_at_cout("btx\r\n", info); + break; + case 7: + isdn_tty_at_cout("data\r\n", info); + break; + default: + sprintf(s, "%d\r\n", info->last_si); + isdn_tty_at_cout(s, info); + break; + } + sprintf(s, " Hangup location: %s\r\n", info->last_lhup ? "local" : "remote"); + isdn_tty_at_cout(s, info); + sprintf(s, " Last cause: %s\r\n", info->last_cause); + isdn_tty_at_cout(s, info); +} + +/* + * Parse AT&.. commands. + */ +static int +isdn_tty_cmd_ATand(char **p, modem_info *info) +{ + atemu *m = &info->emu; + int i; + char rb[100]; + +#define MAXRB (sizeof(rb) - 1) + + switch (*p[0]) { + case 'B': + /* &B - Set Buffersize */ + p[0]++; + i = isdn_getnum(p); + if ((i < 0) || (i > ISDN_SERIAL_XMIT_MAX)) + PARSE_ERROR1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[REG_SI1] & 1) && (i > VBUF)) + PARSE_ERROR1; +#endif + m->mdmreg[REG_PSIZE] = i / 16; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + info->xmit_size /= 10; + } + break; + case 'C': + /* &C - DCD Status */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_DCD] &= ~BIT_DCD; + break; + case 1: + m->mdmreg[REG_DCD] |= BIT_DCD; + break; + default: + PARSE_ERROR1 + } + break; + case 'D': + /* &D - Set DTR-Low-behavior */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_DTRHUP] &= ~BIT_DTRHUP; + m->mdmreg[REG_DTRR] &= ~BIT_DTRR; + break; + case 2: + m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP; + m->mdmreg[REG_DTRR] &= ~BIT_DTRR; + break; + case 3: + m->mdmreg[REG_DTRHUP] |= BIT_DTRHUP; + m->mdmreg[REG_DTRR] |= BIT_DTRR; + break; + default: + PARSE_ERROR1 + } + break; + case 'E': + /* &E -Set EAZ/MSN */ + p[0]++; + isdn_tty_get_msnstr(m->msn, p); + break; + case 'F': + /* &F -Set Factory-Defaults */ + p[0]++; + if (info->msr & UART_MSR_DCD) + PARSE_ERROR1; + isdn_tty_reset_profile(m); + isdn_tty_modem_reset_regs(info, 1); + break; +#ifdef DUMMY_HAYES_AT + case 'K': + /* only for be compilant with common scripts */ + /* &K Flowcontrol - no function */ + p[0]++; + isdn_getnum(p); + break; +#endif + case 'L': + /* &L -Set Numbers to listen on */ + p[0]++; + i = 0; + while (*p[0] && (strchr("0123456789,-*[]?;", *p[0])) && + (i < ISDN_LMSNLEN - 1)) + m->lmsn[i++] = *p[0]++; + m->lmsn[i] = '\0'; + break; + case 'R': + /* &R - Set V.110 bitrate adaption */ + p[0]++; + i = isdn_getnum(p); + switch (i) { + case 0: + /* Switch off V.110, back to X.75 */ + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_SI2] = 0; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + break; + case 9600: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11096; + m->mdmreg[REG_SI2] = 197; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + case 19200: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11019; + m->mdmreg[REG_SI2] = 199; + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + case 38400: + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_V11038; + m->mdmreg[REG_SI2] = 198; /* no existing standard for this */ + info->xmit_size = m->mdmreg[REG_PSIZE] * 16 / 10; + break; + default: + PARSE_ERROR1; + } + /* Switch off T.70 */ + m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT); + /* Set Service 7 */ + m->mdmreg[REG_SI1] |= 4; + break; + case 'S': + /* &S - Set Windowsize */ + p[0]++; + i = isdn_getnum(p); + if ((i > 0) && (i < 9)) + m->mdmreg[REG_WSIZE] = i; + else + PARSE_ERROR1; + break; + case 'V': + /* &V - Show registers */ + p[0]++; + isdn_tty_at_cout("\r\n", info); + for (i = 0; i < ISDN_MODEM_NUMREG; i++) { + sprintf(rb, "S%02d=%03d%s", i, + m->mdmreg[i], ((i + 1) % 10) ? " " : "\r\n"); + isdn_tty_at_cout(rb, info); + } + sprintf(rb, "\r\nEAZ/MSN: %.50s\r\n", + strlen(m->msn) ? m->msn : "None"); + isdn_tty_at_cout(rb, info); + if (strlen(m->lmsn)) { + isdn_tty_at_cout("\r\nListen: ", info); + isdn_tty_at_cout(m->lmsn, info); + isdn_tty_at_cout("\r\n", info); + } + break; + case 'W': + /* &W - Write Profile */ + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + modem_write_profile(m); + break; + default: + PARSE_ERROR1; + } + break; + case 'X': + /* &X - Switch to BTX-Mode and T.70 */ + p[0]++; + switch (isdn_getnum(p)) { + case 0: + m->mdmreg[REG_T70] &= ~(BIT_T70 | BIT_T70_EXT); + info->xmit_size = m->mdmreg[REG_PSIZE] * 16; + break; + case 1: + m->mdmreg[REG_T70] |= BIT_T70; + m->mdmreg[REG_T70] &= ~BIT_T70_EXT; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + info->xmit_size = 112; + m->mdmreg[REG_SI1] = 4; + m->mdmreg[REG_SI2] = 0; + break; + case 2: + m->mdmreg[REG_T70] |= (BIT_T70 | BIT_T70_EXT); + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + info->xmit_size = 112; + m->mdmreg[REG_SI1] = 4; + m->mdmreg[REG_SI2] = 0; + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; +} + +static int +isdn_tty_check_ats(int mreg, int mval, modem_info *info, atemu *m) +{ + /* Some plausibility checks */ + switch (mreg) { + case REG_L2PROT: + if (mval > ISDN_PROTO_L2_MAX) + return 1; + break; + case REG_PSIZE: + if ((mval * 16) > ISDN_SERIAL_XMIT_MAX) + return 1; +#ifdef CONFIG_ISDN_AUDIO + if ((m->mdmreg[REG_SI1] & 1) && (mval > VBUFX)) + return 1; +#endif + info->xmit_size = mval * 16; + switch (m->mdmreg[REG_L2PROT]) { + case ISDN_PROTO_L2_V11096: + case ISDN_PROTO_L2_V11019: + case ISDN_PROTO_L2_V11038: + info->xmit_size /= 10; + } + break; + case REG_SI1I: + case REG_PLAN: + case REG_SCREEN: + /* readonly registers */ + return 1; + } + return 0; +} + +/* + * Perform ATS command + */ +static int +isdn_tty_cmd_ATS(char **p, modem_info *info) +{ + atemu *m = &info->emu; + int bitpos; + int mreg; + int mval; + int bval; + + mreg = isdn_getnum(p); + if (mreg < 0 || mreg >= ISDN_MODEM_NUMREG) + PARSE_ERROR1; + switch (*p[0]) { + case '=': + p[0]++; + mval = isdn_getnum(p); + if (mval < 0 || mval > 255) + PARSE_ERROR1; + if (isdn_tty_check_ats(mreg, mval, info, m)) + PARSE_ERROR1; + m->mdmreg[mreg] = mval; + break; + case '.': + /* Set/Clear a single bit */ + p[0]++; + bitpos = isdn_getnum(p); + if ((bitpos < 0) || (bitpos > 7)) + PARSE_ERROR1; + switch (*p[0]) { + case '=': + p[0]++; + bval = isdn_getnum(p); + if (bval < 0 || bval > 1) + PARSE_ERROR1; + if (bval) + mval = m->mdmreg[mreg] | (1 << bitpos); + else + mval = m->mdmreg[mreg] & ~(1 << bitpos); + if (isdn_tty_check_ats(mreg, mval, info, m)) + PARSE_ERROR1; + m->mdmreg[mreg] = mval; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout((m->mdmreg[mreg] & (1 << bitpos)) ? "1" : "0", + info); + break; + default: + PARSE_ERROR1; + } + break; + case '?': + p[0]++; + isdn_tty_show_profile(mreg, info); + break; + default: + PARSE_ERROR1; + break; + } + return 0; +} + +/* + * Perform ATA command + */ +static void +isdn_tty_cmd_ATA(modem_info *info) +{ + atemu *m = &info->emu; + isdn_ctrl cmd; + int l2; + + if (info->msr & UART_MSR_RI) { + /* Accept incoming call */ + info->last_dir = 0; + strcpy(info->last_num, dev->num[info->drv_index]); + m->mdmreg[REG_RINGCNT] = 0; + info->msr &= ~UART_MSR_RI; + l2 = m->mdmreg[REG_L2PROT]; +#ifdef CONFIG_ISDN_AUDIO + /* If more than one bit set in reg18, autoselect Layer2 */ + if ((m->mdmreg[REG_SI1] & m->mdmreg[REG_SI1I]) != m->mdmreg[REG_SI1]) { + if (m->mdmreg[REG_SI1I] == 1) { + if ((l2 != ISDN_PROTO_L2_MODEM) && (l2 != ISDN_PROTO_L2_FAX)) + l2 = ISDN_PROTO_L2_TRANS; + } else + l2 = ISDN_PROTO_L2_X75I; + } +#endif + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL2; + cmd.arg = info->isdn_channel + (l2 << 8); + info->last_l2 = l2; + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_SETL3; + cmd.arg = info->isdn_channel + (m->mdmreg[REG_L3PROT] << 8); +#ifdef CONFIG_ISDN_TTY_FAX + if (l2 == ISDN_PROTO_L2_FAX) { + cmd.parm.fax = info->fax; + info->fax->direction = ISDN_TTY_FAX_CONN_IN; + } +#endif + isdn_command(&cmd); + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_ACCEPTD; + info->dialing = 16; + info->emu.carrierwait = 0; + isdn_command(&cmd); + isdn_timer_ctrl(ISDN_TIMER_CARRIER, 1); + } else + isdn_tty_modem_result(RESULT_NO_ANSWER, info); +} + +#ifdef CONFIG_ISDN_AUDIO +/* + * Parse AT+F.. commands + */ +static int +isdn_tty_cmd_PLUSF(char **p, modem_info *info) +{ + atemu *m = &info->emu; + char rs[20]; + + if (!strncmp(p[0], "CLASS", 5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", + (m->mdmreg[REG_SI1] & 1) ? 8 : 0); +#ifdef CONFIG_ISDN_TTY_FAX + if (TTY_IS_FCLASS2(info)) + sprintf(rs, "\r\n2"); + else if (TTY_IS_FCLASS1(info)) + sprintf(rs, "\r\n1"); +#endif + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS; + m->mdmreg[REG_SI1] = 4; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; +#ifdef CONFIG_ISDN_TTY_FAX + case '1': + p[0]++; + if (!(dev->global_features & + ISDN_FEATURE_L3_FCLASS1)) + PARSE_ERROR1; + m->mdmreg[REG_SI1] = 1; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS1; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; + case '2': + p[0]++; + if (!(dev->global_features & + ISDN_FEATURE_L3_FCLASS2)) + PARSE_ERROR1; + m->mdmreg[REG_SI1] = 1; + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_FAX; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_FCLASS2; + info->xmit_size = + m->mdmreg[REG_PSIZE] * 16; + break; +#endif + case '8': + p[0]++; + /* L2 will change on dialout with si=1 */ + m->mdmreg[REG_L2PROT] = ISDN_PROTO_L2_X75I; + m->mdmreg[REG_L3PROT] = ISDN_PROTO_L3_TRANS; + m->mdmreg[REG_SI1] = 5; + info->xmit_size = VBUF; + break; + case '?': + p[0]++; + strcpy(rs, "\r\n0,"); +#ifdef CONFIG_ISDN_TTY_FAX + if (dev->global_features & + ISDN_FEATURE_L3_FCLASS1) + strcat(rs, "1,"); + if (dev->global_features & + ISDN_FEATURE_L3_FCLASS2) + strcat(rs, "2,"); +#endif + strcat(rs, "8"); + isdn_tty_at_cout(rs, info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; + } +#ifdef CONFIG_ISDN_TTY_FAX + return (isdn_tty_cmd_PLUSF_FAX(p, info)); +#else + PARSE_ERROR1; +#endif +} + +/* + * Parse AT+V.. commands + */ +static int +isdn_tty_cmd_PLUSV(char **p, modem_info *info) +{ + atemu *m = &info->emu; + isdn_ctrl cmd; + static char *vcmd[] = + {"NH", "IP", "LS", "RX", "SD", "SM", "TX", "DD", NULL}; + int i; + int par1; + int par2; + char rs[20]; + + i = 0; + while (vcmd[i]) { + if (!strncmp(vcmd[i], p[0], 2)) { + p[0] += 2; + break; + } + i++; + } + switch (i) { + case 0: + /* AT+VNH - Auto hangup feature */ + switch (*p[0]) { + case '?': + p[0]++; + isdn_tty_at_cout("\r\n1", info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '1': + p[0]++; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n1", info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 1: + /* AT+VIP - Reset all voice parameters */ + isdn_tty_modem_reset_vpar(m); + break; + case 2: + /* AT+VLS - Select device, accept incoming call */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", m->vpar[0]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '0': + p[0]++; + m->vpar[0] = 0; + break; + case '2': + p[0]++; + m->vpar[0] = 2; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n0,2", info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 3: + /* AT+VRX - Start recording */ + if (!m->vpar[0]) + PARSE_ERROR1; + if (info->online != 1) { + isdn_tty_modem_result(RESULT_NO_ANSWER, info); + return 1; + } + info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state); + if (!info->dtmf_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n"); + PARSE_ERROR1; + } + info->silence_state = isdn_audio_silence_init(info->silence_state); + if (!info->silence_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc silence state\n"); + PARSE_ERROR1; + } + if (m->vpar[3] < 5) { + info->adpcmr = isdn_audio_adpcm_init(info->adpcmr, m->vpar[3]); + if (!info->adpcmr) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n"); + PARSE_ERROR1; + } + } +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: +VRX\n"); +#endif + info->vonline |= 1; + isdn_tty_modem_result(RESULT_CONNECT, info); + return 0; + break; + case 4: + /* AT+VSD - Silence detection */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n<%d>,<%d>", + m->vpar[1], + m->vpar[2]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if ((*p[0] >= '0') && (*p[0] <= '9')) { + par1 = isdn_getnum(p); + if ((par1 < 0) || (par1 > 31)) + PARSE_ERROR1; + if (*p[0] != ',') + PARSE_ERROR1; + p[0]++; + par2 = isdn_getnum(p); + if ((par2 < 0) || (par2 > 255)) + PARSE_ERROR1; + m->vpar[1] = par1; + m->vpar[2] = par2; + break; + } else + if (*p[0] == '?') { + p[0]++; + isdn_tty_at_cout("\r\n<0-31>,<0-255>", + info); + break; + } else + PARSE_ERROR1; + break; + default: + PARSE_ERROR1; + } + break; + case 5: + /* AT+VSM - Select compression */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n<%d>,<%d><8000>", + m->vpar[3], + m->vpar[1]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + switch (*p[0]) { + case '2': + case '3': + case '4': + case '5': + case '6': + par1 = isdn_getnum(p); + if ((par1 < 2) || (par1 > 6)) + PARSE_ERROR1; + m->vpar[3] = par1; + break; + case '?': + p[0]++; + isdn_tty_at_cout("\r\n2;ADPCM;2;0;(8000)\r\n", + info); + isdn_tty_at_cout("3;ADPCM;3;0;(8000)\r\n", + info); + isdn_tty_at_cout("4;ADPCM;4;0;(8000)\r\n", + info); + isdn_tty_at_cout("5;ALAW;8;0;(8000)\r\n", + info); + isdn_tty_at_cout("6;ULAW;8;0;(8000)\r\n", + info); + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + break; + case 6: + /* AT+VTX - Start sending */ + if (!m->vpar[0]) + PARSE_ERROR1; + if (info->online != 1) { + isdn_tty_modem_result(RESULT_NO_ANSWER, info); + return 1; + } + info->dtmf_state = isdn_audio_dtmf_init(info->dtmf_state); + if (!info->dtmf_state) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc dtmf state\n"); + PARSE_ERROR1; + } + if (m->vpar[3] < 5) { + info->adpcms = isdn_audio_adpcm_init(info->adpcms, m->vpar[3]); + if (!info->adpcms) { + printk(KERN_WARNING "isdn_tty: Couldn't malloc adpcm state\n"); + PARSE_ERROR1; + } + } +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: +VTX\n"); +#endif + m->lastDLE = 0; + info->vonline |= 2; + isdn_tty_modem_result(RESULT_CONNECT, info); + return 0; + break; + case 7: + /* AT+VDD - DTMF detection */ + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n<%d>,<%d>", + m->vpar[4], + m->vpar[5]); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if ((*p[0] >= '0') && (*p[0] <= '9')) { + if (info->online != 1) + PARSE_ERROR1; + par1 = isdn_getnum(p); + if ((par1 < 0) || (par1 > 15)) + PARSE_ERROR1; + if (*p[0] != ',') + PARSE_ERROR1; + p[0]++; + par2 = isdn_getnum(p); + if ((par2 < 0) || (par2 > 255)) + PARSE_ERROR1; + m->vpar[4] = par1; + m->vpar[5] = par2; + cmd.driver = info->isdn_driver; + cmd.command = ISDN_CMD_AUDIO; + cmd.arg = info->isdn_channel + (ISDN_AUDIO_SETDD << 8); + cmd.parm.num[0] = par1; + cmd.parm.num[1] = par2; + isdn_command(&cmd); + break; + } else + if (*p[0] == '?') { + p[0]++; + isdn_tty_at_cout("\r\n<0-15>,<0-255>", + info); + break; + } else + PARSE_ERROR1; + break; + default: + PARSE_ERROR1; + } + break; + default: + PARSE_ERROR1; + } + return 0; +} +#endif /* CONFIG_ISDN_AUDIO */ + +/* + * Parse and perform an AT-command-line. + */ +static void +isdn_tty_parse_at(modem_info *info) +{ + atemu *m = &info->emu; + char *p; + char ds[ISDN_MSNLEN]; + +#ifdef ISDN_DEBUG_AT + printk(KERN_DEBUG "AT: '%s'\n", m->mdmcmd); +#endif + for (p = &m->mdmcmd[2]; *p;) { + switch (*p) { + case ' ': + p++; + break; + case 'A': + /* A - Accept incoming call */ + p++; + isdn_tty_cmd_ATA(info); + return; + case 'D': + /* D - Dial */ + if (info->msr & UART_MSR_DCD) + PARSE_ERROR; + if (info->msr & UART_MSR_RI) { + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + return; + } + isdn_tty_getdial(++p, ds, sizeof ds); + p += strlen(p); + if (!strlen(m->msn)) + isdn_tty_modem_result(RESULT_NO_MSN_EAZ, info); + else if (strlen(ds)) + isdn_tty_dial(ds, info, m); + else + PARSE_ERROR; + return; + case 'E': + /* E - Turn Echo on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[REG_ECHO] &= ~BIT_ECHO; + break; + case 1: + m->mdmreg[REG_ECHO] |= BIT_ECHO; + break; + default: + PARSE_ERROR; + } + break; + case 'H': + /* H - On/Off-hook */ + p++; + switch (*p) { + case '0': + p++; + isdn_tty_on_hook(info); + break; + case '1': + p++; + isdn_tty_off_hook(); + break; + default: + isdn_tty_on_hook(info); + break; + } + break; + case 'I': + /* I - Information */ + p++; + isdn_tty_at_cout("\r\nLinux ISDN", info); + switch (*p) { + case '0': + case '1': + p++; + break; + case '2': + p++; + isdn_tty_report(info); + break; + case '3': + p++; + snprintf(ds, sizeof(ds), "\r\n%d", info->emu.charge); + isdn_tty_at_cout(ds, info); + break; + default:; + } + break; +#ifdef DUMMY_HAYES_AT + case 'L': + case 'M': + /* only for be compilant with common scripts */ + /* no function */ + p++; + isdn_getnum(&p); + break; +#endif + case 'O': + /* O - Go online */ + p++; + if (info->msr & UART_MSR_DCD) + /* if B-Channel is up */ + isdn_tty_modem_result((m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM) ? RESULT_CONNECT : RESULT_CONNECT64000, info); + else + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + return; + case 'Q': + /* Q - Turn Emulator messages on/off */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[REG_RESP] |= BIT_RESP; + break; + case 1: + m->mdmreg[REG_RESP] &= ~BIT_RESP; + break; + default: + PARSE_ERROR; + } + break; + case 'S': + /* S - Set/Get Register */ + p++; + if (isdn_tty_cmd_ATS(&p, info)) + return; + break; + case 'V': + /* V - Numeric or ASCII Emulator-messages */ + p++; + switch (isdn_getnum(&p)) { + case 0: + m->mdmreg[REG_RESP] |= BIT_RESPNUM; + break; + case 1: + m->mdmreg[REG_RESP] &= ~BIT_RESPNUM; + break; + default: + PARSE_ERROR; + } + break; + case 'Z': + /* Z - Load Registers from Profile */ + p++; + if (info->msr & UART_MSR_DCD) { + info->online = 0; + isdn_tty_on_hook(info); + } + isdn_tty_modem_reset_regs(info, 1); + break; + case '+': + p++; + switch (*p) { +#ifdef CONFIG_ISDN_AUDIO + case 'F': + p++; + if (isdn_tty_cmd_PLUSF(&p, info)) + return; + break; + case 'V': + if ((!(m->mdmreg[REG_SI1] & 1)) || + (m->mdmreg[REG_L2PROT] == ISDN_PROTO_L2_MODEM)) + PARSE_ERROR; + p++; + if (isdn_tty_cmd_PLUSV(&p, info)) + return; + break; +#endif /* CONFIG_ISDN_AUDIO */ + case 'S': /* SUSPEND */ + p++; + isdn_tty_get_msnstr(ds, &p); + isdn_tty_suspend(ds, info, m); + break; + case 'R': /* RESUME */ + p++; + isdn_tty_get_msnstr(ds, &p); + isdn_tty_resume(ds, info, m); + break; + case 'M': /* MESSAGE */ + p++; + isdn_tty_send_msg(info, m, p); + break; + default: + PARSE_ERROR; + } + break; + case '&': + p++; + if (isdn_tty_cmd_ATand(&p, info)) + return; + break; + default: + PARSE_ERROR; + } + } +#ifdef CONFIG_ISDN_AUDIO + if (!info->vonline) +#endif + isdn_tty_modem_result(RESULT_OK, info); +} + +/* Need own toupper() because standard-toupper is not available + * within modules. + */ +#define my_toupper(c) (((c >= 'a') && (c <= 'z')) ? (c & 0xdf) : c) + +/* + * Perform line-editing of AT-commands + * + * Parameters: + * p inputbuffer + * count length of buffer + * channel index to line (minor-device) + */ +static int +isdn_tty_edit_at(const char *p, int count, modem_info *info) +{ + atemu *m = &info->emu; + int total = 0; + u_char c; + char eb[2]; + int cnt; + + for (cnt = count; cnt > 0; p++, cnt--) { + c = *p; + total++; + if (c == m->mdmreg[REG_CR] || c == m->mdmreg[REG_LF]) { + /* Separator (CR or LF) */ + m->mdmcmd[m->mdmcmdl] = 0; + if (m->mdmreg[REG_ECHO] & BIT_ECHO) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if ((m->mdmcmdl >= 2) && (!(strncmp(m->mdmcmd, "AT", 2)))) + isdn_tty_parse_at(info); + m->mdmcmdl = 0; + continue; + } + if (c == m->mdmreg[REG_BS] && m->mdmreg[REG_BS] < 128) { + /* Backspace-Function */ + if ((m->mdmcmdl > 2) || (!m->mdmcmdl)) { + if (m->mdmcmdl) + m->mdmcmdl--; + if (m->mdmreg[REG_ECHO] & BIT_ECHO) + isdn_tty_at_cout("\b", info); + } + continue; + } + if (cmdchar(c)) { + if (m->mdmreg[REG_ECHO] & BIT_ECHO) { + eb[0] = c; + eb[1] = 0; + isdn_tty_at_cout(eb, info); + } + if (m->mdmcmdl < 255) { + c = my_toupper(c); + switch (m->mdmcmdl) { + case 1: + if (c == 'T') { + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + break; + } else + m->mdmcmdl = 0; + /* Fall through, check for 'A' */ + case 0: + if (c == 'A') { + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + } + break; + default: + m->mdmcmd[m->mdmcmdl] = c; + m->mdmcmd[++m->mdmcmdl] = 0; + } + } + } + } + return total; +} + +/* + * Switch all modem-channels who are online and got a valid + * escape-sequence 1.5 seconds ago, to command-mode. + * This function is called every second via timer-interrupt from within + * timer-dispatcher isdn_timer_function() + */ +void +isdn_tty_modem_escape(void) +{ + int ton = 0; + int i; + int midx; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) + if (USG_MODEM(dev->usage[i]) && (midx = dev->m_idx[i]) >= 0) { + modem_info *info = &dev->mdm.info[midx]; + if (info->online) { + ton = 1; + if ((info->emu.pluscount == 3) && + time_after(jiffies, + info->emu.lastplus + PLUSWAIT2)) { + info->emu.pluscount = 0; + info->online = 0; + isdn_tty_modem_result(RESULT_OK, info); + } + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMPLUS, ton); +} + +/* + * Put a RING-message to all modem-channels who have the RI-bit set. + * This function is called every second via timer-interrupt from within + * timer-dispatcher isdn_timer_function() + */ +void +isdn_tty_modem_ring(void) +{ + int ton = 0; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (info->msr & UART_MSR_RI) { + ton = 1; + isdn_tty_modem_result(RESULT_RING, info); + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMRING, ton); +} + +/* + * For all online tty's, try sending data to + * the lower levels. + */ +void +isdn_tty_modem_xmit(void) +{ + int ton = 1; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (info->online) { + ton = 1; + isdn_tty_senddown(info); + isdn_tty_tint(info); + } + } + isdn_timer_ctrl(ISDN_TIMER_MODEMXMIT, ton); +} + +/* + * Check all channels if we have a 'no carrier' timeout. + * Timeout value is set by Register S7. + */ +void +isdn_tty_carrier_timeout(void) +{ + int ton = 0; + int i; + + for (i = 0; i < ISDN_MAX_CHANNELS; i++) { + modem_info *info = &dev->mdm.info[i]; + if (!info->dialing) + continue; + if (info->emu.carrierwait++ > info->emu.mdmreg[REG_WAITC]) { + info->dialing = 0; + isdn_tty_modem_result(RESULT_NO_CARRIER, info); + isdn_tty_modem_hup(info, 1); + } else + ton = 1; + } + isdn_timer_ctrl(ISDN_TIMER_CARRIER, ton); +} diff --git a/kernel/drivers/isdn/i4l/isdn_tty.h b/kernel/drivers/isdn/i4l/isdn_tty.h new file mode 100644 index 000000000..a6f801d22 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_tty.h @@ -0,0 +1,120 @@ +/* $Id: isdn_tty.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, tty related functions (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. + * + */ + + +#define DLE 0x10 +#define ETX 0x03 +#define DC4 0x14 + + +/* + * Definition of some special Registers of AT-Emulator + */ +#define REG_RINGATA 0 +#define REG_RINGCNT 1 /* ring counter register */ +#define REG_ESC 2 +#define REG_CR 3 +#define REG_LF 4 +#define REG_BS 5 + +#define REG_WAITC 7 + +#define REG_RESP 12 /* show response messages register */ +#define BIT_RESP 1 /* show response messages bit */ +#define REG_RESPNUM 12 /* show numeric responses register */ +#define BIT_RESPNUM 2 /* show numeric responses bit */ +#define REG_ECHO 12 +#define BIT_ECHO 4 +#define REG_DCD 12 +#define BIT_DCD 8 +#define REG_CTS 12 +#define BIT_CTS 16 +#define REG_DTRR 12 +#define BIT_DTRR 32 +#define REG_DSR 12 +#define BIT_DSR 64 +#define REG_CPPP 12 +#define BIT_CPPP 128 + +#define REG_DXMT 13 +#define BIT_DXMT 1 +#define REG_T70 13 +#define BIT_T70 2 +#define BIT_T70_EXT 32 +#define REG_DTRHUP 13 +#define BIT_DTRHUP 4 +#define REG_RESPXT 13 +#define BIT_RESPXT 8 +#define REG_CIDONCE 13 +#define BIT_CIDONCE 16 +#define REG_RUNG 13 /* show RUNG message register */ +#define BIT_RUNG 64 /* show RUNG message bit */ +#define REG_DISPLAY 13 +#define BIT_DISPLAY 128 + +#define REG_L2PROT 14 +#define REG_L3PROT 15 +#define REG_PSIZE 16 +#define REG_WSIZE 17 +#define REG_SI1 18 +#define REG_SI2 19 +#define REG_SI1I 20 +#define REG_PLAN 21 +#define REG_SCREEN 22 + +#define REG_CPN 23 +#define BIT_CPN 1 +#define REG_CPNFCON 23 +#define BIT_CPNFCON 2 +#define REG_CDN 23 +#define BIT_CDN 4 + +/* defines for result codes */ +#define RESULT_OK 0 +#define RESULT_CONNECT 1 +#define RESULT_RING 2 +#define RESULT_NO_CARRIER 3 +#define RESULT_ERROR 4 +#define RESULT_CONNECT64000 5 +#define RESULT_NO_DIALTONE 6 +#define RESULT_BUSY 7 +#define RESULT_NO_ANSWER 8 +#define RESULT_RINGING 9 +#define RESULT_NO_MSN_EAZ 10 +#define RESULT_VCON 11 +#define RESULT_RUNG 12 + +#define TTY_IS_FCLASS1(info) \ + ((info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_FAX) && \ + (info->emu.mdmreg[REG_L3PROT] == ISDN_PROTO_L3_FCLASS1)) +#define TTY_IS_FCLASS2(info) \ + ((info->emu.mdmreg[REG_L2PROT] == ISDN_PROTO_L2_FAX) && \ + (info->emu.mdmreg[REG_L3PROT] == ISDN_PROTO_L3_FCLASS2)) + +extern void isdn_tty_modem_escape(void); +extern void isdn_tty_modem_ring(void); +extern void isdn_tty_carrier_timeout(void); +extern void isdn_tty_modem_xmit(void); +extern int isdn_tty_modem_init(void); +extern void isdn_tty_exit(void); +extern void isdn_tty_readmodem(void); +extern int isdn_tty_find_icall(int, int, setup_parm *); +extern int isdn_tty_stat_callback(int, isdn_ctrl *); +extern int isdn_tty_rcv_skb(int, int, int, struct sk_buff *); +extern int isdn_tty_capi_facility(capi_msg *cm); +extern void isdn_tty_at_cout(char *, modem_info *); +extern void isdn_tty_modem_hup(modem_info *, int); +#ifdef CONFIG_ISDN_TTY_FAX +extern int isdn_tty_cmd_PLUSF_FAX(char **, modem_info *); +extern int isdn_tty_fax_command(modem_info *, isdn_ctrl *); +extern void isdn_tty_fax_bitorder(modem_info *, struct sk_buff *); +#endif diff --git a/kernel/drivers/isdn/i4l/isdn_ttyfax.c b/kernel/drivers/isdn/i4l/isdn_ttyfax.c new file mode 100644 index 000000000..47aae4916 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_ttyfax.c @@ -0,0 +1,1123 @@ +/* $Id: isdn_ttyfax.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, tty_fax AT-command emulator (linklevel). + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 by Ralf Spachmann (mel@melware.de) + * Copyright 1999 by Cytronics & Melware + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#undef ISDN_TTY_FAX_STAT_DEBUG +#undef ISDN_TTY_FAX_CMD_DEBUG + +#include <linux/isdn.h> +#include "isdn_common.h" +#include "isdn_tty.h" +#include "isdn_ttyfax.h" + + +static char *isdn_tty_fax_revision = "$Revision: 1.1.2.2 $"; + +#define PARSE_ERROR1 { isdn_tty_fax_modem_result(1, info); return 1; } + +static char * +isdn_getrev(const char *revision) +{ + char *rev; + char *p; + + if ((p = strchr(revision, ':'))) { + rev = p + 2; + p = strchr(rev, '$'); + *--p = 0; + } else + rev = "???"; + return rev; +} + +/* + * Fax Class 2 Modem results + * + */ + +static void +isdn_tty_fax_modem_result(int code, modem_info *info) +{ + atemu *m = &info->emu; + T30_s *f = info->fax; + char rs[50]; + char rss[50]; + char *rp; + int i; + static char *msg[] = + {"OK", "ERROR", "+FCON", "+FCSI:", "+FDIS:", + "+FHNG:", "+FDCS:", "CONNECT", "+FTSI:", + "+FCFR", "+FPTS:", "+FET:"}; + + + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[code], info); + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax send %s on ttyI%d\n", + msg[code], info->line); +#endif + switch (code) { + case 0: /* OK */ + break; + case 1: /* ERROR */ + break; + case 2: /* +FCON */ + /* Append CPN, if enabled */ + if ((m->mdmreg[REG_CPNFCON] & BIT_CPNFCON) && + (!(dev->usage[info->isdn_channel] & ISDN_USAGE_OUTGOING))) { + sprintf(rs, "/%s", m->cpn); + isdn_tty_at_cout(rs, info); + } + info->online = 1; + f->fet = 0; + if (f->phase == ISDN_FAX_PHASE_A) + f->phase = ISDN_FAX_PHASE_B; + break; + case 3: /* +FCSI */ + case 8: /* +FTSI */ + sprintf(rs, "\"%s\"", f->r_id); + isdn_tty_at_cout(rs, info); + break; + case 4: /* +FDIS */ + rs[0] = 0; + rp = &f->r_resolution; + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax DIS=%s on ttyI%d\n", + rs, info->line); +#endif + break; + case 5: /* +FHNG */ + sprintf(rs, "%d", f->code); + isdn_tty_at_cout(rs, info); + info->faxonline = 0; + break; + case 6: /* +FDCS */ + rs[0] = 0; + rp = &f->r_resolution; + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax DCS=%s on ttyI%d\n", + rs, info->line); +#endif + break; + case 7: /* CONNECT */ + info->faxonline |= 2; + break; + case 9: /* FCFR */ + break; + case 10: /* FPTS */ + isdn_tty_at_cout("1", info); + break; + case 11: /* FET */ + sprintf(rs, "%d", f->fet); + isdn_tty_at_cout(rs, info); + break; + } + + isdn_tty_at_cout("\r\n", info); + + switch (code) { + case 7: /* CONNECT */ + info->online = 2; + if (info->faxonline & 1) { + sprintf(rs, "%c", XON); + isdn_tty_at_cout(rs, info); + } + break; + } +} + +static int +isdn_tty_fax_command1(modem_info *info, isdn_ctrl *c) +{ + static char *msg[] = + {"OK", "CONNECT", "NO CARRIER", "ERROR", "FCERROR"}; + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: FCLASS1 cmd(%d)\n", c->parm.aux.cmd); +#endif + if (c->parm.aux.cmd < ISDN_FAX_CLASS1_QUERY) { + if (info->online) + info->online = 1; + isdn_tty_at_cout("\r\n", info); + isdn_tty_at_cout(msg[c->parm.aux.cmd], info); + isdn_tty_at_cout("\r\n", info); + } + switch (c->parm.aux.cmd) { + case ISDN_FAX_CLASS1_CONNECT: + info->online = 2; + break; + case ISDN_FAX_CLASS1_OK: + case ISDN_FAX_CLASS1_FCERROR: + case ISDN_FAX_CLASS1_ERROR: + case ISDN_FAX_CLASS1_NOCARR: + break; + case ISDN_FAX_CLASS1_QUERY: + isdn_tty_at_cout("\r\n", info); + if (!c->parm.aux.para[0]) { + isdn_tty_at_cout(msg[ISDN_FAX_CLASS1_ERROR], info); + isdn_tty_at_cout("\r\n", info); + } else { + isdn_tty_at_cout(c->parm.aux.para, info); + isdn_tty_at_cout("\r\nOK\r\n", info); + } + break; + } + return (0); +} + +int +isdn_tty_fax_command(modem_info *info, isdn_ctrl *c) +{ + T30_s *f = info->fax; + char rs[10]; + + if (TTY_IS_FCLASS1(info)) + return (isdn_tty_fax_command1(info, c)); + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax cmd %d on ttyI%d\n", + f->r_code, info->line); +#endif + switch (f->r_code) { + case ISDN_TTY_FAX_FCON: + info->faxonline = 1; + isdn_tty_fax_modem_result(2, info); /* +FCON */ + return (0); + case ISDN_TTY_FAX_FCON_I: + info->faxonline = 16; + isdn_tty_fax_modem_result(2, info); /* +FCON */ + return (0); + case ISDN_TTY_FAX_RID: + if (info->faxonline & 1) + isdn_tty_fax_modem_result(3, info); /* +FCSI */ + if (info->faxonline & 16) + isdn_tty_fax_modem_result(8, info); /* +FTSI */ + return (0); + case ISDN_TTY_FAX_DIS: + isdn_tty_fax_modem_result(4, info); /* +FDIS */ + return (0); + case ISDN_TTY_FAX_HNG: + if (f->phase == ISDN_FAX_PHASE_C) { + if (f->direction == ISDN_TTY_FAX_CONN_IN) { + sprintf(rs, "%c%c", DLE, ETX); + isdn_tty_at_cout(rs, info); + } else { + sprintf(rs, "%c", 0x18); + isdn_tty_at_cout(rs, info); + } + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + } + f->phase = ISDN_FAX_PHASE_E; + isdn_tty_fax_modem_result(5, info); /* +FHNG */ + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_DCS: + isdn_tty_fax_modem_result(6, info); /* +FDCS */ + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + f->phase = ISDN_FAX_PHASE_C; + return (0); + case ISDN_TTY_FAX_TRAIN_OK: + isdn_tty_fax_modem_result(6, info); /* +FDCS */ + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_SENT: + isdn_tty_fax_modem_result(0, info); /* OK */ + return (0); + case ISDN_TTY_FAX_CFR: + isdn_tty_fax_modem_result(9, info); /* +FCFR */ + return (0); + case ISDN_TTY_FAX_ET: + sprintf(rs, "%c%c", DLE, ETX); + isdn_tty_at_cout(rs, info); + isdn_tty_fax_modem_result(10, info); /* +FPTS */ + isdn_tty_fax_modem_result(11, info); /* +FET */ + isdn_tty_fax_modem_result(0, info); /* OK */ + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + f->phase = ISDN_FAX_PHASE_D; + return (0); + case ISDN_TTY_FAX_PTS: + isdn_tty_fax_modem_result(10, info); /* +FPTS */ + if (f->direction == ISDN_TTY_FAX_CONN_OUT) { + if (f->fet == 1) + f->phase = ISDN_FAX_PHASE_B; + if (f->fet == 0) + isdn_tty_fax_modem_result(0, info); /* OK */ + } + return (0); + case ISDN_TTY_FAX_EOP: + info->faxonline &= ~2; /* leave data mode */ + info->online = 1; + f->phase = ISDN_FAX_PHASE_D; + return (0); + + } + return (-1); +} + + +void +isdn_tty_fax_bitorder(modem_info *info, struct sk_buff *skb) +{ + __u8 LeftMask; + __u8 RightMask; + __u8 fBit; + __u8 Data; + int i; + + if (!info->fax->bor) { + for (i = 0; i < skb->len; i++) { + Data = skb->data[i]; + for ( + LeftMask = 0x80, RightMask = 0x01; + LeftMask > RightMask; + LeftMask >>= 1, RightMask <<= 1 + ) { + fBit = (Data & LeftMask); + if (Data & RightMask) + Data |= LeftMask; + else + Data &= ~LeftMask; + if (fBit) + Data |= RightMask; + else + Data &= ~RightMask; + + } + skb->data[i] = Data; + } + } +} + +/* + * Parse AT+F.. FAX class 1 commands + */ + +static int +isdn_tty_cmd_FCLASS1(char **p, modem_info *info) +{ + static char *cmd[] = + {"AE", "TS", "RS", "TM", "RM", "TH", "RH"}; + isdn_ctrl c; + int par, i; + u_long flags; + + for (c.parm.aux.cmd = 0; c.parm.aux.cmd < 7; c.parm.aux.cmd++) + if (!strncmp(p[0], cmd[c.parm.aux.cmd], 2)) + break; + +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty_cmd_FCLASS1 (%s,%d)\n", p[0], c.parm.aux.cmd); +#endif + if (c.parm.aux.cmd == 7) + PARSE_ERROR1; + + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + c.parm.aux.subcmd = AT_QUERY; + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + c.parm.aux.subcmd = AT_EQ_QUERY; + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + c.parm.aux.subcmd = AT_EQ_VALUE; + c.parm.aux.para[0] = par; + } + break; + case 0: + c.parm.aux.subcmd = AT_COMMAND; + break; + default: + PARSE_ERROR1; + } + c.command = ISDN_CMD_FAXCMD; +#ifdef ISDN_TTY_FAX_CMD_DEBUG + printk(KERN_DEBUG "isdn_tty_cmd_FCLASS1 %d/%d/%d)\n", + c.parm.aux.cmd, c.parm.aux.subcmd, c.parm.aux.para[0]); +#endif + if (info->isdn_driver < 0) { + if ((c.parm.aux.subcmd == AT_EQ_VALUE) || + (c.parm.aux.subcmd == AT_COMMAND)) { + PARSE_ERROR1; + } + spin_lock_irqsave(&dev->lock, flags); + /* get a temporary connection to the first free fax driver */ + i = isdn_get_free_channel(ISDN_USAGE_FAX, ISDN_PROTO_L2_FAX, + ISDN_PROTO_L3_FCLASS1, -1, -1, "00"); + if (i < 0) { + spin_unlock_irqrestore(&dev->lock, flags); + PARSE_ERROR1; + } + info->isdn_driver = dev->drvmap[i]; + info->isdn_channel = dev->chanmap[i]; + info->drv_index = i; + dev->m_idx[i] = info->line; + spin_unlock_irqrestore(&dev->lock, flags); + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + isdn_command(&c); + spin_lock_irqsave(&dev->lock, flags); + isdn_free_channel(info->isdn_driver, info->isdn_channel, + ISDN_USAGE_FAX); + info->isdn_driver = -1; + info->isdn_channel = -1; + if (info->drv_index >= 0) { + dev->m_idx[info->drv_index] = -1; + info->drv_index = -1; + } + spin_unlock_irqrestore(&dev->lock, flags); + } else { + c.driver = info->isdn_driver; + c.arg = info->isdn_channel; + isdn_command(&c); + } + return 1; +} + +/* + * Parse AT+F.. FAX class 2 commands + */ + +static int +isdn_tty_cmd_FCLASS2(char **p, modem_info *info) +{ + atemu *m = &info->emu; + T30_s *f = info->fax; + isdn_ctrl cmd; + int par; + char rs[50]; + char rss[50]; + int maxdccval[] = + {1, 5, 2, 2, 3, 2, 0, 7}; + + /* FAA still unchanged */ + if (!strncmp(p[0], "AA", 2)) { /* TODO */ + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", 0); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BADLIN=value - dummy 0=disable errorchk disabled, 1-255 nr. of lines for making page bad */ + if (!strncmp(p[0], "BADLIN", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->badlin); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->badlin = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBADLIN=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BADMUL=value - dummy 0=disable errorchk disabled (threshold multiplier) */ + if (!strncmp(p[0], "BADMUL", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->badmul); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->badmul = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBADMUL=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BOR=n - Phase C bit order, 0=direct, 1=reverse */ + if (!strncmp(p[0], "BOR", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->bor); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->bor = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBOR=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* NBC=n - No Best Capabilities */ + if (!strncmp(p[0], "NBC", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->nbc); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->nbc = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FNBC=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* BUF? - Readonly buffersize readout */ + if (!strncmp(p[0], "BUF?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FBUF? (%d) \n", (16 * m->mdmreg[REG_PSIZE])); +#endif + p[0]++; + sprintf(rs, "\r\n %d ", (16 * m->mdmreg[REG_PSIZE])); + isdn_tty_at_cout(rs, info); + return 0; + } + /* CIG=string - local fax station id string for polling rx */ + if (!strncmp(p[0], "CIG", 3)) { + int i, r; + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n\"%s\"", f->pollid); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n\"STRING\""); + isdn_tty_at_cout(rs, info); + } else { + if (*p[0] == '"') + p[0]++; + for (i = 0; (*p[0]) && i < (FAXIDLEN - 1) && (*p[0] != '"'); i++) { + f->pollid[i] = *p[0]++; + } + if (*p[0] == '"') + p[0]++; + for (r = i; r < FAXIDLEN; r++) { + f->pollid[r] = 32; + } + f->pollid[FAXIDLEN - 1] = 0; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax local poll ID rx \"%s\"\n", f->pollid); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CQ=n - copy qlty chk, 0= no chk, 1=only 1D chk, 2=1D+2D chk */ + if (!strncmp(p[0], "CQ", 2)) { + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->cq); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1,2"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 2)) + PARSE_ERROR1; + f->cq = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCQ=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CR=n - can receive? 0= no data rx or poll remote dev, 1=do receive data or poll remote dev */ + if (!strncmp(p[0], "CR", 2)) { + p[0] += 2; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->cr); /* read actual value from struct and print */ + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); /* display online help */ + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->cr = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCR=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* CTCRTY=value - ECM retry count */ + if (!strncmp(p[0], "CTCRTY", 6)) { + p[0] += 6; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->ctcrty); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->ctcrty = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FCTCRTY=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DCC=vr,br,wd,ln,df,ec,bf,st - DCE capabilities parms */ + if (!strncmp(p[0], "DCC", 3)) { + char *rp = &f->resolution; + int i; + + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + strcpy(rs, "\r\n"); + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + isdn_tty_at_cout("\r\n(0,1),(0-5),(0-2),(0-2),(0-3),(0-2),(0),(0-7)", info); + p[0]++; + } else { + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 8); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[i]) { + PARSE_ERROR1; + } + rp[i] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDCC capabilities DCE=%d,%d,%d,%d,%d,%d,%d,%d\n", + rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7]); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DIS=vr,br,wd,ln,df,ec,bf,st - current session parms */ + if (!strncmp(p[0], "DIS", 3)) { + char *rp = &f->resolution; + int i; + + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + strcpy(rs, "\r\n"); + for (i = 0; i < 8; i++) { + sprintf(rss, "%c%s", rp[i] + 48, + (i < 7) ? "," : ""); + strcat(rs, rss); + } + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + isdn_tty_at_cout("\r\n(0,1),(0-5),(0-2),(0-2),(0-3),(0-2),(0),(0-7)", info); + p[0]++; + } else { + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 8); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[i]) { + PARSE_ERROR1; + } + rp[i] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDIS session parms=%d,%d,%d,%d,%d,%d,%d,%d\n", + rp[0], rp[1], rp[2], rp[3], rp[4], rp[5], rp[6], rp[7]); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* DR - Receive Phase C data command, initiates document reception */ + if (!strncmp(p[0], "DR", 2)) { + p[0] += 2; + if ((info->faxonline & 16) && /* incoming connection */ + ((f->phase == ISDN_FAX_PHASE_B) || (f->phase == ISDN_FAX_PHASE_D))) { +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDR\n"); +#endif + f->code = ISDN_TTY_FAX_DR; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); + if (f->phase == ISDN_FAX_PHASE_B) { + f->phase = ISDN_FAX_PHASE_C; + } else if (f->phase == ISDN_FAX_PHASE_D) { + switch (f->fet) { + case 0: /* next page will be received */ + f->phase = ISDN_FAX_PHASE_C; + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + break; + case 1: /* next doc will be received */ + f->phase = ISDN_FAX_PHASE_B; + break; + case 2: /* fax session is terminating */ + f->phase = ISDN_FAX_PHASE_E; + break; + default: + PARSE_ERROR1; + } + } + } else { + PARSE_ERROR1; + } + return 1; + } + /* DT=df,vr,wd,ln - TX phase C data command (release DCE to proceed with negotiation) */ + if (!strncmp(p[0], "DT", 2)) { + int i, val[] = + {4, 0, 2, 3}; + char *rp = &f->resolution; + + p[0] += 2; + if (!(info->faxonline & 1)) /* not outgoing connection */ + PARSE_ERROR1; + + for (i = 0; (((*p[0] >= '0') && (*p[0] <= '9')) || (*p[0] == ',')) && (i < 4); i++) { + if (*p[0] != ',') { + if ((*p[0] - 48) > maxdccval[val[i]]) { + PARSE_ERROR1; + } + rp[val[i]] = *p[0] - 48; + p[0]++; + if (*p[0] == ',') + p[0]++; + } else + p[0]++; + } +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FDT tx data command parms=%d,%d,%d,%d\n", + rp[4], rp[0], rp[2], rp[3]); +#endif + if ((f->phase == ISDN_FAX_PHASE_B) || (f->phase == ISDN_FAX_PHASE_D)) { + f->code = ISDN_TTY_FAX_DT; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); + if (f->phase == ISDN_FAX_PHASE_D) { + f->phase = ISDN_FAX_PHASE_C; + isdn_tty_fax_modem_result(7, info); /* CONNECT */ + } + } else { + PARSE_ERROR1; + } + return 1; + } + /* ECM=n - Error mode control 0=disabled, 2=enabled, handled by DCE alone incl. buff of partial pages */ + if (!strncmp(p[0], "ECM", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->ecm); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,2"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par != 0) && (par != 2)) + PARSE_ERROR1; + f->ecm = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FECM=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* ET=n - End of page or document */ + if (!strncmp(p[0], "ET=", 3)) { + p[0] += 3; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-2"); + isdn_tty_at_cout(rs, info); + } else { + if ((f->phase != ISDN_FAX_PHASE_D) || + (!(info->faxonline & 1))) + PARSE_ERROR1; + par = isdn_getnum(p); + if ((par < 0) || (par > 2)) + PARSE_ERROR1; + f->fet = par; + f->code = ISDN_TTY_FAX_ET; + cmd.driver = info->isdn_driver; + cmd.arg = info->isdn_channel; + cmd.command = ISDN_CMD_FAXCMD; + isdn_command(&cmd); +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FET=%d\n", par); +#endif + return 1; + } + return 0; + } + /* K - terminate */ + if (!strncmp(p[0], "K", 1)) { + p[0] += 1; + if ((f->phase == ISDN_FAX_PHASE_IDLE) || (f->phase == ISDN_FAX_PHASE_E)) + PARSE_ERROR1; + isdn_tty_modem_hup(info, 1); + return 1; + } + /* LID=string - local fax ID */ + if (!strncmp(p[0], "LID", 3)) { + int i, r; + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n\"%s\"", f->id); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n\"STRING\""); + isdn_tty_at_cout(rs, info); + } else { + if (*p[0] == '"') + p[0]++; + for (i = 0; (*p[0]) && i < (FAXIDLEN - 1) && (*p[0] != '"'); i++) { + f->id[i] = *p[0]++; + } + if (*p[0] == '"') + p[0]++; + for (r = i; r < FAXIDLEN; r++) { + f->id[r] = 32; + } + f->id[FAXIDLEN - 1] = 0; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax local ID \"%s\"\n", f->id); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + + /* MDL? - DCE Model */ + if (!strncmp(p[0], "MDL?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FMDL?\n"); +#endif + isdn_tty_at_cout("\r\nisdn4linux", info); + return 0; + } + /* MFR? - DCE Manufacturer */ + if (!strncmp(p[0], "MFR?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FMFR?\n"); +#endif + isdn_tty_at_cout("\r\nisdn4linux", info); + return 0; + } + /* MINSP=n - Minimum Speed for Phase C */ + if (!strncmp(p[0], "MINSP", 5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->minsp); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-5"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 5)) + PARSE_ERROR1; + f->minsp = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FMINSP=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* PHCTO=value - DTE phase C timeout */ + if (!strncmp(p[0], "PHCTO", 5)) { + p[0] += 5; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->phcto); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0-255"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 255)) + PARSE_ERROR1; + f->phcto = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FPHCTO=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + + /* REL=n - Phase C received EOL alignment */ + if (!strncmp(p[0], "REL", 3)) { + p[0] += 3; + switch (*p[0]) { + case '?': + p[0]++; + sprintf(rs, "\r\n%d", f->rel); + isdn_tty_at_cout(rs, info); + break; + case '=': + p[0]++; + if (*p[0] == '?') { + p[0]++; + sprintf(rs, "\r\n0,1"); + isdn_tty_at_cout(rs, info); + } else { + par = isdn_getnum(p); + if ((par < 0) || (par > 1)) + PARSE_ERROR1; + f->rel = par; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FREL=%d\n", par); +#endif + } + break; + default: + PARSE_ERROR1; + } + return 0; + } + /* REV? - DCE Revision */ + if (!strncmp(p[0], "REV?", 4)) { + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: FREV?\n"); +#endif + strcpy(rss, isdn_tty_fax_revision); + sprintf(rs, "\r\nRev: %s", isdn_getrev(rss)); + isdn_tty_at_cout(rs, info); + return 0; + } + + /* Phase C Transmit Data Block Size */ + if (!strncmp(p[0], "TBC=", 4)) { /* dummy, not used */ + p[0] += 4; +#ifdef ISDN_TTY_FAX_STAT_DEBUG + printk(KERN_DEBUG "isdn_tty: Fax FTBC=%c\n", *p[0]); +#endif + switch (*p[0]) { + case '0': + p[0]++; + break; + default: + PARSE_ERROR1; + } + return 0; + } + printk(KERN_DEBUG "isdn_tty: unknown token=>AT+F%s<\n", p[0]); + PARSE_ERROR1; +} + +int +isdn_tty_cmd_PLUSF_FAX(char **p, modem_info *info) +{ + if (TTY_IS_FCLASS2(info)) + return (isdn_tty_cmd_FCLASS2(p, info)); + else if (TTY_IS_FCLASS1(info)) + return (isdn_tty_cmd_FCLASS1(p, info)); + PARSE_ERROR1; +} diff --git a/kernel/drivers/isdn/i4l/isdn_ttyfax.h b/kernel/drivers/isdn/i4l/isdn_ttyfax.h new file mode 100644 index 000000000..ccda4fcf8 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_ttyfax.h @@ -0,0 +1,17 @@ +/* $Id: isdn_ttyfax.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, tty_fax related functions (linklevel). + * + * Copyright 1999 by Armin Schindler (mac@melware.de) + * Copyright 1999 by Ralf Spachmann (mel@melware.de) + * Copyright 1999 by Cytronics & Melware + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + + +#define XON 0x11 +#define XOFF 0x13 +#define DC2 0x12 diff --git a/kernel/drivers/isdn/i4l/isdn_v110.c b/kernel/drivers/isdn/i4l/isdn_v110.c new file mode 100644 index 000000000..52827a80c --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_v110.c @@ -0,0 +1,616 @@ +/* $Id: isdn_v110.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, V.110 related functions (linklevel). + * + * Copyright by Thomas Pfeiffer (pfeiffer@pds.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/delay.h> + +#include <linux/isdn.h> +#include "isdn_v110.h" + +#undef ISDN_V110_DEBUG + +char *isdn_v110_revision = "$Revision: 1.1.2.2 $"; + +#define V110_38400 255 +#define V110_19200 15 +#define V110_9600 3 + +/* + * The following data are precoded matrices, online and offline matrix + * for 9600, 19200 und 38400, respectively + */ +static unsigned char V110_OnMatrix_9600[] = +{0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xfd}; + +static unsigned char V110_OffMatrix_9600[] = +{0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +static unsigned char V110_OnMatrix_19200[] = +{0xf0, 0xf0, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, + 0xfd, 0xff, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xf7}; + +static unsigned char V110_OffMatrix_19200[] = +{0xf0, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + +static unsigned char V110_OnMatrix_38400[] = +{0x00, 0x7f, 0x7f, 0x7f, 0x7f, 0xfd, 0x7f, 0x7f, 0x7f, 0x7f}; + +static unsigned char V110_OffMatrix_38400[] = +{0x00, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff}; + +/* + * FlipBits reorders sequences of keylen bits in one byte. + * E.g. source order 7654321 will be converted to 45670123 when keylen = 4, + * and to 67452301 when keylen = 2. This is necessary because ordering on + * the isdn line is the other way. + */ +static inline unsigned char +FlipBits(unsigned char c, int keylen) +{ + unsigned char b = c; + unsigned char bit = 128; + int i; + int j; + int hunks = (8 / keylen); + + c = 0; + for (i = 0; i < hunks; i++) { + for (j = 0; j < keylen; j++) { + if (b & (bit >> j)) + c |= bit >> (keylen - j - 1); + } + bit >>= keylen; + } + return c; +} + + +/* isdn_v110_open allocates and initializes private V.110 data + * structures and returns a pointer to these. + */ +static isdn_v110_stream * +isdn_v110_open(unsigned char key, int hdrlen, int maxsize) +{ + int i; + isdn_v110_stream *v; + + if ((v = kzalloc(sizeof(isdn_v110_stream), GFP_ATOMIC)) == NULL) + return NULL; + v->key = key; + v->nbits = 0; + for (i = 0; key & (1 << i); i++) + v->nbits++; + + v->nbytes = 8 / v->nbits; + v->decodelen = 0; + + switch (key) { + case V110_38400: + v->OnlineFrame = V110_OnMatrix_38400; + v->OfflineFrame = V110_OffMatrix_38400; + break; + case V110_19200: + v->OnlineFrame = V110_OnMatrix_19200; + v->OfflineFrame = V110_OffMatrix_19200; + break; + default: + v->OnlineFrame = V110_OnMatrix_9600; + v->OfflineFrame = V110_OffMatrix_9600; + break; + } + v->framelen = v->nbytes * 10; + v->SyncInit = 5; + v->introducer = 0; + v->dbit = 1; + v->b = 0; + v->skbres = hdrlen; + v->maxsize = maxsize - hdrlen; + if ((v->encodebuf = kmalloc(maxsize, GFP_ATOMIC)) == NULL) { + kfree(v); + return NULL; + } + return v; +} + +/* isdn_v110_close frees private V.110 data structures */ +void +isdn_v110_close(isdn_v110_stream *v) +{ + if (v == NULL) + return; +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "v110 close\n"); +#endif + kfree(v->encodebuf); + kfree(v); +} + + +/* + * ValidHeaderBytes return the number of valid bytes in v->decodebuf + */ +static int +ValidHeaderBytes(isdn_v110_stream *v) +{ + int i; + for (i = 0; (i < v->decodelen) && (i < v->nbytes); i++) + if ((v->decodebuf[i] & v->key) != 0) + break; + return i; +} + +/* + * SyncHeader moves the decodebuf ptr to the next valid header + */ +static void +SyncHeader(isdn_v110_stream *v) +{ + unsigned char *rbuf = v->decodebuf; + int len = v->decodelen; + + if (len == 0) + return; + for (rbuf++, len--; len > 0; len--, rbuf++) /* such den SyncHeader in buf ! */ + if ((*rbuf & v->key) == 0) /* erstes byte gefunden ? */ + break; /* jupp! */ + if (len) + memcpy(v->decodebuf, rbuf, len); + + v->decodelen = len; +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: Header resync\n"); +#endif +} + +/* DecodeMatrix takes n (n>=1) matrices (v110 frames, 10 bytes) where + len is the number of matrix-lines. len must be a multiple of 10, i.e. + only complete matices must be given. + From these, netto data is extracted and returned in buf. The return-value + is the bytecount of the decoded data. +*/ +static int +DecodeMatrix(isdn_v110_stream *v, unsigned char *m, int len, unsigned char *buf) +{ + int line = 0; + int buflen = 0; + int mbit = 64; + int introducer = v->introducer; + int dbit = v->dbit; + unsigned char b = v->b; + + while (line < len) { /* Are we done with all lines of the matrix? */ + if ((line % 10) == 0) { /* the 0. line of the matrix is always 0 ! */ + if (m[line] != 0x00) { /* not 0 ? -> error! */ +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: DecodeMatrix, V110 Bad Header\n"); + /* returning now is not the right thing, though :-( */ +#endif + } + line++; /* next line of matrix */ + continue; + } else if ((line % 10) == 5) { /* in line 5 there's only e-bits ! */ + if ((m[line] & 0x70) != 0x30) { /* 011 has to be at the beginning! */ +#ifdef ISDN_V110_DEBUG + printk(KERN_DEBUG "isdn_v110: DecodeMatrix, V110 Bad 5th line\n"); + /* returning now is not the right thing, though :-( */ +#endif + } + line++; /* next line */ + continue; + } else if (!introducer) { /* every byte starts with 10 (stopbit, startbit) */ + introducer = (m[line] & mbit) ? 0 : 1; /* current bit of the matrix */ + next_byte: + if (mbit > 2) { /* was it the last bit in this line ? */ + mbit >>= 1; /* no -> take next */ + continue; + } /* otherwise start with leftmost bit in the next line */ + mbit = 64; + line++; + continue; + } else { /* otherwise we need to set a data bit */ + if (m[line] & mbit) /* was that bit set in the matrix ? */ + b |= dbit; /* yes -> set it in the data byte */ + else + b &= dbit - 1; /* no -> clear it in the data byte */ + if (dbit < 128) /* is that data byte done ? */ + dbit <<= 1; /* no, got the next bit */ + else { /* data byte is done */ + buf[buflen++] = b; /* copy byte into the output buffer */ + introducer = b = 0; /* init of the intro sequence and of the data byte */ + dbit = 1; /* next we look for the 0th bit */ + } + goto next_byte; /* look for next bit in the matrix */ + } + } + v->introducer = introducer; + v->dbit = dbit; + v->b = b; + return buflen; /* return number of bytes in the output buffer */ +} + +/* + * DecodeStream receives V.110 coded data from the input stream. It recovers the + * original frames. + * The input stream doesn't need to be framed + */ +struct sk_buff * +isdn_v110_decode(isdn_v110_stream *v, struct sk_buff *skb) +{ + int i; + int j; + int len; + unsigned char *v110_buf; + unsigned char *rbuf; + + if (!skb) { + printk(KERN_WARNING "isdn_v110_decode called with NULL skb!\n"); + return NULL; + } + rbuf = skb->data; + len = skb->len; + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_decode called with NULL stream!\n"); + dev_kfree_skb(skb); + return NULL; + } + if (v->decodelen == 0) /* cache empty? */ + for (; len > 0; len--, rbuf++) /* scan for SyncHeader in buf */ + if ((*rbuf & v->key) == 0) + break; /* found first byte */ + if (len == 0) { + dev_kfree_skb(skb); + return NULL; + } + /* copy new data to decode-buffer */ + memcpy(&(v->decodebuf[v->decodelen]), rbuf, len); + v->decodelen += len; +ReSync: + if (v->decodelen < v->nbytes) { /* got a new header ? */ + dev_kfree_skb(skb); + return NULL; /* no, try later */ + } + if (ValidHeaderBytes(v) != v->nbytes) { /* is that a valid header? */ + SyncHeader(v); /* no -> look for header */ + goto ReSync; + } + len = (v->decodelen - (v->decodelen % (10 * v->nbytes))) / v->nbytes; + if ((v110_buf = kmalloc(len, GFP_ATOMIC)) == NULL) { + printk(KERN_WARNING "isdn_v110_decode: Couldn't allocate v110_buf\n"); + dev_kfree_skb(skb); + return NULL; + } + for (i = 0; i < len; i++) { + v110_buf[i] = 0; + for (j = 0; j < v->nbytes; j++) + v110_buf[i] |= (v->decodebuf[(i * v->nbytes) + j] & v->key) << (8 - ((j + 1) * v->nbits)); + v110_buf[i] = FlipBits(v110_buf[i], v->nbits); + } + v->decodelen = (v->decodelen % (10 * v->nbytes)); + memcpy(v->decodebuf, &(v->decodebuf[len * v->nbytes]), v->decodelen); + + skb_trim(skb, DecodeMatrix(v, v110_buf, len, skb->data)); + kfree(v110_buf); + if (skb->len) + return skb; + else { + kfree_skb(skb); + return NULL; + } +} + +/* EncodeMatrix takes input data in buf, len is the bytecount. + Data is encoded into v110 frames in m. Return value is the number of + matrix-lines generated. +*/ +static int +EncodeMatrix(unsigned char *buf, int len, unsigned char *m, int mlen) +{ + int line = 0; + int i = 0; + int mbit = 128; + int dbit = 1; + int introducer = 3; + int ibit[] = {0, 1, 1}; + + while ((i < len) && (line < mlen)) { /* while we still have input data */ + switch (line % 10) { /* in which line of the matrix are we? */ + case 0: + m[line++] = 0x00; /* line 0 is always 0 */ + mbit = 128; /* go on with the 7th bit */ + break; + case 5: + m[line++] = 0xbf; /* line 5 is always 10111111 */ + mbit = 128; /* go on with the 7th bit */ + break; + } + if (line >= mlen) { + printk(KERN_WARNING "isdn_v110 (EncodeMatrix): buffer full!\n"); + return line; + } + next_bit: + switch (mbit) { /* leftmost or rightmost bit ? */ + case 1: + line++; /* rightmost -> go to next line */ + if (line >= mlen) { + printk(KERN_WARNING "isdn_v110 (EncodeMatrix): buffer full!\n"); + return line; + } + case 128: + m[line] = 128; /* leftmost -> set byte to 1000000 */ + mbit = 64; /* current bit in the matrix line */ + continue; + } + if (introducer) { /* set 110 sequence ? */ + introducer--; /* set on digit less */ + m[line] |= ibit[introducer] ? mbit : 0; /* set corresponding bit */ + mbit >>= 1; /* bit of matrix line >> 1 */ + goto next_bit; /* and go on there */ + } /* else push data bits into the matrix! */ + m[line] |= (buf[i] & dbit) ? mbit : 0; /* set data bit in matrix */ + if (dbit == 128) { /* was it the last one? */ + dbit = 1; /* then go on with first bit of */ + i++; /* next byte in input buffer */ + if (i < len) /* input buffer done ? */ + introducer = 3; /* no, write introducer 110 */ + else { /* input buffer done ! */ + m[line] |= (mbit - 1) & 0xfe; /* set remaining bits in line to 1 */ + break; + } + } else /* not the last data bit */ + dbit <<= 1; /* then go to next data bit */ + mbit >>= 1; /* go to next bit of matrix */ + goto next_bit; + + } + /* if necessary, generate remaining lines of the matrix... */ + if ((line) && ((line + 10) < mlen)) + switch (++line % 10) { + case 1: + m[line++] = 0xfe; + case 2: + m[line++] = 0xfe; + case 3: + m[line++] = 0xfe; + case 4: + m[line++] = 0xfe; + case 5: + m[line++] = 0xbf; + case 6: + m[line++] = 0xfe; + case 7: + m[line++] = 0xfe; + case 8: + m[line++] = 0xfe; + case 9: + m[line++] = 0xfe; + } + return line; /* that's how many lines we have */ +} + +/* + * Build a sync frame. + */ +static struct sk_buff * +isdn_v110_sync(isdn_v110_stream *v) +{ + struct sk_buff *skb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_sync called with NULL stream!\n"); + return NULL; + } + if ((skb = dev_alloc_skb(v->framelen + v->skbres))) { + skb_reserve(skb, v->skbres); + memcpy(skb_put(skb, v->framelen), v->OfflineFrame, v->framelen); + } + return skb; +} + +/* + * Build an idle frame. + */ +static struct sk_buff * +isdn_v110_idle(isdn_v110_stream *v) +{ + struct sk_buff *skb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_sync called with NULL stream!\n"); + return NULL; + } + if ((skb = dev_alloc_skb(v->framelen + v->skbres))) { + skb_reserve(skb, v->skbres); + memcpy(skb_put(skb, v->framelen), v->OnlineFrame, v->framelen); + } + return skb; +} + +struct sk_buff * +isdn_v110_encode(isdn_v110_stream *v, struct sk_buff *skb) +{ + int i; + int j; + int rlen; + int mlen; + int olen; + int size; + int sval1; + int sval2; + int nframes; + unsigned char *v110buf; + unsigned char *rbuf; + struct sk_buff *nskb; + + if (v == NULL) { + /* invalid handle, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_encode called with NULL stream!\n"); + return NULL; + } + if (!skb) { + /* invalid skb, no chance to proceed */ + printk(KERN_WARNING "isdn_v110_encode called with NULL skb!\n"); + return NULL; + } + rlen = skb->len; + nframes = (rlen + 3) / 4; + v110buf = v->encodebuf; + if ((nframes * 40) > v->maxsize) { + size = v->maxsize; + rlen = v->maxsize / 40; + } else + size = nframes * 40; + if (!(nskb = dev_alloc_skb(size + v->skbres + sizeof(int)))) { + printk(KERN_WARNING "isdn_v110_encode: Couldn't alloc skb\n"); + return NULL; + } + skb_reserve(nskb, v->skbres + sizeof(int)); + if (skb->len == 0) { + memcpy(skb_put(nskb, v->framelen), v->OnlineFrame, v->framelen); + *((int *)skb_push(nskb, sizeof(int))) = 0; + return nskb; + } + mlen = EncodeMatrix(skb->data, rlen, v110buf, size); + /* now distribute 2 or 4 bits each to the output stream! */ + rbuf = skb_put(nskb, size); + olen = 0; + sval1 = 8 - v->nbits; + sval2 = v->key << sval1; + for (i = 0; i < mlen; i++) { + v110buf[i] = FlipBits(v110buf[i], v->nbits); + for (j = 0; j < v->nbytes; j++) { + if (size--) + *rbuf++ = ~v->key | (((v110buf[i] << (j * v->nbits)) & sval2) >> sval1); + else { + printk(KERN_WARNING "isdn_v110_encode: buffers full!\n"); + goto buffer_full; + } + olen++; + } + } +buffer_full: + skb_trim(nskb, olen); + *((int *)skb_push(nskb, sizeof(int))) = rlen; + return nskb; +} + +int +isdn_v110_stat_callback(int idx, isdn_ctrl *c) +{ + isdn_v110_stream *v = NULL; + int i; + int ret = 0; + + if (idx < 0) + return 0; + switch (c->command) { + case ISDN_STAT_BSENT: + /* Keep the send-queue of the driver filled + * with frames: + * If number of outstanding frames < 3, + * send down an Idle-Frame (or an Sync-Frame, if + * v->SyncInit != 0). + */ + if (!(v = dev->v110[idx])) + return 0; + atomic_inc(&dev->v110use[idx]); + for (i = 0; i * v->framelen < c->parm.length; i++) { + if (v->skbidle > 0) { + v->skbidle--; + ret = 1; + } else { + if (v->skbuser > 0) + v->skbuser--; + ret = 0; + } + } + for (i = v->skbuser + v->skbidle; i < 2; i++) { + struct sk_buff *skb; + if (v->SyncInit > 0) + skb = isdn_v110_sync(v); + else + skb = isdn_v110_idle(v); + if (skb) { + if (dev->drv[c->driver]->interface->writebuf_skb(c->driver, c->arg, 1, skb) <= 0) { + dev_kfree_skb(skb); + break; + } else { + if (v->SyncInit) + v->SyncInit--; + v->skbidle++; + } + } else + break; + } + atomic_dec(&dev->v110use[idx]); + return ret; + case ISDN_STAT_DHUP: + case ISDN_STAT_BHUP: + while (1) { + atomic_inc(&dev->v110use[idx]); + if (atomic_dec_and_test(&dev->v110use[idx])) { + isdn_v110_close(dev->v110[idx]); + dev->v110[idx] = NULL; + break; + } + mdelay(1); + } + break; + case ISDN_STAT_BCONN: + if (dev->v110emu[idx] && (dev->v110[idx] == NULL)) { + int hdrlen = dev->drv[c->driver]->interface->hl_hdrlen; + int maxsize = dev->drv[c->driver]->interface->maxbufsize; + atomic_inc(&dev->v110use[idx]); + switch (dev->v110emu[idx]) { + case ISDN_PROTO_L2_V11096: + dev->v110[idx] = isdn_v110_open(V110_9600, hdrlen, maxsize); + break; + case ISDN_PROTO_L2_V11019: + dev->v110[idx] = isdn_v110_open(V110_19200, hdrlen, maxsize); + break; + case ISDN_PROTO_L2_V11038: + dev->v110[idx] = isdn_v110_open(V110_38400, hdrlen, maxsize); + break; + default:; + } + if ((v = dev->v110[idx])) { + while (v->SyncInit) { + struct sk_buff *skb = isdn_v110_sync(v); + if (dev->drv[c->driver]->interface->writebuf_skb(c->driver, c->arg, 1, skb) <= 0) { + dev_kfree_skb(skb); + /* Unable to send, try later */ + break; + } + v->SyncInit--; + v->skbidle++; + } + } else + printk(KERN_WARNING "isdn_v110: Couldn't open stream for chan %d\n", idx); + atomic_dec(&dev->v110use[idx]); + } + break; + default: + return 0; + } + return 0; +} diff --git a/kernel/drivers/isdn/i4l/isdn_v110.h b/kernel/drivers/isdn/i4l/isdn_v110.h new file mode 100644 index 000000000..de774ab59 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_v110.h @@ -0,0 +1,29 @@ +/* $Id: isdn_v110.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, V.110 related functions (linklevel). + * + * Copyright by Thomas Pfeiffer (pfeiffer@pds.de) + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _isdn_v110_h_ +#define _isdn_v110_h_ + +/* + * isdn_v110_encode will take raw data and encode it using V.110 + */ +extern struct sk_buff *isdn_v110_encode(isdn_v110_stream *, struct sk_buff *); + +/* + * isdn_v110_decode receives V.110 coded data from the stream and rebuilds + * frames from them. The source stream doesn't need to be framed. + */ +extern struct sk_buff *isdn_v110_decode(isdn_v110_stream *, struct sk_buff *); + +extern int isdn_v110_stat_callback(int, isdn_ctrl *); +extern void isdn_v110_close(isdn_v110_stream *v); + +#endif diff --git a/kernel/drivers/isdn/i4l/isdn_x25iface.c b/kernel/drivers/isdn/i4l/isdn_x25iface.c new file mode 100644 index 000000000..e2d4e5823 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_x25iface.c @@ -0,0 +1,332 @@ +/* $Id: isdn_x25iface.c,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * Linux ISDN subsystem, X.25 related functions + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * stuff needed to support the Linux X.25 PLP code on top of devices that + * can provide a lab_b service using the concap_proto mechanism. + * This module supports a network interface which provides lapb_sematics + * -- as defined in Documentation/networking/x25-iface.txt -- to + * the upper layer and assumes that the lower layer provides a reliable + * data link service by means of the concap_device_ops callbacks. + * + * Only protocol specific stuff goes here. Device specific stuff + * goes to another -- device related -- concap_proto support source file. + * + */ + +/* #include <linux/isdn.h> */ +#include <linux/netdevice.h> +#include <linux/concap.h> +#include <linux/slab.h> +#include <linux/wanrouter.h> +#include <net/x25device.h> +#include "isdn_x25iface.h" + +/* for debugging messages not to cause an oops when device pointer is NULL*/ +#define MY_DEVNAME(dev) ((dev) ? (dev)->name : "DEVICE UNSPECIFIED") + + +typedef struct isdn_x25iface_proto_data { + int magic; + enum wan_states state; + /* Private stuff, not to be accessed via proto_data. We provide the + other storage for the concap_proto instance here as well, + enabling us to allocate both with just one kmalloc(): */ + struct concap_proto priv; +} ix25_pdata_t; + + + +/* is now in header file (extern): struct concap_proto * isdn_x25iface_proto_new(void); */ +static void isdn_x25iface_proto_del(struct concap_proto *); +static int isdn_x25iface_proto_close(struct concap_proto *); +static int isdn_x25iface_proto_restart(struct concap_proto *, + struct net_device *, + struct concap_device_ops *); +static int isdn_x25iface_xmit(struct concap_proto *, struct sk_buff *); +static int isdn_x25iface_receive(struct concap_proto *, struct sk_buff *); +static int isdn_x25iface_connect_ind(struct concap_proto *); +static int isdn_x25iface_disconn_ind(struct concap_proto *); + + +static struct concap_proto_ops ix25_pops = { + &isdn_x25iface_proto_new, + &isdn_x25iface_proto_del, + &isdn_x25iface_proto_restart, + &isdn_x25iface_proto_close, + &isdn_x25iface_xmit, + &isdn_x25iface_receive, + &isdn_x25iface_connect_ind, + &isdn_x25iface_disconn_ind +}; + +/* error message helper function */ +static void illegal_state_warn(unsigned state, unsigned char firstbyte) +{ + printk(KERN_WARNING "isdn_x25iface: firstbyte %x illegal in" + "current state %d\n", firstbyte, state); +} + +/* check protocol data field for consistency */ +static int pdata_is_bad(ix25_pdata_t *pda) { + + if (pda && pda->magic == ISDN_X25IFACE_MAGIC) return 0; + printk(KERN_WARNING + "isdn_x25iface_xxx: illegal pointer to proto data\n"); + return 1; +} + +/* create a new x25 interface protocol instance + */ +struct concap_proto *isdn_x25iface_proto_new(void) +{ + ix25_pdata_t *tmp = kmalloc(sizeof(ix25_pdata_t), GFP_KERNEL); + IX25DEBUG("isdn_x25iface_proto_new\n"); + if (tmp) { + tmp->magic = ISDN_X25IFACE_MAGIC; + tmp->state = WAN_UNCONFIGURED; + /* private data space used to hold the concap_proto data. + Only to be accessed via the returned pointer */ + spin_lock_init(&tmp->priv.lock); + tmp->priv.dops = NULL; + tmp->priv.net_dev = NULL; + tmp->priv.pops = &ix25_pops; + tmp->priv.flags = 0; + tmp->priv.proto_data = tmp; + return (&(tmp->priv)); + } + return NULL; +}; + +/* close the x25iface encapsulation protocol + */ +static int isdn_x25iface_proto_close(struct concap_proto *cprot) { + + ix25_pdata_t *tmp; + int ret = 0; + ulong flags; + + if (!cprot) { + printk(KERN_ERR "isdn_x25iface_proto_close: " + "invalid concap_proto pointer\n"); + return -1; + } + IX25DEBUG("isdn_x25iface_proto_close %s \n", MY_DEVNAME(cprot->net_dev)); + spin_lock_irqsave(&cprot->lock, flags); + cprot->dops = NULL; + cprot->net_dev = NULL; + tmp = cprot->proto_data; + if (pdata_is_bad(tmp)) { + ret = -1; + } else { + tmp->state = WAN_UNCONFIGURED; + } + spin_unlock_irqrestore(&cprot->lock, flags); + return ret; +} + +/* Delete the x25iface encapsulation protocol instance + */ +static void isdn_x25iface_proto_del(struct concap_proto *cprot) { + + ix25_pdata_t *tmp; + + IX25DEBUG("isdn_x25iface_proto_del \n"); + if (!cprot) { + printk(KERN_ERR "isdn_x25iface_proto_del: " + "concap_proto pointer is NULL\n"); + return; + } + tmp = cprot->proto_data; + if (tmp == NULL) { + printk(KERN_ERR "isdn_x25iface_proto_del: inconsistent " + "proto_data pointer (maybe already deleted?)\n"); + return; + } + /* close if the protocol is still open */ + if (cprot->dops) isdn_x25iface_proto_close(cprot); + /* freeing the storage should be sufficient now. But some additional + settings might help to catch wild pointer bugs */ + tmp->magic = 0; + cprot->proto_data = NULL; + + kfree(tmp); + return; +} + +/* (re-)initialize the data structures for x25iface encapsulation + */ +static int isdn_x25iface_proto_restart(struct concap_proto *cprot, + struct net_device *ndev, + struct concap_device_ops *dops) +{ + ix25_pdata_t *pda = cprot->proto_data; + ulong flags; + + IX25DEBUG("isdn_x25iface_proto_restart %s \n", MY_DEVNAME(ndev)); + + if (pdata_is_bad(pda)) return -1; + + if (!(dops && dops->data_req && dops->connect_req + && dops->disconn_req)) { + printk(KERN_WARNING "isdn_x25iface_restart: required dops" + " missing\n"); + isdn_x25iface_proto_close(cprot); + return -1; + } + spin_lock_irqsave(&cprot->lock, flags); + cprot->net_dev = ndev; + cprot->pops = &ix25_pops; + cprot->dops = dops; + pda->state = WAN_DISCONNECTED; + spin_unlock_irqrestore(&cprot->lock, flags); + return 0; +} + +/* deliver a dl_data frame received from i4l HL driver to the network layer + */ +static int isdn_x25iface_receive(struct concap_proto *cprot, struct sk_buff *skb) +{ + IX25DEBUG("isdn_x25iface_receive %s \n", MY_DEVNAME(cprot->net_dev)); + if (((ix25_pdata_t *)(cprot->proto_data)) + ->state == WAN_CONNECTED) { + if (skb_push(skb, 1)) { + skb->data[0] = X25_IFACE_DATA; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } + } + printk(KERN_WARNING "isdn_x25iface_receive %s: not connected, skb dropped\n", MY_DEVNAME(cprot->net_dev)); + dev_kfree_skb(skb); + return -1; +} + +/* a connection set up is indicated by lower layer + */ +static int isdn_x25iface_connect_ind(struct concap_proto *cprot) +{ + struct sk_buff *skb; + enum wan_states *state_p + = &(((ix25_pdata_t *)(cprot->proto_data))->state); + IX25DEBUG("isdn_x25iface_connect_ind %s \n" + , MY_DEVNAME(cprot->net_dev)); + if (*state_p == WAN_UNCONFIGURED) { + printk(KERN_WARNING + "isdn_x25iface_connect_ind while unconfigured %s\n" + , MY_DEVNAME(cprot->net_dev)); + return -1; + } + *state_p = WAN_CONNECTED; + + skb = dev_alloc_skb(1); + if (skb) { + *(skb_put(skb, 1)) = X25_IFACE_CONNECT; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } else { + printk(KERN_WARNING "isdn_x25iface_connect_ind: " + " out of memory -- disconnecting\n"); + cprot->dops->disconn_req(cprot); + return -1; + } +} + +/* a disconnect is indicated by lower layer + */ +static int isdn_x25iface_disconn_ind(struct concap_proto *cprot) +{ + struct sk_buff *skb; + enum wan_states *state_p + = &(((ix25_pdata_t *)(cprot->proto_data))->state); + IX25DEBUG("isdn_x25iface_disconn_ind %s \n", MY_DEVNAME(cprot->net_dev)); + if (*state_p == WAN_UNCONFIGURED) { + printk(KERN_WARNING + "isdn_x25iface_disconn_ind while unconfigured\n"); + return -1; + } + if (!cprot->net_dev) return -1; + *state_p = WAN_DISCONNECTED; + skb = dev_alloc_skb(1); + if (skb) { + *(skb_put(skb, 1)) = X25_IFACE_DISCONNECT; + skb->protocol = x25_type_trans(skb, cprot->net_dev); + netif_rx(skb); + return 0; + } else { + printk(KERN_WARNING "isdn_x25iface_disconn_ind:" + " out of memory\n"); + return -1; + } +} + +/* process a frame handed over to us from linux network layer. First byte + semantics as defined in Documentation/networking/x25-iface.txt +*/ +static int isdn_x25iface_xmit(struct concap_proto *cprot, struct sk_buff *skb) +{ + unsigned char firstbyte = skb->data[0]; + enum wan_states *state = &((ix25_pdata_t *)cprot->proto_data)->state; + int ret = 0; + IX25DEBUG("isdn_x25iface_xmit: %s first=%x state=%d\n", + MY_DEVNAME(cprot->net_dev), firstbyte, *state); + switch (firstbyte) { + case X25_IFACE_DATA: + if (*state == WAN_CONNECTED) { + skb_pull(skb, 1); + cprot->net_dev->trans_start = jiffies; + ret = (cprot->dops->data_req(cprot, skb)); + /* prepare for future retransmissions */ + if (ret) skb_push(skb, 1); + return ret; + } + illegal_state_warn(*state, firstbyte); + break; + case X25_IFACE_CONNECT: + if (*state == WAN_DISCONNECTED) { + *state = WAN_CONNECTING; + ret = cprot->dops->connect_req(cprot); + if (ret) { + /* reset state and notify upper layer about + * immidiatly failed attempts */ + isdn_x25iface_disconn_ind(cprot); + } + } else { + illegal_state_warn(*state, firstbyte); + } + break; + case X25_IFACE_DISCONNECT: + switch (*state) { + case WAN_DISCONNECTED: + /* Should not happen. However, give upper layer a + chance to recover from inconstistency but don't + trust the lower layer sending the disconn_confirm + when already disconnected */ + printk(KERN_WARNING "isdn_x25iface_xmit: disconnect " + " requested while disconnected\n"); + isdn_x25iface_disconn_ind(cprot); + break; /* prevent infinite loops */ + case WAN_CONNECTING: + case WAN_CONNECTED: + *state = WAN_DISCONNECTED; + cprot->dops->disconn_req(cprot); + break; + default: + illegal_state_warn(*state, firstbyte); + } + break; + case X25_IFACE_PARAMS: + printk(KERN_WARNING "isdn_x25iface_xmit: setting of lapb" + " options not yet supported\n"); + break; + default: + printk(KERN_WARNING "isdn_x25iface_xmit: frame with illegal" + " first byte %x ignored:\n", firstbyte); + } + dev_kfree_skb(skb); + return 0; +} diff --git a/kernel/drivers/isdn/i4l/isdn_x25iface.h b/kernel/drivers/isdn/i4l/isdn_x25iface.h new file mode 100644 index 000000000..ca08e082c --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdn_x25iface.h @@ -0,0 +1,30 @@ +/* $Id: isdn_x25iface.h,v 1.1.2.2 2004/01/12 22:37:19 keil Exp $ + * + * header for Linux ISDN subsystem, x.25 related functions + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + */ + +#ifndef _LINUX_ISDN_X25IFACE_H +#define _LINUX_ISDN_X25IFACE_H + +#define ISDN_X25IFACE_MAGIC 0x1e75a2b9 +/* #define DEBUG_ISDN_X25 if you want isdn_x25 debugging messages */ +#ifdef DEBUG_ISDN_X25 +# define IX25DEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args) +#else +# define IX25DEBUG(fmt, args...) +#endif + +#include <linux/skbuff.h> +#include <linux/isdn.h> +#include <linux/concap.h> + +extern struct concap_proto_ops *isdn_x25iface_concap_proto_ops_pt; +extern struct concap_proto *isdn_x25iface_proto_new(void); + + + +#endif diff --git a/kernel/drivers/isdn/i4l/isdnhdlc.c b/kernel/drivers/isdn/i4l/isdnhdlc.c new file mode 100644 index 000000000..027d1c590 --- /dev/null +++ b/kernel/drivers/isdn/i4l/isdnhdlc.c @@ -0,0 +1,630 @@ +/* + * isdnhdlc.c -- General purpose ISDN HDLC decoder. + * + * Copyright (C) + * 2009 Karsten Keil <keil@b1-systems.de> + * 2002 Wolfgang Mües <wolfgang@iksw-muees.de> + * 2001 Frode Isaksen <fisaksen@bewan.com> + * 2001 Kai Germaschewski <kai.germaschewski@gmx.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/crc-ccitt.h> +#include <linux/isdn/hdlc.h> +#include <linux/bitrev.h> + +/*-------------------------------------------------------------------*/ + +MODULE_AUTHOR("Wolfgang Mües <wolfgang@iksw-muees.de>, " + "Frode Isaksen <fisaksen@bewan.com>, " + "Kai Germaschewski <kai.germaschewski@gmx.de>"); +MODULE_DESCRIPTION("General purpose ISDN HDLC decoder"); +MODULE_LICENSE("GPL"); + +/*-------------------------------------------------------------------*/ + +enum { + HDLC_FAST_IDLE, HDLC_GET_FLAG_B0, HDLC_GETFLAG_B1A6, HDLC_GETFLAG_B7, + HDLC_GET_DATA, HDLC_FAST_FLAG +}; + +enum { + HDLC_SEND_DATA, HDLC_SEND_CRC1, HDLC_SEND_FAST_FLAG, + HDLC_SEND_FIRST_FLAG, HDLC_SEND_CRC2, HDLC_SEND_CLOSING_FLAG, + HDLC_SEND_IDLE1, HDLC_SEND_FAST_IDLE, HDLC_SENDFLAG_B0, + HDLC_SENDFLAG_B1A6, HDLC_SENDFLAG_B7, STOPPED, HDLC_SENDFLAG_ONE +}; + +void isdnhdlc_rcv_init(struct isdnhdlc_vars *hdlc, u32 features) +{ + memset(hdlc, 0, sizeof(struct isdnhdlc_vars)); + hdlc->state = HDLC_GET_DATA; + if (features & HDLC_56KBIT) + hdlc->do_adapt56 = 1; + if (features & HDLC_BITREVERSE) + hdlc->do_bitreverse = 1; +} +EXPORT_SYMBOL(isdnhdlc_out_init); + +void isdnhdlc_out_init(struct isdnhdlc_vars *hdlc, u32 features) +{ + memset(hdlc, 0, sizeof(struct isdnhdlc_vars)); + if (features & HDLC_DCHANNEL) { + hdlc->dchannel = 1; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + hdlc->dchannel = 0; + hdlc->state = HDLC_SEND_FAST_FLAG; + hdlc->ffvalue = 0x7e; + } + hdlc->cbin = 0x7e; + if (features & HDLC_56KBIT) { + hdlc->do_adapt56 = 1; + hdlc->state = HDLC_SENDFLAG_B0; + } else + hdlc->data_bits = 8; + if (features & HDLC_BITREVERSE) + hdlc->do_bitreverse = 1; +} +EXPORT_SYMBOL(isdnhdlc_rcv_init); + +static int +check_frame(struct isdnhdlc_vars *hdlc) +{ + int status; + + if (hdlc->dstpos < 2) /* too small - framing error */ + status = -HDLC_FRAMING_ERROR; + else if (hdlc->crc != 0xf0b8) /* crc error */ + status = -HDLC_CRC_ERROR; + else { + /* remove CRC */ + hdlc->dstpos -= 2; + /* good frame */ + status = hdlc->dstpos; + } + return status; +} + +/* + isdnhdlc_decode - decodes HDLC frames from a transparent bit stream. + + The source buffer is scanned for valid HDLC frames looking for + flags (01111110) to indicate the start of a frame. If the start of + the frame is found, the bit stuffing is removed (0 after 5 1's). + When a new flag is found, the complete frame has been received + and the CRC is checked. + If a valid frame is found, the function returns the frame length + excluding the CRC with the bit HDLC_END_OF_FRAME set. + If the beginning of a valid frame is found, the function returns + the length. + If a framing error is found (too many 1s and not a flag) the function + returns the length with the bit HDLC_FRAMING_ERROR set. + If a CRC error is found the function returns the length with the + bit HDLC_CRC_ERROR set. + If the frame length exceeds the destination buffer size, the function + returns the length with the bit HDLC_LENGTH_ERROR set. + + src - source buffer + slen - source buffer length + count - number of bytes removed (decoded) from the source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of decoded bytes in the destination buffer and status + flag. +*/ +int isdnhdlc_decode(struct isdnhdlc_vars *hdlc, const u8 *src, int slen, + int *count, u8 *dst, int dsize) +{ + int status = 0; + + static const unsigned char fast_flag[] = { + 0x00, 0x00, 0x00, 0x20, 0x30, 0x38, 0x3c, 0x3e, 0x3f + }; + + static const unsigned char fast_flag_value[] = { + 0x00, 0x7e, 0xfc, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3f + }; + + static const unsigned char fast_abort[] = { + 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff + }; + +#define handle_fast_flag(h) \ + do { \ + if (h->cbin == fast_flag[h->bit_shift]) { \ + h->ffvalue = fast_flag_value[h->bit_shift]; \ + h->state = HDLC_FAST_FLAG; \ + h->ffbit_shift = h->bit_shift; \ + h->bit_shift = 1; \ + } else { \ + h->state = HDLC_GET_DATA; \ + h->data_received = 0; \ + } \ + } while (0) + +#define handle_abort(h) \ + do { \ + h->shift_reg = fast_abort[h->ffbit_shift - 1]; \ + h->hdlc_bits1 = h->ffbit_shift - 2; \ + if (h->hdlc_bits1 < 0) \ + h->hdlc_bits1 = 0; \ + h->data_bits = h->ffbit_shift - 1; \ + h->state = HDLC_GET_DATA; \ + h->data_received = 0; \ + } while (0) + + *count = slen; + + while (slen > 0) { + if (hdlc->bit_shift == 0) { + /* the code is for bitreverse streams */ + if (hdlc->do_bitreverse == 0) + hdlc->cbin = bitrev8(*src++); + else + hdlc->cbin = *src++; + slen--; + hdlc->bit_shift = 8; + if (hdlc->do_adapt56) + hdlc->bit_shift--; + } + + switch (hdlc->state) { + case STOPPED: + return 0; + case HDLC_FAST_IDLE: + if (hdlc->cbin == 0xff) { + hdlc->bit_shift = 0; + break; + } + hdlc->state = HDLC_GET_FLAG_B0; + hdlc->hdlc_bits1 = 0; + hdlc->bit_shift = 8; + break; + case HDLC_GET_FLAG_B0: + if (!(hdlc->cbin & 0x80)) { + hdlc->state = HDLC_GETFLAG_B1A6; + hdlc->hdlc_bits1 = 0; + } else { + if ((!hdlc->do_adapt56) && + (++hdlc->hdlc_bits1 >= 8) && + (hdlc->bit_shift == 1)) + hdlc->state = HDLC_FAST_IDLE; + } + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_GETFLAG_B1A6: + if (hdlc->cbin & 0x80) { + hdlc->hdlc_bits1++; + if (hdlc->hdlc_bits1 == 6) + hdlc->state = HDLC_GETFLAG_B7; + } else + hdlc->hdlc_bits1 = 0; + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_GETFLAG_B7: + if (hdlc->cbin & 0x80) { + hdlc->state = HDLC_GET_FLAG_B0; + } else { + hdlc->state = HDLC_GET_DATA; + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->hdlc_bits1 = 0; + hdlc->data_bits = 0; + hdlc->data_received = 0; + } + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_GET_DATA: + if (hdlc->cbin & 0x80) { + hdlc->hdlc_bits1++; + switch (hdlc->hdlc_bits1) { + case 6: + break; + case 7: + if (hdlc->data_received) + /* bad frame */ + status = -HDLC_FRAMING_ERROR; + if (!hdlc->do_adapt56) { + if (hdlc->cbin == fast_abort + [hdlc->bit_shift + 1]) { + hdlc->state = + HDLC_FAST_IDLE; + hdlc->bit_shift = 1; + break; + } + } else + hdlc->state = HDLC_GET_FLAG_B0; + break; + default: + hdlc->shift_reg >>= 1; + hdlc->shift_reg |= 0x80; + hdlc->data_bits++; + break; + } + } else { + switch (hdlc->hdlc_bits1) { + case 5: + break; + case 6: + if (hdlc->data_received) + status = check_frame(hdlc); + hdlc->crc = 0xffff; + hdlc->shift_reg = 0; + hdlc->data_bits = 0; + if (!hdlc->do_adapt56) + handle_fast_flag(hdlc); + else { + hdlc->state = HDLC_GET_DATA; + hdlc->data_received = 0; + } + break; + default: + hdlc->shift_reg >>= 1; + hdlc->data_bits++; + break; + } + hdlc->hdlc_bits1 = 0; + } + if (status) { + hdlc->dstpos = 0; + *count -= slen; + hdlc->cbin <<= 1; + hdlc->bit_shift--; + return status; + } + if (hdlc->data_bits == 8) { + hdlc->data_bits = 0; + hdlc->data_received = 1; + hdlc->crc = crc_ccitt_byte(hdlc->crc, + hdlc->shift_reg); + + /* good byte received */ + if (hdlc->dstpos < dsize) + dst[hdlc->dstpos++] = hdlc->shift_reg; + else { + /* frame too long */ + status = -HDLC_LENGTH_ERROR; + hdlc->dstpos = 0; + } + } + hdlc->cbin <<= 1; + hdlc->bit_shift--; + break; + case HDLC_FAST_FLAG: + if (hdlc->cbin == hdlc->ffvalue) { + hdlc->bit_shift = 0; + break; + } else { + if (hdlc->cbin == 0xff) { + hdlc->state = HDLC_FAST_IDLE; + hdlc->bit_shift = 0; + } else if (hdlc->ffbit_shift == 8) { + hdlc->state = HDLC_GETFLAG_B7; + break; + } else + handle_abort(hdlc); + } + break; + default: + break; + } + } + *count -= slen; + return 0; +} +EXPORT_SYMBOL(isdnhdlc_decode); +/* + isdnhdlc_encode - encodes HDLC frames to a transparent bit stream. + + The bit stream starts with a beginning flag (01111110). After + that each byte is added to the bit stream with bit stuffing added + (0 after 5 1's). + When the last byte has been removed from the source buffer, the + CRC (2 bytes is added) and the frame terminates with the ending flag. + For the dchannel, the idle character (all 1's) is also added at the end. + If this function is called with empty source buffer (slen=0), flags or + idle character will be generated. + + src - source buffer + slen - source buffer length + count - number of bytes removed (encoded) from source buffer + dst _ destination buffer + dsize - destination buffer size + returns - number of encoded bytes in the destination buffer +*/ +int isdnhdlc_encode(struct isdnhdlc_vars *hdlc, const u8 *src, u16 slen, + int *count, u8 *dst, int dsize) +{ + static const unsigned char xfast_flag_value[] = { + 0x7e, 0x3f, 0x9f, 0xcf, 0xe7, 0xf3, 0xf9, 0xfc, 0x7e + }; + + int len = 0; + + *count = slen; + + /* special handling for one byte frames */ + if ((slen == 1) && (hdlc->state == HDLC_SEND_FAST_FLAG)) + hdlc->state = HDLC_SENDFLAG_ONE; + while (dsize > 0) { + if (hdlc->bit_shift == 0) { + if (slen && !hdlc->do_closing) { + hdlc->shift_reg = *src++; + slen--; + if (slen == 0) + /* closing sequence, CRC + flag(s) */ + hdlc->do_closing = 1; + hdlc->bit_shift = 8; + } else { + if (hdlc->state == HDLC_SEND_DATA) { + if (hdlc->data_received) { + hdlc->state = HDLC_SEND_CRC1; + hdlc->crc ^= 0xffff; + hdlc->bit_shift = 8; + hdlc->shift_reg = + hdlc->crc & 0xff; + } else if (!hdlc->do_adapt56) + hdlc->state = + HDLC_SEND_FAST_FLAG; + else + hdlc->state = + HDLC_SENDFLAG_B0; + } + + } + } + + switch (hdlc->state) { + case STOPPED: + while (dsize--) + *dst++ = 0xff; + return dsize; + case HDLC_SEND_FAST_FLAG: + hdlc->do_closing = 0; + if (slen == 0) { + /* the code is for bitreverse streams */ + if (hdlc->do_bitreverse == 0) + *dst++ = bitrev8(hdlc->ffvalue); + else + *dst++ = hdlc->ffvalue; + len++; + dsize--; + break; + } + /* fall through */ + case HDLC_SENDFLAG_ONE: + if (hdlc->bit_shift == 8) { + hdlc->cbin = hdlc->ffvalue >> + (8 - hdlc->data_bits); + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SENDFLAG_B0: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->hdlc_bits1 = 0; + hdlc->state = HDLC_SENDFLAG_B1A6; + break; + case HDLC_SENDFLAG_B1A6: + hdlc->cbin <<= 1; + hdlc->data_bits++; + hdlc->cbin++; + if (++hdlc->hdlc_bits1 == 6) + hdlc->state = HDLC_SENDFLAG_B7; + break; + case HDLC_SENDFLAG_B7: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (slen == 0) { + hdlc->state = HDLC_SENDFLAG_B0; + break; + } + if (hdlc->bit_shift == 8) { + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + hdlc->data_received = 1; + } + break; + case HDLC_SEND_FIRST_FLAG: + hdlc->data_received = 1; + if (hdlc->data_bits == 8) { + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + break; + } + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (hdlc->shift_reg & 0x01) + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if (hdlc->bit_shift == 0) { + hdlc->state = HDLC_SEND_DATA; + hdlc->crc = 0xffff; + hdlc->hdlc_bits1 = 0; + } + break; + case HDLC_SEND_DATA: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (hdlc->hdlc_bits1 == 5) { + hdlc->hdlc_bits1 = 0; + break; + } + if (hdlc->bit_shift == 8) + hdlc->crc = crc_ccitt_byte(hdlc->crc, + hdlc->shift_reg); + if (hdlc->shift_reg & 0x01) { + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + break; + case HDLC_SEND_CRC1: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (hdlc->hdlc_bits1 == 5) { + hdlc->hdlc_bits1 = 0; + break; + } + if (hdlc->shift_reg & 0x01) { + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if (hdlc->bit_shift == 0) { + hdlc->shift_reg = (hdlc->crc >> 8); + hdlc->state = HDLC_SEND_CRC2; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CRC2: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (hdlc->hdlc_bits1 == 5) { + hdlc->hdlc_bits1 = 0; + break; + } + if (hdlc->shift_reg & 0x01) { + hdlc->hdlc_bits1++; + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } else { + hdlc->hdlc_bits1 = 0; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + } + if (hdlc->bit_shift == 0) { + hdlc->shift_reg = 0x7e; + hdlc->state = HDLC_SEND_CLOSING_FLAG; + hdlc->bit_shift = 8; + } + break; + case HDLC_SEND_CLOSING_FLAG: + hdlc->cbin <<= 1; + hdlc->data_bits++; + if (hdlc->hdlc_bits1 == 5) { + hdlc->hdlc_bits1 = 0; + break; + } + if (hdlc->shift_reg & 0x01) + hdlc->cbin++; + hdlc->shift_reg >>= 1; + hdlc->bit_shift--; + if (hdlc->bit_shift == 0) { + hdlc->ffvalue = + xfast_flag_value[hdlc->data_bits]; + if (hdlc->dchannel) { + hdlc->ffvalue = 0x7e; + hdlc->state = HDLC_SEND_IDLE1; + hdlc->bit_shift = 8-hdlc->data_bits; + if (hdlc->bit_shift == 0) + hdlc->state = + HDLC_SEND_FAST_IDLE; + } else { + if (!hdlc->do_adapt56) { + hdlc->state = + HDLC_SEND_FAST_FLAG; + hdlc->data_received = 0; + } else { + hdlc->state = HDLC_SENDFLAG_B0; + hdlc->data_received = 0; + } + /* Finished this frame, send flags */ + if (dsize > 1) + dsize = 1; + } + } + break; + case HDLC_SEND_IDLE1: + hdlc->do_closing = 0; + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + hdlc->bit_shift--; + if (hdlc->bit_shift == 0) { + hdlc->state = HDLC_SEND_FAST_IDLE; + hdlc->bit_shift = 0; + } + break; + case HDLC_SEND_FAST_IDLE: + hdlc->do_closing = 0; + hdlc->cbin = 0xff; + hdlc->data_bits = 8; + if (hdlc->bit_shift == 8) { + hdlc->cbin = 0x7e; + hdlc->state = HDLC_SEND_FIRST_FLAG; + } else { + /* the code is for bitreverse streams */ + if (hdlc->do_bitreverse == 0) + *dst++ = bitrev8(hdlc->cbin); + else + *dst++ = hdlc->cbin; + hdlc->bit_shift = 0; + hdlc->data_bits = 0; + len++; + dsize = 0; + } + break; + default: + break; + } + if (hdlc->do_adapt56) { + if (hdlc->data_bits == 7) { + hdlc->cbin <<= 1; + hdlc->cbin++; + hdlc->data_bits++; + } + } + if (hdlc->data_bits == 8) { + /* the code is for bitreverse streams */ + if (hdlc->do_bitreverse == 0) + *dst++ = bitrev8(hdlc->cbin); + else + *dst++ = hdlc->cbin; + hdlc->data_bits = 0; + len++; + dsize--; + } + } + *count -= slen; + + return len; +} +EXPORT_SYMBOL(isdnhdlc_encode); |