1093 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1093 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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, ¤t_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);
 |