patch-1.3.93 linux/drivers/isdn/isdn_net.c

Next file: linux/drivers/isdn/isdn_net.h
Previous file: linux/drivers/isdn/isdn_common.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.92/linux/drivers/isdn/isdn_net.c linux/drivers/isdn/isdn_net.c
@@ -1,4 +1,4 @@
-/* $Id: isdn_net.c,v 1.4 1996/02/19 15:23:38 fritz Exp fritz $
+/* $Id: isdn_net.c,v 1.5 1996/04/20 16:28:38 fritz Exp $
  *
  * Linux ISDN subsystem, network interfaces and related functions (linklevel).
  *
@@ -21,6 +21,15 @@
  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
  *
  * $Log: isdn_net.c,v $
+ * Revision 1.5  1996/04/20 16:28:38  fritz
+ * Made more parameters of the dial statemachine user-configurable and
+ * added hangup after dial for more reliability using callback.
+ * Changed all io going through generic routines in isdn_common.c
+ * Added missing call to dev_free_skb on failed dialing.
+ * Added uihdlc encapsulation.
+ * Fixed isdn_net_setcfg not to destroy interface-flags anymore.
+ * Misc. typos.
+ *
  * Revision 1.4  1996/02/19 15:23:38  fritz
  * Bugfix: Sync-PPP packets got compressed twice, when resent due to
  *         send-queue-full reject.
@@ -70,7 +79,7 @@
 static int isdn_net_start_xmit(struct sk_buff *, struct device *);
 static int isdn_net_xmit(struct device *, isdn_net_local *, struct sk_buff *);
  
-char *isdn_net_revision = "$Revision: 1.4 $";
+char *isdn_net_revision = "$Revision: 1.5 $";
 
  /*
   * Code for raw-networking over ISDN
@@ -208,10 +217,15 @@
 				return 1;
 			case ISDN_STAT_DCONN:
 				/* D-Channel is up */
-				if (lp->dialstate == 4 || lp->dialstate == 7
-                                    || lp->dialstate == 8) {
-					lp->dialstate++;
-                                        return 1;
+                                switch (lp->dialstate) {
+                                        case 4:
+                                        case 7:
+                                        case 8:
+                                                lp->dialstate++;
+                                                return 1;
+                                        case 12:
+                                                lp->dialstate = 5;
+                                                return 1;
                                 }
 				break;
 			case ISDN_STAT_DHUP:
@@ -236,25 +250,32 @@
                                 break;
 			case ISDN_STAT_BCONN:
 				/* B-Channel is up */
-				if (lp->dialstate >= 5 && lp->dialstate <= 10) {
-					if (lp->dialstate <= 6) {
-                                                dev->usage[idx] |= ISDN_USAGE_OUTGOING;
-                                                isdn_info_update();
-					} else
-                                                dev->rx_netdev[idx] = p;
-					lp->dialstate = 0;
-                                        isdn_timer_ctrl(ISDN_TIMER_NETHANGUP,1);
-					printk(KERN_INFO "isdn_net: %s connected\n", lp->name);
-					/* If first Chargeinfo comes before B-Channel connect,
-					 * we correct the timestamp here.
-					 */
-					lp->chargetime = jiffies;
-                                        /* Immediately send first skb to speed up arp */
-                                        if (lp->first_skb) {
-                                                if (!(isdn_net_xmit(&p->dev,lp,lp->first_skb)))
-                                                        lp->first_skb = NULL;
-                                        }
-                                        return 1;
+                                switch (lp->dialstate) {
+                                        case 5:
+                                        case 6:
+                                        case 7:
+                                        case 8:
+                                        case 9:
+                                        case 10:
+                                        case 12:
+                                                if (lp->dialstate <= 6) {
+                                                        dev->usage[idx] |= ISDN_USAGE_OUTGOING;
+                                                        isdn_info_update();
+                                                } else
+                                                        dev->rx_netdev[idx] = p;
+                                                lp->dialstate = 0;
+                                                isdn_timer_ctrl(ISDN_TIMER_NETHANGUP,1);
+                                                printk(KERN_INFO "isdn_net: %s connected\n", lp->name);
+                                                /* If first Chargeinfo comes before B-Channel connect,
+                                                 * we correct the timestamp here.
+                                                 */
+                                                lp->chargetime = jiffies;
+                                                /* Immediately send first skb to speed up arp */
+                                                if (lp->first_skb) {
+                                                        if (!(isdn_net_xmit(&p->dev,lp,lp->first_skb)))
+                                                                lp->first_skb = NULL;
+                                                }
+                                                return 1;
 				}
 				break;
 			case ISDN_STAT_NODCH:
@@ -316,6 +337,10 @@
 	isdn_ctrl cmd;
 
 	while (p) {
+#ifdef ISDN_DEBUG_NET_DIAL
+                if (p->local.dialstate)
+                        printk(KERN_DEBUG "%s: dialstate=%d\n", p->local.name,p->local.dialstate);
+#endif
 		switch (p->local.dialstate) {
 		case 0:
 			/* Nothing to do for this interface */
@@ -359,6 +384,7 @@
 			p->local.huptimer = 0;
 			p->local.outgoing = 1;
 			p->local.hupflags |= 1;
+                        p->local.hupflags &= ~2;
 			if (!strcmp(p->local.dial->num, "LEASED")) {
 				p->local.dialstate = 4;
 				printk(KERN_INFO "%s: Open leased line ...\n", p->local.name);
@@ -388,18 +414,20 @@
 				dev->drv[p->local.isdn_device]->interface->command(&cmd);
 			}
 			anymore = 1;
-			p->local.dialstate++;
+			p->local.dialstate =
+                                (p->local.cbdelay &&
+                                 (p->local.flags & ISDN_NET_CBOUT))?12:4;
 			break;
 		case 4:
-			/* Wait for D-Channel-connect or incoming call, if passive
-			 * callback configured. If timeout and max retries not
+			/* Wait for D-Channel-connect.
+                         * If timeout and max retries not
 			 * reached, switch back to state 3.
 			 */
 			if (p->local.dtimer++ > ISDN_TIMER_DTIMEOUT10)
 				if (p->local.dialretry < p->local.dialmax) {
 					p->local.dialstate = 3;
 				} else
-					isdn_net_hangup(&p->dev);
+                                        isdn_net_hangup(&p->dev);
 			anymore = 1;
 			break;
 		case 5:
@@ -456,7 +484,7 @@
 			p->local.dialstate++;
 			break;
 		case 8:
-		case 10:
+                case 10:
 			/*  Wait for B- or D-channel-connect */
 #ifdef ISDN_DEBUG_NET_DIAL
 			printk(KERN_DEBUG "dialtimer4: %d\n", p->local.dtimer);
@@ -466,6 +494,28 @@
 			else
 				anymore = 1;
 			break;
+                case 11:
+                        /* Callback Delay */
+                        if (p->local.dtimer++ > p->local.cbdelay)
+                                p->local.dialstate = 1;
+                        anymore = 1;
+                        break;
+		case 12:
+			/* Remote does callback. Hangup after cbdelay, then wait for incoming
+                         * call (in state 4).
+			 */
+                        if (p->local.dtimer++ > p->local.cbdelay) {
+                                printk(KERN_INFO "%s: hangup waiting for callback ...\n", p->local.name);
+                                p->local.dtimer = 0;
+                                p->local.dialstate = 4;
+                                cmd.driver = p->local.isdn_device;
+                                cmd.command = ISDN_CMD_HANGUP;
+                                cmd.arg = p->local.isdn_channel;
+                                (void) dev->drv[cmd.driver]->interface->command(&cmd);
+                                isdn_all_eaz(p->local.isdn_device, p->local.isdn_channel);
+                        }
+                        anymore = 1;
+			break;
 		default:
 			printk(KERN_WARNING "isdn_net: Illegal dialstate %d for device %s\n",
 			       p->local.dialstate, p->local.name);
@@ -476,24 +526,6 @@
 }
 
 /*
- * Send-data-helpfunction for net-interfaces
- */
-int
-isdn_net_send(u_char * buf, int di, int ch, int len)
-{
-	int l;
-
-	if ((l = dev->drv[di]->interface->writebuf(di, ch, buf, len, 0)) == len)
-		return 1;
-	/* Device driver queue full (or packet > 4000 bytes, should never
-	 * happen)
-	 */
-	if (l == -EINVAL)
-		printk(KERN_ERR "isdn_net: Huh, sending pkt too big!\n");
-	return 0;
-}
-
-/*
  * Perform hangup for a net-interface.
  */
 void
@@ -505,6 +537,10 @@
 
 	save_flags(flags);
 	cli();
+        if (lp->first_skb) {
+                dev_kfree_skb(lp->first_skb,FREE_WRITE);
+                lp->first_skb = NULL;
+        }
 	if (lp->flags & ISDN_NET_CONNECTED) {
 		printk(KERN_INFO "isdn_net: local hangup %s\n", lp->name);
 		lp->dialstate = 0;
@@ -560,9 +596,9 @@
                         len = 4;
 #ifdef CONFIG_ISDN_MPP
                         if (lp->ppp_minor!=-1) {
-                                if (ippp_table[lp->ppp_minor].mpppcfg &
+                                if (ippp_table[lp->ppp_minor]->mpppcfg &
                                     SC_MP_PROT) {
-                                        if (ippp_table[lp->ppp_minor].mpppcfg &
+                                        if (ippp_table[lp->ppp_minor]->mpppcfg &
                                             SC_OUT_SHORT_SEQ)
                                                 len = 7;
                                         else
@@ -634,16 +670,8 @@
 	int ret;
 	
 	lp->transcount += skb->len;
-	if (dev->drv[lp->isdn_device]->interface->writebuf_skb) 
-		ret = dev->drv[lp->isdn_device]->interface->
-			writebuf_skb(lp->isdn_device, lp->isdn_channel, skb);
-	else {	      
-		if ((ret = isdn_net_send(skb->data, lp->isdn_device,
-                                    lp->isdn_channel, skb->len)))
-		        dev_kfree_skb(skb, FREE_WRITE);
-	}
-
-	if (ret)
+        ret = isdn_writebuf_skb_stub(lp->isdn_device, lp->isdn_channel, skb);
+	if (ret == skb->len)
 		clear_bit(0, (void *)&(ndev->tbusy));
 	return (!ret);
 }                                      
@@ -790,6 +818,11 @@
                                 /* remember first skb to speed up arp
                                  * when using encap ETHER
                                  */
+                                if (lp->first_skb) {
+                                        printk(KERN_WARNING "isdn_net_start_xmit: First skb already set!\n");
+                                        dev_kfree_skb(lp->first_skb,FREE_WRITE);
+                                        lp->first_skb = NULL;
+                                }
                                 lp->first_skb = skb;
 				/* Initiate dialing */
 				isdn_net_dial();
@@ -951,35 +984,39 @@
         isdn_dumppkt("R:", skb->data, skb->len, 40);
 #endif
 	switch (lp->p_encap) {
-	case ISDN_NET_ENCAP_ETHER:
-		/* Ethernet over ISDN */
-		skb->protocol = isdn_net_type_trans(skb,ndev);
-		break;
-	case ISDN_NET_ENCAP_RAWIP:
-		/* RAW-IP without MAC-Header */
-		skb->protocol = htons(ETH_P_IP);
-		break;
-	case ISDN_NET_ENCAP_CISCOHDLC:
-		/* CISCO-HDLC IP with type field and  fake I-frame-header */
-		skb_pull(skb, 2);
-		/* Fall through */
-	case ISDN_NET_ENCAP_IPTYP:
-		/* IP with type field */
-		skb->protocol = *(unsigned short *)&(skb->data[0]);
-		skb_pull(skb, 2);
-                if (*(unsigned short *)skb->data == 0xFFFF)
-                        skb->protocol = htons(ETH_P_802_3);
-		break;
+                case ISDN_NET_ENCAP_ETHER:
+                        /* Ethernet over ISDN */
+                        skb->protocol = isdn_net_type_trans(skb,ndev);
+                        break;
+                case ISDN_NET_ENCAP_UIHDLC:
+                        /* HDLC with UI-frame (for ispa with -h1 option) */
+                        skb_pull(skb,2);
+                        /* Fall through */
+                case ISDN_NET_ENCAP_RAWIP:
+                        /* RAW-IP without MAC-Header */
+                        skb->protocol = htons(ETH_P_IP);
+                        break;
+                case ISDN_NET_ENCAP_CISCOHDLC:
+                        /* CISCO-HDLC IP with type field and  fake I-frame-header */
+                        skb_pull(skb, 2);
+                        /* Fall through */
+                case ISDN_NET_ENCAP_IPTYP:
+                        /* IP with type field */
+                        skb->protocol = *(unsigned short *)&(skb->data[0]);
+                        skb_pull(skb, 2);
+                        if (*(unsigned short *)skb->data == 0xFFFF)
+                                skb->protocol = htons(ETH_P_802_3);
+                        break;
 #ifdef CONFIG_ISDN_PPP
-	case ISDN_NET_ENCAP_SYNCPPP:
-		isdn_ppp_receive(lp->netdev, olp, skb);
-		return;
-#endif
-        default:
-                printk(KERN_WARNING "%s: unknown encapsulation, dropping\n",
-                       lp->name);
-                kfree_skb(skb,FREE_READ);
-                return;
+                case ISDN_NET_ENCAP_SYNCPPP:
+                        isdn_ppp_receive(lp->netdev, olp, skb);
+                        return;
+#endif
+                default:
+                        printk(KERN_WARNING "%s: unknown encapsulation, dropping\n",
+                               lp->name);
+                        kfree_skb(skb,FREE_READ);
+                        return;
 	}
 	netif_rx(skb);
 	return;
@@ -1100,6 +1137,11 @@
                         *((ushort*) skb_push(skb, 2)) = htons(type);
                         len = 2;
                         break;
+                case ISDN_NET_ENCAP_UIHDLC:
+                        /* HDLC with UI-Frames (for ispa with -h1 option) */
+                        *((ushort*) skb_push(skb, 2)) = htons(0x0103);
+                        len = 2;
+                        break;
                 case ISDN_NET_ENCAP_CISCOHDLC:
 			skb_push(skb, 4);
                         skb->data[0] = 0x0f;
@@ -1113,9 +1155,9 @@
                         len = 4;
 #ifdef CONFIG_ISDN_MPP
                         if (lp->ppp_minor!=-1) {
-                                if (ippp_table[lp->ppp_minor].mpppcfg &
+                                if (ippp_table[lp->ppp_minor]->mpppcfg &
                                     SC_MP_PROT) {
-                                        if (ippp_table[lp->ppp_minor].mpppcfg &
+                                        if (ippp_table[lp->ppp_minor]->mpppcfg &
                                             SC_OUT_SHORT_SEQ)
                                                 len = 7;
                                         else
@@ -1355,7 +1397,9 @@
  *
  * Return-Value: 0 = No appropriate interface for this call.
  *               1 = Call accepted
- *               2 = Do callback
+ *               2 = Reject call, wait cbdelay, then call back
+ *               3 = Reject call
+ *               4 = Wait cbdelay, then call back
  */
 int
 isdn_net_find_icall(int di, int ch, int idx, char *num)
@@ -1436,11 +1480,11 @@
 		printk(KERN_DEBUG "n_fi: if='%s', l.msn=%s, l.flags=%d, l.dstate=%d\n",
 		       p->local.name, p->local.msn, p->local.flags, p->local.dialstate);
 #endif
-		if ((!strcmp(isdn_map_eaz2msn(p->local.msn, di), eaz)) &&	/* EAZ is matching   */
-		    (((!(p->local.flags & ISDN_NET_CONNECTED)) &&	/* but not connected */
-		      (USG_NONE(dev->usage[idx]))) ||	/* and ch. unused or */
-		     (((p->local.dialstate == 4) &&	/* if dialing        */
-		       (!(p->local.flags & ISDN_NET_CALLBACK)))		/* but no callback   */
+		if ((!strcmp(isdn_map_eaz2msn(p->local.msn, di), eaz)) &&	    /* EAZ is matching   */
+		    (((!(p->local.flags & ISDN_NET_CONNECTED)) &&	            /* but not connected */
+		      (USG_NONE(dev->usage[idx]))) ||	                            /* and ch. unused or */
+		     ((((p->local.dialstate == 4) || (p->local.dialstate == 12)) && /* if dialing        */
+		       (!(p->local.flags & ISDN_NET_CALLBACK)))		            /* but no callback   */
 		     ))) {
 #ifdef ISDN_DEBUG_NET_ICALL
 			printk(KERN_DEBUG "n_fi: match1, pdev=%d pch=%d\n",
@@ -1513,7 +1557,7 @@
 						continue;
 					}
 				}
-			}
+			} /* if (dev->usage[idx] & ISDN_USAGE_EXCLUSIVE) */
 #ifdef ISDN_DEBUG_NET_ICALL
 			printk(KERN_DEBUG "n_fi: match2\n");
 #endif
@@ -1578,7 +1622,8 @@
 							return 0;
 						}
 						/* Setup dialstate. */
-						lp->dialstate = 1;
+                                                lp->dtimer = 0;
+						lp->dialstate = 11;
 						lp->flags |= ISDN_NET_CONNECTED;
 						/* Connect interface with channel */
 						isdn_net_bind_channel(lp, chi);
@@ -1592,9 +1637,9 @@
 								return 0;
 							}
 #endif
-						/* Initiate dialing by returning 2 */
+						/* Initiate dialing by returning 2 or 4 */
 						restore_flags(flags);
-						return 2;
+						return (lp->flags & ISDN_NET_CBHUP)?2:4;
 					} else
 						printk(KERN_WARNING "isdn_net: %s: No phone number\n", lp->name);
 					restore_flags(flags);
@@ -1613,7 +1658,7 @@
 #endif
 					/* if this interface is dialing, it does it probably on a different
 					   device, so free this device */
-					if (p->local.dialstate == 4)
+					if ((p->local.dialstate == 4) || (p->local.dialstate == 12))
 						isdn_free_channel(p->local.isdn_device, p->local.isdn_channel,
 							 ISDN_USAGE_NET);
 					dev->usage[idx] &= ISDN_USAGE_EXCLUSIVE;
@@ -1630,6 +1675,7 @@
 					p->local.outgoing = 0;
 					p->local.huptimer = 0;
 					p->local.hupflags |= 1;
+                                        p->local.hupflags &= ~2;
 #ifdef CONFIG_ISDN_PPP
 					if (lp->p_encap == ISDN_NET_ENCAP_SYNCPPP)
 						if (isdn_ppp_bind(lp) < 0) {
@@ -1732,7 +1778,7 @@
  * Allocate a new network-interface and initialize it's data structures.
  */
 char *
- isdn_net_new(char *name, struct device *master)
+isdn_net_new(char *name, struct device *master)
 {
 	isdn_net_dev *netdev;
 
@@ -1802,8 +1848,9 @@
 	netdev->local.hupflags = 8;	/* Do hangup even on incoming calls */
 	netdev->local.onhtime = 10;	/* Default hangup-time for saving costs
 					   of those who forget configuring this */
-	/* The following should be configurable via ioctl */
 	netdev->local.dialmax = 1;
+        netdev->local.flags = ISDN_NET_CBHUP; /* Hangup before Callback */
+        netdev->local.cbdelay = 25;      /* Wait 5 secs before Callback */
 	/* Put into to netdev-chain */
 	netdev->next = (void *) dev->netdev;
 	dev->netdev = netdev;
@@ -1811,7 +1858,7 @@
 }
 
 char *
- isdn_net_newslave(char *parm)
+isdn_net_newslave(char *parm)
 {
 	char *p = strchr(parm, ',');
 	isdn_net_dev *n;
@@ -1860,9 +1907,7 @@
 			printk(KERN_WARNING "isdn_net: No driver with selected features\n");
 			return -ENODEV;
 		}
-                if ((p->local.p_encap != cfg->p_encap) &&
-                    ((p->local.p_encap == ISDN_NET_ENCAP_RAWIP) ||
-                     (cfg->p_encap == ISDN_NET_ENCAP_RAWIP)        ))
+                if (p->local.p_encap != cfg->p_encap)
                         if (p->dev.start) {
                                 printk(KERN_WARNING
                                        "%s: cannot change encap when if is up\n",
@@ -1939,16 +1984,30 @@
 		p->local.charge      = cfg->charge;
 		p->local.l2_proto    = cfg->l2_proto;
 		p->local.l3_proto    = cfg->l3_proto;
+                p->local.cbdelay     = cfg->cbdelay;
+                p->local.dialmax     = cfg->dialmax;
 		p->local.slavedelay  = cfg->slavedelay * HZ;
-		p->local.p_encap     = cfg->p_encap;
 		if (cfg->secure)
 			p->local.flags |= ISDN_NET_SECURE;
 		else
 			p->local.flags &= ~ISDN_NET_SECURE;
-		if (cfg->callback)
-			p->local.flags |= ISDN_NET_CALLBACK;
+		if (cfg->cbhup)
+			p->local.flags |= ISDN_NET_CBHUP;
 		else
-			p->local.flags &= ~ISDN_NET_CALLBACK;
+			p->local.flags &= ~ISDN_NET_CBHUP;
+		switch (cfg->callback) {
+                        case 0:
+                                p->local.flags &= ~(ISDN_NET_CALLBACK&ISDN_NET_CBOUT);
+                                break;
+                        case 1:
+                                p->local.flags |= ISDN_NET_CALLBACK;
+                                p->local.flags &= ~ISDN_NET_CBOUT;
+                                break;
+                        case 2:
+                                p->local.flags |= ISDN_NET_CBOUT;
+                                p->local.flags &= ~ISDN_NET_CALLBACK;
+                                break;
+                }
 		if (cfg->chargehup)
 			p->local.hupflags |= 4;
 		else
@@ -1957,23 +2016,26 @@
 			p->local.hupflags |= 8;
 		else
 			p->local.hupflags &= ~8;
-                if (cfg->p_encap == ISDN_NET_ENCAP_RAWIP) {
-                        p->dev.hard_header         = NULL;
-                        p->dev.header_cache_bind   = NULL;
-                        p->dev.header_cache_update = NULL;
-                        p->dev.flags               = IFF_NOARP;
-                } else {
-                        p->dev.hard_header = isdn_net_header;
-                        if (cfg->p_encap == ISDN_NET_ENCAP_ETHER) {
-                                p->dev.header_cache_bind   = p->local.org_hcb;
-                                p->dev.header_cache_update = p->local.org_hcu;
-                                p->dev.flags = IFF_BROADCAST | IFF_MULTICAST;
-                        } else {
+		if (cfg->p_encap != p->local.p_encap) {
+                        if (cfg->p_encap == ISDN_NET_ENCAP_RAWIP) {
+                                p->dev.hard_header         = NULL;
                                 p->dev.header_cache_bind   = NULL;
                                 p->dev.header_cache_update = NULL;
                                 p->dev.flags               = IFF_NOARP;
+                        } else {
+                                p->dev.hard_header = isdn_net_header;
+                                if (cfg->p_encap == ISDN_NET_ENCAP_ETHER) {
+                                        p->dev.header_cache_bind   = p->local.org_hcb;
+                                        p->dev.header_cache_update = p->local.org_hcu;
+                                        p->dev.flags = IFF_BROADCAST | IFF_MULTICAST;
+                                } else {
+                                        p->dev.header_cache_bind   = NULL;
+                                        p->dev.header_cache_update = NULL;
+                                        p->dev.flags               = IFF_NOARP;
+                                }
                         }
                 }
+		p->local.p_encap     = cfg->p_encap;
                 return 0;
 	}
 	return -ENODEV;
@@ -2001,8 +2063,12 @@
 		cfg->p_encap = p->local.p_encap;
 		cfg->secure = (p->local.flags & ISDN_NET_SECURE) ? 1 : 0;
 		cfg->callback = (p->local.flags & ISDN_NET_CALLBACK) ? 1 : 0;
+		cfg->callback = (p->local.flags & ISDN_NET_CBOUT) ? 2 : 0;
+		cfg->cbhup = (p->local.flags & ISDN_NET_CBHUP) ? 1 : 0;
 		cfg->chargehup = (p->local.hupflags & 4) ? 1 : 0;
 		cfg->ihup = (p->local.hupflags & 8) ? 1 : 0;
+                cfg->cbdelay = p->local.cbdelay;
+                cfg->dialmax = p->local.dialmax;
 		cfg->slavedelay = p->local.slavedelay / HZ;
 		if (p->local.slave)
 			strcpy(cfg->slave, ((isdn_net_local *) p->local.slave->priv)->name);
@@ -2074,6 +2140,7 @@
 		n = n->next;
 		more = 1;
 	}
+        put_fs_byte(0,phones);
 	restore_flags(flags);
 	return count;
 }

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this