diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-06-10 05:30:17 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2016-06-10 05:30:17 -0300 |
commit | d635711daa98be86d4c7fd01499c34f566b54ccb (patch) | |
tree | aa5cc3760a27c3d57146498cb82fa549547de06c /drivers/staging/i4l | |
parent | c91265cd0efb83778f015b4d4b1129bd2cfd075e (diff) |
Linux-libre 4.6.2-gnu
Diffstat (limited to 'drivers/staging/i4l')
32 files changed, 9636 insertions, 0 deletions
diff --git a/drivers/staging/i4l/Documentation/README.act2000 b/drivers/staging/i4l/Documentation/README.act2000 new file mode 100644 index 000000000..ce7115e7f --- /dev/null +++ b/drivers/staging/i4l/Documentation/README.act2000 @@ -0,0 +1,104 @@ +$Id: README.act2000,v 1.3 2000/08/06 09:22:51 armin Exp $ + +This document describes the ACT2000 driver for the +IBM Active 2000 ISDN card. + +There are 3 Types of this card available. A ISA-, MCA-, and PCMCIA-Bus +Version. Currently, only the ISA-Bus version of the card is supported. +However MCA and PCMCIA will follow soon. + +The ISA-Bus Version uses 8 IO-ports. The base port address has to be set +manually using the DIP switches. + +Setting up the DIP switches for the IBM Active 2000 ISDN card: + + Note: S5 and S6 always set off! + + S1 S2 S3 S4 Base-port + on on on on 0x0200 (Factory default) + off on on on 0x0240 + on off on on 0x0280 + off off on on 0x02c0 + on on off on 0x0300 + off on off on 0x0340 + on off off on 0x0380 + on on on off 0xcfe0 + off on on off 0xcfa0 + on off on off 0xcf60 + off off on off 0xcf20 + on on off off 0xcee0 + off on off off 0xcea0 + on off off off 0xce60 + off off off off Card disabled + +IRQ is configured by software. Possible values are: + + 3, 5, 7, 10, 11, 12, 15 and none (polled mode) + + +The ACT2000 driver may either be built into the kernel or as a module. +Initialization depends on how the driver is built: + +Driver built into the kernel: + + The ACT2000 driver can be configured using the commandline-feature while + loading the kernel with LILO or LOADLIN. It accepts the following syntax: + + act2000=b,p,i[,idstring] + + where + + b = Bus-Type (1=ISA, 2=MCA, 3=PCMCIA) + p = portbase (-1 means autoprobe) + i = Interrupt (-1 means use next free IRQ, 0 means polled mode) + + The idstring is an arbitrary string used for referencing the card + by the actctrl tool later. + + Defaults used, when no parameters given at all: + + 1,-1,-1,"" + + which means: Autoprobe for an ISA card, use next free IRQ, let the + ISDN linklevel fill the IdString (usually "line0" for the first card). + + If you like to use more than one card, you can use the program + "actctrl" from the utility-package to configure additional cards. + + Using the "actctrl"-utility, portbase and irq can also be changed + during runtime. The D-channel protocol is configured by the "dproto" + option of the "actctrl"-utility after loading the firmware into the + card's memory using the "actctrl"-utility. + +Driver built as module: + + The module act2000.o can be configured during modprobe (insmod) by + appending its parameters to the modprobe resp. insmod commandline. + The following syntax is accepted: + + act_bus=b act_port=p act_irq=i act_id=idstring + + where b, p, i and idstring have the same meanings as the parameters + described for the builtin version above. + + Using the "actctrl"-utility, the same features apply to the modularized + version as to the kernel-builtin one. (i.e. loading of firmware and + configuring the D-channel protocol) + +Loading the firmware into the card: + + The firmware is supplied together with the isdn4k-utils package. It + can be found in the subdirectory act2000/firmware/ + + Assuming you have installed the utility-package correctly, the firmware + will be downloaded into the card using the following command: + + actctrl -d idstring load /etc/isdn/bip11.btl + + where idstring is the Name of the card, given during insmod-time or + (for kernel-builtin driver) on the kernel commandline. If only one + ISDN card is used, the -d isdstrin may be omitted. + + For further documentation (adding more IBM Active 2000 cards), refer to + the manpage actctrl.8 which is included in the isdn4k-utils package. + diff --git a/drivers/staging/i4l/Documentation/README.icn b/drivers/staging/i4l/Documentation/README.icn new file mode 100644 index 000000000..13f833d4e --- /dev/null +++ b/drivers/staging/i4l/Documentation/README.icn @@ -0,0 +1,148 @@ +$Id: README.icn,v 1.7 2000/08/06 09:22:51 armin Exp $ + +You can get the ICN-ISDN-card from: + +Thinking Objects Software GmbH +Versbacher Röthe 159 +97078 Würzburg +Tel: +49 931 2877950 +Fax: +49 931 2877951 + +email info@think.de +WWW http:/www.think.de + + +The card communicates with the PC by two interfaces: + 1. A range of 4 successive port-addresses, whose base address can be + configured with the switches. + 2. A memory window with 16KB-256KB size, which can be setup in 16k steps + over the whole range of 16MB. Isdn4linux only uses a 16k window. + The base address of the window can be configured when loading + the lowlevel-module (see README). If using more than one card, + all cards are mapped to the same window and activated as needed. + +Setting up the IO-address dipswitches for the ICN-ISDN-card: + + Two types of cards exist, one with dip-switches and one with + hook-switches. + + 1. Setting for the card with hook-switches: + + (0 = switch closed, 1 = switch open) + + S3 S2 S1 Base-address + 0 0 0 0x300 + 0 0 1 0x310 + 0 1 0 0x320 (Default for isdn4linux) + 0 1 1 0x330 + 1 0 0 0x340 + 1 0 1 0x350 + 1 1 0 0x360 + 1 1 1 NOT ALLOWED! + + 2. Setting for the card with dip-switches: + + (0 = switch closed, 1 = switch open) + + S1 S2 S3 S4 Base-Address + 0 0 0 0 0x300 + 0 0 0 1 0x310 + 0 0 1 0 0x320 (Default for isdn4linux) + 0 0 1 1 0x330 + 0 1 0 0 0x340 + 0 1 0 1 0x350 + 0 1 1 0 0x360 + 0 1 1 1 NOT ALLOWED! + 1 0 0 0 0x308 + 1 0 0 1 0x318 + 1 0 1 0 0x328 + 1 0 1 1 0x338 + 1 1 0 0 0x348 + 1 1 0 1 0x358 + 1 1 1 0 0x368 + 1 1 1 1 NOT ALLOWED! + +The ICN driver may be built into the kernel or as a module. Initialization +depends on how the driver is built: + +Driver built into the kernel: + + The ICN driver can be configured using the commandline-feature while + loading the kernel with LILO or LOADLIN. It accepts the following syntax: + + icn=p,m[,idstring1[,idstring2]] + + where + + p = portbase (default: 0x320) + m = shared memory (default: 0xd0000) + + When using the ICN double card (4B), you MUST define TWO idstrings. + idstring must start with a character! There is no way for the driver + to distinguish between a 2B and 4B type card. Therefore, by supplying + TWO idstrings, you tell the driver that you have a 4B installed. + + If you like to use more than one card, you can use the program + "icnctrl" from the utility-package to configure additional cards. + You need to configure shared memory only once, since the icn-driver + maps all cards into the same address-space. + + Using the "icnctrl"-utility, portbase and shared memory can also be + changed during runtime. + + The D-channel protocol is configured by loading different firmware + into the card's memory using the "icnctrl"-utility. + + +Driver built as module: + + The module icn.o can be configured during "insmod'ing" it by + appending its parameters to the insmod-commandline. The following + syntax is accepted: + + portbase=p membase=m icn_id=idstring [icn_id2=idstring2] + + where p, m, idstring1 and idstring2 have the same meanings as the + parameters described for the kernel-version above. + + When using the ICN double card (4B), you MUST define TWO idstrings. + idstring must start with a character! There is no way for the driver + to distinguish between a 2B and 4B type card. Therefore, by supplying + TWO idstrings, you tell the driver that you have a 4B installed. + + Using the "icnctrl"-utility, the same features apply to the modularized + version like to the kernel-builtin one. + + The D-channel protocol is configured by loading different firmware + into the card's memory using the "icnctrl"-utility. + +Loading the firmware into the card: + + The firmware is supplied together with the isdn4k-utils package. It + can be found in the subdirectory icnctrl/firmware/ + + There are 3 files: + + loadpg.bin - Image of the bootstrap loader. + pc_1t_ca.bin - Image of firmware for german 1TR6 protocol. + pc_eu_ca.bin - Image if firmware for EDSS1 (Euro-ISDN) protocol. + + Assuming you have installed the utility-package correctly, the firmware + will be downloaded into the 2B-card using the following command: + + icnctrl -d Idstring load /etc/isdn/loadpg.bin /etc/isdn/pc_XX_ca.bin + + where XX is either "1t" or "eu", depending on the D-Channel protocol + used on your S0-bus and Idstring is the Name of the card, given during + insmod-time or (for kernel-builtin driver) on the kernel commandline. + + To load a 4B-card, the same command is used, except a second firmware + file is appended to the commandline of icnctrl. + + -> After downloading firmware, the two LEDs at the back cover of the card + (ICN-4B: 4 LEDs) must be blinking intermittently now. If a connection + is up, the corresponding led is lit continuously. + + For further documentation (adding more ICN-cards), refer to the manpage + icnctrl.8 which is included in the isdn4k-utils package. + diff --git a/drivers/staging/i4l/Documentation/README.pcbit b/drivers/staging/i4l/Documentation/README.pcbit new file mode 100644 index 000000000..512500228 --- /dev/null +++ b/drivers/staging/i4l/Documentation/README.pcbit @@ -0,0 +1,40 @@ +------------------------------------------------------------------------------ + README file for the PCBIT-D Device Driver. +------------------------------------------------------------------------------ + +The PCBIT is a Euro ISDN adapter manufactured in Portugal by Octal and +developed in cooperation with Portugal Telecom and Inesc. +The driver interfaces with the standard kernel isdn facilities +originally developed by Fritz Elfert in the isdn4linux project. + +The common versions of the pcbit board require a firmware that is +distributed (and copyrighted) by the manufacturer. To load this +firmware you need "pcbitctl" available on the standard isdn4k-utils +package or in the pcbit package available in: + +ftp://ftp.di.fc.ul.pt/pub/systems/Linux/isdn + +Known Limitations: + +- The board reset procedure is at the moment incorrect and will only +allow you to load the firmware after a hard reset. + +- Only HDLC in B-channels is supported at the moment. There is no +current support for X.25 in B or D channels nor LAPD in B +channels. The main reason is that these two other protocol modes have, +to my knowledge, very little use. If you want to see them implemented +*do* send me a mail. + +- The driver often triggers errors in the board that I and the +manufacturer believe to be caused by bugs in the firmware. The current +version includes several procedures for error recovery that should +allow normal operation. Plans for the future include cooperation with +the manufacturer in order to solve this problem. + +Information/hints/help can be obtained in the linux isdn +mailing list (isdn4linux@listserv.isdn4linux.de) or directly from me. + +regards, + Pedro. + +<roque@di.fc.ul.pt> diff --git a/drivers/staging/i4l/Documentation/README.sc b/drivers/staging/i4l/Documentation/README.sc new file mode 100644 index 000000000..1153cd926 --- /dev/null +++ b/drivers/staging/i4l/Documentation/README.sc @@ -0,0 +1,281 @@ +Welcome to Beta Release 2 of the combination ISDN driver for SpellCaster's +ISA ISDN adapters. Please note this release 2 includes support for the +DataCommute/BRI and TeleCommute/BRI adapters only and any other use is +guaranteed to fail. If you have a DataCommute/PRI installed in the test +computer, we recommend removing it as it will be detected but will not +be usable. To see what we have done to Beta Release 2, see section 3. + +Speaking of guarantees, THIS IS BETA SOFTWARE and as such contains +bugs and defects either known or unknown. Use this software at your own +risk. There is NO SUPPORT for this software. Some help may be available +through the web site or the mailing list but such support is totally at +our own option and without warranty. If you choose to assume all and +total risk by using this driver, we encourage you to join the beta +mailing list. + +To join the Linux beta mailing list, send a message to: +majordomo@spellcast.com with the words "subscribe linux-beta" as the only +contents of the message. Do not include a signature. If you choose to +remove yourself from this list at a later date, send another message to +the same address with the words "unsubscribe linux-beta" as its only +contents. + +TABLE OF CONTENTS +----------------- + 1. Introduction + 1.1 What is ISDN4Linux? + 1.2 What is different between this driver and previous drivers? + 1.3 How do I setup my system with the correct software to use + this driver release? + + 2. Basic Operations + 2.1 Unpacking and installing the driver + 2.2 Read the man pages!!! + 2.3 Installing the driver + 2.4 Removing the driver + 2.5 What to do if it doesn't load + 2.6 How to setup ISDN4Linux with the driver + + 3. Beta Change Summaries and Miscellaneous Notes + +1. Introduction +--------------- + +The revision 2 Linux driver for SpellCaster ISA ISDN adapters is built +upon ISDN4Linux available separately or as included in Linux 2.0 and later. +The driver will support a maximum of 4 adapters in any one system of any +type including DataCommute/BRI, DataCommute/PRI and TeleCommute/BRI for a +maximum of 92 channels for host. The driver is supplied as a module in +source form and needs to be complied before it can be used. It has been +tested on Linux 2.0.20. + +1.1 What Is ISDN4Linux + +ISDN4Linux is a driver and set of tools used to access and use ISDN devices +on a Linux platform in a common and standard way. It supports HDLC and PPP +protocols and offers channel bundling and MLPPP support. To use ISDN4Linux +you need to configure your kernel for ISDN support and get the ISDN4Linux +tool kit from our web site. + +ISDN4Linux creates a channel pool from all of the available ISDN channels +and therefore can function across adapters. When an ISDN4Linux compliant +driver (such as ours) is loaded, all of the channels go into a pool and +are used on a first-come first-served basis. In addition, individual +channels can be specifically bound to particular interfaces. + +1.2 What is different between this driver and previous drivers? + +The revision 2 driver besides adopting the ISDN4Linux architecture has many +subtle and not so subtle functional differences from previous releases. These +include: + - More efficient shared memory management combined with a simpler + configuration. All adapters now use only 16Kbytes of shared RAM + versus between 16K and 64K. New methods for using the shared RAM + allow us to utilize all of the available RAM on the adapter through + only one 16K page. + - Better detection of available upper memory. The probing routines + have been improved to better detect available shared RAM pages and + used pages are now locked. + - Decreased loading time and a wider range of I/O ports probed. + We have significantly reduced the amount of time it takes to load + the driver and at the same time doubled the number of I/O ports + probed increasing the likelihood of finding an adapter. + - We now support all ISA adapter models with a single driver instead + of separate drivers for each model. The revision 2 driver supports + the DataCommute/BRI, DataCommute/PRI and TeleCommute/BRI in any + combination up to a maximum of four adapters per system. + - On board PPP protocol support has been removed in favour of the + sync-PPP support used in ISDN4Linux. This means more control of + the protocol parameters, faster negotiation time and a more + familiar interface. + +1.3 How do I setup my system with the correct software to use + this driver release? + +Before you can compile, install and use the SpellCaster ISA ISDN driver, you +must ensure that the following software is installed, configured and running: + + - Linux kernel 2.0.20 or later with the required init and ps + versions. Please see your distribution vendor for the correct + utility packages. The latest kernel is available from + ftp://sunsite.unc.edu/pub/Linux/kernel/v2.0/ + + - The latest modules package (modules-2.0.0.tar.gz) from + ftp://sunsite.unc.edu/pub/Linux/kernel/modules-2.0.0.tar.gz + + - The ISDN4Linux tools available from + ftp://ftp.franken.de/pub/isdn4linux/v2.0/isdn4k-utils-2.0.tar.gz + This package may fail to compile for you so you can alternatively + get a pre-compiled version from + ftp://ftp.spellcast.com/pub/drivers/isdn4linux/isdn4k-bin-2.0.tar.gz + + +2. Basic Operations +------------------- + +2.1 Unpacking and installing the driver + + 1. As root, create a directory in a convenient place. We suggest + /usr/src/spellcaster. + + 2. Unpack the archive with : + tar xzf sc-n.nn.tar.gz -C /usr/src/spellcaster + + 3. Change directory to /usr/src/spellcaster + + 4. Read the README and RELNOTES files. + + 5. Run 'make' and if all goes well, run 'make install'. + +2.2 Read the man pages!!! + +Make sure you read the scctrl(8) and sc(4) manual pages before continuing +any further. Type 'man 8 scctrl' and 'man 4 sc'. + +2.3 Installing the driver + +To install the driver, type '/sbin/insmod sc' as root. sc(4) details options +you can specify but you shouldn't need to use any unless this doesn't work. + +Make sure the driver loaded and detected all of the adapters by typing +'dmesg'. + +The driver can be configured so that it is loaded upon startup. To do this, +edit the file "/etc/modules/'uname -f'/'uname -v'" and insert the driver name +"sc" into this file. + +2.4 Removing the driver + +To remove the driver, delete any interfaces that may exist (see isdnctrl(8) +for more on this) and then type '/sbin/rmmod sc'. + +2.5 What to do if it doesn't load + +If, when you try to install the driver, you get a message mentioning +'register_isdn' then you do not have the ISDN4Linux system installed. Please +make sure that ISDN support is configured in the kernel. + +If you get a message that says 'initialization of sc failed', then the +driver failed to detect an adapter or failed to find resources needed such +as a free IRQ line or shared memory segment. If you are sure there are free +resources available, use the insmod options detailed in sc(4) to override +the probing function. + +Upon testing, the following problem was noted, the driver would load without +problems, but the board would not respond beyond that point. When a check was +done with 'cat /proc/interrupts' the interrupt count for sc was 0. In the event +of this problem, change the BIOS settings so that the interrupts in question are +reserved for ISA use only. + + +2.6 How to setup ISDN4Linux with the driver + +There are three main configurations which you can use with the driver: + +A) Basic HDLC connection +B) PPP connection +C) MLPPP connection + +It should be mentioned here that you may also use a tty connection if you +desire. The Documentation directory of the isdn4linux subsystem offers good +documentation on this feature. + +A) 10 steps to the establishment of a basic HDLC connection +----------------------------------------------------------- + +- please open the isdn-hdlc file in the examples directory and follow along... + + This file is a script used to configure a BRI ISDN TA to establish a + basic HDLC connection between its two channels. Two network + interfaces are created and two routes added between the channels. + + i) using the isdnctrl utility, add an interface with "addif" and + name it "isdn0" + ii) add the outgoing and inbound telephone numbers + iii) set the Layer 2 protocol to hdlc + iv) set the eaz of the interface to be the phone number of that + specific channel + v) to turn the callback features off, set the callback to "off" and + the callback delay (cbdelay) to 0. + vi) the hangup timeout can be set to a specified number of seconds + vii) the hangup upon incoming call can be set on or off + viii) use the ifconfig command to bring up the network interface with + a specific IP address and point to point address + ix) add a route to the IP address through the isdn0 interface + x) a ping should result in the establishment of the connection + + +B) Establishment of a PPP connection +------------------------------------ + +- please open the isdn-ppp file in the examples directory and follow along... + + This file is a script used to configure a BRI ISDN TA to establish a + PPP connection between the two channels. The file is almost + identical to the HDLC connection example except that the packet + encapsulation type has to be set. + + use the same procedure as in the HDLC connection from steps i) to + iii) then, after the Layer 2 protocol is set, set the encapsulation + "encap" to syncppp. With this done, the rest of the steps, iv) to x) + can be followed from above. + + Then, the ipppd (ippp daemon) must be setup: + + xi) use the ipppd function found in /sbin/ipppd to set the following: + xii) take out (minus) VJ compression and bsd compression + xiii) set the mru size to 2000 + xiv) link the two /dev interfaces to the daemon + +NOTE: A "*" in the inbound telephone number specifies that a call can be +accepted on any number. + +C) Establishment of a MLPPP connection +-------------------------------------- + +- please open the isdn-mppp file in the examples directory and follow along... + + This file is a script used to configure a BRI ISDN TA to accept a + Multi Link PPP connection. + + i) using the isdnctrl utility, add an interface with "addif" and + name it "ippp0" + ii) add the inbound telephone number + iii) set the Layer 2 protocol to hdlc and the Layer 3 protocol to + trans (transparent) + iv) set the packet encapsulation to syncppp + v) set the eaz of the interface to be the phone number of that + specific channel + vi) to turn the callback features off, set the callback to "off" and + the callback delay (cbdelay) to 0. + vi) the hangup timeout can be set to a specified number of seconds + vii) the hangup upon incoming call can be set on or off + viii) add a slave interface and name it "ippp32" for example + ix) set the similar parameters for the ippp32 interface + x) use the ifconfig command to bring-up the ippp0 interface with a + specific IP address and point to point address + xi) add a route to the IP address through the ippp0 interface + xii) use the ipppd function found in /sbin/ipppd to set the following: + xiii) take out (minus) bsd compression + xiv) set the mru size to 2000 + xv) add (+) the multi-link function "+mp" + xvi) link the two /dev interfaces to the daemon + +NOTE: To use the MLPPP connection to dial OUT to a MLPPP connection, change +the inbound telephone numbers to the outgoing telephone numbers of the MLPPP +host. + + +3. Beta Change Summaries and Miscellaneous Notes +------------------------------------------------ +When using the "scctrl" utility to upload firmware revisions on the board, +please note that the byte count displayed at the end of the operation may be +different from the total number of bytes in the "dcbfwn.nn.sr" file. Please +disregard the displayed byte count. + +It was noted that in Beta Release 1, the module would fail to load and result +in a segmentation fault when 'insmod'ed. This problem was created when one of +the isdn4linux parameters, (isdn_ctrl, data field) was filled in. In some +cases, this data field was NULL, and was left unchecked, so when it was +referenced... segv. The bug has been fixed around line 63-68 of event.c. + diff --git a/drivers/staging/i4l/Kconfig b/drivers/staging/i4l/Kconfig new file mode 100644 index 000000000..920216e88 --- /dev/null +++ b/drivers/staging/i4l/Kconfig @@ -0,0 +1,13 @@ +# +# Old ISDN4Linux config +# +menu "Old ISDN4Linux (deprecated)" + depends on ISDN_I4L + +source "drivers/staging/i4l/icn/Kconfig" + +source "drivers/staging/i4l/pcbit/Kconfig" + +source "drivers/staging/i4l/act2000/Kconfig" + +endmenu diff --git a/drivers/staging/i4l/Makefile b/drivers/staging/i4l/Makefile new file mode 100644 index 000000000..158b87093 --- /dev/null +++ b/drivers/staging/i4l/Makefile @@ -0,0 +1,5 @@ +# Makefile for the old ISDN I4L subsystem and device drivers. + +obj-$(CONFIG_ISDN_DRV_ICN) += icn/ +obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit/ +obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000/ diff --git a/drivers/staging/i4l/TODO b/drivers/staging/i4l/TODO new file mode 100644 index 000000000..6fe2c08be --- /dev/null +++ b/drivers/staging/i4l/TODO @@ -0,0 +1,3 @@ +* The icn, pcbit and act2000 drivers are dead, remove them in 2017 + after another longterm kernel has been released, just in the + unlikely case someone still has this hardware. diff --git a/drivers/staging/i4l/act2000/Kconfig b/drivers/staging/i4l/act2000/Kconfig new file mode 100644 index 000000000..fa2673fc6 --- /dev/null +++ b/drivers/staging/i4l/act2000/Kconfig @@ -0,0 +1,9 @@ +config ISDN_DRV_ACT2000 + tristate "IBM Active 2000 support" + depends on ISA + help + Say Y here if you have an IBM Active 2000 ISDN card. In order to use + this card, additional firmware is necessary, which has to be loaded + into the card using a utility which is part of the latest + isdn4k-utils package. Please read the file + <file:Documentation/isdn/README.act2000> for more information. diff --git a/drivers/staging/i4l/act2000/Makefile b/drivers/staging/i4l/act2000/Makefile new file mode 100644 index 000000000..05e582fb5 --- /dev/null +++ b/drivers/staging/i4l/act2000/Makefile @@ -0,0 +1,9 @@ +# Makefile for the act2000 ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_ACT2000) += act2000.o + +# Multipart objects. + +act2000-y := module.o capi.o act2000_isa.o diff --git a/drivers/staging/i4l/act2000/act2000.h b/drivers/staging/i4l/act2000/act2000.h new file mode 100644 index 000000000..321d437f5 --- /dev/null +++ b/drivers/staging/i4l/act2000/act2000.h @@ -0,0 +1,202 @@ +/* $Id: act2000.h,v 1.8.6.3 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef act2000_h +#define act2000_h + +#include <linux/compiler.h> + +#define ACT2000_IOCTL_SETPORT 1 +#define ACT2000_IOCTL_GETPORT 2 +#define ACT2000_IOCTL_SETIRQ 3 +#define ACT2000_IOCTL_GETIRQ 4 +#define ACT2000_IOCTL_SETBUS 5 +#define ACT2000_IOCTL_GETBUS 6 +#define ACT2000_IOCTL_SETPROTO 7 +#define ACT2000_IOCTL_GETPROTO 8 +#define ACT2000_IOCTL_SETMSN 9 +#define ACT2000_IOCTL_GETMSN 10 +#define ACT2000_IOCTL_LOADBOOT 11 +#define ACT2000_IOCTL_ADDCARD 12 + +#define ACT2000_IOCTL_TEST 98 +#define ACT2000_IOCTL_DEBUGVAR 99 + +#define ACT2000_BUS_ISA 1 +#define ACT2000_BUS_MCA 2 +#define ACT2000_BUS_PCMCIA 3 + +/* Struct for adding new cards */ +typedef struct act2000_cdef { + int bus; + int port; + int irq; + char id[10]; +} act2000_cdef; + +/* Struct for downloading firmware */ +typedef struct act2000_ddef { + int length; /* Length of code */ + char __user *buffer; /* Ptr. to code */ +} act2000_ddef; + +typedef struct act2000_fwid { + char isdn[4]; + char revlen[2]; + char revision[504]; +} act2000_fwid; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/workqueue.h> +#include <linux/interrupt.h> +#include <linux/skbuff.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <asm/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/ctype.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +#define ACT2000_PORTLEN 8 + +#define ACT2000_FLAGS_RUNNING 1 /* Cards driver activated */ +#define ACT2000_FLAGS_PVALID 2 /* Cards port is valid */ +#define ACT2000_FLAGS_IVALID 4 /* Cards irq is valid */ +#define ACT2000_FLAGS_LOADED 8 /* Firmware loaded */ + +#define ACT2000_BCH 2 /* # of channels per card */ + +/* D-Channel states */ +#define ACT2000_STATE_NULL 0 +#define ACT2000_STATE_ICALL 1 +#define ACT2000_STATE_OCALL 2 +#define ACT2000_STATE_IWAIT 3 +#define ACT2000_STATE_OWAIT 4 +#define ACT2000_STATE_IBWAIT 5 +#define ACT2000_STATE_OBWAIT 6 +#define ACT2000_STATE_BWAIT 7 +#define ACT2000_STATE_BHWAIT 8 +#define ACT2000_STATE_BHWAIT2 9 +#define ACT2000_STATE_DHWAIT 10 +#define ACT2000_STATE_DHWAIT2 11 +#define ACT2000_STATE_BSETUP 12 +#define ACT2000_STATE_ACTIVE 13 + +#define ACT2000_MAX_QUEUED 8000 /* 2 * maxbuff */ + +#define ACT2000_LOCK_TX 0 +#define ACT2000_LOCK_RX 1 + +typedef struct act2000_chan { + unsigned short callref; /* Call Reference */ + unsigned short fsm_state; /* Current D-Channel state */ + unsigned short eazmask; /* EAZ-Mask for this Channel */ + short queued; /* User-Data Bytes in TX queue */ + unsigned short plci; + unsigned short ncci; + unsigned char l2prot; /* Layer 2 protocol */ + unsigned char l3prot; /* Layer 3 protocol */ +} act2000_chan; + +typedef struct msn_entry { + char eaz; + char msn[16]; + struct msn_entry *next; +} msn_entry; + +typedef struct irq_data_isa { + __u8 *rcvptr; + __u16 rcvidx; + __u16 rcvlen; + struct sk_buff *rcvskb; + __u8 rcvignore; + __u8 rcvhdr[8]; +} irq_data_isa; + +typedef union act2000_irq_data { + irq_data_isa isa; +} act2000_irq_data; + +/* + * Per card driver data + */ +typedef struct act2000_card { + unsigned short port; /* Base-port-address */ + unsigned short irq; /* Interrupt */ + u_char ptype; /* Protocol type (1TR6 or Euro) */ + u_char bus; /* Cardtype (ISA, MCA, PCMCIA) */ + struct act2000_card *next; /* Pointer to next device struct */ + spinlock_t lock; /* protect critical operations */ + int myid; /* Driver-Nr. assigned by linklevel */ + unsigned long flags; /* Statusflags */ + unsigned long ilock; /* Semaphores for IRQ-Routines */ + struct sk_buff_head rcvq; /* Receive-Message queue */ + struct sk_buff_head sndq; /* Send-Message queue */ + struct sk_buff_head ackq; /* Data-Ack-Message queue */ + u_char *ack_msg; /* Ptr to User Data in User skb */ + __u16 need_b3ack; /* Flag: Need ACK for current skb */ + struct sk_buff *sbuf; /* skb which is currently sent */ + struct timer_list ptimer; /* Poll timer */ + struct work_struct snd_tq; /* Task struct for xmit bh */ + struct work_struct rcv_tq; /* Task struct for rcv bh */ + struct work_struct poll_tq; /* Task struct for polled rcv bh */ + msn_entry *msn_list; + unsigned short msgnum; /* Message number for sending */ + spinlock_t mnlock; /* lock for msgnum */ + act2000_chan bch[ACT2000_BCH]; /* B-Channel status/control */ + char status_buf[256]; /* Buffer for status messages */ + char *status_buf_read; + char *status_buf_write; + char *status_buf_end; + act2000_irq_data idat; /* Data used for IRQ handler */ + isdn_if interface; /* Interface to upper layer */ + char regname[35]; /* Name used for request_region */ +} act2000_card; + +static inline void act2000_schedule_tx(act2000_card *card) +{ + schedule_work(&card->snd_tq); +} + +static inline void act2000_schedule_rx(act2000_card *card) +{ + schedule_work(&card->rcv_tq); +} + +static inline void act2000_schedule_poll(act2000_card *card) +{ + schedule_work(&card->poll_tq); +} + +extern char *act2000_find_eaz(act2000_card *, char); + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* act2000_h */ diff --git a/drivers/staging/i4l/act2000/act2000_isa.c b/drivers/staging/i4l/act2000/act2000_isa.c new file mode 100644 index 000000000..b5fad29a9 --- /dev/null +++ b/drivers/staging/i4l/act2000/act2000_isa.c @@ -0,0 +1,443 @@ +/* $Id: act2000_isa.c,v 1.11.6.3 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version). + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "act2000_isa.h" +#include "capi.h" + +/* + * Reset Controller, then try to read the Card's signature. + + Return: + * 1 = Signature found. + * 0 = Signature not found. + */ +static int +act2000_isa_reset(unsigned short portbase) +{ + unsigned char reg; + int i; + int found; + int serial = 0; + + found = 0; + if ((reg = inb(portbase + ISA_COR)) != 0xff) { + outb(reg | ISA_COR_RESET, portbase + ISA_COR); + mdelay(10); + outb(reg, portbase + ISA_COR); + mdelay(10); + + for (i = 0; i < 16; i++) { + if (inb(portbase + ISA_ISR) & ISA_ISR_SERIAL) + serial |= 0x10000; + serial >>= 1; + } + if (serial == ISA_SER_ID) + found++; + } + return found; +} + +int +act2000_isa_detect(unsigned short portbase) +{ + int ret = 0; + + if (request_region(portbase, ACT2000_PORTLEN, "act2000isa")) { + ret = act2000_isa_reset(portbase); + release_region(portbase, ISA_REGION); + } + return ret; +} + +static irqreturn_t +act2000_isa_interrupt(int dummy, void *dev_id) +{ + act2000_card *card = dev_id; + u_char istatus; + + istatus = (inb(ISA_PORT_ISR) & 0x07); + if (istatus & ISA_ISR_OUT) { + /* RX fifo has data */ + istatus &= ISA_ISR_OUT_MASK; + outb(0, ISA_PORT_SIS); + act2000_isa_receive(card); + outb(ISA_SIS_INT, ISA_PORT_SIS); + } + if (istatus & ISA_ISR_ERR) { + /* Error Interrupt */ + istatus &= ISA_ISR_ERR_MASK; + printk(KERN_WARNING "act2000: errIRQ\n"); + } + if (istatus) + printk(KERN_DEBUG "act2000: ?IRQ %d %02x\n", card->irq, istatus); + return IRQ_HANDLED; +} + +static void +act2000_isa_select_irq(act2000_card *card) +{ + unsigned char reg; + + reg = (inb(ISA_PORT_COR) & ~ISA_COR_IRQOFF) | ISA_COR_PERR; + switch (card->irq) { + case 3: + reg = ISA_COR_IRQ03; + break; + case 5: + reg = ISA_COR_IRQ05; + break; + case 7: + reg = ISA_COR_IRQ07; + break; + case 10: + reg = ISA_COR_IRQ10; + break; + case 11: + reg = ISA_COR_IRQ11; + break; + case 12: + reg = ISA_COR_IRQ12; + break; + case 15: + reg = ISA_COR_IRQ15; + break; + } + outb(reg, ISA_PORT_COR); +} + +static void +act2000_isa_enable_irq(act2000_card *card) +{ + act2000_isa_select_irq(card); + /* Enable READ irq */ + outb(ISA_SIS_INT, ISA_PORT_SIS); +} + +/* + * Install interrupt handler, enable irq on card. + * If irq is -1, choose next free irq, else irq is given explicitly. + */ +int +act2000_isa_config_irq(act2000_card *card, short irq) +{ + int old_irq; + + if (card->flags & ACT2000_FLAGS_IVALID) { + free_irq(card->irq, card); + } + card->flags &= ~ACT2000_FLAGS_IVALID; + outb(ISA_COR_IRQOFF, ISA_PORT_COR); + if (!irq) + return 0; + + old_irq = card->irq; + card->irq = irq; + if (request_irq(irq, &act2000_isa_interrupt, 0, card->regname, card)) { + card->irq = old_irq; + card->flags |= ACT2000_FLAGS_IVALID; + printk(KERN_WARNING + "act2000: Could not request irq %d\n", irq); + return -EBUSY; + } else { + act2000_isa_select_irq(card); + /* Disable READ and WRITE irq */ + outb(0, ISA_PORT_SIS); + outb(0, ISA_PORT_SOS); + } + return 0; +} + +int +act2000_isa_config_port(act2000_card *card, unsigned short portbase) +{ + if (card->flags & ACT2000_FLAGS_PVALID) { + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + } + if (request_region(portbase, ACT2000_PORTLEN, card->regname) == NULL) + return -EBUSY; + else { + card->port = portbase; + card->flags |= ACT2000_FLAGS_PVALID; + return 0; + } +} + +/* + * Release ressources, used by an adaptor. + */ +void +act2000_isa_release(act2000_card *card) +{ + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + if (card->flags & ACT2000_FLAGS_IVALID) + free_irq(card->irq, card); + + card->flags &= ~ACT2000_FLAGS_IVALID; + if (card->flags & ACT2000_FLAGS_PVALID) + release_region(card->port, ISA_REGION); + card->flags &= ~ACT2000_FLAGS_PVALID; + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +act2000_isa_writeb(act2000_card *card, u_char data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SOS) & ISA_SOS_READY) { + outb(data, ISA_PORT_SDO); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +static int +act2000_isa_readb(act2000_card *card, u_char *data) +{ + u_char timeout = 40; + + while (timeout) { + if (inb(ISA_PORT_SIS) & ISA_SIS_READY) { + *data = inb(ISA_PORT_SDI); + return 0; + } else { + timeout--; + udelay(10); + } + } + return 1; +} + +void +act2000_isa_receive(act2000_card *card) +{ + u_char c; + + if (test_and_set_bit(ACT2000_LOCK_RX, (void *) &card->ilock) != 0) + return; + while (!act2000_isa_readb(card, &c)) { + if (card->idat.isa.rcvidx < 8) { + card->idat.isa.rcvhdr[card->idat.isa.rcvidx++] = c; + if (card->idat.isa.rcvidx == 8) { + int valid = actcapi_chkhdr(card, (actcapi_msghdr *)&card->idat.isa.rcvhdr); + + if (valid) { + card->idat.isa.rcvlen = ((actcapi_msghdr *)&card->idat.isa.rcvhdr)->len; + card->idat.isa.rcvskb = dev_alloc_skb(card->idat.isa.rcvlen); + if (card->idat.isa.rcvskb == NULL) { + card->idat.isa.rcvignore = 1; + printk(KERN_WARNING + "act2000_isa_receive: no memory\n"); + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); + return; + } + memcpy(skb_put(card->idat.isa.rcvskb, 8), card->idat.isa.rcvhdr, 8); + card->idat.isa.rcvptr = skb_put(card->idat.isa.rcvskb, card->idat.isa.rcvlen - 8); + } else { + card->idat.isa.rcvidx = 0; + printk(KERN_WARNING + "act2000_isa_receive: Invalid CAPI msg\n"); + { + int i; __u8 *p; __u8 *t; __u8 tmp[30]; + for (i = 0, p = (__u8 *)&card->idat.isa.rcvhdr, t = tmp; i < 8; i++) + t += sprintf(t, "%02x ", *(p++)); + printk(KERN_WARNING "act2000_isa_receive: %s\n", tmp); + } + } + } + } else { + if (!card->idat.isa.rcvignore) + *card->idat.isa.rcvptr++ = c; + if (++card->idat.isa.rcvidx >= card->idat.isa.rcvlen) { + if (!card->idat.isa.rcvignore) { + skb_queue_tail(&card->rcvq, card->idat.isa.rcvskb); + act2000_schedule_rx(card); + } + card->idat.isa.rcvidx = 0; + card->idat.isa.rcvlen = 8; + card->idat.isa.rcvignore = 0; + card->idat.isa.rcvskb = NULL; + card->idat.isa.rcvptr = card->idat.isa.rcvhdr; + } + } + } + if (!(card->flags & ACT2000_FLAGS_IVALID)) { + /* In polling mode, schedule myself */ + if ((card->idat.isa.rcvidx) && + (card->idat.isa.rcvignore || + (card->idat.isa.rcvidx < card->idat.isa.rcvlen))) + act2000_schedule_poll(card); + } + test_and_clear_bit(ACT2000_LOCK_RX, (void *) &card->ilock); +} + +void +act2000_isa_send(act2000_card *card) +{ + unsigned long flags; + struct sk_buff *skb; + actcapi_msg *msg; + int l; + + if (test_and_set_bit(ACT2000_LOCK_TX, (void *) &card->ilock) != 0) + return; + while (1) { + spin_lock_irqsave(&card->lock, flags); + if (!(card->sbuf)) { + if ((card->sbuf = skb_dequeue(&card->sndq))) { + card->ack_msg = card->sbuf->data; + msg = (actcapi_msg *)card->sbuf->data; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0)) { + /* Save flags in message */ + card->need_b3ack = msg->msg.data_b3_req.flags; + msg->msg.data_b3_req.flags = 0; + } + } + } + spin_unlock_irqrestore(&card->lock, flags); + if (!(card->sbuf)) { + /* No more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + return; + } + skb = card->sbuf; + l = 0; + while (skb->len) { + if (act2000_isa_writeb(card, *(skb->data))) { + /* Fifo is full, but more data to send */ + test_and_clear_bit(ACT2000_LOCK_TX, (void *) &card->ilock); + /* Schedule myself */ + act2000_schedule_tx(card); + return; + } + skb_pull(skb, 1); + l++; + } + msg = (actcapi_msg *)card->ack_msg; + if ((msg->hdr.cmd.cmd == 0x86) && + (msg->hdr.cmd.subcmd == 0)) { + /* + * If it's user data, reset data-ptr + * and put skb into ackq. + */ + skb->data = card->ack_msg; + /* Restore flags in message */ + msg->msg.data_b3_req.flags = card->need_b3ack; + skb_queue_tail(&card->ackq, skb); + } else + dev_kfree_skb(skb); + card->sbuf = NULL; + } +} + +/* + * Get firmware ID, check for 'ISDN' signature. + */ +static int +act2000_isa_getid(act2000_card *card) +{ + + act2000_fwid fid; + u_char *p = (u_char *)&fid; + int count = 0; + + while (1) { + if (count > 510) + return -EPROTO; + if (act2000_isa_readb(card, p++)) + break; + count++; + } + if (count <= 20) { + printk(KERN_WARNING "act2000: No Firmware-ID!\n"); + return -ETIME; + } + *p = '\0'; + fid.revlen[0] = '\0'; + if (strcmp(fid.isdn, "ISDN")) { + printk(KERN_WARNING "act2000: Wrong Firmware-ID!\n"); + return -EPROTO; + } + if ((p = strchr(fid.revision, '\n'))) + *p = '\0'; + printk(KERN_INFO "act2000: Firmware-ID: %s\n", fid.revision); + if (card->flags & ACT2000_FLAGS_IVALID) { + printk(KERN_DEBUG "Enabling Interrupts ...\n"); + act2000_isa_enable_irq(card); + } + return 0; +} + +/* + * Download microcode into card, check Firmware signature. + */ +int +act2000_isa_download(act2000_card *card, act2000_ddef __user *cb) +{ + unsigned int length; + int l; + int c; + long timeout; + u_char *b; + u_char __user *p; + u_char *buf; + act2000_ddef cblock; + + if (!act2000_isa_reset(card->port)) + return -ENXIO; + msleep_interruptible(500); + if (copy_from_user(&cblock, cb, sizeof(cblock))) + return -EFAULT; + length = cblock.length; + p = cblock.buffer; + if (!access_ok(VERIFY_READ, p, length)) + return -EFAULT; + buf = kmalloc(1024, GFP_KERNEL); + if (!buf) + return -ENOMEM; + timeout = 0; + while (length) { + l = (length > 1024) ? 1024 : length; + c = 0; + b = buf; + if (copy_from_user(buf, p, l)) { + kfree(buf); + return -EFAULT; + } + while (c < l) { + if (act2000_isa_writeb(card, *b++)) { + printk(KERN_WARNING + "act2000: loader timed out" + " len=%d c=%d\n", length, c); + kfree(buf); + return -ETIME; + } + c++; + } + length -= l; + p += l; + } + kfree(buf); + msleep_interruptible(500); + return (act2000_isa_getid(card)); +} diff --git a/drivers/staging/i4l/act2000/act2000_isa.h b/drivers/staging/i4l/act2000/act2000_isa.h new file mode 100644 index 000000000..1a728984e --- /dev/null +++ b/drivers/staging/i4l/act2000/act2000_isa.h @@ -0,0 +1,136 @@ +/* $Id: act2000_isa.h,v 1.4.6.1 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000 (ISA-Version). + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef act2000_isa_h +#define act2000_isa_h + +#define ISA_POLL_LOOP 40 /* Try to read-write before give up */ + +typedef enum { + INT_NO_CHANGE = 0, /* Do not change the Mask */ + INT_ON = 1, /* Set to Enable */ + INT_OFF = 2, /* Set to Disable */ +} ISA_INT_T; + +/**************************************************************************/ +/* Configuration Register COR (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* Soft Res| IRQM | IRQ Select | N/A | WAIT |Proc err */ +/**************************************************************************/ +#define ISA_COR 0 /* Offset for ISA config register */ +#define ISA_COR_PERR 0x01 /* Processor Error Enabled */ +#define ISA_COR_WS 0x02 /* Insert Wait State if 1 */ +#define ISA_COR_IRQOFF 0x38 /* No Interrupt */ +#define ISA_COR_IRQ07 0x30 /* IRQ 7 Enable */ +#define ISA_COR_IRQ05 0x28 /* IRQ 5 Enable */ +#define ISA_COR_IRQ03 0x20 /* IRQ 3 Enable */ +#define ISA_COR_IRQ10 0x18 /* IRQ 10 Enable */ +#define ISA_COR_IRQ11 0x10 /* IRQ 11 Enable */ +#define ISA_COR_IRQ12 0x08 /* IRQ 12 Enable */ +#define ISA_COR_IRQ15 0x00 /* IRQ 15 Enable */ +#define ISA_COR_IRQPULSE 0x40 /* 0 = Level 1 = Pulse Interrupt */ +#define ISA_COR_RESET 0x80 /* Soft Reset for Transputer */ + +/**************************************************************************/ +/* Interrupt Source Register ISR (RO) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A |Err sig |Ser ID |IN Intr |Out Intr| Error */ +/**************************************************************************/ +#define ISA_ISR 1 /* Offset for Interrupt Register */ +#define ISA_ISR_ERR 0x01 /* Error Interrupt */ +#define ISA_ISR_OUT 0x02 /* Output Interrupt */ +#define ISA_ISR_INP 0x04 /* Input Interrupt */ +#define ISA_ISR_SERIAL 0x08 /* Read out Serial ID after Reset */ +#define ISA_ISR_ERRSIG 0x10 /* Error Signal Input */ +#define ISA_ISR_ERR_MASK 0xfe /* Mask Error Interrupt */ +#define ISA_ISR_OUT_MASK 0xfd /* Mask Output Interrupt */ +#define ISA_ISR_INP_MASK 0xfb /* Mask Input Interrupt */ + +/* Signature delivered after Reset at ISA_ISR_SERIAL (LSB first) */ +#define ISA_SER_ID 0x0201 /* ID for ISA Card */ + +/**************************************************************************/ +/* EEPROM Register EPR (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A |ROM Hold| ROM CS |ROM CLK | ROM IN |ROM Out */ +/**************************************************************************/ +#define ISA_EPR 2 /* Offset for this Register */ +#define ISA_EPR_OUT 0x01 /* Rome Register Out (RO) */ +#define ISA_EPR_IN 0x02 /* Rom Register In (WR) */ +#define ISA_EPR_CLK 0x04 /* Rom Clock (WR) */ +#define ISA_EPR_CS 0x08 /* Rom Cip Select (WR) */ +#define ISA_EPR_HOLD 0x10 /* Rom Hold Signal (WR) */ + +/**************************************************************************/ +/* EEPROM enable Register EER (unused) */ +/**************************************************************************/ +#define ISA_EER 3 /* Offset for this Register */ + +/**************************************************************************/ +/* SLC Data Input SDI (RO) */ +/**************************************************************************/ +#define ISA_SDI 4 /* Offset for this Register */ + +/**************************************************************************/ +/* SLC Data Output SDO (WO) */ +/**************************************************************************/ +#define ISA_SDO 5 /* Offset for this Register */ + +/**************************************************************************/ +/* IMS C011 Mode 2 Input Status Register for INMOS CPU SIS (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Data Pre */ +/**************************************************************************/ +#define ISA_SIS 6 /* Offset for this Register */ +#define ISA_SIS_READY 0x01 /* If 1 : data is available */ +#define ISA_SIS_INT 0x02 /* Enable Interrupt for READ */ + +/**************************************************************************/ +/* IMS C011 Mode 2 Output Status Register from INMOS CPU SOS (RW) */ +/**************************************************************************/ +/* 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 */ +/* N/A | N/A | N/A | N/A | N/A | N/A |Int Ena |Out Rdy */ +/**************************************************************************/ +#define ISA_SOS 7 /* Offset for this Register */ +#define ISA_SOS_READY 0x01 /* If 1 : we can write Data */ +#define ISA_SOS_INT 0x02 /* Enable Interrupt for WRITE */ + +#define ISA_REGION 8 /* Number of Registers */ + + +/* Macros for accessing ports */ +#define ISA_PORT_COR (card->port + ISA_COR) +#define ISA_PORT_ISR (card->port + ISA_ISR) +#define ISA_PORT_EPR (card->port + ISA_EPR) +#define ISA_PORT_EER (card->port + ISA_EER) +#define ISA_PORT_SDI (card->port + ISA_SDI) +#define ISA_PORT_SDO (card->port + ISA_SDO) +#define ISA_PORT_SIS (card->port + ISA_SIS) +#define ISA_PORT_SOS (card->port + ISA_SOS) + +/* Prototypes */ + +extern int act2000_isa_detect(unsigned short portbase); +extern int act2000_isa_config_irq(act2000_card *card, short irq); +extern int act2000_isa_config_port(act2000_card *card, unsigned short portbase); +extern int act2000_isa_download(act2000_card *card, act2000_ddef __user *cb); +extern void act2000_isa_release(act2000_card *card); +extern void act2000_isa_receive(act2000_card *card); +extern void act2000_isa_send(act2000_card *card); + +#endif /* act2000_isa_h */ diff --git a/drivers/staging/i4l/act2000/capi.c b/drivers/staging/i4l/act2000/capi.c new file mode 100644 index 000000000..3f66ca20b --- /dev/null +++ b/drivers/staging/i4l/act2000/capi.c @@ -0,0 +1,1180 @@ +/* $Id: capi.c,v 1.9.6.2 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * CAPI encoder/decoder + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "capi.h" + +static actcapi_msgdsc valid_msg[] = { + {{ 0x86, 0x02}, "DATA_B3_IND"}, /* DATA_B3_IND/CONF must be first because of speed!!! */ + {{ 0x86, 0x01}, "DATA_B3_CONF"}, + {{ 0x02, 0x01}, "CONNECT_CONF"}, + {{ 0x02, 0x02}, "CONNECT_IND"}, + {{ 0x09, 0x01}, "CONNECT_INFO_CONF"}, + {{ 0x03, 0x02}, "CONNECT_ACTIVE_IND"}, + {{ 0x04, 0x01}, "DISCONNECT_CONF"}, + {{ 0x04, 0x02}, "DISCONNECT_IND"}, + {{ 0x05, 0x01}, "LISTEN_CONF"}, + {{ 0x06, 0x01}, "GET_PARAMS_CONF"}, + {{ 0x07, 0x01}, "INFO_CONF"}, + {{ 0x07, 0x02}, "INFO_IND"}, + {{ 0x08, 0x01}, "DATA_CONF"}, + {{ 0x08, 0x02}, "DATA_IND"}, + {{ 0x40, 0x01}, "SELECT_B2_PROTOCOL_CONF"}, + {{ 0x80, 0x01}, "SELECT_B3_PROTOCOL_CONF"}, + {{ 0x81, 0x01}, "LISTEN_B3_CONF"}, + {{ 0x82, 0x01}, "CONNECT_B3_CONF"}, + {{ 0x82, 0x02}, "CONNECT_B3_IND"}, + {{ 0x83, 0x02}, "CONNECT_B3_ACTIVE_IND"}, + {{ 0x84, 0x01}, "DISCONNECT_B3_CONF"}, + {{ 0x84, 0x02}, "DISCONNECT_B3_IND"}, + {{ 0x85, 0x01}, "GET_B3_PARAMS_CONF"}, + {{ 0x01, 0x01}, "RESET_B3_CONF"}, + {{ 0x01, 0x02}, "RESET_B3_IND"}, + /* {{ 0x87, 0x02, "HANDSET_IND"}, not implemented */ + {{ 0xff, 0x01}, "MANUFACTURER_CONF"}, + {{ 0xff, 0x02}, "MANUFACTURER_IND"}, +#ifdef DEBUG_MSG + /* Requests */ + {{ 0x01, 0x00}, "RESET_B3_REQ"}, + {{ 0x02, 0x00}, "CONNECT_REQ"}, + {{ 0x04, 0x00}, "DISCONNECT_REQ"}, + {{ 0x05, 0x00}, "LISTEN_REQ"}, + {{ 0x06, 0x00}, "GET_PARAMS_REQ"}, + {{ 0x07, 0x00}, "INFO_REQ"}, + {{ 0x08, 0x00}, "DATA_REQ"}, + {{ 0x09, 0x00}, "CONNECT_INFO_REQ"}, + {{ 0x40, 0x00}, "SELECT_B2_PROTOCOL_REQ"}, + {{ 0x80, 0x00}, "SELECT_B3_PROTOCOL_REQ"}, + {{ 0x81, 0x00}, "LISTEN_B3_REQ"}, + {{ 0x82, 0x00}, "CONNECT_B3_REQ"}, + {{ 0x84, 0x00}, "DISCONNECT_B3_REQ"}, + {{ 0x85, 0x00}, "GET_B3_PARAMS_REQ"}, + {{ 0x86, 0x00}, "DATA_B3_REQ"}, + {{ 0xff, 0x00}, "MANUFACTURER_REQ"}, + /* Responses */ + {{ 0x01, 0x03}, "RESET_B3_RESP"}, + {{ 0x02, 0x03}, "CONNECT_RESP"}, + {{ 0x03, 0x03}, "CONNECT_ACTIVE_RESP"}, + {{ 0x04, 0x03}, "DISCONNECT_RESP"}, + {{ 0x07, 0x03}, "INFO_RESP"}, + {{ 0x08, 0x03}, "DATA_RESP"}, + {{ 0x82, 0x03}, "CONNECT_B3_RESP"}, + {{ 0x83, 0x03}, "CONNECT_B3_ACTIVE_RESP"}, + {{ 0x84, 0x03}, "DISCONNECT_B3_RESP"}, + {{ 0x86, 0x03}, "DATA_B3_RESP"}, + {{ 0xff, 0x03}, "MANUFACTURER_RESP"}, +#endif + {{ 0x00, 0x00}, NULL}, +}; +#define num_valid_imsg 27 /* MANUFACTURER_IND */ + +/* + * Check for a valid incoming CAPI message. + * Return: + * 0 = Invalid message + * 1 = Valid message, no B-Channel-data + * 2 = Valid message, B-Channel-data + */ +int +actcapi_chkhdr(act2000_card *card, actcapi_msghdr *hdr) +{ + int i; + + if (hdr->applicationID != 1) + return 0; + if (hdr->len < 9) + return 0; + for (i = 0; i < num_valid_imsg; i++) + if ((hdr->cmd.cmd == valid_msg[i].cmd.cmd) && + (hdr->cmd.subcmd == valid_msg[i].cmd.subcmd)) { + return (i ? 1 : 2); + } + return 0; +} + +#define ACTCAPI_MKHDR(l, c, s) { \ + skb = alloc_skb(l + 8, GFP_ATOMIC); \ + if (skb) { \ + m = (actcapi_msg *)skb_put(skb, l + 8); \ + m->hdr.len = l + 8; \ + m->hdr.applicationID = 1; \ + m->hdr.cmd.cmd = c; \ + m->hdr.cmd.subcmd = s; \ + m->hdr.msgnum = actcapi_nextsmsg(card); \ + } else m = NULL; \ + } + +#define ACTCAPI_CHKSKB if (!skb) { \ + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); \ + return; \ + } + +#define ACTCAPI_QUEUE_TX { \ + actcapi_debug_msg(skb, 1); \ + skb_queue_tail(&card->sndq, skb); \ + act2000_schedule_tx(card); \ + } + +int +actcapi_listen_req(act2000_card *card) +{ + __u16 eazmask = 0; + int i; + actcapi_msg *m; + struct sk_buff *skb; + + for (i = 0; i < ACT2000_BCH; i++) + eazmask |= card->bch[i].eazmask; + ACTCAPI_MKHDR(9, 0x05, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.listen_req.controller = 0; + m->msg.listen_req.infomask = 0x3f; /* All information */ + m->msg.listen_req.eazmask = eazmask; + m->msg.listen_req.simask = (eazmask) ? 0x86 : 0; /* All SI's */ + ACTCAPI_QUEUE_TX; + return 0; +} + +int +actcapi_connect_req(act2000_card *card, act2000_chan *chan, char *phone, + char eaz, int si1, int si2) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR((11 + strlen(phone)), 0x02, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + chan->fsm_state = ACT2000_STATE_NULL; + return -ENOMEM; + } + m->msg.connect_req.controller = 0; + m->msg.connect_req.bchan = 0x83; + m->msg.connect_req.infomask = 0x3f; + m->msg.connect_req.si1 = si1; + m->msg.connect_req.si2 = si2; + m->msg.connect_req.eaz = eaz ? eaz : '0'; + m->msg.connect_req.addr.len = strlen(phone) + 1; + m->msg.connect_req.addr.tnp = 0x81; + memcpy(m->msg.connect_req.addr.num, phone, strlen(phone)); + chan->callref = m->hdr.msgnum; + ACTCAPI_QUEUE_TX; + return 0; +} + +static void +actcapi_connect_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x82, 0x00); + ACTCAPI_CHKSKB; + m->msg.connect_b3_req.plci = chan->plci; + memset(&m->msg.connect_b3_req.ncpi, 0, + sizeof(m->msg.connect_b3_req.ncpi)); + m->msg.connect_b3_req.ncpi.len = 13; + m->msg.connect_b3_req.ncpi.modulo = 8; + ACTCAPI_QUEUE_TX; +} + +/* + * Set net type (1TR6) or (EDSS1) + */ +int +actcapi_manufacturer_req_net(act2000_card *card) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(5, 0xff, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_net.manuf_msg = 0x11; + m->msg.manufacturer_req_net.controller = 1; + m->msg.manufacturer_req_net.nettype = (card->ptype == ISDN_PTYPE_EURO) ? 1 : 0; + ACTCAPI_QUEUE_TX; + printk(KERN_INFO "act2000 %s: D-channel protocol now %s\n", + card->interface.id, (card->ptype == ISDN_PTYPE_EURO) ? "euro" : "1tr6"); + card->interface.features &= + ~(ISDN_FEATURE_P_UNKNOWN | ISDN_FEATURE_P_EURO | ISDN_FEATURE_P_1TR6); + card->interface.features |= + ((card->ptype == ISDN_PTYPE_EURO) ? ISDN_FEATURE_P_EURO : ISDN_FEATURE_P_1TR6); + return 0; +} + +/* + * Switch V.42 on or off + */ +#if 0 +int +actcapi_manufacturer_req_v42(act2000_card *card, ulong arg) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(8, 0xff, 0x00); + if (!skb) { + + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_v42.manuf_msg = 0x10; + m->msg.manufacturer_req_v42.controller = 0; + m->msg.manufacturer_req_v42.v42control = (arg ? 1 : 0); + ACTCAPI_QUEUE_TX; + return 0; +} +#endif /* 0 */ + +/* + * Set error-handler + */ +int +actcapi_manufacturer_req_errh(act2000_card *card) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(4, 0xff, 0x00); + if (!skb) { + + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_err.manuf_msg = 0x03; + m->msg.manufacturer_req_err.controller = 0; + ACTCAPI_QUEUE_TX; + return 0; +} + +/* + * Set MSN-Mapping. + */ +int +actcapi_manufacturer_req_msn(act2000_card *card) +{ + msn_entry *p = card->msn_list; + actcapi_msg *m; + struct sk_buff *skb; + int len; + + while (p) { + int i; + + len = strlen(p->msn); + for (i = 0; i < 2; i++) { + ACTCAPI_MKHDR(6 + len, 0xff, 0x00); + if (!skb) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return -ENOMEM; + } + m->msg.manufacturer_req_msn.manuf_msg = 0x13 + i; + m->msg.manufacturer_req_msn.controller = 0; + m->msg.manufacturer_req_msn.msnmap.eaz = p->eaz; + m->msg.manufacturer_req_msn.msnmap.len = len; + memcpy(m->msg.manufacturer_req_msn.msnmap.msn, p->msn, len); + ACTCAPI_QUEUE_TX; + } + p = p->next; + } + return 0; +} + +void +actcapi_select_b2_protocol_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(10, 0x40, 0x00); + ACTCAPI_CHKSKB; + m->msg.select_b2_protocol_req.plci = chan->plci; + memset(&m->msg.select_b2_protocol_req.dlpd, 0, + sizeof(m->msg.select_b2_protocol_req.dlpd)); + m->msg.select_b2_protocol_req.dlpd.len = 6; + switch (chan->l2prot) { + case ISDN_PROTO_L2_TRANS: + m->msg.select_b2_protocol_req.protocol = 0x03; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + break; + case ISDN_PROTO_L2_HDLC: + m->msg.select_b2_protocol_req.protocol = 0x02; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + break; + case ISDN_PROTO_L2_X75I: + case ISDN_PROTO_L2_X75UI: + case ISDN_PROTO_L2_X75BUI: + m->msg.select_b2_protocol_req.protocol = 0x01; + m->msg.select_b2_protocol_req.dlpd.dlen = 4000; + m->msg.select_b2_protocol_req.dlpd.laa = 3; + m->msg.select_b2_protocol_req.dlpd.lab = 1; + m->msg.select_b2_protocol_req.dlpd.win = 7; + m->msg.select_b2_protocol_req.dlpd.modulo = 8; + break; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_select_b3_protocol_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x80, 0x00); + ACTCAPI_CHKSKB; + m->msg.select_b3_protocol_req.plci = chan->plci; + memset(&m->msg.select_b3_protocol_req.ncpd, 0, + sizeof(m->msg.select_b3_protocol_req.ncpd)); + switch (chan->l3prot) { + case ISDN_PROTO_L3_TRANS: + m->msg.select_b3_protocol_req.protocol = 0x04; + m->msg.select_b3_protocol_req.ncpd.len = 13; + m->msg.select_b3_protocol_req.ncpd.modulo = 8; + break; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_listen_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x81, 0x00); + ACTCAPI_CHKSKB; + m->msg.listen_b3_req.plci = chan->plci; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(3, 0x04, 0x00); + ACTCAPI_CHKSKB; + m->msg.disconnect_req.plci = chan->plci; + m->msg.disconnect_req.cause = 0; + ACTCAPI_QUEUE_TX; +} + +void +actcapi_disconnect_b3_req(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(17, 0x84, 0x00); + ACTCAPI_CHKSKB; + m->msg.disconnect_b3_req.ncci = chan->ncci; + memset(&m->msg.disconnect_b3_req.ncpi, 0, + sizeof(m->msg.disconnect_b3_req.ncpi)); + m->msg.disconnect_b3_req.ncpi.len = 13; + m->msg.disconnect_b3_req.ncpi.modulo = 8; + chan->fsm_state = ACT2000_STATE_BHWAIT; + ACTCAPI_QUEUE_TX; +} + +void +actcapi_connect_resp(act2000_card *card, act2000_chan *chan, __u8 cause) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(3, 0x02, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_resp.plci = chan->plci; + m->msg.connect_resp.rejectcause = cause; + if (cause) { + chan->fsm_state = ACT2000_STATE_NULL; + chan->plci = 0x8000; + } else + chan->fsm_state = ACT2000_STATE_IWAIT; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_active_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x03, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_resp.plci = chan->plci; + if (chan->fsm_state == ACT2000_STATE_IWAIT) + chan->fsm_state = ACT2000_STATE_IBWAIT; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_b3_resp(act2000_card *card, act2000_chan *chan, __u8 rejectcause) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR((rejectcause ? 3 : 17), 0x82, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_b3_resp.ncci = chan->ncci; + m->msg.connect_b3_resp.rejectcause = rejectcause; + if (!rejectcause) { + memset(&m->msg.connect_b3_resp.ncpi, 0, + sizeof(m->msg.connect_b3_resp.ncpi)); + m->msg.connect_b3_resp.ncpi.len = 13; + m->msg.connect_b3_resp.ncpi.modulo = 8; + chan->fsm_state = ACT2000_STATE_BWAIT; + } + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_connect_b3_active_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x83, 0x03); + ACTCAPI_CHKSKB; + m->msg.connect_b3_active_resp.ncci = chan->ncci; + chan->fsm_state = ACT2000_STATE_ACTIVE; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_info_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x07, 0x03); + ACTCAPI_CHKSKB; + m->msg.info_resp.plci = chan->plci; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_b3_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x84, 0x03); + ACTCAPI_CHKSKB; + m->msg.disconnect_b3_resp.ncci = chan->ncci; + chan->ncci = 0x8000; + chan->queued = 0; + ACTCAPI_QUEUE_TX; +} + +static void +actcapi_disconnect_resp(act2000_card *card, act2000_chan *chan) +{ + actcapi_msg *m; + struct sk_buff *skb; + + ACTCAPI_MKHDR(2, 0x04, 0x03); + ACTCAPI_CHKSKB; + m->msg.disconnect_resp.plci = chan->plci; + chan->plci = 0x8000; + ACTCAPI_QUEUE_TX; +} + +static int +new_plci(act2000_card *card, __u16 plci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].plci == 0x8000) { + card->bch[i].plci = plci; + return i; + } + return -1; +} + +static int +find_plci(act2000_card *card, __u16 plci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].plci == plci) + return i; + return -1; +} + +static int +find_ncci(act2000_card *card, __u16 ncci) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if (card->bch[i].ncci == ncci) + return i; + return -1; +} + +static int +find_dialing(act2000_card *card, __u16 callref) +{ + int i; + for (i = 0; i < ACT2000_BCH; i++) + if ((card->bch[i].callref == callref) && + (card->bch[i].fsm_state == ACT2000_STATE_OCALL)) + return i; + return -1; +} + +static int +actcapi_data_b3_ind(act2000_card *card, struct sk_buff *skb) { + __u16 plci; + __u16 ncci; + __u16 controller; + __u8 blocknr; + int chan; + actcapi_msg *msg = (actcapi_msg *)skb->data; + + EVAL_NCCI(msg->msg.data_b3_ind.fakencci, plci, controller, ncci); + chan = find_ncci(card, ncci); + if (chan < 0) + return 0; + if (card->bch[chan].fsm_state != ACT2000_STATE_ACTIVE) + return 0; + if (card->bch[chan].plci != plci) + return 0; + blocknr = msg->msg.data_b3_ind.blocknr; + skb_pull(skb, 19); + card->interface.rcvcallb_skb(card->myid, chan, skb); + if (!(skb = alloc_skb(11, GFP_ATOMIC))) { + printk(KERN_WARNING "actcapi: alloc_skb failed\n"); + return 1; + } + msg = (actcapi_msg *)skb_put(skb, 11); + msg->hdr.len = 11; + msg->hdr.applicationID = 1; + msg->hdr.cmd.cmd = 0x86; + msg->hdr.cmd.subcmd = 0x03; + msg->hdr.msgnum = actcapi_nextsmsg(card); + msg->msg.data_b3_resp.ncci = ncci; + msg->msg.data_b3_resp.blocknr = blocknr; + ACTCAPI_QUEUE_TX; + return 1; +} + +/* + * Walk over ackq, unlink DATA_B3_REQ from it, if + * ncci and blocknr are matching. + * Decrement queued-bytes counter. + */ +static int +handle_ack(act2000_card *card, act2000_chan *chan, __u8 blocknr) { + unsigned long flags; + struct sk_buff *skb; + struct sk_buff *tmp; + struct actcapi_msg *m; + int ret = 0; + + spin_lock_irqsave(&card->lock, flags); + skb = skb_peek(&card->ackq); + spin_unlock_irqrestore(&card->lock, flags); + if (!skb) { + printk(KERN_WARNING "act2000: handle_ack nothing found!\n"); + return 0; + } + tmp = skb; + while (1) { + m = (actcapi_msg *)tmp->data; + if ((((m->msg.data_b3_req.fakencci >> 8) & 0xff) == chan->ncci) && + (m->msg.data_b3_req.blocknr == blocknr)) { + /* found corresponding DATA_B3_REQ */ + skb_unlink(tmp, &card->ackq); + chan->queued -= m->msg.data_b3_req.datalen; + if (m->msg.data_b3_req.flags) + ret = m->msg.data_b3_req.datalen; + dev_kfree_skb(tmp); + if (chan->queued < 0) + chan->queued = 0; + return ret; + } + spin_lock_irqsave(&card->lock, flags); + tmp = skb_peek((struct sk_buff_head *)tmp); + spin_unlock_irqrestore(&card->lock, flags); + if ((tmp == skb) || (tmp == NULL)) { + /* reached end of queue */ + printk(KERN_WARNING "act2000: handle_ack nothing found!\n"); + return 0; + } + } +} + +void +actcapi_dispatch(struct work_struct *work) +{ + struct act2000_card *card = + container_of(work, struct act2000_card, rcv_tq); + struct sk_buff *skb; + actcapi_msg *msg; + __u16 ccmd; + int chan; + int len; + act2000_chan *ctmp; + isdn_ctrl cmd; + char tmp[170]; + + while ((skb = skb_dequeue(&card->rcvq))) { + actcapi_debug_msg(skb, 0); + msg = (actcapi_msg *)skb->data; + ccmd = ((msg->hdr.cmd.cmd << 8) | msg->hdr.cmd.subcmd); + switch (ccmd) { + case 0x8602: + /* DATA_B3_IND */ + if (actcapi_data_b3_ind(card, skb)) + return; + break; + case 0x8601: + /* DATA_B3_CONF */ + chan = find_ncci(card, msg->msg.data_b3_conf.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_ACTIVE)) { + if (msg->msg.data_b3_conf.info != 0) + printk(KERN_WARNING "act2000: DATA_B3_CONF: %04x\n", + msg->msg.data_b3_conf.info); + len = handle_ack(card, &card->bch[chan], + msg->msg.data_b3_conf.blocknr); + if (len) { + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BSENT; + cmd.arg = chan; + cmd.parm.length = len; + card->interface.statcallb(&cmd); + } + } + break; + case 0x0201: + /* CONNECT_CONF */ + chan = find_dialing(card, msg->hdr.msgnum); + if (chan >= 0) { + if (msg->msg.connect_conf.info) { + card->bch[chan].fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + card->bch[chan].fsm_state = ACT2000_STATE_OWAIT; + card->bch[chan].plci = msg->msg.connect_conf.plci; + } + } + break; + case 0x0202: + /* CONNECT_IND */ + chan = new_plci(card, msg->msg.connect_ind.plci); + if (chan < 0) { + ctmp = (act2000_chan *)tmp; + ctmp->plci = msg->msg.connect_ind.plci; + actcapi_connect_resp(card, ctmp, 0x11); /* All Card-Cannels busy */ + } else { + card->bch[chan].fsm_state = ACT2000_STATE_ICALL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_ICALL; + cmd.arg = chan; + cmd.parm.setup.si1 = msg->msg.connect_ind.si1; + cmd.parm.setup.si2 = msg->msg.connect_ind.si2; + if (card->ptype == ISDN_PTYPE_EURO) + strcpy(cmd.parm.setup.eazmsn, + act2000_find_eaz(card, msg->msg.connect_ind.eaz)); + else { + cmd.parm.setup.eazmsn[0] = msg->msg.connect_ind.eaz; + cmd.parm.setup.eazmsn[1] = 0; + } + memset(cmd.parm.setup.phone, 0, sizeof(cmd.parm.setup.phone)); + memcpy(cmd.parm.setup.phone, msg->msg.connect_ind.addr.num, + msg->msg.connect_ind.addr.len - 1); + cmd.parm.setup.plan = msg->msg.connect_ind.addr.tnp; + cmd.parm.setup.screen = 0; + if (card->interface.statcallb(&cmd) == 2) + actcapi_connect_resp(card, &card->bch[chan], 0x15); /* Reject Call */ + } + break; + case 0x0302: + /* CONNECT_ACTIVE_IND */ + chan = find_plci(card, msg->msg.connect_active_ind.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_IWAIT: + actcapi_connect_active_resp(card, &card->bch[chan]); + break; + case ACT2000_STATE_OWAIT: + actcapi_connect_active_resp(card, &card->bch[chan]); + actcapi_select_b2_protocol_req(card, &card->bch[chan]); + break; + } + break; + case 0x8202: + /* CONNECT_B3_IND */ + chan = find_plci(card, msg->msg.connect_b3_ind.plci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_IBWAIT)) { + card->bch[chan].ncci = msg->msg.connect_b3_ind.ncci; + actcapi_connect_b3_resp(card, &card->bch[chan], 0); + } else { + ctmp = (act2000_chan *)tmp; + ctmp->ncci = msg->msg.connect_b3_ind.ncci; + actcapi_connect_b3_resp(card, ctmp, 0x11); /* All Card-Cannels busy */ + } + break; + case 0x8302: + /* CONNECT_B3_ACTIVE_IND */ + chan = find_ncci(card, msg->msg.connect_b3_active_ind.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BWAIT)) { + actcapi_connect_b3_active_resp(card, &card->bch[chan]); + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BCONN; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + case 0x8402: + /* DISCONNECT_B3_IND */ + chan = find_ncci(card, msg->msg.disconnect_b3_ind.ncci); + if (chan >= 0) { + ctmp = &card->bch[chan]; + actcapi_disconnect_b3_resp(card, ctmp); + switch (ctmp->fsm_state) { + case ACT2000_STATE_ACTIVE: + ctmp->fsm_state = ACT2000_STATE_DHWAIT2; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + break; + case ACT2000_STATE_BHWAIT2: + actcapi_disconnect_req(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_DHWAIT; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_BHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + break; + } + } + break; + case 0x0402: + /* DISCONNECT_IND */ + chan = find_plci(card, msg->msg.disconnect_ind.plci); + if (chan >= 0) { + ctmp = &card->bch[chan]; + actcapi_disconnect_resp(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp = (act2000_chan *)tmp; + ctmp->plci = msg->msg.disconnect_ind.plci; + actcapi_disconnect_resp(card, ctmp); + } + break; + case 0x4001: + /* SELECT_B2_PROTOCOL_CONF */ + chan = find_plci(card, msg->msg.select_b2_protocol_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.select_b2_protocol_conf.info == 0) + actcapi_select_b3_protocol_req(card, ctmp); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + } + break; + case 0x8001: + /* SELECT_B3_PROTOCOL_CONF */ + chan = find_plci(card, msg->msg.select_b3_protocol_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.select_b3_protocol_conf.info == 0) + actcapi_listen_b3_req(card, ctmp); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + } + break; + case 0x8101: + /* LISTEN_B3_CONF */ + chan = find_plci(card, msg->msg.listen_b3_conf.plci); + if (chan >= 0) + switch (card->bch[chan].fsm_state) { + case ACT2000_STATE_ICALL: + ctmp = &card->bch[chan]; + if (msg->msg.listen_b3_conf.info == 0) + actcapi_connect_resp(card, ctmp, 0); + else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + case ACT2000_STATE_OWAIT: + ctmp = &card->bch[chan]; + if (msg->msg.listen_b3_conf.info == 0) { + actcapi_connect_b3_req(card, ctmp); + ctmp->fsm_state = ACT2000_STATE_OBWAIT; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DCONN; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } + break; + } + break; + case 0x8201: + /* CONNECT_B3_CONF */ + chan = find_plci(card, msg->msg.connect_b3_conf.plci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_OBWAIT)) { + ctmp = &card->bch[chan]; + if (msg->msg.connect_b3_conf.info) { + ctmp->fsm_state = ACT2000_STATE_NULL; + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg = chan; + card->interface.statcallb(&cmd); + } else { + ctmp->ncci = msg->msg.connect_b3_conf.ncci; + ctmp->fsm_state = ACT2000_STATE_BWAIT; + } + } + break; + case 0x8401: + /* DISCONNECT_B3_CONF */ + chan = find_ncci(card, msg->msg.disconnect_b3_conf.ncci); + if ((chan >= 0) && (card->bch[chan].fsm_state == ACT2000_STATE_BHWAIT)) + card->bch[chan].fsm_state = ACT2000_STATE_BHWAIT2; + break; + case 0x0702: + /* INFO_IND */ + chan = find_plci(card, msg->msg.info_ind.plci); + if (chan >= 0) + /* TODO: Eval Charging info / cause */ + actcapi_info_resp(card, &card->bch[chan]); + break; + case 0x0401: + /* LISTEN_CONF */ + case 0x0501: + /* LISTEN_CONF */ + case 0xff01: + /* MANUFACTURER_CONF */ + break; + case 0xff02: + /* MANUFACTURER_IND */ + if (msg->msg.manuf_msg == 3) { + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, + &msg->msg.manufacturer_ind_err.errstring, + msg->hdr.len - 16); + if (msg->msg.manufacturer_ind_err.errcode) + printk(KERN_WARNING "act2000: %s\n", tmp); + else { + printk(KERN_DEBUG "act2000: %s\n", tmp); + if ((!strncmp(tmp, "INFO: Trace buffer con", 22)) || + (!strncmp(tmp, "INFO: Compile Date/Tim", 22))) { + card->flags |= ACT2000_FLAGS_RUNNING; + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + actcapi_manufacturer_req_net(card); + actcapi_manufacturer_req_msn(card); + actcapi_listen_req(card); + card->interface.statcallb(&cmd); + } + } + } + break; + default: + printk(KERN_WARNING "act2000: UNHANDLED Message %04x\n", ccmd); + break; + } + dev_kfree_skb(skb); + } +} + +#ifdef DEBUG_MSG +static void +actcapi_debug_caddr(actcapi_addr *addr) +{ + char tmp[30]; + + printk(KERN_DEBUG " Alen = %d\n", addr->len); + if (addr->len > 0) + printk(KERN_DEBUG " Atnp = 0x%02x\n", addr->tnp); + if (addr->len > 1) { + memset(tmp, 0, 30); + memcpy(tmp, addr->num, addr->len - 1); + printk(KERN_DEBUG " Anum = '%s'\n", tmp); + } +} + +static void +actcapi_debug_ncpi(actcapi_ncpi *ncpi) +{ + printk(KERN_DEBUG " ncpi.len = %d\n", ncpi->len); + if (ncpi->len >= 2) + printk(KERN_DEBUG " ncpi.lic = 0x%04x\n", ncpi->lic); + if (ncpi->len >= 4) + printk(KERN_DEBUG " ncpi.hic = 0x%04x\n", ncpi->hic); + if (ncpi->len >= 6) + printk(KERN_DEBUG " ncpi.ltc = 0x%04x\n", ncpi->ltc); + if (ncpi->len >= 8) + printk(KERN_DEBUG " ncpi.htc = 0x%04x\n", ncpi->htc); + if (ncpi->len >= 10) + printk(KERN_DEBUG " ncpi.loc = 0x%04x\n", ncpi->loc); + if (ncpi->len >= 12) + printk(KERN_DEBUG " ncpi.hoc = 0x%04x\n", ncpi->hoc); + if (ncpi->len >= 13) + printk(KERN_DEBUG " ncpi.mod = %d\n", ncpi->modulo); +} + +static void +actcapi_debug_dlpd(actcapi_dlpd *dlpd) +{ + printk(KERN_DEBUG " dlpd.len = %d\n", dlpd->len); + if (dlpd->len >= 2) + printk(KERN_DEBUG " dlpd.dlen = 0x%04x\n", dlpd->dlen); + if (dlpd->len >= 3) + printk(KERN_DEBUG " dlpd.laa = 0x%02x\n", dlpd->laa); + if (dlpd->len >= 4) + printk(KERN_DEBUG " dlpd.lab = 0x%02x\n", dlpd->lab); + if (dlpd->len >= 5) + printk(KERN_DEBUG " dlpd.modulo = %d\n", dlpd->modulo); + if (dlpd->len >= 6) + printk(KERN_DEBUG " dlpd.win = %d\n", dlpd->win); +} + +#ifdef DEBUG_DUMP_SKB +static void dump_skb(struct sk_buff *skb) { + char tmp[80]; + char *p = skb->data; + char *t = tmp; + int i; + + for (i = 0; i < skb->len; i++) { + t += sprintf(t, "%02x ", *p++ & 0xff); + if ((i & 0x0f) == 8) { + printk(KERN_DEBUG "dump: %s\n", tmp); + t = tmp; + } + } + if (i & 0x07) + printk(KERN_DEBUG "dump: %s\n", tmp); +} +#endif + +void +actcapi_debug_msg(struct sk_buff *skb, int direction) +{ + actcapi_msg *msg = (actcapi_msg *)skb->data; + char *descr; + int i; + char tmp[170]; + +#ifndef DEBUG_DATA_MSG + if (msg->hdr.cmd.cmd == 0x86) + return; +#endif + descr = "INVALID"; +#ifdef DEBUG_DUMP_SKB + dump_skb(skb); +#endif + for (i = 0; i < ARRAY_SIZE(valid_msg); i++) + if ((msg->hdr.cmd.cmd == valid_msg[i].cmd.cmd) && + (msg->hdr.cmd.subcmd == valid_msg[i].cmd.subcmd)) { + descr = valid_msg[i].description; + break; + } + printk(KERN_DEBUG "%s %s msg\n", direction ? "Outgoing" : "Incoming", descr); + printk(KERN_DEBUG " ApplID = %d\n", msg->hdr.applicationID); + printk(KERN_DEBUG " Len = %d\n", msg->hdr.len); + printk(KERN_DEBUG " MsgNum = 0x%04x\n", msg->hdr.msgnum); + printk(KERN_DEBUG " Cmd = 0x%02x\n", msg->hdr.cmd.cmd); + printk(KERN_DEBUG " SubCmd = 0x%02x\n", msg->hdr.cmd.subcmd); + switch (i) { + case 0: + /* DATA B3 IND */ + printk(KERN_DEBUG " BLOCK = 0x%02x\n", + msg->msg.data_b3_ind.blocknr); + break; + case 2: + /* CONNECT CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.connect_conf.info); + break; + case 3: + /* CONNECT IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_ind.plci); + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.connect_ind.controller); + printk(KERN_DEBUG " SI1 = %d\n", + msg->msg.connect_ind.si1); + printk(KERN_DEBUG " SI2 = %d\n", + msg->msg.connect_ind.si2); + printk(KERN_DEBUG " EAZ = '%c'\n", + msg->msg.connect_ind.eaz); + actcapi_debug_caddr(&msg->msg.connect_ind.addr); + break; + case 5: + /* CONNECT ACTIVE IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_active_ind.plci); + actcapi_debug_caddr(&msg->msg.connect_active_ind.addr); + break; + case 8: + /* LISTEN CONF */ + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.listen_conf.controller); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.listen_conf.info); + break; + case 11: + /* INFO IND */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.info_ind.plci); + printk(KERN_DEBUG " Imsk = 0x%04x\n", + msg->msg.info_ind.nr.mask); + if (msg->hdr.len > 12) { + int l = msg->hdr.len - 12; + int j; + char *p = tmp; + for (j = 0; j < l; j++) + p += sprintf(p, "%02x ", msg->msg.info_ind.el.display[j]); + printk(KERN_DEBUG " D = '%s'\n", tmp); + } + break; + case 14: + /* SELECT B2 PROTOCOL CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b2_protocol_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.select_b2_protocol_conf.info); + break; + case 15: + /* SELECT B3 PROTOCOL CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b3_protocol_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.select_b3_protocol_conf.info); + break; + case 16: + /* LISTEN B3 CONF */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.listen_b3_conf.plci); + printk(KERN_DEBUG " Info = 0x%04x\n", + msg->msg.listen_b3_conf.info); + break; + case 18: + /* CONNECT B3 IND */ + printk(KERN_DEBUG " NCCI = 0x%04x\n", + msg->msg.connect_b3_ind.ncci); + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_b3_ind.plci); + actcapi_debug_ncpi(&msg->msg.connect_b3_ind.ncpi); + break; + case 19: + /* CONNECT B3 ACTIVE IND */ + printk(KERN_DEBUG " NCCI = 0x%04x\n", + msg->msg.connect_b3_active_ind.ncci); + actcapi_debug_ncpi(&msg->msg.connect_b3_active_ind.ncpi); + break; + case 26: + /* MANUFACTURER IND */ + printk(KERN_DEBUG " Mmsg = 0x%02x\n", + msg->msg.manufacturer_ind_err.manuf_msg); + switch (msg->msg.manufacturer_ind_err.manuf_msg) { + case 3: + printk(KERN_DEBUG " Contr = %d\n", + msg->msg.manufacturer_ind_err.controller); + printk(KERN_DEBUG " Code = 0x%08x\n", + msg->msg.manufacturer_ind_err.errcode); + memset(tmp, 0, sizeof(tmp)); + strncpy(tmp, &msg->msg.manufacturer_ind_err.errstring, + msg->hdr.len - 16); + printk(KERN_DEBUG " Emsg = '%s'\n", tmp); + break; + } + break; + case 30: + /* LISTEN REQ */ + printk(KERN_DEBUG " Imsk = 0x%08x\n", + msg->msg.listen_req.infomask); + printk(KERN_DEBUG " Emsk = 0x%04x\n", + msg->msg.listen_req.eazmask); + printk(KERN_DEBUG " Smsk = 0x%04x\n", + msg->msg.listen_req.simask); + break; + case 35: + /* SELECT_B2_PROTOCOL_REQ */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.select_b2_protocol_req.plci); + printk(KERN_DEBUG " prot = 0x%02x\n", + msg->msg.select_b2_protocol_req.protocol); + if (msg->hdr.len >= 11) + printk(KERN_DEBUG "No dlpd\n"); + else + actcapi_debug_dlpd(&msg->msg.select_b2_protocol_req.dlpd); + break; + case 44: + /* CONNECT RESP */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_resp.plci); + printk(KERN_DEBUG " CAUSE = 0x%02x\n", + msg->msg.connect_resp.rejectcause); + break; + case 45: + /* CONNECT ACTIVE RESP */ + printk(KERN_DEBUG " PLCI = 0x%04x\n", + msg->msg.connect_active_resp.plci); + break; + } +} +#endif diff --git a/drivers/staging/i4l/act2000/capi.h b/drivers/staging/i4l/act2000/capi.h new file mode 100644 index 000000000..01ccdecd4 --- /dev/null +++ b/drivers/staging/i4l/act2000/capi.h @@ -0,0 +1,365 @@ +/* $Id: capi.h,v 1.6.6.2 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#ifndef CAPI_H +#define CAPI_H + +/* Command-part of a CAPI message */ +typedef struct actcapi_msgcmd { + __u8 cmd; + __u8 subcmd; +} actcapi_msgcmd; + +/* CAPI message header */ +typedef struct actcapi_msghdr { + __u16 len; + __u16 applicationID; + actcapi_msgcmd cmd; + __u16 msgnum; +} actcapi_msghdr; + +/* CAPI message description (for debugging) */ +typedef struct actcapi_msgdsc { + actcapi_msgcmd cmd; + char *description; +} actcapi_msgdsc; + +/* CAPI Address */ +typedef struct actcapi_addr { + __u8 len; /* Length of element */ + __u8 tnp; /* Type/Numbering Plan */ + __u8 num[20]; /* Caller ID */ +} actcapi_addr; + +/* CAPI INFO element mask */ +typedef union actcapi_infonr { /* info number */ + __u16 mask; /* info-mask field */ + struct bmask { /* bit definitions */ + unsigned codes:3; /* code set */ + unsigned rsvd:5; /* reserved */ + unsigned svind:1; /* single, variable length ind. */ + unsigned wtype:7; /* W-element type */ + } bmask; +} actcapi_infonr; + +/* CAPI INFO element */ +typedef union actcapi_infoel { /* info element */ + __u8 len; /* length of info element */ + __u8 display[40]; /* display contents */ + __u8 uuinfo[40]; /* User-user info field */ + struct cause { /* Cause information */ + unsigned ext2:1; /* extension */ + unsigned cod:2; /* coding standard */ + unsigned spare:1; /* spare */ + unsigned loc:4; /* location */ + unsigned ext1:1; /* extension */ + unsigned cval:7; /* Cause value */ + } cause; + struct charge { /* Charging information */ + __u8 toc; /* type of charging info */ + __u8 unit[10]; /* charging units */ + } charge; + __u8 date[20]; /* date fields */ + __u8 stat; /* state of remote party */ +} actcapi_infoel; + +/* Message for EAZ<->MSN Mapping */ +typedef struct actcapi_msn { + __u8 eaz; + __u8 len; /* Length of MSN */ + __u8 msn[15]; +} __attribute__((packed)) actcapi_msn; + +typedef struct actcapi_dlpd { + __u8 len; /* Length of structure */ + __u16 dlen; /* Data Length */ + __u8 laa; /* Link Address A */ + __u8 lab; /* Link Address B */ + __u8 modulo; /* Modulo Mode */ + __u8 win; /* Window size */ + __u8 xid[100]; /* XID Information */ +} __attribute__((packed)) actcapi_dlpd; + +typedef struct actcapi_ncpd { + __u8 len; /* Length of structure */ + __u16 lic; + __u16 hic; + __u16 ltc; + __u16 htc; + __u16 loc; + __u16 hoc; + __u8 modulo; +} __attribute__((packed)) actcapi_ncpd; +#define actcapi_ncpi actcapi_ncpd + +/* + * Layout of NCCI field in a B3 DATA CAPI message is different from + * standard at act2000: + * + * Bit 0-4 = PLCI + * Bit 5-7 = Controller + * Bit 8-15 = NCCI + */ +#define MAKE_NCCI(plci, contr, ncci) \ + ((plci & 0x1f) | ((contr & 0x7) << 5) | ((ncci & 0xff) << 8)) + +#define EVAL_NCCI(fakencci, plci, contr, ncci) { \ + plci = fakencci & 0x1f; \ + contr = (fakencci >> 5) & 0x7; \ + ncci = (fakencci >> 8) & 0xff; \ + } + +/* + * Layout of PLCI field in a B3 DATA CAPI message is different from + * standard at act2000: + * + * Bit 0-4 = PLCI + * Bit 5-7 = Controller + * Bit 8-15 = reserved (must be 0) + */ +#define MAKE_PLCI(plci, contr) \ + ((plci & 0x1f) | ((contr & 0x7) << 5)) + +#define EVAL_PLCI(fakeplci, plci, contr) { \ + plci = fakeplci & 0x1f; \ + contr = (fakeplci >> 5) & 0x7; \ + } + +typedef struct actcapi_msg { + actcapi_msghdr hdr; + union { + __u16 manuf_msg; + struct manufacturer_req_net { + __u16 manuf_msg; + __u16 controller; + __u8 nettype; + } manufacturer_req_net; + struct manufacturer_req_v42 { + __u16 manuf_msg; + __u16 controller; + __u32 v42control; + } manufacturer_req_v42; + struct manufacturer_conf_v42 { + __u16 manuf_msg; + __u16 controller; + } manufacturer_conf_v42; + struct manufacturer_req_err { + __u16 manuf_msg; + __u16 controller; + } manufacturer_req_err; + struct manufacturer_ind_err { + __u16 manuf_msg; + __u16 controller; + __u32 errcode; + __u8 errstring; /* actually up to 160 */ + } manufacturer_ind_err; + struct manufacturer_req_msn { + __u16 manuf_msg; + __u16 controller; + actcapi_msn msnmap; + } __attribute ((packed)) manufacturer_req_msn; + /* TODO: TraceInit-req/conf/ind/resp and + * TraceDump-req/conf/ind/resp + */ + struct connect_req { + __u8 controller; + __u8 bchan; + __u32 infomask; + __u8 si1; + __u8 si2; + __u8 eaz; + actcapi_addr addr; + } __attribute__ ((packed)) connect_req; + struct connect_conf { + __u16 plci; + __u16 info; + } connect_conf; + struct connect_ind { + __u16 plci; + __u8 controller; + __u8 si1; + __u8 si2; + __u8 eaz; + actcapi_addr addr; + } __attribute__ ((packed)) connect_ind; + struct connect_resp { + __u16 plci; + __u8 rejectcause; + } connect_resp; + struct connect_active_ind { + __u16 plci; + actcapi_addr addr; + } __attribute__ ((packed)) connect_active_ind; + struct connect_active_resp { + __u16 plci; + } connect_active_resp; + struct connect_b3_req { + __u16 plci; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) connect_b3_req; + struct connect_b3_conf { + __u16 plci; + __u16 ncci; + __u16 info; + } connect_b3_conf; + struct connect_b3_ind { + __u16 ncci; + __u16 plci; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) connect_b3_ind; + struct connect_b3_resp { + __u16 ncci; + __u8 rejectcause; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) connect_b3_resp; + struct disconnect_req { + __u16 plci; + __u8 cause; + } disconnect_req; + struct disconnect_conf { + __u16 plci; + __u16 info; + } disconnect_conf; + struct disconnect_ind { + __u16 plci; + __u16 info; + } disconnect_ind; + struct disconnect_resp { + __u16 plci; + } disconnect_resp; + struct connect_b3_active_ind { + __u16 ncci; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) connect_b3_active_ind; + struct connect_b3_active_resp { + __u16 ncci; + } connect_b3_active_resp; + struct disconnect_b3_req { + __u16 ncci; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) disconnect_b3_req; + struct disconnect_b3_conf { + __u16 ncci; + __u16 info; + } disconnect_b3_conf; + struct disconnect_b3_ind { + __u16 ncci; + __u16 info; + actcapi_ncpi ncpi; + } __attribute__ ((packed)) disconnect_b3_ind; + struct disconnect_b3_resp { + __u16 ncci; + } disconnect_b3_resp; + struct info_ind { + __u16 plci; + actcapi_infonr nr; + actcapi_infoel el; + } __attribute__ ((packed)) info_ind; + struct info_resp { + __u16 plci; + } info_resp; + struct listen_b3_req { + __u16 plci; + } listen_b3_req; + struct listen_b3_conf { + __u16 plci; + __u16 info; + } listen_b3_conf; + struct select_b2_protocol_req { + __u16 plci; + __u8 protocol; + actcapi_dlpd dlpd; + } __attribute__ ((packed)) select_b2_protocol_req; + struct select_b2_protocol_conf { + __u16 plci; + __u16 info; + } select_b2_protocol_conf; + struct select_b3_protocol_req { + __u16 plci; + __u8 protocol; + actcapi_ncpd ncpd; + } __attribute__ ((packed)) select_b3_protocol_req; + struct select_b3_protocol_conf { + __u16 plci; + __u16 info; + } select_b3_protocol_conf; + struct listen_req { + __u8 controller; + __u32 infomask; + __u16 eazmask; + __u16 simask; + } __attribute__ ((packed)) listen_req; + struct listen_conf { + __u8 controller; + __u16 info; + } __attribute__ ((packed)) listen_conf; + struct data_b3_req { + __u16 fakencci; + __u16 datalen; + __u32 unused; + __u8 blocknr; + __u16 flags; + } __attribute ((packed)) data_b3_req; + struct data_b3_ind { + __u16 fakencci; + __u16 datalen; + __u32 unused; + __u8 blocknr; + __u16 flags; + } __attribute__ ((packed)) data_b3_ind; + struct data_b3_resp { + __u16 ncci; + __u8 blocknr; + } __attribute__ ((packed)) data_b3_resp; + struct data_b3_conf { + __u16 ncci; + __u8 blocknr; + __u16 info; + } __attribute__ ((packed)) data_b3_conf; + } msg; +} __attribute__ ((packed)) actcapi_msg; + +static inline unsigned short +actcapi_nextsmsg(act2000_card *card) +{ + unsigned long flags; + unsigned short n; + + spin_lock_irqsave(&card->mnlock, flags); + n = card->msgnum; + card->msgnum++; + card->msgnum &= 0x7fff; + spin_unlock_irqrestore(&card->mnlock, flags); + return n; +} +#define DEBUG_MSG +#undef DEBUG_DATA_MSG +#undef DEBUG_DUMP_SKB + +extern int actcapi_chkhdr(act2000_card *, actcapi_msghdr *); +extern int actcapi_listen_req(act2000_card *); +extern int actcapi_manufacturer_req_net(act2000_card *); +extern int actcapi_manufacturer_req_errh(act2000_card *); +extern int actcapi_manufacturer_req_msn(act2000_card *); +extern int actcapi_connect_req(act2000_card *, act2000_chan *, char *, char, int, int); +extern void actcapi_select_b2_protocol_req(act2000_card *, act2000_chan *); +extern void actcapi_disconnect_b3_req(act2000_card *, act2000_chan *); +extern void actcapi_connect_resp(act2000_card *, act2000_chan *, __u8); +extern void actcapi_dispatch(struct work_struct *); +#ifdef DEBUG_MSG +extern void actcapi_debug_msg(struct sk_buff *skb, int); +#else +#define actcapi_debug_msg(skb, len) +#endif +#endif diff --git a/drivers/staging/i4l/act2000/module.c b/drivers/staging/i4l/act2000/module.c new file mode 100644 index 000000000..68073d0da --- /dev/null +++ b/drivers/staging/i4l/act2000/module.c @@ -0,0 +1,813 @@ +/* $Id: module.c,v 1.14.6.4 2001/09/23 22:24:32 kai Exp $ + * + * ISDN lowlevel-module for the IBM ISDN-S0 Active 2000. + * + * Author Fritz Elfert + * Copyright 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. + * + * Thanks to Friedemann Baitinger and IBM Germany + * + */ + +#include "act2000.h" +#include "act2000_isa.h" +#include "capi.h" +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/init.h> + +static unsigned short act2000_isa_ports[] = +{ + 0x0200, 0x0240, 0x0280, 0x02c0, 0x0300, 0x0340, 0x0380, + 0xcfe0, 0xcfa0, 0xcf60, 0xcf20, 0xcee0, 0xcea0, 0xce60, +}; + +static act2000_card *cards = (act2000_card *) NULL; + +/* Parameters to be set by insmod */ +static int act_bus = 0; +static int act_port = -1; /* -1 = Autoprobe */ +static int act_irq = -1; +static char *act_id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +MODULE_DESCRIPTION("ISDN4Linux: Driver for IBM Active 2000 ISDN card"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); +MODULE_PARM_DESC(act_bus, "BusType of first card, 1=ISA, 2=MCA, 3=PCMCIA, currently only ISA"); +MODULE_PARM_DESC(act_port, "Base port address of first card"); +MODULE_PARM_DESC(act_irq, "IRQ of first card"); +MODULE_PARM_DESC(act_id, "ID-String of first card"); +module_param(act_bus, int, 0); +module_param(act_port, int, 0); +module_param(act_irq, int, 0); +module_param(act_id, charp, 0); + +static int act2000_addcard(int, int, int, char *); + +static act2000_chan * +find_channel(act2000_card *card, int channel) +{ + if ((channel >= 0) && (channel < ACT2000_BCH)) + return &(card->bch[channel]); + printk(KERN_WARNING "act2000: Invalid channel %d\n", channel); + return NULL; +} + +/* + * Free MSN list + */ +static void +act2000_clear_msn(act2000_card *card) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q; + unsigned long flags; + + spin_lock_irqsave(&card->lock, flags); + card->msn_list = NULL; + spin_unlock_irqrestore(&card->lock, flags); + while (p) { + q = p->next; + kfree(p); + p = q; + } +} + +/* + * Find an MSN entry in the list. + * If ia5 != 0, return IA5-encoded EAZ, else + * return a bitmask with corresponding bit set. + */ +static __u16 +act2000_find_msn(act2000_card *card, char *msn, int ia5) +{ + struct msn_entry *p = card->msn_list; + __u8 eaz = '0'; + + while (p) { + if (!strcmp(p->msn, msn)) { + eaz = p->eaz; + break; + } + p = p->next; + } + if (!ia5) + return (1 << (eaz - '0')); + else + return eaz; +} + +/* + * Find an EAZ entry in the list. + * return a string with corresponding msn. + */ +char * +act2000_find_eaz(act2000_card *card, char eaz) +{ + struct msn_entry *p = card->msn_list; + + while (p) { + if (p->eaz == eaz) + return (p->msn); + p = p->next; + } + return ("\0"); +} + +/* + * Add or delete an MSN to the MSN list + * + * First character of msneaz is EAZ, rest is MSN. + * If length of eazmsn is 1, delete that entry. + */ +static int +act2000_set_msn(act2000_card *card, char *eazmsn) +{ + struct msn_entry *p = card->msn_list; + struct msn_entry *q = NULL; + unsigned long flags; + int i; + + if (!strlen(eazmsn)) + return 0; + if (strlen(eazmsn) > 16) + return -EINVAL; + for (i = 0; i < strlen(eazmsn); i++) + if (!isdigit(eazmsn[i])) + return -EINVAL; + if (strlen(eazmsn) == 1) { + /* Delete a single MSN */ + while (p) { + if (p->eaz == eazmsn[0]) { + spin_lock_irqsave(&card->lock, flags); + if (q) + q->next = p->next; + else + card->msn_list = p->next; + spin_unlock_irqrestore(&card->lock, flags); + kfree(p); + printk(KERN_DEBUG + "Mapping for EAZ %c deleted\n", + eazmsn[0]); + return 0; + } + q = p; + p = p->next; + } + return 0; + } + /* Add a single MSN */ + while (p) { + /* Found in list, replace MSN */ + if (p->eaz == eazmsn[0]) { + spin_lock_irqsave(&card->lock, flags); + strcpy(p->msn, &eazmsn[1]); + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_DEBUG + "Mapping for EAZ %c changed to %s\n", + eazmsn[0], + &eazmsn[1]); + return 0; + } + p = p->next; + } + /* Not found in list, add new entry */ + p = kmalloc(sizeof(msn_entry), GFP_KERNEL); + if (!p) + return -ENOMEM; + p->eaz = eazmsn[0]; + strcpy(p->msn, &eazmsn[1]); + p->next = card->msn_list; + spin_lock_irqsave(&card->lock, flags); + card->msn_list = p; + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_DEBUG + "Mapping %c -> %s added\n", + eazmsn[0], + &eazmsn[1]); + return 0; +} + +static void +act2000_transmit(struct work_struct *work) +{ + struct act2000_card *card = + container_of(work, struct act2000_card, snd_tq); + + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_send(card); + break; + case ACT2000_BUS_PCMCIA: + case ACT2000_BUS_MCA: + default: + printk(KERN_WARNING + "act2000_transmit: Illegal bustype %d\n", card->bus); + } +} + +static void +act2000_receive(struct work_struct *work) +{ + struct act2000_card *card = + container_of(work, struct act2000_card, poll_tq); + + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_receive(card); + break; + case ACT2000_BUS_PCMCIA: + case ACT2000_BUS_MCA: + default: + printk(KERN_WARNING + "act2000_receive: Illegal bustype %d\n", card->bus); + } +} + +static void +act2000_poll(unsigned long data) +{ + act2000_card *card = (act2000_card *)data; + unsigned long flags; + + act2000_receive(&card->poll_tq); + spin_lock_irqsave(&card->lock, flags); + mod_timer(&card->ptimer, jiffies + 3); + spin_unlock_irqrestore(&card->lock, flags); +} + +static int +act2000_command(act2000_card *card, isdn_ctrl *c) +{ + ulong a; + act2000_chan *chan; + act2000_cdef cdef; + isdn_ctrl cmd; + char tmp[17]; + int ret; + unsigned long flags; + void __user *arg; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + arg = (void __user *)a; + switch (c->arg) { + case ACT2000_IOCTL_LOADBOOT: + switch (card->bus) { + case ACT2000_BUS_ISA: + ret = act2000_isa_download(card, + arg); + if (!ret) { + card->flags |= ACT2000_FLAGS_LOADED; + if (!(card->flags & ACT2000_FLAGS_IVALID)) { + card->ptimer.expires = jiffies + 3; + card->ptimer.function = act2000_poll; + card->ptimer.data = (unsigned long)card; + add_timer(&card->ptimer); + } + actcapi_manufacturer_req_errh(card); + } + break; + default: + printk(KERN_WARNING + "act2000: Illegal BUS type %d\n", + card->bus); + ret = -EIO; + } + return ret; + case ACT2000_IOCTL_SETPROTO: + card->ptype = a ? ISDN_PTYPE_EURO : ISDN_PTYPE_1TR6; + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return 0; + actcapi_manufacturer_req_net(card); + return 0; + case ACT2000_IOCTL_SETMSN: + if (copy_from_user(tmp, arg, + sizeof(tmp))) + return -EFAULT; + if ((ret = act2000_set_msn(card, tmp))) + return ret; + if (card->flags & ACT2000_FLAGS_RUNNING) + return (actcapi_manufacturer_req_msn(card)); + return 0; + case ACT2000_IOCTL_ADDCARD: + if (copy_from_user(&cdef, arg, + sizeof(cdef))) + return -EFAULT; + if (act2000_addcard(cdef.bus, cdef.port, cdef.irq, cdef.id)) + return -EIO; + return 0; + case ACT2000_IOCTL_TEST: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return 0; + default: + return -EINVAL; + } + break; + case ISDN_CMD_DIAL: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + spin_lock_irqsave(&card->lock, flags); + if (chan->fsm_state != ACT2000_STATE_NULL) { + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_WARNING "Dial on channel with state %d\n", + chan->fsm_state); + return -EBUSY; + } + if (card->ptype == ISDN_PTYPE_EURO) + tmp[0] = act2000_find_msn(card, c->parm.setup.eazmsn, 1); + else + tmp[0] = c->parm.setup.eazmsn[0]; + chan->fsm_state = ACT2000_STATE_OCALL; + chan->callref = 0xffff; + spin_unlock_irqrestore(&card->lock, flags); + ret = actcapi_connect_req(card, chan, c->parm.setup.phone, + tmp[0], c->parm.setup.si1, + c->parm.setup.si2); + if (ret) { + cmd.driver = card->myid; + cmd.command = ISDN_STAT_DHUP; + cmd.arg &= 0x0f; + card->interface.statcallb(&cmd); + } + return ret; + case ISDN_CMD_ACCEPTD: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + if (chan->fsm_state == ACT2000_STATE_ICALL) + actcapi_select_b2_protocol_req(card, chan); + return 0; + case ISDN_CMD_ACCEPTB: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return 0; + case ISDN_CMD_HANGUP: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + switch (chan->fsm_state) { + case ACT2000_STATE_ICALL: + case ACT2000_STATE_BSETUP: + actcapi_connect_resp(card, chan, 0x15); + break; + case ACT2000_STATE_ACTIVE: + actcapi_disconnect_b3_req(card, chan); + break; + } + return 0; + case ISDN_CMD_SETEAZ: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + if (strlen(c->parm.num)) { + if (card->ptype == ISDN_PTYPE_EURO) { + chan->eazmask = act2000_find_msn(card, c->parm.num, 0); + } + if (card->ptype == ISDN_PTYPE_1TR6) { + int i; + chan->eazmask = 0; + for (i = 0; i < strlen(c->parm.num); i++) + if (isdigit(c->parm.num[i])) + chan->eazmask |= (1 << (c->parm.num[i] - '0')); + } + } else + chan->eazmask = 0x3ff; + actcapi_listen_req(card); + return 0; + case ISDN_CMD_CLREAZ: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->eazmask = 0; + actcapi_listen_req(card); + return 0; + case ISDN_CMD_SETL2: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->l2prot = (c->arg >> 8); + return 0; + case ISDN_CMD_SETL3: + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + if ((c->arg >> 8) != ISDN_PROTO_L3_TRANS) { + printk(KERN_WARNING "L3 protocol unknown\n"); + return -1; + } + if (!(chan = find_channel(card, c->arg & 0x0f))) + break; + chan->l3prot = (c->arg >> 8); + return 0; + } + + return -EINVAL; +} + +static int +act2000_sendbuf(act2000_card *card, int channel, int ack, struct sk_buff *skb) +{ + struct sk_buff *xmit_skb; + int len; + act2000_chan *chan; + actcapi_msg *msg; + + if (!(chan = find_channel(card, channel))) + return -1; + if (chan->fsm_state != ACT2000_STATE_ACTIVE) + return -1; + len = skb->len; + if ((chan->queued + len) >= ACT2000_MAX_QUEUED) + return 0; + if (!len) + return 0; + if (skb_headroom(skb) < 19) { + printk(KERN_WARNING "act2000_sendbuf: Headroom only %d\n", + skb_headroom(skb)); + xmit_skb = alloc_skb(len + 19, GFP_ATOMIC); + if (!xmit_skb) { + printk(KERN_WARNING "act2000_sendbuf: Out of memory\n"); + return 0; + } + skb_reserve(xmit_skb, 19); + skb_copy_from_linear_data(skb, skb_put(xmit_skb, len), len); + } else { + xmit_skb = skb_clone(skb, GFP_ATOMIC); + if (!xmit_skb) { + printk(KERN_WARNING "act2000_sendbuf: Out of memory\n"); + return 0; + } + } + dev_kfree_skb(skb); + msg = (actcapi_msg *)skb_push(xmit_skb, 19); + msg->hdr.len = 19 + len; + msg->hdr.applicationID = 1; + msg->hdr.cmd.cmd = 0x86; + msg->hdr.cmd.subcmd = 0x00; + msg->hdr.msgnum = actcapi_nextsmsg(card); + msg->msg.data_b3_req.datalen = len; + msg->msg.data_b3_req.blocknr = (msg->hdr.msgnum & 0xff); + msg->msg.data_b3_req.fakencci = MAKE_NCCI(chan->plci, 0, chan->ncci); + msg->msg.data_b3_req.flags = ack; /* Will be set to 0 on actual sending */ + actcapi_debug_msg(xmit_skb, 1); + chan->queued += len; + skb_queue_tail(&card->sndq, xmit_skb); + act2000_schedule_tx(card); + return len; +} + + +/* Read the Status-replies from the Interface */ +static int +act2000_readstatus(u_char __user *buf, int len, act2000_card *card) +{ + int count; + u_char __user *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->status_buf_read == card->status_buf_write) + return count; + put_user(*card->status_buf_read++, p); + if (card->status_buf_read > card->status_buf_end) + card->status_buf_read = card->status_buf; + } + return count; +} + +/* + * Find card with given driverId + */ +static inline act2000_card * +act2000_findcard(int driverid) +{ + act2000_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (act2000_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl *c) +{ + act2000_card *card = act2000_findcard(c->driver); + + if (card) + return (act2000_command(card, c)); + printk(KERN_ERR + "act2000: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static int +if_writecmd(const u_char __user *buf, int len, int id, int channel) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return (len); + } + printk(KERN_ERR + "act2000: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char __user *buf, int len, int id, int channel) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return (act2000_readstatus(buf, len, card)); + } + printk(KERN_ERR + "act2000: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + act2000_card *card = act2000_findcard(id); + + if (card) { + if (!(card->flags & ACT2000_FLAGS_RUNNING)) + return -ENODEV; + return (act2000_sendbuf(card, channel, ack, skb)); + } + printk(KERN_ERR + "act2000: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; +} + + +/* + * Allocate a new card-struct, initialize it + * link it into cards-list. + */ +static void +act2000_alloccard(int bus, int port, int irq, char *id) +{ + int i; + act2000_card *card; + if (!(card = kzalloc(sizeof(act2000_card), GFP_KERNEL))) { + printk(KERN_WARNING + "act2000: (%s) Could not allocate card-struct.\n", id); + return; + } + spin_lock_init(&card->lock); + spin_lock_init(&card->mnlock); + skb_queue_head_init(&card->sndq); + skb_queue_head_init(&card->rcvq); + skb_queue_head_init(&card->ackq); + INIT_WORK(&card->snd_tq, act2000_transmit); + INIT_WORK(&card->rcv_tq, actcapi_dispatch); + INIT_WORK(&card->poll_tq, act2000_receive); + init_timer(&card->ptimer); + card->interface.owner = THIS_MODULE; + card->interface.channels = ACT2000_BCH; + card->interface.maxbufsize = 4000; + card->interface.command = if_command; + card->interface.writebuf_skb = if_sendbuf; + card->interface.writecmd = if_writecmd; + card->interface.readstat = if_readstatus; + card->interface.features = + ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN; + card->interface.hl_hdrlen = 20; + card->ptype = ISDN_PTYPE_EURO; + strlcpy(card->interface.id, id, sizeof(card->interface.id)); + for (i = 0; i < ACT2000_BCH; i++) { + card->bch[i].plci = 0x8000; + card->bch[i].ncci = 0x8000; + card->bch[i].l2prot = ISDN_PROTO_L2_X75I; + card->bch[i].l3prot = ISDN_PROTO_L3_TRANS; + } + card->myid = -1; + card->bus = bus; + card->port = port; + card->irq = irq; + card->next = cards; + cards = card; +} + +/* + * register card at linklevel + */ +static int +act2000_registercard(act2000_card *card) +{ + switch (card->bus) { + case ACT2000_BUS_ISA: + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: Illegal BUS type %d\n", + card->bus); + return -1; + } + if (!register_isdn(&card->interface)) { + printk(KERN_WARNING + "act2000: Unable to register %s\n", + card->interface.id); + return -1; + } + card->myid = card->interface.channels; + sprintf(card->regname, "act2000-isdn (%s)", card->interface.id); + return 0; +} + +static void +unregister_card(act2000_card *card) +{ + isdn_ctrl cmd; + + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + switch (card->bus) { + case ACT2000_BUS_ISA: + act2000_isa_release(card); + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: Invalid BUS type %d\n", + card->bus); + break; + } +} + +static int +act2000_addcard(int bus, int port, int irq, char *id) +{ + act2000_card *p; + act2000_card *q = NULL; + int initialized; + int added = 0; + int failed = 0; + int i; + + if (!bus) + bus = ACT2000_BUS_ISA; + if (port != -1) { + /* Port defined, do fixed setup */ + act2000_alloccard(bus, port, irq, id); + } else { + /* No port defined, perform autoprobing. + * This may result in more than one card detected. + */ + switch (bus) { + case ACT2000_BUS_ISA: + for (i = 0; i < ARRAY_SIZE(act2000_isa_ports); i++) + if (act2000_isa_detect(act2000_isa_ports[i])) { + printk(KERN_INFO "act2000: Detected " + "ISA card at port 0x%x\n", + act2000_isa_ports[i]); + act2000_alloccard(bus, + act2000_isa_ports[i], irq, id); + } + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: addcard: Invalid BUS type %d\n", bus); + } + } + if (!cards) + return 1; + p = cards; + while (p) { + initialized = 0; + if (!p->interface.statcallb) { + /* Not yet registered. + * Try to register and activate it. + */ + added++; + switch (p->bus) { + case ACT2000_BUS_ISA: + if (act2000_isa_detect(p->port)) { + if (act2000_registercard(p)) + break; + if (act2000_isa_config_port(p, p->port)) { + printk(KERN_WARNING + "act2000: Could not request port 0x%04x\n", + p->port); + unregister_card(p); + p->interface.statcallb = NULL; + break; + } + if (act2000_isa_config_irq(p, p->irq)) { + printk(KERN_INFO + "act2000: No IRQ available, fallback to polling\n"); + /* Fall back to polled operation */ + p->irq = 0; + } + printk(KERN_INFO + "act2000: ISA" + "-type card at port " + "0x%04x ", + p->port); + if (p->irq) + printk("irq %d\n", p->irq); + else + printk("polled\n"); + initialized = 1; + } + break; + case ACT2000_BUS_MCA: + case ACT2000_BUS_PCMCIA: + default: + printk(KERN_WARNING + "act2000: addcard: Invalid BUS type %d\n", + p->bus); + } + } else + /* Card already initialized */ + initialized = 1; + if (initialized) { + /* Init OK, next card ... */ + q = p; + p = p->next; + } else { + /* Init failed, remove card from list, free memory */ + printk(KERN_WARNING + "act2000: Initialization of %s failed\n", + p->interface.id); + if (q) { + q->next = p->next; + kfree(p); + p = q->next; + } else { + cards = p->next; + kfree(p); + p = cards; + } + failed++; + } + } + return (added - failed); +} + +#define DRIVERNAME "IBM Active 2000 ISDN driver" + +static int __init act2000_init(void) +{ + printk(KERN_INFO "%s\n", DRIVERNAME); + if (!cards) + act2000_addcard(act_bus, act_port, act_irq, act_id); + if (!cards) + printk(KERN_INFO "act2000: No cards defined yet\n"); + return 0; +} + +static void __exit act2000_exit(void) +{ + act2000_card *card = cards; + act2000_card *last; + while (card) { + unregister_card(card); + del_timer_sync(&card->ptimer); + card = card->next; + } + card = cards; + while (card) { + last = card; + card = card->next; + act2000_clear_msn(last); + kfree(last); + } + printk(KERN_INFO "%s unloaded\n", DRIVERNAME); +} + +module_init(act2000_init); +module_exit(act2000_exit); diff --git a/drivers/staging/i4l/icn/Kconfig b/drivers/staging/i4l/icn/Kconfig new file mode 100644 index 000000000..4534f21a1 --- /dev/null +++ b/drivers/staging/i4l/icn/Kconfig @@ -0,0 +1,12 @@ +config ISDN_DRV_ICN + tristate "ICN 2B and 4B support" + depends on ISA + help + This enables support for two kinds of ISDN-cards made by a German + company called ICN. 2B is the standard version for a single ISDN + line with two B-channels, 4B supports two ISDN lines. For running + this card, additional firmware is necessary, which has to be + downloaded into the card using a utility which is distributed + separately. See <file:Documentation/isdn/README> and + <file:Documentation/isdn/README.icn> for more + information. diff --git a/drivers/staging/i4l/icn/Makefile b/drivers/staging/i4l/icn/Makefile new file mode 100644 index 000000000..d9b476fcf --- /dev/null +++ b/drivers/staging/i4l/icn/Makefile @@ -0,0 +1,5 @@ +# Makefile for the icn ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_ICN) += icn.o diff --git a/drivers/staging/i4l/icn/icn.c b/drivers/staging/i4l/icn/icn.c new file mode 100644 index 000000000..46d957c34 --- /dev/null +++ b/drivers/staging/i4l/icn/icn.c @@ -0,0 +1,1693 @@ +/* $Id: icn.c,v 1.65.6.8 2001/09/23 22:24:55 kai Exp $ + * + * ISDN low-level module for the ICN active ISDN-Card. + * + * Copyright 1994,95,96 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. + * + */ + +#include "icn.h" +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/sched.h> + +static int portbase = ICN_BASEADDR; +static unsigned long membase = ICN_MEMADDR; +static char *icn_id = "\0"; +static char *icn_id2 = "\0"; + +MODULE_DESCRIPTION("ISDN4Linux: Driver for ICN active ISDN card"); +MODULE_AUTHOR("Fritz Elfert"); +MODULE_LICENSE("GPL"); +module_param(portbase, int, 0); +MODULE_PARM_DESC(portbase, "Port address of first card"); +module_param(membase, ulong, 0); +MODULE_PARM_DESC(membase, "Shared memory address of all cards"); +module_param(icn_id, charp, 0); +MODULE_PARM_DESC(icn_id, "ID-String of first card"); +module_param(icn_id2, charp, 0); +MODULE_PARM_DESC(icn_id2, "ID-String of first card, second S0 (4B only)"); + +/* + * Verbose bootcode- and protocol-downloading. + */ +#undef BOOT_DEBUG + +/* + * Verbose Shmem-Mapping. + */ +#undef MAP_DEBUG + +static char +*revision = "$Revision: 1.65.6.8 $"; + +static int icn_addcard(int, char *, char *); + +/* + * Free send-queue completely. + * Parameter: + * card = pointer to card struct + * channel = channel number + */ +static void +icn_free_queue(icn_card *card, int channel) +{ + struct sk_buff_head *queue = &card->spqueue[channel]; + struct sk_buff *skb; + + skb_queue_purge(queue); + card->xlen[channel] = 0; + card->sndcount[channel] = 0; + if ((skb = card->xskb[channel])) { + card->xskb[channel] = NULL; + dev_kfree_skb(skb); + } +} + +/* Put a value into a shift-register, highest bit first. + * Parameters: + * port = port for output (bit 0 is significant) + * val = value to be output + * firstbit = Bit-Number of highest bit + * bitcount = Number of bits to output + */ +static inline void +icn_shiftout(unsigned short port, + unsigned long val, + int firstbit, + int bitcount) +{ + + register u_char s; + register u_char c; + + for (s = firstbit, c = bitcount; c > 0; s--, c--) + OUTB_P((u_char) ((val >> s) & 1) ? 0xff : 0, port); +} + +/* + * disable a cards shared memory + */ +static inline void +icn_disable_ram(icn_card *card) +{ + OUTB_P(0, ICN_MAPRAM); +} + +/* + * enable a cards shared memory + */ +static inline void +icn_enable_ram(icn_card *card) +{ + OUTB_P(0xff, ICN_MAPRAM); +} + +/* + * Map a cards channel0 (Bank0/Bank8) or channel1 (Bank4/Bank12) + * + * must called with holding the devlock + */ +static inline void +icn_map_channel(icn_card *card, int channel) +{ +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_map_channel %d %d\n", dev.channel, channel); +#endif + if ((channel == dev.channel) && (card == dev.mcard)) + return; + if (dev.mcard) + icn_disable_ram(dev.mcard); + icn_shiftout(ICN_BANK, chan2bank[channel], 3, 4); /* Select Bank */ + icn_enable_ram(card); + dev.mcard = card; + dev.channel = channel; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_map_channel done\n"); +#endif +} + +/* + * Lock a cards channel. + * Return 0 if requested card/channel is unmapped (failure). + * Return 1 on success. + * + * must called with holding the devlock + */ +static inline int +icn_lock_channel(icn_card *card, int channel) +{ + register int retval; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d\n", channel); +#endif + if ((dev.channel == channel) && (card == dev.mcard)) { + dev.chanlock++; + retval = 1; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d OK\n", channel); +#endif + } else { + retval = 0; +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_lock_channel %d FAILED, dc=%d\n", channel, dev.channel); +#endif + } + return retval; +} + +/* + * Release current card/channel lock + * + * must called with holding the devlock + */ +static inline void +__icn_release_channel(void) +{ +#ifdef MAP_DEBUG + printk(KERN_DEBUG "icn_release_channel l=%d\n", dev.chanlock); +#endif + if (dev.chanlock > 0) + dev.chanlock--; +} + +/* + * Release current card/channel lock + */ +static inline void +icn_release_channel(void) +{ + ulong flags; + + spin_lock_irqsave(&dev.devlock, flags); + __icn_release_channel(); + spin_unlock_irqrestore(&dev.devlock, flags); +} + +/* + * Try to map and lock a cards channel. + * Return 1 on success, 0 on failure. + */ +static inline int +icn_trymaplock_channel(icn_card *card, int channel) +{ + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock c=%d dc=%d l=%d\n", channel, dev.channel, + dev.chanlock); +#endif + spin_lock_irqsave(&dev.devlock, flags); + if ((!dev.chanlock) || + ((dev.channel == channel) && (dev.mcard == card))) { + dev.chanlock++; + icn_map_channel(card, channel); + spin_unlock_irqrestore(&dev.devlock, flags); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock %d OK\n", channel); +#endif + return 1; + } + spin_unlock_irqrestore(&dev.devlock, flags); +#ifdef MAP_DEBUG + printk(KERN_DEBUG "trymaplock %d FAILED\n", channel); +#endif + return 0; +} + +/* + * Release current card/channel lock, + * then map same or other channel without locking. + */ +static inline void +icn_maprelease_channel(icn_card *card, int channel) +{ + ulong flags; + +#ifdef MAP_DEBUG + printk(KERN_DEBUG "map_release c=%d l=%d\n", channel, dev.chanlock); +#endif + spin_lock_irqsave(&dev.devlock, flags); + if (dev.chanlock > 0) + dev.chanlock--; + if (!dev.chanlock) + icn_map_channel(card, channel); + spin_unlock_irqrestore(&dev.devlock, flags); +} + +/* Get Data from the B-Channel, assemble fragmented packets and put them + * into receive-queue. Wake up any B-Channel-reading processes. + * This routine is called via timer-callback from icn_pollbchan(). + */ + +static void +icn_pollbchan_receive(int channel, icn_card *card) +{ + int mch = channel + ((card->secondhalf) ? 2 : 0); + int eflag; + int cnt; + struct sk_buff *skb; + + if (icn_trymaplock_channel(card, mch)) { + while (rbavl) { + cnt = readb(&rbuf_l); + if ((card->rcvidx[channel] + cnt) > 4000) { + printk(KERN_WARNING + "icn: (%s) bogus packet on ch%d, dropping.\n", + CID, + channel + 1); + card->rcvidx[channel] = 0; + eflag = 0; + } else { + memcpy_fromio(&card->rcvbuf[channel][card->rcvidx[channel]], + &rbuf_d, cnt); + card->rcvidx[channel] += cnt; + eflag = readb(&rbuf_f); + } + rbnext; + icn_maprelease_channel(card, mch & 2); + if (!eflag) { + if ((cnt = card->rcvidx[channel])) { + if (!(skb = dev_alloc_skb(cnt))) { + printk(KERN_WARNING "icn: receive out of memory\n"); + break; + } + memcpy(skb_put(skb, cnt), card->rcvbuf[channel], cnt); + card->rcvidx[channel] = 0; + card->interface.rcvcallb_skb(card->myid, channel, skb); + } + } + if (!icn_trymaplock_channel(card, mch)) + break; + } + icn_maprelease_channel(card, mch & 2); + } +} + +/* Send data-packet to B-Channel, split it up into fragments of + * ICN_FRAGSIZE length. If last fragment is sent out, signal + * success to upper layers via statcallb with ISDN_STAT_BSENT argument. + * This routine is called via timer-callback from icn_pollbchan() or + * directly from icn_sendbuf(). + */ + +static void +icn_pollbchan_send(int channel, icn_card *card) +{ + int mch = channel + ((card->secondhalf) ? 2 : 0); + int cnt; + unsigned long flags; + struct sk_buff *skb; + isdn_ctrl cmd; + + if (!(card->sndcount[channel] || card->xskb[channel] || + !skb_queue_empty(&card->spqueue[channel]))) + return; + if (icn_trymaplock_channel(card, mch)) { + while (sbfree && + (card->sndcount[channel] || + !skb_queue_empty(&card->spqueue[channel]) || + card->xskb[channel])) { + spin_lock_irqsave(&card->lock, flags); + if (card->xmit_lock[channel]) { + spin_unlock_irqrestore(&card->lock, flags); + break; + } + card->xmit_lock[channel]++; + spin_unlock_irqrestore(&card->lock, flags); + skb = card->xskb[channel]; + if (!skb) { + skb = skb_dequeue(&card->spqueue[channel]); + if (skb) { + /* Pop ACK-flag off skb. + * Store length to xlen. + */ + if (*(skb_pull(skb, 1))) + card->xlen[channel] = skb->len; + else + card->xlen[channel] = 0; + } + } + if (!skb) + break; + if (skb->len > ICN_FRAGSIZE) { + writeb(0xff, &sbuf_f); + cnt = ICN_FRAGSIZE; + } else { + writeb(0x0, &sbuf_f); + cnt = skb->len; + } + writeb(cnt, &sbuf_l); + memcpy_toio(&sbuf_d, skb->data, cnt); + skb_pull(skb, cnt); + sbnext; /* switch to next buffer */ + icn_maprelease_channel(card, mch & 2); + spin_lock_irqsave(&card->lock, flags); + card->sndcount[channel] -= cnt; + if (!skb->len) { + if (card->xskb[channel]) + card->xskb[channel] = NULL; + card->xmit_lock[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + dev_kfree_skb(skb); + if (card->xlen[channel]) { + cmd.command = ISDN_STAT_BSENT; + cmd.driver = card->myid; + cmd.arg = channel; + cmd.parm.length = card->xlen[channel]; + card->interface.statcallb(&cmd); + } + } else { + card->xskb[channel] = skb; + card->xmit_lock[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + } + if (!icn_trymaplock_channel(card, mch)) + break; + } + icn_maprelease_channel(card, mch & 2); + } +} + +/* Send/Receive Data to/from the B-Channel. + * This routine is called via timer-callback. + * It schedules itself while any B-Channel is open. + */ + +static void +icn_pollbchan(unsigned long data) +{ + icn_card *card = (icn_card *) data; + unsigned long flags; + + if (card->flags & ICN_FLAGS_B1ACTIVE) { + icn_pollbchan_receive(0, card); + icn_pollbchan_send(0, card); + } + if (card->flags & ICN_FLAGS_B2ACTIVE) { + icn_pollbchan_receive(1, card); + icn_pollbchan_send(1, card); + } + if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) { + /* schedule b-channel polling again */ + spin_lock_irqsave(&card->lock, flags); + mod_timer(&card->rb_timer, jiffies + ICN_TIMER_BCREAD); + card->flags |= ICN_FLAGS_RBTIMER; + spin_unlock_irqrestore(&card->lock, flags); + } else + card->flags &= ~ICN_FLAGS_RBTIMER; +} + +typedef struct icn_stat { + char *statstr; + int command; + int action; +} icn_stat; +/* *INDENT-OFF* */ +static icn_stat icn_stat_table[] = +{ + {"BCON_", ISDN_STAT_BCONN, 1}, /* B-Channel connected */ + {"BDIS_", ISDN_STAT_BHUP, 2}, /* B-Channel disconnected */ + /* + ** add d-channel connect and disconnect support to link-level + */ + {"DCON_", ISDN_STAT_DCONN, 10}, /* D-Channel connected */ + {"DDIS_", ISDN_STAT_DHUP, 11}, /* D-Channel disconnected */ + {"DCAL_I", ISDN_STAT_ICALL, 3}, /* Incoming call dialup-line */ + {"DSCA_I", ISDN_STAT_ICALL, 3}, /* Incoming call 1TR6-SPV */ + {"FCALL", ISDN_STAT_ICALL, 4}, /* Leased line connection up */ + {"CIF", ISDN_STAT_CINF, 5}, /* Charge-info, 1TR6-type */ + {"AOC", ISDN_STAT_CINF, 6}, /* Charge-info, DSS1-type */ + {"CAU", ISDN_STAT_CAUSE, 7}, /* Cause code */ + {"TEI OK", ISDN_STAT_RUN, 0}, /* Card connected to wallplug */ + {"E_L1: ACT FAIL", ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {"E_L2: DATA LIN", ISDN_STAT_BHUP, 8}, /* Layer-2 data link lost */ + {"E_L1: ACTIVATION FAILED", + ISDN_STAT_BHUP, 8}, /* Layer-1 activation failed */ + {NULL, 0, -1} +}; +/* *INDENT-ON* */ + + +/* + * Check Statusqueue-Pointer from isdn-cards. + * If there are new status-replies from the interface, check + * them against B-Channel-connects/disconnects and set flags accordingly. + * Wake-Up any processes, who are reading the status-device. + * If there are B-Channels open, initiate a timer-callback to + * icn_pollbchan(). + * This routine is called periodically via timer. + */ + +static void +icn_parse_status(u_char *status, int channel, icn_card *card) +{ + icn_stat *s = icn_stat_table; + int action = -1; + unsigned long flags; + isdn_ctrl cmd; + + while (s->statstr) { + if (!strncmp(status, s->statstr, strlen(s->statstr))) { + cmd.command = s->command; + action = s->action; + break; + } + s++; + } + if (action == -1) + return; + cmd.driver = card->myid; + cmd.arg = channel; + switch (action) { + case 11: + spin_lock_irqsave(&card->lock, flags); + icn_free_queue(card, channel); + card->rcvidx[channel] = 0; + + if (card->flags & + ((channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE)) { + + isdn_ctrl ncmd; + + card->flags &= ~((channel) ? + ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE); + + memset(&ncmd, 0, sizeof(ncmd)); + + ncmd.driver = card->myid; + ncmd.arg = channel; + ncmd.command = ISDN_STAT_BHUP; + spin_unlock_irqrestore(&card->lock, flags); + card->interface.statcallb(&cmd); + } else + spin_unlock_irqrestore(&card->lock, flags); + break; + case 1: + spin_lock_irqsave(&card->lock, flags); + icn_free_queue(card, channel); + card->flags |= (channel) ? + ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE; + spin_unlock_irqrestore(&card->lock, flags); + break; + case 2: + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~((channel) ? + ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE); + icn_free_queue(card, channel); + card->rcvidx[channel] = 0; + spin_unlock_irqrestore(&card->lock, flags); + break; + case 3: + { + char *t = status + 6; + char *s = strchr(t, ','); + + *s++ = '\0'; + strlcpy(cmd.parm.setup.phone, t, + sizeof(cmd.parm.setup.phone)); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd.parm.setup.si1 = 0; + else + cmd.parm.setup.si1 = + simple_strtoul(t, NULL, 10); + s = strchr(t = s, ','); + *s++ = '\0'; + if (!strlen(t)) + cmd.parm.setup.si2 = 0; + else + cmd.parm.setup.si2 = + simple_strtoul(t, NULL, 10); + strlcpy(cmd.parm.setup.eazmsn, s, + sizeof(cmd.parm.setup.eazmsn)); + } + cmd.parm.setup.plan = 0; + cmd.parm.setup.screen = 0; + break; + case 4: + sprintf(cmd.parm.setup.phone, "LEASED%d", card->myid); + sprintf(cmd.parm.setup.eazmsn, "%d", channel + 1); + cmd.parm.setup.si1 = 7; + cmd.parm.setup.si2 = 0; + cmd.parm.setup.plan = 0; + cmd.parm.setup.screen = 0; + break; + case 5: + strlcpy(cmd.parm.num, status + 3, sizeof(cmd.parm.num)); + break; + case 6: + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%d", + (int) simple_strtoul(status + 7, NULL, 16)); + break; + case 7: + status += 3; + if (strlen(status) == 4) + snprintf(cmd.parm.num, sizeof(cmd.parm.num), "%s%c%c", + status + 2, *status, *(status + 1)); + else + strlcpy(cmd.parm.num, status + 1, sizeof(cmd.parm.num)); + break; + case 8: + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~ICN_FLAGS_B1ACTIVE; + icn_free_queue(card, 0); + card->rcvidx[0] = 0; + spin_unlock_irqrestore(&card->lock, flags); + cmd.arg = 0; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_DHUP; + cmd.arg = 0; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_BHUP; + spin_lock_irqsave(&card->lock, flags); + card->flags &= ~ICN_FLAGS_B2ACTIVE; + icn_free_queue(card, 1); + card->rcvidx[1] = 0; + spin_unlock_irqrestore(&card->lock, flags); + cmd.arg = 1; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + cmd.command = ISDN_STAT_DHUP; + cmd.arg = 1; + cmd.driver = card->myid; + break; + } + card->interface.statcallb(&cmd); + return; +} + +static void +icn_putmsg(icn_card *card, unsigned char c) +{ + ulong flags; + + spin_lock_irqsave(&card->lock, flags); + *card->msg_buf_write++ = (c == 0xff) ? '\n' : c; + if (card->msg_buf_write == card->msg_buf_read) { + if (++card->msg_buf_read > card->msg_buf_end) + card->msg_buf_read = card->msg_buf; + } + if (card->msg_buf_write > card->msg_buf_end) + card->msg_buf_write = card->msg_buf; + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +icn_polldchan(unsigned long data) +{ + icn_card *card = (icn_card *) data; + int mch = card->secondhalf ? 2 : 0; + int avail = 0; + int left; + u_char c; + int ch; + unsigned long flags; + int i; + u_char *p; + isdn_ctrl cmd; + + if (icn_trymaplock_channel(card, mch)) { + avail = msg_avail; + for (left = avail, i = readb(&msg_o); left > 0; i++, left--) { + c = readb(&dev.shmem->comm_buffers.iopc_buf[i & 0xff]); + icn_putmsg(card, c); + if (c == 0xff) { + card->imsg[card->iptr] = 0; + card->iptr = 0; + if (card->imsg[0] == '0' && card->imsg[1] >= '0' && + card->imsg[1] <= '2' && card->imsg[2] == ';') { + ch = (card->imsg[1] - '0') - 1; + p = &card->imsg[3]; + icn_parse_status(p, ch, card); + } else { + p = card->imsg; + if (!strncmp(p, "DRV1.", 5)) { + u_char vstr[10]; + u_char *q = vstr; + + printk(KERN_INFO "icn: (%s) %s\n", CID, p); + if (!strncmp(p + 7, "TC", 2)) { + card->ptype = ISDN_PTYPE_1TR6; + card->interface.features |= ISDN_FEATURE_P_1TR6; + printk(KERN_INFO + "icn: (%s) 1TR6-Protocol loaded and running\n", CID); + } + if (!strncmp(p + 7, "EC", 2)) { + card->ptype = ISDN_PTYPE_EURO; + card->interface.features |= ISDN_FEATURE_P_EURO; + printk(KERN_INFO + "icn: (%s) Euro-Protocol loaded and running\n", CID); + } + p = strstr(card->imsg, "BRV") + 3; + while (*p) { + if (*p >= '0' && *p <= '9') + *q++ = *p; + p++; + } + *q = '\0'; + strcat(vstr, "000"); + vstr[3] = '\0'; + card->fw_rev = (int) simple_strtoul(vstr, NULL, 10); + continue; + + } + } + } else { + card->imsg[card->iptr] = c; + if (card->iptr < 59) + card->iptr++; + } + } + writeb((readb(&msg_o) + avail) & 0xff, &msg_o); + icn_release_channel(); + } + if (avail) { + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = avail; + card->interface.statcallb(&cmd); + } + spin_lock_irqsave(&card->lock, flags); + if (card->flags & (ICN_FLAGS_B1ACTIVE | ICN_FLAGS_B2ACTIVE)) + if (!(card->flags & ICN_FLAGS_RBTIMER)) { + /* schedule b-channel polling */ + card->flags |= ICN_FLAGS_RBTIMER; + del_timer(&card->rb_timer); + card->rb_timer.function = icn_pollbchan; + card->rb_timer.data = (unsigned long) card; + card->rb_timer.expires = jiffies + ICN_TIMER_BCREAD; + add_timer(&card->rb_timer); + } + /* schedule again */ + mod_timer(&card->st_timer, jiffies + ICN_TIMER_DCREAD); + spin_unlock_irqrestore(&card->lock, flags); +} + +/* Append a packet to the transmit buffer-queue. + * Parameters: + * channel = Number of B-channel + * skb = pointer to sk_buff + * card = pointer to card-struct + * Return: + * Number of bytes transferred, -E??? on error + */ + +static int +icn_sendbuf(int channel, int ack, struct sk_buff *skb, icn_card *card) +{ + int len = skb->len; + unsigned long flags; + struct sk_buff *nskb; + + if (len > 4000) { + printk(KERN_WARNING + "icn: Send packet too large\n"); + return -EINVAL; + } + if (len) { + if (!(card->flags & (channel) ? ICN_FLAGS_B2ACTIVE : ICN_FLAGS_B1ACTIVE)) + return 0; + if (card->sndcount[channel] > ICN_MAX_SQUEUE) + return 0; + /* TODO test headroom or use skb->nb to flag ACK */ + nskb = skb_clone(skb, GFP_ATOMIC); + if (nskb) { + /* Push ACK flag as one + * byte in front of data. + */ + *(skb_push(nskb, 1)) = ack ? 1 : 0; + skb_queue_tail(&card->spqueue[channel], nskb); + dev_kfree_skb(skb); + } else + len = 0; + spin_lock_irqsave(&card->lock, flags); + card->sndcount[channel] += len; + spin_unlock_irqrestore(&card->lock, flags); + } + return len; +} + +/* + * Check card's status after starting the bootstrap loader. + * On entry, the card's shared memory has already to be mapped. + * Return: + * 0 on success (Boot loader ready) + * -EIO on failure (timeout) + */ +static int +icn_check_loader(int cardnumber) +{ + int timer = 0; + + while (1) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d ?\n", cardnumber); +#endif + if (readb(&dev.shmem->data_control.scns) || + readb(&dev.shmem->data_control.scnr)) { + if (timer++ > 5) { + printk(KERN_WARNING + "icn: Boot-Loader %d timed out.\n", + cardnumber); + icn_release_channel(); + return -EIO; + } +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d TO?\n", cardnumber); +#endif + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } else { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Loader %d OK\n", cardnumber); +#endif + icn_release_channel(); + return 0; + } + } +} + +/* Load the boot-code into the interface-card's memory and start it. + * Always called from user-process. + * + * Parameters: + * buffer = pointer to packet + * Return: + * 0 if successfully loaded + */ + +#ifdef BOOT_DEBUG +#define SLEEP(sec) { \ + int slsec = sec; \ + printk(KERN_DEBUG "SLEEP(%d)\n", slsec); \ + while (slsec) { \ + msleep_interruptible(1000); \ + slsec--; \ + } \ + } +#else +#define SLEEP(sec) +#endif + +static int +icn_loadboot(u_char __user *buffer, icn_card *card) +{ + int ret; + u_char *codebuf; + unsigned long flags; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadboot called, buffaddr=%08lx\n", (ulong) buffer); +#endif + if (!(codebuf = kmalloc(ICN_CODE_STAGE1, GFP_KERNEL))) { + printk(KERN_WARNING "icn: Could not allocate code buffer\n"); + ret = -ENOMEM; + goto out; + } + if (copy_from_user(codebuf, buffer, ICN_CODE_STAGE1)) { + ret = -EFAULT; + goto out_kfree; + } + if (!card->rvalid) { + if (!request_region(card->port, ICN_PORTLEN, card->regname)) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, + card->port, + card->port + ICN_PORTLEN); + ret = -EBUSY; + goto out_kfree; + } + card->rvalid = 1; + if (card->doubleS0) + card->other->rvalid = 1; + } + if (!dev.mvalid) { + if (!request_mem_region(dev.memaddr, 0x4000, "icn-isdn (all cards)")) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", dev.memaddr); + ret = -EBUSY; + goto out_kfree; + } + dev.shmem = ioremap(dev.memaddr, 0x4000); + dev.mvalid = 1; + } + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + icn_shiftout(ICN_CFG, 0x0f, 3, 4); /* Windowsize= 16k */ + icn_shiftout(ICN_CFG, dev.memaddr, 23, 10); /* Set RAM-Addr. */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "shmem=%08lx\n", dev.memaddr); +#endif + SLEEP(1); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + icn_map_channel(card, 0); /* Select Bank 0 */ + icn_lock_channel(card, 0); /* Lock Bank 0 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transferred\n"); +#endif + if (card->doubleS0) { + SLEEP(1); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 8\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + __icn_release_channel(); + icn_map_channel(card, 2); /* Select Bank 8 */ + icn_lock_channel(card, 2); /* Lock Bank 8 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + memcpy_toio(dev.shmem, codebuf, ICN_CODE_STAGE1); /* Copy code */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Bootloader transferred\n"); +#endif + } + SLEEP(1); + OUTB_P(0xff, ICN_RUN); /* Start Boot-Code */ + if ((ret = icn_check_loader(card->doubleS0 ? 2 : 1))) { + goto out_kfree; + } + if (!card->doubleS0) { + ret = 0; + goto out_kfree; + } + /* reached only, if we have a Double-S0-Card */ +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Map Bank 0\n"); +#endif + spin_lock_irqsave(&dev.devlock, flags); + icn_map_channel(card, 0); /* Select Bank 0 */ + icn_lock_channel(card, 0); /* Lock Bank 0 */ + spin_unlock_irqrestore(&dev.devlock, flags); + SLEEP(1); + ret = (icn_check_loader(1)); + +out_kfree: + kfree(codebuf); +out: + return ret; +} + +static int +icn_loadproto(u_char __user *buffer, icn_card *card) +{ + register u_char __user *p = buffer; + u_char codebuf[256]; + uint left = ICN_CODE_STAGE2; + uint cnt; + int timer; + unsigned long flags; + +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "icn_loadproto called\n"); +#endif + if (!access_ok(VERIFY_READ, buffer, ICN_CODE_STAGE2)) + return -EFAULT; + timer = 0; + spin_lock_irqsave(&dev.devlock, flags); + if (card->secondhalf) { + icn_map_channel(card, 2); + icn_lock_channel(card, 2); + } else { + icn_map_channel(card, 0); + icn_lock_channel(card, 0); + } + spin_unlock_irqrestore(&dev.devlock, flags); + while (left) { + if (sbfree) { /* If there is a free buffer... */ + cnt = left; + if (cnt > 256) + cnt = 256; + if (copy_from_user(codebuf, p, cnt)) { + icn_maprelease_channel(card, 0); + return -EFAULT; + } + memcpy_toio(&sbuf_l, codebuf, cnt); /* copy data */ + sbnext; /* switch to next buffer */ + p += cnt; + left -= cnt; + timer = 0; + } else { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "boot 2 !sbfree\n"); +#endif + if (timer++ > 5) { + icn_maprelease_channel(card, 0); + return -EIO; + } + schedule_timeout_interruptible(10); + } + } + writeb(0x20, &sbuf_n); + timer = 0; + while (1) { + if (readb(&cmd_o) || readb(&cmd_i)) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto?\n"); +#endif + if (timer++ > 5) { + printk(KERN_WARNING + "icn: (%s) Protocol timed out.\n", + CID); +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto TO!\n"); +#endif + icn_maprelease_channel(card, 0); + return -EIO; + } +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto TO?\n"); +#endif + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } else { + if ((card->secondhalf) || (!card->doubleS0)) { +#ifdef BOOT_DEBUG + printk(KERN_DEBUG "Proto loaded, install poll-timer %d\n", + card->secondhalf); +#endif + spin_lock_irqsave(&card->lock, flags); + init_timer(&card->st_timer); + card->st_timer.expires = jiffies + ICN_TIMER_DCREAD; + card->st_timer.function = icn_polldchan; + card->st_timer.data = (unsigned long) card; + add_timer(&card->st_timer); + card->flags |= ICN_FLAGS_RUNNING; + if (card->doubleS0) { + init_timer(&card->other->st_timer); + card->other->st_timer.expires = jiffies + ICN_TIMER_DCREAD; + card->other->st_timer.function = icn_polldchan; + card->other->st_timer.data = (unsigned long) card->other; + add_timer(&card->other->st_timer); + card->other->flags |= ICN_FLAGS_RUNNING; + } + spin_unlock_irqrestore(&card->lock, flags); + } + icn_maprelease_channel(card, 0); + return 0; + } + } +} + +/* Read the Status-replies from the Interface */ +static int +icn_readstatus(u_char __user *buf, int len, icn_card *card) +{ + int count; + u_char __user *p; + + for (p = buf, count = 0; count < len; p++, count++) { + if (card->msg_buf_read == card->msg_buf_write) + return count; + if (put_user(*card->msg_buf_read++, p)) + return -EFAULT; + if (card->msg_buf_read > card->msg_buf_end) + card->msg_buf_read = card->msg_buf; + } + return count; +} + +/* Put command-strings into the command-queue of the Interface */ +static int +icn_writecmd(const u_char *buf, int len, int user, icn_card *card) +{ + int mch = card->secondhalf ? 2 : 0; + int pp; + int i; + int count; + int xcount; + int ocount; + int loop; + unsigned long flags; + int lastmap_channel; + struct icn_card *lastmap_card; + u_char *p; + isdn_ctrl cmd; + u_char msg[0x100]; + + ocount = 1; + xcount = loop = 0; + while (len) { + count = cmd_free; + if (count > len) + count = len; + if (user) { + if (copy_from_user(msg, buf, count)) + return -EFAULT; + } else + memcpy(msg, buf, count); + + spin_lock_irqsave(&dev.devlock, flags); + lastmap_card = dev.mcard; + lastmap_channel = dev.channel; + icn_map_channel(card, mch); + + icn_putmsg(card, '>'); + for (p = msg, pp = readb(&cmd_i), i = count; i > 0; i--, p++, pp + ++) { + writeb((*p == '\n') ? 0xff : *p, + &dev.shmem->comm_buffers.pcio_buf[pp & 0xff]); + len--; + xcount++; + icn_putmsg(card, *p); + if ((*p == '\n') && (i > 1)) { + icn_putmsg(card, '>'); + ocount++; + } + ocount++; + } + writeb((readb(&cmd_i) + count) & 0xff, &cmd_i); + if (lastmap_card) + icn_map_channel(lastmap_card, lastmap_channel); + spin_unlock_irqrestore(&dev.devlock, flags); + if (len) { + mdelay(1); + if (loop++ > 20) + break; + } else + break; + } + if (len && (!user)) + printk(KERN_WARNING "icn: writemsg incomplete!\n"); + cmd.command = ISDN_STAT_STAVAIL; + cmd.driver = card->myid; + cmd.arg = ocount; + card->interface.statcallb(&cmd); + return xcount; +} + +/* + * Delete card's pending timers, send STOP to linklevel + */ +static void +icn_stopcard(icn_card *card) +{ + unsigned long flags; + isdn_ctrl cmd; + + spin_lock_irqsave(&card->lock, flags); + if (card->flags & ICN_FLAGS_RUNNING) { + card->flags &= ~ICN_FLAGS_RUNNING; + del_timer(&card->st_timer); + del_timer(&card->rb_timer); + spin_unlock_irqrestore(&card->lock, flags); + cmd.command = ISDN_STAT_STOP; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + if (card->doubleS0) + icn_stopcard(card->other); + } else + spin_unlock_irqrestore(&card->lock, flags); +} + +static void +icn_stopallcards(void) +{ + icn_card *p = cards; + + while (p) { + icn_stopcard(p); + p = p->next; + } +} + +/* + * Unmap all cards, because some of them may be mapped accidetly during + * autoprobing of some network drivers (SMC-driver?) + */ +static void +icn_disable_cards(void) +{ + icn_card *card = cards; + + while (card) { + if (!request_region(card->port, ICN_PORTLEN, "icn-isdn")) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, + card->port, + card->port + ICN_PORTLEN); + } else { + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + release_region(card->port, ICN_PORTLEN); + } + card = card->next; + } +} + +static int +icn_command(isdn_ctrl *c, icn_card *card) +{ + ulong a; + ulong flags; + int i; + char cbuf[80]; + isdn_ctrl cmd; + icn_cdef cdef; + char __user *arg; + + switch (c->command) { + case ISDN_CMD_IOCTL: + memcpy(&a, c->parm.num, sizeof(ulong)); + arg = (char __user *)a; + switch (c->arg) { + case ICN_IOCTL_SETMMIO: + if (dev.memaddr != (a & 0x0ffc000)) { + if (!request_mem_region(a & 0x0ffc000, 0x4000, "icn-isdn (all cards)")) { + printk(KERN_WARNING + "icn: memory at 0x%08lx in use.\n", + a & 0x0ffc000); + return -EINVAL; + } + release_mem_region(a & 0x0ffc000, 0x4000); + icn_stopallcards(); + spin_lock_irqsave(&card->lock, flags); + if (dev.mvalid) { + iounmap(dev.shmem); + release_mem_region(dev.memaddr, 0x4000); + } + dev.mvalid = 0; + dev.memaddr = a & 0x0ffc000; + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_INFO + "icn: (%s) mmio set to 0x%08lx\n", + CID, + dev.memaddr); + } + break; + case ICN_IOCTL_GETMMIO: + return (long) dev.memaddr; + case ICN_IOCTL_SETPORT: + if (a == 0x300 || a == 0x310 || a == 0x320 || a == 0x330 + || a == 0x340 || a == 0x350 || a == 0x360 || + a == 0x308 || a == 0x318 || a == 0x328 || a == 0x338 + || a == 0x348 || a == 0x358 || a == 0x368) { + if (card->port != (unsigned short) a) { + if (!request_region((unsigned short) a, ICN_PORTLEN, "icn-isdn")) { + printk(KERN_WARNING + "icn: (%s) ports 0x%03x-0x%03x in use.\n", + CID, (int) a, (int) a + ICN_PORTLEN); + return -EINVAL; + } + release_region((unsigned short) a, ICN_PORTLEN); + icn_stopcard(card); + spin_lock_irqsave(&card->lock, flags); + if (card->rvalid) + release_region(card->port, ICN_PORTLEN); + card->port = (unsigned short) a; + card->rvalid = 0; + if (card->doubleS0) { + card->other->port = (unsigned short) a; + card->other->rvalid = 0; + } + spin_unlock_irqrestore(&card->lock, flags); + printk(KERN_INFO + "icn: (%s) port set to 0x%03x\n", + CID, card->port); + } + } else + return -EINVAL; + break; + case ICN_IOCTL_GETPORT: + return (int) card->port; + case ICN_IOCTL_GETDOUBLE: + return (int) card->doubleS0; + case ICN_IOCTL_DEBUGVAR: + if (copy_to_user(arg, + &card, + sizeof(ulong))) + return -EFAULT; + a += sizeof(ulong); + { + ulong l = (ulong)&dev; + if (copy_to_user(arg, + &l, + sizeof(ulong))) + return -EFAULT; + } + return 0; + case ICN_IOCTL_LOADBOOT: + if (dev.firstload) { + icn_disable_cards(); + dev.firstload = 0; + } + icn_stopcard(card); + return (icn_loadboot(arg, card)); + case ICN_IOCTL_LOADPROTO: + icn_stopcard(card); + if ((i = (icn_loadproto(arg, card)))) + return i; + if (card->doubleS0) + i = icn_loadproto(arg + ICN_CODE_STAGE2, card->other); + return i; + break; + case ICN_IOCTL_ADDCARD: + if (!dev.firstload) + return -EBUSY; + if (copy_from_user(&cdef, + arg, + sizeof(cdef))) + return -EFAULT; + return (icn_addcard(cdef.port, cdef.id1, cdef.id2)); + break; + case ICN_IOCTL_LEASEDCFG: + if (a) { + if (!card->leased) { + card->leased = 1; + while (card->ptype == ISDN_PTYPE_UNKNOWN) { + msleep_interruptible(ICN_BOOT_TIMEOUT1); + } + msleep_interruptible(ICN_BOOT_TIMEOUT1); + sprintf(cbuf, "00;FV2ON\n01;EAZ%c\n02;EAZ%c\n", + (a & 1) ? '1' : 'C', (a & 2) ? '2' : 'C'); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "icn: (%s) Leased-line mode enabled\n", + CID); + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + } else { + if (card->leased) { + card->leased = 0; + sprintf(cbuf, "00;FV2OFF\n"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + printk(KERN_INFO + "icn: (%s) Leased-line mode disabled\n", + CID); + cmd.command = ISDN_STAT_RUN; + cmd.driver = card->myid; + cmd.arg = 0; + card->interface.statcallb(&cmd); + } + } + return 0; + default: + return -EINVAL; + } + break; + case ISDN_CMD_DIAL: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (card->leased) + break; + if ((c->arg & 255) < ICN_BCH) { + char *p; + char dcode[4]; + + a = c->arg; + p = c->parm.setup.phone; + if (*p == 's' || *p == 'S') { + /* Dial for SPV */ + p++; + strcpy(dcode, "SCA"); + } else + /* Normal Dial */ + strcpy(dcode, "CAL"); + snprintf(cbuf, sizeof(cbuf), + "%02d;D%s_R%s,%02d,%02d,%s\n", (int) (a + 1), + dcode, p, c->parm.setup.si1, + c->parm.setup.si2, c->parm.setup.eazmsn); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTD: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->fw_rev >= 300) { + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) a); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) a); + break; + } + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + sprintf(cbuf, "%02d;DCON_R\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_ACCEPTB: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->fw_rev >= 300) + switch (card->l2_proto[a - 1]) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BCON_R,BX75\n", (int) a); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BCON_R,BTRA\n", (int) a); + break; + } else + sprintf(cbuf, "%02d;BCON_R\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_HANGUP: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + sprintf(cbuf, "%02d;BDIS_R\n%02d;DDIS_R\n", (int) a, (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETEAZ: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->ptype == ISDN_PTYPE_EURO) { + sprintf(cbuf, "%02d;MS%s%s\n", (int) a, + c->parm.num[0] ? "N" : "ALL", c->parm.num); + } else + sprintf(cbuf, "%02d;EAZ%s\n", (int) a, + c->parm.num[0] ? (char *)(c->parm.num) : "0123456789"); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_CLREAZ: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if (card->leased) + break; + if (c->arg < ICN_BCH) { + a = c->arg + 1; + if (card->ptype == ISDN_PTYPE_EURO) + sprintf(cbuf, "%02d;MSNC\n", (int) a); + else + sprintf(cbuf, "%02d;EAZC\n", (int) a); + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + } + break; + case ISDN_CMD_SETL2: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + if ((c->arg & 255) < ICN_BCH) { + a = c->arg; + switch (a >> 8) { + case ISDN_PROTO_L2_X75I: + sprintf(cbuf, "%02d;BX75\n", (int) (a & 255) + 1); + break; + case ISDN_PROTO_L2_HDLC: + sprintf(cbuf, "%02d;BTRA\n", (int) (a & 255) + 1); + break; + default: + return -EINVAL; + } + i = icn_writecmd(cbuf, strlen(cbuf), 0, card); + card->l2_proto[a & 255] = (a >> 8); + } + break; + case ISDN_CMD_SETL3: + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + return 0; + default: + return -EINVAL; + } + return 0; +} + +/* + * Find card with given driverId + */ +static inline icn_card * +icn_findcard(int driverid) +{ + icn_card *p = cards; + + while (p) { + if (p->myid == driverid) + return p; + p = p->next; + } + return (icn_card *) 0; +} + +/* + * Wrapper functions for interface to linklevel + */ +static int +if_command(isdn_ctrl *c) +{ + icn_card *card = icn_findcard(c->driver); + + if (card) + return (icn_command(c, card)); + printk(KERN_ERR + "icn: if_command %d called with invalid driverId %d!\n", + c->command, c->driver); + return -ENODEV; +} + +static int +if_writecmd(const u_char __user *buf, int len, int id, int channel) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + return (icn_writecmd(buf, len, 1, card)); + } + printk(KERN_ERR + "icn: if_writecmd called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_readstatus(u_char __user *buf, int len, int id, int channel) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + return (icn_readstatus(buf, len, card)); + } + printk(KERN_ERR + "icn: if_readstatus called with invalid driverId!\n"); + return -ENODEV; +} + +static int +if_sendbuf(int id, int channel, int ack, struct sk_buff *skb) +{ + icn_card *card = icn_findcard(id); + + if (card) { + if (!(card->flags & ICN_FLAGS_RUNNING)) + return -ENODEV; + return (icn_sendbuf(channel, ack, skb, card)); + } + printk(KERN_ERR + "icn: if_sendbuf called with invalid driverId!\n"); + return -ENODEV; +} + +/* + * Allocate a new card-struct, initialize it + * link it into cards-list and register it at linklevel. + */ +static icn_card * +icn_initcard(int port, char *id) +{ + icn_card *card; + int i; + + if (!(card = kzalloc(sizeof(icn_card), GFP_KERNEL))) { + printk(KERN_WARNING + "icn: (%s) Could not allocate card-struct.\n", id); + return (icn_card *) 0; + } + spin_lock_init(&card->lock); + card->port = port; + card->interface.owner = THIS_MODULE; + card->interface.hl_hdrlen = 1; + card->interface.channels = ICN_BCH; + card->interface.maxbufsize = 4000; + card->interface.command = if_command; + card->interface.writebuf_skb = if_sendbuf; + card->interface.writecmd = if_writecmd; + card->interface.readstat = if_readstatus; + card->interface.features = ISDN_FEATURE_L2_X75I | + ISDN_FEATURE_L2_HDLC | + ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_P_UNKNOWN; + card->ptype = ISDN_PTYPE_UNKNOWN; + strlcpy(card->interface.id, id, sizeof(card->interface.id)); + card->msg_buf_write = card->msg_buf; + card->msg_buf_read = card->msg_buf; + card->msg_buf_end = &card->msg_buf[sizeof(card->msg_buf) - 1]; + for (i = 0; i < ICN_BCH; i++) { + card->l2_proto[i] = ISDN_PROTO_L2_X75I; + skb_queue_head_init(&card->spqueue[i]); + } + card->next = cards; + cards = card; + if (!register_isdn(&card->interface)) { + cards = cards->next; + printk(KERN_WARNING + "icn: Unable to register %s\n", id); + kfree(card); + return (icn_card *) 0; + } + card->myid = card->interface.channels; + sprintf(card->regname, "icn-isdn (%s)", card->interface.id); + return card; +} + +static int +icn_addcard(int port, char *id1, char *id2) +{ + icn_card *card; + icn_card *card2; + + if (!(card = icn_initcard(port, id1))) { + return -EIO; + } + if (!strlen(id2)) { + printk(KERN_INFO + "icn: (%s) ICN-2B, port 0x%x added\n", + card->interface.id, port); + return 0; + } + if (!(card2 = icn_initcard(port, id2))) { + printk(KERN_INFO + "icn: (%s) half ICN-4B, port 0x%x added\n", id2, port); + return 0; + } + card->doubleS0 = 1; + card->secondhalf = 0; + card->other = card2; + card2->doubleS0 = 1; + card2->secondhalf = 1; + card2->other = card; + printk(KERN_INFO + "icn: (%s and %s) ICN-4B, port 0x%x added\n", + card->interface.id, card2->interface.id, port); + return 0; +} + +#ifndef MODULE +static int __init +icn_setup(char *line) +{ + char *p, *str; + int ints[3]; + static char sid[20]; + static char sid2[20]; + + str = get_options(line, 2, ints); + if (ints[0]) + portbase = ints[1]; + if (ints[0] > 1) + membase = (unsigned long)ints[2]; + if (str && *str) { + strlcpy(sid, str, sizeof(sid)); + icn_id = sid; + if ((p = strchr(sid, ','))) { + *p++ = 0; + strcpy(sid2, p); + icn_id2 = sid2; + } + } + return (1); +} +__setup("icn=", icn_setup); +#endif /* MODULE */ + +static int __init icn_init(void) +{ + char *p; + char rev[21]; + + memset(&dev, 0, sizeof(icn_dev)); + dev.memaddr = (membase & 0x0ffc000); + dev.channel = -1; + dev.mcard = NULL; + dev.firstload = 1; + spin_lock_init(&dev.devlock); + + if ((p = strchr(revision, ':'))) { + strncpy(rev, p + 1, 20); + rev[20] = '\0'; + p = strchr(rev, '$'); + if (p) + *p = 0; + } else + strcpy(rev, " ??? "); + printk(KERN_NOTICE "ICN-ISDN-driver Rev%smem=0x%08lx\n", rev, + dev.memaddr); + return (icn_addcard(portbase, icn_id, icn_id2)); +} + +static void __exit icn_exit(void) +{ + isdn_ctrl cmd; + icn_card *card = cards; + icn_card *last, *tmpcard; + int i; + unsigned long flags; + + icn_stopallcards(); + while (card) { + cmd.command = ISDN_STAT_UNLOAD; + cmd.driver = card->myid; + card->interface.statcallb(&cmd); + spin_lock_irqsave(&card->lock, flags); + if (card->rvalid) { + OUTB_P(0, ICN_RUN); /* Reset Controller */ + OUTB_P(0, ICN_MAPRAM); /* Disable RAM */ + if (card->secondhalf || (!card->doubleS0)) { + release_region(card->port, ICN_PORTLEN); + card->rvalid = 0; + } + for (i = 0; i < ICN_BCH; i++) + icn_free_queue(card, i); + } + tmpcard = card->next; + spin_unlock_irqrestore(&card->lock, flags); + card = tmpcard; + } + card = cards; + cards = NULL; + while (card) { + last = card; + card = card->next; + kfree(last); + } + if (dev.mvalid) { + iounmap(dev.shmem); + release_mem_region(dev.memaddr, 0x4000); + } + printk(KERN_NOTICE "ICN-ISDN-driver unloaded\n"); +} + +module_init(icn_init); +module_exit(icn_exit); diff --git a/drivers/staging/i4l/icn/icn.h b/drivers/staging/i4l/icn/icn.h new file mode 100644 index 000000000..f8f2e76d3 --- /dev/null +++ b/drivers/staging/i4l/icn/icn.h @@ -0,0 +1,253 @@ +/* $Id: icn.h,v 1.30.6.5 2001/09/23 22:24:55 kai Exp $ + * + * ISDN lowlevel-module for the ICN active ISDN-Card. + * + * Copyright 1994 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. + * + */ + +#ifndef icn_h +#define icn_h + +#define ICN_IOCTL_SETMMIO 0 +#define ICN_IOCTL_GETMMIO 1 +#define ICN_IOCTL_SETPORT 2 +#define ICN_IOCTL_GETPORT 3 +#define ICN_IOCTL_LOADBOOT 4 +#define ICN_IOCTL_LOADPROTO 5 +#define ICN_IOCTL_LEASEDCFG 6 +#define ICN_IOCTL_GETDOUBLE 7 +#define ICN_IOCTL_DEBUGVAR 8 +#define ICN_IOCTL_ADDCARD 9 + +/* Struct for adding new cards */ +typedef struct icn_cdef { + int port; + char id1[10]; + char id2[10]; +} icn_cdef; + +#if defined(__KERNEL__) || defined(__DEBUGVAR__) + +#ifdef __KERNEL__ +/* Kernel includes */ + +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/major.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/signal.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/isdnif.h> + +#endif /* __KERNEL__ */ + +/* some useful macros for debugging */ +#ifdef ICN_DEBUG_PORT +#define OUTB_P(v, p) {printk(KERN_DEBUG "icn: outb_p(0x%02x,0x%03x)\n", v, p); outb_p(v, p);} +#else +#define OUTB_P outb +#endif + +/* Defaults for Port-Address and shared-memory */ +#define ICN_BASEADDR 0x320 +#define ICN_PORTLEN (0x04) +#define ICN_MEMADDR 0x0d0000 + +#define ICN_FLAGS_B1ACTIVE 1 /* B-Channel-1 is open */ +#define ICN_FLAGS_B2ACTIVE 2 /* B-Channel-2 is open */ +#define ICN_FLAGS_RUNNING 4 /* Cards driver activated */ +#define ICN_FLAGS_RBTIMER 8 /* cyclic scheduling of B-Channel-poll */ + +#define ICN_BOOT_TIMEOUT1 1000 /* Delay for Boot-download (msecs) */ + +#define ICN_TIMER_BCREAD (HZ / 100) /* B-Channel poll-cycle */ +#define ICN_TIMER_DCREAD (HZ / 2) /* D-Channel poll-cycle */ + +#define ICN_CODE_STAGE1 4096 /* Size of bootcode */ +#define ICN_CODE_STAGE2 65536 /* Size of protocol-code */ + +#define ICN_MAX_SQUEUE 8000 /* Max. outstanding send-data (2* hw-buf.) */ +#define ICN_FRAGSIZE (250) /* Max. size of send-fragments */ +#define ICN_BCH 2 /* Number of supported channels per card */ + +/* type-definitions for accessing the mmap-io-areas */ + +#define SHM_DCTL_OFFSET (0) /* Offset to data-controlstructures in shm */ +#define SHM_CCTL_OFFSET (0x1d2) /* Offset to comm-controlstructures in shm */ +#define SHM_CBUF_OFFSET (0x200) /* Offset to comm-buffers in shm */ +#define SHM_DBUF_OFFSET (0x2000) /* Offset to data-buffers in shm */ + +/* + * Layout of card's data buffers + */ +typedef struct { + unsigned char length; /* Bytecount of fragment (max 250) */ + unsigned char endflag; /* 0=last frag., 0xff=frag. continued */ + unsigned char data[ICN_FRAGSIZE]; /* The data */ + /* Fill to 256 bytes */ + char unused[0x100 - ICN_FRAGSIZE - 2]; +} frag_buf; + +/* + * Layout of card's shared memory + */ +typedef union { + struct { + unsigned char scns; /* Index to free SendFrag. */ + unsigned char scnr; /* Index to active SendFrag READONLY */ + unsigned char ecns; /* Index to free RcvFrag. READONLY */ + unsigned char ecnr; /* Index to valid RcvFrag */ + char unused[6]; + unsigned short fuell1; /* Internal Buf Bytecount */ + } data_control; + struct { + char unused[SHM_CCTL_OFFSET]; + unsigned char iopc_i; /* Read-Ptr Status-Queue READONLY */ + unsigned char iopc_o; /* Write-Ptr Status-Queue */ + unsigned char pcio_i; /* Write-Ptr Command-Queue */ + unsigned char pcio_o; /* Read-Ptr Command Queue READONLY */ + } comm_control; + struct { + char unused[SHM_CBUF_OFFSET]; + unsigned char pcio_buf[0x100]; /* Ring-Buffer Command-Queue */ + unsigned char iopc_buf[0x100]; /* Ring-Buffer Status-Queue */ + } comm_buffers; + struct { + char unused[SHM_DBUF_OFFSET]; + frag_buf receive_buf[0x10]; + frag_buf send_buf[0x10]; + } data_buffers; +} icn_shmem; + +/* + * Per card driver data + */ +typedef struct icn_card { + struct icn_card *next; /* Pointer to next device struct */ + struct icn_card *other; /* Pointer to other card for ICN4B */ + unsigned short port; /* Base-port-address */ + int myid; /* Driver-Nr. assigned by linklevel */ + int rvalid; /* IO-portregion has been requested */ + int leased; /* Flag: This Adapter is connected */ + /* to a leased line */ + unsigned short flags; /* Statusflags */ + int doubleS0; /* Flag: ICN4B */ + int secondhalf; /* Flag: Second half of a doubleS0 */ + int fw_rev; /* Firmware revision loaded */ + int ptype; /* Protocol type (1TR6 or Euro) */ + struct timer_list st_timer; /* Timer for Status-Polls */ + struct timer_list rb_timer; /* Timer for B-Channel-Polls */ + u_char rcvbuf[ICN_BCH][4096]; /* B-Channel-Receive-Buffers */ + int rcvidx[ICN_BCH]; /* Index for above buffers */ + int l2_proto[ICN_BCH]; /* Current layer-2-protocol */ + isdn_if interface; /* Interface to upper layer */ + int iptr; /* Index to imsg-buffer */ + char imsg[60]; /* Internal buf for status-parsing */ + char msg_buf[2048]; /* Buffer for status-messages */ + char *msg_buf_write; /* Writepointer for statusbuffer */ + char *msg_buf_read; /* Readpointer for statusbuffer */ + char *msg_buf_end; /* Pointer to end of statusbuffer */ + int sndcount[ICN_BCH]; /* Byte-counters for B-Ch.-send */ + int xlen[ICN_BCH]; /* Byte-counters/Flags for sent-ACK */ + struct sk_buff *xskb[ICN_BCH]; /* Current transmitted skb */ + struct sk_buff_head spqueue[ICN_BCH]; /* Sendqueue */ + char regname[35]; /* Name used for request_region */ + u_char xmit_lock[ICN_BCH]; /* Semaphore for pollbchan_send()*/ + spinlock_t lock; /* protect critical operations */ +} icn_card; + +/* + * Main driver data + */ +typedef struct icn_dev { + spinlock_t devlock; /* spinlock to protect this struct */ + unsigned long memaddr; /* Address of memory mapped buffers */ + icn_shmem __iomem *shmem; /* Pointer to memory-mapped-buffers */ + int mvalid; /* IO-shmem has been requested */ + int channel; /* Currently mapped channel */ + struct icn_card *mcard; /* Currently mapped card */ + int chanlock; /* Semaphore for channel-mapping */ + int firstload; /* Flag: firmware never loaded */ +} icn_dev; + +typedef icn_dev *icn_devptr; + +#ifdef __KERNEL__ + +static icn_card *cards = (icn_card *) 0; +static u_char chan2bank[] = +{0, 4, 8, 12}; /* for icn_map_channel() */ + +static icn_dev dev; + +#endif /* __KERNEL__ */ + +/* Utility-Macros */ + +/* Macros for accessing ports */ +#define ICN_CFG (card->port) +#define ICN_MAPRAM (card->port + 1) +#define ICN_RUN (card->port + 2) +#define ICN_BANK (card->port + 3) + +/* Return true, if there is a free transmit-buffer */ +#define sbfree (((readb(&dev.shmem->data_control.scns) + 1) & 0xf) != \ + readb(&dev.shmem->data_control.scnr)) + +/* Switch to next transmit-buffer */ +#define sbnext (writeb((readb(&dev.shmem->data_control.scns) + 1) & 0xf, \ + &dev.shmem->data_control.scns)) + +/* Shortcuts for transmit-buffer-access */ +#define sbuf_n dev.shmem->data_control.scns +#define sbuf_d dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].data +#define sbuf_l dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].length +#define sbuf_f dev.shmem->data_buffers.send_buf[readb(&sbuf_n)].endflag + +/* Return true, if there is receive-data is available */ +#define rbavl (readb(&dev.shmem->data_control.ecnr) != \ + readb(&dev.shmem->data_control.ecns)) + +/* Switch to next receive-buffer */ +#define rbnext (writeb((readb(&dev.shmem->data_control.ecnr) + 1) & 0xf, \ + &dev.shmem->data_control.ecnr)) + +/* Shortcuts for receive-buffer-access */ +#define rbuf_n dev.shmem->data_control.ecnr +#define rbuf_d dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].data +#define rbuf_l dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].length +#define rbuf_f dev.shmem->data_buffers.receive_buf[readb(&rbuf_n)].endflag + +/* Shortcuts for command-buffer-access */ +#define cmd_o (dev.shmem->comm_control.pcio_o) +#define cmd_i (dev.shmem->comm_control.pcio_i) + +/* Return free space in command-buffer */ +#define cmd_free ((readb(&cmd_i) >= readb(&cmd_o)) ? \ + 0x100 - readb(&cmd_i) + readb(&cmd_o) : \ + readb(&cmd_o) - readb(&cmd_i)) + +/* Shortcuts for message-buffer-access */ +#define msg_o (dev.shmem->comm_control.iopc_o) +#define msg_i (dev.shmem->comm_control.iopc_i) + +/* Return length of Message, if avail. */ +#define msg_avail ((readb(&msg_o) > readb(&msg_i)) ? \ + 0x100 - readb(&msg_o) + readb(&msg_i) : \ + readb(&msg_i) - readb(&msg_o)) + +#define CID (card->interface.id) + +#endif /* defined(__KERNEL__) || defined(__DEBUGVAR__) */ +#endif /* icn_h */ diff --git a/drivers/staging/i4l/pcbit/Kconfig b/drivers/staging/i4l/pcbit/Kconfig new file mode 100644 index 000000000..e9b2dd85d --- /dev/null +++ b/drivers/staging/i4l/pcbit/Kconfig @@ -0,0 +1,10 @@ +config ISDN_DRV_PCBIT + tristate "PCBIT-D support" + depends on ISA && (BROKEN || X86) + help + This enables support for the PCBIT ISDN-card. This card is + manufactured in Portugal by Octal. For running this card, + additional firmware is necessary, which has to be downloaded into + the card using a utility which is distributed separately. See + <file:Documentation/isdn/README> and + <file:Documentation/isdn/README.pcbit> for more information. diff --git a/drivers/staging/i4l/pcbit/Makefile b/drivers/staging/i4l/pcbit/Makefile new file mode 100644 index 000000000..2d026c324 --- /dev/null +++ b/drivers/staging/i4l/pcbit/Makefile @@ -0,0 +1,9 @@ +# Makefile for the pcbit ISDN device driver + +# Each configuration option enables a list of files. + +obj-$(CONFIG_ISDN_DRV_PCBIT) += pcbit.o + +# Multipart objects. + +pcbit-y := module.o edss1.o drv.o layer2.o capi.o callbacks.o diff --git a/drivers/staging/i4l/pcbit/callbacks.c b/drivers/staging/i4l/pcbit/callbacks.c new file mode 100644 index 000000000..efb6d6a36 --- /dev/null +++ b/drivers/staging/i4l/pcbit/callbacks.c @@ -0,0 +1,345 @@ +/* + * Callbacks for the FSM + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +/* + * Fix: 19981230 - Carlos Morgado <chbm@techie.com> + * Port of Nelson Escravana's <nelson.escravana@usa.net> fix to CalledPN + * NULL pointer dereference in cb_in_1 (originally fixed in 2.0) + */ + +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/skbuff.h> + +#include <asm/io.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "layer2.h" +#include "edss1.h" +#include "callbacks.h" +#include "capi.h" + +ushort last_ref_num = 1; + +/* + * send_conn_req + * + */ + +void cb_out_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *cbdata) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + +#ifdef DEBUG + printk(KERN_DEBUG "Called Party Number: %s\n", + cbdata->data.setup.CalledPN); +#endif + /* + * hdr - kmalloc in capi_conn_req + * - kfree when msg has been sent + */ + + if ((len = capi_conn_req(cbdata->data.setup.CalledPN, &skb, + chan->proto)) < 0) + { + printk("capi_conn_req failed\n"); + return; + } + + + refnum = last_ref_num++ & 0x7fffU; + + chan->callref = 0; + chan->layer2link = 0; + chan->snum = 0; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_REQ, refnum, skb, len); +} + +/* + * rcv CONNECT + * will go into ACTIVE state + * send CONN_ACTIVE_RESP + * send Select protocol request + */ + +void cb_out_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len = capi_conn_active_resp(chan, &skb)) < 0) + { + printk("capi_conn_active_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_ACTV_RESP, refnum, skb, len); + + + ictl.command = ISDN_STAT_DCONN; + ictl.driver = dev->id; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); + + /* ACTIVE D-channel */ + + /* Select protocol */ + + if ((len = capi_select_proto_req(chan, &skb, 1 /*outgoing*/)) < 0) { + printk("capi_select_proto_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len); +} + + +/* + * Incoming call received + * inform user + */ + +void cb_in_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *cbdata) +{ + isdn_ctrl ictl; + unsigned short refnum; + struct sk_buff *skb; + int len; + + + ictl.command = ISDN_STAT_ICALL; + ictl.driver = dev->id; + ictl.arg = chan->id; + + /* + * ictl.num >= strlen() + strlen() + 5 + */ + + if (cbdata->data.setup.CallingPN == NULL) { + printk(KERN_DEBUG "NULL CallingPN to phone; using 0\n"); + strcpy(ictl.parm.setup.phone, "0"); + } + else { + strcpy(ictl.parm.setup.phone, cbdata->data.setup.CallingPN); + } + if (cbdata->data.setup.CalledPN == NULL) { + printk(KERN_DEBUG "NULL CalledPN to eazmsn; using 0\n"); + strcpy(ictl.parm.setup.eazmsn, "0"); + } + else { + strcpy(ictl.parm.setup.eazmsn, cbdata->data.setup.CalledPN); + } + ictl.parm.setup.si1 = 7; + ictl.parm.setup.si2 = 0; + ictl.parm.setup.plan = 0; + ictl.parm.setup.screen = 0; + +#ifdef DEBUG + printk(KERN_DEBUG "statstr: %s\n", ictl.num); +#endif + + dev->dev_if->statcallb(&ictl); + + + if ((len = capi_conn_resp(chan, &skb)) < 0) { + printk(KERN_DEBUG "capi_conn_resp failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_CONN_RESP, refnum, skb, len); +} + +/* + * user has replied + * open the channel + * send CONNECT message CONNECT_ACTIVE_REQ in CAPI + */ + +void cb_in_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + unsigned short refnum; + struct sk_buff *skb; + int len; + + if ((len = capi_conn_active_req(chan, &skb)) < 0) { + printk(KERN_DEBUG "capi_conn_active_req failed\n"); + return; + } + + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + printk(KERN_DEBUG "sending MSG_CONN_ACTV_REQ\n"); + pcbit_l2_write(dev, MSG_CONN_ACTV_REQ, refnum, skb, len); +} + +/* + * CONN_ACK arrived + * start b-proto selection + * + */ + +void cb_in_3(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + unsigned short refnum; + struct sk_buff *skb; + int len; + + if ((len = capi_select_proto_req(chan, &skb, 0 /*incoming*/)) < 0) + { + printk("capi_select_proto_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_SELP_REQ, refnum, skb, len); + +} + + +/* + * Received disconnect ind on active state + * send disconnect resp + * send msg to user + */ +void cb_disc_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + isdn_ctrl ictl; + + if ((len = capi_disc_resp(chan, &skb)) < 0) { + printk("capi_disc_resp failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_DISC_RESP, refnum, skb, len); + + ictl.command = ISDN_STAT_BHUP; + ictl.driver = dev->id; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); +} + + +/* + * User HANGUP on active/call proceeding state + * send disc.req + */ +void cb_disc_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len = capi_disc_req(chan->callref, &skb, CAUSE_NORMAL)) < 0) + { + printk("capi_disc_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb, len); +} + +/* + * Disc confirm received send BHUP + * Problem: when the HL driver sends the disc req itself + * LL receives BHUP + */ +void cb_disc_3(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + + ictl.command = ISDN_STAT_BHUP; + ictl.driver = dev->id; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); +} + +void cb_notdone(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ +} + +/* + * send activate b-chan protocol + */ +void cb_selp_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + struct sk_buff *skb; + int len; + ushort refnum; + + if ((len = capi_activate_transp_req(chan, &skb)) < 0) + { + printk("capi_conn_activate_transp_req failed\n"); + return; + } + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_ACT_TRANSP_REQ, refnum, skb, len); +} + +/* + * Inform User that the B-channel is available + */ +void cb_open(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data) +{ + isdn_ctrl ictl; + + ictl.command = ISDN_STAT_BCONN; + ictl.driver = dev->id; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); +} diff --git a/drivers/staging/i4l/pcbit/callbacks.h b/drivers/staging/i4l/pcbit/callbacks.h new file mode 100644 index 000000000..a036b4a7f --- /dev/null +++ b/drivers/staging/i4l/pcbit/callbacks.h @@ -0,0 +1,44 @@ +/* + * Callbacks prototypes for FSM + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +#ifndef CALLBACKS_H +#define CALLBACKS_H + + +extern void cb_out_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +extern void cb_out_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +extern void cb_in_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); +extern void cb_in_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); +extern void cb_in_3(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +extern void cb_disc_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); +extern void cb_disc_2(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); +extern void cb_disc_3(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +extern void cb_notdone(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +extern void cb_selp_1(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); +extern void cb_open(struct pcbit_dev *dev, struct pcbit_chan *chan, + struct callb_data *data); + +#endif diff --git a/drivers/staging/i4l/pcbit/capi.c b/drivers/staging/i4l/pcbit/capi.c new file mode 100644 index 000000000..4e3cbf857 --- /dev/null +++ b/drivers/staging/i4l/pcbit/capi.c @@ -0,0 +1,649 @@ +/* + * CAPI encoder/decoder for + * Portugal Telecom CAPI 2.0 + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + * + * Not compatible with the AVM Gmbh. CAPI 2.0 + * + */ + +/* + * Documentation: + * - "Common ISDN API - Perfil Português - Versão 2.1", + * Telecom Portugal, Fev 1992. + * - "Common ISDN API - Especificação de protocolos para + * acesso aos canais B", Inesc, Jan 1994. + */ + +/* + * TODO: better decoding of Information Elements + * for debug purposes mainly + * encode our number in CallerPN and ConnectedPN + */ + +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/mm.h> + +#include <linux/skbuff.h> + +#include <asm/io.h> +#include <asm/string.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "edss1.h" +#include "capi.h" + + +/* + * Encoding of CAPI messages + * + */ + +int capi_conn_req(const char *calledPN, struct sk_buff **skb, int proto) +{ + ushort len; + + /* + * length + * AppInfoMask - 2 + * BC0 - 3 + * BC1 - 1 + * Chan - 2 + * Keypad - 1 + * CPN - 1 + * CPSA - 1 + * CalledPN - 2 + strlen + * CalledPSA - 1 + * rest... - 4 + * ---------------- + * Total 18 + strlen + */ + + len = 18 + strlen(calledPN); + + if (proto == ISDN_PROTO_L2_TRANS) + len++; + + if ((*skb = dev_alloc_skb(len)) == NULL) { + + printk(KERN_WARNING "capi_conn_req: alloc_skb failed\n"); + return -1; + } + + /* InfoElmMask */ + *((ushort *)skb_put(*skb, 2)) = AppInfoMask; + + if (proto == ISDN_PROTO_L2_TRANS) + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 3; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x80; /* Speech */ + *(skb_put(*skb, 1)) = 0x10; /* Circuit Mode */ + *(skb_put(*skb, 1)) = 0x23; /* A-law */ + } + else + { + /* Bearer Capability - Mandatory*/ + *(skb_put(*skb, 1)) = 2; /* BC0.Length */ + *(skb_put(*skb, 1)) = 0x88; /* Digital Information */ + *(skb_put(*skb, 1)) = 0x90; /* BC0.Octect4 */ + } + + /* Bearer Capability - Optional*/ + *(skb_put(*skb, 1)) = 0; /* BC1.Length = 0 */ + + *(skb_put(*skb, 1)) = 1; /* ChannelID.Length = 1 */ + *(skb_put(*skb, 1)) = 0x83; /* Basic Interface - Any Channel */ + + *(skb_put(*skb, 1)) = 0; /* Keypad.Length = 0 */ + + + *(skb_put(*skb, 1)) = 0; /* CallingPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* CallingPSA.Length = 0 */ + + /* Called Party Number */ + *(skb_put(*skb, 1)) = strlen(calledPN) + 1; + *(skb_put(*skb, 1)) = 0x81; + memcpy(skb_put(*skb, strlen(calledPN)), calledPN, strlen(calledPN)); + + /* '#' */ + + *(skb_put(*skb, 1)) = 0; /* CalledPSA.Length = 0 */ + + /* LLC.Length = 0; */ + /* HLC0.Length = 0; */ + /* HLC1.Length = 0; */ + /* UTUS.Length = 0; */ + memset(skb_put(*skb, 4), 0, 4); + + return len; +} + +int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(5)) == NULL) { + + printk(KERN_WARNING "capi_conn_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + *(skb_put(*skb, 1)) = 0x01; /* ACCEPT_CALL */ + *(skb_put(*skb, 1)) = 0; + *(skb_put(*skb, 1)) = 0; + + return 5; +} + +int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb) +{ + /* + * 8 bytes + */ + + if ((*skb = dev_alloc_skb(8)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + *(skb_put(*skb, 1)) = 0; /* BC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* ConnectedPN.Length = 0 */ + *(skb_put(*skb, 1)) = 0; /* PSA.Length */ + *(skb_put(*skb, 1)) = 0; /* LLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* HLC.Length = 0; */ + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 8; +} + +int capi_conn_active_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + /* + * 2 bytes + */ + + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_conn_active_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + return 2; +} + + +int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb, + int outgoing) +{ + + /* + * 18 bytes + */ + + if ((*skb = dev_alloc_skb(18)) == NULL) { + + printk(KERN_WARNING "capi_select_proto_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + /* Layer2 protocol */ + + switch (chan->proto) { + case ISDN_PROTO_L2_X75I: + *(skb_put(*skb, 1)) = 0x05; /* LAPB */ + break; + case ISDN_PROTO_L2_HDLC: + *(skb_put(*skb, 1)) = 0x02; + break; + case ISDN_PROTO_L2_TRANS: + /* + * Voice (a-law) + */ + *(skb_put(*skb, 1)) = 0x06; + break; + default: +#ifdef DEBUG + printk(KERN_DEBUG "Transparent\n"); +#endif + *(skb_put(*skb, 1)) = 0x03; + break; + } + + *(skb_put(*skb, 1)) = (outgoing ? 0x02 : 0x42); /* Don't ask */ + *(skb_put(*skb, 1)) = 0x00; + + *((ushort *) skb_put(*skb, 2)) = MRU; + + + *(skb_put(*skb, 1)) = 0x08; /* Modulo */ + *(skb_put(*skb, 1)) = 0x07; /* Max Window */ + + *(skb_put(*skb, 1)) = 0x01; /* No Layer3 Protocol */ + + /* + * 2 - layer3 MTU [10] + * - Modulo [12] + * - Window + * - layer1 proto [14] + * - bitrate + * - sub-channel [16] + * - layer1dataformat [17] + */ + + memset(skb_put(*skb, 8), 0, 8); + + return 18; +} + + +int capi_activate_transp_req(struct pcbit_chan *chan, struct sk_buff **skb) +{ + + if ((*skb = dev_alloc_skb(7)) == NULL) { + + printk(KERN_WARNING "capi_activate_transp_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + + *(skb_put(*skb, 1)) = chan->layer2link; /* Layer2 id */ + *(skb_put(*skb, 1)) = 0x00; /* Transmit by default */ + + *((ushort *) skb_put(*skb, 2)) = MRU; + + *(skb_put(*skb, 1)) = 0x01; /* Enables reception*/ + + return 7; +} + +int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort data_len; + + + /* + * callref - 2 + * layer2link - 1 + * wBlockLength - 2 + * data - 4 + * sernum - 1 + */ + + data_len = skb->len; + + if (skb_headroom(skb) < 10) + { + printk(KERN_CRIT "No headspace (%u) on headroom %p for capi header\n", skb_headroom(skb), skb); + } + else + { + skb_push(skb, 10); + } + + *((u16 *) (skb->data)) = chan->callref; + skb->data[2] = chan->layer2link; + *((u16 *) (skb->data + 3)) = data_len; + + chan->s_refnum = (chan->s_refnum + 1) % 8; + *((u32 *) (skb->data + 5)) = chan->s_refnum; + + skb->data[9] = 0; /* HDLC frame number */ + + return 10; +} + +int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb) + +{ + if ((*skb = dev_alloc_skb(4)) == NULL) { + + printk(KERN_WARNING "capi_tdata_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + *(skb_put(*skb, 1)) = chan->layer2link; + *(skb_put(*skb, 1)) = chan->r_refnum; + + return (*skb)->len; +} + +int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause) +{ + + if ((*skb = dev_alloc_skb(6)) == NULL) { + + printk(KERN_WARNING "capi_disc_req: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = callref; + + *(skb_put(*skb, 1)) = 2; /* Cause.Length = 2; */ + *(skb_put(*skb, 1)) = 0x80; + *(skb_put(*skb, 1)) = 0x80 | cause; + + /* + * Change it: we should send 'Sic transit gloria Mundi' here ;-) + */ + + *(skb_put(*skb, 1)) = 0; /* UTUS.Length = 0; */ + + return 6; +} + +int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb) +{ + if ((*skb = dev_alloc_skb(2)) == NULL) { + + printk(KERN_WARNING "capi_disc_resp: alloc_skb failed\n"); + return -1; + } + + *((ushort *)skb_put(*skb, 2)) = chan->callref; + + return 2; +} + + +/* + * Decoding of CAPI messages + * + */ + +int capi_decode_conn_ind(struct pcbit_chan *chan, + struct sk_buff *skb, + struct callb_data *info) +{ + int CIlen, len; + + /* Call Reference [CAPI] */ + chan->callref = *((ushort *)skb->data); + skb_pull(skb, 2); + +#ifdef DEBUG + printk(KERN_DEBUG "Call Reference: %04x\n", chan->callref); +#endif + + /* Channel Identification */ + + /* Expect + Len = 1 + Octect 3 = 0100 10CC - [ 7 Basic, 4 , 2-1 chan ] + */ + + CIlen = skb->data[0]; +#ifdef DEBUG + if (CIlen == 1) { + + if (((skb->data[1]) & 0xFC) == 0x48) + printk(KERN_DEBUG "decode_conn_ind: chan ok\n"); + printk(KERN_DEBUG "phyChan = %d\n", skb->data[1] & 0x03); + } + else + printk(KERN_DEBUG "conn_ind: CIlen = %d\n", CIlen); +#endif + skb_pull(skb, CIlen + 1); + + /* Calling Party Number */ + /* An "additional service" as far as Portugal Telecom is concerned */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + +#ifdef DEBUG + printk(KERN_DEBUG "CPN: Octect 3 %02x\n", skb->data[1]); +#endif + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CallingPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + skb_copy_from_linear_data_offset(skb, count + 1, + info->data.setup.CallingPN, + len - count); + info->data.setup.CallingPN[len - count] = 0; + + } + else { + info->data.setup.CallingPN = NULL; + printk(KERN_DEBUG "NULL CallingPN\n"); + } + + skb_pull(skb, len + 1); + + /* Calling Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* Called Party Number */ + + len = skb->data[0]; + + if (len > 0) { + int count = 1; + + if ((skb->data[1] & 0x80) == 0) + count = 2; + + if (!(info->data.setup.CalledPN = kmalloc(len - count + 1, GFP_ATOMIC))) + return -1; + + skb_copy_from_linear_data_offset(skb, count + 1, + info->data.setup.CalledPN, + len - count); + info->data.setup.CalledPN[len - count] = 0; + + } + else { + info->data.setup.CalledPN = NULL; + printk(KERN_DEBUG "NULL CalledPN\n"); + } + + skb_pull(skb, len + 1); + + /* Called Party Subaddress */ + skb_pull(skb, skb->data[0] + 1); + + /* LLC */ + skb_pull(skb, skb->data[0] + 1); + + /* HLC */ + skb_pull(skb, skb->data[0] + 1); + + /* U2U */ + skb_pull(skb, skb->data[0] + 1); + + return 0; +} + +/* + * returns errcode + */ + +int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb, + int *complete) +{ + int errcode; + + chan->callref = *((ushort *)skb->data); /* Update CallReference */ + skb_pull(skb, 2); + + errcode = *((ushort *) skb->data); /* read errcode */ + skb_pull(skb, 2); + + *complete = *(skb->data); + skb_pull(skb, 1); + + /* FIX ME */ + /* This is actually a firmware bug */ + if (!*complete) + { + printk(KERN_DEBUG "complete=%02x\n", *complete); + *complete = 1; + } + + + /* Optional Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* Channel Identification */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Compatibility follows */ + skb_pull(skb, *(skb->data) + 1); + + return errcode; +} + +int capi_decode_conn_actv_ind(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + char str[32]; +#endif + + /* Yet Another Bearer Capability */ + skb_pull(skb, *(skb->data) + 1); + + + /* Connected Party Number */ + len = *(skb->data); + +#ifdef DEBUG + if (len > 1 && len < 31) { + skb_copy_from_linear_data_offset(skb, 2, str, len - 1); + str[len] = 0; + printk(KERN_DEBUG "Connected Party Number: %s\n", str); + } + else + printk(KERN_DEBUG "actv_ind CPN len = %d\n", len); +#endif + + skb_pull(skb, len + 1); + + /* Connected Subaddress */ + skb_pull(skb, *(skb->data) + 1); + + /* Low Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + /* High Layer Capability */ + skb_pull(skb, *(skb->data) + 1); + + return 0; +} + +int capi_decode_conn_actv_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + /* Channel Identification + skb_pull(skb, skb->data[0] + 1); + */ + return errcode; +} + + +int capi_decode_sel_proto_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + chan->layer2link = *(skb->data); + skb_pull(skb, 1); + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_actv_trans_conf(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort errcode; + + if (chan->layer2link != *(skb->data)) + printk("capi_decode_actv_trans_conf: layer2link doesn't match\n"); + + skb_pull(skb, 1); + + errcode = *((ushort *)skb->data); + skb_pull(skb, 2); + + return errcode; +} + +int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb) +{ + ushort len; +#ifdef DEBUG + int i; +#endif + /* Cause */ + + len = *(skb->data); + skb_pull(skb, 1); + +#ifdef DEBUG + + for (i = 0; i < len; i++) + printk(KERN_DEBUG "Cause Octect %d: %02x\n", i + 3, + *(skb->data + i)); +#endif + + skb_pull(skb, len); + + return 0; +} + +#ifdef DEBUG +int capi_decode_debug_188(u_char *hdr, ushort hdrlen) +{ + char str[64]; + int len; + + len = hdr[0]; + + if (len < 64 && len == hdrlen - 1) { + memcpy(str, hdr + 1, hdrlen - 1); + str[hdrlen - 1] = 0; + printk("%s\n", str); + } + else + printk("debug message incorrect\n"); + + return 0; +} +#endif diff --git a/drivers/staging/i4l/pcbit/capi.h b/drivers/staging/i4l/pcbit/capi.h new file mode 100644 index 000000000..635f63476 --- /dev/null +++ b/drivers/staging/i4l/pcbit/capi.h @@ -0,0 +1,81 @@ +/* + * CAPI encode/decode prototypes and defines + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +#ifndef CAPI_H +#define CAPI_H + + +#define REQ_CAUSE 0x01 +#define REQ_DISPLAY 0x04 +#define REQ_USER_TO_USER 0x08 + +#define AppInfoMask REQ_CAUSE | REQ_DISPLAY | REQ_USER_TO_USER + +/* Connection Setup */ +extern int capi_conn_req(const char *calledPN, struct sk_buff **buf, + int proto); +extern int capi_decode_conn_conf(struct pcbit_chan *chan, struct sk_buff *skb, + int *complete); + +extern int capi_decode_conn_ind(struct pcbit_chan *chan, struct sk_buff *skb, + struct callb_data *info); +extern int capi_conn_resp(struct pcbit_chan *chan, struct sk_buff **skb); + +extern int capi_conn_active_req(struct pcbit_chan *chan, struct sk_buff **skb); +extern int capi_decode_conn_actv_conf(struct pcbit_chan *chan, + struct sk_buff *skb); + +extern int capi_decode_conn_actv_ind(struct pcbit_chan *chan, + struct sk_buff *skb); +extern int capi_conn_active_resp(struct pcbit_chan *chan, + struct sk_buff **skb); + +/* Data */ +extern int capi_select_proto_req(struct pcbit_chan *chan, struct sk_buff **skb, + int outgoing); +extern int capi_decode_sel_proto_conf(struct pcbit_chan *chan, + struct sk_buff *skb); + +extern int capi_activate_transp_req(struct pcbit_chan *chan, + struct sk_buff **skb); +extern int capi_decode_actv_trans_conf(struct pcbit_chan *chan, + struct sk_buff *skb); + +extern int capi_tdata_req(struct pcbit_chan *chan, struct sk_buff *skb); +extern int capi_tdata_resp(struct pcbit_chan *chan, struct sk_buff **skb); + +/* Connection Termination */ +extern int capi_disc_req(ushort callref, struct sk_buff **skb, u_char cause); + +extern int capi_decode_disc_ind(struct pcbit_chan *chan, struct sk_buff *skb); +extern int capi_disc_resp(struct pcbit_chan *chan, struct sk_buff **skb); + +#ifdef DEBUG +extern int capi_decode_debug_188(u_char *hdr, ushort hdrlen); +#endif + +static inline struct pcbit_chan * +capi_channel(struct pcbit_dev *dev, struct sk_buff *skb) +{ + ushort callref; + + callref = *((ushort *)skb->data); + skb_pull(skb, 2); + + if (dev->b1->callref == callref) + return dev->b1; + else if (dev->b2->callref == callref) + return dev->b2; + + return NULL; +} + +#endif diff --git a/drivers/staging/i4l/pcbit/drv.c b/drivers/staging/i4l/pcbit/drv.c new file mode 100644 index 000000000..4172e22ae --- /dev/null +++ b/drivers/staging/i4l/pcbit/drv.c @@ -0,0 +1,1077 @@ +/* + * PCBIT-D interface with isdn4linux + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +/* + * Fixes: + * + * Nuno Grilo <l38486@alfa.ist.utl.pt> + * fixed msn_list NULL pointer dereference. + * + */ + +#include <linux/module.h> + + +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/interrupt.h> +#include <linux/string.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> +#include <asm/string.h> +#include <asm/io.h> +#include <linux/ioport.h> + +#include "pcbit.h" +#include "edss1.h" +#include "layer2.h" +#include "capi.h" + + +extern ushort last_ref_num; + +static int pcbit_ioctl(isdn_ctrl *ctl); + +static char *pcbit_devname[MAX_PCBIT_CARDS] = { + "pcbit0", + "pcbit1", + "pcbit2", + "pcbit3" +}; + +/* + * prototypes + */ + +static int pcbit_command(isdn_ctrl *ctl); +static int pcbit_stat(u_char __user *buf, int len, int, int); +static int pcbit_xmit(int driver, int chan, int ack, struct sk_buff *skb); +static int pcbit_writecmd(const u_char __user *, int, int, int); + +static int set_protocol_running(struct pcbit_dev *dev); + +static void pcbit_clear_msn(struct pcbit_dev *dev); +static void pcbit_set_msn(struct pcbit_dev *dev, char *list); +static int pcbit_check_msn(struct pcbit_dev *dev, char *msn); + + +int pcbit_init_dev(int board, int mem_base, int irq) +{ + struct pcbit_dev *dev; + isdn_if *dev_if; + + if ((dev = kzalloc(sizeof(struct pcbit_dev), GFP_KERNEL)) == NULL) + { + printk("pcbit_init: couldn't malloc pcbit_dev struct\n"); + return -ENOMEM; + } + + dev_pcbit[board] = dev; + init_waitqueue_head(&dev->set_running_wq); + spin_lock_init(&dev->lock); + + if (mem_base >= 0xA0000 && mem_base <= 0xFFFFF) { + dev->ph_mem = mem_base; + if (!request_mem_region(dev->ph_mem, 4096, "PCBIT mem")) { + printk(KERN_WARNING + "PCBIT: memory region %lx-%lx already in use\n", + dev->ph_mem, dev->ph_mem + 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EACCES; + } + dev->sh_mem = ioremap(dev->ph_mem, 4096); + } + else + { + printk("memory address invalid"); + kfree(dev); + dev_pcbit[board] = NULL; + return -EACCES; + } + + dev->b1 = kzalloc(sizeof(struct pcbit_chan), GFP_KERNEL); + if (!dev->b1) { + printk("pcbit_init: couldn't malloc pcbit_chan struct\n"); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + return -ENOMEM; + } + + dev->b2 = kzalloc(sizeof(struct pcbit_chan), GFP_KERNEL); + if (!dev->b2) { + printk("pcbit_init: couldn't malloc pcbit_chan struct\n"); + kfree(dev->b1); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + return -ENOMEM; + } + + dev->b2->id = 1; + + INIT_WORK(&dev->qdelivery, pcbit_deliver); + + /* + * interrupts + */ + + if (request_irq(irq, &pcbit_irq_handler, 0, pcbit_devname[board], dev) != 0) + { + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->irq = irq; + + /* next frame to be received */ + dev->rcv_seq = 0; + dev->send_seq = 0; + dev->unack_seq = 0; + + dev->hl_hdrlen = 16; + + dev_if = kmalloc(sizeof(isdn_if), GFP_KERNEL); + + if (!dev_if) { + free_irq(irq, dev); + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->dev_if = dev_if; + + dev_if->owner = THIS_MODULE; + + dev_if->channels = 2; + + dev_if->features = (ISDN_FEATURE_P_EURO | ISDN_FEATURE_L3_TRANS | + ISDN_FEATURE_L2_HDLC | ISDN_FEATURE_L2_TRANS); + + dev_if->writebuf_skb = pcbit_xmit; + dev_if->hl_hdrlen = 16; + + dev_if->maxbufsize = MAXBUFSIZE; + dev_if->command = pcbit_command; + + dev_if->writecmd = pcbit_writecmd; + dev_if->readstat = pcbit_stat; + + + strcpy(dev_if->id, pcbit_devname[board]); + + if (!register_isdn(dev_if)) { + free_irq(irq, dev); + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + dev_pcbit[board] = NULL; + return -EIO; + } + + dev->id = dev_if->channels; + + + dev->l2_state = L2_DOWN; + dev->free = 511; + + /* + * set_protocol_running(dev); + */ + + return 0; +} + +#ifdef MODULE +void pcbit_terminate(int board) +{ + struct pcbit_dev *dev; + + dev = dev_pcbit[board]; + + if (dev) { + /* unregister_isdn(dev->dev_if); */ + free_irq(dev->irq, dev); + pcbit_clear_msn(dev); + kfree(dev->dev_if); + if (dev->b1->fsm_timer.function) + del_timer(&dev->b1->fsm_timer); + if (dev->b2->fsm_timer.function) + del_timer(&dev->b2->fsm_timer); + kfree(dev->b1); + kfree(dev->b2); + iounmap(dev->sh_mem); + release_mem_region(dev->ph_mem, 4096); + kfree(dev); + } +} +#endif + +static int pcbit_command(isdn_ctrl *ctl) +{ + struct pcbit_dev *dev; + struct pcbit_chan *chan; + struct callb_data info; + + dev = finddev(ctl->driver); + + if (!dev) + { + printk("pcbit_command: unknown device\n"); + return -1; + } + + chan = (ctl->arg & 0x0F) ? dev->b2 : dev->b1; + + + switch (ctl->command) { + case ISDN_CMD_IOCTL: + return pcbit_ioctl(ctl); + break; + case ISDN_CMD_DIAL: + info.type = EV_USR_SETUP_REQ; + info.data.setup.CalledPN = (char *) &ctl->parm.setup.phone; + pcbit_fsm_event(dev, chan, EV_USR_SETUP_REQ, &info); + break; + case ISDN_CMD_ACCEPTD: + pcbit_fsm_event(dev, chan, EV_USR_SETUP_RESP, NULL); + break; + case ISDN_CMD_ACCEPTB: + printk("ISDN_CMD_ACCEPTB - not really needed\n"); + break; + case ISDN_CMD_HANGUP: + pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL); + break; + case ISDN_CMD_SETL2: + chan->proto = (ctl->arg >> 8); + break; + case ISDN_CMD_CLREAZ: + pcbit_clear_msn(dev); + break; + case ISDN_CMD_SETEAZ: + pcbit_set_msn(dev, ctl->parm.num); + break; + case ISDN_CMD_SETL3: + if ((ctl->arg >> 8) != ISDN_PROTO_L3_TRANS) + printk(KERN_DEBUG "L3 protocol unknown\n"); + break; + default: + printk(KERN_DEBUG "pcbit_command: unknown command\n"); + break; + }; + + return 0; +} + +/* + * Another Hack :-( + * on some conditions the board stops sending TDATA_CONFs + * let's see if we can turn around the problem + */ + +#ifdef BLOCK_TIMER +static void pcbit_block_timer(unsigned long data) +{ + struct pcbit_chan *chan; + struct pcbit_dev *dev; + isdn_ctrl ictl; + + chan = (struct pcbit_chan *)data; + + dev = chan2dev(chan); + + if (dev == NULL) { + printk(KERN_DEBUG "pcbit: chan2dev failed\n"); + return; + } + + del_timer(&chan->block_timer); + chan->block_timer.function = NULL; + +#ifdef DEBUG + printk(KERN_DEBUG "pcbit_block_timer\n"); +#endif + chan->queued = 0; + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); +} +#endif + +static int pcbit_xmit(int driver, int chnum, int ack, struct sk_buff *skb) +{ + ushort hdrlen; + int refnum, len; + struct pcbit_chan *chan; + struct pcbit_dev *dev; + + dev = finddev(driver); + if (dev == NULL) + { + printk("finddev returned NULL"); + return -1; + } + + chan = chnum ? dev->b2 : dev->b1; + + + if (chan->fsm_state != ST_ACTIVE) + return -1; + + if (chan->queued >= MAX_QUEUED) + { +#ifdef DEBUG_QUEUE + printk(KERN_DEBUG + "pcbit: %d packets already in queue - write fails\n", + chan->queued); +#endif + /* + * packet stays on the head of the device queue + * since dev_start_xmit will fail + * see net/core/dev.c + */ +#ifdef BLOCK_TIMER + if (chan->block_timer.function == NULL) { + init_timer(&chan->block_timer); + chan->block_timer.function = &pcbit_block_timer; + chan->block_timer.data = (long) chan; + chan->block_timer.expires = jiffies + 1 * HZ; + add_timer(&chan->block_timer); + } +#endif + return 0; + } + + + chan->queued++; + + len = skb->len; + + hdrlen = capi_tdata_req(chan, skb); + + refnum = last_ref_num++ & 0x7fffU; + chan->s_refnum = refnum; + + pcbit_l2_write(dev, MSG_TDATA_REQ, refnum, skb, hdrlen); + + return len; +} + +static int pcbit_writecmd(const u_char __user *buf, int len, int driver, int channel) +{ + struct pcbit_dev *dev; + int i, j; + const u_char *loadbuf; + u_char *ptr = NULL; + u_char *cbuf; + + int errstat; + + dev = finddev(driver); + + if (!dev) + { + printk("pcbit_writecmd: couldn't find device"); + return -ENODEV; + } + + switch (dev->l2_state) { + case L2_LWMODE: + /* check (size <= rdp_size); write buf into board */ + if (len < 0 || len > BANK4 + 1 || len > 1024) + { + printk("pcbit_writecmd: invalid length %d\n", len); + return -EINVAL; + } + + cbuf = memdup_user(buf, len); + if (IS_ERR(cbuf)) + return PTR_ERR(cbuf); + + memcpy_toio(dev->sh_mem, cbuf, len); + kfree(cbuf); + return len; + case L2_FWMODE: + /* this is the hard part */ + /* dumb board */ + /* get it into kernel space */ + if ((ptr = kmalloc(len, GFP_KERNEL)) == NULL) + return -ENOMEM; + if (copy_from_user(ptr, buf, len)) { + kfree(ptr); + return -EFAULT; + } + loadbuf = ptr; + + errstat = 0; + + for (i = 0; i < len; i++) + { + for (j = 0; j < LOAD_RETRY; j++) + if (!(readb(dev->sh_mem + dev->loadptr))) + break; + + if (j == LOAD_RETRY) + { + errstat = -ETIME; + printk("TIMEOUT i=%d\n", i); + break; + } + writeb(loadbuf[i], dev->sh_mem + dev->loadptr + 1); + writeb(0x01, dev->sh_mem + dev->loadptr); + + dev->loadptr += 2; + if (dev->loadptr > LOAD_ZONE_END) + dev->loadptr = LOAD_ZONE_START; + } + kfree(ptr); + + return errstat ? errstat : len; + default: + return -EBUSY; + } +} + +/* + * demultiplexing of messages + * + */ + +void pcbit_l3_receive(struct pcbit_dev *dev, ulong msg, + struct sk_buff *skb, + ushort hdr_len, ushort refnum) +{ + struct pcbit_chan *chan; + struct sk_buff *skb2; + unsigned short len; + struct callb_data cbdata; + int complete, err; + isdn_ctrl ictl; + + switch (msg) { + + case MSG_TDATA_IND: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + chan->r_refnum = skb->data[7]; + skb_pull(skb, 8); + + dev->dev_if->rcvcallb_skb(dev->id, chan->id, skb); + + if (capi_tdata_resp(chan, &skb2) > 0) + pcbit_l2_write(dev, MSG_TDATA_RESP, refnum, + skb2, skb2->len); + return; + break; + case MSG_TDATA_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + +#ifdef DEBUG + if ((*((ushort *)(skb->data + 2))) != 0) { + printk(KERN_DEBUG "TDATA_CONF error\n"); + } +#endif +#ifdef BLOCK_TIMER + if (chan->queued == MAX_QUEUED) { + del_timer(&chan->block_timer); + chan->block_timer.function = NULL; + } + +#endif + chan->queued--; + + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); + break; + + case MSG_CONN_IND: + /* + * channel: 1st not used will do + * if both are used we're in trouble + */ + + if (!dev->b1->fsm_state) + chan = dev->b1; + else if (!dev->b2->fsm_state) + chan = dev->b2; + else { + printk(KERN_INFO + "Incoming connection: no channels available"); + + if ((len = capi_disc_req(*(ushort *)(skb->data), &skb2, CAUSE_NOCHAN)) > 0) + pcbit_l2_write(dev, MSG_DISC_REQ, refnum, skb2, len); + break; + } + + cbdata.data.setup.CalledPN = NULL; + cbdata.data.setup.CallingPN = NULL; + + capi_decode_conn_ind(chan, skb, &cbdata); + cbdata.type = EV_NET_SETUP; + + pcbit_fsm_event(dev, chan, EV_NET_SETUP, NULL); + + if (pcbit_check_msn(dev, cbdata.data.setup.CallingPN)) + pcbit_fsm_event(dev, chan, EV_USR_PROCED_REQ, &cbdata); + else + pcbit_fsm_event(dev, chan, EV_USR_RELEASE_REQ, NULL); + + kfree(cbdata.data.setup.CalledPN); + kfree(cbdata.data.setup.CallingPN); + break; + + case MSG_CONN_CONF: + /* + * We should be able to find the channel by the message + * reference number. The current version of the firmware + * doesn't sent the ref number correctly. + */ +#ifdef DEBUG + printk(KERN_DEBUG "refnum=%04x b1=%04x b2=%04x\n", refnum, + dev->b1->s_refnum, + dev->b2->s_refnum); +#endif + /* We just try to find a channel in the right state */ + + if (dev->b1->fsm_state == ST_CALL_INIT) + chan = dev->b1; + else { + if (dev->b2->s_refnum == ST_CALL_INIT) + chan = dev->b2; + else { + chan = NULL; + printk(KERN_WARNING "Connection Confirm - no channel in Call Init state\n"); + break; + } + } + if (capi_decode_conn_conf(chan, skb, &complete)) { + printk(KERN_DEBUG "conn_conf indicates error\n"); + pcbit_fsm_event(dev, chan, EV_ERROR, NULL); + } + else + if (complete) + pcbit_fsm_event(dev, chan, EV_NET_CALL_PROC, NULL); + else + pcbit_fsm_event(dev, chan, EV_NET_SETUP_ACK, NULL); + break; + case MSG_CONN_ACTV_IND: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (capi_decode_conn_actv_ind(chan, skb)) { + printk("error in capi_decode_conn_actv_ind\n"); + /* pcbit_fsm_event(dev, chan, EV_ERROR, NULL); */ + break; + } + chan->r_refnum = refnum; + pcbit_fsm_event(dev, chan, EV_NET_CONN, NULL); + break; + case MSG_CONN_ACTV_CONF: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (capi_decode_conn_actv_conf(chan, skb) == 0) + pcbit_fsm_event(dev, chan, EV_NET_CONN_ACK, NULL); + + else + printk(KERN_DEBUG "decode_conn_actv_conf failed\n"); + break; + + case MSG_SELP_CONF: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!(err = capi_decode_sel_proto_conf(chan, skb))) + pcbit_fsm_event(dev, chan, EV_NET_SELP_RESP, NULL); + else { + /* Error */ + printk("error %d - capi_decode_sel_proto_conf\n", err); + } + break; + case MSG_ACT_TRANSP_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_actv_trans_conf(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_ACTV_RESP, NULL); + break; + + case MSG_DISC_IND: + + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_disc_ind(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_DISC, NULL); + else + printk(KERN_WARNING "capi_decode_disc_ind - error\n"); + break; + case MSG_DISC_CONF: + if (!(chan = capi_channel(dev, skb))) { + printk(KERN_WARNING + "CAPI header: unknown channel id\n"); + break; + } + + if (!capi_decode_disc_ind(chan, skb)) + pcbit_fsm_event(dev, chan, EV_NET_RELEASE, NULL); + else + printk(KERN_WARNING "capi_decode_disc_conf - error\n"); + break; + case MSG_INFO_IND: +#ifdef DEBUG + printk(KERN_DEBUG "received Info Indication - discarded\n"); +#endif + break; +#ifdef DEBUG + case MSG_DEBUG_188: + capi_decode_debug_188(skb->data, skb->len); + break; + + default: + printk(KERN_DEBUG "pcbit_l3_receive: unknown message %08lx\n", + msg); + break; +#endif + } + + kfree_skb(skb); + +} + +/* + * Single statbuf + * should be a statbuf per device + */ + +static char statbuf[STATBUF_LEN]; +static int stat_st = 0; +static int stat_end = 0; + +static int pcbit_stat(u_char __user *buf, int len, int driver, int channel) +{ + int stat_count; + stat_count = stat_end - stat_st; + + if (stat_count < 0) + stat_count = STATBUF_LEN - stat_st + stat_end; + + /* FIXME: should we sleep and wait for more cookies ? */ + if (len > stat_count) + len = stat_count; + + if (stat_st < stat_end) + { + if (copy_to_user(buf, statbuf + stat_st, len)) + return -EFAULT; + stat_st += len; + } + else + { + if (len > STATBUF_LEN - stat_st) + { + if (copy_to_user(buf, statbuf + stat_st, + STATBUF_LEN - stat_st)) + return -EFAULT; + if (copy_to_user(buf, statbuf, + len - (STATBUF_LEN - stat_st))) + return -EFAULT; + + stat_st = len - (STATBUF_LEN - stat_st); + } + else + { + if (copy_to_user(buf, statbuf + stat_st, len)) + return -EFAULT; + + stat_st += len; + + if (stat_st == STATBUF_LEN) + stat_st = 0; + } + } + + if (stat_st == stat_end) + stat_st = stat_end = 0; + + return len; +} + +static void pcbit_logstat(struct pcbit_dev *dev, char *str) +{ + int i; + isdn_ctrl ictl; + + for (i = stat_end; i < strlen(str); i++) + { + statbuf[i] = str[i]; + stat_end = (stat_end + 1) % STATBUF_LEN; + if (stat_end == stat_st) + stat_st = (stat_st + 1) % STATBUF_LEN; + } + + ictl.command = ISDN_STAT_STAVAIL; + ictl.driver = dev->id; + ictl.arg = strlen(str); + dev->dev_if->statcallb(&ictl); +} + +void pcbit_state_change(struct pcbit_dev *dev, struct pcbit_chan *chan, + unsigned short i, unsigned short ev, unsigned short f) +{ + char buf[256]; + + sprintf(buf, "change on device: %d channel:%d\n%s -> %s -> %s\n", + dev->id, chan->id, + isdn_state_table[i], strisdnevent(ev), isdn_state_table[f] + ); + +#ifdef DEBUG + printk("%s", buf); +#endif + + pcbit_logstat(dev, buf); +} + +static void set_running_timeout(unsigned long ptr) +{ + struct pcbit_dev *dev; + +#ifdef DEBUG + printk(KERN_DEBUG "set_running_timeout\n"); +#endif + dev = (struct pcbit_dev *) ptr; + + dev->l2_state = L2_DOWN; + wake_up_interruptible(&dev->set_running_wq); +} + +static int set_protocol_running(struct pcbit_dev *dev) +{ + isdn_ctrl ctl; + + init_timer(&dev->set_running_timer); + + dev->set_running_timer.function = &set_running_timeout; + dev->set_running_timer.data = (ulong) dev; + dev->set_running_timer.expires = jiffies + SET_RUN_TIMEOUT; + + /* kick it */ + + dev->l2_state = L2_STARTING; + + writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)), + dev->sh_mem + BANK4); + + add_timer(&dev->set_running_timer); + + wait_event(dev->set_running_wq, dev->l2_state == L2_RUNNING || + dev->l2_state == L2_DOWN); + + del_timer(&dev->set_running_timer); + + if (dev->l2_state == L2_RUNNING) + { + printk(KERN_DEBUG "pcbit: running\n"); + + dev->unack_seq = dev->send_seq; + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + /* tell the good news to the upper layer */ + ctl.driver = dev->id; + ctl.command = ISDN_STAT_RUN; + + dev->dev_if->statcallb(&ctl); + } + else + { + printk(KERN_DEBUG "pcbit: initialization failed\n"); + printk(KERN_DEBUG "pcbit: firmware not loaded\n"); + +#ifdef DEBUG + printk(KERN_DEBUG "Bank3 = %02x\n", + readb(dev->sh_mem + BANK3)); +#endif + writeb(0x40, dev->sh_mem + BANK4); + + /* warn the upper layer */ + ctl.driver = dev->id; + ctl.command = ISDN_STAT_STOP; + + dev->dev_if->statcallb(&ctl); + + return -EL2HLT; /* Level 2 halted */ + } + + return 0; +} + +static int pcbit_ioctl(isdn_ctrl *ctl) +{ + struct pcbit_dev *dev; + struct pcbit_ioctl *cmd; + + dev = finddev(ctl->driver); + + if (!dev) + { + printk(KERN_DEBUG "pcbit_ioctl: unknown device\n"); + return -ENODEV; + } + + cmd = (struct pcbit_ioctl *) ctl->parm.num; + + switch (ctl->arg) { + case PCBIT_IOCTL_GETSTAT: + cmd->info.l2_status = dev->l2_state; + break; + + case PCBIT_IOCTL_STRLOAD: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + dev->unack_seq = dev->send_seq = dev->rcv_seq = 0; + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + dev->l2_state = L2_LOADING; + break; + + case PCBIT_IOCTL_LWMODE: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + + dev->l2_state = L2_LWMODE; + break; + + case PCBIT_IOCTL_FWMODE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + dev->loadptr = LOAD_ZONE_START; + dev->l2_state = L2_FWMODE; + + break; + case PCBIT_IOCTL_ENDLOAD: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + dev->l2_state = L2_DOWN; + break; + + case PCBIT_IOCTL_SETBYTE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + /* check addr */ + if (cmd->info.rdp_byte.addr > BANK4) + return -EFAULT; + + writeb(cmd->info.rdp_byte.value, dev->sh_mem + cmd->info.rdp_byte.addr); + break; + case PCBIT_IOCTL_GETBYTE: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + + /* check addr */ + + if (cmd->info.rdp_byte.addr > BANK4) + { + printk("getbyte: invalid addr %04x\n", cmd->info.rdp_byte.addr); + return -EFAULT; + } + + cmd->info.rdp_byte.value = readb(dev->sh_mem + cmd->info.rdp_byte.addr); + break; + case PCBIT_IOCTL_RUNNING: + if (dev->l2_state == L2_RUNNING) + return -EBUSY; + return set_protocol_running(dev); + break; + case PCBIT_IOCTL_WATCH188: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_WATCH188, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_PING188: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_PING188_REQ, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_APION: + if (dev->l2_state != L2_LOADING) + return -EINVAL; + pcbit_l2_write(dev, MSG_API_ON, 0x0001, NULL, 0); + break; + case PCBIT_IOCTL_STOP: + dev->l2_state = L2_DOWN; + writeb(0x40, dev->sh_mem + BANK4); + dev->rcv_seq = 0; + dev->send_seq = 0; + dev->unack_seq = 0; + break; + default: + printk("error: unknown ioctl\n"); + break; + }; + return 0; +} + +/* + * MSN list handling + * + * if null reject all calls + * if first entry has null MSN accept all calls + */ + +static void pcbit_clear_msn(struct pcbit_dev *dev) +{ + struct msn_entry *ptr, *back; + + for (ptr = dev->msn_list; ptr;) + { + back = ptr->next; + kfree(ptr); + ptr = back; + } + + dev->msn_list = NULL; +} + +static void pcbit_set_msn(struct pcbit_dev *dev, char *list) +{ + struct msn_entry *ptr; + struct msn_entry *back = NULL; + char *cp, *sp; + int len; + + if (strlen(list) == 0) { + ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC); + if (!ptr) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + + ptr->msn = NULL; + + ptr->next = dev->msn_list; + dev->msn_list = ptr; + + return; + } + + if (dev->msn_list) + for (back = dev->msn_list; back->next; back = back->next); + + sp = list; + + do { + cp = strchr(sp, ','); + if (cp) + len = cp - sp; + else + len = strlen(sp); + + ptr = kmalloc(sizeof(struct msn_entry), GFP_ATOMIC); + + if (!ptr) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + ptr->next = NULL; + + ptr->msn = kmalloc(len + 1, GFP_ATOMIC); + if (!ptr->msn) { + printk(KERN_WARNING "kmalloc failed\n"); + kfree(ptr); + return; + } + + memcpy(ptr->msn, sp, len); + ptr->msn[len] = 0; + +#ifdef DEBUG + printk(KERN_DEBUG "msn: %s\n", ptr->msn); +#endif + if (dev->msn_list == NULL) + dev->msn_list = ptr; + else + back->next = ptr; + back = ptr; + sp += len; + } while (cp); +} + +/* + * check if we do signal or reject an incoming call + */ +static int pcbit_check_msn(struct pcbit_dev *dev, char *msn) +{ + struct msn_entry *ptr; + + for (ptr = dev->msn_list; ptr; ptr = ptr->next) { + + if (ptr->msn == NULL) + return 1; + + if (strcmp(ptr->msn, msn) == 0) + return 1; + } + + return 0; +} diff --git a/drivers/staging/i4l/pcbit/edss1.c b/drivers/staging/i4l/pcbit/edss1.c new file mode 100644 index 000000000..b2262ba6f --- /dev/null +++ b/drivers/staging/i4l/pcbit/edss1.c @@ -0,0 +1,313 @@ +/* + * DSS.1 Finite State Machine + * base: ITU-T Rec Q.931 + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +/* + * TODO: complete the FSM + * move state/event descriptions to a user space logger + */ + +#include <linux/string.h> +#include <linux/kernel.h> + +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/skbuff.h> + +#include <linux/timer.h> +#include <asm/io.h> + +#include <linux/isdnif.h> + +#include "pcbit.h" +#include "edss1.h" +#include "layer2.h" +#include "callbacks.h" + + +const char * const isdn_state_table[] = { + "Closed", + "Call initiated", + "Overlap sending", + "Outgoing call proceeding", + "NOT DEFINED", + "Call delivered", + "Call present", + "Call received", + "Connect request", + "Incoming call proceeding", + "Active", + "Disconnect request", + "Disconnect indication", + "NOT DEFINED", + "NOT DEFINED", + "Suspend request", + "NOT DEFINED", + "Resume request", + "NOT DEFINED", + "Release Request", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "NOT DEFINED", + "Overlap receiving", + "Select protocol on B-Channel", + "Activate B-channel protocol" +}; + +#ifdef DEBUG_ERRS +static +struct CauseValue { + byte nr; + char *descr; +} cvlist[] = { + {0x01, "Unallocated (unassigned) number"}, + {0x02, "No route to specified transit network"}, + {0x03, "No route to destination"}, + {0x04, "Send special information tone"}, + {0x05, "Misdialled trunk prefix"}, + {0x06, "Channel unacceptable"}, + {0x07, "Channel awarded and being delivered in an established channel"}, + {0x08, "Preemption"}, + {0x09, "Preemption - circuit reserved for reuse"}, + {0x10, "Normal call clearing"}, + {0x11, "User busy"}, + {0x12, "No user responding"}, + {0x13, "No answer from user (user alerted)"}, + {0x14, "Subscriber absent"}, + {0x15, "Call rejected"}, + {0x16, "Number changed"}, + {0x1a, "non-selected user clearing"}, + {0x1b, "Destination out of order"}, + {0x1c, "Invalid number format (address incomplete)"}, + {0x1d, "Facility rejected"}, + {0x1e, "Response to Status enquiry"}, + {0x1f, "Normal, unspecified"}, + {0x22, "No circuit/channel available"}, + {0x26, "Network out of order"}, + {0x27, "Permanent frame mode connection out-of-service"}, + {0x28, "Permanent frame mode connection operational"}, + {0x29, "Temporary failure"}, + {0x2a, "Switching equipment congestion"}, + {0x2b, "Access information discarded"}, + {0x2c, "Requested circuit/channel not available"}, + {0x2e, "Precedence call blocked"}, + {0x2f, "Resource unavailable, unspecified"}, + {0x31, "Quality of service unavailable"}, + {0x32, "Requested facility not subscribed"}, + {0x35, "Outgoing calls barred within CUG"}, + {0x37, "Incoming calls barred within CUG"}, + {0x39, "Bearer capability not authorized"}, + {0x3a, "Bearer capability not presently available"}, + {0x3e, "Inconsistency in designated outgoing access information and subscriber class"}, + {0x3f, "Service or option not available, unspecified"}, + {0x41, "Bearer capability not implemented"}, + {0x42, "Channel type not implemented"}, + {0x43, "Requested facility not implemented"}, + {0x44, "Only restricted digital information bearer capability is available"}, + {0x4f, "Service or option not implemented"}, + {0x51, "Invalid call reference value"}, + {0x52, "Identified channel does not exist"}, + {0x53, "A suspended call exists, but this call identity does not"}, + {0x54, "Call identity in use"}, + {0x55, "No call suspended"}, + {0x56, "Call having the requested call identity has been cleared"}, + {0x57, "User not member of CUG"}, + {0x58, "Incompatible destination"}, + {0x5a, "Non-existent CUG"}, + {0x5b, "Invalid transit network selection"}, + {0x5f, "Invalid message, unspecified"}, + {0x60, "Mandatory information element is missing"}, + {0x61, "Message type non-existent or not implemented"}, + {0x62, "Message not compatible with call state or message type non-existent or not implemented"}, + {0x63, "Information element/parameter non-existent or not implemented"}, + {0x64, "Invalid information element contents"}, + {0x65, "Message not compatible with call state"}, + {0x66, "Recovery on timer expiry"}, + {0x67, "Parameter non-existent or not implemented - passed on"}, + {0x6e, "Message with unrecognized parameter discarded"}, + {0x6f, "Protocol error, unspecified"}, + {0x7f, "Interworking, unspecified"} +}; + +#endif + +static struct isdn_event_desc { + unsigned short ev; + char *desc; +} isdn_event_table[] = { + {EV_USR_SETUP_REQ, "CC->L3: Setup Request"}, + {EV_USR_SETUP_RESP, "CC->L3: Setup Response"}, + {EV_USR_PROCED_REQ, "CC->L3: Proceeding Request"}, + {EV_USR_RELEASE_REQ, "CC->L3: Release Request"}, + + {EV_NET_SETUP, "NET->TE: setup "}, + {EV_NET_CALL_PROC, "NET->TE: call proceeding"}, + {EV_NET_SETUP_ACK, "NET->TE: setup acknowledge (more info needed)"}, + {EV_NET_CONN, "NET->TE: connect"}, + {EV_NET_CONN_ACK, "NET->TE: connect acknowledge"}, + {EV_NET_DISC, "NET->TE: disconnect indication"}, + {EV_NET_RELEASE, "NET->TE: release"}, + {EV_NET_RELEASE_COMP, "NET->TE: release complete"}, + {EV_NET_SELP_RESP, "Board: Select B-channel protocol ack"}, + {EV_NET_ACTV_RESP, "Board: Activate B-channel protocol ack"}, + {EV_TIMER, "Timeout"}, + {0, "NULL"} +}; + +char *strisdnevent(ushort ev) +{ + struct isdn_event_desc *entry; + + for (entry = isdn_event_table; entry->ev; entry++) + if (entry->ev == ev) + break; + + return entry->desc; +} + +/* + * Euro ISDN finite state machine + */ + +static struct fsm_timer_entry fsm_timers[] = { + {ST_CALL_PROC, 10}, + {ST_DISC_REQ, 2}, + {ST_ACTIVE_SELP, 5}, + {ST_ACTIVE_ACTV, 5}, + {ST_INCM_PROC, 10}, + {ST_CONN_REQ, 2}, + {0xff, 0} +}; + +static struct fsm_entry fsm_table[] = { +/* Connect Phase */ + /* Outgoing */ + {ST_NULL, ST_CALL_INIT, EV_USR_SETUP_REQ, cb_out_1}, + + {ST_CALL_INIT, ST_OVER_SEND, EV_NET_SETUP_ACK, cb_notdone}, + {ST_CALL_INIT, ST_CALL_PROC, EV_NET_CALL_PROC, NULL}, + {ST_CALL_INIT, ST_NULL, EV_NET_DISC, cb_out_2}, + + {ST_CALL_PROC, ST_ACTIVE_SELP, EV_NET_CONN, cb_out_2}, + {ST_CALL_PROC, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_CALL_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + /* Incoming */ + {ST_NULL, ST_CALL_PRES, EV_NET_SETUP, NULL}, + + {ST_CALL_PRES, ST_INCM_PROC, EV_USR_PROCED_REQ, cb_in_1}, + {ST_CALL_PRES, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_INCM_PROC, ST_CONN_REQ, EV_USR_SETUP_RESP, cb_in_2}, + {ST_INCM_PROC, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_CONN_REQ, ST_ACTIVE_SELP, EV_NET_CONN_ACK, cb_in_3}, + + /* Active */ + {ST_ACTIVE, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_ACTIVE, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + {ST_ACTIVE, ST_NULL, EV_NET_RELEASE, cb_disc_3}, + + /* Disconnect */ + + {ST_DISC_REQ, ST_NULL, EV_NET_DISC, cb_disc_1}, + {ST_DISC_REQ, ST_NULL, EV_NET_RELEASE, cb_disc_3}, + + /* protocol selection */ + {ST_ACTIVE_SELP, ST_ACTIVE_ACTV, EV_NET_SELP_RESP, cb_selp_1}, + {ST_ACTIVE_SELP, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + {ST_ACTIVE_ACTV, ST_ACTIVE, EV_NET_ACTV_RESP, cb_open}, + {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_USR_RELEASE_REQ, cb_disc_2}, + + /* Timers */ + {ST_CALL_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_DISC_REQ, ST_NULL, EV_TIMER, cb_disc_3}, + {ST_ACTIVE_SELP, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_ACTIVE_ACTV, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_INCM_PROC, ST_DISC_REQ, EV_TIMER, cb_disc_2}, + {ST_CONN_REQ, ST_CONN_REQ, EV_TIMER, cb_in_2}, + + {0xff, 0, 0, NULL} +}; + + +static void pcbit_fsm_timer(unsigned long data) +{ + struct pcbit_dev *dev; + struct pcbit_chan *chan; + + chan = (struct pcbit_chan *) data; + + del_timer(&chan->fsm_timer); + chan->fsm_timer.function = NULL; + + dev = chan2dev(chan); + + if (dev == NULL) { + printk(KERN_WARNING "pcbit: timer for unknown device\n"); + return; + } + + pcbit_fsm_event(dev, chan, EV_TIMER, NULL); +} + + +void pcbit_fsm_event(struct pcbit_dev *dev, struct pcbit_chan *chan, + unsigned short event, struct callb_data *data) +{ + struct fsm_entry *action; + struct fsm_timer_entry *tentry; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + for (action = fsm_table; action->init != 0xff; action++) + if (action->init == chan->fsm_state && action->event == event) + break; + + if (action->init == 0xff) { + + spin_unlock_irqrestore(&dev->lock, flags); + printk(KERN_DEBUG "fsm error: event %x on state %x\n", + event, chan->fsm_state); + return; + } + + if (chan->fsm_timer.function) { + del_timer(&chan->fsm_timer); + chan->fsm_timer.function = NULL; + } + + chan->fsm_state = action->final; + + pcbit_state_change(dev, chan, action->init, event, action->final); + + for (tentry = fsm_timers; tentry->init != 0xff; tentry++) + if (tentry->init == chan->fsm_state) + break; + + if (tentry->init != 0xff) { + init_timer(&chan->fsm_timer); + chan->fsm_timer.function = &pcbit_fsm_timer; + chan->fsm_timer.data = (ulong) chan; + chan->fsm_timer.expires = jiffies + tentry->timeout * HZ; + add_timer(&chan->fsm_timer); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + if (action->callb) + action->callb(dev, chan, data); + +} diff --git a/drivers/staging/i4l/pcbit/edss1.h b/drivers/staging/i4l/pcbit/edss1.h new file mode 100644 index 000000000..2f6b3a8ed --- /dev/null +++ b/drivers/staging/i4l/pcbit/edss1.h @@ -0,0 +1,99 @@ +/* + * DSS.1 module definitions + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +#ifndef EDSS1_H +#define EDSS1_H + +/* ISDN states */ + +#define ST_NULL 0 +#define ST_CALL_INIT 1 /* Call initiated */ +#define ST_OVER_SEND 2 /* Overlap sending - Requests More Info 4 call */ +#define ST_CALL_PROC 3 /* Call Proceeding */ +#define ST_CALL_DELV 4 +#define ST_CALL_PRES 6 /* Call Present - Received CONN.IND */ +#define ST_CALL_RECV 7 /* Alerting sent */ +#define ST_CONN_REQ 8 /* Answered - waiting 4 CONN.CONF */ +#define ST_INCM_PROC 9 +#define ST_ACTIVE 10 +#define ST_DISC_REQ 11 +#define ST_DISC_IND 12 +#define ST_SUSP_REQ 15 +#define ST_RESM_REQ 17 +#define ST_RELS_REQ 19 +#define ST_OVER_RECV 25 + +#define ST_ACTIVE_SELP 26 /* Select protocol on B-Channel */ +#define ST_ACTIVE_ACTV 27 /* Activate B-channel protocol */ + +#define MAX_STATE ST_ACTIVE_ACTV + +#define EV_NULL 0 +#define EV_USR_SETUP_REQ 1 +#define EV_USR_SETUP_RESP 2 +#define EV_USR_PROCED_REQ 3 +#define EV_USR_RELEASE_REQ 4 +#define EV_USR_REJECT_REQ 4 + +#define EV_NET_SETUP 16 +#define EV_NET_CALL_PROC 17 +#define EV_NET_SETUP_ACK 18 +#define EV_NET_CONN 19 +#define EV_NET_CONN_ACK 20 + +#define EV_NET_SELP_RESP 21 +#define EV_NET_ACTV_RESP 22 + +#define EV_NET_DISC 23 +#define EV_NET_RELEASE 24 +#define EV_NET_RELEASE_COMP 25 + +#define EV_TIMER 26 +#define EV_ERROR 32 + +/* + * Cause values + * only the ones we use + */ + +#define CAUSE_NORMAL 0x10U +#define CAUSE_NOCHAN 0x22U + +struct callb_data { + unsigned short type; + union { + struct ConnInfo { + char *CalledPN; + char *CallingPN; + } setup; + unsigned short cause; + } data; +}; + +struct fsm_entry { + unsigned short init; + unsigned short final; + unsigned short event; + void (*callb)(struct pcbit_dev *, struct pcbit_chan *, struct callb_data*); +}; + +struct fsm_timer_entry { + unsigned short init; + unsigned long timeout; /* in seconds */ +}; + +extern const char * const isdn_state_table[]; + +void pcbit_fsm_event(struct pcbit_dev *, struct pcbit_chan *, + unsigned short event, struct callb_data *); +char *strisdnevent(ushort ev); + +#endif diff --git a/drivers/staging/i4l/pcbit/layer2.c b/drivers/staging/i4l/pcbit/layer2.c new file mode 100644 index 000000000..46e1240ae --- /dev/null +++ b/drivers/staging/i4l/pcbit/layer2.c @@ -0,0 +1,712 @@ +/* + * PCBIT-D low-layer interface + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +/* + * 19991203 - Fernando Carvalho - takion@superbofh.org + * Hacked to compile with egcs and run with current version of isdn modules + */ + +/* + * Based on documentation provided by Inesc: + * - "Interface com bus do PC para o PCBIT e PCBIT-D", Inesc, Jan 93 + */ + +/* + * TODO: better handling of errors + * re-write/remove debug printks + */ + +#include <linux/string.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/mm.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> + +#include <asm/io.h> + + +#include "pcbit.h" +#include "layer2.h" +#include "edss1.h" + +#undef DEBUG_FRAG + + +/* + * Prototypes + */ + +static void pcbit_transmit(struct pcbit_dev *dev); + +static void pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack); + +static void pcbit_l2_error(struct pcbit_dev *dev); +static void pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info); +static void pcbit_l2_err_recover(unsigned long data); + +static void pcbit_firmware_bug(struct pcbit_dev *dev); + +static __inline__ void +pcbit_sched_delivery(struct pcbit_dev *dev) +{ + schedule_work(&dev->qdelivery); +} + + +/* + * Called from layer3 + */ + +int +pcbit_l2_write(struct pcbit_dev *dev, ulong msg, ushort refnum, + struct sk_buff *skb, unsigned short hdr_len) +{ + struct frame_buf *frame, + *ptr; + unsigned long flags; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { + dev_kfree_skb(skb); + return -1; + } + if ((frame = kmalloc(sizeof(struct frame_buf), + GFP_ATOMIC)) == NULL) { + dev_kfree_skb(skb); + return -1; + } + frame->msg = msg; + frame->refnum = refnum; + frame->copied = 0; + frame->hdr_len = hdr_len; + + if (skb) + frame->dt_len = skb->len - hdr_len; + else + frame->dt_len = 0; + + frame->skb = skb; + + frame->next = NULL; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->write_queue == NULL) { + dev->write_queue = frame; + spin_unlock_irqrestore(&dev->lock, flags); + pcbit_transmit(dev); + } else { + for (ptr = dev->write_queue; ptr->next; ptr = ptr->next); + ptr->next = frame; + + spin_unlock_irqrestore(&dev->lock, flags); + } + return 0; +} + +static __inline__ void +pcbit_tx_update(struct pcbit_dev *dev, ushort len) +{ + u_char info; + + dev->send_seq = (dev->send_seq + 1) % 8; + + dev->fsize[dev->send_seq] = len; + info = 0; + info |= dev->rcv_seq << 3; + info |= dev->send_seq; + + writeb(info, dev->sh_mem + BANK4); + +} + +/* + * called by interrupt service routine or by write_2 + */ + +static void +pcbit_transmit(struct pcbit_dev *dev) +{ + struct frame_buf *frame = NULL; + unsigned char unacked; + int flen; /* fragment frame length including all headers */ + int free; + int count, + cp_len; + unsigned long flags; + unsigned short tt; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) + return; + + unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->free > 16 && dev->write_queue && unacked < 7) { + + if (!dev->w_busy) + dev->w_busy = 1; + else { + spin_unlock_irqrestore(&dev->lock, flags); + return; + } + + + frame = dev->write_queue; + free = dev->free; + + spin_unlock_irqrestore(&dev->lock, flags); + + if (frame->copied == 0) { + + /* Type 0 frame */ + + ulong msg; + + if (frame->skb) + flen = FRAME_HDR_LEN + PREHDR_LEN + frame->skb->len; + else + flen = FRAME_HDR_LEN + PREHDR_LEN; + + if (flen > free) + flen = free; + + msg = frame->msg; + + /* + * Board level 2 header + */ + + pcbit_writew(dev, flen - FRAME_HDR_LEN); + + pcbit_writeb(dev, GET_MSG_CPU(msg)); + + pcbit_writeb(dev, GET_MSG_PROC(msg)); + + /* TH */ + pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); + + /* TD */ + pcbit_writew(dev, frame->dt_len); + + + /* + * Board level 3 fixed-header + */ + + /* LEN = TH */ + pcbit_writew(dev, frame->hdr_len + PREHDR_LEN); + + /* XX */ + pcbit_writew(dev, 0); + + /* C + S */ + pcbit_writeb(dev, GET_MSG_CMD(msg)); + pcbit_writeb(dev, GET_MSG_SCMD(msg)); + + /* NUM */ + pcbit_writew(dev, frame->refnum); + + count = FRAME_HDR_LEN + PREHDR_LEN; + } else { + /* Type 1 frame */ + + flen = 2 + (frame->skb->len - frame->copied); + + if (flen > free) + flen = free; + + /* TT */ + tt = ((ushort) (flen - 2)) | 0x8000U; /* Type 1 */ + pcbit_writew(dev, tt); + + count = 2; + } + + if (frame->skb) { + cp_len = frame->skb->len - frame->copied; + if (cp_len > flen - count) + cp_len = flen - count; + + memcpy_topcbit(dev, frame->skb->data + frame->copied, + cp_len); + frame->copied += cp_len; + } + /* bookkeeping */ + dev->free -= flen; + pcbit_tx_update(dev, flen); + + spin_lock_irqsave(&dev->lock, flags); + + if (frame->skb == NULL || frame->copied == frame->skb->len) { + + dev->write_queue = frame->next; + + if (frame->skb != NULL) { + /* free frame */ + dev_kfree_skb(frame->skb); + } + kfree(frame); + } + dev->w_busy = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + spin_unlock_irqrestore(&dev->lock, flags); +#ifdef DEBUG + printk(KERN_DEBUG "unacked %d free %d write_queue %s\n", + unacked, dev->free, dev->write_queue ? "not empty" : + "empty"); +#endif + } +} + + +/* + * deliver a queued frame to the upper layer + */ + +void +pcbit_deliver(struct work_struct *work) +{ + struct frame_buf *frame; + unsigned long flags, msg; + struct pcbit_dev *dev = + container_of(work, struct pcbit_dev, qdelivery); + + spin_lock_irqsave(&dev->lock, flags); + + while ((frame = dev->read_queue)) { + dev->read_queue = frame->next; + spin_unlock_irqrestore(&dev->lock, flags); + + msg = 0; + SET_MSG_CPU(msg, 0); + SET_MSG_PROC(msg, 0); + SET_MSG_CMD(msg, frame->skb->data[2]); + SET_MSG_SCMD(msg, frame->skb->data[3]); + + frame->refnum = *((ushort *)frame->skb->data + 4); + frame->msg = *((ulong *)&msg); + + skb_pull(frame->skb, 6); + + pcbit_l3_receive(dev, frame->msg, frame->skb, frame->hdr_len, + frame->refnum); + + kfree(frame); + + spin_lock_irqsave(&dev->lock, flags); + } + + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * Reads BANK 2 & Reassembles + */ + +static void +pcbit_receive(struct pcbit_dev *dev) +{ + unsigned short tt; + u_char cpu, + proc; + struct frame_buf *frame = NULL; + unsigned long flags; + u_char type1; + + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) + return; + + tt = pcbit_readw(dev); + + if ((tt & 0x7fffU) > 511) { + printk(KERN_INFO "pcbit: invalid frame length -> TT=%04x\n", + tt); + pcbit_l2_error(dev); + return; + } + if (!(tt & 0x8000U)) { /* Type 0 */ + type1 = 0; + + if (dev->read_frame) { + printk(KERN_DEBUG "pcbit_receive: Type 0 frame and read_frame != NULL\n"); + /* discard previous queued frame */ + kfree_skb(dev->read_frame->skb); + kfree(dev->read_frame); + dev->read_frame = NULL; + } + frame = kzalloc(sizeof(struct frame_buf), GFP_ATOMIC); + + if (frame == NULL) { + printk(KERN_WARNING "kmalloc failed\n"); + return; + } + + cpu = pcbit_readb(dev); + proc = pcbit_readb(dev); + + + if (cpu != 0x06 && cpu != 0x02) { + printk(KERN_DEBUG "pcbit: invalid cpu value\n"); + kfree(frame); + pcbit_l2_error(dev); + return; + } + /* + * we discard cpu & proc on receiving + * but we read it to update the pointer + */ + + frame->hdr_len = pcbit_readw(dev); + frame->dt_len = pcbit_readw(dev); + + /* + * 0 sized packet + * I don't know if they are an error or not... + * But they are very frequent + * Not documented + */ + + if (frame->hdr_len == 0) { + kfree(frame); +#ifdef DEBUG + printk(KERN_DEBUG "0 sized frame\n"); +#endif + pcbit_firmware_bug(dev); + return; + } + /* sanity check the length values */ + if (frame->hdr_len > 1024 || frame->dt_len > 2048) { +#ifdef DEBUG + printk(KERN_DEBUG "length problem: "); + printk(KERN_DEBUG "TH=%04x TD=%04x\n", + frame->hdr_len, + frame->dt_len); +#endif + pcbit_l2_error(dev); + kfree(frame); + return; + } + /* minimum frame read */ + + frame->skb = dev_alloc_skb(frame->hdr_len + frame->dt_len + + ((frame->hdr_len + 15) & ~15)); + + if (!frame->skb) { + printk(KERN_DEBUG "pcbit_receive: out of memory\n"); + kfree(frame); + return; + } + /* 16 byte alignment for IP */ + if (frame->dt_len) + skb_reserve(frame->skb, (frame->hdr_len + 15) & ~15); + + } else { + /* Type 1 */ + type1 = 1; + tt &= 0x7fffU; + + if (!(frame = dev->read_frame)) { + printk("Type 1 frame and no frame queued\n"); + /* usually after an error: toss frame */ + dev->readptr += tt; + if (dev->readptr > dev->sh_mem + BANK2 + BANKLEN) + dev->readptr -= BANKLEN; + return; + + } + } + + memcpy_frompcbit(dev, skb_put(frame->skb, tt), tt); + + frame->copied += tt; + spin_lock_irqsave(&dev->lock, flags); + if (frame->copied == frame->hdr_len + frame->dt_len) { + + if (type1) { + dev->read_frame = NULL; + } + if (dev->read_queue) { + struct frame_buf *ptr; + for (ptr = dev->read_queue; ptr->next; ptr = ptr->next); + ptr->next = frame; + } else + dev->read_queue = frame; + + } else { + dev->read_frame = frame; + } + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* + * The board sends 0 sized frames + * They are TDATA_CONFs that get messed up somehow + * gotta send a fake acknowledgment to the upper layer somehow + */ + +static __inline__ void +pcbit_fake_conf(struct pcbit_dev *dev, struct pcbit_chan *chan) +{ + isdn_ctrl ictl; + + if (chan->queued) { + chan->queued--; + + ictl.driver = dev->id; + ictl.command = ISDN_STAT_BSENT; + ictl.arg = chan->id; + dev->dev_if->statcallb(&ictl); + } +} + +static void +pcbit_firmware_bug(struct pcbit_dev *dev) +{ + struct pcbit_chan *chan; + + chan = dev->b1; + + if (chan->fsm_state == ST_ACTIVE) { + pcbit_fake_conf(dev, chan); + } + chan = dev->b2; + + if (chan->fsm_state == ST_ACTIVE) { + pcbit_fake_conf(dev, chan); + } +} + +irqreturn_t +pcbit_irq_handler(int interrupt, void *devptr) +{ + struct pcbit_dev *dev; + u_char info, + ack_seq, + read_seq; + + dev = (struct pcbit_dev *) devptr; + + if (!dev) { + printk(KERN_WARNING "pcbit_irq_handler: wrong device\n"); + return IRQ_NONE; + } + if (dev->interrupt) { + printk(KERN_DEBUG "pcbit: reentering interrupt handler\n"); + return IRQ_HANDLED; + } + dev->interrupt = 1; + + info = readb(dev->sh_mem + BANK3); + + if (dev->l2_state == L2_STARTING || dev->l2_state == L2_ERROR) { + pcbit_l2_active_conf(dev, info); + dev->interrupt = 0; + return IRQ_HANDLED; + } + if (info & 0x40U) { /* E bit set */ +#ifdef DEBUG + printk(KERN_DEBUG "pcbit_irq_handler: E bit on\n"); +#endif + pcbit_l2_error(dev); + dev->interrupt = 0; + return IRQ_HANDLED; + } + if (dev->l2_state != L2_RUNNING && dev->l2_state != L2_LOADING) { + dev->interrupt = 0; + return IRQ_HANDLED; + } + ack_seq = (info >> 3) & 0x07U; + read_seq = (info & 0x07U); + + dev->interrupt = 0; + + if (read_seq != dev->rcv_seq) { + while (read_seq != dev->rcv_seq) { + pcbit_receive(dev); + dev->rcv_seq = (dev->rcv_seq + 1) % 8; + } + pcbit_sched_delivery(dev); + } + if (ack_seq != dev->unack_seq) { + pcbit_recv_ack(dev, ack_seq); + } + info = dev->rcv_seq << 3; + info |= dev->send_seq; + + writeb(info, dev->sh_mem + BANK4); + return IRQ_HANDLED; +} + + +static void +pcbit_l2_active_conf(struct pcbit_dev *dev, u_char info) +{ + u_char state; + + state = dev->l2_state; + +#ifdef DEBUG + printk(KERN_DEBUG "layer2_active_confirm\n"); +#endif + + + if (info & 0x80U) { + dev->rcv_seq = info & 0x07U; + dev->l2_state = L2_RUNNING; + } else + dev->l2_state = L2_DOWN; + + if (state == L2_STARTING) + wake_up_interruptible(&dev->set_running_wq); + + if (state == L2_ERROR && dev->l2_state == L2_RUNNING) { + pcbit_transmit(dev); + } +} + +static void +pcbit_l2_err_recover(unsigned long data) +{ + + struct pcbit_dev *dev; + struct frame_buf *frame; + + dev = (struct pcbit_dev *) data; + + del_timer(&dev->error_recover_timer); + if (dev->w_busy || dev->r_busy) { + init_timer(&dev->error_recover_timer); + dev->error_recover_timer.expires = jiffies + ERRTIME; + add_timer(&dev->error_recover_timer); + return; + } + dev->w_busy = dev->r_busy = 1; + + if (dev->read_frame) { + kfree_skb(dev->read_frame->skb); + kfree(dev->read_frame); + dev->read_frame = NULL; + } + if (dev->write_queue) { + frame = dev->write_queue; +#ifdef FREE_ON_ERROR + dev->write_queue = dev->write_queue->next; + + if (frame->skb) { + dev_kfree_skb(frame->skb); + } + kfree(frame); +#else + frame->copied = 0; +#endif + } + dev->rcv_seq = dev->send_seq = dev->unack_seq = 0; + dev->free = 511; + dev->l2_state = L2_ERROR; + + /* this is an hack... */ + pcbit_firmware_bug(dev); + + dev->writeptr = dev->sh_mem; + dev->readptr = dev->sh_mem + BANK2; + + writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)), + dev->sh_mem + BANK4); + dev->w_busy = dev->r_busy = 0; + +} + +static void +pcbit_l2_error(struct pcbit_dev *dev) +{ + if (dev->l2_state == L2_RUNNING) { + + printk(KERN_INFO "pcbit: layer 2 error\n"); + +#ifdef DEBUG + log_state(dev); +#endif + + dev->l2_state = L2_DOWN; + + init_timer(&dev->error_recover_timer); + dev->error_recover_timer.function = &pcbit_l2_err_recover; + dev->error_recover_timer.data = (ulong) dev; + dev->error_recover_timer.expires = jiffies + ERRTIME; + add_timer(&dev->error_recover_timer); + } +} + +/* + * Description: + * if board acks frames + * update dev->free + * call pcbit_transmit to write possible queued frames + */ + +static void +pcbit_recv_ack(struct pcbit_dev *dev, unsigned char ack) +{ + int i, + count; + int unacked; + + unacked = (dev->send_seq + (8 - dev->unack_seq)) & 0x07; + + /* dev->unack_seq < ack <= dev->send_seq; */ + + if (unacked) { + + if (dev->send_seq > dev->unack_seq) { + if (ack <= dev->unack_seq || ack > dev->send_seq) { + printk(KERN_DEBUG + "layer 2 ack unacceptable - dev %d", + dev->id); + + pcbit_l2_error(dev); + } else if (ack > dev->send_seq && ack <= dev->unack_seq) { + printk(KERN_DEBUG + "layer 2 ack unacceptable - dev %d", + dev->id); + pcbit_l2_error(dev); + } + } + /* ack is acceptable */ + + + i = dev->unack_seq; + + do { + dev->unack_seq = i = (i + 1) % 8; + dev->free += dev->fsize[i]; + } while (i != ack); + + count = 0; + while (count < 7 && dev->write_queue) { + u8 lsend_seq = dev->send_seq; + + pcbit_transmit(dev); + + if (dev->send_seq == lsend_seq) + break; + count++; + } + } else + printk(KERN_DEBUG "recv_ack: unacked = 0\n"); +} diff --git a/drivers/staging/i4l/pcbit/layer2.h b/drivers/staging/i4l/pcbit/layer2.h new file mode 100644 index 000000000..be1327bc1 --- /dev/null +++ b/drivers/staging/i4l/pcbit/layer2.h @@ -0,0 +1,281 @@ +/* + * PCBIT-D low-layer interface definitions + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +/* + * 19991203 - Fernando Carvalho - takion@superbofh.org + * Hacked to compile with egcs and run with current version of isdn modules + */ + +#ifndef LAYER2_H +#define LAYER2_H + +#include <linux/interrupt.h> + +#include <asm/byteorder.h> + +#define BANK1 0x0000U /* PC -> Board */ +#define BANK2 0x01ffU /* Board -> PC */ +#define BANK3 0x03feU /* Att Board */ +#define BANK4 0x03ffU /* Att PC */ + +#define BANKLEN 0x01FFU + +#define LOAD_ZONE_START 0x03f8U +#define LOAD_ZONE_END 0x03fdU + +#define LOAD_RETRY 18000000 + + + +/* TAM - XX - C - S - NUM */ +#define PREHDR_LEN 8 +/* TT - M - I - TH - TD */ +#define FRAME_HDR_LEN 8 + +#define MSG_CONN_REQ 0x08000100 +#define MSG_CONN_CONF 0x00000101 +#define MSG_CONN_IND 0x00000102 +#define MSG_CONN_RESP 0x08000103 + +#define MSG_CONN_ACTV_REQ 0x08000300 +#define MSG_CONN_ACTV_CONF 0x00000301 +#define MSG_CONN_ACTV_IND 0x00000302 +#define MSG_CONN_ACTV_RESP 0x08000303 + +#define MSG_DISC_REQ 0x08000400 +#define MSG_DISC_CONF 0x00000401 +#define MSG_DISC_IND 0x00000402 +#define MSG_DISC_RESP 0x08000403 + +#define MSG_TDATA_REQ 0x0908E200 +#define MSG_TDATA_CONF 0x0000E201 +#define MSG_TDATA_IND 0x0000E202 +#define MSG_TDATA_RESP 0x0908E203 + +#define MSG_SELP_REQ 0x09004000 +#define MSG_SELP_CONF 0x00004001 + +#define MSG_ACT_TRANSP_REQ 0x0908E000 +#define MSG_ACT_TRANSP_CONF 0x0000E001 + +#define MSG_STPROT_REQ 0x09004100 +#define MSG_STPROT_CONF 0x00004101 + +#define MSG_PING188_REQ 0x09030500 +#define MSG_PING188_CONF 0x000005bc + +#define MSG_WATCH188 0x09030400 + +#define MSG_API_ON 0x08020102 +#define MSG_POOL_PCBIT 0x08020400 +#define MSG_POOL_PCBIT_CONF 0x00000401 + +#define MSG_INFO_IND 0x00002602 +#define MSG_INFO_RESP 0x08002603 + +#define MSG_DEBUG_188 0x0000ff00 + +/* + + long 4 3 2 1 + Intel 1 2 3 4 +*/ + +#ifdef __LITTLE_ENDIAN +#define SET_MSG_SCMD(msg, ch) (msg = (msg & 0xffffff00) | (((ch) & 0xff))) +#define SET_MSG_CMD(msg, ch) (msg = (msg & 0xffff00ff) | (((ch) & 0xff) << 8)) +#define SET_MSG_PROC(msg, ch) (msg = (msg & 0xff00ffff) | (((ch) & 0xff) << 16)) +#define SET_MSG_CPU(msg, ch) (msg = (msg & 0x00ffffff) | (((ch) & 0xff) << 24)) + +#define GET_MSG_SCMD(msg) ((msg) & 0xFF) +#define GET_MSG_CMD(msg) ((msg) >> 8 & 0xFF) +#define GET_MSG_PROC(msg) ((msg) >> 16 & 0xFF) +#define GET_MSG_CPU(msg) ((msg) >> 24) + +#else +#error "Non-Intel CPU" +#endif + +#define MAX_QUEUED 7 + +#define SCHED_READ 0x01 +#define SCHED_WRITE 0x02 + +#define SET_RUN_TIMEOUT 2 * HZ /* 2 seconds */ + +struct frame_buf { + ulong msg; + unsigned int refnum; + unsigned int dt_len; + unsigned int hdr_len; + struct sk_buff *skb; + unsigned int copied; + struct frame_buf *next; +}; + +extern int pcbit_l2_write(struct pcbit_dev *dev, ulong msg, ushort refnum, + struct sk_buff *skb, unsigned short hdr_len); + +extern irqreturn_t pcbit_irq_handler(int interrupt, void *); + +extern struct pcbit_dev *dev_pcbit[MAX_PCBIT_CARDS]; + +#ifdef DEBUG +static __inline__ void log_state(struct pcbit_dev *dev) { + printk(KERN_DEBUG "writeptr = %ld\n", + (ulong) (dev->writeptr - dev->sh_mem)); + printk(KERN_DEBUG "readptr = %ld\n", + (ulong) (dev->readptr - (dev->sh_mem + BANK2))); + printk(KERN_DEBUG "{rcv_seq=%01x, send_seq=%01x, unack_seq=%01x}\n", + dev->rcv_seq, dev->send_seq, dev->unack_seq); +} +#endif + +static __inline__ struct pcbit_dev *chan2dev(struct pcbit_chan *chan) +{ + struct pcbit_dev *dev; + int i; + + + for (i = 0; i < MAX_PCBIT_CARDS; i++) + if ((dev = dev_pcbit[i])) + if (dev->b1 == chan || dev->b2 == chan) + return dev; + return NULL; + +} + +static __inline__ struct pcbit_dev *finddev(int id) +{ + struct pcbit_dev *dev; + int i; + + for (i = 0; i < MAX_PCBIT_CARDS; i++) + if ((dev = dev_pcbit[i])) + if (dev->id == id) + return dev; + return NULL; +} + + +/* + * Support routines for reading and writing in the board + */ + +static __inline__ void pcbit_writeb(struct pcbit_dev *dev, unsigned char dt) +{ + writeb(dt, dev->writeptr++); + if (dev->writeptr == dev->sh_mem + BANKLEN) + dev->writeptr = dev->sh_mem; +} + +static __inline__ void pcbit_writew(struct pcbit_dev *dev, unsigned short dt) +{ + int dist; + + dist = BANKLEN - (dev->writeptr - dev->sh_mem); + switch (dist) { + case 2: + writew(dt, dev->writeptr); + dev->writeptr = dev->sh_mem; + break; + case 1: + writeb((u_char) (dt & 0x00ffU), dev->writeptr); + dev->writeptr = dev->sh_mem; + writeb((u_char) (dt >> 8), dev->writeptr++); + break; + default: + writew(dt, dev->writeptr); + dev->writeptr += 2; + break; + }; +} + +static __inline__ void memcpy_topcbit(struct pcbit_dev *dev, u_char *data, + int len) +{ + int diff; + + diff = len - (BANKLEN - (dev->writeptr - dev->sh_mem)); + + if (diff > 0) + { + memcpy_toio(dev->writeptr, data, len - diff); + memcpy_toio(dev->sh_mem, data + (len - diff), diff); + dev->writeptr = dev->sh_mem + diff; + } + else + { + memcpy_toio(dev->writeptr, data, len); + + dev->writeptr += len; + if (diff == 0) + dev->writeptr = dev->sh_mem; + } +} + +static __inline__ unsigned char pcbit_readb(struct pcbit_dev *dev) +{ + unsigned char val; + + val = readb(dev->readptr++); + if (dev->readptr == dev->sh_mem + BANK2 + BANKLEN) + dev->readptr = dev->sh_mem + BANK2; + + return val; +} + +static __inline__ unsigned short pcbit_readw(struct pcbit_dev *dev) +{ + int dist; + unsigned short val; + + dist = BANKLEN - (dev->readptr - (dev->sh_mem + BANK2)); + switch (dist) { + case 2: + val = readw(dev->readptr); + dev->readptr = dev->sh_mem + BANK2; + break; + case 1: + val = readb(dev->readptr); + dev->readptr = dev->sh_mem + BANK2; + val = (readb(dev->readptr++) << 8) | val; + break; + default: + val = readw(dev->readptr); + dev->readptr += 2; + break; + }; + return val; +} + +static __inline__ void memcpy_frompcbit(struct pcbit_dev *dev, u_char *data, int len) +{ + int diff; + + diff = len - (BANKLEN - (dev->readptr - (dev->sh_mem + BANK2))); + if (diff > 0) + { + memcpy_fromio(data, dev->readptr, len - diff); + memcpy_fromio(data + (len - diff), dev->sh_mem + BANK2 , diff); + dev->readptr = dev->sh_mem + BANK2 + diff; + } + else + { + memcpy_fromio(data, dev->readptr, len); + dev->readptr += len; + if (diff == 0) + dev->readptr = dev->sh_mem + BANK2; + } +} + + +#endif diff --git a/drivers/staging/i4l/pcbit/module.c b/drivers/staging/i4l/pcbit/module.c new file mode 100644 index 000000000..0a59bd0b8 --- /dev/null +++ b/drivers/staging/i4l/pcbit/module.c @@ -0,0 +1,125 @@ +/* + * PCBIT-D module support + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * 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/string.h> +#include <linux/kernel.h> +#include <linux/skbuff.h> + +#include <linux/isdnif.h> +#include "pcbit.h" + +MODULE_DESCRIPTION("ISDN4Linux: Driver for PCBIT-T card"); +MODULE_AUTHOR("Pedro Roque Marques"); +MODULE_LICENSE("GPL"); + +static int mem[MAX_PCBIT_CARDS]; +static int irq[MAX_PCBIT_CARDS]; + +module_param_array(mem, int, NULL, 0); +module_param_array(irq, int, NULL, 0); + +static int num_boards; +struct pcbit_dev *dev_pcbit[MAX_PCBIT_CARDS]; + +static int __init pcbit_init(void) +{ + int board; + + num_boards = 0; + + printk(KERN_NOTICE + "PCBIT-D device driver v 0.5-fjpc0 19991204 - " + "Copyright (C) 1996 Universidade de Lisboa\n"); + + if (mem[0] || irq[0]) + { + for (board = 0; board < MAX_PCBIT_CARDS && mem[board] && irq[board]; board++) + { + if (!mem[board]) + mem[board] = 0xD0000; + if (!irq[board]) + irq[board] = 5; + + if (pcbit_init_dev(board, mem[board], irq[board]) == 0) + num_boards++; + + else + { + printk(KERN_WARNING + "pcbit_init failed for dev %d", + board + 1); + return -EIO; + } + } + } + + /* Hardcoded default settings detection */ + + if (!num_boards) + { + printk(KERN_INFO + "Trying to detect board using default settings\n"); + if (pcbit_init_dev(0, 0xD0000, 5) == 0) + num_boards++; + else + return -EIO; + } + return 0; +} + +static void __exit pcbit_exit(void) +{ +#ifdef MODULE + int board; + + for (board = 0; board < num_boards; board++) + pcbit_terminate(board); + printk(KERN_NOTICE + "PCBIT-D module unloaded\n"); +#endif +} + +#ifndef MODULE +#define MAX_PARA (MAX_PCBIT_CARDS * 2) +static int __init pcbit_setup(char *line) +{ + int i, j, argc; + char *str; + int ints[MAX_PARA + 1]; + + str = get_options(line, MAX_PARA, ints); + argc = ints[0]; + i = 0; + j = 1; + + while (argc && (i < MAX_PCBIT_CARDS)) { + + if (argc) { + mem[i] = ints[j]; + j++; argc--; + } + + if (argc) { + irq[i] = ints[j]; + j++; argc--; + } + + i++; + } + return (1); +} +__setup("pcbit=", pcbit_setup); +#endif + +module_init(pcbit_init); +module_exit(pcbit_exit); diff --git a/drivers/staging/i4l/pcbit/pcbit.h b/drivers/staging/i4l/pcbit/pcbit.h new file mode 100644 index 000000000..0a5a99440 --- /dev/null +++ b/drivers/staging/i4l/pcbit/pcbit.h @@ -0,0 +1,177 @@ +/* + * PCBIT-D device driver definitions + * + * Copyright (C) 1996 Universidade de Lisboa + * + * Written by Pedro Roque Marques (roque@di.fc.ul.pt) + * + * This software may be used and distributed according to the terms of + * the GNU General Public License, incorporated herein by reference. + */ + +#ifndef PCBIT_H +#define PCBIT_H + +#include <linux/workqueue.h> + +#define MAX_PCBIT_CARDS 4 + + +#define BLOCK_TIMER + +#ifdef __KERNEL__ + +struct pcbit_chan { + unsigned short id; + unsigned short callref; /* Call Reference */ + unsigned char proto; /* layer2protocol */ + unsigned char queued; /* unacked data messages */ + unsigned char layer2link; /* used in TData */ + unsigned char snum; /* used in TData */ + unsigned short s_refnum; + unsigned short r_refnum; + unsigned short fsm_state; + struct timer_list fsm_timer; +#ifdef BLOCK_TIMER + struct timer_list block_timer; +#endif +}; + +struct msn_entry { + char *msn; + struct msn_entry *next; +}; + +struct pcbit_dev { + /* board */ + + volatile unsigned char __iomem *sh_mem; /* RDP address */ + unsigned long ph_mem; + unsigned int irq; + unsigned int id; + unsigned int interrupt; /* set during interrupt + processing */ + spinlock_t lock; + /* isdn4linux */ + + struct msn_entry *msn_list; /* ISDN address list */ + + isdn_if *dev_if; + + ushort ll_hdrlen; + ushort hl_hdrlen; + + /* link layer */ + unsigned char l2_state; + + struct frame_buf *read_queue; + struct frame_buf *read_frame; + struct frame_buf *write_queue; + + /* Protocol start */ + wait_queue_head_t set_running_wq; + struct timer_list set_running_timer; + + struct timer_list error_recover_timer; + + struct work_struct qdelivery; + + u_char w_busy; + u_char r_busy; + + volatile unsigned char __iomem *readptr; + volatile unsigned char __iomem *writeptr; + + ushort loadptr; + + unsigned short fsize[8]; /* sent layer2 frames size */ + + unsigned char send_seq; + unsigned char rcv_seq; + unsigned char unack_seq; + + unsigned short free; + + /* channels */ + + struct pcbit_chan *b1; + struct pcbit_chan *b2; +}; + +#define STATS_TIMER (10 * HZ) +#define ERRTIME (HZ / 10) + +/* MRU */ +#define MAXBUFSIZE 1534 +#define MRU MAXBUFSIZE + +#define STATBUF_LEN 2048 +/* + * + */ + +#endif /* __KERNEL__ */ + +/* isdn_ctrl only allows a long sized argument */ + +struct pcbit_ioctl { + union { + struct byte_op { + ushort addr; + ushort value; + } rdp_byte; + unsigned long l2_status; + } info; +}; + + + +#define PCBIT_IOCTL_GETSTAT 0x01 /* layer2 status */ +#define PCBIT_IOCTL_LWMODE 0x02 /* linear write mode */ +#define PCBIT_IOCTL_STRLOAD 0x03 /* start load mode */ +#define PCBIT_IOCTL_ENDLOAD 0x04 /* end load mode */ +#define PCBIT_IOCTL_SETBYTE 0x05 /* set byte */ +#define PCBIT_IOCTL_GETBYTE 0x06 /* get byte */ +#define PCBIT_IOCTL_RUNNING 0x07 /* set protocol running */ +#define PCBIT_IOCTL_WATCH188 0x08 /* set watch 188 */ +#define PCBIT_IOCTL_PING188 0x09 /* ping 188 */ +#define PCBIT_IOCTL_FWMODE 0x0A /* firmware write mode */ +#define PCBIT_IOCTL_STOP 0x0B /* stop protocol */ +#define PCBIT_IOCTL_APION 0x0C /* issue API_ON */ + +#ifndef __KERNEL__ + +#define PCBIT_GETSTAT (PCBIT_IOCTL_GETSTAT + IIOCDRVCTL) +#define PCBIT_LWMODE (PCBIT_IOCTL_LWMODE + IIOCDRVCTL) +#define PCBIT_STRLOAD (PCBIT_IOCTL_STRLOAD + IIOCDRVCTL) +#define PCBIT_ENDLOAD (PCBIT_IOCTL_ENDLOAD + IIOCDRVCTL) +#define PCBIT_SETBYTE (PCBIT_IOCTL_SETBYTE + IIOCDRVCTL) +#define PCBIT_GETBYTE (PCBIT_IOCTL_GETBYTE + IIOCDRVCTL) +#define PCBIT_RUNNING (PCBIT_IOCTL_RUNNING + IIOCDRVCTL) +#define PCBIT_WATCH188 (PCBIT_IOCTL_WATCH188 + IIOCDRVCTL) +#define PCBIT_PING188 (PCBIT_IOCTL_PING188 + IIOCDRVCTL) +#define PCBIT_FWMODE (PCBIT_IOCTL_FWMODE + IIOCDRVCTL) +#define PCBIT_STOP (PCBIT_IOCTL_STOP + IIOCDRVCTL) +#define PCBIT_APION (PCBIT_IOCTL_APION + IIOCDRVCTL) + +#define MAXSUPERLINE 3000 + +#endif + +#define L2_DOWN 0 +#define L2_LOADING 1 +#define L2_LWMODE 2 +#define L2_FWMODE 3 +#define L2_STARTING 4 +#define L2_RUNNING 5 +#define L2_ERROR 6 + +void pcbit_deliver(struct work_struct *work); +int pcbit_init_dev(int board, int mem_base, int irq); +void pcbit_terminate(int board); +void pcbit_l3_receive(struct pcbit_dev *dev, ulong msg, struct sk_buff *skb, + ushort hdr_len, ushort refnum); +void pcbit_state_change(struct pcbit_dev *dev, struct pcbit_chan *chan, + unsigned short i, unsigned short ev, unsigned short f); + +#endif |