diff options
Diffstat (limited to 'drivers/isdn/mISDN/hwchannel.c')
-rw-r--r-- | drivers/isdn/mISDN/hwchannel.c | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/drivers/isdn/mISDN/hwchannel.c b/drivers/isdn/mISDN/hwchannel.c new file mode 100644 index 000000000000..2596fba4e614 --- /dev/null +++ b/drivers/isdn/mISDN/hwchannel.c | |||
@@ -0,0 +1,365 @@ | |||
1 | /* | ||
2 | * | ||
3 | * Author Karsten Keil <kkeil@novell.com> | ||
4 | * | ||
5 | * Copyright 2008 by Karsten Keil <kkeil@novell.com> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License version 2 as | ||
9 | * published by the Free Software Foundation. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/mISDNhw.h> | ||
20 | |||
21 | static void | ||
22 | dchannel_bh(struct work_struct *ws) | ||
23 | { | ||
24 | struct dchannel *dch = container_of(ws, struct dchannel, workq); | ||
25 | struct sk_buff *skb; | ||
26 | int err; | ||
27 | |||
28 | if (test_and_clear_bit(FLG_RECVQUEUE, &dch->Flags)) { | ||
29 | while ((skb = skb_dequeue(&dch->rqueue))) { | ||
30 | if (likely(dch->dev.D.peer)) { | ||
31 | err = dch->dev.D.recv(dch->dev.D.peer, skb); | ||
32 | if (err) | ||
33 | dev_kfree_skb(skb); | ||
34 | } else | ||
35 | dev_kfree_skb(skb); | ||
36 | } | ||
37 | } | ||
38 | if (test_and_clear_bit(FLG_PHCHANGE, &dch->Flags)) { | ||
39 | if (dch->phfunc) | ||
40 | dch->phfunc(dch); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | static void | ||
45 | bchannel_bh(struct work_struct *ws) | ||
46 | { | ||
47 | struct bchannel *bch = container_of(ws, struct bchannel, workq); | ||
48 | struct sk_buff *skb; | ||
49 | int err; | ||
50 | |||
51 | if (test_and_clear_bit(FLG_RECVQUEUE, &bch->Flags)) { | ||
52 | while ((skb = skb_dequeue(&bch->rqueue))) { | ||
53 | if (bch->rcount >= 64) | ||
54 | printk(KERN_WARNING "B-channel %p receive " | ||
55 | "queue if full, but empties...\n", bch); | ||
56 | bch->rcount--; | ||
57 | if (likely(bch->ch.peer)) { | ||
58 | err = bch->ch.recv(bch->ch.peer, skb); | ||
59 | if (err) | ||
60 | dev_kfree_skb(skb); | ||
61 | } else | ||
62 | dev_kfree_skb(skb); | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | int | ||
68 | mISDN_initdchannel(struct dchannel *ch, int maxlen, void *phf) | ||
69 | { | ||
70 | test_and_set_bit(FLG_HDLC, &ch->Flags); | ||
71 | ch->maxlen = maxlen; | ||
72 | ch->hw = NULL; | ||
73 | ch->rx_skb = NULL; | ||
74 | ch->tx_skb = NULL; | ||
75 | ch->tx_idx = 0; | ||
76 | ch->phfunc = phf; | ||
77 | skb_queue_head_init(&ch->squeue); | ||
78 | skb_queue_head_init(&ch->rqueue); | ||
79 | INIT_LIST_HEAD(&ch->dev.bchannels); | ||
80 | INIT_WORK(&ch->workq, dchannel_bh); | ||
81 | return 0; | ||
82 | } | ||
83 | EXPORT_SYMBOL(mISDN_initdchannel); | ||
84 | |||
85 | int | ||
86 | mISDN_initbchannel(struct bchannel *ch, int maxlen) | ||
87 | { | ||
88 | ch->Flags = 0; | ||
89 | ch->maxlen = maxlen; | ||
90 | ch->hw = NULL; | ||
91 | ch->rx_skb = NULL; | ||
92 | ch->tx_skb = NULL; | ||
93 | ch->tx_idx = 0; | ||
94 | skb_queue_head_init(&ch->rqueue); | ||
95 | ch->rcount = 0; | ||
96 | ch->next_skb = NULL; | ||
97 | INIT_WORK(&ch->workq, bchannel_bh); | ||
98 | return 0; | ||
99 | } | ||
100 | EXPORT_SYMBOL(mISDN_initbchannel); | ||
101 | |||
102 | int | ||
103 | mISDN_freedchannel(struct dchannel *ch) | ||
104 | { | ||
105 | if (ch->tx_skb) { | ||
106 | dev_kfree_skb(ch->tx_skb); | ||
107 | ch->tx_skb = NULL; | ||
108 | } | ||
109 | if (ch->rx_skb) { | ||
110 | dev_kfree_skb(ch->rx_skb); | ||
111 | ch->rx_skb = NULL; | ||
112 | } | ||
113 | skb_queue_purge(&ch->squeue); | ||
114 | skb_queue_purge(&ch->rqueue); | ||
115 | flush_scheduled_work(); | ||
116 | return 0; | ||
117 | } | ||
118 | EXPORT_SYMBOL(mISDN_freedchannel); | ||
119 | |||
120 | int | ||
121 | mISDN_freebchannel(struct bchannel *ch) | ||
122 | { | ||
123 | if (ch->tx_skb) { | ||
124 | dev_kfree_skb(ch->tx_skb); | ||
125 | ch->tx_skb = NULL; | ||
126 | } | ||
127 | if (ch->rx_skb) { | ||
128 | dev_kfree_skb(ch->rx_skb); | ||
129 | ch->rx_skb = NULL; | ||
130 | } | ||
131 | if (ch->next_skb) { | ||
132 | dev_kfree_skb(ch->next_skb); | ||
133 | ch->next_skb = NULL; | ||
134 | } | ||
135 | skb_queue_purge(&ch->rqueue); | ||
136 | ch->rcount = 0; | ||
137 | flush_scheduled_work(); | ||
138 | return 0; | ||
139 | } | ||
140 | EXPORT_SYMBOL(mISDN_freebchannel); | ||
141 | |||
142 | static inline u_int | ||
143 | get_sapi_tei(u_char *p) | ||
144 | { | ||
145 | u_int sapi, tei; | ||
146 | |||
147 | sapi = *p >> 2; | ||
148 | tei = p[1] >> 1; | ||
149 | return sapi | (tei << 8); | ||
150 | } | ||
151 | |||
152 | void | ||
153 | recv_Dchannel(struct dchannel *dch) | ||
154 | { | ||
155 | struct mISDNhead *hh; | ||
156 | |||
157 | if (dch->rx_skb->len < 2) { /* at least 2 for sapi / tei */ | ||
158 | dev_kfree_skb(dch->rx_skb); | ||
159 | dch->rx_skb = NULL; | ||
160 | return; | ||
161 | } | ||
162 | hh = mISDN_HEAD_P(dch->rx_skb); | ||
163 | hh->prim = PH_DATA_IND; | ||
164 | hh->id = get_sapi_tei(dch->rx_skb->data); | ||
165 | skb_queue_tail(&dch->rqueue, dch->rx_skb); | ||
166 | dch->rx_skb = NULL; | ||
167 | schedule_event(dch, FLG_RECVQUEUE); | ||
168 | } | ||
169 | EXPORT_SYMBOL(recv_Dchannel); | ||
170 | |||
171 | void | ||
172 | recv_Bchannel(struct bchannel *bch) | ||
173 | { | ||
174 | struct mISDNhead *hh; | ||
175 | |||
176 | hh = mISDN_HEAD_P(bch->rx_skb); | ||
177 | hh->prim = PH_DATA_IND; | ||
178 | hh->id = MISDN_ID_ANY; | ||
179 | if (bch->rcount >= 64) { | ||
180 | dev_kfree_skb(bch->rx_skb); | ||
181 | bch->rx_skb = NULL; | ||
182 | return; | ||
183 | } | ||
184 | bch->rcount++; | ||
185 | skb_queue_tail(&bch->rqueue, bch->rx_skb); | ||
186 | bch->rx_skb = NULL; | ||
187 | schedule_event(bch, FLG_RECVQUEUE); | ||
188 | } | ||
189 | EXPORT_SYMBOL(recv_Bchannel); | ||
190 | |||
191 | void | ||
192 | recv_Dchannel_skb(struct dchannel *dch, struct sk_buff *skb) | ||
193 | { | ||
194 | skb_queue_tail(&dch->rqueue, skb); | ||
195 | schedule_event(dch, FLG_RECVQUEUE); | ||
196 | } | ||
197 | EXPORT_SYMBOL(recv_Dchannel_skb); | ||
198 | |||
199 | void | ||
200 | recv_Bchannel_skb(struct bchannel *bch, struct sk_buff *skb) | ||
201 | { | ||
202 | if (bch->rcount >= 64) { | ||
203 | dev_kfree_skb(skb); | ||
204 | return; | ||
205 | } | ||
206 | bch->rcount++; | ||
207 | skb_queue_tail(&bch->rqueue, skb); | ||
208 | schedule_event(bch, FLG_RECVQUEUE); | ||
209 | } | ||
210 | EXPORT_SYMBOL(recv_Bchannel_skb); | ||
211 | |||
212 | static void | ||
213 | confirm_Dsend(struct dchannel *dch) | ||
214 | { | ||
215 | struct sk_buff *skb; | ||
216 | |||
217 | skb = _alloc_mISDN_skb(PH_DATA_CNF, mISDN_HEAD_ID(dch->tx_skb), | ||
218 | 0, NULL, GFP_ATOMIC); | ||
219 | if (!skb) { | ||
220 | printk(KERN_ERR "%s: no skb id %x\n", __func__, | ||
221 | mISDN_HEAD_ID(dch->tx_skb)); | ||
222 | return; | ||
223 | } | ||
224 | skb_queue_tail(&dch->rqueue, skb); | ||
225 | schedule_event(dch, FLG_RECVQUEUE); | ||
226 | } | ||
227 | |||
228 | int | ||
229 | get_next_dframe(struct dchannel *dch) | ||
230 | { | ||
231 | dch->tx_idx = 0; | ||
232 | dch->tx_skb = skb_dequeue(&dch->squeue); | ||
233 | if (dch->tx_skb) { | ||
234 | confirm_Dsend(dch); | ||
235 | return 1; | ||
236 | } | ||
237 | dch->tx_skb = NULL; | ||
238 | test_and_clear_bit(FLG_TX_BUSY, &dch->Flags); | ||
239 | return 0; | ||
240 | } | ||
241 | EXPORT_SYMBOL(get_next_dframe); | ||
242 | |||
243 | void | ||
244 | confirm_Bsend(struct bchannel *bch) | ||
245 | { | ||
246 | struct sk_buff *skb; | ||
247 | |||
248 | if (bch->rcount >= 64) | ||
249 | return; | ||
250 | skb = _alloc_mISDN_skb(PH_DATA_CNF, mISDN_HEAD_ID(bch->tx_skb), | ||
251 | 0, NULL, GFP_ATOMIC); | ||
252 | if (!skb) { | ||
253 | printk(KERN_ERR "%s: no skb id %x\n", __func__, | ||
254 | mISDN_HEAD_ID(bch->tx_skb)); | ||
255 | return; | ||
256 | } | ||
257 | bch->rcount++; | ||
258 | skb_queue_tail(&bch->rqueue, skb); | ||
259 | schedule_event(bch, FLG_RECVQUEUE); | ||
260 | } | ||
261 | EXPORT_SYMBOL(confirm_Bsend); | ||
262 | |||
263 | int | ||
264 | get_next_bframe(struct bchannel *bch) | ||
265 | { | ||
266 | bch->tx_idx = 0; | ||
267 | if (test_bit(FLG_TX_NEXT, &bch->Flags)) { | ||
268 | bch->tx_skb = bch->next_skb; | ||
269 | if (bch->tx_skb) { | ||
270 | bch->next_skb = NULL; | ||
271 | test_and_clear_bit(FLG_TX_NEXT, &bch->Flags); | ||
272 | if (!test_bit(FLG_TRANSPARENT, &bch->Flags)) | ||
273 | confirm_Bsend(bch); /* not for transparent */ | ||
274 | return 1; | ||
275 | } else { | ||
276 | test_and_clear_bit(FLG_TX_NEXT, &bch->Flags); | ||
277 | printk(KERN_WARNING "B TX_NEXT without skb\n"); | ||
278 | } | ||
279 | } | ||
280 | bch->tx_skb = NULL; | ||
281 | test_and_clear_bit(FLG_TX_BUSY, &bch->Flags); | ||
282 | return 0; | ||
283 | } | ||
284 | EXPORT_SYMBOL(get_next_bframe); | ||
285 | |||
286 | void | ||
287 | queue_ch_frame(struct mISDNchannel *ch, u_int pr, int id, struct sk_buff *skb) | ||
288 | { | ||
289 | struct mISDNhead *hh; | ||
290 | |||
291 | if (!skb) { | ||
292 | _queue_data(ch, pr, id, 0, NULL, GFP_ATOMIC); | ||
293 | } else { | ||
294 | if (ch->peer) { | ||
295 | hh = mISDN_HEAD_P(skb); | ||
296 | hh->prim = pr; | ||
297 | hh->id = id; | ||
298 | if (!ch->recv(ch->peer, skb)) | ||
299 | return; | ||
300 | } | ||
301 | dev_kfree_skb(skb); | ||
302 | } | ||
303 | } | ||
304 | EXPORT_SYMBOL(queue_ch_frame); | ||
305 | |||
306 | int | ||
307 | dchannel_senddata(struct dchannel *ch, struct sk_buff *skb) | ||
308 | { | ||
309 | /* check oversize */ | ||
310 | if (skb->len <= 0) { | ||
311 | printk(KERN_WARNING "%s: skb too small\n", __func__); | ||
312 | return -EINVAL; | ||
313 | } | ||
314 | if (skb->len > ch->maxlen) { | ||
315 | printk(KERN_WARNING "%s: skb too large(%d/%d)\n", | ||
316 | __func__, skb->len, ch->maxlen); | ||
317 | return -EINVAL; | ||
318 | } | ||
319 | /* HW lock must be obtained */ | ||
320 | if (test_and_set_bit(FLG_TX_BUSY, &ch->Flags)) { | ||
321 | skb_queue_tail(&ch->squeue, skb); | ||
322 | return 0; | ||
323 | } else { | ||
324 | /* write to fifo */ | ||
325 | ch->tx_skb = skb; | ||
326 | ch->tx_idx = 0; | ||
327 | return 1; | ||
328 | } | ||
329 | } | ||
330 | EXPORT_SYMBOL(dchannel_senddata); | ||
331 | |||
332 | int | ||
333 | bchannel_senddata(struct bchannel *ch, struct sk_buff *skb) | ||
334 | { | ||
335 | |||
336 | /* check oversize */ | ||
337 | if (skb->len <= 0) { | ||
338 | printk(KERN_WARNING "%s: skb too small\n", __func__); | ||
339 | return -EINVAL; | ||
340 | } | ||
341 | if (skb->len > ch->maxlen) { | ||
342 | printk(KERN_WARNING "%s: skb too large(%d/%d)\n", | ||
343 | __func__, skb->len, ch->maxlen); | ||
344 | return -EINVAL; | ||
345 | } | ||
346 | /* HW lock must be obtained */ | ||
347 | /* check for pending next_skb */ | ||
348 | if (ch->next_skb) { | ||
349 | printk(KERN_WARNING | ||
350 | "%s: next_skb exist ERROR (skb->len=%d next_skb->len=%d)\n", | ||
351 | __func__, skb->len, ch->next_skb->len); | ||
352 | return -EBUSY; | ||
353 | } | ||
354 | if (test_and_set_bit(FLG_TX_BUSY, &ch->Flags)) { | ||
355 | test_and_set_bit(FLG_TX_NEXT, &ch->Flags); | ||
356 | ch->next_skb = skb; | ||
357 | return 0; | ||
358 | } else { | ||
359 | /* write to fifo */ | ||
360 | ch->tx_skb = skb; | ||
361 | ch->tx_idx = 0; | ||
362 | return 1; | ||
363 | } | ||
364 | } | ||
365 | EXPORT_SYMBOL(bchannel_senddata); | ||