Commit | Line | Data |
---|---|---|
ee5d8f4d | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 LT |
2 | /* |
3 | * LAPB release 002 | |
4 | * | |
5 | * This code REQUIRES 2.1.15 or higher/ NET3.038 | |
6 | * | |
1da177e4 LT |
7 | * History |
8 | * LAPB 001 Jonathan Naylor Started Coding | |
9 | * LAPB 002 Jonathan Naylor New timer architecture. | |
10 | * 2000-10-29 Henner Eisen lapb_data_indication() return status. | |
11 | */ | |
56d6c3d7 | 12 | |
a508da6c JP |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | ||
1da177e4 LT |
15 | #include <linux/module.h> |
16 | #include <linux/errno.h> | |
17 | #include <linux/types.h> | |
18 | #include <linux/socket.h> | |
19 | #include <linux/in.h> | |
20 | #include <linux/kernel.h> | |
21 | #include <linux/jiffies.h> | |
22 | #include <linux/timer.h> | |
23 | #include <linux/string.h> | |
24 | #include <linux/sockios.h> | |
25 | #include <linux/net.h> | |
26 | #include <linux/inet.h> | |
27 | #include <linux/if_arp.h> | |
28 | #include <linux/skbuff.h> | |
5a0e3ad6 | 29 | #include <linux/slab.h> |
1da177e4 | 30 | #include <net/sock.h> |
7c0f6ba6 | 31 | #include <linux/uaccess.h> |
1da177e4 LT |
32 | #include <linux/fcntl.h> |
33 | #include <linux/mm.h> | |
34 | #include <linux/interrupt.h> | |
35 | #include <linux/stat.h> | |
36 | #include <linux/init.h> | |
37 | #include <net/lapb.h> | |
38 | ||
14d0e7b7 | 39 | static LIST_HEAD(lapb_list); |
1da177e4 LT |
40 | static DEFINE_RWLOCK(lapb_list_lock); |
41 | ||
42 | /* | |
56d6c3d7 | 43 | * Free an allocated lapb control block. |
1da177e4 LT |
44 | */ |
45 | static void lapb_free_cb(struct lapb_cb *lapb) | |
46 | { | |
47 | kfree(lapb); | |
48 | } | |
49 | ||
50 | static __inline__ void lapb_hold(struct lapb_cb *lapb) | |
51 | { | |
0408c58b | 52 | refcount_inc(&lapb->refcnt); |
1da177e4 LT |
53 | } |
54 | ||
55 | static __inline__ void lapb_put(struct lapb_cb *lapb) | |
56 | { | |
0408c58b | 57 | if (refcount_dec_and_test(&lapb->refcnt)) |
1da177e4 LT |
58 | lapb_free_cb(lapb); |
59 | } | |
60 | ||
61 | /* | |
62 | * Socket removal during an interrupt is now safe. | |
63 | */ | |
64 | static void __lapb_remove_cb(struct lapb_cb *lapb) | |
65 | { | |
66 | if (lapb->node.next) { | |
67 | list_del(&lapb->node); | |
68 | lapb_put(lapb); | |
69 | } | |
70 | } | |
71 | ||
72 | /* | |
73 | * Add a socket to the bound sockets list. | |
74 | */ | |
75 | static void __lapb_insert_cb(struct lapb_cb *lapb) | |
76 | { | |
77 | list_add(&lapb->node, &lapb_list); | |
78 | lapb_hold(lapb); | |
79 | } | |
80 | ||
81 | static struct lapb_cb *__lapb_devtostruct(struct net_device *dev) | |
82 | { | |
1da177e4 LT |
83 | struct lapb_cb *lapb, *use = NULL; |
84 | ||
e8333284 | 85 | list_for_each_entry(lapb, &lapb_list, node) { |
1da177e4 LT |
86 | if (lapb->dev == dev) { |
87 | use = lapb; | |
88 | break; | |
89 | } | |
90 | } | |
91 | ||
92 | if (use) | |
93 | lapb_hold(use); | |
94 | ||
95 | return use; | |
96 | } | |
97 | ||
98 | static struct lapb_cb *lapb_devtostruct(struct net_device *dev) | |
99 | { | |
100 | struct lapb_cb *rc; | |
101 | ||
102 | read_lock_bh(&lapb_list_lock); | |
103 | rc = __lapb_devtostruct(dev); | |
104 | read_unlock_bh(&lapb_list_lock); | |
105 | ||
106 | return rc; | |
107 | } | |
108 | /* | |
109 | * Create an empty LAPB control block. | |
110 | */ | |
111 | static struct lapb_cb *lapb_create_cb(void) | |
112 | { | |
0da974f4 | 113 | struct lapb_cb *lapb = kzalloc(sizeof(*lapb), GFP_ATOMIC); |
1da177e4 | 114 | |
1da177e4 LT |
115 | if (!lapb) |
116 | goto out; | |
117 | ||
1da177e4 LT |
118 | skb_queue_head_init(&lapb->write_queue); |
119 | skb_queue_head_init(&lapb->ack_queue); | |
120 | ||
83a37b32 KC |
121 | timer_setup(&lapb->t1timer, NULL, 0); |
122 | timer_setup(&lapb->t2timer, NULL, 0); | |
65d2dbb3 XH |
123 | lapb->t1timer_running = false; |
124 | lapb->t2timer_running = false; | |
1da177e4 LT |
125 | |
126 | lapb->t1 = LAPB_DEFAULT_T1; | |
127 | lapb->t2 = LAPB_DEFAULT_T2; | |
128 | lapb->n2 = LAPB_DEFAULT_N2; | |
129 | lapb->mode = LAPB_DEFAULT_MODE; | |
130 | lapb->window = LAPB_DEFAULT_WINDOW; | |
131 | lapb->state = LAPB_STATE_0; | |
b491e6a7 XH |
132 | |
133 | spin_lock_init(&lapb->lock); | |
0408c58b | 134 | refcount_set(&lapb->refcnt, 1); |
1da177e4 LT |
135 | out: |
136 | return lapb; | |
137 | } | |
138 | ||
d97a077a | 139 | int lapb_register(struct net_device *dev, |
140 | const struct lapb_register_struct *callbacks) | |
1da177e4 LT |
141 | { |
142 | struct lapb_cb *lapb; | |
143 | int rc = LAPB_BADTOKEN; | |
144 | ||
145 | write_lock_bh(&lapb_list_lock); | |
146 | ||
147 | lapb = __lapb_devtostruct(dev); | |
148 | if (lapb) { | |
149 | lapb_put(lapb); | |
150 | goto out; | |
151 | } | |
152 | ||
153 | lapb = lapb_create_cb(); | |
154 | rc = LAPB_NOMEM; | |
155 | if (!lapb) | |
156 | goto out; | |
157 | ||
158 | lapb->dev = dev; | |
d97a077a | 159 | lapb->callbacks = callbacks; |
1da177e4 LT |
160 | |
161 | __lapb_insert_cb(lapb); | |
162 | ||
163 | lapb_start_t1timer(lapb); | |
164 | ||
165 | rc = LAPB_OK; | |
166 | out: | |
167 | write_unlock_bh(&lapb_list_lock); | |
168 | return rc; | |
169 | } | |
4201c926 | 170 | EXPORT_SYMBOL(lapb_register); |
1da177e4 LT |
171 | |
172 | int lapb_unregister(struct net_device *dev) | |
173 | { | |
174 | struct lapb_cb *lapb; | |
175 | int rc = LAPB_BADTOKEN; | |
176 | ||
177 | write_lock_bh(&lapb_list_lock); | |
178 | lapb = __lapb_devtostruct(dev); | |
179 | if (!lapb) | |
180 | goto out; | |
6be8e297 | 181 | lapb_put(lapb); |
1da177e4 | 182 | |
b491e6a7 XH |
183 | /* Wait for other refs to "lapb" to drop */ |
184 | while (refcount_read(&lapb->refcnt) > 2) | |
185 | usleep_range(1, 10); | |
186 | ||
187 | spin_lock_bh(&lapb->lock); | |
188 | ||
1da177e4 LT |
189 | lapb_stop_t1timer(lapb); |
190 | lapb_stop_t2timer(lapb); | |
191 | ||
192 | lapb_clear_queues(lapb); | |
193 | ||
b491e6a7 XH |
194 | spin_unlock_bh(&lapb->lock); |
195 | ||
196 | /* Wait for running timers to stop */ | |
197 | del_timer_sync(&lapb->t1timer); | |
198 | del_timer_sync(&lapb->t2timer); | |
199 | ||
1da177e4 LT |
200 | __lapb_remove_cb(lapb); |
201 | ||
202 | lapb_put(lapb); | |
203 | rc = LAPB_OK; | |
204 | out: | |
205 | write_unlock_bh(&lapb_list_lock); | |
206 | return rc; | |
207 | } | |
75da1469 | 208 | EXPORT_SYMBOL(lapb_unregister); |
1da177e4 LT |
209 | |
210 | int lapb_getparms(struct net_device *dev, struct lapb_parms_struct *parms) | |
211 | { | |
212 | int rc = LAPB_BADTOKEN; | |
213 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
214 | ||
215 | if (!lapb) | |
216 | goto out; | |
217 | ||
b491e6a7 XH |
218 | spin_lock_bh(&lapb->lock); |
219 | ||
1da177e4 LT |
220 | parms->t1 = lapb->t1 / HZ; |
221 | parms->t2 = lapb->t2 / HZ; | |
222 | parms->n2 = lapb->n2; | |
223 | parms->n2count = lapb->n2count; | |
224 | parms->state = lapb->state; | |
225 | parms->window = lapb->window; | |
226 | parms->mode = lapb->mode; | |
227 | ||
228 | if (!timer_pending(&lapb->t1timer)) | |
229 | parms->t1timer = 0; | |
230 | else | |
231 | parms->t1timer = (lapb->t1timer.expires - jiffies) / HZ; | |
232 | ||
233 | if (!timer_pending(&lapb->t2timer)) | |
234 | parms->t2timer = 0; | |
235 | else | |
236 | parms->t2timer = (lapb->t2timer.expires - jiffies) / HZ; | |
237 | ||
b491e6a7 | 238 | spin_unlock_bh(&lapb->lock); |
1da177e4 LT |
239 | lapb_put(lapb); |
240 | rc = LAPB_OK; | |
241 | out: | |
242 | return rc; | |
243 | } | |
75da1469 | 244 | EXPORT_SYMBOL(lapb_getparms); |
1da177e4 LT |
245 | |
246 | int lapb_setparms(struct net_device *dev, struct lapb_parms_struct *parms) | |
247 | { | |
248 | int rc = LAPB_BADTOKEN; | |
249 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
250 | ||
251 | if (!lapb) | |
252 | goto out; | |
253 | ||
b491e6a7 XH |
254 | spin_lock_bh(&lapb->lock); |
255 | ||
1da177e4 LT |
256 | rc = LAPB_INVALUE; |
257 | if (parms->t1 < 1 || parms->t2 < 1 || parms->n2 < 1) | |
258 | goto out_put; | |
259 | ||
260 | if (lapb->state == LAPB_STATE_0) { | |
558e10a5 DC |
261 | if (parms->mode & LAPB_EXTENDED) { |
262 | if (parms->window < 1 || parms->window > 127) | |
263 | goto out_put; | |
264 | } else { | |
265 | if (parms->window < 1 || parms->window > 7) | |
266 | goto out_put; | |
267 | } | |
1da177e4 LT |
268 | lapb->mode = parms->mode; |
269 | lapb->window = parms->window; | |
270 | } | |
271 | ||
272 | lapb->t1 = parms->t1 * HZ; | |
273 | lapb->t2 = parms->t2 * HZ; | |
274 | lapb->n2 = parms->n2; | |
275 | ||
276 | rc = LAPB_OK; | |
277 | out_put: | |
b491e6a7 | 278 | spin_unlock_bh(&lapb->lock); |
1da177e4 LT |
279 | lapb_put(lapb); |
280 | out: | |
281 | return rc; | |
282 | } | |
75da1469 | 283 | EXPORT_SYMBOL(lapb_setparms); |
1da177e4 LT |
284 | |
285 | int lapb_connect_request(struct net_device *dev) | |
286 | { | |
287 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
288 | int rc = LAPB_BADTOKEN; | |
289 | ||
290 | if (!lapb) | |
291 | goto out; | |
292 | ||
b491e6a7 XH |
293 | spin_lock_bh(&lapb->lock); |
294 | ||
1da177e4 LT |
295 | rc = LAPB_OK; |
296 | if (lapb->state == LAPB_STATE_1) | |
297 | goto out_put; | |
298 | ||
299 | rc = LAPB_CONNECTED; | |
300 | if (lapb->state == LAPB_STATE_3 || lapb->state == LAPB_STATE_4) | |
301 | goto out_put; | |
302 | ||
303 | lapb_establish_data_link(lapb); | |
304 | ||
a508da6c | 305 | lapb_dbg(0, "(%p) S0 -> S1\n", lapb->dev); |
1da177e4 LT |
306 | lapb->state = LAPB_STATE_1; |
307 | ||
308 | rc = LAPB_OK; | |
309 | out_put: | |
b491e6a7 | 310 | spin_unlock_bh(&lapb->lock); |
1da177e4 LT |
311 | lapb_put(lapb); |
312 | out: | |
313 | return rc; | |
314 | } | |
75da1469 | 315 | EXPORT_SYMBOL(lapb_connect_request); |
1da177e4 | 316 | |
b491e6a7 | 317 | static int __lapb_disconnect_request(struct lapb_cb *lapb) |
1da177e4 | 318 | { |
1da177e4 | 319 | switch (lapb->state) { |
fcb261f3 | 320 | case LAPB_STATE_0: |
b491e6a7 | 321 | return LAPB_NOTCONNECTED; |
1da177e4 | 322 | |
fcb261f3 | 323 | case LAPB_STATE_1: |
a508da6c JP |
324 | lapb_dbg(1, "(%p) S1 TX DISC(1)\n", lapb->dev); |
325 | lapb_dbg(0, "(%p) S1 -> S0\n", lapb->dev); | |
fcb261f3 JP |
326 | lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND); |
327 | lapb->state = LAPB_STATE_0; | |
328 | lapb_start_t1timer(lapb); | |
b491e6a7 | 329 | return LAPB_NOTCONNECTED; |
fcb261f3 JP |
330 | |
331 | case LAPB_STATE_2: | |
b491e6a7 | 332 | return LAPB_OK; |
1da177e4 LT |
333 | } |
334 | ||
335 | lapb_clear_queues(lapb); | |
336 | lapb->n2count = 0; | |
337 | lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND); | |
338 | lapb_start_t1timer(lapb); | |
339 | lapb_stop_t2timer(lapb); | |
340 | lapb->state = LAPB_STATE_2; | |
341 | ||
a508da6c JP |
342 | lapb_dbg(1, "(%p) S3 DISC(1)\n", lapb->dev); |
343 | lapb_dbg(0, "(%p) S3 -> S2\n", lapb->dev); | |
1da177e4 | 344 | |
b491e6a7 XH |
345 | return LAPB_OK; |
346 | } | |
347 | ||
348 | int lapb_disconnect_request(struct net_device *dev) | |
349 | { | |
350 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
351 | int rc = LAPB_BADTOKEN; | |
352 | ||
353 | if (!lapb) | |
354 | goto out; | |
355 | ||
356 | spin_lock_bh(&lapb->lock); | |
357 | ||
358 | rc = __lapb_disconnect_request(lapb); | |
359 | ||
360 | spin_unlock_bh(&lapb->lock); | |
1da177e4 LT |
361 | lapb_put(lapb); |
362 | out: | |
363 | return rc; | |
364 | } | |
75da1469 | 365 | EXPORT_SYMBOL(lapb_disconnect_request); |
1da177e4 LT |
366 | |
367 | int lapb_data_request(struct net_device *dev, struct sk_buff *skb) | |
368 | { | |
369 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
370 | int rc = LAPB_BADTOKEN; | |
371 | ||
372 | if (!lapb) | |
373 | goto out; | |
374 | ||
b491e6a7 XH |
375 | spin_lock_bh(&lapb->lock); |
376 | ||
1da177e4 LT |
377 | rc = LAPB_NOTCONNECTED; |
378 | if (lapb->state != LAPB_STATE_3 && lapb->state != LAPB_STATE_4) | |
379 | goto out_put; | |
380 | ||
381 | skb_queue_tail(&lapb->write_queue, skb); | |
382 | lapb_kick(lapb); | |
383 | rc = LAPB_OK; | |
384 | out_put: | |
b491e6a7 | 385 | spin_unlock_bh(&lapb->lock); |
1da177e4 LT |
386 | lapb_put(lapb); |
387 | out: | |
388 | return rc; | |
389 | } | |
75da1469 | 390 | EXPORT_SYMBOL(lapb_data_request); |
1da177e4 LT |
391 | |
392 | int lapb_data_received(struct net_device *dev, struct sk_buff *skb) | |
393 | { | |
394 | struct lapb_cb *lapb = lapb_devtostruct(dev); | |
395 | int rc = LAPB_BADTOKEN; | |
396 | ||
397 | if (lapb) { | |
b491e6a7 | 398 | spin_lock_bh(&lapb->lock); |
1da177e4 | 399 | lapb_data_input(lapb, skb); |
b491e6a7 | 400 | spin_unlock_bh(&lapb->lock); |
1da177e4 LT |
401 | lapb_put(lapb); |
402 | rc = LAPB_OK; | |
403 | } | |
404 | ||
405 | return rc; | |
406 | } | |
75da1469 | 407 | EXPORT_SYMBOL(lapb_data_received); |
1da177e4 LT |
408 | |
409 | void lapb_connect_confirmation(struct lapb_cb *lapb, int reason) | |
410 | { | |
d97a077a | 411 | if (lapb->callbacks->connect_confirmation) |
412 | lapb->callbacks->connect_confirmation(lapb->dev, reason); | |
1da177e4 LT |
413 | } |
414 | ||
415 | void lapb_connect_indication(struct lapb_cb *lapb, int reason) | |
416 | { | |
d97a077a | 417 | if (lapb->callbacks->connect_indication) |
418 | lapb->callbacks->connect_indication(lapb->dev, reason); | |
1da177e4 LT |
419 | } |
420 | ||
421 | void lapb_disconnect_confirmation(struct lapb_cb *lapb, int reason) | |
422 | { | |
d97a077a | 423 | if (lapb->callbacks->disconnect_confirmation) |
424 | lapb->callbacks->disconnect_confirmation(lapb->dev, reason); | |
1da177e4 LT |
425 | } |
426 | ||
427 | void lapb_disconnect_indication(struct lapb_cb *lapb, int reason) | |
428 | { | |
d97a077a | 429 | if (lapb->callbacks->disconnect_indication) |
430 | lapb->callbacks->disconnect_indication(lapb->dev, reason); | |
1da177e4 LT |
431 | } |
432 | ||
433 | int lapb_data_indication(struct lapb_cb *lapb, struct sk_buff *skb) | |
434 | { | |
d97a077a | 435 | if (lapb->callbacks->data_indication) |
436 | return lapb->callbacks->data_indication(lapb->dev, skb); | |
1da177e4 LT |
437 | |
438 | kfree_skb(skb); | |
0e8635a8 | 439 | return NET_RX_SUCCESS; /* For now; must be != NET_RX_DROP */ |
1da177e4 LT |
440 | } |
441 | ||
442 | int lapb_data_transmit(struct lapb_cb *lapb, struct sk_buff *skb) | |
443 | { | |
444 | int used = 0; | |
445 | ||
d97a077a | 446 | if (lapb->callbacks->data_transmit) { |
447 | lapb->callbacks->data_transmit(lapb->dev, skb); | |
1da177e4 LT |
448 | used = 1; |
449 | } | |
450 | ||
451 | return used; | |
452 | } | |
453 | ||
a4989fa9 MS |
454 | /* Handle device status changes. */ |
455 | static int lapb_device_event(struct notifier_block *this, unsigned long event, | |
456 | void *ptr) | |
457 | { | |
458 | struct net_device *dev = netdev_notifier_info_to_dev(ptr); | |
459 | struct lapb_cb *lapb; | |
460 | ||
461 | if (!net_eq(dev_net(dev), &init_net)) | |
462 | return NOTIFY_DONE; | |
463 | ||
464 | if (dev->type != ARPHRD_X25) | |
465 | return NOTIFY_DONE; | |
466 | ||
467 | lapb = lapb_devtostruct(dev); | |
468 | if (!lapb) | |
469 | return NOTIFY_DONE; | |
470 | ||
b491e6a7 XH |
471 | spin_lock_bh(&lapb->lock); |
472 | ||
a4989fa9 MS |
473 | switch (event) { |
474 | case NETDEV_UP: | |
475 | lapb_dbg(0, "(%p) Interface up: %s\n", dev, dev->name); | |
476 | ||
477 | if (netif_carrier_ok(dev)) { | |
478 | lapb_dbg(0, "(%p): Carrier is already up: %s\n", dev, | |
479 | dev->name); | |
480 | if (lapb->mode & LAPB_DCE) { | |
481 | lapb_start_t1timer(lapb); | |
482 | } else { | |
483 | if (lapb->state == LAPB_STATE_0) { | |
484 | lapb->state = LAPB_STATE_1; | |
485 | lapb_establish_data_link(lapb); | |
486 | } | |
487 | } | |
488 | } | |
489 | break; | |
490 | case NETDEV_GOING_DOWN: | |
491 | if (netif_carrier_ok(dev)) | |
b491e6a7 | 492 | __lapb_disconnect_request(lapb); |
a4989fa9 MS |
493 | break; |
494 | case NETDEV_DOWN: | |
495 | lapb_dbg(0, "(%p) Interface down: %s\n", dev, dev->name); | |
496 | lapb_dbg(0, "(%p) S%d -> S0\n", dev, lapb->state); | |
497 | lapb_clear_queues(lapb); | |
498 | lapb->state = LAPB_STATE_0; | |
499 | lapb->n2count = 0; | |
500 | lapb_stop_t1timer(lapb); | |
501 | lapb_stop_t2timer(lapb); | |
502 | break; | |
503 | case NETDEV_CHANGE: | |
504 | if (netif_carrier_ok(dev)) { | |
505 | lapb_dbg(0, "(%p): Carrier detected: %s\n", dev, | |
506 | dev->name); | |
507 | if (lapb->mode & LAPB_DCE) { | |
508 | lapb_start_t1timer(lapb); | |
509 | } else { | |
510 | if (lapb->state == LAPB_STATE_0) { | |
511 | lapb->state = LAPB_STATE_1; | |
512 | lapb_establish_data_link(lapb); | |
513 | } | |
514 | } | |
515 | } else { | |
516 | lapb_dbg(0, "(%p) Carrier lost: %s\n", dev, dev->name); | |
517 | lapb_dbg(0, "(%p) S%d -> S0\n", dev, lapb->state); | |
518 | lapb_clear_queues(lapb); | |
519 | lapb->state = LAPB_STATE_0; | |
520 | lapb->n2count = 0; | |
521 | lapb_stop_t1timer(lapb); | |
522 | lapb_stop_t2timer(lapb); | |
523 | } | |
524 | break; | |
525 | } | |
526 | ||
b491e6a7 | 527 | spin_unlock_bh(&lapb->lock); |
b40f97b9 | 528 | lapb_put(lapb); |
a4989fa9 MS |
529 | return NOTIFY_DONE; |
530 | } | |
531 | ||
532 | static struct notifier_block lapb_dev_notifier = { | |
533 | .notifier_call = lapb_device_event, | |
534 | }; | |
535 | ||
1da177e4 LT |
536 | static int __init lapb_init(void) |
537 | { | |
a4989fa9 | 538 | return register_netdevice_notifier(&lapb_dev_notifier); |
1da177e4 LT |
539 | } |
540 | ||
541 | static void __exit lapb_exit(void) | |
542 | { | |
543 | WARN_ON(!list_empty(&lapb_list)); | |
a4989fa9 MS |
544 | |
545 | unregister_netdevice_notifier(&lapb_dev_notifier); | |
1da177e4 LT |
546 | } |
547 | ||
548 | MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>"); | |
549 | MODULE_DESCRIPTION("The X.25 Link Access Procedure B link layer protocol"); | |
550 | MODULE_LICENSE("GPL"); | |
551 | ||
552 | module_init(lapb_init); | |
553 | module_exit(lapb_exit); |