Commit 02bbd980 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

staging: i4l: delete the whole thing

It's now 2017, and a new LTS kernel has been chosen, so let's do what we
said we would do in the TODO file and delete this code.  If it's still
needed, and a maintainer steps up to take it over, we will easily revert
it.

Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Karsten Keil <isdn@linux-pingi.de>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 457c005a
......@@ -96,8 +96,6 @@ source "drivers/staging/wilc1000/Kconfig"
source "drivers/staging/most/Kconfig"
source "drivers/staging/i4l/Kconfig"
source "drivers/staging/ks7010/Kconfig"
source "drivers/staging/greybus/Kconfig"
......
......@@ -37,7 +37,6 @@ obj-$(CONFIG_FB_TFT) += fbtft/
obj-$(CONFIG_FSL_MC_BUS) += fsl-mc/
obj-$(CONFIG_WILC1000) += wilc1000/
obj-$(CONFIG_MOST) += most/
obj-$(CONFIG_ISDN_I4L) += i4l/
obj-$(CONFIG_KS7010) += ks7010/
obj-$(CONFIG_GREYBUS) += greybus/
obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/
$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.
$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.
------------------------------------------------------------------------------
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>
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.
#
# 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
# 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/
* 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.
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.
# 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
/* $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 */
/* $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;
reg = inb(portbase + ISA_COR);
if (reg != 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))
return -EBUSY;
else {
card->port = portbase;
card->flags |= ACT2000_FLAGS_PVALID;
return 0;
}
}
/*
* Release resources, 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) {
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)) {
card->sbuf = skb_dequeue(&card->sndq);
if (card->sbuf) {
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;
}
p = strchr(fid.revision, '\n');
if (p)
*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;
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;
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);
}
/* $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 */
/* $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;
__u8 blocknr;
int chan;
actcapi_msg *msg = (actcapi_msg *)skb->data;
EVAL_NCCI(msg->msg.data_b3_ind.fakencci, plci, 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) {
/* 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
/* $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, ncci) { \
plci = fakencci & 0x1f; \
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)
*/
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
/* $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;
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;
ret = act2000_set_msn(card, tmp);
if (ret)
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);
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.
# Makefile for the icn ISDN device driver
# Each configuration option enables a list of files.
obj-$(CONFIG_ISDN_DRV_ICN) += icn.o
/* $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;
skb = card->xskb[channel];
if (skb) {
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) {
cnt = card->rcvidx[channel];
if (cnt) {
skb = dev_alloc_skb(cnt);
if (!skb) {
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
codebuf = memdup_user(buffer, ICN_CODE_STAGE1);
if (IS_ERR(codebuf))
return PTR_ERR(codebuf);
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 */
ret = icn_check_loader(card->doubleS0 ? 2 : 1);
if (ret)
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);
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);
setup_timer(&card->st_timer, icn_polldchan,
(unsigned long)card);
mod_timer(&card->st_timer,
jiffies + ICN_TIMER_DCREAD);
card->flags |= ICN_FLAGS_RUNNING;
if (card->doubleS0) {
setup_timer(&card->other->st_timer,
icn_polldchan,
(unsigned long)card->other);
mod_timer(&card->other->st_timer,
jiffies + ICN_TIMER_DCREAD);
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 __user *ubuf, const u_char *kbuf, 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, ubuf, count))
return -EFAULT;
} else
memcpy(msg, kbuf, 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);
i = (icn_loadproto(arg, card));
if (i)
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(NULL, 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(NULL, 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(NULL, 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(NULL, cbuf,
strlen(cbuf), 0,
card);
}
sprintf(cbuf, "%02d;DCON_R\n", (int)a);
i = icn_writecmd(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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(NULL, 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, NULL, 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;
card = kzalloc(sizeof(icn_card), GFP_KERNEL);
if (!card) {
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;
card = icn_initcard(port, id1);
if (!card)
return -EIO;
if (!strlen(id2)) {
printk(KERN_INFO
"icn: (%s) ICN-2B, port 0x%x added\n",
card->interface.id, port);
return 0;
}
card2 = icn_initcard(port, id2);
if (!card2) {
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;
p = strchr(sid, ',');
if (p) {
*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);
p = strchr(revision, ':');
if (p) {
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);
/* $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) {pr_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 */
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.
# 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
/*
* 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 <linux/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);
}
/*
* 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
/*
* 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/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/skbuff.h>
#include <linux/io.h>
#include <linux/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
/*
* 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
/*
* 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/skbuff.h>
#include <linux/isdnif.h>
#include <linux/string.h>
#include <linux/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) {
setup_timer(&chan->block_timer, &pcbit_block_timer,
(long)chan);
mod_timer(&chan->block_timer, jiffies + 1 * HZ);
}
#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;
static int stat_end;
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;
setup_timer(&dev->set_running_timer, &set_running_timeout, (ulong)dev);
/* kick it */
dev->l2_state = L2_STARTING;
writeb((0x80U | ((dev->rcv_seq & 0x07) << 3) | (dev->send_seq & 0x07)),
dev->sh_mem + BANK4);
mod_timer(&dev->set_running_timer, jiffies + SET_RUN_TIMEOUT);
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;
}
/*
* 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 <linux/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) {
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) {
setup_timer(&chan->fsm_timer, &pcbit_fsm_timer, (ulong)chan);
mod_timer(&chan->fsm_timer, jiffies + tentry->timeout * HZ);
}
spin_unlock_irqrestore(&dev->lock, flags);
if (action->callb)
action->callb(dev, chan, data);
}
/*
* 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
/*
* 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 <linux/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;
setup_timer(&dev->error_recover_timer, &pcbit_l2_err_recover,
(ulong)dev);
mod_timer(&dev->error_recover_timer, jiffies + ERRTIME);
}
}
/*
* 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");
}
/*
* 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
/*
* 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);
/*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment