| /* | 
 |  * | 
 |  * 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(&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; | 
 | 	skb_queue_purge(&ch->rqueue); | 
 | 	ch->rcount = 0; | 
 | } | 
 | EXPORT_SYMBOL(mISDN_clear_bchannel); | 
 |  | 
 | void | 
 | mISDN_freebchannel(struct bchannel *ch) | 
 | { | 
 | 	cancel_work_sync(&ch->workq); | 
 | 	mISDN_clear_bchannel(ch); | 
 | } | 
 | 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); |