Эх сурвалжийг харах

Merge branch 'ieee802154-for-davem-2018-08-06' of git://git.kernel.org/pub/scm/linux/kernel/git/sschmidt/wpan-next

Stefan Schmidt says:

====================
pull-request: ieee802154-next 2018-08-06

An update from ieee802154 for *net-next*

Romuald added a socket option to get the LQI value of the received datagram.
Alexander added a new hardware simulation driver modelled after hwsim of the
wireless people. It allows runtime configuration for new nodes and edges over a
netlink interface (a config utlity is making its way into wpan-tools).
We also have three fixes in here. One from Colin which is more of a cleanup and
two from Alex fixing tailroom and single frame space problems.
I would normally put the last two into my fixes tree, but given we are already
in -rc8 I simply put them here and added a cc: stable to them.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
David S. Miller 7 жил өмнө
parent
commit
de7de576ec

+ 11 - 0
drivers/net/ieee802154/Kconfig

@@ -115,3 +115,14 @@ config IEEE802154_MCR20A
 
 	  This driver can also be built as a module. To do so, say M here.
 	  the module will be called 'mcr20a'.
+
+config IEEE802154_HWSIM
+	depends on IEEE802154_DRIVERS && MAC802154
+	tristate "Simulated radio testing tool for mac802154"
+	---help---
+	  This driver is a developer testing tool that can be used to test
+	  IEEE 802.15.4 networking stack (mac802154) functionality. This is not
+	  needed for normal wpan usage and is only for testing.
+
+	  This driver can also be built as a module. To do so say M here.
+	  The module will be called 'mac802154_hwsim'.

+ 1 - 0
drivers/net/ieee802154/Makefile

@@ -7,3 +7,4 @@ obj-$(CONFIG_IEEE802154_ATUSB) += atusb.o
 obj-$(CONFIG_IEEE802154_ADF7242) += adf7242.o
 obj-$(CONFIG_IEEE802154_CA8210) += ca8210.o
 obj-$(CONFIG_IEEE802154_MCR20A) += mcr20a.o
+obj-$(CONFIG_IEEE802154_HWSIM) += mac802154_hwsim.o

+ 3 - 0
drivers/net/ieee802154/fakelb.c

@@ -254,6 +254,9 @@ static __init int fakelb_init_module(void)
 {
 	ieee802154fake_dev = platform_device_register_simple(
 			     "ieee802154fakelb", -1, NULL, 0);
+
+	pr_warn("fakelb driver is marked as deprecated, please use mac802154_hwsim!\n");
+
 	return platform_driver_register(&ieee802154fake_driver);
 }
 

+ 919 - 0
drivers/net/ieee802154/mac802154_hwsim.c

@@ -0,0 +1,919 @@
+/*
+ * 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>
+#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 __rcu LIST_HEAD(hwsim_ifup_phys);
+
+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;
+	struct hwsim_edge_info *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 __rcu edges;
+
+	struct list_head list;
+	struct list_head list_ifup;
+};
+
+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;
+
+	pib_old = phy->pib;
+	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;
+	list_add_rcu(&phy->list_ifup, &hwsim_ifup_phys);
+	synchronize_rcu();
+
+	return 0;
+}
+
+static void hwsim_hw_stop(struct ieee802154_hw *hw)
+{
+	struct hwsim_phy *phy = hw->priv;
+
+	phy->suspended = true;
+	list_del_rcu(&phy->list_ifup);
+	synchronize_rcu();
+}
+
+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;
+	e->info = einfo;
+	e->endpoint = endpoint;
+
+	return e;
+}
+
+static void hwsim_free_edge(struct hwsim_edge *e)
+{
+	kfree_rcu(e->info, 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(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(edge_attrs, MAC802154_HWSIM_EDGE_ATTR_MAX + 1,
+			     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;
+
+	if (nla_parse_nested(edge_attrs, MAC802154_HWSIM_EDGE_ATTR_MAX + 1,
+			     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 (!info) {
+		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:
+	list_for_each_entry(phy, &hwsim_phys, list) {
+		list_del_rcu(&e->list);
+		hwsim_free_edge(e);
+	}
+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;
+	}
+
+	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)
+			goto err_reg;
+	}
+	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)
+{
+	hwsim_edge_unsubscribe_me(phy);
+
+	list_del(&phy->list);
+	kfree_rcu(phy->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 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);

+ 73 - 0
drivers/net/ieee802154/mac802154_hwsim.h

@@ -0,0 +1,73 @@
+#ifndef __MAC802154_HWSIM_H
+#define __MAC802154_HWSIM_H
+
+/* mac802154 hwsim netlink commands
+ *
+ * @MAC802154_HWSIM_CMD_UNSPEC: unspecified command to catch error
+ * @MAC802154_HWSIM_CMD_GET_RADIO: fetch information about existing radios
+ * @MAC802154_HWSIM_CMD_SET_RADIO: change radio parameters during runtime
+ * @MAC802154_HWSIM_CMD_NEW_RADIO: create a new radio with the given parameters
+ *	returns the radio ID (>= 0) or negative on errors, if successful
+ *	then multicast the result
+ * @MAC802154_HWSIM_CMD_DEL_RADIO: destroy a radio, reply is multicasted
+ * @MAC802154_HWSIM_CMD_GET_EDGE: fetch information about existing edges
+ * @MAC802154_HWSIM_CMD_SET_EDGE: change edge parameters during runtime
+ * @MAC802154_HWSIM_CMD_DEL_EDGE: delete edges between radios
+ * @MAC802154_HWSIM_CMD_NEW_EDGE: create a new edge between two radios
+ * @__MAC802154_HWSIM_CMD_MAX: enum limit
+ */
+enum {
+	MAC802154_HWSIM_CMD_UNSPEC,
+
+	MAC802154_HWSIM_CMD_GET_RADIO,
+	MAC802154_HWSIM_CMD_SET_RADIO,
+	MAC802154_HWSIM_CMD_NEW_RADIO,
+	MAC802154_HWSIM_CMD_DEL_RADIO,
+
+	MAC802154_HWSIM_CMD_GET_EDGE,
+	MAC802154_HWSIM_CMD_SET_EDGE,
+	MAC802154_HWSIM_CMD_DEL_EDGE,
+	MAC802154_HWSIM_CMD_NEW_EDGE,
+
+	__MAC802154_HWSIM_CMD_MAX,
+};
+
+#define MAC802154_HWSIM_CMD_MAX (__MAC802154_HWSIM_MAX - 1)
+
+/* mac802154 hwsim netlink attributes
+ *
+ * @MAC802154_HWSIM_ATTR_UNSPEC: unspecified attribute to catch error
+ * @MAC802154_HWSIM_ATTR_RADIO_ID: u32 attribute to identify the radio
+ * @MAC802154_HWSIM_ATTR_EDGE: nested attribute of edges
+ * @MAC802154_HWSIM_ATTR_EDGES: list if nested attributes which contains the
+ *	edge information according the radio id
+ * @__MAC802154_HWSIM_ATTR_MAX: enum limit
+ */
+enum {
+	MAC802154_HWSIM_ATTR_UNSPEC,
+	MAC802154_HWSIM_ATTR_RADIO_ID,
+	MAC802154_HWSIM_ATTR_RADIO_EDGE,
+	MAC802154_HWSIM_ATTR_RADIO_EDGES,
+	__MAC802154_HWSIM_ATTR_MAX,
+};
+
+#define MAC802154_HWSIM_ATTR_MAX (__MAC802154_HWSIM_ATTR_MAX - 1)
+
+/* mac802154 hwsim edge netlink attributes
+ *
+ * @MAC802154_HWSIM_EDGE_ATTR_UNSPEC: unspecified attribute to catch error
+ * @MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID: radio id where the edge points to
+ * @MAC802154_HWSIM_EDGE_ATTR_LQI: LQI value which the endpoint radio will
+ *	receive for this edge
+ * @__MAC802154_HWSIM_ATTR_MAX: enum limit
+ */
+enum {
+	MAC802154_HWSIM_EDGE_ATTR_UNSPEC,
+	MAC802154_HWSIM_EDGE_ATTR_ENDPOINT_ID,
+	MAC802154_HWSIM_EDGE_ATTR_LQI,
+	__MAC802154_HWSIM_EDGE_ATTR_MAX,
+};
+
+#define MAC802154_HWSIM_EDGE_ATTR_MAX (__MAC802154_HWSIM_EDGE_ATTR_MAX - 1)
+
+#endif /* __MAC802154_HWSIM_H */

+ 1 - 0
include/net/af_ieee802154.h

@@ -56,6 +56,7 @@ struct sockaddr_ieee802154 {
 #define WPAN_WANTACK		0
 #define WPAN_SECURITY		1
 #define WPAN_SECURITY_LEVEL	2
+#define WPAN_WANTLQI		3
 
 #define WPAN_SECURITY_DEFAULT	0
 #define WPAN_SECURITY_OFF	1

+ 0 - 5
net/ieee802154/6lowpan/reassembly.c

@@ -40,9 +40,6 @@ static int lowpan_frag_reasm(struct lowpan_frag_queue *fq,
 static void lowpan_frag_init(struct inet_frag_queue *q, const void *a)
 {
 	const struct frag_lowpan_compare_key *key = a;
-	struct lowpan_frag_queue *fq;
-
-	fq = container_of(q, struct lowpan_frag_queue, q);
 
 	BUILD_BUG_ON(sizeof(*key) > sizeof(q->key));
 	memcpy(&q->key, key, sizeof(*key));
@@ -52,10 +49,8 @@ static void lowpan_frag_expire(struct timer_list *t)
 {
 	struct inet_frag_queue *frag = from_timer(frag, t, timer);
 	struct frag_queue *fq;
-	struct net *net;
 
 	fq = container_of(frag, struct frag_queue, q);
-	net = container_of(fq->q.net, struct net, ieee802154_lowpan.frags);
 
 	spin_lock(&fq->q.lock);
 

+ 18 - 3
net/ieee802154/6lowpan/tx.c

@@ -265,9 +265,24 @@ netdev_tx_t lowpan_xmit(struct sk_buff *skb, struct net_device *ldev)
 	/* We must take a copy of the skb before we modify/replace the ipv6
 	 * header as the header could be used elsewhere
 	 */
-	skb = skb_unshare(skb, GFP_ATOMIC);
-	if (!skb)
-		return NET_XMIT_DROP;
+	if (unlikely(skb_headroom(skb) < ldev->needed_headroom ||
+		     skb_tailroom(skb) < ldev->needed_tailroom)) {
+		struct sk_buff *nskb;
+
+		nskb = skb_copy_expand(skb, ldev->needed_headroom,
+				       ldev->needed_tailroom, GFP_ATOMIC);
+		if (likely(nskb)) {
+			consume_skb(skb);
+			skb = nskb;
+		} else {
+			kfree_skb(skb);
+			return NET_XMIT_DROP;
+		}
+	} else {
+		skb = skb_unshare(skb, GFP_ATOMIC);
+		if (!skb)
+			return NET_XMIT_DROP;
+	}
 
 	ret = lowpan_header(skb, ldev, &dgram_size, &dgram_offset);
 	if (ret < 0) {

+ 17 - 0
net/ieee802154/socket.c

@@ -25,6 +25,7 @@
 #include <linux/termios.h>	/* For TIOCOUTQ/INQ */
 #include <linux/list.h>
 #include <linux/slab.h>
+#include <linux/socket.h>
 #include <net/datalink.h>
 #include <net/psnap.h>
 #include <net/sock.h>
@@ -452,6 +453,7 @@ struct dgram_sock {
 	unsigned int bound:1;
 	unsigned int connected:1;
 	unsigned int want_ack:1;
+	unsigned int want_lqi:1;
 	unsigned int secen:1;
 	unsigned int secen_override:1;
 	unsigned int seclevel:3;
@@ -486,6 +488,7 @@ static int dgram_init(struct sock *sk)
 	struct dgram_sock *ro = dgram_sk(sk);
 
 	ro->want_ack = 1;
+	ro->want_lqi = 0;
 	return 0;
 }
 
@@ -713,6 +716,7 @@ static int dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	size_t copied = 0;
 	int err = -EOPNOTSUPP;
 	struct sk_buff *skb;
+	struct dgram_sock *ro = dgram_sk(sk);
 	DECLARE_SOCKADDR(struct sockaddr_ieee802154 *, saddr, msg->msg_name);
 
 	skb = skb_recv_datagram(sk, flags, noblock, &err);
@@ -744,6 +748,13 @@ static int dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 		*addr_len = sizeof(*saddr);
 	}
 
+	if (ro->want_lqi) {
+		err = put_cmsg(msg, SOL_IEEE802154, WPAN_WANTLQI,
+			       sizeof(uint8_t), &(mac_cb(skb)->lqi));
+		if (err)
+			goto done;
+	}
+
 	if (flags & MSG_TRUNC)
 		copied = skb->len;
 done:
@@ -847,6 +858,9 @@ static int dgram_getsockopt(struct sock *sk, int level, int optname,
 	case WPAN_WANTACK:
 		val = ro->want_ack;
 		break;
+	case WPAN_WANTLQI:
+		val = ro->want_lqi;
+		break;
 	case WPAN_SECURITY:
 		if (!ro->secen_override)
 			val = WPAN_SECURITY_DEFAULT;
@@ -892,6 +906,9 @@ static int dgram_setsockopt(struct sock *sk, int level, int optname,
 	case WPAN_WANTACK:
 		ro->want_ack = !!val;
 		break;
+	case WPAN_WANTLQI:
+		ro->want_lqi = !!val;
+		break;
 	case WPAN_SECURITY:
 		if (!ns_capable(net->user_ns, CAP_NET_ADMIN) &&
 		    !ns_capable(net->user_ns, CAP_NET_RAW)) {

+ 14 - 1
net/mac802154/tx.c

@@ -63,8 +63,21 @@ ieee802154_tx(struct ieee802154_local *local, struct sk_buff *skb)
 	int ret;
 
 	if (!(local->hw.flags & IEEE802154_HW_TX_OMIT_CKSUM)) {
-		u16 crc = crc_ccitt(0, skb->data, skb->len);
+		struct sk_buff *nskb;
+		u16 crc;
+
+		if (unlikely(skb_tailroom(skb) < IEEE802154_FCS_LEN)) {
+			nskb = skb_copy_expand(skb, 0, IEEE802154_FCS_LEN,
+					       GFP_ATOMIC);
+			if (likely(nskb)) {
+				consume_skb(skb);
+				skb = nskb;
+			} else {
+				goto err_tx;
+			}
+		}
 
+		crc = crc_ccitt(0, skb->data, skb->len);
 		put_unaligned_le16(crc, skb_put(skb, 2));
 	}