linux/drivers/isdn/i4l/isdn_common.c
Kees Cook 5e22002aa8 isdn: Disable IIOCDBGVAR
It was possible to directly leak the kernel address where the isdn_dev
structure pointer was stored. This is a kernel ASLR bypass for anyone
with access to the ioctl. The code had been present since the beginning
of git history, though this shouldn't ever be needed for normal operation,
therefore remove it.

Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Cc: Karsten Keil <isdn@linux-pingi.de>
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-08-16 12:26:24 -07:00

2370 lines
56 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(struct timer_list *unused)
{
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 __poll_t
isdn_poll(struct file *file, poll_table *wait)
{
__poll_t 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 = EPOLLOUT | EPOLLWRNORM; */
if (file->private_data) {
mask |= EPOLLIN | EPOLLRDNORM;
}
goto out;
}
if (minor >= ISDN_MINOR_CTRL && minor <= ISDN_MINOR_CTRLMAX) {
if (drvidx < 0) {
/* driver deregistered while file open */
mask = EPOLLHUP;
goto out;
}
poll_wait(file, &(dev->drv[drvidx]->st_waitq), wait);
mask = EPOLLOUT | EPOLLWRNORM;
if (dev->drv[drvidx]->stavail) {
mask |= EPOLLIN | EPOLLRDNORM;
}
goto out;
}
#ifdef CONFIG_ISDN_PPP
if (minor <= ISDN_MINOR_PPPMAX) {
mask = isdn_ppp_poll(file, wait);
goto out;
}
#endif
mask = EPOLLERR;
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;
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;
bname[sizeof(bname)-1] = 0;
} 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;
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;
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) {
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:
return -EINVAL;
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;
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 = kcalloc(m, sizeof(int), GFP_ATOMIC))) {
printk(KERN_WARNING "register_isdn: Could not alloc rcverr\n");
return -1;
}
if ((adding) && (d->rcvcount))
kfree(d->rcvcount);
if (!(d->rcvcount = kcalloc(m, sizeof(int), 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);
}
d->rpqueue = kmalloc_array(m, sizeof(struct sk_buff_head), GFP_ATOMIC);
if (!d->rpqueue) {
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(array3_size(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;
}
timer_setup(&dev->timer, isdn_timer_funct, 0);
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);