/* $Id: isdn_common.c,v 1.1.2.3 2004/02/10 01:07:13 keil Exp $
*
* Linux ISDN subsystem, common used functions (linklevel).
*
* 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;
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 (!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(c