diff options
Diffstat (limited to 'drivers/net/wan/hdlc_generic.c')
-rw-r--r-- | drivers/net/wan/hdlc_generic.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/drivers/net/wan/hdlc_generic.c b/drivers/net/wan/hdlc_generic.c new file mode 100644 index 000000000000..6ed064cb4469 --- /dev/null +++ b/drivers/net/wan/hdlc_generic.c @@ -0,0 +1,343 @@ +/* + * Generic HDLC support routines for Linux + * + * Copyright (C) 1999 - 2003 Krzysztof Halasa <khc@pm.waw.pl> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + * + * Currently supported: + * * raw IP-in-HDLC + * * Cisco HDLC + * * Frame Relay with ANSI or CCITT LMI (both user and network side) + * * PPP + * * X.25 + * + * Use sethdlc utility to set line parameters, protocol and PVCs + * + * How does it work: + * - proto.open(), close(), start(), stop() calls are serialized. + * The order is: open, [ start, stop ... ] close ... + * - proto.start() and stop() are called with spin_lock_irq held. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/errno.h> +#include <linux/if_arp.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/pkt_sched.h> +#include <linux/inetdevice.h> +#include <linux/lapb.h> +#include <linux/rtnetlink.h> +#include <linux/hdlc.h> + + +static const char* version = "HDLC support module revision 1.17"; + +#undef DEBUG_LINK + + +static int hdlc_change_mtu(struct net_device *dev, int new_mtu) +{ + if ((new_mtu < 68) || (new_mtu > HDLC_MAX_MTU)) + return -EINVAL; + dev->mtu = new_mtu; + return 0; +} + + + +static struct net_device_stats *hdlc_get_stats(struct net_device *dev) +{ + return hdlc_stats(dev); +} + + + +static int hdlc_rcv(struct sk_buff *skb, struct net_device *dev, + struct packet_type *p) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + if (hdlc->proto.netif_rx) + return hdlc->proto.netif_rx(skb); + + hdlc->stats.rx_dropped++; /* Shouldn't happen */ + dev_kfree_skb(skb); + return NET_RX_DROP; +} + + + +static void __hdlc_set_carrier_on(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + if (hdlc->proto.start) + return hdlc->proto.start(dev); +#ifdef DEBUG_LINK + if (netif_carrier_ok(dev)) + printk(KERN_ERR "hdlc_set_carrier_on(): already on\n"); +#endif + netif_carrier_on(dev); +} + + + +static void __hdlc_set_carrier_off(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + if (hdlc->proto.stop) + return hdlc->proto.stop(dev); + +#ifdef DEBUG_LINK + if (!netif_carrier_ok(dev)) + printk(KERN_ERR "hdlc_set_carrier_off(): already off\n"); +#endif + netif_carrier_off(dev); +} + + + +void hdlc_set_carrier(int on, struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + unsigned long flags; + on = on ? 1 : 0; + +#ifdef DEBUG_LINK + printk(KERN_DEBUG "hdlc_set_carrier %i\n", on); +#endif + + spin_lock_irqsave(&hdlc->state_lock, flags); + + if (hdlc->carrier == on) + goto carrier_exit; /* no change in DCD line level */ + +#ifdef DEBUG_LINK + printk(KERN_INFO "%s: carrier %s\n", dev->name, on ? "ON" : "off"); +#endif + hdlc->carrier = on; + + if (!hdlc->open) + goto carrier_exit; + + if (hdlc->carrier) + __hdlc_set_carrier_on(dev); + else + __hdlc_set_carrier_off(dev); + +carrier_exit: + spin_unlock_irqrestore(&hdlc->state_lock, flags); +} + + + +/* Must be called by hardware driver when HDLC device is being opened */ +int hdlc_open(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); +#ifdef DEBUG_LINK + printk(KERN_DEBUG "hdlc_open() carrier %i open %i\n", + hdlc->carrier, hdlc->open); +#endif + + if (hdlc->proto.id == -1) + return -ENOSYS; /* no protocol attached */ + + if (hdlc->proto.open) { + int result = hdlc->proto.open(dev); + if (result) + return result; + } + + spin_lock_irq(&hdlc->state_lock); + + if (hdlc->carrier) + __hdlc_set_carrier_on(dev); + + hdlc->open = 1; + + spin_unlock_irq(&hdlc->state_lock); + return 0; +} + + + +/* Must be called by hardware driver when HDLC device is being closed */ +void hdlc_close(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); +#ifdef DEBUG_LINK + printk(KERN_DEBUG "hdlc_close() carrier %i open %i\n", + hdlc->carrier, hdlc->open); +#endif + + spin_lock_irq(&hdlc->state_lock); + + hdlc->open = 0; + if (hdlc->carrier) + __hdlc_set_carrier_off(dev); + + spin_unlock_irq(&hdlc->state_lock); + + if (hdlc->proto.close) + hdlc->proto.close(dev); +} + + + +#ifndef CONFIG_HDLC_RAW +#define hdlc_raw_ioctl(dev, ifr) -ENOSYS +#endif + +#ifndef CONFIG_HDLC_RAW_ETH +#define hdlc_raw_eth_ioctl(dev, ifr) -ENOSYS +#endif + +#ifndef CONFIG_HDLC_PPP +#define hdlc_ppp_ioctl(dev, ifr) -ENOSYS +#endif + +#ifndef CONFIG_HDLC_CISCO +#define hdlc_cisco_ioctl(dev, ifr) -ENOSYS +#endif + +#ifndef CONFIG_HDLC_FR +#define hdlc_fr_ioctl(dev, ifr) -ENOSYS +#endif + +#ifndef CONFIG_HDLC_X25 +#define hdlc_x25_ioctl(dev, ifr) -ENOSYS +#endif + + +int hdlc_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + unsigned int proto; + + if (cmd != SIOCWANDEV) + return -EINVAL; + + switch(ifr->ifr_settings.type) { + case IF_PROTO_HDLC: + case IF_PROTO_HDLC_ETH: + case IF_PROTO_PPP: + case IF_PROTO_CISCO: + case IF_PROTO_FR: + case IF_PROTO_X25: + proto = ifr->ifr_settings.type; + break; + + default: + proto = hdlc->proto.id; + } + + switch(proto) { + case IF_PROTO_HDLC: return hdlc_raw_ioctl(dev, ifr); + case IF_PROTO_HDLC_ETH: return hdlc_raw_eth_ioctl(dev, ifr); + case IF_PROTO_PPP: return hdlc_ppp_ioctl(dev, ifr); + case IF_PROTO_CISCO: return hdlc_cisco_ioctl(dev, ifr); + case IF_PROTO_FR: return hdlc_fr_ioctl(dev, ifr); + case IF_PROTO_X25: return hdlc_x25_ioctl(dev, ifr); + default: return -EINVAL; + } +} + +static void hdlc_setup(struct net_device *dev) +{ + hdlc_device *hdlc = dev_to_hdlc(dev); + + dev->get_stats = hdlc_get_stats; + dev->change_mtu = hdlc_change_mtu; + dev->mtu = HDLC_MAX_MTU; + + dev->type = ARPHRD_RAWHDLC; + dev->hard_header_len = 16; + + dev->flags = IFF_POINTOPOINT | IFF_NOARP; + + hdlc->proto.id = -1; + hdlc->proto.detach = NULL; + hdlc->carrier = 1; + hdlc->open = 0; + spin_lock_init(&hdlc->state_lock); +} + +struct net_device *alloc_hdlcdev(void *priv) +{ + struct net_device *dev; + dev = alloc_netdev(sizeof(hdlc_device), "hdlc%d", hdlc_setup); + if (dev) + dev_to_hdlc(dev)->priv = priv; + return dev; +} + +int register_hdlc_device(struct net_device *dev) +{ + int result = dev_alloc_name(dev, "hdlc%d"); + if (result < 0) + return result; + + result = register_netdev(dev); + if (result != 0) + return -EIO; + + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); /* no carrier until DCD goes up */ + + return 0; +} + + + +void unregister_hdlc_device(struct net_device *dev) +{ + rtnl_lock(); + hdlc_proto_detach(dev_to_hdlc(dev)); + unregister_netdevice(dev); + rtnl_unlock(); +} + + + +MODULE_AUTHOR("Krzysztof Halasa <khc@pm.waw.pl>"); +MODULE_DESCRIPTION("HDLC support module"); +MODULE_LICENSE("GPL v2"); + +EXPORT_SYMBOL(hdlc_open); +EXPORT_SYMBOL(hdlc_close); +EXPORT_SYMBOL(hdlc_set_carrier); +EXPORT_SYMBOL(hdlc_ioctl); +EXPORT_SYMBOL(alloc_hdlcdev); +EXPORT_SYMBOL(register_hdlc_device); +EXPORT_SYMBOL(unregister_hdlc_device); + +static struct packet_type hdlc_packet_type = { + .type = __constant_htons(ETH_P_HDLC), + .func = hdlc_rcv, +}; + + +static int __init hdlc_module_init(void) +{ + printk(KERN_INFO "%s\n", version); + dev_add_pack(&hdlc_packet_type); + return 0; +} + + + +static void __exit hdlc_module_exit(void) +{ + dev_remove_pack(&hdlc_packet_type); +} + + +module_init(hdlc_module_init); +module_exit(hdlc_module_exit); |