summaryrefslogblamecommitdiff
path: root/drivers/net/ieee802154/mac802154_hwsim.c
blob: 2c2483bbe780aa905acb16784cf6cb43e3af6237 (plain) (tree)
1
2
3
4
5
6
7
                                        





                                                        








                                                      
                            


                            
                                  










                                                                                 

















                                                           
                                            
                                                   











                                   
                                           











                                    
                               

                              












                                                                    
                                                                          

                                                                       



                                         
                                                


                               
                                             
 

                               



                                                
                                     
 




                                          
                                                                          

                                         

                              
 

                                        
                                                                               
                          
 

                   
 






                                                                   
 

                                        
                                                                                   
                          
 
                   

 

















                                                                           
                                                                     






















































































                                                                                             


























                                                                            
                                                                                      












                                                   
 







                                                   




                                                                   















                                                                                    









                                                           
                                               














































                                                                          

                                                                           





                                                       

                                                                                 






































                                                                             
                
















































                                                                          
                                               









































































                                                                                       
                                           






                                                 






                                         









                                                                         
                                                          


                                                          
                                                                                                                                                          
























































                                                                            
                                                          


                                                          
                                                                                                                                                          




































                                                                            
                                                  




                                 
                                                          


                                                          
                                                                                                                                                          

                               
                                                                 














                                                                            
                     







                                                          

                                                                                           
                                          
                                                  




















                                                                                  
                                                     

                                                     
                                                                                




                                                     
                                                                                




                                                     
                                                                                




                                                    
                                                                                




                                                    
                                                                                




                                                    
                                                                                








                                                               
                                    
                              

                                                
                                                          




















































































                                                                                

                                       
        

                                                       


                                       
                          





























































                                                                      
                          

                                                                      
                                          


                                    
                                              








                                                      

                                                       
                                           
                 







                                               

                                          








                                            
                              
                             
 


                                       

                        



                                                       



                                        


























                                                                          
                                                      






                                                             



                                                       
                                   


























                                                                               
                                                       

                                                   











                                                           
// SPDX-License-Identifier: GPL-2.0-only
/*
 * HWSIM IEEE 802.15.4 interface
 *
 * (C) 2018 Mojatau, Alexander Aring <aring@mojatau.com>
 * Copyright 2007-2012 Siemens AG
 *
 * 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>
#include <linux/rtnetlink.h>
#include <linux/netdevice.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#include <net/ieee802154_netdev.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 ieee802154_hw_addr_filt filt;
	enum ieee802154_filtering_level filt_level;

	struct rcu_head rcu;
};

struct hwsim_edge_info {
	u8 lqi;

	struct rcu_head rcu;
};

struct hwsim_edge {
	struct hwsim_phy *endpoint;
	struct hwsim_edge_info __rcu *info;

	struct list_head list;
	struct rcu_head rcu;
};

struct hwsim_phy {
	struct ieee802154_hw *hw;
	u32 idx;

	struct hwsim_pib __rcu *pib;

	bool suspended;
	struct list_head edges;

	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_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel,
			    struct ieee802154_hw_addr_filt *filt,
			    enum ieee802154_filtering_level filt_level)
{
	struct hwsim_phy *phy = hw->priv;
	struct hwsim_pib *pib, *pib_old;

	pib = kzalloc(sizeof(*pib), GFP_ATOMIC);
	if (!pib)
		return -ENOMEM;

	pib_old = rtnl_dereference(phy->pib);

	pib->page = page;
	pib->channel = channel;
	pib->filt.short_addr = filt->short_addr;
	pib->filt.pan_id = filt->pan_id;
	pib->filt.ieee_addr = filt->ieee_addr;
	pib->filt.pan_coord = filt->pan_coord;
	pib->filt_level = filt_level;

	rcu_assign_pointer(phy->pib, pib);
	kfree_rcu(pib_old, rcu);
	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;
	int ret;

	rcu_read_lock();
	pib = rcu_dereference(phy->pib);
	ret = hwsim_update_pib(hw, page, channel, &pib->filt, pib->filt_level);
	rcu_read_unlock();

	return ret;
}

static int hwsim_hw_addr_filt(struct ieee802154_hw *hw,
			      struct ieee802154_hw_addr_filt *filt,
			      unsigned long changed)
{
	struct hwsim_phy *phy = hw->priv;
	struct hwsim_pib *pib;
	int ret;

	rcu_read_lock();
	pib = rcu_dereference(phy->pib);
	ret = hwsim_update_pib(hw, pib->page, pib->channel, filt, pib->filt_level);
	rcu_read_unlock();

	return ret;
}

static void hwsim_hw_receive(struct ieee802154_hw *hw, struct sk_buff *skb,
			     u8 lqi)
{
	struct ieee802154_hdr hdr;
	struct hwsim_phy *phy = hw->priv;
	struct hwsim_pib *pib;

	rcu_read_lock();
	pib = rcu_dereference(phy->pib);

	if (!pskb_may_pull(skb, 3)) {
		dev_dbg(hw->parent, "invalid frame\n");
		goto drop;
	}

	memcpy(&hdr, skb->data, 3);

	/* Level 4 filtering: Frame fields validity */
	if (pib->filt_level == IEEE802154_FILTERING_4_FRAME_FIELDS) {
		/* a) Drop reserved frame types */
		switch (mac_cb(skb)->type) {
		case IEEE802154_FC_TYPE_BEACON:
		case IEEE802154_FC_TYPE_DATA:
		case IEEE802154_FC_TYPE_ACK:
		case IEEE802154_FC_TYPE_MAC_CMD:
			break;
		default:
			dev_dbg(hw->parent, "unrecognized frame type 0x%x\n",
				mac_cb(skb)->type);
			goto drop;
		}

		/* b) Drop reserved frame versions */
		switch (hdr.fc.version) {
		case IEEE802154_2003_STD:
		case IEEE802154_2006_STD:
		case IEEE802154_STD:
			break;
		default:
			dev_dbg(hw->parent,
				"unrecognized frame version 0x%x\n",
				hdr.fc.version);
			goto drop;
		}

		/* c) PAN ID constraints */
		if ((mac_cb(skb)->dest.mode == IEEE802154_ADDR_LONG ||
		     mac_cb(skb)->dest.mode == IEEE802154_ADDR_SHORT) &&
		    mac_cb(skb)->dest.pan_id != pib->filt.pan_id &&
		    mac_cb(skb)->dest.pan_id != cpu_to_le16(IEEE802154_PANID_BROADCAST)) {
			dev_dbg(hw->parent,
				"unrecognized PAN ID %04x\n",
				le16_to_cpu(mac_cb(skb)->dest.pan_id));
			goto drop;
		}

		/* d1) Short address constraints */
		if (mac_cb(skb)->dest.mode == IEEE802154_ADDR_SHORT &&
		    mac_cb(skb)->dest.short_addr != pib->filt.short_addr &&
		    mac_cb(skb)->dest.short_addr != cpu_to_le16(IEEE802154_ADDR_BROADCAST)) {
			dev_dbg(hw->parent,
				"unrecognized short address %04x\n",
				le16_to_cpu(mac_cb(skb)->dest.short_addr));
			goto drop;
		}

		/* d2) Extended address constraints */
		if (mac_cb(skb)->dest.mode == IEEE802154_ADDR_LONG &&
		    mac_cb(skb)->dest.extended_addr != pib->filt.ieee_addr) {
			dev_dbg(hw->parent,
				"unrecognized long address 0x%016llx\n",
				mac_cb(skb)->dest.extended_addr);
			goto drop;
		}

		/* d4) Specific PAN coordinator case (no parent) */
		if ((mac_cb(skb)->type == IEEE802154_FC_TYPE_DATA ||
		     mac_cb(skb)->type == IEEE802154_FC_TYPE_MAC_CMD) &&
		    mac_cb(skb)->dest.mode == IEEE802154_ADDR_NONE) {
			dev_dbg(hw->parent,
				"relaying is not supported\n");
			goto drop;
		}

		/* e) Beacon frames follow specific PAN ID rules */
		if (mac_cb(skb)->type == IEEE802154_FC_TYPE_BEACON &&
		    pib->filt.pan_id != cpu_to_le16(IEEE802154_PANID_BROADCAST) &&
		    mac_cb(skb)->dest.pan_id != pib->filt.pan_id) {
			dev_dbg(hw->parent,
				"invalid beacon PAN ID %04x\n",
				le16_to_cpu(mac_cb(skb)->dest.pan_id));
			goto drop;
		}
	}

	rcu_read_unlock();

	ieee802154_rx_irqsafe(hw, skb, lqi);

	return;

drop:
	rcu_read_unlock();
	kfree_skb(skb);
}

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)
				hwsim_hw_receive(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)
{
	enum ieee802154_filtering_level filt_level;
	struct hwsim_phy *phy = hw->priv;
	struct hwsim_pib *pib;
	int ret;

	if (on)
		filt_level = IEEE802154_FILTERING_NONE;
	else
		filt_level = IEEE802154_FILTERING_4_FRAME_FIELDS;

	rcu_read_lock();
	pib = rcu_dereference(phy->pib);
	ret = hwsim_update_pib(hw, pib->page, pib->channel, &pib->filt, filt_level);
	rcu_read_unlock();

	return ret;
}

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,
	.set_hw_addr_filt = hwsim_hw_addr_filt,
};

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_noflag(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_noflag(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;

	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;
		}

		res = 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;
	rcu_assign_pointer(e->info, einfo);
	e->endpoint = endpoint;

	return e;
}

static void hwsim_free_edge(struct hwsim_edge *e)
{
	struct hwsim_edge_info *einfo;

	rcu_read_lock();
	einfo = rcu_dereference(e->info);
	rcu_read_unlock();

	kfree_rcu(einfo, rcu);
	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_deprecated(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;

	if (nla_parse_nested_deprecated(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]);

	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, *einfo_old;
	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;

	if (nla_parse_nested_deprecated(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] ||
	    !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);
	if (!einfo) {
		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;
			einfo_old = rcu_replace_pointer(e->info, einfo,
							lockdep_is_held(&hwsim_phys_lock));
			rcu_read_unlock();
			kfree_rcu(einfo_old, rcu);
			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_small_ops hwsim_nl_ops[] = {
	{
		.cmd = MAC802154_HWSIM_CMD_NEW_RADIO,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.doit = hwsim_new_radio_nl,
		.flags = GENL_UNS_ADMIN_PERM,
	},
	{
		.cmd = MAC802154_HWSIM_CMD_DEL_RADIO,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.doit = hwsim_del_radio_nl,
		.flags = GENL_UNS_ADMIN_PERM,
	},
	{
		.cmd = MAC802154_HWSIM_CMD_GET_RADIO,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.doit = hwsim_get_radio_nl,
		.dumpit = hwsim_dump_radio_nl,
	},
	{
		.cmd = MAC802154_HWSIM_CMD_NEW_EDGE,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.doit = hwsim_new_edge_nl,
		.flags = GENL_UNS_ADMIN_PERM,
	},
	{
		.cmd = MAC802154_HWSIM_CMD_DEL_EDGE,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.doit = hwsim_del_edge_nl,
		.flags = GENL_UNS_ADMIN_PERM,
	},
	{
		.cmd = MAC802154_HWSIM_CMD_SET_EDGE,
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
		.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,
	.policy = hwsim_genl_policy,
	.module = THIS_MODULE,
	.small_ops = hwsim_nl_ops,
	.n_small_ops = ARRAY_SIZE(hwsim_nl_ops),
	.resv_start_op = MAC802154_HWSIM_CMD_NEW_EDGE + 1,
	.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;

sub_fail:
	hwsim_edge_unsubscribe_me(phy);
me_fail:
	rcu_read_lock();
	list_for_each_entry_rcu(e, &phy->edges, list) {
		list_del_rcu(&e->list);
		hwsim_free_edge(e);
	}
	rcu_read_unlock();
	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;
	}

	pib->channel = 13;
	pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST);
	pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST);
	rcu_assign_pointer(phy->pib, pib);
	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);
		if (err < 0) {
			mutex_unlock(&hwsim_phys_lock);
			goto err_subscribe;
		}
	}
	list_add_tail(&phy->list, &hwsim_phys);
	mutex_unlock(&hwsim_phys_lock);

	hwsim_mcast_new_radio(info, phy);

	return idx;

err_subscribe:
	ieee802154_unregister_hw(phy->hw);
err_reg:
	kfree(pib);
err_pib:
	ieee802154_free_hw(phy->hw);
	return err;
}

static void hwsim_del(struct hwsim_phy *phy)
{
	struct hwsim_pib *pib;
	struct hwsim_edge *e;

	hwsim_edge_unsubscribe_me(phy);

	list_del(&phy->list);

	rcu_read_lock();
	list_for_each_entry_rcu(e, &phy->edges, list) {
		list_del_rcu(&e->list);
		hwsim_free_edge(e);
	}
	pib = rcu_dereference(phy->pib);
	rcu_read_unlock();

	kfree_rcu(pib, rcu);

	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 void 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);
}

static struct platform_driver mac802154hwsim_driver = {
	.probe = hwsim_probe,
	.remove_new = 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:
	platform_device_unregister(mac802154hwsim_dev);
platform_dev:
	genl_unregister_family(&hwsim_genl_family);
	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);