/*****************************************************************************/
/*
* 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;
}