c27b46e7f1
MISDN_CTRL_RX_OFF is a meachanism to discard RX data in the driver if the data is not needed by the application. It can be used when playing mesages, but not recording or with unidirectional protocols. Signed-off-by: Karsten Keil <kkeil@linux-pingi.de> Signed-off-by: David S. Miller <davem@davemloft.net>
528 lines
12 KiB
C
528 lines
12 KiB
C
/*
|
|
*
|
|
* Author Karsten Keil <kkeil@novell.com>
|
|
*
|
|
* Copyright 2008 by Karsten Keil <kkeil@novell.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mISDNhw.h>
|
|
|
|
static void
|
|
dchannel_bh(struct work_struct *ws)
|
|
{
|
|
struct dchannel *dch = container_of(ws, struct dchannel, workq);
|
|
struct sk_buff *skb;
|
|
int err;
|
|
|
|
if (test_and_clear_bit(FLG_RECVQUEUE, &dch->Flags)) {
|
|
while ((skb = skb_dequeue(&dch->rqueue))) {
|
|
if (likely(dch->dev.D.peer)) {
|
|
err = dch->dev.D.recv(dch->dev.D.peer, skb);
|
|
if (err)
|
|
dev_kfree_skb(skb);
|
|
} else
|
|
dev_kfree_skb(skb);
|
|
}
|
|
}
|
|
if (test_and_clear_bit(FLG_PHCHANGE, &dch->Flags)) {
|
|
if (dch->phfunc)
|
|
dch->phfunc(dch);
|
|
}
|
|
}
|
|
|
|
static void
|
|
bchannel_bh(struct work_struct *ws)
|
|
{
|
|
struct bchannel *bch = container_of(ws, struct bchannel, workq);
|
|
struct sk_buff *skb;
|
|
int err;
|
|
|
|
if (test_and_clear_bit(FLG_RECVQUEUE, &bch->Flags)) {
|
|
while ((skb = skb_dequeue(&bch->rqueue))) {
|
|
bch->rcount--;
|
|
if (likely(bch->ch.peer)) {
|
|
err = bch->ch.recv(bch->ch.peer, skb);
|
|
if (err)
|
|
dev_kfree_skb(skb);
|
|
} else
|
|
dev_kfree_skb(skb);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
mISDN_initdchannel(struct dchannel *ch, int maxlen, void *phf)
|
|
{
|
|
test_and_set_bit(FLG_HDLC, &ch->Flags);
|
|
ch->maxlen = maxlen;
|
|
ch->hw = NULL;
|
|
ch->rx_skb = NULL;
|
|
ch->tx_skb = NULL;
|
|
ch->tx_idx = 0;
|
|
ch->phfunc = phf;
|
|
skb_queue_head_init(&ch->squeue);
|
|
skb_queue_head_init(&ch->rqueue);
|
|
INIT_LIST_HEAD(&ch->dev.bchannels);
|
|
INIT_WORK(&ch->workq, dchannel_bh);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_initdchannel);
|
|
|
|
int
|
|
mISDN_initbchannel(struct bchannel *ch, unsigned short maxlen,
|
|
unsigned short minlen)
|
|
{
|
|
ch->Flags = 0;
|
|
ch->minlen = minlen;
|
|
ch->next_minlen = minlen;
|
|
ch->init_minlen = minlen;
|
|
ch->maxlen = maxlen;
|
|
ch->next_maxlen = maxlen;
|
|
ch->init_maxlen = maxlen;
|
|
ch->hw = NULL;
|
|
ch->rx_skb = NULL;
|
|
ch->tx_skb = NULL;
|
|
ch->tx_idx = 0;
|
|
skb_queue_head_init(&ch->rqueue);
|
|
ch->rcount = 0;
|
|
ch->next_skb = NULL;
|
|
INIT_WORK(&ch->workq, bchannel_bh);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_initbchannel);
|
|
|
|
int
|
|
mISDN_freedchannel(struct dchannel *ch)
|
|
{
|
|
if (ch->tx_skb) {
|
|
dev_kfree_skb(ch->tx_skb);
|
|
ch->tx_skb = NULL;
|
|
}
|
|
if (ch->rx_skb) {
|
|
dev_kfree_skb(ch->rx_skb);
|
|
ch->rx_skb = NULL;
|
|
}
|
|
skb_queue_purge(&ch->squeue);
|
|
skb_queue_purge(&ch->rqueue);
|
|
flush_work_sync(&ch->workq);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_freedchannel);
|
|
|
|
void
|
|
mISDN_clear_bchannel(struct bchannel *ch)
|
|
{
|
|
if (ch->tx_skb) {
|
|
dev_kfree_skb(ch->tx_skb);
|
|
ch->tx_skb = NULL;
|
|
}
|
|
ch->tx_idx = 0;
|
|
if (ch->rx_skb) {
|
|
dev_kfree_skb(ch->rx_skb);
|
|
ch->rx_skb = NULL;
|
|
}
|
|
if (ch->next_skb) {
|
|
dev_kfree_skb(ch->next_skb);
|
|
ch->next_skb = NULL;
|
|
}
|
|
test_and_clear_bit(FLG_TX_BUSY, &ch->Flags);
|
|
test_and_clear_bit(FLG_TX_NEXT, &ch->Flags);
|
|
test_and_clear_bit(FLG_ACTIVE, &ch->Flags);
|
|
test_and_clear_bit(FLG_FILLEMPTY, &ch->Flags);
|
|
test_and_clear_bit(FLG_TX_EMPTY, &ch->Flags);
|
|
test_and_clear_bit(FLG_RX_OFF, &ch->Flags);
|
|
ch->dropcnt = 0;
|
|
ch->minlen = ch->init_minlen;
|
|
ch->next_minlen = ch->init_minlen;
|
|
ch->maxlen = ch->init_maxlen;
|
|
ch->next_maxlen = ch->init_maxlen;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_clear_bchannel);
|
|
|
|
int
|
|
mISDN_freebchannel(struct bchannel *ch)
|
|
{
|
|
mISDN_clear_bchannel(ch);
|
|
skb_queue_purge(&ch->rqueue);
|
|
ch->rcount = 0;
|
|
flush_work_sync(&ch->workq);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_freebchannel);
|
|
|
|
int
|
|
mISDN_ctrl_bchannel(struct bchannel *bch, struct mISDN_ctrl_req *cq)
|
|
{
|
|
int ret = 0;
|
|
|
|
switch (cq->op) {
|
|
case MISDN_CTRL_GETOP:
|
|
cq->op = MISDN_CTRL_RX_BUFFER | MISDN_CTRL_FILL_EMPTY |
|
|
MISDN_CTRL_RX_OFF;
|
|
break;
|
|
case MISDN_CTRL_FILL_EMPTY:
|
|
if (cq->p1) {
|
|
memset(bch->fill, cq->p2 & 0xff, MISDN_BCH_FILL_SIZE);
|
|
test_and_set_bit(FLG_FILLEMPTY, &bch->Flags);
|
|
} else {
|
|
test_and_clear_bit(FLG_FILLEMPTY, &bch->Flags);
|
|
}
|
|
break;
|
|
case MISDN_CTRL_RX_OFF:
|
|
/* read back dropped byte count */
|
|
cq->p2 = bch->dropcnt;
|
|
if (cq->p1)
|
|
test_and_set_bit(FLG_RX_OFF, &bch->Flags);
|
|
else
|
|
test_and_clear_bit(FLG_RX_OFF, &bch->Flags);
|
|
bch->dropcnt = 0;
|
|
break;
|
|
case MISDN_CTRL_RX_BUFFER:
|
|
if (cq->p2 > MISDN_CTRL_RX_SIZE_IGNORE)
|
|
bch->next_maxlen = cq->p2;
|
|
if (cq->p1 > MISDN_CTRL_RX_SIZE_IGNORE)
|
|
bch->next_minlen = cq->p1;
|
|
/* we return the old values */
|
|
cq->p1 = bch->minlen;
|
|
cq->p2 = bch->maxlen;
|
|
break;
|
|
default:
|
|
pr_info("mISDN unhandled control %x operation\n", cq->op);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(mISDN_ctrl_bchannel);
|
|
|
|
static inline u_int
|
|
get_sapi_tei(u_char *p)
|
|
{
|
|
u_int sapi, tei;
|
|
|
|
sapi = *p >> 2;
|
|
tei = p[1] >> 1;
|
|
return sapi | (tei << 8);
|
|
}
|
|
|
|
void
|
|
recv_Dchannel(struct dchannel *dch)
|
|
{
|
|
struct mISDNhead *hh;
|
|
|
|
if (dch->rx_skb->len < 2) { /* at least 2 for sapi / tei */
|
|
dev_kfree_skb(dch->rx_skb);
|
|
dch->rx_skb = NULL;
|
|
return;
|
|
}
|
|
hh = mISDN_HEAD_P(dch->rx_skb);
|
|
hh->prim = PH_DATA_IND;
|
|
hh->id = get_sapi_tei(dch->rx_skb->data);
|
|
skb_queue_tail(&dch->rqueue, dch->rx_skb);
|
|
dch->rx_skb = NULL;
|
|
schedule_event(dch, FLG_RECVQUEUE);
|
|
}
|
|
EXPORT_SYMBOL(recv_Dchannel);
|
|
|
|
void
|
|
recv_Echannel(struct dchannel *ech, struct dchannel *dch)
|
|
{
|
|
struct mISDNhead *hh;
|
|
|
|
if (ech->rx_skb->len < 2) { /* at least 2 for sapi / tei */
|
|
dev_kfree_skb(ech->rx_skb);
|
|
ech->rx_skb = NULL;
|
|
return;
|
|
}
|
|
hh = mISDN_HEAD_P(ech->rx_skb);
|
|
hh->prim = PH_DATA_E_IND;
|
|
hh->id = get_sapi_tei(ech->rx_skb->data);
|
|
skb_queue_tail(&dch->rqueue, ech->rx_skb);
|
|
ech->rx_skb = NULL;
|
|
schedule_event(dch, FLG_RECVQUEUE);
|
|
}
|
|
EXPORT_SYMBOL(recv_Echannel);
|
|
|
|
void
|
|
recv_Bchannel(struct bchannel *bch, unsigned int id, bool force)
|
|
{
|
|
struct mISDNhead *hh;
|
|
|
|
/* if allocation did fail upper functions still may call us */
|
|
if (unlikely(!bch->rx_skb))
|
|
return;
|
|
if (unlikely(!bch->rx_skb->len)) {
|
|
/* we have no data to send - this may happen after recovery
|
|
* from overflow or too small allocation.
|
|
* We need to free the buffer here */
|
|
dev_kfree_skb(bch->rx_skb);
|
|
bch->rx_skb = NULL;
|
|
} else {
|
|
if (test_bit(FLG_TRANSPARENT, &bch->Flags) &&
|
|
(bch->rx_skb->len < bch->minlen) && !force)
|
|
return;
|
|
hh = mISDN_HEAD_P(bch->rx_skb);
|
|
hh->prim = PH_DATA_IND;
|
|
hh->id = id;
|
|
if (bch->rcount >= 64) {
|
|
printk(KERN_WARNING
|
|
"B%d receive queue overflow - flushing!\n",
|
|
bch->nr);
|
|
skb_queue_purge(&bch->rqueue);
|
|
}
|
|
bch->rcount++;
|
|
skb_queue_tail(&bch->rqueue, bch->rx_skb);
|
|
bch->rx_skb = NULL;
|
|
schedule_event(bch, FLG_RECVQUEUE);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(recv_Bchannel);
|
|
|
|
void
|
|
recv_Dchannel_skb(struct dchannel *dch, struct sk_buff *skb)
|
|
{
|
|
skb_queue_tail(&dch->rqueue, skb);
|
|
schedule_event(dch, FLG_RECVQUEUE);
|
|
}
|
|
EXPORT_SYMBOL(recv_Dchannel_skb);
|
|
|
|
void
|
|
recv_Bchannel_skb(struct bchannel *bch, struct sk_buff *skb)
|
|
{
|
|
if (bch->rcount >= 64) {
|
|
printk(KERN_WARNING "B-channel %p receive queue overflow, "
|
|
"flushing!\n", bch);
|
|
skb_queue_purge(&bch->rqueue);
|
|
bch->rcount = 0;
|
|
}
|
|
bch->rcount++;
|
|
skb_queue_tail(&bch->rqueue, skb);
|
|
schedule_event(bch, FLG_RECVQUEUE);
|
|
}
|
|
EXPORT_SYMBOL(recv_Bchannel_skb);
|
|
|
|
static void
|
|
confirm_Dsend(struct dchannel *dch)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
skb = _alloc_mISDN_skb(PH_DATA_CNF, mISDN_HEAD_ID(dch->tx_skb),
|
|
0, NULL, GFP_ATOMIC);
|
|
if (!skb) {
|
|
printk(KERN_ERR "%s: no skb id %x\n", __func__,
|
|
mISDN_HEAD_ID(dch->tx_skb));
|
|
return;
|
|
}
|
|
skb_queue_tail(&dch->rqueue, skb);
|
|
schedule_event(dch, FLG_RECVQUEUE);
|
|
}
|
|
|
|
int
|
|
get_next_dframe(struct dchannel *dch)
|
|
{
|
|
dch->tx_idx = 0;
|
|
dch->tx_skb = skb_dequeue(&dch->squeue);
|
|
if (dch->tx_skb) {
|
|
confirm_Dsend(dch);
|
|
return 1;
|
|
}
|
|
dch->tx_skb = NULL;
|
|
test_and_clear_bit(FLG_TX_BUSY, &dch->Flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(get_next_dframe);
|
|
|
|
static void
|
|
confirm_Bsend(struct bchannel *bch)
|
|
{
|
|
struct sk_buff *skb;
|
|
|
|
if (bch->rcount >= 64) {
|
|
printk(KERN_WARNING "B-channel %p receive queue overflow, "
|
|
"flushing!\n", bch);
|
|
skb_queue_purge(&bch->rqueue);
|
|
bch->rcount = 0;
|
|
}
|
|
skb = _alloc_mISDN_skb(PH_DATA_CNF, mISDN_HEAD_ID(bch->tx_skb),
|
|
0, NULL, GFP_ATOMIC);
|
|
if (!skb) {
|
|
printk(KERN_ERR "%s: no skb id %x\n", __func__,
|
|
mISDN_HEAD_ID(bch->tx_skb));
|
|
return;
|
|
}
|
|
bch->rcount++;
|
|
skb_queue_tail(&bch->rqueue, skb);
|
|
schedule_event(bch, FLG_RECVQUEUE);
|
|
}
|
|
|
|
int
|
|
get_next_bframe(struct bchannel *bch)
|
|
{
|
|
bch->tx_idx = 0;
|
|
if (test_bit(FLG_TX_NEXT, &bch->Flags)) {
|
|
bch->tx_skb = bch->next_skb;
|
|
if (bch->tx_skb) {
|
|
bch->next_skb = NULL;
|
|
test_and_clear_bit(FLG_TX_NEXT, &bch->Flags);
|
|
/* confirm imediately to allow next data */
|
|
confirm_Bsend(bch);
|
|
return 1;
|
|
} else {
|
|
test_and_clear_bit(FLG_TX_NEXT, &bch->Flags);
|
|
printk(KERN_WARNING "B TX_NEXT without skb\n");
|
|
}
|
|
}
|
|
bch->tx_skb = NULL;
|
|
test_and_clear_bit(FLG_TX_BUSY, &bch->Flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(get_next_bframe);
|
|
|
|
void
|
|
queue_ch_frame(struct mISDNchannel *ch, u_int pr, int id, struct sk_buff *skb)
|
|
{
|
|
struct mISDNhead *hh;
|
|
|
|
if (!skb) {
|
|
_queue_data(ch, pr, id, 0, NULL, GFP_ATOMIC);
|
|
} else {
|
|
if (ch->peer) {
|
|
hh = mISDN_HEAD_P(skb);
|
|
hh->prim = pr;
|
|
hh->id = id;
|
|
if (!ch->recv(ch->peer, skb))
|
|
return;
|
|
}
|
|
dev_kfree_skb(skb);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(queue_ch_frame);
|
|
|
|
int
|
|
dchannel_senddata(struct dchannel *ch, struct sk_buff *skb)
|
|
{
|
|
/* check oversize */
|
|
if (skb->len <= 0) {
|
|
printk(KERN_WARNING "%s: skb too small\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (skb->len > ch->maxlen) {
|
|
printk(KERN_WARNING "%s: skb too large(%d/%d)\n",
|
|
__func__, skb->len, ch->maxlen);
|
|
return -EINVAL;
|
|
}
|
|
/* HW lock must be obtained */
|
|
if (test_and_set_bit(FLG_TX_BUSY, &ch->Flags)) {
|
|
skb_queue_tail(&ch->squeue, skb);
|
|
return 0;
|
|
} else {
|
|
/* write to fifo */
|
|
ch->tx_skb = skb;
|
|
ch->tx_idx = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(dchannel_senddata);
|
|
|
|
int
|
|
bchannel_senddata(struct bchannel *ch, struct sk_buff *skb)
|
|
{
|
|
|
|
/* check oversize */
|
|
if (skb->len <= 0) {
|
|
printk(KERN_WARNING "%s: skb too small\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
if (skb->len > ch->maxlen) {
|
|
printk(KERN_WARNING "%s: skb too large(%d/%d)\n",
|
|
__func__, skb->len, ch->maxlen);
|
|
return -EINVAL;
|
|
}
|
|
/* HW lock must be obtained */
|
|
/* check for pending next_skb */
|
|
if (ch->next_skb) {
|
|
printk(KERN_WARNING
|
|
"%s: next_skb exist ERROR (skb->len=%d next_skb->len=%d)\n",
|
|
__func__, skb->len, ch->next_skb->len);
|
|
return -EBUSY;
|
|
}
|
|
if (test_and_set_bit(FLG_TX_BUSY, &ch->Flags)) {
|
|
test_and_set_bit(FLG_TX_NEXT, &ch->Flags);
|
|
ch->next_skb = skb;
|
|
return 0;
|
|
} else {
|
|
/* write to fifo */
|
|
ch->tx_skb = skb;
|
|
ch->tx_idx = 0;
|
|
confirm_Bsend(ch);
|
|
return 1;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(bchannel_senddata);
|
|
|
|
/* The function allocates a new receive skb on demand with a size for the
|
|
* requirements of the current protocol. It returns the tailroom of the
|
|
* receive skb or an error.
|
|
*/
|
|
int
|
|
bchannel_get_rxbuf(struct bchannel *bch, int reqlen)
|
|
{
|
|
int len;
|
|
|
|
if (bch->rx_skb) {
|
|
len = skb_tailroom(bch->rx_skb);
|
|
if (len < reqlen) {
|
|
pr_warning("B%d no space for %d (only %d) bytes\n",
|
|
bch->nr, reqlen, len);
|
|
if (test_bit(FLG_TRANSPARENT, &bch->Flags)) {
|
|
/* send what we have now and try a new buffer */
|
|
recv_Bchannel(bch, 0, true);
|
|
} else {
|
|
/* on HDLC we have to drop too big frames */
|
|
return -EMSGSIZE;
|
|
}
|
|
} else {
|
|
return len;
|
|
}
|
|
}
|
|
/* update current min/max length first */
|
|
if (unlikely(bch->maxlen != bch->next_maxlen))
|
|
bch->maxlen = bch->next_maxlen;
|
|
if (unlikely(bch->minlen != bch->next_minlen))
|
|
bch->minlen = bch->next_minlen;
|
|
if (unlikely(reqlen > bch->maxlen))
|
|
return -EMSGSIZE;
|
|
if (test_bit(FLG_TRANSPARENT, &bch->Flags)) {
|
|
if (reqlen >= bch->minlen) {
|
|
len = reqlen;
|
|
} else {
|
|
len = 2 * bch->minlen;
|
|
if (len > bch->maxlen)
|
|
len = bch->maxlen;
|
|
}
|
|
} else {
|
|
/* with HDLC we do not know the length yet */
|
|
len = bch->maxlen;
|
|
}
|
|
bch->rx_skb = mI_alloc_skb(len, GFP_ATOMIC);
|
|
if (!bch->rx_skb) {
|
|
pr_warning("B%d receive no memory for %d bytes\n",
|
|
bch->nr, len);
|
|
len = -ENOMEM;
|
|
}
|
|
return len;
|
|
}
|
|
EXPORT_SYMBOL(bchannel_get_rxbuf);
|