/* bhs.c * A Babylon to ISDN 4 Linux Hisax interface module. * * Copyright (C) 1997-2000 SpellCaster Telecommunications Inc. * $Id: babhisax.c,v 1.1 2004/03/11 03:59:30 bcrl Exp $ * Released under the GNU Public License. See LICENSE file for details. */ #include "bab_module.h" #include #include #include #include "isdn.h" #include #include #include "vercomp.h" #include "aps_if.h" #include "../scb/scioc.h" #include "babylon_ext.h" #include "hisax/hisax.h" #include "../scb/message.h" #define BHS_VERSION "1.0" #define BHS_MAX_CARDS 4 #define BHS_SPIDSIZE 49 #define BHS_DNSIZE BHS_SPIDSIZE typedef struct { char spid[BHS_SPIDSIZE]; char dn[BHS_DNSIZE]; int cause; } chan_params_t; #define BHS_MAX_CARDS 4 typedef struct bhs { channel_t ch[2]; chan_params_t chan_params[2]; int registered; int hangup_count[2]; struct timer_list timer[2]; int timer_on[2]; isdn_if *iif; int stavail; u16 sesn_id; int switchtype; u8 rem_addr[6]; u8 type; u8 subtype; #if LINUX_VERSION_CODE < 0x02032B struct wait_queue *waitq; /* Wait-Queue for isdninfo */ #else wait_queue_head_t waitq; #endif int debugged; } bhs_t, *bhs_p; bhs_t bhs[BHS_MAX_CARDS]; int nextchan=0; int lock_bchan=0; int bhs_hangup(struct channel *ch); /* * Parameters that can be set by insmod(8) */ static unsigned int debug=0; static char *bhs_numspid[8]={0,0,0,0,0,0,0,0}; static char numspid_kmalloc[8] = {0,0,0,0,0,0,0,0}; static int numspid_inuse[256] = {0,0,0,0,0,0,0,0}; static char *numspid[8]={0,0,0,0,0,0,0,0}; #ifdef MODULE #if LINUX_VERSION_CODE > 0x20100 MODULE_PARM(debug, "1i"); // MODULE_PARM(exactnum, "1i"); // MODULE_PARM(double_connect, "1i"); MODULE_PARM(numspid, "1-8s"); #endif #endif #define milliseconds(x) (((x)*HZ)/1000L) void bhs_timeout(unsigned long arg); void bhs_mod_inc(char *where) { MOD_INC_USE_COUNT; if (debug & 0x4000) printk(KERN_INFO "babhisax: USE_COUNT incremented from %s\n",where); } void bhs_mod_dec(char *where) { MOD_DEC_USE_COUNT; if (debug & 0x4000) printk(KERN_INFO "babhisax: USE_COUNT decremented from %s\n",where); } // find_driver will find the bhs structure that matches the driver ID // presented from the HiSax driver. If the driver ID is negative then // the first valid HiSax driver is returned. static bhs_p find_driver(int driverid) { bhs_p driver=bhs; int i; for(i=0;iiif && (driverid < 0 || driver->iif->channels == driverid)) break; return (i == BHS_MAX_CARDS) ? NULL : driver; } /* * bab_atoi: * Convert a hex string into a number. */ static unsigned int bab_atoi(char* n, int base) { int b = base; int factor = 1; int i = 0; unsigned int x = 0, k = 0; if (b <= 0) { b = 10; } for (i = 0; i < strlen(n); i++) { if (b == 16 && 'A' <= n[i] <= 'F') { x = n[i] - 'A' + 10; } else if (b == 16 && 'a' <= n[i] <= 'f') { x = n[i] - 'a' + 10; } else if ((b == 16 || b == 10) && 0 <= n[i] <= 9) { x = n[i] - 30; } else if (b == 8 && 0 <= n[i] <= 8) { x = n[i] - 30; } else { return -1; /* error ! */ } k += x * factor; factor *= b; } return k; } /* * num_spid: separates the NUM:SPID string into the * dn and the spid. */ static int num_spid(char *n, char *s, char *ns) { char tmp[100]; int i = 0; if (ns == NULL) { n[0] = '\0'; s[0] = '\0'; return 0; } strncpy(tmp, ns, 100); while ((tmp[i] != '\0') && (tmp[i] != ':')) i++; if (tmp[i] == '\0') { n[0] = '\0'; s[0] = '\0'; return 0; } tmp[i] = '\0'; strncpy(n, &tmp[0], BHS_DNSIZE); strncpy(s, &tmp[i+1], BHS_SPIDSIZE); return 0; } static int bhs_setspid(char *spid, int idx) { char s[BHS_SPIDSIZE], n[BHS_DNSIZE]; if (idx >= 2*BHS_MAX_CARDS) return -EINVAL; if (num_spid(n, s, bhs_numspid[idx]) < 0) return -EINVAL; if (numspid_kmalloc[idx]) kfree(bhs_numspid[idx]); bhs_numspid[idx] = kmalloc(BHS_DNSIZE+BHS_SPIDSIZE+1,GFP_KERNEL); if (bhs_numspid[idx] == NULL) return -ENOMEM; numspid_kmalloc[idx] = 1; strncpy(bhs_numspid[idx], n, BHS_DNSIZE); strcat(bhs_numspid[idx], ":"); strncat(bhs_numspid[idx], spid, BHS_SPIDSIZE); return 0; } static int bhs_setdn(char *dn, int idx) { char s[BHS_SPIDSIZE], n[BHS_DNSIZE]; if (idx >= 2*BHS_MAX_CARDS) return -EINVAL; if (num_spid(n, s, bhs_numspid[idx]) < 0) return -EINVAL; if (numspid_kmalloc[idx]) kfree(bhs_numspid[idx]); bhs_numspid[idx] = kmalloc(BHS_DNSIZE+BHS_SPIDSIZE+1,GFP_KERNEL); if (bhs_numspid[idx] == NULL) return -ENOMEM; numspid_kmalloc[idx] = 1; strncpy(bhs_numspid[idx], dn, BHS_DNSIZE); strcat(bhs_numspid[idx], ":"); strncat(bhs_numspid[idx], s, BHS_SPIDSIZE); return 0; } static int bhs_getspid(char *spid, int idx) { char s[BHS_SPIDSIZE], n[BHS_DNSIZE]; if (num_spid(n, s, bhs_numspid[idx]) < 0) return -1; strncpy(spid, s, BHS_SPIDSIZE); return 0; } static int bhs_getdn(char *dn, int idx) { char s[BHS_SPIDSIZE], n[BHS_DNSIZE]; if (num_spid(n, s, bhs_numspid[idx]) < 0) return -1; strncpy(dn, n, BHS_DNSIZE); return 0; } static int isdn_open(struct inode *ino, struct file *filep) { isdn_ctrl cmd; bhs_p driver = find_driver(-1); uint minor = MINOR(ino->i_rdev); if (debug & 0x4000) printk(KERN_INFO "isdn_open: Opening device %d\n",minor); // An error occurs if the device is located from 0 to BHS_MAX_CARDS*2 and // the device is already in use if (numspid_inuse[minor]) return -EACCES; numspid_inuse[minor]++; /* File is on */ // Lock the channel so that the hisax driver cannot be unloaded while the // status file is open if (driver) { cmd.driver = driver->iif->channels; cmd.command = ISDN_CMD_LOCK; driver->iif->command(&cmd); } bhs_mod_inc("isdn_open"); return 0; } #if LINUX_VERSION_CODE > 0x20100 static int isdn_close(struct inode *ino, struct file *filep) #else static void isdn_close(struct inode *ino, struct file *filep) #endif { isdn_ctrl cmd; bhs_p driver = find_driver(-1); uint minor = MINOR(ino->i_rdev); if (debug & 0x4000) printk(KERN_INFO "isdn_close: closing device %d\n",minor); if (numspid_inuse[minor]) { numspid_inuse[minor] = 0; /* File is off */ } else { #if LINUX_VERSION_CODE > 0x20100 return -EPERM; // Cannot close file that hasn't been opened. #else return; #endif } // UnLock the channel so that the hisax driver can be unloaded while the // status file is closed. if (driver) { cmd.driver = driver->iif->channels; cmd.command = ISDN_CMD_UNLOCK; driver->iif->command(&cmd); } bhs_mod_dec("isdn_close"); #if LINUX_VERSION_CODE > 0x20100 return 0; #else return; #endif } int isdn_dummy3(void) { printk(KERN_INFO "bhs: in isdn_dummy3\n"); return -5; } int isdn_dummy4(void) { printk(KERN_INFO "bhs: in isdn_dummy4\n"); return -5; } int isdn_dummy5(void) { printk(KERN_INFO "bhs: in isdn_dummy5\n"); return -5; } int isdn_dummy6(void) { printk(KERN_INFO "bhs: in isdn_dummy6\n"); return -5; } #if LINUX_VERSION_CODE < 0x20100 static int isdn_read(struct inode *ino, struct file *file, char *buf, int count) #else static ssize_t isdn_read(struct file *filp, char *buf, size_t count, loff_t *off) #endif { int i,acount; bhs_p dev=bhs; long flags; #if LINUX_VERSION_CODE < 0x20100 uint minor = MINOR(ino->i_rdev); char *s,*d,*kbuf; #else uint minor = MINOR(filp->f_dentry->d_inode->i_rdev); #endif int len; // The first BHS_MAX_CARDS*2 devices map to the number and spids // Return the number and spids on the first read, then 0 on all subsequent // reads. if (minor < BHS_MAX_CARDS*2) { if (numspid_inuse[minor]++ == 1) { #if LINUX_VERSION_CODE > 0x20100 strcpy(buf,bhs_numspid[minor]); strcat(buf,"\n"); #else for(s=bhs_numspid[minor],d=buf;*s;s++,d++) put_user(*s,d); put_user('\n',d); #endif return strlen(bhs_numspid[minor])+1; } else return 0; } for(i=0;iiif) break; if (i==BHS_MAX_CARDS) return -ENODEV; // Note. Need to fix for multiple cards. For now will select first mapped // device. if (minor <= ISDN_MINOR_CTRLMAX) { // Just indicate no bytes available if no bytes are available if (!dev->stavail) { #if LINUX_VERSION_CODE < 0x02032B interruptible_sleep_on(&(dev->waitq)); #else interruptible_sleep_on(&(dev->waitq)); #endif } if (dev->iif->readstat) { acount = MIN(count, dev->stavail); #if LINUX_VERSION_CODE > 0x20100 len = dev->iif->readstat(buf, acount, 1, dev->iif->channels, 0); #else // Do the readstat into a kernel buffer then copy from the kernel // buffer into the user buffer for 2.0.X kernels. kbuf = kmalloc(acount,GFP_KERNEL); len = dev->iif->readstat(kbuf, acount, 1, dev->iif->channels, 0); for(s=kbuf,d=buf;acount;s++,d++,acount--) put_user(*s,d); kfree(kbuf); #endif } else { len = 0; } save_flags(flags); cli(); if (len) dev->stavail -= len; else dev->stavail = 0; restore_flags(flags); #if LINUX_VERSION_CODE > 0x20100 *off += len; #endif return len; } else return -ENODEV; } #if LINUX_VERSION_CODE < 0x20100 static int isdn_write(struct inode *ino, struct file *file, const char *buf, int count) #else static ssize_t isdn_write(struct file *file, const char *buf, size_t count, loff_t *off) #endif { #if LINUX_VERSION_CODE < 0x20100 uint minor = MINOR(ino->i_rdev); #else uint minor = MINOR(file->f_dentry->d_inode->i_rdev); #endif int len = 0; #if LINUX_VERSION_CODE < 0x20100 char *s,*d; int lc; #endif // The first BHS_MAX_CARDS*2 devices map to the number and spids // Return the number and spids on the first read, then 0 on all subsequent // reads. if (minor < BHS_MAX_CARDS*2) { if (numspid_inuse[minor]++ == 1) { // Check to see if the numspid was kmalloced. If so then free it. if (numspid_kmalloc[minor]) kfree(bhs_numspid[minor]); len = count < 160 ? count : 159; // Now allocate new space for new spid and mark as kmalloced bhs_numspid[minor] = kmalloc(len,GFP_KERNEL); numspid_kmalloc[minor] = 1; #if LINUX_VERSION_CODE < 0x20100 for(s=(char *)buf,d=bhs_numspid[minor],lc=len;lc;s++,lc--) { *d = get_user(s); // Don't copy newlines. if (*d != '\n') d++; else len--; } #else strncpy(bhs_numspid[minor],buf,len); #endif bhs_numspid[minor][len] = '\0'; return count; } else return 0; } return -ENODEV; } static struct file_operations isdn_fops = { #if LINUX_VERSION_CODE >= 0x02032B THIS_MODULE, #endif NULL, /* isdn_seek */ isdn_read, /* isdn_read */ isdn_write, NULL, /* isdn_readdir */ NULL, /* isdn_poll */ NULL, /* isdn_ioctl */ NULL, /* isdn_mmap */ isdn_open, /* open */ #if LINUX_VERSION_CODE >= 0x020100 NULL, /* flush */ #endif isdn_close, /* close */ NULL /* fsync */ }; void bhsreceive(int driverid, int chan, struct sk_buff *skb) { bhs_p driver=find_driver(driverid); if (debug & 0x4000) printk(KERN_INFO "In bhsreceive.\n"); driver->ch[chan].stats.rx_packets ++; #if LINUX_VERSION_CODE >= 0x020100 driver->ch[chan].stats.rx_bytes += skb->len; #endif ch_Input(&driver->ch[chan], skb); return; } int bhs_match_channel(int driver,isdn_ctrl *info) { int i,rv,offset; char *colon; char *eaz = info->parm.setup.eazmsn; int reqchan = info->arg; bhs_t *card = find_driver(driver); if (debug & 0x4000) printk(KERN_INFO "bhs_match_channel: searching for %s in driver %d.\n", eaz,driver); if (card == NULL) { if (debug & 0x4000) { printk(KERN_WARNING "bhs_match_channel: Invalid driverid (%d)!\n", driver); } return -1; } if (card->switchtype == 4 /* NI1 */) { if (debug & 0x4000) { printk(KERN_INFO "bhs_match_channel: Using NI-1 parameters.\n"); } for(i=driver<<1;i < (driver+1) << 1;i++) { colon = strchr(bhs_numspid[i],':'); if (!colon) return -1; *colon = '\0'; offset = strlen(bhs_numspid[i]) - strlen(eaz); if (debug & 0x4000) printk(KERN_INFO "bhs_match_channel: comparing %s and %d: %s.\n", eaz,i,bhs_numspid[i]); rv = strcmp(eaz,bhs_numspid[i]+offset); *colon = ':'; if (!rv) { if (debug & 0x4000) printk(KERN_INFO "bhs_match_channel: matched %s and %s.\n", eaz,bhs_numspid[i]+offset); return i-driver; } } } else if (card->switchtype == 2 /* EuroISDN */) { if (debug & 0x4000) { printk(KERN_INFO "bhs_match_channel: Using DSS-1 parameters.\n"); } i = (driver<<1) + reqchan; colon = strchr(bhs_numspid[i], ':'); if (!colon) return -1; *colon = '\0'; offset = strlen(bhs_numspid[i]) - strlen(eaz); if (debug & 0x4000) { printk(KERN_INFO "bhs_match_channel: comparing %s and %d:%s %d:%d.\n", eaz, i, bhs_numspid[i], driver, reqchan); } rv = strcmp(eaz, bhs_numspid[i]+offset); *colon = ':'; if (!rv) { if (debug & 0x4000) { printk(KERN_INFO "bhs_match_channel: matched %s and %s.\n", eaz, bhs_numspid[i]+offset); } return reqchan; } } else /* Unhandled switchtype or no switchtype */ if (debug & 0x4000) { printk(KERN_WARNING "bhs_match_channel: Unhandled switchtype (%d)\n", card->switchtype); } return -1; } int bhsstat(isdn_ctrl *info) { bhs_p driver=find_driver(info->driver); int chanum = info->arg; int matchchan; isdn_ctrl cmd; int i; if ((debug & 0x4000) && info->command != ISDN_STAT_STAVAIL) printk(KERN_INFO "bhs: In bhsstat driver=%d command=%d channel=%d.\n", info->driver,info->command,chanum); switch (info->command) { case ISDN_STAT_DHUP: // Hangup the specified channel in babylon... bhs_hangup(driver->ch+chanum); if (driver->timer_on[chanum]) { del_timer(driver->timer+chanum); driver->timer_on[chanum] = 0; } break; case ISDN_STAT_STOP: for(i=0;iarg, info->parm.num); } if (info->parm.num[0] == 'E') i++; strncpy(loc, &info->parm.num[i], 2); strncpy(cause, &info->parm.num[i+2], 2); driver->chan_params[chanum].cause = bab_atoi(cause, 16); } break; case ISDN_STAT_ICALL: if (debug & 0x4000) { printk(KERN_INFO "bhs: In bhsstat:ICALL phone=%s eazmsn=%s.\n", info->parm.setup.phone,info->parm.setup.eazmsn); } if ((matchchan = bhs_match_channel(driver-bhs, info)) < 0) { printk(KERN_INFO "bhs: In bhsstat: ICALL no matching channel for eazmsn=%s. \n", info->parm.setup.eazmsn); return 0; } if (matchchan != chanum) { printk(KERN_INFO "bhs: In bhsstat: ICALL for channel %d does not match number=%s.\n", chanum, info->parm.setup.eazmsn); return 0; } // Reset the hangup count so that the channel can be reset. driver->hangup_count[chanum] = 0; cmd.driver = info->driver; cmd.command = ISDN_CMD_SETL2; cmd.arg = 768+chanum; driver->iif->command(&cmd); cmd.driver = info->driver; cmd.command = ISDN_CMD_SETL3; cmd.arg = chanum; driver->iif->command(&cmd); cmd.parm.setup.si1 = 7; cmd.parm.setup.si2 = 0; strcpy(cmd.parm.num, bhs_numspid[((driver-bhs)<<1)+chanum]); // sprintf(cmd.parm.setup.phone, "%s", number); sprintf(cmd.parm.setup.eazmsn, "%s", bhs_numspid[(driver-bhs)*2+chanum]); cmd.driver = info->driver; cmd.arg = chanum; cmd.command = ISDN_CMD_ACCEPTD; driver->iif->command(&cmd); driver->ch[chanum].Open(driver->ch+chanum); driver->ch[chanum].state = CS_CONNECTING; // Set up timer to handle dial hangs init_timer(driver->timer+chanum); driver->timer[chanum].data = (unsigned long) (driver->ch+chanum); driver->timer[chanum].function = bhs_timeout; driver->timer[chanum].expires = jiffies + milliseconds(30000); add_timer(driver->timer+chanum); driver->timer_on[chanum] = 1; // Lock the module when a connect occurs. lock_bchan = 1; // 7/12/00 Need to tell lower level that we'll accept the call by returning 1. return 1; // break; case ISDN_STAT_STAVAIL: /* Indicate how much more new data is available to be read. */ if (driver) { driver->stavail += info->arg; #if LINUX_VERSION_CODE < 0x02032B wake_up_interruptible(&(driver->waitq)); #else wake_up_interruptible(&(driver->waitq)); #endif } break; case ISDN_STAT_BCONN: if (lock_bchan) { // Lock the channel so that the hisax driver cannot be unloaded bhs_mod_inc("BCONN"); cmd.driver = info->driver; cmd.command = ISDN_CMD_LOCK; driver->iif->command(&cmd); lock_bchan = 0; } // B channel connected. Signal babylon that connection done. // Note that info->arg has the correct channel number if (debug & 0x4000) printk(KERN_INFO "bhsstat: setting bhs%d.%ld to connected.\n", driver-bhs,info->arg); driver->ch[info->arg].state = CS_CONNECTED; clear_busy(&driver->ch[info->arg]); // Signal babylon that the channel is now up. driver->ch[info->arg].Up(driver->ch+info->arg); /* connect completed ok */ // Delete the timeout timer for the channel if (driver->timer_on[info->arg]) { del_timer(driver->timer+info->arg); driver->timer_on[info->arg] = 0; } break; case ISDN_STAT_BSENT: // B channel packet sent. Signal Babylon if (test_busy(&driver->ch[info->arg])) clear_busy(&driver->ch[info->arg]); driver->ch[info->arg].OutputComplete(&driver->ch[info->arg]); break; case ISDN_STAT_RUN: // Set the debugging level in the low level module. We cheat and do it here // because it must be done once we have transferred the driver number back // to the low level module. if ((debug & 0x4000) && !driver->debugged) { driver->debugged = 1; cmd.driver = info->driver; cmd.command = ISDN_CMD_IOCTL; cmd.arg = 1; *(unsigned int *)cmd.parm.num = debug; driver->iif->command(&cmd); cmd.driver = info->driver; cmd.command = ISDN_CMD_IOCTL; cmd.arg = 11; *(unsigned int *)cmd.parm.num = debug; driver->iif->command(&cmd); cmd.driver = info->driver; cmd.command = ISDN_CMD_IOCTL; cmd.arg = 13; *(unsigned int *)cmd.parm.num = debug; driver->iif->command(&cmd); } default: break; } return 0; } int bhs_output(channel_t *ch, struct sk_buff *skb) { struct bhs *s = (struct bhs *)ch->dev; int chanum = ch - s->ch; unsigned int ret, len; if (CS_CONNECTED != ch->state) { if (debug & 0x4000) printk(KERN_INFO "bhs_output(%d): Ouput on down link!\n",chanum); return -EPIPE; } // Send data to hisax. Tell hisax to send BSENT when done. len = skb->len; ret = s->iif->writebuf_skb(s->iif->channels,chanum,1,skb); if (ret == 0) { set_busy(ch); return -EBUSY; } if (ret == len) { s->ch[chanum].stats.tx_packets ++; #if LINUX_VERSION_CODE >= 0x020100 s->ch[chanum].stats.tx_bytes += len; #endif } if (debug & 0x4000) printk(KERN_INFO "bhs_output(%d) Sent %d of %d\n",chanum, ret, len); return (0); } void bhs_use(struct channel *ch) { bhs_mod_inc("bhs_use"); if (debug & 0x4000) printk(KERN_INFO "bhs: In use\n"); } void bhs_unuse(struct channel *ch) { bhs_mod_dec("bhs_unuse"); if (debug & 0x4000) printk(KERN_INFO "bhs: In unuse\n"); } #define isdn_command(parm) s->iif->command(parm) void bhs_timeout(unsigned long arg) { struct channel *ch = (struct channel *) arg; struct bhs *s = (struct bhs *)ch->dev; int chanum = ch - s->ch; if (debug & 0x4000) printk(KERN_INFO "bhs: Timeout on (%d,%d)\n", s-bhs,chanum); del_timer(s->timer+chanum); s->timer_on[chanum] = 0; bhs_hangup(ch); } int bhs_connect(struct channel *ch, const char *number, u32 flags) { struct bhs *s = (struct bhs *)ch->dev; int devnum = s - bhs; int chanum = ch - s->ch; isdn_ctrl cmd; if (bhs_numspid[devnum*2+chanum] == NULL) { printk(KERN_INFO "%s: No spid set on channel %d\n", ch->device_name, chanum); return -EINVAL; } ch->state = CS_CONNECTING; s->hangup_count[chanum] = 0; if (debug & 0x4000) printk(KERN_INFO "bhs: In connect s = %d chanum = %d\n", s-bhs,chanum); lock_bchan = 1; cmd.driver = s->iif->channels; cmd.arg = chanum; cmd.command = ISDN_CMD_CLREAZ; isdn_command(&cmd); strcpy(cmd.parm.num, bhs_numspid[devnum*2+chanum]); cmd.driver = s->iif->channels; cmd.arg = chanum; cmd.command = ISDN_CMD_SETEAZ; isdn_command(&cmd); cmd.driver = s->iif->channels; cmd.command = ISDN_CMD_SETL2; cmd.arg = 768+chanum; isdn_command(&cmd); cmd.driver = s->iif->channels; cmd.command = ISDN_CMD_SETL3; cmd.arg = chanum; isdn_command(&cmd); cmd.driver = s->iif->channels; cmd.arg = chanum; sprintf(cmd.parm.setup.phone, "%s", number); sprintf(cmd.parm.setup.eazmsn, "%s", bhs_numspid[devnum*2+chanum]); cmd.parm.setup.si1 = 7; cmd.parm.setup.si2 = 0; cmd.command = ISDN_CMD_DIAL; isdn_command(&cmd); ch->Open(ch); // Set up timer to handle dial hangs init_timer(s->timer+chanum); s->timer[chanum].data = (unsigned long) ch; s->timer[chanum].function = bhs_timeout; s->timer[chanum].expires = jiffies + milliseconds(30000); add_timer(s->timer+chanum); s->timer_on[chanum] = 1; return 0; } int bhs_hangup(struct channel *ch) { isdn_ctrl cmd; struct bhs *s = (struct bhs *)ch->dev; int chanum = ch - s->ch; if (debug & 0x4000) { printk(KERN_INFO "bhs: In hangup s = %d chanum = %d\n",s-bhs,chanum); printk(KERN_INFO "bhs: In hangup state = %d\n",ch->state); printk(KERN_INFO "bhs: In hangup count = %d\n",s->hangup_count[chanum]); } if (s->hangup_count[chanum] == 4) return 0; s->hangup_count[chanum]++; switch (ch->state) { case CS_CONNECTED: bhs_mod_dec("bhs_hangup"); // UnLock the channel so that the hisax driver can be unloaded cmd.driver = s->iif->channels; cmd.command = ISDN_CMD_UNLOCK; isdn_command(&cmd); lock_bchan = 0; // Fall through to CONNECTING state case CS_CONNECTING: // Hangup the lower level hisax driver cmd.driver = s->iif->channels; cmd.arg = chanum; cmd.command = ISDN_CMD_HANGUP; isdn_command(&cmd); ch->state = CS_DISCONNECTING; // Fall through to DISCONNECTING state case CS_DISCONNECTING: set_busy(ch); ch->state = CS_IDLE; // ch->Close(ch); if (s->chan_params[chanum].cause == 0) { s->chan_params[chanum].cause = 0x10; } ch->ConnectComplete(ch,s->chan_params[chanum].cause); s->chan_params[chanum].cause = 0; ch->Down(ch); break; default: break; } return 0; } static int bhs_get_cardinfo(bhs_t* card, babylon_card_info_t *info) { isdn_ctrl cmd; cmd.driver = card->iif->channels; cmd.command = ISDN_CMD_IOCTL; cmd.arg = 66; /* SpellCaster Extension */ cmd.parm.status = info; if (card->iif->command(&cmd) == 0) { memcpy(info, cmd.parm.status, sizeof(babylon_card_info_t)); return 0; } else { return -1; } } /* Yes, this function IS slow. Used below in * bhs_ioctl */ static int strncpy_fromfs(char *dst, char *src, int n) { /* I know that this is slow */ while (n-- > 0) { if (copy_from_user(dst, src, 1)) return -EFAULT; src++; dst++; } if (n) *dst = 0; return 0; } static int bhs_ioctl(struct channel *ch, unsigned int cmd, unsigned long arg) { bhs_t *card; scs_ioctl scs; unsigned char tch; char tmp[255]; boardInfo bi; chan_params_t *chan; babylon_card_info_t cardinfo; LnkStats stats; if (copy_from_user(&scs, (void *)arg, sizeof(scs))) return -EFAULT; if (scs.device > BHS_MAX_CARDS || scs.device < 0) return -ENODEV; card = &bhs[scs.device]; if (!card) return -ENODEV; scs.channel--; if (debug > 0x4000) printk("bhs_ioctl: card: %p channel %d\n", card, scs.channel); chan = &(card->chan_params[scs.channel]); switch(scs.command) { case SCIOCSETSWITCH: return -EINVAL; case SCIOCGETSWITCH: if (bhs_get_cardinfo(card, &cardinfo) < 0) { return -EFAULT; } /* Translate from ISDN4Linux -> Babylon * switch types */ switch(cardinfo.switchtype) { case 2: /* EuroISDN */ tch = 4; break; case 4: /* NI1 */ tch = 0; break; } if (copy_to_user(scs.dataptr, &tch, sizeof(tch))) return -EFAULT; return 0; case SCIOCSETSPID: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (strncpy_fromfs(tmp, scs.dataptr, BHS_SPIDSIZE)) return -EFAULT; if (debug >= 0x4000) printk("Setting %s on ch%d\n", tmp, 2*scs.device+scs.channel); return (bhs_setspid(tmp, (2*scs.device+scs.channel))); case SCIOCSETDN: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (strncpy_fromfs(tmp, scs.dataptr, BHS_DNSIZE)) return -EFAULT; return (bhs_setdn(tmp, (2*scs.device+scs.channel))); case SCIOCSETSERIAL: /* Not supported */ return -EINVAL; case SCIOCGETSPID: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (bhs_getspid(tmp, (2*scs.device+scs.channel)) < 0) return -ENODEV; if (copy_to_user(scs.dataptr, tmp, BHS_SPIDSIZE)) return -EFAULT; return 0; case SCIOCGETDN: if (scs.channel < 0 || scs.channel > 1) return -ENODEV; if (bhs_getdn(tmp, (2*scs.device+scs.channel)) < 0) return -ENODEV; if (copy_to_user(scs.dataptr, tmp, BHS_DNSIZE)) return -EFAULT; return 0; case SCIOCGETPROCVER: if (copy_to_user(scs.dataptr, BHS_VERSION, strlen(BHS_VERSION)+1)) return -EFAULT; return 0; return -EINVAL; case SCIOCGETCARDINFO: if (bhs_get_cardinfo(card, &cardinfo) < 0) { return -EFAULT; } memset(&bi, 0, sizeof(bi)); if (cardinfo.subtype == 2 /* DIVA_PCI (see diva.c)*/) { bi.modelid = 8; /* BHS_BOARD_NT1 */ } else if (cardinfo.subtype == 4 /* DIVA_IPAC_PCI (see diva.c) */) { bi.modelid = 9; } bi.iobase = cardinfo.io; bi.rambase = cardinfo.pci_space; bi.irq = cardinfo.irq; bi.ram_size = 0; bi.num_channels = 2; bi.num_spans = 1; strncpy(bi.loadVer, "1.0", 5); strncpy(bi.rev_no, "A", 3); if (copy_to_user(scs.dataptr, &bi, sizeof(bi))) return -EFAULT; return 0; case SCIOCGETPHYSTAT: { struct { u8 b1_status; u8 b2_status; u8 l1_status; } bristat; // First channel translation switch (card->ch[0].state) { case CS_IDLE: case CS_DISCONNECTED: bristat.b1_status = 1; // Ready break; case CS_UNAVAIL: bristat.b1_status = 0; // Unavail break; case CS_DIALING: case CS_RINGING: case CS_CONNECTING: bristat.b1_status = 2; // Connecting break; case CS_CONNECTED: case CS_STALLED: bristat.b1_status = 3; // Connected break; case CS_DISCONNECTING: bristat.b1_status = 4; // Disconnecting break; default: bristat.b1_status = 0; break; } switch (card->ch[1].state) { case CS_IDLE: case CS_DISCONNECTED: bristat.b2_status = 1; // Ready break; case CS_UNAVAIL: bristat.b2_status = 0; // Unavail break; case CS_DIALING: case CS_RINGING: case CS_CONNECTING: bristat.b2_status = 2; // Connecting break; case CS_CONNECTED: case CS_STALLED: bristat.b2_status = 3; // Connected break; case CS_DISCONNECTING: bristat.b2_status = 4; // Disconnecting break; default: bristat.b2_status = 0; } bristat.l1_status = 1; if (copy_to_user(scs.dataptr, &bristat, sizeof(bristat))) return -EFAULT; return 0; } case SCIOCGETLNKSTAT: if (bhs_get_cardinfo(card, &cardinfo) < 0) { return -EFAULT; } stats.tx_good = cardinfo.tx_good[scs.channel]; stats.tx_bad = cardinfo.tx_bad[scs.channel]; stats.rx_good = cardinfo.rx_good[scs.channel]; stats.rx_bad = cardinfo.rx_bad[scs.channel]; stats.tx_dropped = 0; stats.rx_dropped = 0; return 0; case SCIOCRESET: case SCIOCSTART: break; } return -ENOSYS; } int register_isdn(isdn_if *i) { int j,numchan,chan; bhs_p newbhs=bhs; char name[10]; babylon_card_info_t cardinfo; if (debug & 0x4000) printk(KERN_INFO "bhs: In register_isdn.\n"); // Find an open channel to register the new card. for(j=0;jiif) break; // Fail if channel isn't registers. Note that current HISAX code doesn't check // return value and will cause a kernel panic if a channel isn't allocated. if (j == BHS_MAX_CARDS) return 0; /* * If we're running 2.3.43 (but really a 2.4.X) kernel, * we need to init the waitqueue structure */ #if LINUX_VERSION_CODE >= 0x02032B init_waitqueue_head(&(newbhs->waitq)); #endif // Save the interface pointer here. newbhs->iif = i; // Set up so that debugging can be turned on later newbhs->debugged = 0; // Assign the next channel number to the device and update nextchan to the // next available channel. numchan = i->channels; i->channels = nextchan; nextchan += numchan; // Set the callbacks for the device. i->rcvcallb_skb = bhsreceive; i->statcallb = bhsstat; for(chan=0;chan<2;chan++) { sprintf(name,"bhs%d.%d",j,chan); strcpy(newbhs->ch[chan].device_name, name); newbhs->ch[chan].mru = i->maxbufsize; newbhs->ch[chan].use = bhs_use; newbhs->ch[chan].unuse = bhs_unuse; newbhs->ch[chan].Output = bhs_output; newbhs->ch[chan].Connect = bhs_connect; newbhs->ch[chan].Hangup = bhs_hangup; newbhs->ch[chan].ioctl = bhs_ioctl; newbhs->ch[chan].dev = newbhs; newbhs->stavail = 0; strcpy(newbhs->ch[chan].dev_class,"isdn_b"); if (RegisterChannel(&newbhs->ch[chan])) { printk("unable to register channel %d.%d\n",j,chan); return -ENODEV; } newbhs->registered = 1; set_busy(&newbhs->ch[chan]); } bhs_get_cardinfo(newbhs, &cardinfo); newbhs->switchtype = cardinfo.switchtype; newbhs->type = cardinfo.type; newbhs->subtype = cardinfo.subtype; return 1; } int init_module(void) { int i; /* First off we now need to process the numspid argument. Because the 2.0.X insmod interface is dain bramaged, each number/spid combo requires a leading alpha character which is subsequently stripped. */ for(i=0;i