2018-07-14 12:33:05 -04:00
/*
* HWSIM IEEE 802.15 .4 interface
*
* ( C ) 2018 Mojatau , Alexander Aring < aring @ mojatau . com >
* Copyright 2007 - 2012 Siemens AG
*
* 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 .
*
* Based on fakelb , original Written by :
* Sergey Lapin < slapin @ ossfans . org >
* Dmitry Eremin - Solenikov < dbaryshkov @ gmail . com >
* Alexander Smirnov < alex . bluesman . smirnov @ gmail . com >
*/
# include <linux/module.h>
# include <linux/timer.h>
# include <linux/platform_device.h>
2018-08-07 19:32:49 -04:00
# include <linux/rtnetlink.h>
2018-07-14 12:33:05 -04:00
# include <linux/netdevice.h>
# include <linux/device.h>
# include <linux/spinlock.h>
# include <net/mac802154.h>
# include <net/cfg802154.h>
# include <net/genetlink.h>
# include "mac802154_hwsim.h"
MODULE_DESCRIPTION ( " Software simulator of IEEE 802.15.4 radio(s) for mac802154 " ) ;
MODULE_LICENSE ( " GPL " ) ;
static LIST_HEAD ( hwsim_phys ) ;
static DEFINE_MUTEX ( hwsim_phys_lock ) ;
static struct platform_device * mac802154hwsim_dev ;
/* MAC802154_HWSIM netlink family */
static struct genl_family hwsim_genl_family ;
static int hwsim_radio_idx ;
enum hwsim_multicast_groups {
HWSIM_MCGRP_CONFIG ,
} ;
static const struct genl_multicast_group hwsim_mcgrps [ ] = {
[ HWSIM_MCGRP_CONFIG ] = { . name = " config " , } ,
} ;
struct hwsim_pib {
u8 page ;
u8 channel ;
struct rcu_head rcu ;
} ;
struct hwsim_edge_info {
u8 lqi ;
struct rcu_head rcu ;
} ;
struct hwsim_edge {
struct hwsim_phy * endpoint ;
2018-08-07 10:34:44 -04:00
struct hwsim_edge_info __rcu * info ;
2018-07-14 12:33:05 -04:00
struct list_head list ;
struct rcu_head rcu ;
} ;
struct hwsim_phy {
struct ieee802154_hw * hw ;
u32 idx ;
struct hwsim_pib __rcu * pib ;
bool suspended ;
2018-08-07 10:34:44 -04:00
struct list_head edges ;
2018-07-14 12:33:05 -04:00
struct list_head list ;
} ;
static int hwsim_add_one ( struct genl_info * info , struct device * dev ,
bool init ) ;
static void hwsim_del ( struct hwsim_phy * phy ) ;
static int hwsim_hw_ed ( struct ieee802154_hw * hw , u8 * level )
{
* level = 0xbe ;
return 0 ;
}
static int hwsim_hw_channel ( struct ieee802154_hw * hw , u8 page , u8 channel )
{
struct hwsim_phy * phy = hw - > priv ;
struct hwsim_pib * pib , * pib_old ;
pib = kzalloc ( sizeof ( * pib ) , GFP_KERNEL ) ;
if ( ! pib )
return - ENOMEM ;
pib - > page = page ;
pib - > channel = channel ;
2018-08-07 19:32:49 -04:00
pib_old = rtnl_dereference ( phy - > pib ) ;
2018-07-14 12:33:05 -04:00
rcu_assign_pointer ( phy - > pib , pib ) ;
kfree_rcu ( pib_old , rcu ) ;
return 0 ;
}
static int hwsim_hw_xmit ( struct ieee802154_hw * hw , struct sk_buff * skb )
{
struct hwsim_phy * current_phy = hw - > priv ;
struct hwsim_pib * current_pib , * endpoint_pib ;
struct hwsim_edge_info * einfo ;
struct hwsim_edge * e ;
WARN_ON ( current_phy - > suspended ) ;
rcu_read_lock ( ) ;
current_pib = rcu_dereference ( current_phy - > pib ) ;
list_for_each_entry_rcu ( e , & current_phy - > edges , list ) {
/* Can be changed later in rx_irqsafe, but this is only a
* performance tweak . Received radio should drop the frame
* in mac802154 stack anyway . . . so we don ' t need to be
* 100 % of locking here to check on suspended
*/
if ( e - > endpoint - > suspended )
continue ;
endpoint_pib = rcu_dereference ( e - > endpoint - > pib ) ;
if ( current_pib - > page = = endpoint_pib - > page & &
current_pib - > channel = = endpoint_pib - > channel ) {
struct sk_buff * newskb = pskb_copy ( skb , GFP_ATOMIC ) ;
einfo = rcu_dereference ( e - > info ) ;
if ( newskb )
ieee802154_rx_irqsafe ( e - > endpoint - > hw , newskb ,
einfo - > lqi ) ;
}
}
rcu_read_unlock ( ) ;
ieee802154_xmit_complete ( hw , skb , false ) ;
return 0 ;
}
static int hwsim_hw_start ( struct ieee802154_hw * hw )
{
struct hwsim_phy * phy = hw - > priv ;
phy - > suspended = false ;
return 0 ;
}
static void hwsim_hw_stop ( struct ieee802154_hw * hw )
{
struct hwsim_phy * phy = hw - > priv ;
phy - > suspended = true ;
}
static int
hwsim_set_promiscuous_mode ( struct ieee802154_hw * hw , const bool on )
{
return 0 ;
}
static const struct ieee802154_ops hwsim_ops = {
. owner = THIS_MODULE ,
. xmit_async = hwsim_hw_xmit ,
. ed = hwsim_hw_ed ,
. set_channel = hwsim_hw_channel ,
. start = hwsim_hw_start ,
. stop = hwsim_hw_stop ,
. set_promiscuous_mode = hwsim_set_promiscuous_mode ,
} ;
static int hwsim_new_radio_nl ( struct sk_buff * msg , struct genl_info * info )
{
return hwsim_add_one ( info , & mac802154hwsim_dev - > dev , false ) ;
}
static int hwsim_del_radio_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct hwsim_phy * phy , * tmp ;
s64 idx = - 1 ;
if ( ! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] )
return - EINVAL ;
idx = nla_get_u32 ( info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] ) ;
mutex_lock ( & hwsim_phys_lock ) ;
list_for_each_entry_safe ( phy , tmp , & hwsim_phys , list ) {
if ( idx = = phy - > idx ) {
hwsim_del ( phy ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return 0 ;
}
}
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENODEV ;
}
static int append_radio_msg ( struct sk_buff * skb , struct hwsim_phy * phy )
{
struct nlattr * nl_edges , * nl_edge ;
struct hwsim_edge_info * einfo ;
struct hwsim_edge * e ;
int ret ;
ret = nla_put_u32 ( skb , MAC802154_HWSIM_ATTR_RADIO_ID , phy - > idx ) ;
if ( ret < 0 )
return ret ;
rcu_read_lock ( ) ;
if ( list_empty ( & phy - > edges ) ) {
rcu_read_unlock ( ) ;
return 0 ;
}
nl_edges = nla_nest_start ( skb , MAC802154_HWSIM_ATTR_RADIO_EDGES ) ;
if ( ! nl_edges ) {
rcu_read_unlock ( ) ;
return - ENOBUFS ;
}
list_for_each_entry_rcu ( e , & phy - > edges , list ) {
nl_edge = nla_nest_start ( skb , MAC802154_HWSIM_ATTR_RADIO_EDGE ) ;
if ( ! nl_edge ) {
rcu_read_unlock ( ) ;
nla_nest_cancel ( skb , nl_edges ) ;
return - ENOBUFS ;
}
ret = nla_put_u32 ( skb , MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ,
e - > endpoint - > idx ) ;
if ( ret < 0 ) {
rcu_read_unlock ( ) ;
nla_nest_cancel ( skb , nl_edge ) ;
nla_nest_cancel ( skb , nl_edges ) ;
return ret ;
}
einfo = rcu_dereference ( e - > info ) ;
ret = nla_put_u8 ( skb , MAC802154_HWSIM_EDGE_ATTR_LQI ,
einfo - > lqi ) ;
if ( ret < 0 ) {
rcu_read_unlock ( ) ;
nla_nest_cancel ( skb , nl_edge ) ;
nla_nest_cancel ( skb , nl_edges ) ;
return ret ;
}
nla_nest_end ( skb , nl_edge ) ;
}
rcu_read_unlock ( ) ;
nla_nest_end ( skb , nl_edges ) ;
return 0 ;
}
static int hwsim_get_radio ( struct sk_buff * skb , struct hwsim_phy * phy ,
u32 portid , u32 seq ,
struct netlink_callback * cb , int flags )
{
void * hdr ;
int res = - EMSGSIZE ;
hdr = genlmsg_put ( skb , portid , seq , & hwsim_genl_family , flags ,
MAC802154_HWSIM_CMD_GET_RADIO ) ;
if ( ! hdr )
return - EMSGSIZE ;
if ( cb )
genl_dump_check_consistent ( cb , hdr ) ;
res = append_radio_msg ( skb , phy ) ;
if ( res < 0 )
goto out_err ;
genlmsg_end ( skb , hdr ) ;
return 0 ;
out_err :
genlmsg_cancel ( skb , hdr ) ;
return res ;
}
static int hwsim_get_radio_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct hwsim_phy * phy ;
struct sk_buff * skb ;
int idx , res = - ENODEV ;
if ( ! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] )
return - EINVAL ;
idx = nla_get_u32 ( info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] ) ;
mutex_lock ( & hwsim_phys_lock ) ;
list_for_each_entry ( phy , & hwsim_phys , list ) {
if ( phy - > idx ! = idx )
continue ;
skb = nlmsg_new ( NLMSG_DEFAULT_SIZE , GFP_ATOMIC ) ;
if ( ! skb ) {
res = - ENOMEM ;
goto out_err ;
}
res = hwsim_get_radio ( skb , phy , info - > snd_portid ,
info - > snd_seq , NULL , 0 ) ;
if ( res < 0 ) {
nlmsg_free ( skb ) ;
goto out_err ;
}
genlmsg_reply ( skb , info ) ;
break ;
}
out_err :
mutex_unlock ( & hwsim_phys_lock ) ;
return res ;
}
static int hwsim_dump_radio_nl ( struct sk_buff * skb ,
struct netlink_callback * cb )
{
int idx = cb - > args [ 0 ] ;
struct hwsim_phy * phy ;
int res ;
mutex_lock ( & hwsim_phys_lock ) ;
if ( idx = = hwsim_radio_idx )
goto done ;
list_for_each_entry ( phy , & hwsim_phys , list ) {
if ( phy - > idx < idx )
continue ;
res = hwsim_get_radio ( skb , phy , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , cb , NLM_F_MULTI ) ;
if ( res < 0 )
break ;
idx = phy - > idx + 1 ;
}
cb - > args [ 0 ] = idx ;
done :
mutex_unlock ( & hwsim_phys_lock ) ;
return skb - > len ;
}
/* caller need to held hwsim_phys_lock */
static struct hwsim_phy * hwsim_get_radio_by_id ( uint32_t idx )
{
struct hwsim_phy * phy ;
list_for_each_entry ( phy , & hwsim_phys , list ) {
if ( phy - > idx = = idx )
return phy ;
}
return NULL ;
}
static const struct nla_policy hwsim_edge_policy [ MAC802154_HWSIM_EDGE_ATTR_MAX + 1 ] = {
[ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] = { . type = NLA_U32 } ,
[ MAC802154_HWSIM_EDGE_ATTR_LQI ] = { . type = NLA_U8 } ,
} ;
static struct hwsim_edge * hwsim_alloc_edge ( struct hwsim_phy * endpoint , u8 lqi )
{
struct hwsim_edge_info * einfo ;
struct hwsim_edge * e ;
e = kzalloc ( sizeof ( * e ) , GFP_KERNEL ) ;
if ( ! e )
return NULL ;
einfo = kzalloc ( sizeof ( * einfo ) , GFP_KERNEL ) ;
if ( ! einfo ) {
kfree ( e ) ;
return NULL ;
}
einfo - > lqi = 0xff ;
2018-08-07 19:32:49 -04:00
rcu_assign_pointer ( e - > info , einfo ) ;
2018-07-14 12:33:05 -04:00
e - > endpoint = endpoint ;
return e ;
}
static void hwsim_free_edge ( struct hwsim_edge * e )
{
2018-08-07 19:32:49 -04:00
struct hwsim_edge_info * einfo ;
rcu_read_lock ( ) ;
einfo = rcu_dereference ( e - > info ) ;
rcu_read_unlock ( ) ;
kfree_rcu ( einfo , rcu ) ;
2018-07-14 12:33:05 -04:00
kfree_rcu ( e , rcu ) ;
}
static int hwsim_new_edge_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct nlattr * edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_MAX + 1 ] ;
struct hwsim_phy * phy_v0 , * phy_v1 ;
struct hwsim_edge * e ;
u32 v0 , v1 ;
if ( ! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] & &
! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] )
return - EINVAL ;
if ( nla_parse_nested ( edge_attrs , MAC802154_HWSIM_EDGE_ATTR_MAX ,
info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] ,
hwsim_edge_policy , NULL ) )
return - EINVAL ;
if ( ! edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] )
return - EINVAL ;
v0 = nla_get_u32 ( info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] ) ;
v1 = nla_get_u32 ( edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] ) ;
if ( v0 = = v1 )
return - EINVAL ;
mutex_lock ( & hwsim_phys_lock ) ;
phy_v0 = hwsim_get_radio_by_id ( v0 ) ;
if ( ! phy_v0 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
phy_v1 = hwsim_get_radio_by_id ( v1 ) ;
if ( ! phy_v1 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( e , & phy_v0 - > edges , list ) {
if ( e - > endpoint - > idx = = v1 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
rcu_read_unlock ( ) ;
return - EEXIST ;
}
}
rcu_read_unlock ( ) ;
e = hwsim_alloc_edge ( phy_v1 , 0xff ) ;
if ( ! e ) {
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOMEM ;
}
list_add_rcu ( & e - > list , & phy_v0 - > edges ) ;
/* wait until changes are done under hwsim_phys_lock lock
* should prevent of calling this function twice while
* edges list has not the changes yet .
*/
synchronize_rcu ( ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return 0 ;
}
static int hwsim_del_edge_nl ( struct sk_buff * msg , struct genl_info * info )
{
struct nlattr * edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_MAX + 1 ] ;
struct hwsim_phy * phy_v0 ;
struct hwsim_edge * e ;
u32 v0 , v1 ;
if ( ! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] & &
! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] )
return - EINVAL ;
2018-11-29 17:41:54 -05:00
if ( nla_parse_nested ( edge_attrs , MAC802154_HWSIM_EDGE_ATTR_MAX ,
2018-07-14 12:33:05 -04:00
info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] ,
hwsim_edge_policy , NULL ) )
return - EINVAL ;
if ( ! edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] )
return - EINVAL ;
v0 = nla_get_u32 ( info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] ) ;
v1 = nla_get_u32 ( edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] ) ;
mutex_lock ( & hwsim_phys_lock ) ;
phy_v0 = hwsim_get_radio_by_id ( v0 ) ;
if ( ! phy_v0 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( e , & phy_v0 - > edges , list ) {
if ( e - > endpoint - > idx = = v1 ) {
rcu_read_unlock ( ) ;
list_del_rcu ( & e - > list ) ;
hwsim_free_edge ( e ) ;
/* same again - wait until list changes are done */
synchronize_rcu ( ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return 0 ;
}
}
rcu_read_unlock ( ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
static int hwsim_set_edge_lqi ( struct sk_buff * msg , struct genl_info * info )
{
struct nlattr * edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_MAX + 1 ] ;
struct hwsim_edge_info * einfo ;
struct hwsim_phy * phy_v0 ;
struct hwsim_edge * e ;
u32 v0 , v1 ;
u8 lqi ;
if ( ! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] & &
! info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] )
return - EINVAL ;
2018-11-29 17:41:54 -05:00
if ( nla_parse_nested ( edge_attrs , MAC802154_HWSIM_EDGE_ATTR_MAX ,
2018-07-14 12:33:05 -04:00
info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_EDGE ] ,
hwsim_edge_policy , NULL ) )
return - EINVAL ;
if ( ! edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] & &
! edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_LQI ] )
return - EINVAL ;
v0 = nla_get_u32 ( info - > attrs [ MAC802154_HWSIM_ATTR_RADIO_ID ] ) ;
v1 = nla_get_u32 ( edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID ] ) ;
lqi = nla_get_u8 ( edge_attrs [ MAC802154_HWSIM_EDGE_ATTR_LQI ] ) ;
mutex_lock ( & hwsim_phys_lock ) ;
phy_v0 = hwsim_get_radio_by_id ( v0 ) ;
if ( ! phy_v0 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
einfo = kzalloc ( sizeof ( * einfo ) , GFP_KERNEL ) ;
2018-08-08 02:43:46 +00:00
if ( ! einfo ) {
2018-07-14 12:33:05 -04:00
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOMEM ;
}
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( e , & phy_v0 - > edges , list ) {
if ( e - > endpoint - > idx = = v1 ) {
einfo - > lqi = lqi ;
rcu_assign_pointer ( e - > info , einfo ) ;
rcu_read_unlock ( ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return 0 ;
}
}
rcu_read_unlock ( ) ;
kfree ( einfo ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return - ENOENT ;
}
/* MAC802154_HWSIM netlink policy */
static const struct nla_policy hwsim_genl_policy [ MAC802154_HWSIM_ATTR_MAX + 1 ] = {
[ MAC802154_HWSIM_ATTR_RADIO_ID ] = { . type = NLA_U32 } ,
[ MAC802154_HWSIM_ATTR_RADIO_EDGE ] = { . type = NLA_NESTED } ,
[ MAC802154_HWSIM_ATTR_RADIO_EDGES ] = { . type = NLA_NESTED } ,
} ;
/* Generic Netlink operations array */
static const struct genl_ops hwsim_nl_ops [ ] = {
{
. cmd = MAC802154_HWSIM_CMD_NEW_RADIO ,
. policy = hwsim_genl_policy ,
. doit = hwsim_new_radio_nl ,
. flags = GENL_UNS_ADMIN_PERM ,
} ,
{
. cmd = MAC802154_HWSIM_CMD_DEL_RADIO ,
. policy = hwsim_genl_policy ,
. doit = hwsim_del_radio_nl ,
. flags = GENL_UNS_ADMIN_PERM ,
} ,
{
. cmd = MAC802154_HWSIM_CMD_GET_RADIO ,
. policy = hwsim_genl_policy ,
. doit = hwsim_get_radio_nl ,
. dumpit = hwsim_dump_radio_nl ,
} ,
{
. cmd = MAC802154_HWSIM_CMD_NEW_EDGE ,
. policy = hwsim_genl_policy ,
. doit = hwsim_new_edge_nl ,
. flags = GENL_UNS_ADMIN_PERM ,
} ,
{
. cmd = MAC802154_HWSIM_CMD_DEL_EDGE ,
. policy = hwsim_genl_policy ,
. doit = hwsim_del_edge_nl ,
. flags = GENL_UNS_ADMIN_PERM ,
} ,
{
. cmd = MAC802154_HWSIM_CMD_SET_EDGE ,
. policy = hwsim_genl_policy ,
. doit = hwsim_set_edge_lqi ,
. flags = GENL_UNS_ADMIN_PERM ,
} ,
} ;
static struct genl_family hwsim_genl_family __ro_after_init = {
. name = " MAC802154_HWSIM " ,
. version = 1 ,
. maxattr = MAC802154_HWSIM_ATTR_MAX ,
. module = THIS_MODULE ,
. ops = hwsim_nl_ops ,
. n_ops = ARRAY_SIZE ( hwsim_nl_ops ) ,
. mcgrps = hwsim_mcgrps ,
. n_mcgrps = ARRAY_SIZE ( hwsim_mcgrps ) ,
} ;
static void hwsim_mcast_config_msg ( struct sk_buff * mcast_skb ,
struct genl_info * info )
{
if ( info )
genl_notify ( & hwsim_genl_family , mcast_skb , info ,
HWSIM_MCGRP_CONFIG , GFP_KERNEL ) ;
else
genlmsg_multicast ( & hwsim_genl_family , mcast_skb , 0 ,
HWSIM_MCGRP_CONFIG , GFP_KERNEL ) ;
}
static void hwsim_mcast_new_radio ( struct genl_info * info , struct hwsim_phy * phy )
{
struct sk_buff * mcast_skb ;
void * data ;
mcast_skb = genlmsg_new ( GENLMSG_DEFAULT_SIZE , GFP_KERNEL ) ;
if ( ! mcast_skb )
return ;
data = genlmsg_put ( mcast_skb , 0 , 0 , & hwsim_genl_family , 0 ,
MAC802154_HWSIM_CMD_NEW_RADIO ) ;
if ( ! data )
goto out_err ;
if ( append_radio_msg ( mcast_skb , phy ) < 0 )
goto out_err ;
genlmsg_end ( mcast_skb , data ) ;
hwsim_mcast_config_msg ( mcast_skb , info ) ;
return ;
out_err :
genlmsg_cancel ( mcast_skb , data ) ;
nlmsg_free ( mcast_skb ) ;
}
static void hwsim_edge_unsubscribe_me ( struct hwsim_phy * phy )
{
struct hwsim_phy * tmp ;
struct hwsim_edge * e ;
rcu_read_lock ( ) ;
/* going to all phy edges and remove phy from it */
list_for_each_entry ( tmp , & hwsim_phys , list ) {
list_for_each_entry_rcu ( e , & tmp - > edges , list ) {
if ( e - > endpoint - > idx = = phy - > idx ) {
list_del_rcu ( & e - > list ) ;
hwsim_free_edge ( e ) ;
}
}
}
rcu_read_unlock ( ) ;
synchronize_rcu ( ) ;
}
static int hwsim_subscribe_all_others ( struct hwsim_phy * phy )
{
struct hwsim_phy * sub ;
struct hwsim_edge * e ;
list_for_each_entry ( sub , & hwsim_phys , list ) {
e = hwsim_alloc_edge ( sub , 0xff ) ;
if ( ! e )
goto me_fail ;
list_add_rcu ( & e - > list , & phy - > edges ) ;
}
list_for_each_entry ( sub , & hwsim_phys , list ) {
e = hwsim_alloc_edge ( phy , 0xff ) ;
if ( ! e )
goto sub_fail ;
list_add_rcu ( & e - > list , & sub - > edges ) ;
}
return 0 ;
me_fail :
2018-08-12 16:24:56 -04:00
rcu_read_lock ( ) ;
list_for_each_entry_rcu ( e , & phy - > edges , list ) {
2018-07-14 12:33:05 -04:00
list_del_rcu ( & e - > list ) ;
hwsim_free_edge ( e ) ;
}
2018-08-12 16:24:56 -04:00
rcu_read_unlock ( ) ;
2018-07-14 12:33:05 -04:00
sub_fail :
hwsim_edge_unsubscribe_me ( phy ) ;
return - ENOMEM ;
}
static int hwsim_add_one ( struct genl_info * info , struct device * dev ,
bool init )
{
struct ieee802154_hw * hw ;
struct hwsim_phy * phy ;
struct hwsim_pib * pib ;
int idx ;
int err ;
idx = hwsim_radio_idx + + ;
hw = ieee802154_alloc_hw ( sizeof ( * phy ) , & hwsim_ops ) ;
if ( ! hw )
return - ENOMEM ;
phy = hw - > priv ;
phy - > hw = hw ;
/* 868 MHz BPSK 802.15.4-2003 */
hw - > phy - > supported . channels [ 0 ] | = 1 ;
/* 915 MHz BPSK 802.15.4-2003 */
hw - > phy - > supported . channels [ 0 ] | = 0x7fe ;
/* 2.4 GHz O-QPSK 802.15.4-2003 */
hw - > phy - > supported . channels [ 0 ] | = 0x7FFF800 ;
/* 868 MHz ASK 802.15.4-2006 */
hw - > phy - > supported . channels [ 1 ] | = 1 ;
/* 915 MHz ASK 802.15.4-2006 */
hw - > phy - > supported . channels [ 1 ] | = 0x7fe ;
/* 868 MHz O-QPSK 802.15.4-2006 */
hw - > phy - > supported . channels [ 2 ] | = 1 ;
/* 915 MHz O-QPSK 802.15.4-2006 */
hw - > phy - > supported . channels [ 2 ] | = 0x7fe ;
/* 2.4 GHz CSS 802.15.4a-2007 */
hw - > phy - > supported . channels [ 3 ] | = 0x3fff ;
/* UWB Sub-gigahertz 802.15.4a-2007 */
hw - > phy - > supported . channels [ 4 ] | = 1 ;
/* UWB Low band 802.15.4a-2007 */
hw - > phy - > supported . channels [ 4 ] | = 0x1e ;
/* UWB High band 802.15.4a-2007 */
hw - > phy - > supported . channels [ 4 ] | = 0xffe0 ;
/* 750 MHz O-QPSK 802.15.4c-2009 */
hw - > phy - > supported . channels [ 5 ] | = 0xf ;
/* 750 MHz MPSK 802.15.4c-2009 */
hw - > phy - > supported . channels [ 5 ] | = 0xf0 ;
/* 950 MHz BPSK 802.15.4d-2009 */
hw - > phy - > supported . channels [ 6 ] | = 0x3ff ;
/* 950 MHz GFSK 802.15.4d-2009 */
hw - > phy - > supported . channels [ 6 ] | = 0x3ffc00 ;
ieee802154_random_extended_addr ( & hw - > phy - > perm_extended_addr ) ;
/* hwsim phy channel 13 as default */
hw - > phy - > current_channel = 13 ;
pib = kzalloc ( sizeof ( * pib ) , GFP_KERNEL ) ;
if ( ! pib ) {
err = - ENOMEM ;
goto err_pib ;
}
2018-08-07 19:32:49 -04:00
rcu_assign_pointer ( phy - > pib , pib ) ;
2018-07-14 12:33:05 -04:00
phy - > idx = idx ;
INIT_LIST_HEAD ( & phy - > edges ) ;
hw - > flags = IEEE802154_HW_PROMISCUOUS ;
hw - > parent = dev ;
err = ieee802154_register_hw ( hw ) ;
if ( err )
goto err_reg ;
mutex_lock ( & hwsim_phys_lock ) ;
if ( init ) {
err = hwsim_subscribe_all_others ( phy ) ;
2018-08-08 03:10:39 +00:00
if ( err < 0 ) {
mutex_unlock ( & hwsim_phys_lock ) ;
2018-07-14 12:33:05 -04:00
goto err_reg ;
2018-08-08 03:10:39 +00:00
}
2018-07-14 12:33:05 -04:00
}
list_add_tail ( & phy - > list , & hwsim_phys ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
hwsim_mcast_new_radio ( info , phy ) ;
return idx ;
err_reg :
kfree ( pib ) ;
err_pib :
ieee802154_free_hw ( phy - > hw ) ;
return err ;
}
static void hwsim_del ( struct hwsim_phy * phy )
{
2018-08-07 19:32:49 -04:00
struct hwsim_pib * pib ;
2018-07-14 12:33:05 -04:00
hwsim_edge_unsubscribe_me ( phy ) ;
list_del ( & phy - > list ) ;
2018-08-07 19:32:49 -04:00
rcu_read_lock ( ) ;
pib = rcu_dereference ( phy - > pib ) ;
rcu_read_unlock ( ) ;
kfree_rcu ( pib , rcu ) ;
2018-07-14 12:33:05 -04:00
ieee802154_unregister_hw ( phy - > hw ) ;
ieee802154_free_hw ( phy - > hw ) ;
}
static int hwsim_probe ( struct platform_device * pdev )
{
struct hwsim_phy * phy , * tmp ;
int err , i ;
for ( i = 0 ; i < 2 ; i + + ) {
err = hwsim_add_one ( NULL , & pdev - > dev , true ) ;
if ( err < 0 )
goto err_slave ;
}
dev_info ( & pdev - > dev , " Added 2 mac802154 hwsim hardware radios \n " ) ;
return 0 ;
err_slave :
mutex_lock ( & hwsim_phys_lock ) ;
list_for_each_entry_safe ( phy , tmp , & hwsim_phys , list )
hwsim_del ( phy ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return err ;
}
static int hwsim_remove ( struct platform_device * pdev )
{
struct hwsim_phy * phy , * tmp ;
mutex_lock ( & hwsim_phys_lock ) ;
list_for_each_entry_safe ( phy , tmp , & hwsim_phys , list )
hwsim_del ( phy ) ;
mutex_unlock ( & hwsim_phys_lock ) ;
return 0 ;
}
static struct platform_driver mac802154hwsim_driver = {
. probe = hwsim_probe ,
. remove = hwsim_remove ,
. driver = {
. name = " mac802154_hwsim " ,
} ,
} ;
static __init int hwsim_init_module ( void )
{
int rc ;
rc = genl_register_family ( & hwsim_genl_family ) ;
if ( rc )
return rc ;
mac802154hwsim_dev = platform_device_register_simple ( " mac802154_hwsim " ,
- 1 , NULL , 0 ) ;
if ( IS_ERR ( mac802154hwsim_dev ) ) {
rc = PTR_ERR ( mac802154hwsim_dev ) ;
goto platform_dev ;
}
rc = platform_driver_register ( & mac802154hwsim_driver ) ;
if ( rc < 0 )
goto platform_drv ;
return 0 ;
platform_drv :
genl_unregister_family ( & hwsim_genl_family ) ;
platform_dev :
platform_device_unregister ( mac802154hwsim_dev ) ;
return rc ;
}
static __exit void hwsim_remove_module ( void )
{
genl_unregister_family ( & hwsim_genl_family ) ;
platform_driver_unregister ( & mac802154hwsim_driver ) ;
platform_device_unregister ( mac802154hwsim_dev ) ;
}
module_init ( hwsim_init_module ) ;
module_exit ( hwsim_remove_module ) ;