Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* $Id: isdnl3.c,v 2.22.2.3 2004/01/13 14:31:25 keil Exp $ |
2 | * | |
3 | * Author Karsten Keil | |
4 | * based on the teles driver from Jan den Ouden | |
5 | * Copyright by Karsten Keil <keil@isdn4linux.de> | |
6 | * | |
7 | * This software may be used and distributed according to the terms | |
8 | * of the GNU General Public License, incorporated herein by reference. | |
9 | * | |
10 | * For changes and modifications please read | |
11 | * Documentation/isdn/HiSax.cert | |
12 | * | |
13 | * Thanks to Jan den Ouden | |
14 | * Fritz Elfert | |
15 | * | |
16 | */ | |
17 | ||
18 | #include <linux/init.h> | |
19 | #include "hisax.h" | |
20 | #include "isdnl3.h" | |
1da177e4 LT |
21 | |
22 | const char *l3_revision = "$Revision: 2.22.2.3 $"; | |
23 | ||
24 | static struct Fsm l3fsm; | |
25 | ||
26 | enum { | |
27 | ST_L3_LC_REL, | |
28 | ST_L3_LC_ESTAB_WAIT, | |
29 | ST_L3_LC_REL_DELAY, | |
30 | ST_L3_LC_REL_WAIT, | |
31 | ST_L3_LC_ESTAB, | |
32 | }; | |
33 | ||
34 | #define L3_STATE_COUNT (ST_L3_LC_ESTAB+1) | |
35 | ||
36 | static char *strL3State[] = | |
37 | { | |
38 | "ST_L3_LC_REL", | |
39 | "ST_L3_LC_ESTAB_WAIT", | |
40 | "ST_L3_LC_REL_DELAY", | |
41 | "ST_L3_LC_REL_WAIT", | |
42 | "ST_L3_LC_ESTAB", | |
43 | }; | |
44 | ||
45 | enum { | |
46 | EV_ESTABLISH_REQ, | |
47 | EV_ESTABLISH_IND, | |
48 | EV_ESTABLISH_CNF, | |
49 | EV_RELEASE_REQ, | |
50 | EV_RELEASE_CNF, | |
51 | EV_RELEASE_IND, | |
52 | EV_TIMEOUT, | |
53 | }; | |
54 | ||
55 | #define L3_EVENT_COUNT (EV_TIMEOUT+1) | |
56 | ||
57 | static char *strL3Event[] = | |
58 | { | |
59 | "EV_ESTABLISH_REQ", | |
60 | "EV_ESTABLISH_IND", | |
61 | "EV_ESTABLISH_CNF", | |
62 | "EV_RELEASE_REQ", | |
63 | "EV_RELEASE_CNF", | |
64 | "EV_RELEASE_IND", | |
65 | "EV_TIMEOUT", | |
66 | }; | |
67 | ||
68 | static void | |
69 | l3m_debug(struct FsmInst *fi, char *fmt, ...) | |
70 | { | |
71 | va_list args; | |
72 | struct PStack *st = fi->userdata; | |
73 | ||
74 | va_start(args, fmt); | |
75 | VHiSax_putstatus(st->l1.hardware, st->l3.debug_id, fmt, args); | |
76 | va_end(args); | |
77 | } | |
78 | ||
79 | u_char * | |
80 | findie(u_char * p, int size, u_char ie, int wanted_set) | |
81 | { | |
82 | int l, codeset, maincodeset; | |
83 | u_char *pend = p + size; | |
84 | ||
85 | /* skip protocol discriminator, callref and message type */ | |
86 | p++; | |
87 | l = (*p++) & 0xf; | |
88 | p += l; | |
89 | p++; | |
90 | codeset = 0; | |
91 | maincodeset = 0; | |
92 | /* while there are bytes left... */ | |
93 | while (p < pend) { | |
94 | if ((*p & 0xf0) == 0x90) { | |
95 | codeset = *p & 0x07; | |
96 | if (!(*p & 0x08)) | |
97 | maincodeset = codeset; | |
98 | } | |
99 | if (*p & 0x80) | |
100 | p++; | |
101 | else { | |
102 | if (codeset == wanted_set) { | |
103 | if (*p == ie) | |
104 | { /* improved length check (Werner Cornelius) */ | |
105 | if ((pend - p) < 2) | |
106 | return(NULL); | |
107 | if (*(p+1) > (pend - (p+2))) | |
108 | return(NULL); | |
109 | return (p); | |
110 | } | |
111 | ||
112 | if (*p > ie) | |
113 | return (NULL); | |
114 | } | |
115 | p++; | |
116 | l = *p++; | |
117 | p += l; | |
118 | codeset = maincodeset; | |
119 | } | |
120 | } | |
121 | return (NULL); | |
122 | } | |
123 | ||
124 | int | |
125 | getcallref(u_char * p) | |
126 | { | |
127 | int l, cr = 0; | |
128 | ||
129 | p++; /* prot discr */ | |
130 | if (*p & 0xfe) /* wrong callref BRI only 1 octet*/ | |
131 | return(-2); | |
132 | l = 0xf & *p++; /* callref length */ | |
133 | if (!l) /* dummy CallRef */ | |
134 | return(-1); | |
135 | cr = *p++; | |
136 | return (cr); | |
137 | } | |
138 | ||
139 | static int OrigCallRef = 0; | |
140 | ||
141 | int | |
142 | newcallref(void) | |
143 | { | |
144 | if (OrigCallRef == 127) | |
145 | OrigCallRef = 1; | |
146 | else | |
147 | OrigCallRef++; | |
148 | return (OrigCallRef); | |
149 | } | |
150 | ||
151 | void | |
152 | newl3state(struct l3_process *pc, int state) | |
153 | { | |
154 | if (pc->debug & L3_DEB_STATE) | |
155 | l3_debug(pc->st, "newstate cr %d %d --> %d", | |
156 | pc->callref & 0x7F, | |
157 | pc->state, state); | |
158 | pc->state = state; | |
159 | } | |
160 | ||
161 | static void | |
162 | L3ExpireTimer(struct L3Timer *t) | |
163 | { | |
164 | t->pc->st->lli.l4l3(t->pc->st, t->event, t->pc); | |
165 | } | |
166 | ||
167 | void | |
168 | L3InitTimer(struct l3_process *pc, struct L3Timer *t) | |
169 | { | |
170 | t->pc = pc; | |
171 | t->tl.function = (void *) L3ExpireTimer; | |
172 | t->tl.data = (long) t; | |
173 | init_timer(&t->tl); | |
174 | } | |
175 | ||
176 | void | |
177 | L3DelTimer(struct L3Timer *t) | |
178 | { | |
179 | del_timer(&t->tl); | |
180 | } | |
181 | ||
182 | int | |
183 | L3AddTimer(struct L3Timer *t, | |
184 | int millisec, int event) | |
185 | { | |
186 | if (timer_pending(&t->tl)) { | |
187 | printk(KERN_WARNING "L3AddTimer: timer already active!\n"); | |
188 | return -1; | |
189 | } | |
190 | init_timer(&t->tl); | |
191 | t->event = event; | |
192 | t->tl.expires = jiffies + (millisec * HZ) / 1000; | |
193 | add_timer(&t->tl); | |
194 | return 0; | |
195 | } | |
196 | ||
197 | void | |
198 | StopAllL3Timer(struct l3_process *pc) | |
199 | { | |
200 | L3DelTimer(&pc->timer); | |
201 | } | |
202 | ||
203 | struct sk_buff * | |
204 | l3_alloc_skb(int len) | |
205 | { | |
206 | struct sk_buff *skb; | |
207 | ||
208 | if (!(skb = alloc_skb(len + MAX_HEADER_LEN, GFP_ATOMIC))) { | |
209 | printk(KERN_WARNING "HiSax: No skb for D-channel\n"); | |
210 | return (NULL); | |
211 | } | |
212 | skb_reserve(skb, MAX_HEADER_LEN); | |
213 | return (skb); | |
214 | } | |
215 | ||
216 | static void | |
217 | no_l3_proto(struct PStack *st, int pr, void *arg) | |
218 | { | |
219 | struct sk_buff *skb = arg; | |
220 | ||
221 | HiSax_putstatus(st->l1.hardware, "L3", "no D protocol"); | |
222 | if (skb) { | |
223 | dev_kfree_skb(skb); | |
224 | } | |
225 | } | |
226 | ||
227 | static int | |
228 | no_l3_proto_spec(struct PStack *st, isdn_ctrl *ic) | |
229 | { | |
230 | printk(KERN_WARNING "HiSax: no specific protocol handler for proto %lu\n",ic->arg & 0xFF); | |
231 | return(-1); | |
232 | } | |
233 | ||
1da177e4 LT |
234 | struct l3_process |
235 | *getl3proc(struct PStack *st, int cr) | |
236 | { | |
237 | struct l3_process *p = st->l3.proc; | |
238 | ||
239 | while (p) | |
240 | if (p->callref == cr) | |
241 | return (p); | |
242 | else | |
243 | p = p->next; | |
244 | return (NULL); | |
245 | } | |
246 | ||
247 | struct l3_process | |
248 | *new_l3_process(struct PStack *st, int cr) | |
249 | { | |
250 | struct l3_process *p, *np; | |
251 | ||
252 | if (!(p = kmalloc(sizeof(struct l3_process), GFP_ATOMIC))) { | |
253 | printk(KERN_ERR "HiSax can't get memory for cr %d\n", cr); | |
254 | return (NULL); | |
255 | } | |
256 | if (!st->l3.proc) | |
257 | st->l3.proc = p; | |
258 | else { | |
259 | np = st->l3.proc; | |
260 | while (np->next) | |
261 | np = np->next; | |
262 | np->next = p; | |
263 | } | |
264 | p->next = NULL; | |
265 | p->debug = st->l3.debug; | |
266 | p->callref = cr; | |
267 | p->state = 0; | |
268 | p->chan = NULL; | |
269 | p->st = st; | |
270 | p->N303 = st->l3.N303; | |
271 | L3InitTimer(p, &p->timer); | |
272 | return (p); | |
273 | }; | |
274 | ||
275 | void | |
276 | release_l3_process(struct l3_process *p) | |
277 | { | |
278 | struct l3_process *np, *pp = NULL; | |
279 | ||
280 | if (!p) | |
281 | return; | |
282 | np = p->st->l3.proc; | |
283 | while (np) { | |
284 | if (np == p) { | |
285 | StopAllL3Timer(p); | |
286 | if (pp) | |
287 | pp->next = np->next; | |
288 | else if (!(p->st->l3.proc = np->next) && | |
289 | !test_bit(FLG_PTP, &p->st->l2.flag)) { | |
290 | if (p->debug) | |
291 | l3_debug(p->st, "release_l3_process: last process"); | |
b03efcfb | 292 | if (skb_queue_empty(&p->st->l3.squeue)) { |
1da177e4 LT |
293 | if (p->debug) |
294 | l3_debug(p->st, "release_l3_process: release link"); | |
295 | if (p->st->protocol != ISDN_PTYPE_NI1) | |
296 | FsmEvent(&p->st->l3.l3m, EV_RELEASE_REQ, NULL); | |
297 | else | |
298 | FsmEvent(&p->st->l3.l3m, EV_RELEASE_IND, NULL); | |
299 | } else { | |
300 | if (p->debug) | |
301 | l3_debug(p->st, "release_l3_process: not release link"); | |
302 | } | |
303 | } | |
304 | kfree(p); | |
305 | return; | |
306 | } | |
307 | pp = np; | |
308 | np = np->next; | |
309 | } | |
310 | printk(KERN_ERR "HiSax internal L3 error CR(%d) not in list\n", p->callref); | |
311 | l3_debug(p->st, "HiSax internal L3 error CR(%d) not in list", p->callref); | |
312 | }; | |
313 | ||
314 | static void | |
315 | l3ml3p(struct PStack *st, int pr) | |
316 | { | |
317 | struct l3_process *p = st->l3.proc; | |
318 | struct l3_process *np; | |
319 | ||
320 | while (p) { | |
321 | /* p might be kfreed under us, so we need to save where we want to go on */ | |
322 | np = p->next; | |
323 | st->l3.l3ml3(st, pr, p); | |
324 | p = np; | |
325 | } | |
326 | } | |
327 | ||
328 | void | |
329 | setstack_l3dc(struct PStack *st, struct Channel *chanp) | |
330 | { | |
331 | char tmp[64]; | |
332 | ||
333 | st->l3.proc = NULL; | |
334 | st->l3.global = NULL; | |
335 | skb_queue_head_init(&st->l3.squeue); | |
336 | st->l3.l3m.fsm = &l3fsm; | |
337 | st->l3.l3m.state = ST_L3_LC_REL; | |
338 | st->l3.l3m.debug = 1; | |
339 | st->l3.l3m.userdata = st; | |
340 | st->l3.l3m.userint = 0; | |
341 | st->l3.l3m.printdebug = l3m_debug; | |
342 | FsmInitTimer(&st->l3.l3m, &st->l3.l3m_timer); | |
343 | strcpy(st->l3.debug_id, "L3DC "); | |
344 | st->lli.l4l3_proto = no_l3_proto_spec; | |
345 | ||
346 | #ifdef CONFIG_HISAX_EURO | |
347 | if (st->protocol == ISDN_PTYPE_EURO) { | |
348 | setstack_dss1(st); | |
349 | } else | |
350 | #endif | |
351 | #ifdef CONFIG_HISAX_NI1 | |
352 | if (st->protocol == ISDN_PTYPE_NI1) { | |
353 | setstack_ni1(st); | |
354 | } else | |
355 | #endif | |
356 | #ifdef CONFIG_HISAX_1TR6 | |
357 | if (st->protocol == ISDN_PTYPE_1TR6) { | |
358 | setstack_1tr6(st); | |
359 | } else | |
360 | #endif | |
361 | if (st->protocol == ISDN_PTYPE_LEASED) { | |
362 | st->lli.l4l3 = no_l3_proto; | |
363 | st->l2.l2l3 = no_l3_proto; | |
364 | st->l3.l3ml3 = no_l3_proto; | |
365 | printk(KERN_INFO "HiSax: Leased line mode\n"); | |
366 | } else { | |
367 | st->lli.l4l3 = no_l3_proto; | |
368 | st->l2.l2l3 = no_l3_proto; | |
369 | st->l3.l3ml3 = no_l3_proto; | |
370 | sprintf(tmp, "protocol %s not supported", | |
371 | (st->protocol == ISDN_PTYPE_1TR6) ? "1tr6" : | |
372 | (st->protocol == ISDN_PTYPE_EURO) ? "euro" : | |
373 | (st->protocol == ISDN_PTYPE_NI1) ? "ni1" : | |
374 | "unknown"); | |
375 | printk(KERN_WARNING "HiSax: %s\n", tmp); | |
376 | st->protocol = -1; | |
377 | } | |
378 | } | |
379 | ||
672c3fd9 | 380 | static void |
1da177e4 LT |
381 | isdnl3_trans(struct PStack *st, int pr, void *arg) { |
382 | st->l3.l3l2(st, pr, arg); | |
383 | } | |
384 | ||
385 | void | |
386 | releasestack_isdnl3(struct PStack *st) | |
387 | { | |
388 | while (st->l3.proc) | |
389 | release_l3_process(st->l3.proc); | |
390 | if (st->l3.global) { | |
391 | StopAllL3Timer(st->l3.global); | |
392 | kfree(st->l3.global); | |
393 | st->l3.global = NULL; | |
394 | } | |
395 | FsmDelTimer(&st->l3.l3m_timer, 54); | |
396 | skb_queue_purge(&st->l3.squeue); | |
397 | } | |
398 | ||
399 | void | |
400 | setstack_l3bc(struct PStack *st, struct Channel *chanp) | |
401 | { | |
402 | ||
403 | st->l3.proc = NULL; | |
404 | st->l3.global = NULL; | |
405 | skb_queue_head_init(&st->l3.squeue); | |
406 | st->l3.l3m.fsm = &l3fsm; | |
407 | st->l3.l3m.state = ST_L3_LC_REL; | |
408 | st->l3.l3m.debug = 1; | |
409 | st->l3.l3m.userdata = st; | |
410 | st->l3.l3m.userint = 0; | |
411 | st->l3.l3m.printdebug = l3m_debug; | |
412 | strcpy(st->l3.debug_id, "L3BC "); | |
413 | st->lli.l4l3 = isdnl3_trans; | |
414 | } | |
415 | ||
416 | #define DREL_TIMER_VALUE 40000 | |
417 | ||
418 | static void | |
419 | lc_activate(struct FsmInst *fi, int event, void *arg) | |
420 | { | |
421 | struct PStack *st = fi->userdata; | |
422 | ||
423 | FsmChangeState(fi, ST_L3_LC_ESTAB_WAIT); | |
424 | st->l3.l3l2(st, DL_ESTABLISH | REQUEST, NULL); | |
425 | } | |
426 | ||
427 | static void | |
428 | lc_connect(struct FsmInst *fi, int event, void *arg) | |
429 | { | |
430 | struct PStack *st = fi->userdata; | |
431 | struct sk_buff *skb = arg; | |
432 | int dequeued = 0; | |
433 | ||
434 | FsmChangeState(fi, ST_L3_LC_ESTAB); | |
435 | while ((skb = skb_dequeue(&st->l3.squeue))) { | |
436 | st->l3.l3l2(st, DL_DATA | REQUEST, skb); | |
437 | dequeued++; | |
438 | } | |
439 | if ((!st->l3.proc) && dequeued) { | |
440 | if (st->l3.debug) | |
441 | l3_debug(st, "lc_connect: release link"); | |
442 | FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); | |
443 | } else | |
444 | l3ml3p(st, DL_ESTABLISH | INDICATION); | |
445 | } | |
446 | ||
447 | static void | |
448 | lc_connected(struct FsmInst *fi, int event, void *arg) | |
449 | { | |
450 | struct PStack *st = fi->userdata; | |
451 | struct sk_buff *skb = arg; | |
452 | int dequeued = 0; | |
453 | ||
454 | FsmDelTimer(&st->l3.l3m_timer, 51); | |
455 | FsmChangeState(fi, ST_L3_LC_ESTAB); | |
456 | while ((skb = skb_dequeue(&st->l3.squeue))) { | |
457 | st->l3.l3l2(st, DL_DATA | REQUEST, skb); | |
458 | dequeued++; | |
459 | } | |
460 | if ((!st->l3.proc) && dequeued) { | |
461 | if (st->l3.debug) | |
462 | l3_debug(st, "lc_connected: release link"); | |
463 | FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); | |
464 | } else | |
465 | l3ml3p(st, DL_ESTABLISH | CONFIRM); | |
466 | } | |
467 | ||
468 | static void | |
469 | lc_start_delay(struct FsmInst *fi, int event, void *arg) | |
470 | { | |
471 | struct PStack *st = fi->userdata; | |
472 | ||
473 | FsmChangeState(fi, ST_L3_LC_REL_DELAY); | |
474 | FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); | |
475 | } | |
476 | ||
477 | static void | |
478 | lc_start_delay_check(struct FsmInst *fi, int event, void *arg) | |
479 | /* 20/09/00 - GE timer not user for NI-1 as layer 2 should stay up */ | |
480 | { | |
481 | struct PStack *st = fi->userdata; | |
482 | ||
483 | FsmChangeState(fi, ST_L3_LC_REL_DELAY); | |
484 | /* 19/09/00 - GE timer not user for NI-1 */ | |
485 | if (st->protocol != ISDN_PTYPE_NI1) | |
486 | FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 50); | |
487 | } | |
488 | ||
489 | static void | |
490 | lc_release_req(struct FsmInst *fi, int event, void *arg) | |
491 | { | |
492 | struct PStack *st = fi->userdata; | |
493 | ||
494 | if (test_bit(FLG_L2BLOCK, &st->l2.flag)) { | |
495 | if (st->l3.debug) | |
496 | l3_debug(st, "lc_release_req: l2 blocked"); | |
497 | /* restart release timer */ | |
498 | FsmAddTimer(&st->l3.l3m_timer, DREL_TIMER_VALUE, EV_TIMEOUT, NULL, 51); | |
499 | } else { | |
500 | FsmChangeState(fi, ST_L3_LC_REL_WAIT); | |
501 | st->l3.l3l2(st, DL_RELEASE | REQUEST, NULL); | |
502 | } | |
503 | } | |
504 | ||
505 | static void | |
506 | lc_release_ind(struct FsmInst *fi, int event, void *arg) | |
507 | { | |
508 | struct PStack *st = fi->userdata; | |
509 | ||
510 | FsmDelTimer(&st->l3.l3m_timer, 52); | |
511 | FsmChangeState(fi, ST_L3_LC_REL); | |
512 | skb_queue_purge(&st->l3.squeue); | |
513 | l3ml3p(st, DL_RELEASE | INDICATION); | |
514 | } | |
515 | ||
516 | static void | |
517 | lc_release_cnf(struct FsmInst *fi, int event, void *arg) | |
518 | { | |
519 | struct PStack *st = fi->userdata; | |
520 | ||
521 | FsmChangeState(fi, ST_L3_LC_REL); | |
522 | skb_queue_purge(&st->l3.squeue); | |
523 | l3ml3p(st, DL_RELEASE | CONFIRM); | |
524 | } | |
525 | ||
526 | ||
527 | /* *INDENT-OFF* */ | |
528 | static struct FsmNode L3FnList[] __initdata = | |
529 | { | |
530 | {ST_L3_LC_REL, EV_ESTABLISH_REQ, lc_activate}, | |
531 | {ST_L3_LC_REL, EV_ESTABLISH_IND, lc_connect}, | |
532 | {ST_L3_LC_REL, EV_ESTABLISH_CNF, lc_connect}, | |
533 | {ST_L3_LC_ESTAB_WAIT, EV_ESTABLISH_CNF, lc_connected}, | |
534 | {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_REQ, lc_start_delay}, | |
535 | {ST_L3_LC_ESTAB_WAIT, EV_RELEASE_IND, lc_release_ind}, | |
536 | {ST_L3_LC_ESTAB, EV_RELEASE_IND, lc_release_ind}, | |
537 | {ST_L3_LC_ESTAB, EV_RELEASE_REQ, lc_start_delay_check}, | |
538 | {ST_L3_LC_REL_DELAY, EV_RELEASE_IND, lc_release_ind}, | |
539 | {ST_L3_LC_REL_DELAY, EV_ESTABLISH_REQ, lc_connected}, | |
540 | {ST_L3_LC_REL_DELAY, EV_TIMEOUT, lc_release_req}, | |
541 | {ST_L3_LC_REL_WAIT, EV_RELEASE_CNF, lc_release_cnf}, | |
542 | {ST_L3_LC_REL_WAIT, EV_ESTABLISH_REQ, lc_activate}, | |
543 | }; | |
544 | /* *INDENT-ON* */ | |
545 | ||
1da177e4 LT |
546 | void |
547 | l3_msg(struct PStack *st, int pr, void *arg) | |
548 | { | |
549 | switch (pr) { | |
550 | case (DL_DATA | REQUEST): | |
551 | if (st->l3.l3m.state == ST_L3_LC_ESTAB) { | |
552 | st->l3.l3l2(st, pr, arg); | |
553 | } else { | |
554 | struct sk_buff *skb = arg; | |
555 | ||
556 | skb_queue_tail(&st->l3.squeue, skb); | |
557 | FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); | |
558 | } | |
559 | break; | |
560 | case (DL_ESTABLISH | REQUEST): | |
561 | FsmEvent(&st->l3.l3m, EV_ESTABLISH_REQ, NULL); | |
562 | break; | |
563 | case (DL_ESTABLISH | CONFIRM): | |
564 | FsmEvent(&st->l3.l3m, EV_ESTABLISH_CNF, NULL); | |
565 | break; | |
566 | case (DL_ESTABLISH | INDICATION): | |
567 | FsmEvent(&st->l3.l3m, EV_ESTABLISH_IND, NULL); | |
568 | break; | |
569 | case (DL_RELEASE | INDICATION): | |
570 | FsmEvent(&st->l3.l3m, EV_RELEASE_IND, NULL); | |
571 | break; | |
572 | case (DL_RELEASE | CONFIRM): | |
573 | FsmEvent(&st->l3.l3m, EV_RELEASE_CNF, NULL); | |
574 | break; | |
575 | case (DL_RELEASE | REQUEST): | |
576 | FsmEvent(&st->l3.l3m, EV_RELEASE_REQ, NULL); | |
577 | break; | |
578 | } | |
579 | } | |
580 | ||
581 | int __init | |
582 | Isdnl3New(void) | |
583 | { | |
584 | l3fsm.state_count = L3_STATE_COUNT; | |
585 | l3fsm.event_count = L3_EVENT_COUNT; | |
586 | l3fsm.strEvent = strL3Event; | |
587 | l3fsm.strState = strL3State; | |
ba2d6ccb | 588 | return FsmNew(&l3fsm, L3FnList, ARRAY_SIZE(L3FnList)); |
1da177e4 LT |
589 | } |
590 | ||
591 | void | |
592 | Isdnl3Free(void) | |
593 | { | |
594 | FsmFree(&l3fsm); | |
595 | } |