linux/drivers/isdn/i4l/isdn_common.c
Alan Cox 33f0f88f1c [PATCH] TTY layer buffering revamp
The API and code have been through various bits of initial review by
serial driver people but they definitely need to live somewhere for a
while so the unconverted drivers can get knocked into shape, existing
drivers that have been updated can be better tuned and bugs whacked out.

This replaces the tty flip buffers with kmalloc objects in rings. In the
normal situation for an IRQ driven serial port at typical speeds the
behaviour is pretty much the same, two buffers end up allocated and the
kernel cycles between them as before.

When there are delays or at high speed we now behave far better as the
buffer pool can grow a bit rather than lose characters. This also means
that we can operate at higher speeds reliably.

For drivers that receive characters in blocks (DMA based, USB and
especially virtualisation) the layer allows a lot of driver specific
code that works around the tty layer with private secondary queues to be
removed. The IBM folks need this sort of layer, the smart serial port
people do, the virtualisers do (because a virtualised tty typically
operates at infinite speed rather than emulating 9600 baud).

Finally many drivers had invalid and unsafe attempts to avoid buffer
overflows by directly invoking tty methods extracted out of the innards
of work queue structs. These are no longer needed and all go away. That
fixes various random hangs with serial ports on overflow.

The other change in here is to optimise the receive_room path that is
used by some callers. It turns out that only one ldisc uses receive room
except asa constant and it updates it far far less than the value is
read. We thus make it a variable not a function call.

I expect the code to contain bugs due to the size alone but I'll be
watching and squashing them and feeding out new patches as it goes.

Because the buffers now dynamically expand you should only run out of
buffering when the kernel runs out of memory for real.  That means a lot of
the horrible hacks high performance drivers used to do just aren't needed any
more.

Description:

tty_insert_flip_char is an old API and continues to work as before, as does
tty_flip_buffer_push() [this is why many drivers dont need modification].  It
does now also return the number of chars inserted

There are also

tty_buffer_request_room(tty, len)

which asks for a buffer block of the length requested and returns the space
found.  This improves efficiency with hardware that knows how much to
transfer.

and tty_insert_flip_string_flags(tty, str, flags, len)

to insert a string of characters and flags

For a smart interface the usual code is

    len = tty_request_buffer_room(tty, amount_hardware_says);
    tty_insert_flip_string(tty, buffer_from_card, len);

More description!

At the moment tty buffers are attached directly to the tty.  This is causing a
lot of the problems related to tty layer locking, also problems at high speed
and also with bursty data (such as occurs in virtualised environments)

I'm working on ripping out the flip buffers and replacing them with a pool of
dynamically allocated buffers.  This allows both for old style "byte I/O"
devices and also helps virtualisation and smart devices where large blocks of
data suddenely materialise and need storing.

So far so good.  Lots of drivers reference tty->flip.*.  Several of them also
call directly and unsafely into function pointers it provides.  This will all
break.  Most drivers can use tty_insert_flip_char which can be kept as an API
but others need more.

At the moment I've added the following interfaces, if people think more will
be needed now is a good time to say

 int tty_buffer_request_room(tty, size)

Try and ensure at least size bytes are available, returns actual room (may be
zero).  At the moment it just uses the flipbuf space but that will change.
Repeated calls without characters being added are not cumulative.  (ie if you
call it with 1, 1, 1, and then 4 you'll have four characters of space.  The
other functions will also try and grow buffers in future but this will be a
more efficient way when you know block sizes.

 int tty_insert_flip_char(tty, ch, flag)

As before insert a character if there is room.  Now returns 1 for success, 0
for failure.

 int tty_insert_flip_string(tty, str, len)

Insert a block of non error characters.  Returns the number inserted.

 int tty_prepare_flip_string(tty, strptr, len)

Adjust the buffer to allow len characters to be added.  Returns a buffer
pointer in strptr and the length available.  This allows for hardware that
needs to use functions like insl or mencpy_fromio.

Signed-off-by: Alan Cox <alan@redhat.com>
Cc: Paul Fulghum <paulkf@microgate.com>
Signed-off-by: Hirokazu Takata <takata@linux-m32r.org>
Signed-off-by: Serge Hallyn <serue@us.ibm.com>
Signed-off-by: Jeff Dike <jdike@addtoit.com>
Signed-off-by: John Hawkes <hawkes@sgi.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-01-10 08:01:59 -08:00

2366 lines
57 KiB
C

/* $Id: isdn_common.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $
*
* Linux ISDN subsystem, common used functions (linklevel).
*
* Copyright 1994-1999 by Fritz Elfert (fritz@isdn4linux.de)
* Copyright 1995,96 Thinking Objects Software GmbH Wuerzburg
* Copyright 1995,96 by Michael Hipp (Michael.Hipp@student.uni-tuebingen.de)
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/vmalloc.h>
#include <linux/isdn.h>
#include <linux/smp_lock.h>
#include "isdn_common.h"
#include "isdn_tty.h"
#include "isdn_net.h"
#include "isdn_ppp.h"
#ifdef CONFIG_ISDN_AUDIO
#include "isdn_audio.h"
#endif
#ifdef CONFIG_ISDN_DIVERSION_MODULE
#define CONFIG_ISDN_DIVERSION
#endif
#ifdef CONFIG_ISDN_DIVERSION
#include <linux/isdn_divertif.h>
#endif /* CONFIG_ISDN_DIVERSION */
#include "isdn_v110.h"
/* Debugflags */
#undef ISDN_DEBUG_STATCALLB
MODULE_DESCRIPTION("ISDN4Linux: link layer");
MODULE_AUTHOR("Fritz Elfert");
MODULE_LICENSE("GPL");
isdn_dev *dev;
static char *isdn_revision = "$Revision: 1.1.2.3 $";
extern char *isdn_net_revision;
extern char *isdn_tty_revision;
#ifdef CONFIG_ISDN_PPP
extern char *isdn_ppp_revision;
#else
static char *isdn_ppp_revision = ": none $";
#endif
#ifdef CONFIG_ISDN_AUDIO
extern char *isdn_audio_revision;
#else
static char *isdn_audio_revision = ": none $";
#endif
extern char *isdn_v110_revision;
#ifdef CONFIG_ISDN_DIVERSION
static isdn_divert_if *divert_if; /* = NULL */
#endif /* CONFIG_ISDN_DIVERSION */
static int isdn_writebuf_stub(int, int, const u_char __user *, int);
static void set_global_features(void);
static int isdn_wildmat(char *s, char *p);
static int isdn_add_channels(isdn_driver_t *d, int drvidx, int n, int adding);
static inline void
isdn_lock_driver(isdn_driver_t *drv)
{
try_module_get(drv->interface->owner);
drv->locks++;
}
void
isdn_lock_drivers(void)
{
int i;
for (i = 0; i < ISDN_MAX_DRIVERS; i++) {
if (!dev->drv[i])
continue;
isdn_lock_driver(dev->drv[i]);
}
}
static inline void
isdn_unlock_driver(isdn_driver_t *drv)
{
if (drv->locks > 0) {
drv->locks--;
module_put(drv->interface->owner);
}
}
void
isdn_unlock_drivers(void)
{
int i;
for (i = 0; i < ISDN_MAX_DRIVERS; i++) {
if (!dev->drv[i])
continue;
isdn_unlock_driver(dev->drv[i]);
}
}
#if defined(ISDN_DEBUG_NET_DUMP) || defined(ISDN_DEBUG_MODEM_DUMP)
void
isdn_dumppkt(char *s, u_char * p, int len, int dumplen)
{
int dumpc;
printk(KERN_DEBUG "%s(%d) ", s, len);
for (dumpc = 0; (dumpc < dumplen) && (len); len--, dumpc++)
printk(" %02x", *p++);
printk("\n");
}
#endif
/*
* I picked the pattern-matching-functions from an old GNU-tar version (1.10)
* It was originally written and put to PD by rs@mirror.TMC.COM (Rich Salz)
*/
static int
isdn_star(char *s, char *p)
{
while (isdn_wildmat(s, p)) {
if (*++s == '\0')
return (2);
}
return (0);
}
/*
* Shell-type Pattern-matching for incoming caller-Ids
* This function gets a string in s and checks, if it matches the pattern
* given in p.
*
* Return:
* 0 = match.
* 1 = no match.
* 2 = no match. Would eventually match, if s would be longer.
*
* Possible Patterns:
*
* '?' matches one character
* '*' matches zero or more characters
* [xyz] matches the set of characters in brackets.
* [^xyz] matches any single character not in the set of characters
*/
static int
isdn_wildmat(char *s, char *p)
{
register int last;
register int matched;
register int reverse;
register int nostar = 1;
if (!(*s) && !(*p))
return(1);
for (; *p; s++, p++)
switch (*p) {
case '\\':
/*
* Literal match with following character,
* fall through.
*/
p++;
default:
if (*s != *p)
return (*s == '\0')?2:1;
continue;
case '?':
/* Match anything. */
if (*s == '\0')
return (2);
continue;
case '*':
nostar = 0;
/* Trailing star matches everything. */
return (*++p ? isdn_star(s, p) : 0);
case '[':
/* [^....] means inverse character class. */
if ((reverse = (p[1] == '^')))
p++;
for (last = 0, matched = 0; *++p && (*p != ']'); last = *p)
/* This next line requires a good C compiler. */
if (*p == '-' ? *s <= *++p && *s >= last : *s == *p)
matched = 1;
if (matched == reverse)
return (1);
continue;
}
return (*s == '\0')?0:nostar;
}
int isdn_msncmp( const char * msn1, const char * msn2 )
{
char TmpMsn1[ ISDN_MSNLEN ];
char TmpMsn2[ ISDN_MSNLEN ];
char *p;
for ( p = TmpMsn1; *msn1 && *msn1 != ':'; ) // Strip off a SPID
*p++ = *msn1++;
*p = '\0';
for ( p = TmpMsn2; *msn2 && *msn2 != ':'; ) // Strip off a SPID
*p++ = *msn2++;
*p = '\0';
return isdn_wildmat( TmpMsn1, TmpMsn2 );
}
int
isdn_dc2minor(int di, int ch)
{
int i;
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (dev->chanmap[i] == ch && dev->drvmap[i] == di)
return i;
return -1;
}
static int isdn_timer_cnt1 = 0;
static int isdn_timer_cnt2 = 0;
static int isdn_timer_cnt3 = 0;
static void
isdn_timer_funct(ulong dummy)
{
int tf = dev->tflags;
if (tf & ISDN_TIMER_FAST) {
if (tf & ISDN_TIMER_MODEMREAD)
isdn_tty_readmodem();
if (tf & ISDN_TIMER_MODEMPLUS)
isdn_tty_modem_escape();
if (tf & ISDN_TIMER_MODEMXMIT)
isdn_tty_modem_xmit();
}
if (tf & ISDN_TIMER_SLOW) {
if (++isdn_timer_cnt1 >= ISDN_TIMER_02SEC) {
isdn_timer_cnt1 = 0;
if (tf & ISDN_TIMER_NETDIAL)
isdn_net_dial();
}
if (++isdn_timer_cnt2 >= ISDN_TIMER_1SEC) {
isdn_timer_cnt2 = 0;
if (tf & ISDN_TIMER_NETHANGUP)
isdn_net_autohup();
if (++isdn_timer_cnt3 >= ISDN_TIMER_RINGING) {
isdn_timer_cnt3 = 0;
if (tf & ISDN_TIMER_MODEMRING)
isdn_tty_modem_ring();
}
if (tf & ISDN_TIMER_CARRIER)
isdn_tty_carrier_timeout();
}
}
if (tf)
mod_timer(&dev->timer, jiffies+ISDN_TIMER_RES);
}
void
isdn_timer_ctrl(int tf, int onoff)
{
unsigned long flags;
int old_tflags;
spin_lock_irqsave(&dev->timerlock, flags);
if ((tf & ISDN_TIMER_SLOW) && (!(dev->tflags & ISDN_TIMER_SLOW))) {
/* If the slow-timer wasn't activated until now */
isdn_timer_cnt1 = 0;
isdn_timer_cnt2 = 0;
}
old_tflags = dev->tflags;
if (onoff)
dev->tflags |= tf;
else
dev->tflags &= ~tf;
if (dev->tflags && !old_tflags)
mod_timer(&dev->timer, jiffies+ISDN_TIMER_RES);
spin_unlock_irqrestore(&dev->timerlock, flags);
}
/*
* Receive a packet from B-Channel. (Called from low-level-module)
*/
static void
isdn_receive_skb_callback(int di, int channel, struct sk_buff *skb)
{
int i;
if ((i = isdn_dc2minor(di, channel)) == -1) {
dev_kfree_skb(skb);
return;
}
/* Update statistics */
dev->ibytes[i] += skb->len;
/* First, try to deliver data to network-device */
if (isdn_net_rcv_skb(i, skb))
return;
/* V.110 handling
* makes sense for async streams only, so it is
* called after possible net-device delivery.
*/
if (dev->v110[i]) {
atomic_inc(&dev->v110use[i]);
skb = isdn_v110_decode(dev->v110[i], skb);
atomic_dec(&dev->v110use[i]);
if (!skb)
return;
}
/* No network-device found, deliver to tty or raw-channel */
if (skb->len) {
if (isdn_tty_rcv_skb(i, di, channel, skb))
return;
wake_up_interruptible(&dev->drv[di]->rcv_waitq[channel]);
} else
dev_kfree_skb(skb);
}
/*
* Intercept command from Linklevel to Lowlevel.
* If layer 2 protocol is V.110 and this is not supported by current
* lowlevel-driver, use driver's transparent mode and handle V.110 in
* linklevel instead.
*/
int
isdn_command(isdn_ctrl *cmd)
{
if (cmd->driver == -1) {
printk(KERN_WARNING "isdn_command command(%x) driver -1\n", cmd->command);
return(1);
}
if (cmd->command == ISDN_CMD_SETL2) {
int idx = isdn_dc2minor(cmd->driver, cmd->arg & 255);
unsigned long l2prot = (cmd->arg >> 8) & 255;
unsigned long features = (dev->drv[cmd->driver]->interface->features
>> ISDN_FEATURE_L2_SHIFT) &
ISDN_FEATURE_L2_MASK;
unsigned long l2_feature = (1 << l2prot);
switch (l2prot) {
case ISDN_PROTO_L2_V11096:
case ISDN_PROTO_L2_V11019:
case ISDN_PROTO_L2_V11038:
/* If V.110 requested, but not supported by
* HL-driver, set emulator-flag and change
* Layer-2 to transparent
*/
if (!(features & l2_feature)) {
dev->v110emu[idx] = l2prot;
cmd->arg = (cmd->arg & 255) |
(ISDN_PROTO_L2_TRANS << 8);
} else
dev->v110emu[idx] = 0;
}
}
return dev->drv[cmd->driver]->interface->command(cmd);
}
void
isdn_all_eaz(int di, int ch)
{
isdn_ctrl cmd;
if (di < 0)
return;
cmd.driver = di;
cmd.arg = ch;
cmd.command = ISDN_CMD_SETEAZ;
cmd.parm.num[0] = '\0';
isdn_command(&cmd);
}
/*
* Begin of a CAPI like LL<->HL interface, currently used only for
* supplementary service (CAPI 2.0 part III)
*/
#include <linux/isdn/capicmd.h>
static int
isdn_capi_rec_hl_msg(capi_msg *cm) {
int di;
int ch;
di = (cm->adr.Controller & 0x7f) -1;
ch = isdn_dc2minor(di, (cm->adr.Controller>>8)& 0x7f);
switch(cm->Command) {
case CAPI_FACILITY:
/* in the moment only handled in tty */
return(isdn_tty_capi_facility(cm));
default:
return(-1);
}
}
static int
isdn_status_callback(isdn_ctrl * c)
{
int di;
u_long flags;
int i;
int r;
int retval = 0;
isdn_ctrl cmd;
isdn_net_dev *p;
di = c->driver;
i = isdn_dc2minor(di, c->arg);
switch (c->command) {
case ISDN_STAT_BSENT:
if (i < 0)
return -1;
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
if (isdn_net_stat_callback(i, c))
return 0;
if (isdn_v110_stat_callback(i, c))
return 0;
if (isdn_tty_stat_callback(i, c))
return 0;
wake_up_interruptible(&dev->drv[di]->snd_waitq[c->arg]);
break;
case ISDN_STAT_STAVAIL:
dev->drv[di]->stavail += c->arg;
wake_up_interruptible(&dev->drv[di]->st_waitq);
break;
case ISDN_STAT_RUN:
dev->drv[di]->flags |= DRV_FLAG_RUNNING;
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (dev->drvmap[i] == di)
isdn_all_eaz(di, dev->chanmap[i]);
set_global_features();
break;
case ISDN_STAT_STOP:
dev->drv[di]->flags &= ~DRV_FLAG_RUNNING;
break;
case ISDN_STAT_ICALL:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "ICALL (net): %d %ld %s\n", di, c->arg, c->parm.num);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED) {
cmd.driver = di;
cmd.arg = c->arg;
cmd.command = ISDN_CMD_HANGUP;
isdn_command(&cmd);
return 0;
}
/* Try to find a network-interface which will accept incoming call */
r = ((c->command == ISDN_STAT_ICALLW) ? 0 : isdn_net_find_icall(di, c->arg, i, &c->parm.setup));
switch (r) {
case 0:
/* No network-device replies.
* Try ttyI's.
* These return 0 on no match, 1 on match and
* 3 on eventually match, if CID is longer.
*/
if (c->command == ISDN_STAT_ICALL)
if ((retval = isdn_tty_find_icall(di, c->arg, &c->parm.setup))) return(retval);
#ifdef CONFIG_ISDN_DIVERSION
if (divert_if)
if ((retval = divert_if->stat_callback(c)))
return(retval); /* processed */
#endif /* CONFIG_ISDN_DIVERSION */
if ((!retval) && (dev->drv[di]->flags & DRV_FLAG_REJBUS)) {
/* No tty responding */
cmd.driver = di;
cmd.arg = c->arg;
cmd.command = ISDN_CMD_HANGUP;
isdn_command(&cmd);
retval = 2;
}
break;
case 1:
/* Schedule connection-setup */
isdn_net_dial();
cmd.driver = di;
cmd.arg = c->arg;
cmd.command = ISDN_CMD_ACCEPTD;
for ( p = dev->netdev; p; p = p->next )
if ( p->local->isdn_channel == cmd.arg )
{
strcpy( cmd.parm.setup.eazmsn, p->local->msn );
isdn_command(&cmd);
retval = 1;
break;
}
break;
case 2: /* For calling back, first reject incoming call ... */
case 3: /* Interface found, but down, reject call actively */
retval = 2;
printk(KERN_INFO "isdn: Rejecting Call\n");
cmd.driver = di;
cmd.arg = c->arg;
cmd.command = ISDN_CMD_HANGUP;
isdn_command(&cmd);
if (r == 3)
break;
/* Fall through */
case 4:
/* ... then start callback. */
isdn_net_dial();
break;
case 5:
/* Number would eventually match, if longer */
retval = 3;
break;
}
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "ICALL: ret=%d\n", retval);
#endif
return retval;
break;
case ISDN_STAT_CINF:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "CINF: %ld %s\n", c->arg, c->parm.num);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
if (strcmp(c->parm.num, "0"))
isdn_net_stat_callback(i, c);
isdn_tty_stat_callback(i, c);
break;
case ISDN_STAT_CAUSE:
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "CAUSE: %ld %s\n", c->arg, c->parm.num);
#endif
printk(KERN_INFO "isdn: %s,ch%ld cause: %s\n",
dev->drvid[di], c->arg, c->parm.num);
isdn_tty_stat_callback(i, c);
#ifdef CONFIG_ISDN_DIVERSION
if (divert_if)
divert_if->stat_callback(c);
#endif /* CONFIG_ISDN_DIVERSION */
break;
case ISDN_STAT_DISPLAY:
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "DISPLAY: %ld %s\n", c->arg, c->parm.display);
#endif
isdn_tty_stat_callback(i, c);
#ifdef CONFIG_ISDN_DIVERSION
if (divert_if)
divert_if->stat_callback(c);
#endif /* CONFIG_ISDN_DIVERSION */
break;
case ISDN_STAT_DCONN:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "DCONN: %ld\n", c->arg);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
/* Find any net-device, waiting for D-channel setup */
if (isdn_net_stat_callback(i, c))
break;
isdn_v110_stat_callback(i, c);
/* Find any ttyI, waiting for D-channel setup */
if (isdn_tty_stat_callback(i, c)) {
cmd.driver = di;
cmd.arg = c->arg;
cmd.command = ISDN_CMD_ACCEPTB;
isdn_command(&cmd);
break;
}
break;
case ISDN_STAT_DHUP:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "DHUP: %ld\n", c->arg);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
dev->drv[di]->online &= ~(1 << (c->arg));
isdn_info_update();
/* Signal hangup to network-devices */
if (isdn_net_stat_callback(i, c))
break;
isdn_v110_stat_callback(i, c);
if (isdn_tty_stat_callback(i, c))
break;
#ifdef CONFIG_ISDN_DIVERSION
if (divert_if)
divert_if->stat_callback(c);
#endif /* CONFIG_ISDN_DIVERSION */
break;
break;
case ISDN_STAT_BCONN:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "BCONN: %ld\n", c->arg);
#endif
/* Signal B-channel-connect to network-devices */
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
dev->drv[di]->online |= (1 << (c->arg));
isdn_info_update();
if (isdn_net_stat_callback(i, c))
break;
isdn_v110_stat_callback(i, c);
if (isdn_tty_stat_callback(i, c))
break;
break;
case ISDN_STAT_BHUP:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "BHUP: %ld\n", c->arg);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
dev->drv[di]->online &= ~(1 << (c->arg));
isdn_info_update();
#ifdef CONFIG_ISDN_X25
/* Signal hangup to network-devices */
if (isdn_net_stat_callback(i, c))
break;
#endif
isdn_v110_stat_callback(i, c);
if (isdn_tty_stat_callback(i, c))
break;
break;
case ISDN_STAT_NODCH:
if (i < 0)
return -1;
#ifdef ISDN_DEBUG_STATCALLB
printk(KERN_DEBUG "NODCH: %ld\n", c->arg);
#endif
if (dev->global_flags & ISDN_GLOBAL_STOPPED)
return 0;
if (isdn_net_stat_callback(i, c))
break;
if (isdn_tty_stat_callback(i, c))
break;
break;
case ISDN_STAT_ADDCH:
spin_lock_irqsave(&dev->lock, flags);
if (isdn_add_channels(dev->drv[di], di, c->arg, 1)) {
spin_unlock_irqrestore(&dev->lock, flags);
return -1;
}
spin_unlock_irqrestore(&dev->lock, flags);
isdn_info_update();
break;
case ISDN_STAT_DISCH:
spin_lock_irqsave(&dev->lock, flags);
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if ((dev->drvmap[i] == di) &&
(dev->chanmap[i] == c->arg)) {
if (c->parm.num[0])
dev->usage[i] &= ~ISDN_USAGE_DISABLED;
else
if (USG_NONE(dev->usage[i])) {
dev->usage[i] |= ISDN_USAGE_DISABLED;
}
else
retval = -1;
break;
}
spin_unlock_irqrestore(&dev->lock, flags);
isdn_info_update();
break;
case ISDN_STAT_UNLOAD:
while (dev->drv[di]->locks > 0) {
isdn_unlock_driver(dev->drv[di]);
}
spin_lock_irqsave(&dev->lock, flags);
isdn_tty_stat_callback(i, c);
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (dev->drvmap[i] == di) {
dev->drvmap[i] = -1;
dev->chanmap[i] = -1;
dev->usage[i] &= ~ISDN_USAGE_DISABLED;
}
dev->drivers--;
dev->channels -= dev->drv[di]->channels;
kfree(dev->drv[di]->rcverr);
kfree(dev->drv[di]->rcvcount);
for (i = 0; i < dev->drv[di]->channels; i++)
skb_queue_purge(&dev->drv[di]->rpqueue[i]);
kfree(dev->drv[di]->rpqueue);
kfree(dev->drv[di]->rcv_waitq);
kfree(dev->drv[di]);
dev->drv[di] = NULL;
dev->drvid[di][0] = '\0';
isdn_info_update();
set_global_features();
spin_unlock_irqrestore(&dev->lock, flags);
return 0;
case ISDN_STAT_L1ERR:
break;
case CAPI_PUT_MESSAGE:
return(isdn_capi_rec_hl_msg(&c->parm.cmsg));
#ifdef CONFIG_ISDN_TTY_FAX
case ISDN_STAT_FAXIND:
isdn_tty_stat_callback(i, c);
break;
#endif
#ifdef CONFIG_ISDN_AUDIO
case ISDN_STAT_AUDIO:
isdn_tty_stat_callback(i, c);
break;
#endif
#ifdef CONFIG_ISDN_DIVERSION
case ISDN_STAT_PROT:
case ISDN_STAT_REDIR:
if (divert_if)
return(divert_if->stat_callback(c));
#endif /* CONFIG_ISDN_DIVERSION */
default:
return -1;
}
return 0;
}
/*
* Get integer from char-pointer, set pointer to end of number
*/
int
isdn_getnum(char **p)
{
int v = -1;
while (*p[0] >= '0' && *p[0] <= '9')
v = ((v < 0) ? 0 : (v * 10)) + (int) ((*p[0]++) - '0');
return v;
}
#define DLE 0x10
/*
* isdn_readbchan() tries to get data from the read-queue.
* It MUST be called with interrupts off.
*
* Be aware that this is not an atomic operation when sleep != 0, even though
* interrupts are turned off! Well, like that we are currently only called
* on behalf of a read system call on raw device files (which are documented
* to be dangerous and for for debugging purpose only). The inode semaphore
* takes care that this is not called for the same minor device number while
* we are sleeping, but access is not serialized against simultaneous read()
* from the corresponding ttyI device. Can other ugly events, like changes
* of the mapping (di,ch)<->minor, happen during the sleep? --he
*/
int
isdn_readbchan(int di, int channel, u_char * buf, u_char * fp, int len, wait_queue_head_t *sleep)
{
int count;
int count_pull;
int count_put;
int dflag;
struct sk_buff *skb;
u_char *cp;
if (!dev->drv[di])
return 0;
if (skb_queue_empty(&dev->drv[di]->rpqueue[channel])) {
if (sleep)
interruptible_sleep_on(sleep);
else
return 0;
}
if (len > dev->drv[di]->rcvcount[channel])
len = dev->drv[di]->rcvcount[channel];
cp = buf;
count = 0;
while (len) {
if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel])))
break;
#ifdef CONFIG_ISDN_AUDIO
if (ISDN_AUDIO_SKB_LOCK(skb))
break;
ISDN_AUDIO_SKB_LOCK(skb) = 1;
if ((ISDN_AUDIO_SKB_DLECOUNT(skb)) || (dev->drv[di]->DLEflag & (1 << channel))) {
char *p = skb->data;
unsigned long DLEmask = (1 << channel);
dflag = 0;
count_pull = count_put = 0;
while ((count_pull < skb->len) && (len > 0)) {
len--;
if (dev->drv[di]->DLEflag & DLEmask) {
*cp++ = DLE;
dev->drv[di]->DLEflag &= ~DLEmask;
} else {
*cp++ = *p;
if (*p == DLE) {
dev->drv[di]->DLEflag |= DLEmask;
(ISDN_AUDIO_SKB_DLECOUNT(skb))--;
}
p++;
count_pull++;
}
count_put++;
}
if (count_pull >= skb->len)
dflag = 1;
} else {
#endif
/* No DLE's in buff, so simply copy it */
dflag = 1;
if ((count_pull = skb->len) > len) {
count_pull = len;
dflag = 0;
}
count_put = count_pull;
memcpy(cp, skb->data, count_put);
cp += count_put;
len -= count_put;
#ifdef CONFIG_ISDN_AUDIO
}
#endif
count += count_put;
if (fp) {
memset(fp, 0, count_put);
fp += count_put;
}
if (dflag) {
/* We got all the data in this buff.
* Now we can dequeue it.
*/
if (fp)
*(fp - 1) = 0xff;
#ifdef CONFIG_ISDN_AUDIO
ISDN_AUDIO_SKB_LOCK(skb) = 0;
#endif
skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]);
dev_kfree_skb(skb);
} else {
/* Not yet emptied this buff, so it
* must stay in the queue, for further calls
* but we pull off the data we got until now.
*/
skb_pull(skb, count_pull);
#ifdef CONFIG_ISDN_AUDIO
ISDN_AUDIO_SKB_LOCK(skb) = 0;
#endif
}
dev->drv[di]->rcvcount[channel] -= count_put;
}
return count;
}
/*
* isdn_readbchan_tty() tries to get data from the read-queue.
* It MUST be called with interrupts off.
*
* Be aware that this is not an atomic operation when sleep != 0, even though
* interrupts are turned off! Well, like that we are currently only called
* on behalf of a read system call on raw device files (which are documented
* to be dangerous and for for debugging purpose only). The inode semaphore
* takes care that this is not called for the same minor device number while
* we are sleeping, but access is not serialized against simultaneous read()
* from the corresponding ttyI device. Can other ugly events, like changes
* of the mapping (di,ch)<->minor, happen during the sleep? --he
*/
int
isdn_readbchan_tty(int di, int channel, struct tty_struct *tty, int cisco_hack)
{
int count;
int count_pull;
int count_put;
int dflag;
struct sk_buff *skb;
char last = 0;
int len;
if (!dev->drv[di])
return 0;
if (skb_queue_empty(&dev->drv[di]->rpqueue[channel]))
return 0;
len = tty_buffer_request_room(tty, dev->drv[di]->rcvcount[channel]);
if(len == 0)
return len;
count = 0;
while (len) {
if (!(skb = skb_peek(&dev->drv[di]->rpqueue[channel])))
break;
#ifdef CONFIG_ISDN_AUDIO
if (ISDN_AUDIO_SKB_LOCK(skb))
break;
ISDN_AUDIO_SKB_LOCK(skb) = 1;
if ((ISDN_AUDIO_SKB_DLECOUNT(skb)) || (dev->drv[di]->DLEflag & (1 << channel))) {
char *p = skb->data;
unsigned long DLEmask = (1 << channel);
dflag = 0;
count_pull = count_put = 0;
while ((count_pull < skb->len) && (len > 0)) {
len--;
if (dev->drv[di]->DLEflag & DLEmask) {
last = DLE;
dev->drv[di]->DLEflag &= ~DLEmask;
} else {
last = *p;
if (last == DLE) {
dev->drv[di]->DLEflag |= DLEmask;
(ISDN_AUDIO_SKB_DLECOUNT(skb))--;
}
p++;
count_pull++;
}
count_put++;
}
if (count_pull >= skb->len)
dflag = 1;
} else {
#endif
/* No DLE's in buff, so simply copy it */
dflag = 1;
if ((count_pull = skb->len) > len) {
count_pull = len;
dflag = 0;
}
count_put = count_pull;
if(count_put > 1)
tty_insert_flip_string(tty, skb->data, count_put - 1);
last = skb->data[count_put] - 1;
len -= count_put;
#ifdef CONFIG_ISDN_AUDIO
}
#endif
count += count_put;
if (dflag) {
/* We got all the data in this buff.
* Now we can dequeue it.
*/
if(cisco_hack)
tty_insert_flip_char(tty, last, 0xFF);
else
tty_insert_flip_char(tty, last, TTY_NORMAL);
#ifdef CONFIG_ISDN_AUDIO
ISDN_AUDIO_SKB_LOCK(skb) = 0;
#endif
skb = skb_dequeue(&dev->drv[di]->rpqueue[channel]);
dev_kfree_skb(skb);
} else {
tty_insert_flip_char(tty, last, TTY_NORMAL);
/* Not yet emptied this buff, so it
* must stay in the queue, for further calls
* but we pull off the data we got until now.
*/
skb_pull(skb, count_pull);
#ifdef CONFIG_ISDN_AUDIO
ISDN_AUDIO_SKB_LOCK(skb) = 0;
#endif
}
dev->drv[di]->rcvcount[channel] -= count_put;
}
return count;
}
static __inline int
isdn_minor2drv(int minor)
{
return (dev->drvmap[minor]);
}
static __inline int
isdn_minor2chan(int minor)
{
return (dev->chanmap[minor]);
}
static char *
isdn_statstr(void)
{
static char istatbuf[2048];
char *p;
int i;
sprintf(istatbuf, "idmap:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
sprintf(p, "%s ", (dev->drvmap[i] < 0) ? "-" : dev->drvid[dev->drvmap[i]]);
p = istatbuf + strlen(istatbuf);
}
sprintf(p, "\nchmap:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
sprintf(p, "%d ", dev->chanmap[i]);
p = istatbuf + strlen(istatbuf);
}
sprintf(p, "\ndrmap:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
sprintf(p, "%d ", dev->drvmap[i]);
p = istatbuf + strlen(istatbuf);
}
sprintf(p, "\nusage:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
sprintf(p, "%d ", dev->usage[i]);
p = istatbuf + strlen(istatbuf);
}
sprintf(p, "\nflags:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_DRIVERS; i++) {
if (dev->drv[i]) {
sprintf(p, "%ld ", dev->drv[i]->online);
p = istatbuf + strlen(istatbuf);
} else {
sprintf(p, "? ");
p = istatbuf + strlen(istatbuf);
}
}
sprintf(p, "\nphone:\t");
p = istatbuf + strlen(istatbuf);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
sprintf(p, "%s ", dev->num[i]);
p = istatbuf + strlen(istatbuf);
}
sprintf(p, "\n");
return istatbuf;
}
/* Module interface-code */
void
isdn_info_update(void)
{
infostruct *p = dev->infochain;
while (p) {
*(p->private) = 1;
p = (infostruct *) p->next;
}
wake_up_interruptible(&(dev->info_waitq));
}
static ssize_t
isdn_read(struct file *file, char __user *buf, size_t count, loff_t * off)
{
uint minor = MINOR(file->f_dentry->d_inode->i_rdev);
int len = 0;
int drvidx;
int chidx;
int retval;
char *p;
lock_kernel();
if (minor == ISDN_MINOR_STATUS) {
if (!file->private_data) {
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto out;
}
interruptible_sleep_on(&(dev->info_waitq));
}
p = isdn_statstr();
file->private_data = NULL;
if ((len = strlen(p)) <= count) {
if (copy_to_user(buf, p, len)) {
retval = -EFAULT;
goto out;
}
*off += len;
retval = len;
goto out;
}
retval = 0;
goto out;
}
if (!dev->drivers) {
retval = -ENODEV;
goto out;
}
if (minor <= ISDN_MINOR_BMAX) {
printk(KERN_WARNING "isdn_read minor %d obsolete!\n", minor);
drvidx = isdn_minor2drv(minor);
if (drvidx < 0) {
retval = -ENODEV;
goto out;
}
if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) {
retval = -ENODEV;
goto out;
}
chidx = isdn_minor2chan(minor);
if (!(p = kmalloc(count, GFP_KERNEL))) {
retval = -ENOMEM;
goto out;
}
len = isdn_readbchan(drvidx, chidx, p, NULL, count,
&dev->drv[drvidx]->rcv_waitq[chidx]);
*off += len;
if (copy_to_user(buf,p,len))
len = -EFAULT;
kfree(p);
retval = len;
goto out;
}
if (minor <= ISDN_MINOR_CTRLMAX) {
drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL);
if (drvidx < 0) {
retval = -ENODEV;
goto out;
}
if (!dev->drv[drvidx]->stavail) {
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
goto out;
}
interruptible_sleep_on(&(dev->drv[drvidx]->st_waitq));
}
if (dev->drv[drvidx]->interface->readstat) {
if (count > dev->drv[drvidx]->stavail)
count = dev->drv[drvidx]->stavail;
len = dev->drv[drvidx]->interface->
readstat(buf, count, drvidx,
isdn_minor2chan(minor));
} else {
len = 0;
}
if (len)
dev->drv[drvidx]->stavail -= len;
else
dev->drv[drvidx]->stavail = 0;
*off += len;
retval = len;
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX) {
retval = isdn_ppp_read(minor - ISDN_MINOR_PPP, file, buf, count);
goto out;
}
#endif
retval = -ENODEV;
out:
unlock_kernel();
return retval;
}
static ssize_t
isdn_write(struct file *file, const char __user *buf, size_t count, loff_t * off)
{
uint minor = MINOR(file->f_dentry->d_inode->i_rdev);
int drvidx;
int chidx;
int retval;
if (minor == ISDN_MINOR_STATUS)
return -EPERM;
if (!dev->drivers)
return -ENODEV;
lock_kernel();
if (minor <= ISDN_MINOR_BMAX) {
printk(KERN_WARNING "isdn_write minor %d obsolete!\n", minor);
drvidx = isdn_minor2drv(minor);
if (drvidx < 0) {
retval = -ENODEV;
goto out;
}
if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING)) {
retval = -ENODEV;
goto out;
}
chidx = isdn_minor2chan(minor);
while (isdn_writebuf_stub(drvidx, chidx, buf, count) != count)
interruptible_sleep_on(&dev->drv[drvidx]->snd_waitq[chidx]);
retval = count;
goto out;
}
if (minor <= ISDN_MINOR_CTRLMAX) {
drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL);
if (drvidx < 0) {
retval = -ENODEV;
goto out;
}
/*
* We want to use the isdnctrl device to load the firmware
*
if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING))
return -ENODEV;
*/
if (dev->drv[drvidx]->interface->writecmd)
retval = dev->drv[drvidx]->interface->
writecmd(buf, count, drvidx, isdn_minor2chan(minor));
else
retval = count;
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX) {
retval = isdn_ppp_write(minor - ISDN_MINOR_PPP, file, buf, count);
goto out;
}
#endif
retval = -ENODEV;
out:
unlock_kernel();
return retval;
}
static unsigned int
isdn_poll(struct file *file, poll_table * wait)
{
unsigned int mask = 0;
unsigned int minor = MINOR(file->f_dentry->d_inode->i_rdev);
int drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL);
lock_kernel();
if (minor == ISDN_MINOR_STATUS) {
poll_wait(file, &(dev->info_waitq), wait);
/* mask = POLLOUT | POLLWRNORM; */
if (file->private_data) {
mask |= POLLIN | POLLRDNORM;
}
goto out;
}
if (minor >= ISDN_MINOR_CTRL && minor <= ISDN_MINOR_CTRLMAX) {
if (drvidx < 0) {
/* driver deregistered while file open */
mask = POLLHUP;
goto out;
}
poll_wait(file, &(dev->drv[drvidx]->st_waitq), wait);
mask = POLLOUT | POLLWRNORM;
if (dev->drv[drvidx]->stavail) {
mask |= POLLIN | POLLRDNORM;
}
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX) {
mask = isdn_ppp_poll(file, wait);
goto out;
}
#endif
mask = POLLERR;
out:
unlock_kernel();
return mask;
}
static int
isdn_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg)
{
uint minor = MINOR(inode->i_rdev);
isdn_ctrl c;
int drvidx;
int chidx;
int ret;
int i;
char __user *p;
char *s;
union iocpar {
char name[10];
char bname[22];
isdn_ioctl_struct iocts;
isdn_net_ioctl_phone phone;
isdn_net_ioctl_cfg cfg;
} iocpar;
void __user *argp = (void __user *)arg;
#define name iocpar.name
#define bname iocpar.bname
#define iocts iocpar.iocts
#define phone iocpar.phone
#define cfg iocpar.cfg
if (minor == ISDN_MINOR_STATUS) {
switch (cmd) {
case IIOCGETDVR:
return (TTY_DV +
(NET_DV << 8) +
(INF_DV << 16));
case IIOCGETCPS:
if (arg) {
ulong __user *p = argp;
int i;
if (!access_ok(VERIFY_WRITE, p,
sizeof(ulong) * ISDN_MAX_CHANNELS * 2))
return -EFAULT;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
put_user(dev->ibytes[i], p++);
put_user(dev->obytes[i], p++);
}
return 0;
} else
return -EINVAL;
break;
#ifdef CONFIG_NETDEVICES
case IIOCNETGPN:
/* Get peer phone number of a connected
* isdn network interface */
if (arg) {
if (copy_from_user(&phone, argp, sizeof(phone)))
return -EFAULT;
return isdn_net_getpeer(&phone, argp);
} else
return -EINVAL;
#endif
default:
return -EINVAL;
}
}
if (!dev->drivers)
return -ENODEV;
if (minor <= ISDN_MINOR_BMAX) {
drvidx = isdn_minor2drv(minor);
if (drvidx < 0)
return -ENODEV;
chidx = isdn_minor2chan(minor);
if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING))
return -ENODEV;
return 0;
}
if (minor <= ISDN_MINOR_CTRLMAX) {
/*
* isdn net devices manage lots of configuration variables as linked lists.
* Those lists must only be manipulated from user space. Some of the ioctl's
* service routines access user space and are not atomic. Therefor, ioctl's
* manipulating the lists and ioctl's sleeping while accessing the lists
* are serialized by means of a semaphore.
*/
switch (cmd) {
case IIOCNETDWRSET:
printk(KERN_INFO "INFO: ISDN_DW_ABC_EXTENSION not enabled\n");
return(-EINVAL);
case IIOCNETLCR:
printk(KERN_INFO "INFO: ISDN_ABC_LCR_SUPPORT not enabled\n");
return -ENODEV;
#ifdef CONFIG_NETDEVICES
case IIOCNETAIF:
/* Add a network-interface */
if (arg) {
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
s = name;
} else {
s = NULL;
}
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
if ((s = isdn_net_new(s, NULL))) {
if (copy_to_user(argp, s, strlen(s) + 1)){
ret = -EFAULT;
} else {
ret = 0;
}
} else
ret = -ENODEV;
up(&dev->sem);
return ret;
case IIOCNETASL:
/* Add a slave to a network-interface */
if (arg) {
if (copy_from_user(bname, argp, sizeof(bname) - 1))
return -EFAULT;
} else
return -EINVAL;
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
if ((s = isdn_net_newslave(bname))) {
if (copy_to_user(argp, s, strlen(s) + 1)){
ret = -EFAULT;
} else {
ret = 0;
}
} else
ret = -ENODEV;
up(&dev->sem);
return ret;
case IIOCNETDIF:
/* Delete a network-interface */
if (arg) {
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
ret = isdn_net_rm(name);
up(&dev->sem);
return ret;
} else
return -EINVAL;
case IIOCNETSCF:
/* Set configurable parameters of a network-interface */
if (arg) {
if (copy_from_user(&cfg, argp, sizeof(cfg)))
return -EFAULT;
return isdn_net_setcfg(&cfg);
} else
return -EINVAL;
case IIOCNETGCF:
/* Get configurable parameters of a network-interface */
if (arg) {
if (copy_from_user(&cfg, argp, sizeof(cfg)))
return -EFAULT;
if (!(ret = isdn_net_getcfg(&cfg))) {
if (copy_to_user(argp, &cfg, sizeof(cfg)))
return -EFAULT;
}
return ret;
} else
return -EINVAL;
case IIOCNETANM:
/* Add a phone-number to a network-interface */
if (arg) {
if (copy_from_user(&phone, argp, sizeof(phone)))
return -EFAULT;
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
ret = isdn_net_addphone(&phone);
up(&dev->sem);
return ret;
} else
return -EINVAL;
case IIOCNETGNM:
/* Get list of phone-numbers of a network-interface */
if (arg) {
if (copy_from_user(&phone, argp, sizeof(phone)))
return -EFAULT;
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
ret = isdn_net_getphones(&phone, argp);
up(&dev->sem);
return ret;
} else
return -EINVAL;
case IIOCNETDNM:
/* Delete a phone-number of a network-interface */
if (arg) {
if (copy_from_user(&phone, argp, sizeof(phone)))
return -EFAULT;
ret = down_interruptible(&dev->sem);
if( ret ) return ret;
ret = isdn_net_delphone(&phone);
up(&dev->sem);
return ret;
} else
return -EINVAL;
case IIOCNETDIL:
/* Force dialing of a network-interface */
if (arg) {
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
return isdn_net_force_dial(name);
} else
return -EINVAL;
#ifdef CONFIG_ISDN_PPP
case IIOCNETALN:
if (!arg)
return -EINVAL;
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
return isdn_ppp_dial_slave(name);
case IIOCNETDLN:
if (!arg)
return -EINVAL;
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
return isdn_ppp_hangup_slave(name);
#endif
case IIOCNETHUP:
/* Force hangup of a network-interface */
if (!arg)
return -EINVAL;
if (copy_from_user(name, argp, sizeof(name)))
return -EFAULT;
return isdn_net_force_hangup(name);
break;
#endif /* CONFIG_NETDEVICES */
case IIOCSETVER:
dev->net_verbose = arg;
printk(KERN_INFO "isdn: Verbose-Level is %d\n", dev->net_verbose);
return 0;
case IIOCSETGST:
if (arg)
dev->global_flags |= ISDN_GLOBAL_STOPPED;
else
dev->global_flags &= ~ISDN_GLOBAL_STOPPED;
printk(KERN_INFO "isdn: Global Mode %s\n",
(dev->global_flags & ISDN_GLOBAL_STOPPED) ? "stopped" : "running");
return 0;
case IIOCSETBRJ:
drvidx = -1;
if (arg) {
int i;
char *p;
if (copy_from_user(&iocts, argp,
sizeof(isdn_ioctl_struct)))
return -EFAULT;
if (strlen(iocts.drvid)) {
if ((p = strchr(iocts.drvid, ',')))
*p = 0;
drvidx = -1;
for (i = 0; i < ISDN_MAX_DRIVERS; i++)
if (!(strcmp(dev->drvid[i], iocts.drvid))) {
drvidx = i;
break;
}
}
}
if (drvidx == -1)
return -ENODEV;
if (iocts.arg)
dev->drv[drvidx]->flags |= DRV_FLAG_REJBUS;
else
dev->drv[drvidx]->flags &= ~DRV_FLAG_REJBUS;
return 0;
case IIOCSIGPRF:
dev->profd = current;
return 0;
break;
case IIOCGETPRF:
/* Get all Modem-Profiles */
if (arg) {
char __user *p = argp;
int i;
if (!access_ok(VERIFY_WRITE, argp,
(ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN)
* ISDN_MAX_CHANNELS))
return -EFAULT;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
if (copy_to_user(p, dev->mdm.info[i].emu.profile,
ISDN_MODEM_NUMREG))
return -EFAULT;
p += ISDN_MODEM_NUMREG;
if (copy_to_user(p, dev->mdm.info[i].emu.pmsn, ISDN_MSNLEN))
return -EFAULT;
p += ISDN_MSNLEN;
if (copy_to_user(p, dev->mdm.info[i].emu.plmsn, ISDN_LMSNLEN))
return -EFAULT;
p += ISDN_LMSNLEN;
}
return (ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN) * ISDN_MAX_CHANNELS;
} else
return -EINVAL;
break;
case IIOCSETPRF:
/* Set all Modem-Profiles */
if (arg) {
char __user *p = argp;
int i;
if (!access_ok(VERIFY_READ, argp,
(ISDN_MODEM_NUMREG + ISDN_MSNLEN + ISDN_LMSNLEN)
* ISDN_MAX_CHANNELS))
return -EFAULT;
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
if (copy_from_user(dev->mdm.info[i].emu.profile, p,
ISDN_MODEM_NUMREG))
return -EFAULT;
p += ISDN_MODEM_NUMREG;
if (copy_from_user(dev->mdm.info[i].emu.plmsn, p, ISDN_LMSNLEN))
return -EFAULT;
p += ISDN_LMSNLEN;
if (copy_from_user(dev->mdm.info[i].emu.pmsn, p, ISDN_MSNLEN))
return -EFAULT;
p += ISDN_MSNLEN;
}
return 0;
} else
return -EINVAL;
break;
case IIOCSETMAP:
case IIOCGETMAP:
/* Set/Get MSN->EAZ-Mapping for a driver */
if (arg) {
if (copy_from_user(&iocts, argp,
sizeof(isdn_ioctl_struct)))
return -EFAULT;
if (strlen(iocts.drvid)) {
drvidx = -1;
for (i = 0; i < ISDN_MAX_DRIVERS; i++)
if (!(strcmp(dev->drvid[i], iocts.drvid))) {
drvidx = i;
break;
}
} else
drvidx = 0;
if (drvidx == -1)
return -ENODEV;
if (cmd == IIOCSETMAP) {
int loop = 1;
p = (char __user *) iocts.arg;
i = 0;
while (loop) {
int j = 0;
while (1) {
if (!access_ok(VERIFY_READ, p, 1))
return -EFAULT;
get_user(bname[j], p++);
switch (bname[j]) {
case '\0':
loop = 0;
/* Fall through */
case ',':
bname[j] = '\0';
strcpy(dev->drv[drvidx]->msn2eaz[i], bname);
j = ISDN_MSNLEN;
break;
default:
j++;
}
if (j >= ISDN_MSNLEN)
break;
}
if (++i > 9)
break;
}
} else {
p = (char __user *) iocts.arg;
for (i = 0; i < 10; i++) {
sprintf(bname, "%s%s",
strlen(dev->drv[drvidx]->msn2eaz[i]) ?
dev->drv[drvidx]->msn2eaz[i] : "_",
(i < 9) ? "," : "\0");
if (copy_to_user(p, bname, strlen(bname) + 1))
return -EFAULT;
p += strlen(bname);
}
}
return 0;
} else
return -EINVAL;
case IIOCDBGVAR:
if (arg) {
if (copy_to_user(argp, &dev, sizeof(ulong)))
return -EFAULT;
return 0;
} else
return -EINVAL;
break;
default:
if ((cmd & IIOCDRVCTL) == IIOCDRVCTL)
cmd = ((cmd >> _IOC_NRSHIFT) & _IOC_NRMASK) & ISDN_DRVIOCTL_MASK;
else
return -EINVAL;
if (arg) {
int i;
char *p;
if (copy_from_user(&iocts, argp, sizeof(isdn_ioctl_struct)))
return -EFAULT;
if (strlen(iocts.drvid)) {
if ((p = strchr(iocts.drvid, ',')))
*p = 0;
drvidx = -1;
for (i = 0; i < ISDN_MAX_DRIVERS; i++)
if (!(strcmp(dev->drvid[i], iocts.drvid))) {
drvidx = i;
break;
}
} else
drvidx = 0;
if (drvidx == -1)
return -ENODEV;
if (!access_ok(VERIFY_WRITE, argp,
sizeof(isdn_ioctl_struct)))
return -EFAULT;
c.driver = drvidx;
c.command = ISDN_CMD_IOCTL;
c.arg = cmd;
memcpy(c.parm.num, &iocts.arg, sizeof(ulong));
ret = isdn_command(&c);
memcpy(&iocts.arg, c.parm.num, sizeof(ulong));
if (copy_to_user(argp, &iocts, sizeof(isdn_ioctl_struct)))
return -EFAULT;
return ret;
} else
return -EINVAL;
}
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX)
return (isdn_ppp_ioctl(minor - ISDN_MINOR_PPP, file, cmd, arg));
#endif
return -ENODEV;
#undef name
#undef bname
#undef iocts
#undef phone
#undef cfg
}
/*
* Open the device code.
*/
static int
isdn_open(struct inode *ino, struct file *filep)
{
uint minor = MINOR(ino->i_rdev);
int drvidx;
int chidx;
int retval = -ENODEV;
if (minor == ISDN_MINOR_STATUS) {
infostruct *p;
if ((p = kmalloc(sizeof(infostruct), GFP_KERNEL))) {
p->next = (char *) dev->infochain;
p->private = (char *) &(filep->private_data);
dev->infochain = p;
/* At opening we allow a single update */
filep->private_data = (char *) 1;
retval = 0;
goto out;
} else {
retval = -ENOMEM;
goto out;
}
}
if (!dev->channels)
goto out;
if (minor <= ISDN_MINOR_BMAX) {
printk(KERN_WARNING "isdn_open minor %d obsolete!\n", minor);
drvidx = isdn_minor2drv(minor);
if (drvidx < 0)
goto out;
chidx = isdn_minor2chan(minor);
if (!(dev->drv[drvidx]->flags & DRV_FLAG_RUNNING))
goto out;
if (!(dev->drv[drvidx]->online & (1 << chidx)))
goto out;
isdn_lock_drivers();
retval = 0;
goto out;
}
if (minor <= ISDN_MINOR_CTRLMAX) {
drvidx = isdn_minor2drv(minor - ISDN_MINOR_CTRL);
if (drvidx < 0)
goto out;
isdn_lock_drivers();
retval = 0;
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX) {
retval = isdn_ppp_open(minor - ISDN_MINOR_PPP, filep);
if (retval == 0)
isdn_lock_drivers();
goto out;
}
#endif
out:
nonseekable_open(ino, filep);
return retval;
}
static int
isdn_close(struct inode *ino, struct file *filep)
{
uint minor = MINOR(ino->i_rdev);
lock_kernel();
if (minor == ISDN_MINOR_STATUS) {
infostruct *p = dev->infochain;
infostruct *q = NULL;
while (p) {
if (p->private == (char *) &(filep->private_data)) {
if (q)
q->next = p->next;
else
dev->infochain = (infostruct *) (p->next);
kfree(p);
goto out;
}
q = p;
p = (infostruct *) (p->next);
}
printk(KERN_WARNING "isdn: No private data while closing isdnctrl\n");
goto out;
}
isdn_unlock_drivers();
if (minor <= ISDN_MINOR_BMAX)
goto out;
if (minor <= ISDN_MINOR_CTRLMAX) {
if (dev->profd == current)
dev->profd = NULL;
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX)
isdn_ppp_release(minor - ISDN_MINOR_PPP, filep);
#endif
out:
unlock_kernel();
return 0;
}
static struct file_operations isdn_fops =
{
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = isdn_read,
.write = isdn_write,
.poll = isdn_poll,
.ioctl = isdn_ioctl,
.open = isdn_open,
.release = isdn_close,
};
char *
isdn_map_eaz2msn(char *msn, int di)
{
isdn_driver_t *this = dev->drv[di];
int i;
if (strlen(msn) == 1) {
i = msn[0] - '0';
if ((i >= 0) && (i <= 9))
if (strlen(this->msn2eaz[i]))
return (this->msn2eaz[i]);
}
return (msn);
}
/*
* Find an unused ISDN-channel, whose feature-flags match the
* given L2- and L3-protocols.
*/
#define L2V (~(ISDN_FEATURE_L2_V11096|ISDN_FEATURE_L2_V11019|ISDN_FEATURE_L2_V11038))
/*
* This function must be called with holding the dev->lock.
*/
int
isdn_get_free_channel(int usage, int l2_proto, int l3_proto, int pre_dev
,int pre_chan, char *msn)
{
int i;
ulong features;
ulong vfeatures;
features = ((1 << l2_proto) | (0x10000 << l3_proto));
vfeatures = (((1 << l2_proto) | (0x10000 << l3_proto)) &
~(ISDN_FEATURE_L2_V11096|ISDN_FEATURE_L2_V11019|ISDN_FEATURE_L2_V11038));
/* If Layer-2 protocol is V.110, accept drivers with
* transparent feature even if these don't support V.110
* because we can emulate this in linklevel.
*/
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (USG_NONE(dev->usage[i]) &&
(dev->drvmap[i] != -1)) {
int d = dev->drvmap[i];
if ((dev->usage[i] & ISDN_USAGE_EXCLUSIVE) &&
((pre_dev != d) || (pre_chan != dev->chanmap[i])))
continue;
if (!strcmp(isdn_map_eaz2msn(msn, d), "-"))
continue;
if (dev->usage[i] & ISDN_USAGE_DISABLED)
continue; /* usage not allowed */
if (dev->drv[d]->flags & DRV_FLAG_RUNNING) {
if (((dev->drv[d]->interface->features & features) == features) ||
(((dev->drv[d]->interface->features & vfeatures) == vfeatures) &&
(dev->drv[d]->interface->features & ISDN_FEATURE_L2_TRANS))) {
if ((pre_dev < 0) || (pre_chan < 0)) {
dev->usage[i] &= ISDN_USAGE_EXCLUSIVE;
dev->usage[i] |= usage;
isdn_info_update();
return i;
} else {
if ((pre_dev == d) && (pre_chan == dev->chanmap[i])) {
dev->usage[i] &= ISDN_USAGE_EXCLUSIVE;
dev->usage[i] |= usage;
isdn_info_update();
return i;
}
}
}
}
}
return -1;
}
/*
* Set state of ISDN-channel to 'unused'
*/
void
isdn_free_channel(int di, int ch, int usage)
{
int i;
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if (((!usage) || ((dev->usage[i] & ISDN_USAGE_MASK) == usage)) &&
(dev->drvmap[i] == di) &&
(dev->chanmap[i] == ch)) {
dev->usage[i] &= (ISDN_USAGE_NONE | ISDN_USAGE_EXCLUSIVE);
strcpy(dev->num[i], "???");
dev->ibytes[i] = 0;
dev->obytes[i] = 0;
// 20.10.99 JIM, try to reinitialize v110 !
dev->v110emu[i] = 0;
atomic_set(&(dev->v110use[i]), 0);
isdn_v110_close(dev->v110[i]);
dev->v110[i] = NULL;
// 20.10.99 JIM, try to reinitialize v110 !
isdn_info_update();
skb_queue_purge(&dev->drv[di]->rpqueue[ch]);
}
}
/*
* Cancel Exclusive-Flag for ISDN-channel
*/
void
isdn_unexclusive_channel(int di, int ch)
{
int i;
for (i = 0; i < ISDN_MAX_CHANNELS; i++)
if ((dev->drvmap[i] == di) &&
(dev->chanmap[i] == ch)) {
dev->usage[i] &= ~ISDN_USAGE_EXCLUSIVE;
isdn_info_update();
return;
}
}
/*
* writebuf replacement for SKB_ABLE drivers
*/
static int
isdn_writebuf_stub(int drvidx, int chan, const u_char __user * buf, int len)
{
int ret;
int hl = dev->drv[drvidx]->interface->hl_hdrlen;
struct sk_buff *skb = alloc_skb(hl + len, GFP_ATOMIC);
if (!skb)
return 0;
skb_reserve(skb, hl);
copy_from_user(skb_put(skb, len), buf, len);
ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, 1, skb);
if (ret <= 0)
dev_kfree_skb(skb);
if (ret > 0)
dev->obytes[isdn_dc2minor(drvidx, chan)] += ret;
return ret;
}
/*
* Return: length of data on success, -ERRcode on failure.
*/
int
isdn_writebuf_skb_stub(int drvidx, int chan, int ack, struct sk_buff *skb)
{
int ret;
struct sk_buff *nskb = NULL;
int v110_ret = skb->len;
int idx = isdn_dc2minor(drvidx, chan);
if (dev->v110[idx]) {
atomic_inc(&dev->v110use[idx]);
nskb = isdn_v110_encode(dev->v110[idx], skb);
atomic_dec(&dev->v110use[idx]);
if (!nskb)
return 0;
v110_ret = *((int *)nskb->data);
skb_pull(nskb, sizeof(int));
if (!nskb->len) {
dev_kfree_skb(nskb);
return v110_ret;
}
/* V.110 must always be acknowledged */
ack = 1;
ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, nskb);
} else {
int hl = dev->drv[drvidx]->interface->hl_hdrlen;
if( skb_headroom(skb) < hl ){
/*
* This should only occur when new HL driver with
* increased hl_hdrlen was loaded after netdevice
* was created and connected to the new driver.
*
* The V.110 branch (re-allocates on its own) does
* not need this
*/
struct sk_buff * skb_tmp;
skb_tmp = skb_realloc_headroom(skb, hl);
printk(KERN_DEBUG "isdn_writebuf_skb_stub: reallocating headroom%s\n", skb_tmp ? "" : " failed");
if (!skb_tmp) return -ENOMEM; /* 0 better? */
ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb_tmp);
if( ret > 0 ){
dev_kfree_skb(skb);
} else {
dev_kfree_skb(skb_tmp);
}
} else {
ret = dev->drv[drvidx]->interface->writebuf_skb(drvidx, chan, ack, skb);
}
}
if (ret > 0) {
dev->obytes[idx] += ret;
if (dev->v110[idx]) {
atomic_inc(&dev->v110use[idx]);
dev->v110[idx]->skbuser++;
atomic_dec(&dev->v110use[idx]);
/* For V.110 return unencoded data length */
ret = v110_ret;
/* if the complete frame was send we free the skb;
if not upper function will requeue the skb */
if (ret == skb->len)
dev_kfree_skb(skb);
}
} else
if (dev->v110[idx])
dev_kfree_skb(nskb);
return ret;
}
static int
isdn_add_channels(isdn_driver_t *d, int drvidx, int n, int adding)
{
int j, k, m;
init_waitqueue_head(&d->st_waitq);
if (d->flags & DRV_FLAG_RUNNING)
return -1;
if (n < 1) return 0;
m = (adding) ? d->channels + n : n;
if (dev->channels + n > ISDN_MAX_CHANNELS) {
printk(KERN_WARNING "register_isdn: Max. %d channels supported\n",
ISDN_MAX_CHANNELS);
return -1;
}
if ((adding) && (d->rcverr))
kfree(d->rcverr);
if (!(d->rcverr = kmalloc(sizeof(int) * m, GFP_ATOMIC))) {
printk(KERN_WARNING "register_isdn: Could not alloc rcverr\n");
return -1;
}
memset((char *) d->rcverr, 0, sizeof(int) * m);
if ((adding) && (d->rcvcount))
kfree(d->rcvcount);
if (!(d->rcvcount = kmalloc(sizeof(int) * m, GFP_ATOMIC))) {
printk(KERN_WARNING "register_isdn: Could not alloc rcvcount\n");
if (!adding)
kfree(d->rcverr);
return -1;
}
memset((char *) d->rcvcount, 0, sizeof(int) * m);
if ((adding) && (d->rpqueue)) {
for (j = 0; j < d->channels; j++)
skb_queue_purge(&d->rpqueue[j]);
kfree(d->rpqueue);
}
if (!(d->rpqueue = kmalloc(sizeof(struct sk_buff_head) * m, GFP_ATOMIC))) {
printk(KERN_WARNING "register_isdn: Could not alloc rpqueue\n");
if (!adding) {
kfree(d->rcvcount);
kfree(d->rcverr);
}
return -1;
}
for (j = 0; j < m; j++) {
skb_queue_head_init(&d->rpqueue[j]);
}
if ((adding) && (d->rcv_waitq))
kfree(d->rcv_waitq);
d->rcv_waitq = kmalloc(sizeof(wait_queue_head_t) * 2 * m, GFP_ATOMIC);
if (!d->rcv_waitq) {
printk(KERN_WARNING "register_isdn: Could not alloc rcv_waitq\n");
if (!adding) {
kfree(d->rpqueue);
kfree(d->rcvcount);
kfree(d->rcverr);
}
return -1;
}
d->snd_waitq = d->rcv_waitq + m;
for (j = 0; j < m; j++) {
init_waitqueue_head(&d->rcv_waitq[j]);
init_waitqueue_head(&d->snd_waitq[j]);
}
dev->channels += n;
for (j = d->channels; j < m; j++)
for (k = 0; k < ISDN_MAX_CHANNELS; k++)
if (dev->chanmap[k] < 0) {
dev->chanmap[k] = j;
dev->drvmap[k] = drvidx;
break;
}
d->channels = m;
return 0;
}
/*
* Low-level-driver registration
*/
static void
set_global_features(void)
{
int drvidx;
dev->global_features = 0;
for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++) {
if (!dev->drv[drvidx])
continue;
if (dev->drv[drvidx]->interface)
dev->global_features |= dev->drv[drvidx]->interface->features;
}
}
#ifdef CONFIG_ISDN_DIVERSION
static char *map_drvname(int di)
{
if ((di < 0) || (di >= ISDN_MAX_DRIVERS))
return(NULL);
return(dev->drvid[di]); /* driver name */
} /* map_drvname */
static int map_namedrv(char *id)
{ int i;
for (i = 0; i < ISDN_MAX_DRIVERS; i++)
{ if (!strcmp(dev->drvid[i],id))
return(i);
}
return(-1);
} /* map_namedrv */
int DIVERT_REG_NAME(isdn_divert_if *i_div)
{
if (i_div->if_magic != DIVERT_IF_MAGIC)
return(DIVERT_VER_ERR);
switch (i_div->cmd)
{
case DIVERT_CMD_REL:
if (divert_if != i_div)
return(DIVERT_REL_ERR);
divert_if = NULL; /* free interface */
return(DIVERT_NO_ERR);
case DIVERT_CMD_REG:
if (divert_if)
return(DIVERT_REG_ERR);
i_div->ll_cmd = isdn_command; /* set command function */
i_div->drv_to_name = map_drvname;
i_div->name_to_drv = map_namedrv;
divert_if = i_div; /* remember interface */
return(DIVERT_NO_ERR);
default:
return(DIVERT_CMD_ERR);
}
} /* DIVERT_REG_NAME */
EXPORT_SYMBOL(DIVERT_REG_NAME);
#endif /* CONFIG_ISDN_DIVERSION */
EXPORT_SYMBOL(register_isdn);
#ifdef CONFIG_ISDN_PPP
EXPORT_SYMBOL(isdn_ppp_register_compressor);
EXPORT_SYMBOL(isdn_ppp_unregister_compressor);
#endif
int
register_isdn(isdn_if * i)
{
isdn_driver_t *d;
int j;
ulong flags;
int drvidx;
if (dev->drivers >= ISDN_MAX_DRIVERS) {
printk(KERN_WARNING "register_isdn: Max. %d drivers supported\n",
ISDN_MAX_DRIVERS);
return 0;
}
if (!i->writebuf_skb) {
printk(KERN_WARNING "register_isdn: No write routine given.\n");
return 0;
}
if (!(d = kmalloc(sizeof(isdn_driver_t), GFP_KERNEL))) {
printk(KERN_WARNING "register_isdn: Could not alloc driver-struct\n");
return 0;
}
memset((char *) d, 0, sizeof(isdn_driver_t));
d->maxbufsize = i->maxbufsize;
d->pktcount = 0;
d->stavail = 0;
d->flags = DRV_FLAG_LOADED;
d->online = 0;
d->interface = i;
d->channels = 0;
spin_lock_irqsave(&dev->lock, flags);
for (drvidx = 0; drvidx < ISDN_MAX_DRIVERS; drvidx++)
if (!dev->drv[drvidx])
break;
if (isdn_add_channels(d, drvidx, i->channels, 0)) {
spin_unlock_irqrestore(&dev->lock, flags);
kfree(d);
return 0;
}
i->channels = drvidx;
i->rcvcallb_skb = isdn_receive_skb_callback;
i->statcallb = isdn_status_callback;
if (!strlen(i->id))
sprintf(i->id, "line%d", drvidx);
for (j = 0; j < drvidx; j++)
if (!strcmp(i->id, dev->drvid[j]))
sprintf(i->id, "line%d", drvidx);
dev->drv[drvidx] = d;
strcpy(dev->drvid[drvidx], i->id);
isdn_info_update();
dev->drivers++;
set_global_features();
spin_unlock_irqrestore(&dev->lock, flags);
return 1;
}
/*
*****************************************************************************
* And now the modules code.
*****************************************************************************
*/
static char *
isdn_getrev(const char *revision)
{
char *rev;
char *p;
if ((p = strchr(revision, ':'))) {
rev = p + 2;
p = strchr(rev, '$');
*--p = 0;
} else
rev = "???";
return rev;
}
/*
* Allocate and initialize all data, register modem-devices
*/
static int __init isdn_init(void)
{
int i;
char tmprev[50];
if (!(dev = (isdn_dev *) vmalloc(sizeof(isdn_dev)))) {
printk(KERN_WARNING "isdn: Could not allocate device-struct.\n");
return -EIO;
}
memset((char *) dev, 0, sizeof(isdn_dev));
init_timer(&dev->timer);
dev->timer.function = isdn_timer_funct;
spin_lock_init(&dev->lock);
spin_lock_init(&dev->timerlock);
#ifdef MODULE
dev->owner = THIS_MODULE;
#endif
init_MUTEX(&dev->sem);
init_waitqueue_head(&dev->info_waitq);
for (i = 0; i < ISDN_MAX_CHANNELS; i++) {
dev->drvmap[i] = -1;
dev->chanmap[i] = -1;
dev->m_idx[i] = -1;
strcpy(dev->num[i], "???");
init_waitqueue_head(&dev->mdm.info[i].open_wait);
init_waitqueue_head(&dev->mdm.info[i].close_wait);
}
if (register_chrdev(ISDN_MAJOR, "isdn", &isdn_fops)) {
printk(KERN_WARNING "isdn: Could not register control devices\n");
vfree(dev);
return -EIO;
}
if ((isdn_tty_modem_init()) < 0) {
printk(KERN_WARNING "isdn: Could not register tty devices\n");
vfree(dev);
unregister_chrdev(ISDN_MAJOR, "isdn");
return -EIO;
}
#ifdef CONFIG_ISDN_PPP
if (isdn_ppp_init() < 0) {
printk(KERN_WARNING "isdn: Could not create PPP-device-structs\n");
isdn_tty_exit();
unregister_chrdev(ISDN_MAJOR, "isdn");
vfree(dev);
return -EIO;
}
#endif /* CONFIG_ISDN_PPP */
strcpy(tmprev, isdn_revision);
printk(KERN_NOTICE "ISDN subsystem Rev: %s/", isdn_getrev(tmprev));
strcpy(tmprev, isdn_tty_revision);
printk("%s/", isdn_getrev(tmprev));
strcpy(tmprev, isdn_net_revision);
printk("%s/", isdn_getrev(tmprev));
strcpy(tmprev, isdn_ppp_revision);
printk("%s/", isdn_getrev(tmprev));
strcpy(tmprev, isdn_audio_revision);
printk("%s/", isdn_getrev(tmprev));
strcpy(tmprev, isdn_v110_revision);
printk("%s", isdn_getrev(tmprev));
#ifdef MODULE
printk(" loaded\n");
#else
printk("\n");
#endif
isdn_info_update();
return 0;
}
/*
* Unload module
*/
static void __exit isdn_exit(void)
{
#ifdef CONFIG_ISDN_PPP
isdn_ppp_cleanup();
#endif
if (isdn_net_rmall() < 0) {
printk(KERN_WARNING "isdn: net-device busy, remove cancelled\n");
return;
}
isdn_tty_exit();
unregister_chrdev(ISDN_MAJOR, "isdn");
del_timer(&dev->timer);
/* call vfree with interrupts enabled, else it will hang */
vfree(dev);
printk(KERN_NOTICE "ISDN-subsystem unloaded\n");
}
module_init(isdn_init);
module_exit(isdn_exit);