// 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/mac802154.h>
#include <net/cfg802154.h>
#include <net/genetlink.h>
#include "mac802154_hwsim.h"
MODULE_DESCRIPTION("Software simulator of IEEE 802.15.4 radio(s) for mac802154");
MODULE_LICENSE("GPL");
static LIST_HEAD(hwsim_phys);
static DEFINE_MUTEX(hwsim_phys_lock);
static struct platform_device *mac802154hwsim_dev;
/* MAC802154_HWSIM netlink family */
static struct genl_family hwsim_genl_family;
static int hwsim_radio_idx;
enum hwsim_multicast_groups {
HWSIM_MCGRP_CONFIG,
};
static const struct genl_multicast_group hwsim_mcgrps[] = {
[HWSIM_MCGRP_CONFIG] = { .name = "config", },
};
struct hwsim_pib {
u8 page;
u8 channel;
struct rcu_head rcu;
};
struct hwsim_edge_info {
u8 lqi;
struct rcu_head rcu;
};
struct hwsim_edge {
struct hwsim_phy *endpoint;
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_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 = rtnl_dereference(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, ¤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)
ieee802154_rx_irqsafe(e->endpoint->hw, newskb,
einfo->lqi);
}
}
rcu_read_unlock();
ieee802154_xmit_complete(hw, skb, false);
return 0;
}
static int hwsim_hw_start(struct ieee802154_hw *hw)
{
struct hwsim_phy *phy = hw->priv;
phy->suspended = false;
return 0;
}
static void hwsim_hw_stop(struct ieee802154_hw *hw)
{
struct hwsim_phy *phy = hw->priv;
phy->suspended = true;
}
static int
hwsim_set_promiscuous_mode(struct ieee802154_hw *hw, const bool on)
{
return 0;
}
static const struct ieee802154_ops hwsim_ops = {
.owner = THIS_MODULE,
.xmit_async = hwsim_hw_xmit,
.ed = hwsim_hw_ed,
.set_channel = hwsim_hw_channel,
.start = hwsim_hw_start,
.stop = hwsim_hw_stop,
.set_promiscuous_mode = hwsim_set_promiscuous_mode,
};
static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
{
return hwsim_add_one(info, &mac802154hwsim_dev->dev, false);
}
static int