diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/wan/dlci.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/wan/dlci.c')
-rw-r--r-- | drivers/net/wan/dlci.c | 566 |
1 files changed, 566 insertions, 0 deletions
diff --git a/drivers/net/wan/dlci.c b/drivers/net/wan/dlci.c new file mode 100644 index 000000000000..6e1ec5bf22fc --- /dev/null +++ b/drivers/net/wan/dlci.c | |||
@@ -0,0 +1,566 @@ | |||
1 | /* | ||
2 | * DLCI Implementation of Frame Relay protocol for Linux, according to | ||
3 | * RFC 1490. This generic device provides en/decapsulation for an | ||
4 | * underlying hardware driver. Routes & IPs are assigned to these | ||
5 | * interfaces. Requires 'dlcicfg' program to create usable | ||
6 | * interfaces, the initial one, 'dlci' is for IOCTL use only. | ||
7 | * | ||
8 | * Version: @(#)dlci.c 0.35 4 Jan 1997 | ||
9 | * | ||
10 | * Author: Mike McLagan <mike.mclagan@linux.org> | ||
11 | * | ||
12 | * Changes: | ||
13 | * | ||
14 | * 0.15 Mike Mclagan Packet freeing, bug in kmalloc call | ||
15 | * DLCI_RET handling | ||
16 | * 0.20 Mike McLagan More conservative on which packets | ||
17 | * are returned for retry and which are | ||
18 | * are dropped. If DLCI_RET_DROP is | ||
19 | * returned from the FRAD, the packet is | ||
20 | * sent back to Linux for re-transmission | ||
21 | * 0.25 Mike McLagan Converted to use SIOC IOCTL calls | ||
22 | * 0.30 Jim Freeman Fixed to allow IPX traffic | ||
23 | * 0.35 Michael Elizabeth Fixed incorrect memcpy_fromfs | ||
24 | * | ||
25 | * This program is free software; you can redistribute it and/or | ||
26 | * modify it under the terms of the GNU General Public License | ||
27 | * as published by the Free Software Foundation; either version | ||
28 | * 2 of the License, or (at your option) any later version. | ||
29 | */ | ||
30 | |||
31 | #include <linux/config.h> /* for CONFIG_DLCI_COUNT */ | ||
32 | #include <linux/module.h> | ||
33 | #include <linux/kernel.h> | ||
34 | #include <linux/types.h> | ||
35 | #include <linux/fcntl.h> | ||
36 | #include <linux/interrupt.h> | ||
37 | #include <linux/ptrace.h> | ||
38 | #include <linux/ioport.h> | ||
39 | #include <linux/in.h> | ||
40 | #include <linux/init.h> | ||
41 | #include <linux/slab.h> | ||
42 | #include <linux/string.h> | ||
43 | #include <linux/errno.h> | ||
44 | #include <linux/netdevice.h> | ||
45 | #include <linux/skbuff.h> | ||
46 | #include <linux/if_arp.h> | ||
47 | #include <linux/if_frad.h> | ||
48 | #include <linux/bitops.h> | ||
49 | |||
50 | #include <net/sock.h> | ||
51 | |||
52 | #include <asm/system.h> | ||
53 | #include <asm/io.h> | ||
54 | #include <asm/dma.h> | ||
55 | #include <asm/uaccess.h> | ||
56 | |||
57 | static const char version[] = "DLCI driver v0.35, 4 Jan 1997, mike.mclagan@linux.org"; | ||
58 | |||
59 | static LIST_HEAD(dlci_devs); | ||
60 | |||
61 | static void dlci_setup(struct net_device *); | ||
62 | |||
63 | /* | ||
64 | * these encapsulate the RFC 1490 requirements as well as | ||
65 | * deal with packet transmission and reception, working with | ||
66 | * the upper network layers | ||
67 | */ | ||
68 | |||
69 | static int dlci_header(struct sk_buff *skb, struct net_device *dev, | ||
70 | unsigned short type, void *daddr, void *saddr, | ||
71 | unsigned len) | ||
72 | { | ||
73 | struct frhdr hdr; | ||
74 | struct dlci_local *dlp; | ||
75 | unsigned int hlen; | ||
76 | char *dest; | ||
77 | |||
78 | dlp = dev->priv; | ||
79 | |||
80 | hdr.control = FRAD_I_UI; | ||
81 | switch(type) | ||
82 | { | ||
83 | case ETH_P_IP: | ||
84 | hdr.IP_NLPID = FRAD_P_IP; | ||
85 | hlen = sizeof(hdr.control) + sizeof(hdr.IP_NLPID); | ||
86 | break; | ||
87 | |||
88 | /* feel free to add other types, if necessary */ | ||
89 | |||
90 | default: | ||
91 | hdr.pad = FRAD_P_PADDING; | ||
92 | hdr.NLPID = FRAD_P_SNAP; | ||
93 | memset(hdr.OUI, 0, sizeof(hdr.OUI)); | ||
94 | hdr.PID = htons(type); | ||
95 | hlen = sizeof(hdr); | ||
96 | break; | ||
97 | } | ||
98 | |||
99 | dest = skb_push(skb, hlen); | ||
100 | if (!dest) | ||
101 | return(0); | ||
102 | |||
103 | memcpy(dest, &hdr, hlen); | ||
104 | |||
105 | return(hlen); | ||
106 | } | ||
107 | |||
108 | static void dlci_receive(struct sk_buff *skb, struct net_device *dev) | ||
109 | { | ||
110 | struct dlci_local *dlp; | ||
111 | struct frhdr *hdr; | ||
112 | int process, header; | ||
113 | |||
114 | dlp = dev->priv; | ||
115 | if (!pskb_may_pull(skb, sizeof(*hdr))) { | ||
116 | printk(KERN_NOTICE "%s: invalid data no header\n", | ||
117 | dev->name); | ||
118 | dlp->stats.rx_errors++; | ||
119 | kfree_skb(skb); | ||
120 | return; | ||
121 | } | ||
122 | |||
123 | hdr = (struct frhdr *) skb->data; | ||
124 | process = 0; | ||
125 | header = 0; | ||
126 | skb->dev = dev; | ||
127 | |||
128 | if (hdr->control != FRAD_I_UI) | ||
129 | { | ||
130 | printk(KERN_NOTICE "%s: Invalid header flag 0x%02X.\n", dev->name, hdr->control); | ||
131 | dlp->stats.rx_errors++; | ||
132 | } | ||
133 | else | ||
134 | switch(hdr->IP_NLPID) | ||
135 | { | ||
136 | case FRAD_P_PADDING: | ||
137 | if (hdr->NLPID != FRAD_P_SNAP) | ||
138 | { | ||
139 | printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->NLPID); | ||
140 | dlp->stats.rx_errors++; | ||
141 | break; | ||
142 | } | ||
143 | |||
144 | if (hdr->OUI[0] + hdr->OUI[1] + hdr->OUI[2] != 0) | ||
145 | { | ||
146 | printk(KERN_NOTICE "%s: Unsupported organizationally unique identifier 0x%02X-%02X-%02X.\n", dev->name, hdr->OUI[0], hdr->OUI[1], hdr->OUI[2]); | ||
147 | dlp->stats.rx_errors++; | ||
148 | break; | ||
149 | } | ||
150 | |||
151 | /* at this point, it's an EtherType frame */ | ||
152 | header = sizeof(struct frhdr); | ||
153 | /* Already in network order ! */ | ||
154 | skb->protocol = hdr->PID; | ||
155 | process = 1; | ||
156 | break; | ||
157 | |||
158 | case FRAD_P_IP: | ||
159 | header = sizeof(hdr->control) + sizeof(hdr->IP_NLPID); | ||
160 | skb->protocol = htons(ETH_P_IP); | ||
161 | process = 1; | ||
162 | break; | ||
163 | |||
164 | case FRAD_P_SNAP: | ||
165 | case FRAD_P_Q933: | ||
166 | case FRAD_P_CLNP: | ||
167 | printk(KERN_NOTICE "%s: Unsupported NLPID 0x%02X.\n", dev->name, hdr->pad); | ||
168 | dlp->stats.rx_errors++; | ||
169 | break; | ||
170 | |||
171 | default: | ||
172 | printk(KERN_NOTICE "%s: Invalid pad byte 0x%02X.\n", dev->name, hdr->pad); | ||
173 | dlp->stats.rx_errors++; | ||
174 | break; | ||
175 | } | ||
176 | |||
177 | if (process) | ||
178 | { | ||
179 | /* we've set up the protocol, so discard the header */ | ||
180 | skb->mac.raw = skb->data; | ||
181 | skb_pull(skb, header); | ||
182 | dlp->stats.rx_bytes += skb->len; | ||
183 | netif_rx(skb); | ||
184 | dlp->stats.rx_packets++; | ||
185 | dev->last_rx = jiffies; | ||
186 | } | ||
187 | else | ||
188 | dev_kfree_skb(skb); | ||
189 | } | ||
190 | |||
191 | static int dlci_transmit(struct sk_buff *skb, struct net_device *dev) | ||
192 | { | ||
193 | struct dlci_local *dlp; | ||
194 | int ret; | ||
195 | |||
196 | ret = 0; | ||
197 | |||
198 | if (!skb || !dev) | ||
199 | return(0); | ||
200 | |||
201 | dlp = dev->priv; | ||
202 | |||
203 | netif_stop_queue(dev); | ||
204 | |||
205 | ret = dlp->slave->hard_start_xmit(skb, dlp->slave); | ||
206 | switch (ret) | ||
207 | { | ||
208 | case DLCI_RET_OK: | ||
209 | dlp->stats.tx_packets++; | ||
210 | ret = 0; | ||
211 | break; | ||
212 | case DLCI_RET_ERR: | ||
213 | dlp->stats.tx_errors++; | ||
214 | ret = 0; | ||
215 | break; | ||
216 | case DLCI_RET_DROP: | ||
217 | dlp->stats.tx_dropped++; | ||
218 | ret = 1; | ||
219 | break; | ||
220 | } | ||
221 | /* Alan Cox recommends always returning 0, and always freeing the packet */ | ||
222 | /* experience suggest a slightly more conservative approach */ | ||
223 | |||
224 | if (!ret) | ||
225 | { | ||
226 | dev_kfree_skb(skb); | ||
227 | netif_wake_queue(dev); | ||
228 | } | ||
229 | return(ret); | ||
230 | } | ||
231 | |||
232 | static int dlci_config(struct net_device *dev, struct dlci_conf __user *conf, int get) | ||
233 | { | ||
234 | struct dlci_conf config; | ||
235 | struct dlci_local *dlp; | ||
236 | struct frad_local *flp; | ||
237 | int err; | ||
238 | |||
239 | dlp = dev->priv; | ||
240 | |||
241 | flp = dlp->slave->priv; | ||
242 | |||
243 | if (!get) | ||
244 | { | ||
245 | if(copy_from_user(&config, conf, sizeof(struct dlci_conf))) | ||
246 | return -EFAULT; | ||
247 | if (config.flags & ~DLCI_VALID_FLAGS) | ||
248 | return(-EINVAL); | ||
249 | memcpy(&dlp->config, &config, sizeof(struct dlci_conf)); | ||
250 | dlp->configured = 1; | ||
251 | } | ||
252 | |||
253 | err = (*flp->dlci_conf)(dlp->slave, dev, get); | ||
254 | if (err) | ||
255 | return(err); | ||
256 | |||
257 | if (get) | ||
258 | { | ||
259 | if(copy_to_user(conf, &dlp->config, sizeof(struct dlci_conf))) | ||
260 | return -EFAULT; | ||
261 | } | ||
262 | |||
263 | return(0); | ||
264 | } | ||
265 | |||
266 | static int dlci_dev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) | ||
267 | { | ||
268 | struct dlci_local *dlp; | ||
269 | |||
270 | if (!capable(CAP_NET_ADMIN)) | ||
271 | return(-EPERM); | ||
272 | |||
273 | dlp = dev->priv; | ||
274 | |||
275 | switch(cmd) | ||
276 | { | ||
277 | case DLCI_GET_SLAVE: | ||
278 | if (!*(short *)(dev->dev_addr)) | ||
279 | return(-EINVAL); | ||
280 | |||
281 | strncpy(ifr->ifr_slave, dlp->slave->name, sizeof(ifr->ifr_slave)); | ||
282 | break; | ||
283 | |||
284 | case DLCI_GET_CONF: | ||
285 | case DLCI_SET_CONF: | ||
286 | if (!*(short *)(dev->dev_addr)) | ||
287 | return(-EINVAL); | ||
288 | |||
289 | return(dlci_config(dev, ifr->ifr_data, cmd == DLCI_GET_CONF)); | ||
290 | break; | ||
291 | |||
292 | default: | ||
293 | return(-EOPNOTSUPP); | ||
294 | } | ||
295 | return(0); | ||
296 | } | ||
297 | |||
298 | static int dlci_change_mtu(struct net_device *dev, int new_mtu) | ||
299 | { | ||
300 | struct dlci_local *dlp; | ||
301 | |||
302 | dlp = dev->priv; | ||
303 | |||
304 | return((*dlp->slave->change_mtu)(dlp->slave, new_mtu)); | ||
305 | } | ||
306 | |||
307 | static int dlci_open(struct net_device *dev) | ||
308 | { | ||
309 | struct dlci_local *dlp; | ||
310 | struct frad_local *flp; | ||
311 | int err; | ||
312 | |||
313 | dlp = dev->priv; | ||
314 | |||
315 | if (!*(short *)(dev->dev_addr)) | ||
316 | return(-EINVAL); | ||
317 | |||
318 | if (!netif_running(dlp->slave)) | ||
319 | return(-ENOTCONN); | ||
320 | |||
321 | flp = dlp->slave->priv; | ||
322 | err = (*flp->activate)(dlp->slave, dev); | ||
323 | if (err) | ||
324 | return(err); | ||
325 | |||
326 | netif_start_queue(dev); | ||
327 | |||
328 | return 0; | ||
329 | } | ||
330 | |||
331 | static int dlci_close(struct net_device *dev) | ||
332 | { | ||
333 | struct dlci_local *dlp; | ||
334 | struct frad_local *flp; | ||
335 | int err; | ||
336 | |||
337 | netif_stop_queue(dev); | ||
338 | |||
339 | dlp = dev->priv; | ||
340 | |||
341 | flp = dlp->slave->priv; | ||
342 | err = (*flp->deactivate)(dlp->slave, dev); | ||
343 | |||
344 | return 0; | ||
345 | } | ||
346 | |||
347 | static struct net_device_stats *dlci_get_stats(struct net_device *dev) | ||
348 | { | ||
349 | struct dlci_local *dlp; | ||
350 | |||
351 | dlp = dev->priv; | ||
352 | |||
353 | return(&dlp->stats); | ||
354 | } | ||
355 | |||
356 | static int dlci_add(struct dlci_add *dlci) | ||
357 | { | ||
358 | struct net_device *master, *slave; | ||
359 | struct dlci_local *dlp; | ||
360 | struct frad_local *flp; | ||
361 | int err = -EINVAL; | ||
362 | |||
363 | |||
364 | /* validate slave device */ | ||
365 | slave = dev_get_by_name(dlci->devname); | ||
366 | if (!slave) | ||
367 | return -ENODEV; | ||
368 | |||
369 | if (slave->type != ARPHRD_FRAD || slave->priv == NULL) | ||
370 | goto err1; | ||
371 | |||
372 | /* create device name */ | ||
373 | master = alloc_netdev( sizeof(struct dlci_local), "dlci%d", | ||
374 | dlci_setup); | ||
375 | if (!master) { | ||
376 | err = -ENOMEM; | ||
377 | goto err1; | ||
378 | } | ||
379 | |||
380 | /* make sure same slave not already registered */ | ||
381 | rtnl_lock(); | ||
382 | list_for_each_entry(dlp, &dlci_devs, list) { | ||
383 | if (dlp->slave == slave) { | ||
384 | err = -EBUSY; | ||
385 | goto err2; | ||
386 | } | ||
387 | } | ||
388 | |||
389 | err = dev_alloc_name(master, master->name); | ||
390 | if (err < 0) | ||
391 | goto err2; | ||
392 | |||
393 | *(short *)(master->dev_addr) = dlci->dlci; | ||
394 | |||
395 | dlp = (struct dlci_local *) master->priv; | ||
396 | dlp->slave = slave; | ||
397 | dlp->master = master; | ||
398 | |||
399 | flp = slave->priv; | ||
400 | err = (*flp->assoc)(slave, master); | ||
401 | if (err < 0) | ||
402 | goto err2; | ||
403 | |||
404 | err = register_netdevice(master); | ||
405 | if (err < 0) | ||
406 | goto err2; | ||
407 | |||
408 | strcpy(dlci->devname, master->name); | ||
409 | |||
410 | list_add(&dlp->list, &dlci_devs); | ||
411 | rtnl_unlock(); | ||
412 | |||
413 | return(0); | ||
414 | |||
415 | err2: | ||
416 | rtnl_unlock(); | ||
417 | free_netdev(master); | ||
418 | err1: | ||
419 | dev_put(slave); | ||
420 | return(err); | ||
421 | } | ||
422 | |||
423 | static int dlci_del(struct dlci_add *dlci) | ||
424 | { | ||
425 | struct dlci_local *dlp; | ||
426 | struct frad_local *flp; | ||
427 | struct net_device *master, *slave; | ||
428 | int err; | ||
429 | |||
430 | /* validate slave device */ | ||
431 | master = __dev_get_by_name(dlci->devname); | ||
432 | if (!master) | ||
433 | return(-ENODEV); | ||
434 | |||
435 | if (netif_running(master)) { | ||
436 | return(-EBUSY); | ||
437 | } | ||
438 | |||
439 | dlp = master->priv; | ||
440 | slave = dlp->slave; | ||
441 | flp = slave->priv; | ||
442 | |||
443 | rtnl_lock(); | ||
444 | err = (*flp->deassoc)(slave, master); | ||
445 | if (!err) { | ||
446 | list_del(&dlp->list); | ||
447 | |||
448 | unregister_netdevice(master); | ||
449 | |||
450 | dev_put(slave); | ||
451 | } | ||
452 | rtnl_unlock(); | ||
453 | |||
454 | return(err); | ||
455 | } | ||
456 | |||
457 | static int dlci_ioctl(unsigned int cmd, void __user *arg) | ||
458 | { | ||
459 | struct dlci_add add; | ||
460 | int err; | ||
461 | |||
462 | if (!capable(CAP_NET_ADMIN)) | ||
463 | return(-EPERM); | ||
464 | |||
465 | if(copy_from_user(&add, arg, sizeof(struct dlci_add))) | ||
466 | return -EFAULT; | ||
467 | |||
468 | switch (cmd) | ||
469 | { | ||
470 | case SIOCADDDLCI: | ||
471 | err = dlci_add(&add); | ||
472 | |||
473 | if (!err) | ||
474 | if(copy_to_user(arg, &add, sizeof(struct dlci_add))) | ||
475 | return -EFAULT; | ||
476 | break; | ||
477 | |||
478 | case SIOCDELDLCI: | ||
479 | err = dlci_del(&add); | ||
480 | break; | ||
481 | |||
482 | default: | ||
483 | err = -EINVAL; | ||
484 | } | ||
485 | |||
486 | return(err); | ||
487 | } | ||
488 | |||
489 | static void dlci_setup(struct net_device *dev) | ||
490 | { | ||
491 | struct dlci_local *dlp = dev->priv; | ||
492 | |||
493 | dev->flags = 0; | ||
494 | dev->open = dlci_open; | ||
495 | dev->stop = dlci_close; | ||
496 | dev->do_ioctl = dlci_dev_ioctl; | ||
497 | dev->hard_start_xmit = dlci_transmit; | ||
498 | dev->hard_header = dlci_header; | ||
499 | dev->get_stats = dlci_get_stats; | ||
500 | dev->change_mtu = dlci_change_mtu; | ||
501 | dev->destructor = free_netdev; | ||
502 | |||
503 | dlp->receive = dlci_receive; | ||
504 | |||
505 | dev->type = ARPHRD_DLCI; | ||
506 | dev->hard_header_len = sizeof(struct frhdr); | ||
507 | dev->addr_len = sizeof(short); | ||
508 | |||
509 | } | ||
510 | |||
511 | /* if slave is unregistering, then cleanup master */ | ||
512 | static int dlci_dev_event(struct notifier_block *unused, | ||
513 | unsigned long event, void *ptr) | ||
514 | { | ||
515 | struct net_device *dev = (struct net_device *) ptr; | ||
516 | |||
517 | if (event == NETDEV_UNREGISTER) { | ||
518 | struct dlci_local *dlp; | ||
519 | |||
520 | list_for_each_entry(dlp, &dlci_devs, list) { | ||
521 | if (dlp->slave == dev) { | ||
522 | list_del(&dlp->list); | ||
523 | unregister_netdevice(dlp->master); | ||
524 | dev_put(dlp->slave); | ||
525 | break; | ||
526 | } | ||
527 | } | ||
528 | } | ||
529 | return NOTIFY_DONE; | ||
530 | } | ||
531 | |||
532 | static struct notifier_block dlci_notifier = { | ||
533 | .notifier_call = dlci_dev_event, | ||
534 | }; | ||
535 | |||
536 | static int __init init_dlci(void) | ||
537 | { | ||
538 | dlci_ioctl_set(dlci_ioctl); | ||
539 | register_netdevice_notifier(&dlci_notifier); | ||
540 | |||
541 | printk("%s.\n", version); | ||
542 | |||
543 | return 0; | ||
544 | } | ||
545 | |||
546 | static void __exit dlci_exit(void) | ||
547 | { | ||
548 | struct dlci_local *dlp, *nxt; | ||
549 | |||
550 | dlci_ioctl_set(NULL); | ||
551 | unregister_netdevice_notifier(&dlci_notifier); | ||
552 | |||
553 | rtnl_lock(); | ||
554 | list_for_each_entry_safe(dlp, nxt, &dlci_devs, list) { | ||
555 | unregister_netdevice(dlp->master); | ||
556 | dev_put(dlp->slave); | ||
557 | } | ||
558 | rtnl_unlock(); | ||
559 | } | ||
560 | |||
561 | module_init(init_dlci); | ||
562 | module_exit(dlci_exit); | ||
563 | |||
564 | MODULE_AUTHOR("Mike McLagan"); | ||
565 | MODULE_DESCRIPTION("Frame Relay DLCI layer"); | ||
566 | MODULE_LICENSE("GPL"); | ||