/* * loop.c loop driver for looped bchannel pairs * * Author Andreas Eversberg (jolly@jolly.de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* module parameters: * interfaces: Number of loop interfaces. Default is 1. */ #include #include #include #include "channel.h" #include "layer1.h" #include "debug.h" #include #include "loop.h" static const char *loop_revision = "$Revision$"; static int loop_cnt; static mISDNobject_t loop_obj; static char LoopName[] = "loop"; /****************/ /* module stuff */ /****************/ static int interfaces; static int debug; #ifdef MODULE MODULE_AUTHOR("Andreas Eversberg"); #ifdef MODULE_LICENSE MODULE_LICENSE("GPL"); #endif MODULE_PARM(interfaces, "1i"); MODULE_PARM(debug, "1i"); #endif /****************************/ /* Layer 1 D-channel access */ /****************************/ /* message transfer from layer 1. */ static int loop_l1hw(mISDNinstance_t *inst, struct sk_buff *skb) { channel_t *dch = container_of(inst, channel_t, inst); loop_t *hc; int ret = 0; mISDN_head_t *hh; hh = mISDN_HEAD_P(skb); hc = dch->inst.privat; if (debug & DEBUG_LOOP_MSG) printk(KERN_DEBUG "%s: unsupported prim %x\n", __FUNCTION__, hh->prim); ret = -EINVAL; if (!ret) dev_kfree_skb(skb); return(ret); } /******************************/ /* Layer2 -> Layer 1 Transfer */ /******************************/ /* messages from layer 2 to layer 1 are processed here. */ static int loop_l2l1(mISDNinstance_t *inst, struct sk_buff *skb) { int ch; channel_t *bch = container_of(inst, channel_t, inst); int ret = -EINVAL; mISDN_head_t *hh; loop_t *hc; struct sk_buff *nskb; hh = mISDN_HEAD_P(skb); hc = bch->inst.privat; ch = bch->channel; if ((hh->prim == PH_DATA_REQ) || (hh->prim == (DL_DATA | REQUEST))) { if (skb->len <= 0) { printk(KERN_WARNING "%s: skb too small\n", __FUNCTION__); return(-EINVAL); } if (skb->len > MAX_DATA_MEM) { printk(KERN_WARNING "%s: skb too large\n", __FUNCTION__); return(-EINVAL); } if ((nskb = skb_clone(skb, GFP_ATOMIC))) queue_ch_frame(hc->bch[ch^1], INDICATION, MISDN_ID_ANY, nskb); skb_trim(skb, 0); return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, hh->dinfo, skb)); } else if ((hh->prim == (PH_ACTIVATE | REQUEST)) || (hh->prim == (DL_ESTABLISH | REQUEST))) { /* activate B-channel if not already activated */ skb_trim(skb, 0); return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, ret, skb)); } else if ((hh->prim == (PH_DEACTIVATE | REQUEST)) || (hh->prim == (DL_RELEASE | REQUEST)) || ((hh->prim == (PH_CONTROL | REQUEST) && (hh->dinfo == HW_DEACTIVATE)))) { skb_trim(skb, 0); return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, ret, skb)); } else if (hh->prim == (PH_CONTROL | REQUEST)) { switch (hh->dinfo) { default: printk(KERN_DEBUG "%s: unknown PH_CONTROL info %x\n", __FUNCTION__, hh->dinfo); ret = -EINVAL; } } else { printk(KERN_WARNING "%s: unknown prim(%x)\n", __FUNCTION__, hh->prim); ret = -EINVAL; } if (!ret) { dev_kfree_skb(skb); } return(ret); } /************************** * remove card from stack * **************************/ static void loop_delete(loop_t *hc) { int ch; u_long flags; if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: entered\n", __FUNCTION__); /* free channels */ if (hc->dch) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: free D-channel\n", __FUNCTION__); mISDN_freechannel(hc->dch); kfree(hc->dch); hc->dch = NULL; } ch = 0; while(ch < LOOP_CHANNELS) { if (hc->bch[ch]) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: free B-channel %d\n", __FUNCTION__, ch); mISDN_freechannel(hc->bch[ch]); kfree(hc->bch[ch]); hc->bch[ch] = NULL; } ch++; } /* remove us from list and delete */ if (debug & DEBUG_LOOP_INIT) printk(KERN_WARNING "%s: remove instance from list\n", __FUNCTION__); spin_lock_irqsave(&loop_obj.lock, flags); list_del(&hc->list); spin_unlock_irqrestore(&loop_obj.lock, flags); if (debug & DEBUG_LOOP_INIT) printk(KERN_WARNING "%s: delete instance\n", __FUNCTION__); kfree(hc); loop_cnt--; if (debug & DEBUG_LOOP_INIT) printk(KERN_WARNING "%s: card successfully removed\n", __FUNCTION__); } static int loop_manager(void *data, u_int prim, void *arg) { loop_t *hc; mISDNinstance_t *inst = data; struct sk_buff *skb; channel_t *dch = NULL; channel_t *bch = NULL; int ch = 0; u_long flags; if (!data) { MGR_HASPROTOCOL_HANDLER(prim,arg,&loop_obj) printk(KERN_ERR "%s: no data prim %x arg %p\n", __FUNCTION__, prim, arg); return(-EINVAL); } /* find channel and card */ spin_lock_irqsave(&loop_obj.lock, flags); list_for_each_entry(hc, &loop_obj.ilist, list) { if (hc->dch) if (&hc->dch->inst == inst) { dch = hc->dch; spin_unlock_irqrestore(&loop_obj.lock, flags); if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: D-channel data %p prim %x arg %p\n", __FUNCTION__, data, prim, arg); goto found; } ch = 0; while(ch < LOOP_CHANNELS) { if (hc->bch[ch]) if (&hc->bch[ch]->inst == inst) { bch = hc->bch[ch]; spin_unlock_irqrestore(&loop_obj.lock, flags); if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: B-channel %d (0..%d) data %p prim %x arg %p\n", __FUNCTION__, ch, LOOP_CHANNELS-1, data, prim, arg); goto found; } } } spin_unlock_irqrestore(&loop_obj.lock, flags); printk(KERN_ERR "%s: no card/channel found data %p prim %x arg %p\n", __FUNCTION__, data, prim, arg); return(-EINVAL); found: switch(prim) { case MGR_REGLAYER | CONFIRM: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_REGLAYER\n", __FUNCTION__); mISDN_setpara(dch, &inst->st->para); break; case MGR_UNREGLAYER | REQUEST: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_UNREGLAYER\n", __FUNCTION__); if (dch) { if ((skb = create_link_skb(PH_CONTROL | REQUEST, HW_DEACTIVATE, 0, NULL, 0))) { if (loop_l1hw(inst, skb)) dev_kfree_skb(skb); } } else if (bch) { if ((skb = create_link_skb(PH_CONTROL | REQUEST, 0, 0, NULL, 0))) { if (loop_l2l1(inst, skb)) dev_kfree_skb(skb); } } loop_obj.ctrl(inst, MGR_UNREGLAYER | REQUEST, NULL); break; case MGR_CLRSTPARA | INDICATION: arg = NULL; // fall through case MGR_ADDSTPARA | INDICATION: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_***STPARA\n", __FUNCTION__); mISDN_setpara(dch, arg); break; case MGR_RELEASE | INDICATION: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_RELEASE = remove port from mISDN\n", __FUNCTION__); if (dch) { loop_delete(hc); /* hc is free */ } break; #ifdef FIXME case MGR_CONNECT | REQUEST: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_CONNECT\n", __FUNCTION__); return(mISDN_ConnectIF(inst, arg)); case MGR_SETIF | REQUEST: case MGR_SETIF | INDICATION: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_SETIF\n", __FUNCTION__); if (dch) return(mISDN_SetIF(inst, arg, prim, loop_l1hw, NULL, dch)); if (bch) return(mISDN_SetIF(inst, arg, prim, loop_l2l1, NULL, bch)); break; case MGR_DISCONNECT | REQUEST: case MGR_DISCONNECT | INDICATION: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_DISCONNECT\n", __FUNCTION__); return(mISDN_DisConnectIF(inst, arg)); #endif #if 0 case MGR_SELCHANNEL | REQUEST: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_SELCHANNEL\n", __FUNCTION__); if (!dch) { printk(KERN_WARNING "%s(MGR_SELCHANNEL|REQUEST): selchannel not dinst\n", __FUNCTION__); return(-EINVAL); } return(SelFreeBChannel(hc, ch, arg)); #endif case MGR_SETSTACK | INDICATION: if (debug & DEBUG_LOOP_MGR) printk(KERN_DEBUG "%s: MGR_SETSTACK\n", __FUNCTION__); if (bch && inst->pid.global==2) { if ((skb = create_link_skb(PH_ACTIVATE | REQUEST, 0, 0, NULL, 0))) { if (loop_l2l1(inst, skb)) dev_kfree_skb(skb); } if (inst->pid.protocol[2] == ISDN_PID_L2_B_TRANS) mISDN_queue_data(inst, FLG_MSG_UP, DL_ESTABLISH | INDICATION, 0, 0, NULL, 0); else mISDN_queue_data(inst, FLG_MSG_UP, PH_ACTIVATE | INDICATION, 0, 0, NULL, 0); } break; PRIM_NOT_HANDLED(MGR_CTRLREADY | INDICATION); PRIM_NOT_HANDLED(MGR_GLOBALOPT | REQUEST); default: printk(KERN_WARNING "%s: prim %x not handled\n", __FUNCTION__, prim); return(-EINVAL); } return(0); } /************************* * create cards instance * *************************/ static int __devinit loop_new(void) { int ret_err=0; int ch; loop_t *hc; mISDN_pid_t pid; mISDNstack_t *dst = NULL; /* make gcc happy */ channel_t *dch; channel_t *bch; u_long flags; if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: Registering loop driver #%d\n", __FUNCTION__, loop_cnt+1); /* allocate structure */ if (!(hc = kmalloc(sizeof(loop_t), GFP_ATOMIC))) { printk(KERN_ERR "No kmem for loop driver\n"); ret_err = -ENOMEM; goto free_object; } memset(hc, 0, sizeof(loop_t)); hc->idx = loop_cnt; hc->id = loop_cnt + 1; sprintf(hc->name, "LOOP#%d", loop_cnt+1); if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: (after APPEND_TO_LIST)\n", __FUNCTION__); spin_lock_irqsave(&loop_obj.lock, flags); list_add_tail(&hc->list, &loop_obj.ilist); spin_unlock_irqrestore(&loop_obj.lock, flags); if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: (after APPEND_TO_LIST)\n", __FUNCTION__); spin_lock_init(&hc->lock); if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: Registering D-channel, card(%d)\n", __FUNCTION__, loop_cnt+1); dch = kmalloc(sizeof(channel_t), GFP_ATOMIC); if (!dch) { ret_err = -ENOMEM; goto free_channels; } memset(dch, 0, sizeof(channel_t)); dch->channel = 0; //dch->debug = debug; dch->inst.obj = &loop_obj; dch->inst.hwlock = &hc->lock; mISDN_init_instance(&dch->inst, &loop_obj, hc, loop_l1hw); dch->inst.pid.layermask = ISDN_LAYER(0); sprintf(dch->inst.name, "LOOP%d", loop_cnt+1); if (mISDN_initchannel(dch, MSK_INIT_DCHANNEL, MAX_DATA_MEM)) { ret_err = -ENOMEM; goto free_channels; } hc->dch = dch; ch=0; while(ch < LOOP_CHANNELS) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: Registering B-channel, card(%d) ch(%d)\n", __FUNCTION__, loop_cnt+1, ch); bch = kmalloc(sizeof(channel_t), GFP_ATOMIC); if (!bch) { ret_err = -ENOMEM; goto free_channels; } memset(bch, 0, sizeof(channel_t)); bch->channel = ch; mISDN_init_instance(&bch->inst, &loop_obj, hc, loop_l2l1); bch->inst.pid.layermask = ISDN_LAYER(0); bch->inst.hwlock = &hc->lock; //bch->debug = debug; sprintf(bch->inst.name, "%s B%d", dch->inst.name, ch+1); if (mISDN_initchannel(bch, MSK_INIT_BCHANNEL, MAX_DATA_MEM)) { kfree(bch); ret_err = -ENOMEM; goto free_channels; } hc->bch[ch] = bch; #ifdef FIXME // TODO if (bch->dev) { bch->dev->wport.pif.func = loop_l2l1; bch->dev->wport.pif.fdata = bch; } #endif ch++; } /* set D-channel */ mISDN_set_dchannel_pid(&pid, 0x00, ISDN_LAYER(0)); pid.protocol[0] = ISDN_PID_L0_LOOP; pid.layermask = ISDN_LAYER(0); /* add stacks */ if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: Adding d-stack: card(%d)\n", __FUNCTION__, loop_cnt+1); if ((ret_err = loop_obj.ctrl(NULL, MGR_NEWSTACK | REQUEST, &dch->inst))) { printk(KERN_ERR "MGR_ADDSTACK REQUEST dch err(%d)\n", ret_err); free_release: loop_delete(hc); /* hc is free */ goto free_object; } dst = dch->inst.st; ch = 0; while(ch < LOOP_CHANNELS) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: Adding b-stack: card(%d) B-channel(%d)\n", __FUNCTION__, loop_cnt+1, ch+1); bch = hc->bch[ch]; if ((ret_err = loop_obj.ctrl(dst, MGR_NEWSTACK | REQUEST, &bch->inst))) { printk(KERN_ERR "MGR_ADDSTACK bchan error %d\n", ret_err); free_delstack: loop_obj.ctrl(dst, MGR_DELSTACK | REQUEST, NULL); goto free_release; } ch++; } if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: (before MGR_SETSTACK REQUEST) layermask=0x%x\n", __FUNCTION__, pid.layermask); if ((ret_err = loop_obj.ctrl(dst, MGR_SETSTACK | REQUEST, &pid))) { printk(KERN_ERR "MGR_SETSTACK REQUEST dch err(%d)\n", ret_err); goto free_delstack; } if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: (after MGR_SETSTACK REQUEST)\n", __FUNCTION__); /* delay some time */ set_current_state(TASK_UNINTERRUPTIBLE); schedule_timeout((100*HZ)/1000); /* Timeout 100ms */ /* tell stack, that we are ready */ loop_obj.ctrl(dst, MGR_CTRLREADY | INDICATION, NULL); loop_cnt++; return(0); /* if an error ocurred */ free_channels: if (hc->dch) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: free D-channel\n", __FUNCTION__); mISDN_freechannel(hc->dch); kfree(hc->dch); hc->dch = NULL; } ch = 0; while(ch < LOOP_CHANNELS) { if (hc->bch[ch]) { if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: free B-channel %d\n", __FUNCTION__, ch); mISDN_freechannel(hc->bch[ch]); kfree(hc->bch[ch]); hc->bch[ch] = NULL; } ch++; } if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: before REMOVE_FROM_LIST (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt); spin_lock_irqsave(&loop_obj.lock, flags); list_del(&hc->list); spin_unlock_irqrestore(&loop_obj.lock, flags); if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: after REMOVE_FROM_LIST (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt); kfree(hc); free_object: return(ret_err); } static void __exit loop_cleanup(void) { loop_t *hc,*next; int err; /* unregister mISDN object */ if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: entered (refcnt = %d loop_cnt = %d)\n", __FUNCTION__, loop_obj.refcnt, loop_cnt); if ((err = mISDN_unregister(&loop_obj))) { printk(KERN_ERR "Can't unregister Loop cards error(%d)\n", err); } /* remove remaining devices, but this should never happen */ if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: now checking ilist (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt); list_for_each_entry_safe(hc, next, &loop_obj.ilist, list) { printk(KERN_ERR "Loop card struct not empty refs %d\n", loop_obj.refcnt); loop_delete(hc); } if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: done (refcnt = %d loop_cnt = %d)\n", __FUNCTION__, loop_obj.refcnt, loop_cnt); } static int __init loop_init(void) { int err; char tmpstr[64]; if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: init entered\n", __FUNCTION__); strcpy(tmpstr, loop_revision); printk(KERN_INFO "mISDN: loop-driver Rev. %s\n", mISDN_getrev(tmpstr)); memset(&loop_obj, 0, sizeof(loop_obj)); #ifdef MODULE loop_obj.owner = THIS_MODULE; #endif spin_lock_init(&loop_obj.lock); INIT_LIST_HEAD(&loop_obj.ilist); loop_obj.name = LoopName; loop_obj.own_ctrl = loop_manager; loop_obj.DPROTO.protocol[0] = ISDN_PID_L0_LOOP; loop_obj.BPROTO.protocol[1] = ISDN_PID_L1_B_64TRANS | ISDN_PID_L1_B_64HDLC; loop_obj.BPROTO.protocol[2] = ISDN_PID_L2_B_TRANS | ISDN_PID_L2_B_RAWDEV; if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: registering loop_obj\n", __FUNCTION__); if ((err = mISDN_register(&loop_obj))) { printk(KERN_ERR "Can't register Loop cards error(%d)\n", err); return(err); } if (debug & DEBUG_LOOP_INIT) printk(KERN_DEBUG "%s: new mISDN object (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt); if (interfaces < 1) interfaces = 1; loop_cnt = 0; while(loop_cnt < interfaces) { if ((err = loop_new())) break; } if (err) { printk(KERN_ERR "error registering pci driver:%x\n",err); loop_cleanup(); return(err); } printk(KERN_INFO "%d devices registered\n", loop_cnt); return(0); } #ifdef MODULE module_init(loop_init); module_exit(loop_cleanup); #endif