patch-2.4.21 linux-2.4.21/drivers/usb/auerisdn_b.c

Next file: linux-2.4.21/drivers/usb/auerisdn_b.h
Previous file: linux-2.4.21/drivers/usb/auerisdn.h
Back to the patch index
Back to the overall index

diff -urN linux-2.4.20/drivers/usb/auerisdn_b.c linux-2.4.21/drivers/usb/auerisdn_b.c
@@ -0,0 +1,689 @@
+/*****************************************************************************/
+/*
+ *      auerisdn_b.c  --  Auerswald PBX/System Telephone ISDN B-channel interface.
+ *
+ *      Copyright (C) 2002  Wolfgang Mües (wolfgang@iksw-muees.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 of the License, 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.
+ */
+ /*****************************************************************************/
+
+#include <linux/isdnif.h>	/* ISDN constants */
+#include <linux/netdevice.h>	/* skb functions */
+
+#undef DEBUG			/* include debug macros until it's done */
+#include <linux/usb.h>		/* standard usb header */
+
+#include "auerisdn.h"
+#include "auermain.h"
+
+/*-------------------------------------------------------------------*/
+/* ISDN B channel support defines                                    */
+#define AUISDN_BC_1MS		8	/* Bytes per channel and ms */
+#define AUISDN_BC_INC		4	/* change INT OUT size increment */
+#define AUISDN_BCDATATHRESHOLD	48	/* for unsymmetric 2-B-channels */
+#define AUISDN_TOGGLETIME	6	/* Timeout for unsymmetric serve */
+
+/*-------------------------------------------------------------------*/
+/* Debug support 						     */
+#ifdef DEBUG
+#define dump( desc, adr, len) \
+do {			\
+	unsigned int u;	\
+	printk (KERN_DEBUG); \
+	printk (desc); \
+	for (u = 0; u < len; u++) \
+		printk (" %02X", adr[u] & 0xFF); \
+	printk ("\n"); \
+} while (0)
+#else
+#define dump( desc, adr, len)
+#endif
+
+/*-------------------------------------------------------------------*/
+
+/* Callback to L2 for HISAX */
+/* This callback can be called from 3 sources:
+   a) from hisax context (answer from a l2l1 function)
+   b) from interrupt context (a B channel paket arrived, a B channel paket was sent)
+   c) from kernel daemon context (probe/disconnecting)
+*/
+void auerisdn_b_l1l2(struct auerisdnbc *bc, int pr, void *arg)
+{
+	struct auerhisax *ahp;
+	struct sk_buff *skb;
+
+	/* do the callback */
+	ahp = bc->cp->isdn.ahp;
+	if (ahp) {
+		ahp->hisax_b_if[bc->channel].ifc.l1l2(&ahp->
+						      hisax_b_if[bc->
+								 channel].
+						      ifc, pr, arg);
+	} else {
+		dbg("auerisdn_b_l1l2 called without ahp");
+		if (pr == (PH_DATA | INDICATION)) {
+			skb = (struct sk_buff *) arg;
+			if (skb) {
+				skb_pull(skb, skb->len);
+				dev_kfree_skb_any(skb);
+			}
+		}
+	}
+}
+
+/* fill the INT OUT data buffer with new data */
+/* Transfer buffer size to fill is in urbp->transfer_buffer_length */
+static void auerisdn_bintbo_newdata(struct auerisdn *ip)
+{
+	unsigned long flags;
+	struct urb *urbp = ip->intbo_urbp;
+	struct auerisdnbc *bc = &ip->bc[0];	/* start with B-channel 0 */
+	struct sk_buff *skb;
+	unsigned char *ucp;
+	int buf_size;
+	int len;
+	int bytes_sent;
+	int i;
+
+	/* FIXME: this algorithm is fixed to 2 B-channels */
+	/* Which B channel should we serve? */
+	if (ip->bc[1].mode != L1_MODE_NULL) {
+		/* B channel 1 is used */
+		if (bc->mode != L1_MODE_NULL) {
+			/* both B-channels are used */
+			if (ip->intbo_toggletimer) {
+				/* simply toggling */
+				ip->intbo_toggletimer--;
+				i = ip->intbo_index ^ 1;	/* serve both channels equal */
+			} else {
+				/* search the B channel with the most demand of data */
+				i = bc->txfree - ip->bc[1].txfree;
+				if (i < -AUISDN_BCDATATHRESHOLD)
+					i = 1;	/* B channel 1 needs more data */
+				else if (i > AUISDN_BCDATATHRESHOLD)
+					i = 0;	/* B channel 0 needs more data */
+				else
+					i = ip->intbo_index ^ 1;	/* serve both channels equal */
+				if (i == ip->intbo_index)
+					ip->intbo_toggletimer =
+					    AUISDN_TOGGLETIME;
+			}
+			bc = &ip->bc[i];
+			ip->intbo_index = i;
+		} else {
+			bc = &ip->bc[1];
+		}
+	}
+	dbg("INTBO: Fill B%d with %d Bytes, %d Bytes free",
+	    bc->channel + 1, urbp->transfer_buffer_length - AUH_SIZE,
+	    bc->txfree);
+
+	/* Fill the buffer with data */
+	ucp = ip->intbo_bufp;
+	*ucp++ = AUH_B1CHANNEL + bc->channel;	/* First byte is channel nr. */
+	buf_size = urbp->transfer_buffer_length - AUH_SIZE;
+	len = 0;
+	while (len < buf_size) {
+		spin_lock_irqsave(&bc->txskb_lock, flags);
+		if ((skb = bc->txskb)) {
+			/* dump ("raw tx data:", skb->data, skb->len); */
+			if (bc->mode == L1_MODE_TRANS) {
+				bytes_sent = buf_size - len;
+				if (skb->len < bytes_sent)
+					bytes_sent = skb->len;
+				{	/* swap tx bytes */
+					register unsigned char *src =
+					    skb->data;
+					unsigned int count;
+					for (count = 0; count < bytes_sent;
+					     count++)
+						*ucp++ =
+						    isdnhdlc_bit_rev_tab
+						    [*src++];
+				}
+				len += bytes_sent;
+				bc->lastbyte = skb->data[bytes_sent - 1];
+			} else {
+				int bs =
+				    isdnhdlc_encode(&bc->outp_hdlc_state,
+						    skb->data, skb->len,
+						    &bytes_sent,
+						    ucp, buf_size - len);
+				/* dump ("hdlc data:", ucp, bs); */
+				len += bs;
+				ucp += bs;
+			}
+			skb_pull(skb, bytes_sent);
+
+			if (!skb->len) {
+				// Frame sent
+				bc->txskb = NULL;
+				spin_unlock_irqrestore(&bc->txskb_lock,
+						       flags);
+				auerisdn_b_l1l2(bc, PH_DATA | CONFIRM,
+						(void *) skb->truesize);
+				dev_kfree_skb_any(skb);
+				continue;	//while
+			}
+		} else {
+			if (bc->mode == L1_MODE_TRANS) {
+				memset(ucp, bc->lastbyte, buf_size - len);
+				ucp += buf_size - len;
+				len = buf_size;
+				/* dbg ("fill = 0xFF"); */
+			} else {
+				// Send flags
+				int bs =
+				    isdnhdlc_encode(&bc->outp_hdlc_state,
+						    NULL, 0, &bytes_sent,
+						    ucp, buf_size - len);
+				/* dbg ("fill = 0x%02X", (int)*ucp); */
+				len += bs;
+				ucp += bs;
+			}
+		}
+		spin_unlock_irqrestore(&bc->txskb_lock, flags);
+	}
+	/* dbg ("%d Bytes to TX buffer", len); */
+}
+
+
+/* INT OUT completion handler */
+static void auerisdn_bintbo_complete(struct urb *urbp)
+{
+	struct auerisdn *ip = urbp->context;
+
+	/* unlink completion? */
+	if ((urbp->status == -ENOENT) || (urbp->status == -ECONNRESET)) {
+		/* should we restart with another size? */
+		if (ip->intbo_state == INTBOS_CHANGE) {
+			dbg("state => RESTART");
+			ip->intbo_state = INTBOS_RESTART;
+		} else {
+			/* set up variables for later restart */
+			dbg("INTBO stopped");
+			ip->intbo_state = INTBOS_IDLE;
+		}
+		/* nothing more to do */
+		return;
+	}
+
+	/* other state != 0? */
+	if (urbp->status) {
+		warn("auerisdn_bintbo_complete: status = %d",
+		     urbp->status);
+		return;
+	}
+
+	/* Should we fill in new data? */
+	if (ip->intbo_state == INTBOS_CHANGE) {
+		dbg("state == INTBOS_CHANGE, no new data");
+		return;
+	}
+
+	/* fill in new data */
+	auerisdn_bintbo_newdata(ip);
+}
+
+/* set up the INT OUT URB the first time */
+/* Don't start the URB */
+static void auerisdn_bintbo_setup(struct auerisdn *ip, unsigned int len)
+{
+	ip->intbo_state = INTBOS_IDLE;
+	FILL_INT_URB(ip->intbo_urbp, ip->usbdev,
+		     usb_sndintpipe(ip->usbdev, ip->intbo_endp),
+		     ip->intbo_bufp, len, auerisdn_bintbo_complete, ip,
+		     ip->outInterval);
+	ip->intbo_urbp->transfer_flags |= USB_ASYNC_UNLINK;
+	ip->intbo_urbp->status = 0;
+}
+
+/* restart the INT OUT endpoint */
+static void auerisdn_bintbo_restart(struct auerisdn *ip)
+{
+	struct urb *urbp = ip->intbo_urbp;
+	int status;
+
+	/* dbg ("auerisdn_intbo_restart"); */
+
+	/* fresh restart */
+	auerisdn_bintbo_setup(ip, ip->paketsize + AUH_SIZE);
+
+	/* Fill in new data */
+	auerisdn_bintbo_newdata(ip);
+
+	/* restart the urb */
+	ip->intbo_state = INTBOS_RUNNING;
+	status = usb_submit_urb(urbp);
+	if (status < 0) {
+		err("can't submit INT OUT urb, status = %d", status);
+		urbp->status = status;
+		urbp->complete(urbp);
+	}
+}
+
+/* change the size of the INT OUT endpoint */
+static void auerisdn_bchange(struct auerisdn *ip, unsigned int paketsize)
+{
+	/* changing... */
+	dbg("txfree[0] = %d, txfree[1] = %d, old size = %d, new size = %d",
+	    ip->bc[0].txfree, ip->bc[1].txfree, ip->paketsize, paketsize);
+	ip->paketsize = paketsize;
+
+	if (paketsize == 0) {
+		/* stop the INT OUT endpoint */
+		dbg("stop unlinking INT out urb");
+		ip->intbo_state = INTBOS_IDLE;
+		usb_unlink_urb(ip->intbo_urbp);
+		return;
+	}
+	if (ip->intbo_state != INTBOS_IDLE) {
+		/* dbg ("unlinking INT out urb"); */
+		ip->intbo_state = INTBOS_CHANGE;
+		usb_unlink_urb(ip->intbo_urbp);
+	} else {
+		/* dbg ("restart immediately"); */
+		auerisdn_bintbo_restart(ip);
+	}
+}
+
+/* serve the outgoing B channel interrupt */
+/* Called from the INT IN completion handler */
+static void auerisdn_bserv(struct auerisdn *ip)
+{
+	struct auerisdnbc *bc;
+	unsigned int u;
+	unsigned int paketsize;
+
+	/* should we start the INT OUT endpoint again? */
+	if (ip->intbo_state == INTBOS_RESTART) {
+		/* dbg ("Restart INT OUT from INT IN"); */
+		auerisdn_bintbo_restart(ip);
+		return;
+	}
+	/* no new calculation if change already in progress */
+	if (ip->intbo_state == INTBOS_CHANGE)
+		return;
+
+	/* calculation of transfer parameters for INT OUT endpoint */
+	paketsize = 0;
+	for (u = 0; u < AUISDN_BCHANNELS; u++) {
+		bc = &ip->bc[u];
+		if (bc->mode != L1_MODE_NULL) {	/* B channel is active */
+			unsigned int bpp = AUISDN_BC_1MS * ip->outInterval;
+			if (bc->txfree < bpp) {	/* buffer is full, throttle */
+				bc->txsize = bpp - AUISDN_BC_INC;
+				paketsize += bpp - AUISDN_BC_INC;
+			} else if (bc->txfree < bpp * 2) {
+				paketsize += bc->txsize;	/* schmidt-trigger, continue */
+			} else if (bc->txfree < bpp * 4) {	/* we are in synch */
+				bc->txsize = bpp;
+				paketsize += bpp;
+			} else if (bc->txfree > bc->ofsize / 2) {/* we have to fill the buffer */
+				bc->txsize = bpp + AUISDN_BC_INC;
+				paketsize += bpp + AUISDN_BC_INC;
+			} else {
+				paketsize += bc->txsize;	/* schmidt-trigger, continue */
+			}
+		}
+	}
+
+	/* check if we have to change the paket size */
+	if (paketsize != ip->paketsize)
+		auerisdn_bchange(ip, paketsize);
+}
+
+/* Send activation/deactivation state to L2 */
+static void auerisdn_bconf(struct auerisdnbc *bc)
+{
+	unsigned long flags;
+	struct sk_buff *skb;
+
+	if (bc->mode == L1_MODE_NULL) {
+		auerisdn_b_l1l2(bc, PH_DEACTIVATE | INDICATION, NULL);
+		/* recycle old txskb */
+		spin_lock_irqsave(&bc->txskb_lock, flags);
+		skb = bc->txskb;
+		bc->txskb = NULL;
+		spin_unlock_irqrestore(&bc->txskb_lock, flags);
+		if (skb) {
+			skb_pull(skb, skb->len);
+			auerisdn_b_l1l2(bc, PH_DATA | CONFIRM,
+					(void *) skb->truesize);
+			dev_kfree_skb_any(skb);
+		}
+	} else {
+		auerisdn_b_l1l2(bc, PH_ACTIVATE | INDICATION, NULL);
+	}
+}
+
+/* B channel setup completion handler */
+static void auerisdn_bmode_complete(struct urb *urb)
+{
+	struct auerswald *cp;
+	struct auerbuf *bp = (struct auerbuf *) urb->context;
+	struct auerisdnbc *bc;
+	int channel;
+
+	dbg("auerisdn_bmode_complete called");
+	cp = ((struct auerswald *) ((char *) (bp->list) -
+				    (unsigned
+				     long) (&((struct auerswald *) 0)->
+					    bufctl)));
+
+	/* select the B-channel */
+	channel = le16_to_cpu(bp->dr->wIndex);
+	channel -= AUH_B1CHANNEL;
+	if (channel < 0)
+		goto rel;
+	if (channel >= AUISDN_BCHANNELS)
+		goto rel;
+	bc = &cp->isdn.bc[channel];
+
+	/* Check for success */
+	if (urb->status) {
+		err("complete with non-zero status: %d", urb->status);
+	} else {
+		bc->mode = *bp->bufp;
+	}
+	/* Signal current mode to L2 */
+	auerisdn_bconf(bc);
+
+	/* reuse the buffer */
+      rel:auerbuf_releasebuf(bp);
+
+	/* Wake up all processes waiting for a buffer */
+	wake_up(&cp->bufferwait);
+}
+
+/* Setup a B channel transfer mode */
+static void auerisdn_bmode(struct auerisdnbc *bc, unsigned int mode)
+{
+	struct auerswald *cp = bc->cp;
+	struct auerbuf *bp;
+	int ret;
+
+	/* don't allow activation on disconnect */
+	if (cp->disconnecting) {
+		mode = L1_MODE_NULL;
+
+		/* Else check if something changed */
+	} else if (bc->mode != mode) {
+		if ((mode != L1_MODE_NULL) && (mode != L1_MODE_TRANS)) {
+			/* init RX hdlc decoder */
+			dbg("rcv init");
+			isdnhdlc_rcv_init(&bc->inp_hdlc_state, 0);
+			/* init TX hdlc decoder */
+			dbg("out init");
+			isdnhdlc_out_init(&bc->outp_hdlc_state, 0, 0);
+		}
+		/* stop ASAP */
+		if (mode == L1_MODE_NULL)
+			bc->mode = mode;
+		if ((bc->mode == L1_MODE_NULL) || (mode == L1_MODE_NULL)) {
+			/* Activation or deactivation required */
+
+			/* get a buffer for the command */
+			bp = auerbuf_getbuf(&cp->bufctl);
+			/* if no buffer available: can't change the mode */
+			if (!bp) {
+				err("auerisdn_bmode: no data buffer available");
+				return;
+			}
+
+			/* fill the control message */
+			bp->dr->bRequestType = AUT_WREQ;
+			bp->dr->bRequest = AUV_CHANNELCTL;
+			if (mode != L1_MODE_NULL)
+				bp->dr->wValue = cpu_to_le16(1);
+			else
+				bp->dr->wValue = cpu_to_le16(0);
+			bp->dr->wIndex =
+			    cpu_to_le16(AUH_B1CHANNEL + bc->channel);
+			bp->dr->wLength = cpu_to_le16(0);
+			*bp->bufp = mode;
+			FILL_CONTROL_URB(bp->urbp, cp->usbdev,
+					 usb_sndctrlpipe(cp->usbdev, 0),
+					 (unsigned char *) bp->dr,
+					 bp->bufp, 0,
+					 (usb_complete_t)
+					 auerisdn_bmode_complete, bp);
+
+			/* submit the control msg */
+			ret =
+			    auerchain_submit_urb(&cp->controlchain,
+						 bp->urbp);
+			if (ret) {
+				bp->urbp->status = ret;
+				auerisdn_bmode_complete(bp->urbp);
+			}
+			return;
+		}
+	}
+	/* new mode is set */
+	bc->mode = mode;
+
+	/* send confirmation to L2 */
+	auerisdn_bconf(bc);
+}
+
+/* B-channel transfer function L2->L1 */
+void auerisdn_b_l2l1(struct hisax_if *ifc, int pr, void *arg,
+		     unsigned int channel)
+{
+	struct auerhisax *ahp;
+	struct auerisdnbc *bc;
+	struct auerswald *cp;
+	struct sk_buff *skb;
+	unsigned long flags;
+	int mode;
+
+	cp = NULL;
+	ahp = (struct auerhisax *) ifc->priv;
+	if (ahp)
+		cp = ahp->cp;
+	if (cp && !cp->disconnecting) {
+		/* normal execution */
+		bc = &cp->isdn.bc[channel];
+		switch (pr) {
+		case PH_ACTIVATE | REQUEST:	/* activation request */
+			mode = (int) arg;	/* one of the L1_MODE constants */
+			dbg("B%d, PH_ACTIVATE_REQUEST Mode = %d",
+			    bc->channel + 1, mode);
+			auerisdn_bmode(bc, mode);
+			break;
+		case PH_DEACTIVATE | REQUEST:	/* deactivation request */
+			dbg("B%d, PH_DEACTIVATE_REQUEST", bc->channel + 1);
+			auerisdn_bmode(bc, L1_MODE_NULL);
+			break;
+		case PH_DATA | REQUEST:	/* Transmit data request */
+			skb = (struct sk_buff *) arg;
+			spin_lock_irqsave(&bc->txskb_lock, flags);
+			if (bc->txskb) {
+				err("Overflow in B channel TX");
+				skb_pull(skb, skb->len);
+				dev_kfree_skb_any(skb);
+			} else {
+				if (cp->disconnecting
+				    || (bc->mode == L1_MODE_NULL)) {
+					skb_pull(skb, skb->len);
+					spin_unlock_irqrestore(&bc->
+							       txskb_lock,
+							       flags);
+					auerisdn_b_l1l2(bc,
+							PH_DATA | CONFIRM,
+							(void *) skb->
+							truesize);
+					dev_kfree_skb_any(skb);
+					goto next;
+				} else
+					bc->txskb = skb;
+			}
+			spin_unlock_irqrestore(&bc->txskb_lock, flags);
+		      next:break;
+		default:
+			warn("pr %#x\n", pr);
+			break;
+		}
+	} else {
+		/* hisax interface is down */
+		switch (pr) {
+		case PH_ACTIVATE | REQUEST:	/* activation request */
+			dbg("B channel: PH_ACTIVATE | REQUEST with interface down");
+			/* don't answer this request! Endless... */
+			break;
+		case PH_DEACTIVATE | REQUEST:	/* deactivation request */
+			dbg("B channel: PH_DEACTIVATE | REQUEST with interface down");
+			ifc->l1l2(ifc, PH_DEACTIVATE | INDICATION, NULL);
+			break;
+		case PH_DATA | REQUEST:	/* Transmit data request */
+			dbg("B channel: PH_DATA | REQUEST with interface down");
+			skb = (struct sk_buff *) arg;
+			/* free data buffer */
+			if (skb) {
+				skb_pull(skb, skb->len);
+				dev_kfree_skb_any(skb);
+			}
+			/* send confirmation back to layer 2 */
+			ifc->l1l2(ifc, PH_DATA | CONFIRM, NULL);
+			break;
+		default:
+			warn("pr %#x\n", pr);
+			break;
+		}
+	}
+}
+
+/* Completion handler for B channel input endpoint */
+void auerisdn_intbi_complete(struct urb *urb)
+{
+	unsigned int bytecount;
+	unsigned char *ucp;
+	int channel;
+	unsigned int syncbit;
+	unsigned int syncdata;
+	struct auerisdnbc *bc;
+	struct sk_buff *skb;
+	int count;
+	int status;
+	struct auerswald *cp = (struct auerswald *) urb->context;
+	/* do not respond to an error condition */
+	if (urb->status != 0) {
+		dbg("nonzero URB status = %d", urb->status);
+		return;
+	}
+	if (cp->disconnecting)
+		return;
+
+	/* Parse and extract the header information */
+	bytecount = urb->actual_length;
+	ucp = cp->isdn.intbi_bufp;
+	if (!bytecount)
+		return;		/* no data */
+	channel = *ucp & AUH_TYPEMASK;
+	syncbit = *ucp & AUH_SYNC;
+	ucp++;
+	bytecount--;
+	channel -= AUH_B1CHANNEL;
+	if (channel < 0)
+		return;		/* unknown data channel, no B1,B2 */
+	if (channel >= AUISDN_BCHANNELS)
+		return;		/* unknown data channel, no B1,B2 */
+	bc = &cp->isdn.bc[channel];
+	if (!bytecount)
+		return;
+	/* Calculate amount of bytes which are free in tx device buffer */
+	bc->txfree = ((255 - *ucp++) * bc->ofsize) / 256;
+	/* dbg ("%d Bytes free in TX buffer", bc->txfree); */
+	bytecount--;
+
+	/* Next Byte: TX sync information */
+	if (syncbit) {
+		if (!bytecount)
+			goto int_tx;
+		syncdata = *ucp++;
+		dbg("Sync data = %d", syncdata);
+		bytecount--;
+	}
+	/* The rest of the paket is plain data */
+	if (!bytecount)
+		goto int_tx;
+	/* dump ("RX Data is:", ucp, bytecount); */
+
+	/* Send B channel data to upper layers */
+	while (bytecount > 0) {
+		if (bc->mode == L1_MODE_NULL) {
+			/* skip the data. Nobody needs them */
+			status = 0;
+			bytecount = 0;
+		} else if (bc->mode == L1_MODE_TRANS) {
+			{	/* swap rx bytes */
+				register unsigned char *dest = bc->rxbuf;
+				status = bytecount;
+				for (; bytecount; bytecount--)
+					*dest++ =
+					    isdnhdlc_bit_rev_tab[*ucp++];
+			}
+
+		} else {
+			status = isdnhdlc_decode(&bc->inp_hdlc_state, ucp,
+						 bytecount, &count,
+						 bc->rxbuf, AUISDN_RXSIZE);
+			ucp += count;
+			bytecount -= count;
+		}
+		if (status > 0) {
+			/* Good frame received */
+			if (!(skb = dev_alloc_skb(status))) {
+				warn("receive out of memory");
+				break;
+			}
+			memcpy(skb_put(skb, status), bc->rxbuf, status);
+			/* dump ("HDLC Paket", bc->rxbuf, status); */
+			auerisdn_b_l1l2(bc, PH_DATA | INDICATION, skb);
+			/* these errors may actually happen at the start of a connection! */
+		} else if (status == -HDLC_CRC_ERROR) {
+			dbg("CRC error");
+		} else if (status == -HDLC_FRAMING_ERROR) {
+			dbg("framing error");
+		} else if (status == -HDLC_LENGTH_ERROR) {
+			dbg("length error");
+		}
+	}
+
+      int_tx:			/* serve the outgoing B channel */
+	auerisdn_bserv(&cp->isdn);
+}
+
+/* Stop the B channel activity. The device is disconnecting */
+/* This function is called after cp->disconnecting is true */
+unsigned int auerisdn_b_disconnect(struct auerswald *cp)
+{
+	unsigned int u;
+	struct auerisdnbc *bc;
+	unsigned int result = 0;
+
+	/* Close the B channels */
+	for (u = 0; u < AUISDN_BCHANNELS; u++) {
+		bc = &cp->isdn.bc[u];
+		if (bc->mode != L1_MODE_NULL) {	/* B channel is active */
+			auerisdn_bmode(bc, L1_MODE_NULL);
+			result = 1;
+		}
+	}
+	/* return 1 if there is B channel traffic */
+	return result;
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)