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