blob: 38cf7c98460760318de87793b614d1abf40a4e93 [file] [log] [blame]
/* $Id: i4lididrv.c,v 1.1.2.2 2002/10/02 14:38:37 armin Exp $
*
* ISDN interface module for Eicon active cards.
* I4L - IDI Interface
*
* Copyright 1998-2000 by Armin Schindler (mac@melware.de)
* Copyright 1999-2002 Cytronics & Melware (info@melware.de)
*
* Thanks to Deutsche Mailbox Saar-Lor-Lux GmbH
* for sponsoring and testing fax
* capabilities with Diva Server cards.
* (dor@deutschemailbox.de)
*
* This software may be used and distributed according to the terms
* of the GNU General Public License, incorporated herein by reference.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include "i4lididrv.h"
#include <linux/smp_lock.h>
#include "divasync.h"
#include "../avmb1/capicmd.h" /* this should be moved in a common place */
EXPORT_NO_SYMBOLS;
#define INCLUDE_INLINE_FUNCS
static eicon_card *cards = (eicon_card *) NULL; /* glob. var , contains
start of card-list */
static char *DRIVERNAME = "Eicon Diva - native I4L Interface driver (http://www.melware.net)";
static char *DRIVERLNAME = "diva2i4l";
static char *DRIVERRELEASE = "2.0";
static char *eicon_revision = "$Revision: 1.1.2.2 $";
extern char *eicon_idi_revision;
#define EICON_CTRL_VERSION 2
ulong DebugVar;
static spinlock_t status_lock;
static spinlock_t ll_lock;
#define MAX_DESCRIPTORS 32
extern void DIVA_DIDD_Read(DESCRIPTOR *, int);
static dword notify_handle;
static DESCRIPTOR DAdapter;
static DESCRIPTOR MAdapter;
/* Parameter to be set by insmod */
static char *id = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
static int debug = 1;
MODULE_DESCRIPTION( "ISDN4Linux Interface for Eicon active card driver");
MODULE_AUTHOR( "Armin Schindler");
MODULE_SUPPORTED_DEVICE( "ISDN subsystem and Eicon active card driver");
MODULE_PARM_DESC(id, "ID-String for ISDN4Linux");
MODULE_PARM(id, "s");
MODULE_PARM_DESC(debug, "Initial debug value");
MODULE_PARM(debug, "i");
MODULE_LICENSE("GPL");
void no_printf (unsigned char * x ,...)
{
/* dummy debug function */
}
DIVA_DI_PRINTF dprintf = no_printf;
#define DBG_MINIMUM (DL_LOG + DL_FTL + DL_ERR)
#define DBG_DEFAULT (DBG_MINIMUM + DL_XLOG + DL_REG)
#include "debuglib.c"
static char *
eicon_getrev(const char *revision)
{
char *rev;
char *p;
if ((p = strchr(revision, ':'))) {
rev = p + 2;
p = strchr(rev, '$');
*--p = 0;
} else rev = "1.0";
return rev;
}
static void
stop_dbg(void)
{
DbgDeregister();
memset(&MAdapter, 0, sizeof(MAdapter));
dprintf = no_printf;
}
static eicon_chan *
find_channel(eicon_card *card, int channel)
{
if ((channel >= 0) && (channel < card->nchannels))
return &(card->bch[channel]);
eicon_log(card, 1, "%s: Invalid channel %d\n", DRIVERLNAME, channel);
return NULL;
}
static void
eicon_rx_request(struct eicon_card *card)
{
struct sk_buff *skb, *skb2, *skb_new;
eicon_IND *ind, *ind2, *ind_new;
eicon_chan *chan;
if (!card) {
eicon_log(card, 1, "%s: NULL card in rcv_dispatch !\n", DRIVERLNAME);
return;
}
while((skb = skb_dequeue(&card->rcvq))) {
ind = (eicon_IND *)skb->data;
if ((chan = card->IdTable[ind->IndId]) == NULL) {
if (DebugVar & 1) {
switch(ind->Ind) {
case N_DISC_ACK:
/* doesn't matter if this happens */
break;
default:
eicon_log(card, 1, "idi: Indication for unknown channel Ind=%d Id=%x\n", ind->Ind, ind->IndId);
eicon_log(card, 1, "idi_hdl: Ch??: Ind=%d Id=%x Ch=%d MInd=%d MLen=%d Len=%d\n",
ind->Ind,ind->IndId,ind->IndCh,ind->MInd,ind->MLength,ind->RBuffer.length);
}
}
dev_kfree_skb(skb);
continue;
}
if (chan->e.complete) { /* check for rec-buffer chaining */
if (ind->MLength == ind->RBuffer.length) {
chan->e.complete = 1;
idi_handle_ind(card, skb);
continue;
}
else {
chan->e.complete = 0;
ind->Ind = ind->MInd;
skb_queue_tail(&chan->e.R, skb);
continue;
}
}
else {
if (!(skb2 = skb_dequeue(&chan->e.R))) {
chan->e.complete = 1;
eicon_log(card, 1, "%s: buffer incomplete, but 0 in queue\n", DRIVERLNAME);
dev_kfree_skb(skb);
continue;
}
ind2 = (eicon_IND *)skb2->data;
skb_new = alloc_skb(((sizeof(eicon_IND)-1)+ind->RBuffer.length+ind2->RBuffer.length),
GFP_ATOMIC);
if (!skb_new) {
eicon_log(card, 1, "%s: skb_alloc failed in rcv_dispatch()\n", DRIVERLNAME);
dev_kfree_skb(skb);
dev_kfree_skb(skb2);
continue;
}
ind_new = (eicon_IND *)skb_put(skb_new,
((sizeof(eicon_IND)-1)+ind->RBuffer.length+ind2->RBuffer.length));
ind_new->Ind = ind2->Ind;
ind_new->IndId = ind2->IndId;
ind_new->IndCh = ind2->IndCh;
ind_new->MInd = ind2->MInd;
ind_new->MLength = ind2->MLength;
ind_new->RBuffer.length = ind2->RBuffer.length + ind->RBuffer.length;
memcpy(&ind_new->RBuffer.P, &ind2->RBuffer.P, ind2->RBuffer.length);
memcpy((&ind_new->RBuffer.P)+ind2->RBuffer.length, &ind->RBuffer.P, ind->RBuffer.length);
dev_kfree_skb(skb);
dev_kfree_skb(skb2);
if (ind->MLength == ind->RBuffer.length) {
chan->e.complete = 2;
idi_handle_ind(card, skb_new);
continue;
}
else {
chan->e.complete = 0;
skb_queue_tail(&chan->e.R, skb_new);
continue;
}
}
}
}
static void
eicon_ack_request(struct eicon_card *card)
{
struct sk_buff *skb;
if (!card) {
eicon_log(card, 1, "%s: NULL card in ack_dispatch!\n", DRIVERLNAME);
return;
}
while((skb = skb_dequeue(&card->rackq))) {
idi_handle_ack(card, skb);
}
}
/*
* IDI-Callback function
*/
static void
eicon_idi_callback(ENTITY *de)
{
eicon_card *ccard = (eicon_card *)de->R;
struct sk_buff *skb;
eicon_RC *ack;
eicon_IND *ind;
int len = 0;
if (de->complete == 255) {
/* Return Code */
skb = alloc_skb(sizeof(eicon_RC), GFP_ATOMIC);
if (!skb) {
eicon_log(ccard, 1, "%s: skb_alloc failed in _idi_callback()\n", DRIVERLNAME);
} else {
ack = (eicon_RC *)skb_put(skb, sizeof(eicon_RC));
ack->Rc = de->Rc;
if (de->Rc == ASSIGN_OK) {
ack->RcId = de->Id;
de->user[1] = de->Id;
} else {
ack->RcId = de->user[1];
}
ack->RcCh = de->RcCh;
ack->Reference = de->user[0];
skb_queue_tail(&ccard->rackq, skb);
eicon_ack_request(ccard);
eicon_log(ccard, 128, "idi_cbk: Ch%d: Rc=%x Id=%x RLen=%x compl=%x\n",
de->user[0], de->Rc, ack->RcId, de->RLength, de->complete);
DBG_TRC(("idi_cbk: Ch%d: Rc=%x Id=%x RLen=%x compl=%x",
de->user[0], de->Rc, ack->RcId, de->RLength, de->complete))
de->Rc = 0;
}
} else {
/* Indication */
if (de->complete) {
len = de->RLength;
} else {
len = 270;
if (de->RLength <= 270)
eicon_log(ccard, 1, "idi_cbk: ind not complete but <= 270\n");
}
skb = alloc_skb((sizeof(eicon_IND) + len - 1), GFP_ATOMIC);
if (!skb) {
eicon_log(ccard, 1, "%s: skb_alloc failed in _idi_callback()\n", DRIVERLNAME);
} else {
ind = (eicon_IND *)skb_put(skb, (sizeof(eicon_IND) + len - 1));
ind->Ind = de->Ind;
ind->IndId = de->user[1];
ind->IndCh = de->IndCh;
ind->MInd = de->Ind;
ind->RBuffer.length = len;
ind->MLength = de->RLength;
memcpy(&ind->RBuffer.P, &de->RBuffer->P, len);
skb_queue_tail(&ccard->rcvq, skb);
eicon_rx_request(ccard);
eicon_log(ccard, 128, "idi_cbk: Ch%d: Ind=%x Id=%x RLen=%x compl=%x\n",
de->user[0], de->Ind, ind->IndId, de->RLength, de->complete);
DBG_TRC(("idi_cbk: Ch%d: Ind=%x Id=%x RLen=%x compl=%x",
de->user[0], de->Ind, ind->IndId, de->RLength, de->complete))
de->Ind = 0;
}
}
de->RNum = 0;
de->RNR = 0;
eicon_tx_request(ccard);
}
/*
** Kernel thread to prevent in_interrupt
*/
static DECLARE_TASK_QUEUE(tq_divad);
static struct semaphore diva_thread_sem;
static struct semaphore diva_thread_end;
static int divad_pid = -1;
static int divad_thread(void * data);
static void diva_tx(void *data);
static atomic_t thread_running;
static void __init
diva_init_thread(void)
{
int pid = 0;
pid = kernel_thread(divad_thread, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
if (pid >= 0) {
divad_pid = pid;
}
}
static int
divad_thread(void * data)
{
atomic_inc(&thread_running);
if (atomic_read(&thread_running) > 1) {
printk(KERN_WARNING"%s: thread already running\n", DRIVERLNAME);
return(0);
}
printk(KERN_INFO "%s: thread started with pid %d\n", DRIVERLNAME, current->pid);
exit_mm(current);
exit_files(current);
exit_fs(current);
/* Set to RealTime */
current->policy = SCHED_FIFO;
current->rt_priority = 33;
strcpy(current->comm, "kdiva2i4ld");
for(;;) {
down_interruptible(&diva_thread_sem);
if(!(atomic_read(&thread_running)))
break;
if(signal_pending(current)) {
/* we may want to do something on signals here */
spin_lock_irq(&current->sigmask_lock);
flush_signals(current);
spin_unlock_irq(&current->sigmask_lock);
} else {
run_task_queue(&tq_divad);
}
}
up(&diva_thread_end);
divad_pid = -1;
return 0;
}
static void
stop_diva_thread(void)
{
if (divad_pid >= 0) {
atomic_set(&thread_running, 0);
up(&diva_thread_sem);
down_interruptible(&diva_thread_end);
}
}
void
eicon_tx_request(struct eicon_card *card)
{
card->tq.routine = diva_tx;
card->tq.data = (void *)card;
queue_task(&card->tq, &tq_divad);
up(&diva_thread_sem);
}
static void
diva_tx(void *data)
{
struct eicon_card *card = (eicon_card *) data;
struct sk_buff *skb;
struct sk_buff *skb2;
eicon_chan *chan;
eicon_chan_ptr *chan2;
eicon_REQ *reqbuf = 0;
int ReqCount = 0;
int tmpid = 0;
int quloop = 1;
int dlev = 0;
ENTITY *ep = 0;
if (!card) {
eicon_log(card, 1, "%s: NULL card in transmit !\n", DRIVERLNAME);
return;
}
ReqCount = 0;
if (!(skb2 = skb_dequeue(&card->sndq)))
quloop = 0;
while(quloop) {
chan2 = (eicon_chan_ptr *)skb2->data;
chan = chan2->ptr;
if (!chan->e.busy) {
if((skb = skb_dequeue(&chan->e.X))) {
reqbuf = (eicon_REQ *)skb->data;
if ((reqbuf->Reference) && (chan->e.B2Id == 0) && (reqbuf->ReqId & 0x1f)) {
eicon_log(card, 16, "%s: transmit: error Id=0 on %d (Net)\n", DRIVERLNAME, chan->No);
} else {
dlev = 160;
if (reqbuf->ReqId & 0x1f) { /* if this is no ASSIGN */
if (!reqbuf->Reference) { /* Signal Layer */
ep = &chan->de;
tmpid = chan->e.D3Id;
chan->e.ReqCh = 0;
}
else { /* Net Layer */
ep = &chan->be;
tmpid = chan->e.B2Id;
chan->e.ReqCh = 1;
if (((reqbuf->Req & 0x0f) == 0x08) ||
((reqbuf->Req & 0x0f) == 0x01)) { /* Send Data */
chan->waitq = reqbuf->XBuffer.length;
chan->waitpq += reqbuf->XBuffer.length;
dlev = 128;
}
}
} else { /* It is an ASSIGN */
if (!reqbuf->Reference)
ep = &chan->de;
else
ep = &chan->be;
ep->Id = reqbuf->ReqId;
tmpid = reqbuf->ReqId;
if (!reqbuf->Reference)
chan->e.ReqCh = 0;
else
chan->e.ReqCh = 1;
}
chan->e.ref = chan->No;
chan->e.Req = reqbuf->Req;
ReqCount++;
if (ep) {
ep->callback = eicon_idi_callback;
ep->R = (BUFFERS *)card;
ep->user[0] = (word)chan->No;
ep->user[1] = (word)tmpid;
ep->XNum = 1;
ep->RNum = 0;
ep->RNR = 0;
ep->Rc = 0;
ep->Ind = 0;
ep->X->PLength = reqbuf->XBuffer.length;
memcpy(ep->X->P, &reqbuf->XBuffer.P, reqbuf->XBuffer.length);
ep->ReqCh = reqbuf->ReqCh;
ep->Req = reqbuf->Req;
}
chan->e.busy = 1;
eicon_log(card, dlev, "idi: Req=%d Id=%x Ch=%d Len=%d Ref=%d\n",
reqbuf->Req, tmpid,
reqbuf->ReqCh, reqbuf->XBuffer.length,
chan->e.ref);
if (ep) {
card->d.request(ep);
if (ep->Rc)
eicon_idi_callback(ep);
}
}
dev_kfree_skb(skb);
}
dev_kfree_skb(skb2);
}
else {
skb_queue_tail(&card->sackq, skb2);
eicon_log(card, 128, "%s: transmit: busy chan %d\n", DRIVERLNAME, chan->No);
}
if (!(skb2 = skb_dequeue(&card->sndq)))
quloop = 0;
}
while((skb = skb_dequeue(&card->sackq))) {
skb_queue_tail(&card->sndq, skb);
}
}
static int
eicon_command(eicon_card * card, isdn_ctrl * c)
{
ulong a;
eicon_chan *chan;
isdn_ctrl cmd;
int ret = 0;
eicon_log(card, 16, "%s_cmd 0x%x with arg 0x%lx (0x%lx)\n", DRIVERLNAME,
c->command, c->arg, (ulong) *c->parm.num);
switch (c->command) {
case ISDN_CMD_IOCTL:
memcpy(&a, c->parm.num, sizeof(ulong));
switch (c->arg) {
case EICON_IOCTL_GETVER:
return(EICON_CTRL_VERSION);
case EICON_IOCTL_MANIF:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!card->d.features & DI_MANAGE)
return -ENODEV;
ret = eicon_idi_manage(
card,
(eicon_manifbuf *)a);
return ret;
case EICON_IOCTL_GETXLOG:
return -ENODEV;
case EICON_IOCTL_DEBUGVAR:
DebugVar = a;
eicon_log(card, 1, "%s: Debug Value set to %ld\n", DRIVERLNAME, DebugVar);
return 0;
case EICON_IOCTL_LOADPCI:
eicon_log(card, 1, "%s: Wrong version of load-utility,\n", DRIVERLNAME);
eicon_log(card, 1, "%s: re-compile eiconctrl !\n", DRIVERLNAME);
eicon_log(card, 1, "%s: Maybe update of utility is necessary !\n", DRIVERLNAME);
return -EINVAL;
default:
return -EINVAL;
}
break;
case ISDN_CMD_DIAL:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
if ((chan->fsm_state != EICON_STATE_NULL) && (chan->fsm_state != EICON_STATE_LISTEN)) {
eicon_log(card, 1, "%s: Dial on channel %d with state %d\n", DRIVERLNAME,
chan->No, chan->fsm_state);
return -EBUSY;
}
chan->fsm_state = EICON_STATE_OCALL;
ret = idi_connect_req(card, chan, c->parm.setup.phone,
c->parm.setup.eazmsn,
c->parm.setup.si1,
c->parm.setup.si2);
if (ret) {
cmd.driver = card->myid;
cmd.command = ISDN_STAT_DHUP;
cmd.arg &= 0x1f;
card->interface.statcallb(&cmd);
}
return ret;
case ISDN_CMD_ACCEPTD:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
if (chan->fsm_state == EICON_STATE_ICALL) {
idi_connect_res(card, chan);
}
return 0;
case ISDN_CMD_ACCEPTB:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
return 0;
case ISDN_CMD_HANGUP:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
idi_hangup(card, chan);
return 0;
case ISDN_CMD_SETEAZ:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
chan->eazmask = 0x3ff;
eicon_idi_listen_req(card, chan);
return 0;
case ISDN_CMD_CLREAZ:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
chan->eazmask = 0;
eicon_idi_listen_req(card, chan);
return 0;
case ISDN_CMD_SETL2:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
chan->l2prot = (c->arg >> 8);
memcpy(chan->a_para, c->parm.aux.para, sizeof(chan->a_para));
return 0;
case ISDN_CMD_SETL3:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
chan->l3prot = (c->arg >> 8);
#ifdef CONFIG_ISDN_TTY_FAX
if (chan->l3prot == ISDN_PROTO_L3_FCLASS2) {
chan->fax = c->parm.fax;
eicon_log(card, 128, "idi_cmd: Ch%d: SETL3 struct fax=0x%x\n",chan->No, chan->fax);
}
#endif
return 0;
#ifdef CONFIG_ISDN_TTY_FAX
case ISDN_CMD_FAXCMD:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
if (!chan->fax)
break;
idi_fax_cmd(card, chan);
return 0;
#endif
case ISDN_CMD_AUDIO:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
idi_audio_cmd(card, chan, c->arg >> 8, c->parm.num);
return 0;
case CAPI_PUT_MESSAGE:
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, c->arg & 0x1f)))
break;
if (c->parm.cmsg.Length < 8)
break;
switch(c->parm.cmsg.Command) {
case CAPI_FACILITY:
if (c->parm.cmsg.Subcommand == CAPI_REQ)
return(capipmsg(card, chan, &c->parm.cmsg));
break;
case CAPI_MANUFACTURER:
default:
break;
}
return 0;
}
return -EINVAL;
}
static int
find_free_number(void)
{
int num = 0;
char cid[40];
eicon_card *p;
ulong flags;
spin_lock_irqsave(&ll_lock, flags);
while(num < 100) {
sprintf(cid, "%s%d", id, num);
num++;
p = cards;
while (p) {
if (!strcmp(p->regname, cid))
break;
p = p->next;
}
if (p)
{
spin_unlock_irqrestore(&ll_lock, flags);
return(num - 1);
}
}
spin_unlock_irqrestore(&ll_lock, flags);
return(999);
}
/*
* Find card with given driverId
*/
static inline eicon_card *
eicon_findcard(int driverid)
{
eicon_card *p;
ulong flags;
spin_lock_irqsave(&ll_lock, flags);
p = cards;
while (p) {
if (p->myid == driverid) {
spin_unlock_irqrestore(&ll_lock, flags);
return p;
}
p = p->next;
}
spin_unlock_irqrestore(&ll_lock, flags);
return (eicon_card *) 0;
}
/*
* Wrapper functions for interface to linklevel
*/
static int
if_command(isdn_ctrl * c)
{
eicon_card *card = eicon_findcard(c->driver);
if (card)
return (eicon_command(card, c));
printk(KERN_ERR
"%s: if_command %d called with invalid driverId %d!\n", DRIVERLNAME,
c->command, c->driver);
return -ENODEV;
}
static int
if_writecmd(const u_char * buf, int len, int user, int id, int channel)
{
/* Not used */
return (len);
}
static int
if_readstatus(u_char * buf, int len, int user, int id, int channel)
{
int count = 0;
int cnt = 0;
u_char *p = buf;
struct sk_buff *skb;
ulong flags;
eicon_card *card = eicon_findcard(id);
if (card) {
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
spin_lock_irqsave(&status_lock, flags);
while((skb = skb_dequeue(&card->statq))) {
if ((skb->len + count) > len)
cnt = len - count;
else
cnt = skb->len;
if (user)
copy_to_user(p, skb->data, cnt);
else
memcpy(p, skb->data, cnt);
count += cnt;
p += cnt;
if (cnt == skb->len) {
dev_kfree_skb(skb);
if (card->statq_entries > 0)
card->statq_entries--;
} else {
skb_pull(skb, cnt);
skb_queue_head(&card->statq, skb);
spin_unlock_irqrestore(&status_lock, flags);
return count;
}
}
card->statq_entries = 0;
spin_unlock_irqrestore(&status_lock, flags);
return count;
}
printk(KERN_ERR
"%s: if_readstatus called with invalid driverId!\n", DRIVERLNAME);
return 0;
}
static int
if_sendbuf(int id, int channel, int ack, struct sk_buff *skb)
{
eicon_card *card = eicon_findcard(id);
eicon_chan *chan;
int ret = 0;
int len;
len = skb->len;
if (card) {
if (!card->flags & EICON_FLAGS_RUNNING)
return -ENODEV;
if (!(chan = find_channel(card, channel)))
return -ENODEV;
if (chan->fsm_state == EICON_STATE_ACTIVE) {
#ifdef CONFIG_ISDN_TTY_FAX
if (chan->l2prot == ISDN_PROTO_L2_FAX) {
if ((ret = idi_faxdata_send(card, chan, skb)) > 0)
ret = len;
}
else
#endif
ret = idi_send_data(card, chan, ack, skb, 1, 1);
return (ret);
} else {
return -ENODEV;
}
}
printk(KERN_ERR
"%s: if_sendbuf called with invalid driverId!\n", DRIVERLNAME);
return -ENODEV;
}
/* jiftime() copied from HiSax */
static inline int jiftime(char *s, long mark)
{
s += 8;
*s-- = '\0';
*s-- = mark % 10 + '0';
mark /= 10;
*s-- = mark % 10 + '0';
mark /= 10;
*s-- = '.';
*s-- = mark % 10 + '0';
mark /= 10;
*s-- = mark % 6 + '0';
mark /= 6;
*s-- = ':';
*s-- = mark % 10 + '0';
mark /= 10;
*s-- = mark % 10 + '0';
return(8);
}
void
eicon_putstatus(eicon_card * card, char * buf)
{
int count;
isdn_ctrl cmd;
u_char *p;
struct sk_buff *skb;
ulong flags;
if (!card) {
if (!(card = cards))
return;
}
spin_lock_irqsave(&status_lock, flags);
count = strlen(buf);
skb = alloc_skb(count, GFP_ATOMIC);
if (!skb) {
spin_unlock_irqrestore(&status_lock, flags);
printk(KERN_ERR "%s: could not alloc skb in putstatus\n", DRIVERLNAME);
return;
}
p = skb_put(skb, count);
memcpy(p, buf, count);
skb_queue_tail(&card->statq, skb);
if (card->statq_entries >= MAX_STATUS_BUFFER) {
if ((skb = skb_dequeue(&card->statq))) {
count -= skb->len;
dev_kfree_skb(skb);
} else
count = 0;
} else
card->statq_entries++;
spin_unlock_irqrestore(&status_lock, flags);
if (count) {
cmd.command = ISDN_STAT_STAVAIL;
cmd.driver = card->myid;
cmd.arg = count;
card->interface.statcallb(&cmd);
}
}
/*
* Debug and Log
*/
void
eicon_log(eicon_card * card, int level, const char *fmt, ...)
{
va_list args;
char Line[160];
u_char *p;
if ((DebugVar & level) || (DebugVar & 256)) {
va_start(args, fmt);
if (DebugVar & level) {
if (DebugVar & 256) {
/* log-buffer */
p = Line;
p += jiftime(p, jiffies);
*p++ = 32;
p += vsprintf(p, fmt, args);
*p = 0;
eicon_putstatus(card, Line);
} else {
/* printk, syslogd */
vsprintf(Line, fmt, args);
printk(KERN_DEBUG "%s", Line);
}
}
va_end(args);
}
}
/*
* Allocate a new card-struct, initialize it
* link it into cards-list.
*/
static void
eicon_alloccard(DESCRIPTOR *d)
{
int j;
char cid[40];
eicon_card *card;
ulong flags;
sprintf(cid, "%s%d", id, find_free_number());
if (!(card = (eicon_card *) kmalloc(sizeof(eicon_card), GFP_KERNEL))) {
eicon_log(card, 1,
"%s: (%s) Could not allocate card-struct.\n", DRIVERLNAME, cid);
return;
}
memset((char *) card, 0, sizeof(eicon_card));
skb_queue_head_init(&card->sndq);
skb_queue_head_init(&card->rcvq);
skb_queue_head_init(&card->rackq);
skb_queue_head_init(&card->sackq);
skb_queue_head_init(&card->statq);
card->statq_entries = 0;
SET_MODULE_OWNER(&card->interface);
card->interface.maxbufsize = 4000;
card->interface.command = if_command;
card->interface.writebuf_skb = if_sendbuf;
card->interface.writecmd = if_writecmd;
card->interface.readstat = if_readstatus;
card->interface.features =
ISDN_FEATURE_L2_X75I |
ISDN_FEATURE_L2_HDLC |
ISDN_FEATURE_L2_TRANS |
ISDN_FEATURE_L3_TRANS |
ISDN_FEATURE_L3_TRANSDSP |
ISDN_FEATURE_P_UNKNOWN;
card->interface.hl_hdrlen = 20;
card->ptype = ISDN_PTYPE_UNKNOWN;
strcpy(card->interface.id, cid);
card->myid = -1;
card->type = d->type;
if (d->features & (DI_FAX3 | DI_EXTD_FAX))
card->interface.features |= (ISDN_FEATURE_L2_FAX | ISDN_FEATURE_L3_FCLASS2);
if (d->features & DI_MODEM)
card->interface.features |= ISDN_FEATURE_L2_MODEM;
if (d->features & DI_V110)
card->interface.features |= (ISDN_FEATURE_L2_V11096|ISDN_FEATURE_L2_V11019|ISDN_FEATURE_L2_V11038);
card->flags = 0;
card->nchannels = d->channels;
card->interface.channels = d->channels;
if (!(card->bch = (eicon_chan *) vmalloc(sizeof(eicon_chan) * (card->nchannels + 1)))) {
eicon_log(card, 1,
"%s: (%s) Could not allocate bch-struct.\n", DRIVERLNAME, cid);
kfree(card);
return;
}
for (j=0; j< (card->nchannels + 1); j++) {
memset((char *)&card->bch[j], 0, sizeof(eicon_chan));
card->bch[j].statectrl = 0;
card->bch[j].l2prot = ISDN_PROTO_L2_X75I;
card->bch[j].l3prot = ISDN_PROTO_L3_TRANS;
card->bch[j].e.D3Id = 0;
card->bch[j].e.B2Id = 0;
card->bch[j].e.Req = 0;
card->bch[j].No = j;
card->bch[j].tskb1 = NULL;
card->bch[j].tskb2 = NULL;
skb_queue_head_init(&card->bch[j].e.X);
skb_queue_head_init(&card->bch[j].e.R);
}
if (!(card->dbuf = (DBUFFER *) kmalloc((sizeof(DBUFFER) * (card->nchannels + 1))*2
, GFP_KERNEL))) {
eicon_log(card, 1,
"%s: (%s) Could not allocate DBUFFER-struct.\n", DRIVERLNAME, cid);
kfree(card);
vfree(card->bch);
return;
}
if (!(card->sbuf = (BUFFERS *) kmalloc((sizeof(BUFFERS) * (card->nchannels + 1)) * 2, GFP_KERNEL))) {
eicon_log(card, 1,
"%s: (%s) Could not allocate BUFFERS-struct.\n", DRIVERLNAME, cid);
kfree(card);
vfree(card->bch);
kfree(card->dbuf);
return;
}
if (!(card->sbufp = (char *) kmalloc((270 * (card->nchannels + 1)) * 2, GFP_KERNEL))) {
eicon_log(card, 1,
"%s: (%s) Could not allocate BUFFERSP-struct.\n", DRIVERLNAME, cid);
kfree(card);
vfree(card->bch);
kfree(card->dbuf);
kfree(card->sbuf);
return;
}
for (j=0; j< (card->nchannels + 1); j++) {
memset((char *)&card->dbuf[j], 0, sizeof(DBUFFER));
card->bch[j].de.RBuffer = (DBUFFER *)&card->dbuf[j];
memset((char *)&card->dbuf[j+(card->nchannels+1)], 0, sizeof(BUFFERS));
card->bch[j].be.RBuffer = (DBUFFER *)&card->dbuf[j+(card->nchannels+1)];
memset((char *)&card->sbuf[j], 0, sizeof(BUFFERS));
card->bch[j].de.X = (BUFFERS *)&card->sbuf[j];
memset((char *)&card->sbuf[j+(card->nchannels+1)], 0, sizeof(BUFFERS));
card->bch[j].be.X = (BUFFERS *)&card->sbuf[j+(card->nchannels+1)];
memset((char *)&card->sbufp[j], 0, 270);
card->bch[j].de.X->P = (char *)&card->sbufp[j * 270];
memset((char *)&card->sbufp[j+(card->nchannels+1)], 0, 270);
card->bch[j].be.X->P = (char *)&card->sbufp[(j+(card->nchannels+1)) * 270];
}
memcpy(&card->d, d, sizeof(*d)); /* DESCRIPTOR entries */
/* initializing some variables */
card->lock = SPIN_LOCK_UNLOCKED;
card->ReadyInt = 0;
for(j = 0; j < 256; j++)
card->IdTable[j] = NULL;
for(j = 0; j < (card->d.channels + 1); j++) {
card->bch[j].e.busy = 0;
card->bch[j].e.D3Id = 0;
card->bch[j].e.B2Id = 0;
card->bch[j].e.ref = 0;
card->bch[j].e.Req = 0;
card->bch[j].e.complete = 1;
card->bch[j].fsm_state = EICON_STATE_NULL;
}
printk(KERN_INFO "%s: registered card '%s' with %d channels\n",
DRIVERLNAME, cid, d->channels);
spin_lock_irqsave(&ll_lock, flags);
card->next = cards;
cards = card;
spin_unlock_irqrestore(&ll_lock, flags);
}
/*
* register card at linklevel
*/
static int
eicon_registercard(eicon_card * card)
{
isdn_ctrl cmd;
if (!register_isdn(&card->interface)) {
printk(KERN_WARNING
"%s: Unable to register %s\n", DRIVERLNAME,
card->interface.id);
return -1;
}
card->myid = card->interface.channels;
sprintf(card->regname, "%s", card->interface.id);
/* after register we start it */
card->flags |= EICON_FLAGS_LOADED;
card->flags |= EICON_FLAGS_RUNNING;
cmd.command = ISDN_STAT_RUN;
cmd.driver = card->myid;
cmd.arg = 0;
card->interface.statcallb(&cmd);
return 0;
}
static void
unregister_card(eicon_card * card, int rme)
{
int count;
int channel;
isdn_ctrl cmd;
eicon_chan *chan;
if(rme) {
/* before unload we need to remove the signal entity */
for(channel = 0; channel < card->nchannels; channel++)
{
chan = &(card->bch[channel]);
if (chan->e.D3Id) {
idi_do_req(card, chan, REMOVE, 0);
count = 100;
while(count--) {
if (!chan->e.D3Id)
break;
SLEEP(2);
}
if (!count)
printk(KERN_WARNING"%s: ch:%d unlink to diva module not successful !\n",
DRIVERLNAME, chan->No);
}
}
}
cmd.command = ISDN_STAT_UNLOAD;
cmd.driver = card->myid;
card->interface.statcallb(&cmd);
DBG_TRC(("channel entities freed"));
}
static void
eicon_freecard(eicon_card *card) {
int i;
for(i = 0; i < (card->nchannels + 1); i++) {
skb_queue_purge(&card->bch[i].e.X);
skb_queue_purge(&card->bch[i].e.R);
}
skb_queue_purge(&card->sndq);
skb_queue_purge(&card->rcvq);
skb_queue_purge(&card->rackq);
skb_queue_purge(&card->sackq);
skb_queue_purge(&card->statq);
kfree(card->sbufp);
kfree(card->sbuf);
kfree(card->dbuf);
vfree(card->bch);
kfree(card);
DBG_TRC(("card structures freed"));
}
static int
eicon_addcard(DESCRIPTOR *d)
{
eicon_card *p;
eicon_card *q = NULL;
int registered;
int added = 0;
int failed = 0;
ulong flags;
eicon_alloccard(d);
p = cards;
while (p) {
registered = 0;
if (!p->interface.statcallb) {
/* Not yet registered.
* Try to register and activate it.
*/
added++;
if (!eicon_registercard(p))
registered = 1;
} else {
/* Card already registered */
registered = 1;
}
if (registered) {
/* Init OK, next card ... */
spin_lock_irqsave(&ll_lock, flags);
q = p;
p = p->next;
spin_unlock_irqrestore(&ll_lock, flags);
} else {
/* registering failed, remove card from list, free memory */
printk(KERN_ERR
"%s: Initialization of %s failed\n", DRIVERLNAME,
p->interface.id);
spin_lock_irqsave(&ll_lock, flags);
if (q) {
q->next = p->next;
eicon_freecard(p);
p = q->next;
} else {
cards = p->next;
eicon_freecard(p);
p = cards;
}
spin_unlock_irqrestore(&ll_lock, flags);
failed++;
}
}
return (added - failed);
}
static void *
didd_callback(void *context, DESCRIPTOR* adapter, int removal)
{
eicon_card *cp = NULL, *lastcp = NULL;
ulong flags;
if (adapter->type == IDI_DADAPTER)
{
printk(KERN_ERR "%s: Change in DAdapter ? Oops ?.\n", DRIVERLNAME);
DBG_ERR(("Notification about IDI_DADAPTER change ! Oops."));
return(NULL);
}
else if (adapter->type == IDI_DIMAINT)
{
if (removal)
{
stop_dbg();
}
else
{
memcpy(&MAdapter, adapter, sizeof(MAdapter));
dprintf = (DIVA_DI_PRINTF)MAdapter.request;
DbgRegister("I4L", DRIVERRELEASE, DBG_DEFAULT);
}
}
else if ((adapter->type > 0) &&
(adapter->type < 16))
{ /* IDI Adapter */
if (removal)
{
spin_lock_irqsave(&ll_lock, flags);
lastcp = cp = cards;
while (cp) {
if (cp->d.request == adapter->request)
{
spin_unlock_irqrestore(&ll_lock, flags);
DBG_LOG(("remove adapter from list"));
unregister_card(cp, 0);
spin_lock_irqsave(&ll_lock, flags);
if (cp == lastcp)
cards = cp->next;
else
lastcp->next = cp->next;
eicon_freecard(cp);
break;
}
lastcp = cp;
cp = cp->next;
}
spin_unlock_irqrestore(&ll_lock, flags);
}
else
{
if (adapter->channels) {
DBG_LOG(("add adapter to list"));
eicon_addcard(adapter);
}
}
}
return(NULL);
}
static int __init
connect_didd(void)
{
int x = 0;
int dadapter = 0;
IDI_SYNC_REQ req;
DESCRIPTOR DIDD_Table[MAX_DESCRIPTORS];
DIVA_DIDD_Read(DIDD_Table, sizeof(DIDD_Table));
for (x = 0; x < MAX_DESCRIPTORS; x++)
{
if (DIDD_Table[x].type == IDI_DADAPTER)
{ /* DADAPTER found */
dadapter = 1;
memcpy(&DAdapter, &DIDD_Table[x], sizeof(DAdapter));
req.didd_notify.e.Req = 0;
req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REGISTER_ADAPTER_NOTIFY;
req.didd_notify.info.callback = didd_callback;
req.didd_notify.info.context = 0;
DAdapter.request((ENTITY *)&req);
if (req.didd_notify.e.Rc != 0xff)
{
stop_dbg();
return(0);
}
notify_handle = req.didd_notify.info.handle;
}
else if (DIDD_Table[x].type == IDI_DIMAINT)
{ /* MAINT found */
memcpy(&MAdapter, &DIDD_Table[x], sizeof(DAdapter));
dprintf = (DIVA_DI_PRINTF)MAdapter.request;
DbgRegister("I4L", DRIVERRELEASE, DBG_DEFAULT);
}
else if ((DIDD_Table[x].type > 0) &&
(DIDD_Table[x].type < 16))
{ /* IDI Adapter found */
if (DIDD_Table[x].channels) {
eicon_addcard(&DIDD_Table[x]);
}
}
}
if (!dadapter) {
stop_dbg();
}
return(dadapter);
}
static void __exit
disconnect_didd(void)
{
IDI_SYNC_REQ req;
stop_dbg();
req.didd_notify.e.Req = 0;
req.didd_notify.e.Rc = IDI_SYNC_REQ_DIDD_REMOVE_ADAPTER_NOTIFY;
req.didd_notify.info.handle = notify_handle;
DAdapter.request((ENTITY *)&req);
}
/*
** proc entry
*/
extern struct proc_dir_entry *proc_net_isdn_eicon;
static struct proc_dir_entry *i4lidi_proc_entry = NULL;
static int
i4lidi_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len = 0;
char tmprev[32];
len += sprintf(page+len, "%s\n", DRIVERNAME);
len += sprintf(page+len, "name : %s\n", DRIVERLNAME);
len += sprintf(page+len, "release : %s\n", DRIVERRELEASE);
strcpy(tmprev, eicon_revision);
len += sprintf(page+len, "revision : %s/", eicon_getrev(tmprev));
strcpy(tmprev, eicon_idi_revision);
len += sprintf(page+len, "%s\n", eicon_getrev(tmprev));
if (off + count >= len)
*eof = 1;
if (len < off)
return 0;
*start = page + off;
return((count < len-off) ? count : len-off);
}
static void __init
create_proc(void)
{
if(!(i4lidi_proc_entry = create_proc_entry(DRIVERLNAME,
S_IFREG | S_IRUGO | S_IWUSR, proc_net_isdn_eicon)))
{
printk(KERN_WARNING "%s: failed to create proc entry.\n", DRIVERLNAME);
return;
}
i4lidi_proc_entry->read_proc = i4lidi_proc_read;
i4lidi_proc_entry->owner = THIS_MODULE;
}
static void __exit
remove_proc(void)
{
if(i4lidi_proc_entry)
remove_proc_entry(DRIVERLNAME, proc_net_isdn_eicon);
}
/*
** load / unload
*/
static int __init
i4l_idi_init(void)
{
int ret = 0;
char tmprev[50];
status_lock = SPIN_LOCK_UNLOCKED;
ll_lock = SPIN_LOCK_UNLOCKED;
MOD_INC_USE_COUNT;
if (strlen(id) < 1)
strcpy(id, "diva");
DebugVar = debug;
init_MUTEX_LOCKED(&diva_thread_sem);
init_MUTEX_LOCKED(&diva_thread_end);
printk(KERN_INFO "%s\n", DRIVERNAME);
printk(KERN_INFO "%s: Rel:%s Rev:",DRIVERLNAME , DRIVERRELEASE);
strcpy(tmprev, eicon_revision);
printk("%s/", eicon_getrev(tmprev));
strcpy(tmprev, eicon_idi_revision);
printk("%s\n", eicon_getrev(tmprev));
diva_init_thread();
if(!connect_didd()) {
printk(KERN_ERR "%s: failed to connect to DIDD.\n", DRIVERLNAME);
stop_diva_thread();
ret = -EIO;
goto out;
}
create_proc();
out:
MOD_DEC_USE_COUNT;
return(ret);
}
static void __exit
i4l_idi_exit(void)
{
eicon_card *card, *last, *cc;
ulong flags;
spin_lock_irqsave(&ll_lock, flags);
cc = cards;
card = cc;
cards = NULL;
spin_unlock_irqrestore(&ll_lock, flags);
remove_proc();
while (card) {
unregister_card(card, 1);
card = card->next;
}
stop_diva_thread();
disconnect_didd();
card = cc;
while (card) {
last = card;
card = card->next;
eicon_freecard(last);
}
printk(KERN_INFO "%s: module unloaded.\n", DRIVERLNAME);
}
module_init(i4l_idi_init);
module_exit(i4l_idi_exit);