2008-09-22 20:03:44 -07:00
/*
* File : pn_dev . c
*
* Phonet network device
*
* Copyright ( C ) 2008 Nokia Corporation .
*
* Contact : Remi Denis - Courmont < remi . denis - courmont @ nokia . com >
* Original author : Sakari Ailus < sakari . ailus @ nokia . com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*/
# include <linux/kernel.h>
# include <linux/net.h>
# include <linux/netdevice.h>
# include <linux/phonet.h>
2009-07-26 13:39:10 -07:00
# include <linux/proc_fs.h>
2009-09-09 00:00:05 +00:00
# include <linux/if_arp.h>
2008-09-22 20:03:44 -07:00
# include <net/sock.h>
2009-01-23 03:00:30 +00:00
# include <net/netns/generic.h>
2008-09-22 20:03:44 -07:00
# include <net/phonet/pn_dev.h>
2009-01-23 03:00:30 +00:00
struct phonet_net {
struct phonet_device_list pndevs ;
2008-09-22 20:03:44 -07:00
} ;
2009-01-23 03:00:30 +00:00
int phonet_net_id ;
struct phonet_device_list * phonet_device_list ( struct net * net )
{
struct phonet_net * pnn = net_generic ( net , phonet_net_id ) ;
return & pnn - > pndevs ;
}
2008-09-22 20:03:44 -07:00
/* Allocate new Phonet device. */
static struct phonet_device * __phonet_device_alloc ( struct net_device * dev )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd = kmalloc ( sizeof ( * pnd ) , GFP_ATOMIC ) ;
if ( pnd = = NULL )
return NULL ;
pnd - > netdev = dev ;
bitmap_zero ( pnd - > addrs , 64 ) ;
2009-01-23 03:00:30 +00:00
list_add ( & pnd - > list , & pndevs - > list ) ;
2008-09-22 20:03:44 -07:00
return pnd ;
}
static struct phonet_device * __phonet_get ( struct net_device * dev )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
2009-01-23 03:00:30 +00:00
list_for_each_entry ( pnd , & pndevs - > list , list ) {
2008-09-22 20:03:44 -07:00
if ( pnd - > netdev = = dev )
return pnd ;
}
return NULL ;
}
2009-06-24 01:07:45 +00:00
static void phonet_device_destroy ( struct net_device * dev )
2008-09-22 20:03:44 -07:00
{
2009-06-24 01:07:45 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
struct phonet_device * pnd ;
ASSERT_RTNL ( ) ;
spin_lock_bh ( & pndevs - > lock ) ;
pnd = __phonet_get ( dev ) ;
if ( pnd )
list_del ( & pnd - > list ) ;
spin_unlock_bh ( & pndevs - > lock ) ;
if ( pnd ) {
u8 addr ;
for ( addr = find_first_bit ( pnd - > addrs , 64 ) ; addr < 64 ;
addr = find_next_bit ( pnd - > addrs , 64 , 1 + addr ) )
phonet_address_notify ( RTM_DELADDR , dev , addr ) ;
kfree ( pnd ) ;
}
2008-09-22 20:03:44 -07:00
}
struct net_device * phonet_device_get ( struct net * net )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( net ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
2009-07-27 08:03:18 -07:00
struct net_device * dev = NULL ;
2008-09-22 20:03:44 -07:00
2009-01-23 03:00:30 +00:00
spin_lock_bh ( & pndevs - > lock ) ;
list_for_each_entry ( pnd , & pndevs - > list , list ) {
2008-09-22 20:03:44 -07:00
dev = pnd - > netdev ;
BUG_ON ( ! dev ) ;
2009-01-23 03:00:30 +00:00
if ( ( dev - > reg_state = = NETREG_REGISTERED ) & &
2008-09-22 20:03:44 -07:00
( ( pnd - > netdev - > flags & IFF_UP ) ) = = IFF_UP )
break ;
dev = NULL ;
}
if ( dev )
dev_hold ( dev ) ;
2009-01-23 03:00:30 +00:00
spin_unlock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
return dev ;
}
int phonet_address_add ( struct net_device * dev , u8 addr )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
int err = 0 ;
2009-01-23 03:00:30 +00:00
spin_lock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
/* Find or create Phonet-specific device data */
pnd = __phonet_get ( dev ) ;
if ( pnd = = NULL )
pnd = __phonet_device_alloc ( dev ) ;
if ( unlikely ( pnd = = NULL ) )
err = - ENOMEM ;
else if ( test_and_set_bit ( addr > > 2 , pnd - > addrs ) )
err = - EEXIST ;
2009-01-23 03:00:30 +00:00
spin_unlock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
return err ;
}
int phonet_address_del ( struct net_device * dev , u8 addr )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
int err = 0 ;
2009-01-23 03:00:30 +00:00
spin_lock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
pnd = __phonet_get ( dev ) ;
if ( ! pnd | | ! test_and_clear_bit ( addr > > 2 , pnd - > addrs ) )
err = - EADDRNOTAVAIL ;
2009-06-24 01:07:45 +00:00
else if ( bitmap_empty ( pnd - > addrs , 64 ) ) {
list_del ( & pnd - > list ) ;
kfree ( pnd ) ;
}
2009-01-23 03:00:30 +00:00
spin_unlock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
return err ;
}
/* Gets a source address toward a destination, through a interface. */
u8 phonet_address_get ( struct net_device * dev , u8 addr )
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( dev_net ( dev ) ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
2009-01-23 03:00:30 +00:00
spin_lock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
pnd = __phonet_get ( dev ) ;
if ( pnd ) {
BUG_ON ( bitmap_empty ( pnd - > addrs , 64 ) ) ;
/* Use same source address as destination, if possible */
if ( ! test_bit ( addr > > 2 , pnd - > addrs ) )
addr = find_first_bit ( pnd - > addrs , 64 ) < < 2 ;
} else
addr = PN_NO_ADDR ;
2009-01-23 03:00:30 +00:00
spin_unlock_bh ( & pndevs - > lock ) ;
2008-09-22 20:03:44 -07:00
return addr ;
}
2008-12-03 15:42:56 -08:00
int phonet_address_lookup ( struct net * net , u8 addr )
2008-09-22 20:03:44 -07:00
{
2009-01-23 03:00:30 +00:00
struct phonet_device_list * pndevs = phonet_device_list ( net ) ;
2008-09-22 20:03:44 -07:00
struct phonet_device * pnd ;
2009-01-23 03:00:30 +00:00
int err = - EADDRNOTAVAIL ;
2008-09-22 20:03:44 -07:00
2009-01-23 03:00:30 +00:00
spin_lock_bh ( & pndevs - > lock ) ;
list_for_each_entry ( pnd , & pndevs - > list , list ) {
2008-09-22 20:03:44 -07:00
/* Don't allow unregistering devices! */
if ( ( pnd - > netdev - > reg_state ! = NETREG_REGISTERED ) | |
( ( pnd - > netdev - > flags & IFF_UP ) ) ! = IFF_UP )
continue ;
if ( test_bit ( addr > > 2 , pnd - > addrs ) ) {
2009-01-23 03:00:30 +00:00
err = 0 ;
goto found ;
2008-09-22 20:03:44 -07:00
}
}
2009-01-23 03:00:30 +00:00
found :
spin_unlock_bh ( & pndevs - > lock ) ;
return err ;
2008-09-22 20:03:44 -07:00
}
2009-09-09 00:00:05 +00:00
/* automatically configure a Phonet device, if supported */
static int phonet_device_autoconf ( struct net_device * dev )
{
struct if_phonet_req req ;
int ret ;
if ( ! dev - > netdev_ops - > ndo_do_ioctl )
return - EOPNOTSUPP ;
ret = dev - > netdev_ops - > ndo_do_ioctl ( dev , ( struct ifreq * ) & req ,
SIOCPNGAUTOCONF ) ;
if ( ret < 0 )
return ret ;
2009-09-14 03:10:27 +00:00
ASSERT_RTNL ( ) ;
ret = phonet_address_add ( dev , req . ifr_phonet_autoconf . device ) ;
if ( ret )
return ret ;
phonet_address_notify ( RTM_NEWADDR , dev ,
req . ifr_phonet_autoconf . device ) ;
return 0 ;
2009-09-09 00:00:05 +00:00
}
2008-09-22 20:03:44 -07:00
/* notify Phonet of device events */
static int phonet_device_notify ( struct notifier_block * me , unsigned long what ,
void * arg )
{
struct net_device * dev = arg ;
2009-09-09 00:00:05 +00:00
switch ( what ) {
case NETDEV_REGISTER :
if ( dev - > type = = ARPHRD_PHONET )
phonet_device_autoconf ( dev ) ;
break ;
case NETDEV_UNREGISTER :
2009-06-24 01:07:45 +00:00
phonet_device_destroy ( dev ) ;
2009-09-09 00:00:05 +00:00
break ;
}
2008-09-22 20:03:44 -07:00
return 0 ;
}
static struct notifier_block phonet_device_notifier = {
. notifier_call = phonet_device_notify ,
. priority = 0 ,
} ;
2009-01-23 03:00:30 +00:00
/* Per-namespace Phonet devices handling */
static int phonet_init_net ( struct net * net )
{
struct phonet_net * pnn = kmalloc ( sizeof ( * pnn ) , GFP_KERNEL ) ;
if ( ! pnn )
return - ENOMEM ;
2009-07-21 01:57:57 +00:00
if ( ! proc_net_fops_create ( net , " phonet " , 0 , & pn_sock_seq_fops ) ) {
kfree ( pnn ) ;
return - ENOMEM ;
}
2009-01-23 03:00:30 +00:00
INIT_LIST_HEAD ( & pnn - > pndevs . list ) ;
spin_lock_init ( & pnn - > pndevs . lock ) ;
net_assign_generic ( net , phonet_net_id , pnn ) ;
return 0 ;
}
static void phonet_exit_net ( struct net * net )
{
struct phonet_net * pnn = net_generic ( net , phonet_net_id ) ;
2009-06-24 01:07:45 +00:00
struct net_device * dev ;
2009-01-23 03:00:30 +00:00
2009-06-24 01:07:45 +00:00
rtnl_lock ( ) ;
for_each_netdev ( net , dev )
phonet_device_destroy ( dev ) ;
rtnl_unlock ( ) ;
2009-07-21 01:57:57 +00:00
proc_net_remove ( net , " phonet " ) ;
2009-01-23 03:00:30 +00:00
kfree ( pnn ) ;
}
static struct pernet_operations phonet_net_ops = {
. init = phonet_init_net ,
. exit = phonet_exit_net ,
} ;
2008-09-22 20:03:44 -07:00
/* Initialize Phonet devices list */
2009-01-23 03:00:27 +00:00
int __init phonet_device_init ( void )
2008-09-22 20:03:44 -07:00
{
2009-01-23 03:00:30 +00:00
int err = register_pernet_gen_device ( & phonet_net_id , & phonet_net_ops ) ;
if ( err )
return err ;
2009-01-23 03:00:28 +00:00
2008-09-22 20:03:44 -07:00
register_netdevice_notifier ( & phonet_device_notifier ) ;
2009-01-23 03:00:28 +00:00
err = phonet_netlink_register ( ) ;
if ( err )
phonet_device_exit ( ) ;
return err ;
2008-09-22 20:03:44 -07:00
}
void phonet_device_exit ( void )
{
rtnl_unregister_all ( PF_PHONET ) ;
2009-01-23 03:00:29 +00:00
unregister_netdevice_notifier ( & phonet_device_notifier ) ;
2009-01-23 03:00:30 +00:00
unregister_pernet_gen_device ( phonet_net_id , & phonet_net_ops ) ;
2008-09-22 20:03:44 -07:00
}