2005-04-17 02:20:36 +04:00
/*
* Generic HDLC support routines for Linux
*
2006-09-27 01:23:45 +04:00
* Copyright ( C ) 1999 - 2006 Krzysztof Halasa < khc @ pm . waw . pl >
2005-04-17 02:20:36 +04:00
*
* 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 :
2006-09-27 01:23:45 +04:00
* - proto - > open ( ) , close ( ) , start ( ) , stop ( ) calls are serialized .
2005-04-17 02:20:36 +04:00
* The order is : open , [ start , stop . . . ] close . . .
2006-09-27 01:23:45 +04:00
* - proto - > start ( ) and stop ( ) are called with spin_lock_irq held .
2005-04-17 02:20:36 +04:00
*/
# 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>
2006-07-13 00:46:12 +04:00
# include <linux/notifier.h>
2005-04-17 02:20:36 +04:00
# include <linux/hdlc.h>
2006-09-27 01:23:45 +04:00
static const char * version = " HDLC support module revision 1.20 " ;
2005-04-17 02:20:36 +04:00
# undef DEBUG_LINK
2006-09-27 01:23:45 +04:00
static struct hdlc_proto * first_proto = NULL ;
2005-04-17 02:20:36 +04:00
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 ,
2005-08-10 06:34:12 +04:00
struct packet_type * p , struct net_device * orig_dev )
2005-04-17 02:20:36 +04:00
{
2006-09-27 01:23:45 +04:00
struct hdlc_device_desc * desc = dev_to_desc ( dev ) ;
if ( desc - > netif_rx )
return desc - > netif_rx ( skb ) ;
2005-04-17 02:20:36 +04:00
2006-09-27 01:23:45 +04:00
desc - > stats . rx_dropped + + ; /* Shouldn't happen */
2005-04-17 02:20:36 +04:00
dev_kfree_skb ( skb ) ;
return NET_RX_DROP ;
}
2006-07-13 00:46:12 +04:00
static inline void hdlc_proto_start ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
hdlc_device * hdlc = dev_to_hdlc ( dev ) ;
2006-09-27 01:23:45 +04:00
if ( hdlc - > proto - > start )
return hdlc - > proto - > start ( dev ) ;
2005-04-17 02:20:36 +04:00
}
2006-07-13 00:46:12 +04:00
static inline void hdlc_proto_stop ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
hdlc_device * hdlc = dev_to_hdlc ( dev ) ;
2006-09-27 01:23:45 +04:00
if ( hdlc - > proto - > stop )
return hdlc - > proto - > stop ( dev ) ;
2005-04-17 02:20:36 +04:00
}
2006-07-13 00:46:12 +04:00
static int hdlc_device_event ( struct notifier_block * this , unsigned long event ,
void * ptr )
2005-04-17 02:20:36 +04:00
{
2006-07-13 00:46:12 +04:00
struct net_device * dev = ptr ;
hdlc_device * hdlc ;
2005-04-17 02:20:36 +04:00
unsigned long flags ;
2006-07-13 00:46:12 +04:00
int on ;
if ( dev - > get_stats ! = hdlc_get_stats )
return NOTIFY_DONE ; /* not an HDLC device */
if ( event ! = NETDEV_CHANGE )
return NOTIFY_DONE ; /* Only interrested in carrier changes */
on = netif_carrier_ok ( dev ) ;
2005-04-17 02:20:36 +04:00
# ifdef DEBUG_LINK
2006-07-13 00:46:12 +04:00
printk ( KERN_DEBUG " %s: hdlc_device_event NETDEV_CHANGE, carrier %i \n " ,
dev - > name , on ) ;
2005-04-17 02:20:36 +04:00
# endif
2006-07-13 00:46:12 +04:00
hdlc = dev_to_hdlc ( dev ) ;
2005-04-17 02:20:36 +04:00
spin_lock_irqsave ( & hdlc - > state_lock , flags ) ;
if ( hdlc - > carrier = = on )
goto carrier_exit ; /* no change in DCD line level */
hdlc - > carrier = on ;
if ( ! hdlc - > open )
goto carrier_exit ;
2005-04-21 17:57:25 +04:00
if ( hdlc - > carrier ) {
printk ( KERN_INFO " %s: Carrier detected \n " , dev - > name ) ;
2006-07-13 00:46:12 +04:00
hdlc_proto_start ( dev ) ;
2005-04-21 17:57:25 +04:00
} else {
printk ( KERN_INFO " %s: Carrier lost \n " , dev - > name ) ;
2006-07-13 00:46:12 +04:00
hdlc_proto_stop ( dev ) ;
2005-04-21 17:57:25 +04:00
}
2005-04-17 02:20:36 +04:00
carrier_exit :
spin_unlock_irqrestore ( & hdlc - > state_lock , flags ) ;
2006-07-13 00:46:12 +04:00
return NOTIFY_DONE ;
2005-04-17 02:20:36 +04:00
}
/* 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
2006-09-27 01:23:45 +04:00
printk ( KERN_DEBUG " %s: hdlc_open() carrier %i open %i \n " , dev - > name ,
2005-04-17 02:20:36 +04:00
hdlc - > carrier , hdlc - > open ) ;
# endif
2006-09-27 01:23:45 +04:00
if ( hdlc - > proto = = NULL )
2005-04-17 02:20:36 +04:00
return - ENOSYS ; /* no protocol attached */
2006-09-27 01:23:45 +04:00
if ( hdlc - > proto - > open ) {
int result = hdlc - > proto - > open ( dev ) ;
2005-04-17 02:20:36 +04:00
if ( result )
return result ;
}
spin_lock_irq ( & hdlc - > state_lock ) ;
2005-04-21 17:57:25 +04:00
if ( hdlc - > carrier ) {
printk ( KERN_INFO " %s: Carrier detected \n " , dev - > name ) ;
2006-07-13 00:46:12 +04:00
hdlc_proto_start ( dev ) ;
2005-04-21 17:57:25 +04:00
} else
printk ( KERN_INFO " %s: No carrier \n " , dev - > name ) ;
2005-04-17 02:20:36 +04:00
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
2006-09-27 01:23:45 +04:00
printk ( KERN_DEBUG " %s: hdlc_close() carrier %i open %i \n " , dev - > name ,
2005-04-17 02:20:36 +04:00
hdlc - > carrier , hdlc - > open ) ;
# endif
spin_lock_irq ( & hdlc - > state_lock ) ;
hdlc - > open = 0 ;
if ( hdlc - > carrier )
2006-07-13 00:46:12 +04:00
hdlc_proto_stop ( dev ) ;
2005-04-17 02:20:36 +04:00
spin_unlock_irq ( & hdlc - > state_lock ) ;
2006-09-27 01:23:45 +04:00
if ( hdlc - > proto - > close )
hdlc - > proto - > close ( dev ) ;
2005-04-17 02:20:36 +04:00
}
int hdlc_ioctl ( struct net_device * dev , struct ifreq * ifr , int cmd )
{
2006-09-27 01:23:45 +04:00
struct hdlc_proto * proto = first_proto ;
int result ;
2005-04-17 02:20:36 +04:00
if ( cmd ! = SIOCWANDEV )
return - EINVAL ;
2006-09-27 01:23:45 +04:00
if ( dev_to_hdlc ( dev ) - > proto ) {
result = dev_to_hdlc ( dev ) - > proto - > ioctl ( dev , ifr ) ;
if ( result ! = - EINVAL )
return result ;
2005-04-17 02:20:36 +04:00
}
2006-09-27 01:23:45 +04:00
/* Not handled by currently attached protocol (if any) */
while ( proto ) {
if ( ( result = proto - > ioctl ( dev , ifr ) ) ! = - EINVAL )
return result ;
proto = proto - > next ;
2005-04-17 02:20:36 +04:00
}
2006-09-27 01:23:45 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2006-06-23 00:20:19 +04:00
void hdlc_setup ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
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 - > carrier = 1 ;
hdlc - > open = 0 ;
spin_lock_init ( & hdlc - > state_lock ) ;
}
struct net_device * alloc_hdlcdev ( void * priv )
{
struct net_device * dev ;
2006-09-27 01:23:45 +04:00
dev = alloc_netdev ( sizeof ( struct hdlc_device_desc ) +
sizeof ( hdlc_device ) , " hdlc%d " , hdlc_setup ) ;
2005-04-17 02:20:36 +04:00
if ( dev )
dev_to_hdlc ( dev ) - > priv = priv ;
return dev ;
}
void unregister_hdlc_device ( struct net_device * dev )
{
rtnl_lock ( ) ;
unregister_netdevice ( dev ) ;
2006-09-27 01:23:45 +04:00
detach_hdlc_protocol ( dev ) ;
2005-04-17 02:20:36 +04:00
rtnl_unlock ( ) ;
}
2006-09-27 01:23:45 +04:00
int attach_hdlc_protocol ( struct net_device * dev , struct hdlc_proto * proto ,
int ( * rx ) ( struct sk_buff * skb ) , size_t size )
{
detach_hdlc_protocol ( dev ) ;
if ( ! try_module_get ( proto - > module ) )
return - ENOSYS ;
if ( size )
if ( ( dev_to_hdlc ( dev ) - > state = kmalloc ( size ,
GFP_KERNEL ) ) = = NULL ) {
printk ( KERN_WARNING " Memory squeeze on "
" hdlc_proto_attach() \n " ) ;
module_put ( proto - > module ) ;
return - ENOBUFS ;
}
dev_to_hdlc ( dev ) - > proto = proto ;
dev_to_desc ( dev ) - > netif_rx = rx ;
return 0 ;
}
void detach_hdlc_protocol ( struct net_device * dev )
{
hdlc_device * hdlc = dev_to_hdlc ( dev ) ;
if ( hdlc - > proto ) {
if ( hdlc - > proto - > detach )
hdlc - > proto - > detach ( dev ) ;
module_put ( hdlc - > proto - > module ) ;
hdlc - > proto = NULL ;
}
kfree ( hdlc - > state ) ;
hdlc - > state = NULL ;
}
void register_hdlc_protocol ( struct hdlc_proto * proto )
{
proto - > next = first_proto ;
first_proto = proto ;
}
void unregister_hdlc_protocol ( struct hdlc_proto * proto )
{
struct hdlc_proto * * p = & first_proto ;
while ( * p ) {
if ( * p = = proto ) {
* p = proto - > next ;
return ;
}
p = & ( ( * p ) - > next ) ;
}
}
2005-04-17 02:20:36 +04:00
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_ioctl ) ;
2006-06-23 00:20:19 +04:00
EXPORT_SYMBOL ( hdlc_setup ) ;
2005-04-17 02:20:36 +04:00
EXPORT_SYMBOL ( alloc_hdlcdev ) ;
EXPORT_SYMBOL ( unregister_hdlc_device ) ;
2006-09-27 01:23:45 +04:00
EXPORT_SYMBOL ( register_hdlc_protocol ) ;
EXPORT_SYMBOL ( unregister_hdlc_protocol ) ;
EXPORT_SYMBOL ( attach_hdlc_protocol ) ;
EXPORT_SYMBOL ( detach_hdlc_protocol ) ;
2005-04-17 02:20:36 +04:00
static struct packet_type hdlc_packet_type = {
. type = __constant_htons ( ETH_P_HDLC ) ,
. func = hdlc_rcv ,
} ;
2006-07-13 00:46:12 +04:00
static struct notifier_block hdlc_notifier = {
. notifier_call = hdlc_device_event ,
} ;
2005-04-17 02:20:36 +04:00
static int __init hdlc_module_init ( void )
{
2006-07-13 00:46:12 +04:00
int result ;
2005-04-17 02:20:36 +04:00
printk ( KERN_INFO " %s \n " , version ) ;
2006-07-13 00:46:12 +04:00
if ( ( result = register_netdevice_notifier ( & hdlc_notifier ) ) ! = 0 )
return result ;
2005-04-17 02:20:36 +04:00
dev_add_pack ( & hdlc_packet_type ) ;
return 0 ;
}
static void __exit hdlc_module_exit ( void )
{
dev_remove_pack ( & hdlc_packet_type ) ;
2006-07-13 00:46:12 +04:00
unregister_netdevice_notifier ( & hdlc_notifier ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( hdlc_module_init ) ;
module_exit ( hdlc_module_exit ) ;