This PR introduces support for use space tools such as wmediumd in the mac802154_hwsim kernel module, similar to the existing support for Wi-Fi simulations via mac80211_hwsim. With this addition, it becomes possible to simulate interference and control transmission behavior in IEEE 802.15.4 networks using a userspace backend. Key changes: * Introduced communication between mac802154_hwsim and the wmediumd daemon via netlink. * Included a packet queue (skb_queue) that ensures packets are only transmitted after validation by wmediumd. * Integrated support to consult wmediumd before transmitting packets, enabling the simulation of interference or transmission failure. Backward Compatibility: The current behavior of mac802154_hwsim remains unchanged when wmediumd is not running. If the daemon is unavailable or disabled, the module continues to operate normally, ensuring full compatibility with existing tools and workflows. Purpose: Enable more realistic simulations of IEEE 802.15.4-based sensor and IoT networks, accounting for factors like interference, link quality, and connectivity. This fills a gap in current emulation tools where mac802154_hwsim lacks channel behavior modeling. Testing: A wmediumd release for mac802154_hwsim is available at https://github.com/ramonfontes/wmediumd_802154/ and you can follow updated instructions there. However, you can also follow the steps below: 1. Building wmediumd_802154: $ cd wmediumd_802154 $ make $ sudo make install 2. Loading the modified mac802154_hwsim kernel module (this PR) $ sudo modprobe mac802154_hwsim radios=3 3. Running wmediumd_802154 $ cd tests $ sudo ./interference.sh $ sudo wmediumd_802154 -s -c tree.cfg 4. Terminal 1: Pinging virtual sensors The network topology of tree.cfg looks like below: s1 -- s0 -- s2 s2 is too far away from s0 and the communication between s0 and s2 is not possible. You can now test connectivity between the virtual sensors using IPv6 link-local addresses. Ping from sensor0 to sensor1: ping -c 2 fe80::2 PING fe80::2 (fe80::2) 56 data bytes 64 bytes from fe80::2%pan0: icmp_seq=1 ttl=64 time=2.33 ms 64 bytes from fe80::2%pan0: icmp_seq=2 ttl=64 time=1.77 ms --- fe80::2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1002ms rtt min/avg/max/mdev = 1.770/2.051/2.332/0.281 ms Ping from sensor0 to sensor2: ping -c 2 fe80::3 PING fe80::3 (fe80::3) 56 data bytes --- fe80::3 ping statistics --- 2 packets transmitted, 0 received, 100% packet loss, time 1054ms Simulated custom topologies using Mininet-WiFi with 802.15.4 support is also possible by running https://github.com/intrig-unicamp/mininet-wifi/blob/9f2cba0d6ebcfebf6c19161b81b6186cfa110cca/examples/wmediumd_interference_lowpan.py Signed-off-by: Ramon Fontes <ramonreisfontes@xxxxxxxxx> --- drivers/net/ieee802154/mac802154_hwsim.c | 975 ++++++++++++++++++++++- drivers/net/ieee802154/mac802154_hwsim.h | 57 +- 2 files changed, 999 insertions(+), 33 deletions(-) diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c index 2f7520454..782aba5fa 100644 --- a/drivers/net/ieee802154/mac802154_hwsim.c +++ b/drivers/net/ieee802154/mac802154_hwsim.c @@ -14,6 +14,7 @@ #include <linux/module.h> #include <linux/timer.h> #include <linux/platform_device.h> +#include <linux/debugfs.h> #include <linux/rtnetlink.h> #include <linux/netdevice.h> #include <linux/device.h> @@ -22,13 +23,23 @@ #include <net/mac802154.h> #include <net/cfg802154.h> #include <net/genetlink.h> +#include <net/netns/generic.h> +#include <linux/rhashtable.h> #include "mac802154_hwsim.h" +#include <linux/virtio.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_config.h> MODULE_DESCRIPTION("Software simulator of IEEE 802.15.4 radio(s) for mac802154"); MODULE_LICENSE("GPL"); +static int radios = 2; +module_param(radios, int, 0444); +MODULE_PARM_DESC(radios, "Number of simulated radios"); + static LIST_HEAD(hwsim_phys); static DEFINE_MUTEX(hwsim_phys_lock); +static int hwsim_radios_generation = 1; static struct platform_device *mac802154hwsim_dev; @@ -37,6 +48,50 @@ static struct genl_family hwsim_genl_family; static int hwsim_radio_idx; +static unsigned int hwsim_net_id; + +static DEFINE_IDA(hwsim_netgroup_ida); + +static struct class *hwsim_class; + +struct hwsim_net { + int netgroup; + u32 wmediumd; +}; + +struct hwsim_cb { + uintptr_t cookie; +}; + +static inline u32 hwsim_net_get_wmediumd(struct net *net) +{ + struct hwsim_net *hwsim_net = net_generic(net, hwsim_net_id); + + return hwsim_net->wmediumd; +} + +static inline void hwsim_net_set_wmediumd(struct net *net, u32 portid) +{ + struct hwsim_net *hwsim_net = net_generic(net, hwsim_net_id); + + hwsim_net->wmediumd = portid; +} + +static inline int hwsim_net_get_netgroup(struct net *net) +{ + struct hwsim_net *hwsim_net = net_generic(net, hwsim_net_id); + + return hwsim_net->netgroup; +} + +static inline int hwsim_net_set_netgroup(struct net *net) +{ + struct hwsim_net *hwsim_net = net_generic(net, hwsim_net_id); + + hwsim_net->netgroup = ida_alloc(&hwsim_netgroup_ida, GFP_KERNEL); + return hwsim_net->netgroup >= 0 ? 0 : -ENOMEM; +} + enum hwsim_multicast_groups { HWSIM_MCGRP_CONFIG, }; @@ -45,6 +100,14 @@ static const struct genl_multicast_group hwsim_mcgrps[] = { [HWSIM_MCGRP_CONFIG] = { .name = "config", }, }; +struct hwsim_pending_tx { + struct ieee802154_hw *hw; + struct sk_buff *skb; + struct list_head list; +}; + +static LIST_HEAD(pending_tx_list); + struct hwsim_pib { u8 page; u8 channel; @@ -73,13 +136,42 @@ struct hwsim_phy { u32 idx; struct hwsim_pib __rcu *pib; + bool rht_inserted; + u8 ieee_addr[8]; + + struct rhash_head rht; + struct dentry *debugfs; + atomic_t pending_cookie; + struct sk_buff_head pending; + struct sk_buff *pending_skb; + struct device *dev; + struct mutex mutex; + + bool destroy_on_close; + u32 portid; bool suspended; struct list_head edges; struct list_head list; + + /* group shared by radios created in the same netns */ + int netgroup; + /* wmediumd portid responsible for netgroup of this radio */ + u32 wmediumd; }; +static const struct rhashtable_params hwsim_rht_params = { + .nelem_hint = 2, + .automatic_shrinking = true, + .key_len = 8, /* ETH_ALEN */ + .key_offset = offsetof(struct hwsim_phy, ieee_addr), + .head_offset = offsetof(struct hwsim_phy, rht), +}; + +static DEFINE_SPINLOCK(hwsim_radio_lock); +static struct rhashtable hwsim_radios_rht; + static int hwsim_add_one(struct genl_info *info, struct device *dev, bool init); static void hwsim_del(struct hwsim_phy *phy); @@ -91,6 +183,17 @@ static int hwsim_hw_ed(struct ieee802154_hw *hw, u8 *level) return 0; } +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 int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, struct ieee802154_hw_addr_filt *filt, enum ieee802154_filtering_level filt_level) @@ -114,9 +217,15 @@ static int hwsim_update_pib(struct ieee802154_hw *hw, u8 page, u8 channel, rcu_assign_pointer(phy->pib, pib); kfree_rcu(pib_old, rcu); + return 0; } +static struct hwsim_phy *get_hwsim_data_ref_from_addr(const u8 *addr) +{ + return rhashtable_lookup_fast(&hwsim_radios_rht, addr, hwsim_rht_params); +} + static int hwsim_hw_channel(struct ieee802154_hw *hw, u8 page, u8 channel) { struct hwsim_phy *phy = hw->priv; @@ -147,6 +256,140 @@ static int hwsim_hw_addr_filt(struct ieee802154_hw *hw, return ret; } +static int hwsim_unicast_netgroup(struct hwsim_phy *data, + struct sk_buff *skb, int portid) +{ + struct net *net; + bool found = false; + int res = -ENOENT; + + rcu_read_lock(); + for_each_net_rcu(net) { + if (data->netgroup == hwsim_net_get_netgroup(net)) { + res = genlmsg_unicast(net, skb, portid); + found = true; + break; + } + } + rcu_read_unlock(); + + if (!found) + nlmsg_free(skb); + + return res; +} + +#if IS_REACHABLE(CONFIG_VIRTIO) + +/* MAC80211_HWSIM virtio queues */ +static struct virtqueue *hwsim_vqs[HWSIM_NUM_VQS]; +static bool hwsim_virtio_enabled; +static DEFINE_SPINLOCK(hwsim_virtio_lock); + +static void hwsim_virtio_rx_work(struct work_struct *work); +static DECLARE_WORK(hwsim_virtio_rx, hwsim_virtio_rx_work); + +static int hwsim_tx_virtio(struct hwsim_phy *phy, + struct sk_buff *skb) +{ + struct scatterlist sg[1]; + unsigned long flags; + int err; + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + if (!hwsim_virtio_enabled) { + err = -ENODEV; + goto out_free; + } + + sg_init_one(sg, skb->head, skb_end_offset(skb)); + err = virtqueue_add_outbuf(hwsim_vqs[HWSIM_VQ_TX], sg, 1, skb, + GFP_ATOMIC); + if (err) + goto out_free; + virtqueue_kick(hwsim_vqs[HWSIM_VQ_TX]); + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + return 0; + +out_free: + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + nlmsg_free(skb); + return err; +} +#else +/* cause a linker error if this ends up being needed */ +extern int hwsim_tx_virtio(struct hwsim_phy *phy, + struct sk_buff *skb); +#define hwsim_virtio_enabled false +#endif + +static void mac802154_hwsim_tx_frame_nl(struct ieee802154_hw *hw, struct sk_buff *my_skb, + int dst_portid) +{ + struct sk_buff *skb; + struct hwsim_phy *phy = hw->priv; + void *msg_head; + unsigned int hwsim_flags = 0; + uintptr_t cookie; + + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_ATOMIC); + + if (skb == NULL) + goto nla_put_failure; + + msg_head = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, + MAC802154_HWSIM_CMD_FRAME); + if (msg_head == NULL) { + pr_debug("mac802154_hwsim: problem with msg_head\n"); + goto nla_put_failure; + } + + u8 addr_buf[8]; + put_unaligned_le64(hw->phy->perm_extended_addr, addr_buf); + put_unaligned_le64(hw->phy->perm_extended_addr, phy->ieee_addr); + + if (nla_put(skb, MAC802154_HWSIM_ATTR_ADDR_TRANSMITTER, + 8, addr_buf)) + goto nla_put_failure; + + /* We get the skb->data */ + if (nla_put(skb, MAC802154_HWSIM_ATTR_FRAME, my_skb->len, my_skb->data)) + goto nla_put_failure; + + if (nla_put_u32(skb, MAC802154_HWSIM_ATTR_FLAGS, hwsim_flags)) + goto nla_put_failure; + + /* We create a cookie to identify this skb */ + cookie = atomic_inc_return(&phy->pending_cookie); + + if (nla_put_u64_64bit(skb, MAC802154_HWSIM_ATTR_COOKIE, cookie, MAC802154_HWSIM_ATTR_PAD)) + goto nla_put_failure; + + genlmsg_end(skb, msg_head); + + struct sk_buff *skb_2 = skb_clone(my_skb, GFP_ATOMIC); + if (!skb_2) + goto err_free_txskb; + + if (hwsim_virtio_enabled) { + if (hwsim_tx_virtio(phy, skb)) + goto err_free_txskb; + } else { + if (hwsim_unicast_netgroup(phy, skb, dst_portid)) + goto err_free_txskb; + } + + /* Enqueue the packet */ + skb_queue_tail(&phy->pending, skb_2); + + return; + +nla_put_failure: + nlmsg_free(skb); +err_free_txskb: + pr_debug("mac802154_hwsim: error occurred in %s\n", __func__); +} + static void hwsim_hw_receive(struct ieee802154_hw *hw, struct sk_buff *skb, u8 lqi) { @@ -259,25 +502,142 @@ static int hwsim_hw_xmit(struct ieee802154_hw *hw, struct sk_buff *skb) struct hwsim_pib *current_pib, *endpoint_pib; struct hwsim_edge_info *einfo; struct hwsim_edge *e; + u32 _portid; WARN_ON(current_phy->suspended); + /* wmediumd mode check */ + _portid = READ_ONCE(current_phy->wmediumd); + + if (_portid || hwsim_virtio_enabled){ + mac802154_hwsim_tx_frame_nl(hw, skb, _portid); + + ieee802154_xmit_complete(hw, skb, false); + return 0; + } + else{ + 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_cloned_frame_received_nl(struct sk_buff *skb_2, + struct genl_info *info) +{ + struct hwsim_phy *data2; + struct ieee802154_hdr *hdr; + const u8 *dst; + int frame_data_len; + void *frame_data; + struct sk_buff *skb = NULL; + struct hwsim_pib *current_pib, *endpoint_pib; + struct hwsim_edge *e; + struct hwsim_edge_info *einfo; + + if (!info->attrs[MAC802154_HWSIM_ATTR_ADDR_RECEIVER] || + !info->attrs[MAC802154_HWSIM_ATTR_FRAME]) + goto out; + + dst = (void *)nla_data(info->attrs[MAC802154_HWSIM_ATTR_ADDR_RECEIVER]); + frame_data_len = nla_len(info->attrs[MAC802154_HWSIM_ATTR_FRAME]); + frame_data = (void *)nla_data(info->attrs[MAC802154_HWSIM_ATTR_FRAME]); + + if (frame_data_len < IEEE802154_MIN_HDR_LEN || + frame_data_len > IEEE802154_MAX_FRAME_LEN) + goto err; + + /* Allocate new skb here */ + skb = alloc_skb(frame_data_len, GFP_KERNEL); + if (skb == NULL) + goto err; + + /* Copy the data */ + skb_put_data(skb, frame_data, frame_data_len); + + ieee802154_hdr_peek_addrs(skb, hdr); + + u8 *frame = frame_data; + u16 fcf = le16_to_cpup((__le16 *)frame); + u8 *ptr = frame + 3; + + u8 src_addr[8]; + u8 dst_addr_mode = (fcf >> 10) & 0x3; + u8 src_addr_mode = (fcf >> 14) & 0x3; + bool intra_pan = (fcf >> 6) & 0x1; + + /* skip the destination */ + if (dst_addr_mode == IEEE802154_ADDR_SHORT) { + /* dest_pan + short addr */ + ptr += 2 + 2; + } else if (dst_addr_mode == IEEE802154_ADDR_LONG) { + /* dest_pan + extended addr */ + ptr += 2 + 8; + } + + /* src_pan */ + if (!intra_pan) + ptr += 2; + + if (src_addr_mode == IEEE802154_ADDR_SHORT) + goto out; + else if (src_addr_mode == IEEE802154_ADDR_LONG) + memcpy(src_addr, ptr, 8); + + data2 = get_hwsim_data_ref_from_addr(src_addr); + if (!data2) + goto out; + + struct ieee802154_hw *hw = data2->hw; + + struct hwsim_phy *current_phy = hw->priv; + 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 - */ + * 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); + current_pib->channel == endpoint_pib->channel) { + + struct ieee802154_hw *hw1 = e->endpoint->hw; + struct hwsim_phy *phy1 = hw1->priv; + u8* addr64 = phy1->ieee_addr; + if (dst_addr_mode == IEEE802154_ADDR_LONG && memcmp(dst, addr64, 8) != 0) + continue; + + 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); @@ -285,8 +645,12 @@ static int hwsim_hw_xmit(struct ieee802154_hw *hw, struct sk_buff *skb) } rcu_read_unlock(); - ieee802154_xmit_complete(hw, skb, false); return 0; +err: + pr_debug("mac802154_hwsim: error occurred in %s\n", __func__); +out: + dev_kfree_skb(skb); + return -EINVAL; } static int hwsim_hw_start(struct ieee802154_hw *hw) @@ -342,6 +706,36 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info) return hwsim_add_one(info, &mac802154hwsim_dev->dev, false); } +static void hwsim_mcast_del_radio(int id, const char *hwname, + struct genl_info *info) +{ + struct sk_buff *skb; + void *data; + int ret; + + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) + return; + + data = genlmsg_put(skb, 0, 0, &hwsim_genl_family, 0, + MAC802154_HWSIM_CMD_DEL_RADIO); + if (!data) + goto error; + + ret = nla_put_u32(skb, MAC802154_HWSIM_ATTR_RADIO_ID, id); + if (ret < 0) + goto error; + + genlmsg_end(skb, data); + + hwsim_mcast_config_msg(skb, info); + + return; + +error: + nlmsg_free(skb); +} + static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info) { struct hwsim_phy *phy, *tmp; @@ -365,6 +759,19 @@ static int hwsim_del_radio_nl(struct sk_buff *msg, struct genl_info *info) return -ENODEV; } +static void hwsim_del_radio(struct hwsim_phy *data, + const char *hwname, + struct genl_info *info) +{ + hwsim_mcast_del_radio(data->idx, hwname, info); + debugfs_remove_recursive(data->debugfs); + ieee802154_unregister_hw(data->hw); + device_release_driver(data->dev); + device_unregister(data->dev); + ieee802154_free_hw(data->hw); +} + + static int append_radio_msg(struct sk_buff *skb, struct hwsim_phy *phy) { struct nlattr *nl_edges, *nl_edge; @@ -459,6 +866,7 @@ static int hwsim_get_radio_nl(struct sk_buff *msg, struct genl_info *info) 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]); @@ -739,12 +1147,103 @@ static int hwsim_set_edge_lqi(struct sk_buff *msg, struct genl_info *info) return -ENOENT; } +static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, + 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; + u8 lqi; + u32 v0, v1; + + 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; +} + +static void hwsim_register_wmediumd(struct net *net, u32 portid) +{ + struct hwsim_phy *data; + + hwsim_net_set_wmediumd(net, portid); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry(data, &hwsim_phys, list) { + if (data->netgroup == hwsim_net_get_netgroup(net)) + data->wmediumd = portid; + } + spin_unlock_bh(&hwsim_radio_lock); +} + +static int hwsim_register_received_nl(struct sk_buff *msg, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + int chans = 1; + + if (chans > 1) + return -EOPNOTSUPP; + + if (hwsim_net_get_wmediumd(net)) + return -EBUSY; + + hwsim_register_wmediumd(net, info->snd_portid); + + pr_debug("mac802154_hwsim: received a REGISTER, " + "switching to wmediumd mode with pid %d\n", info->snd_portid); + + return 0; +} + /* 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 }, + [MAC802154_HWSIM_ATTR_ADDR_RECEIVER] = { .type = NLA_BINARY, .len = 8 }, + [MAC802154_HWSIM_ATTR_ADDR_TRANSMITTER] = { .type = NLA_BINARY, .len = 8 }, + [MAC802154_HWSIM_ATTR_FRAME] = { .type = NLA_BINARY, + .len = IEEE802154_MAX_FRAME_LEN }, }; /* Generic Netlink operations array */ @@ -785,6 +1284,22 @@ static const struct genl_small_ops hwsim_nl_ops[] = { .doit = hwsim_set_edge_lqi, .flags = GENL_UNS_ADMIN_PERM, }, + { + .cmd = MAC802154_HWSIM_CMD_REGISTER, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = hwsim_register_received_nl, + .flags = GENL_UNS_ADMIN_PERM, + }, + { + .cmd = MAC802154_HWSIM_CMD_TX_INFO_FRAME, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = hwsim_tx_info_frame_received_nl, + }, + { + .cmd = MAC802154_HWSIM_CMD_FRAME, + .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, + .doit = hwsim_cloned_frame_received_nl, + }, }; static struct genl_family hwsim_genl_family __ro_after_init = { @@ -792,25 +1307,15 @@ static struct genl_family hwsim_genl_family __ro_after_init = { .version = 1, .maxattr = MAC802154_HWSIM_ATTR_MAX, .policy = hwsim_genl_policy, + .netnsok = true, .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, + .resv_start_op = MAC802154_HWSIM_CMD_TX_INFO_FRAME + 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; @@ -899,8 +1404,10 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, struct ieee802154_hw *hw; struct hwsim_phy *phy; struct hwsim_pib *pib; + struct net *net; int idx; int err; + int ret; idx = hwsim_radio_idx++; @@ -908,9 +1415,17 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, if (!hw) return -ENOMEM; + if (info) + net = genl_info_net(info); + else + net = &init_net; + wpan_phy_net_set(hw->phy, net); + phy = hw->priv; phy->hw = hw; + skb_queue_head_init(&phy->pending); + /* 868 MHz BPSK 802.15.4-2003 */ hw->phy->supported.channels[0] |= 1; /* 915 MHz BPSK 802.15.4-2003 */ @@ -943,6 +1458,7 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, hw->phy->supported.channels[6] |= 0x3ffc00; hw->phy->perm_extended_addr = cpu_to_le64(((u64)0x02 << 56) | ((u64)idx)); + memcpy(phy->ieee_addr, &hw->phy->perm_extended_addr, 8); /* hwsim phy channel 13 as default */ hw->phy->current_channel = 13; @@ -952,6 +1468,9 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, goto err_pib; } + if (info) + phy->portid = info->snd_portid; + pib->channel = 13; pib->filt.short_addr = cpu_to_le16(IEEE802154_ADDR_BROADCAST); pib->filt.pan_id = cpu_to_le16(IEEE802154_PANID_BROADCAST); @@ -962,6 +1481,9 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, hw->flags = IEEE802154_HW_PROMISCUOUS; hw->parent = dev; + phy->netgroup = hwsim_net_get_netgroup(net); + phy->wmediumd = hwsim_net_get_wmediumd(net); + err = ieee802154_register_hw(hw); if (err) goto err_reg; @@ -977,10 +1499,20 @@ static int hwsim_add_one(struct genl_info *info, struct device *dev, list_add_tail(&phy->list, &hwsim_phys); mutex_unlock(&hwsim_phys_lock); + ret = rhashtable_insert_fast(&hwsim_radios_rht, &phy->rht, hwsim_rht_params); + if (ret < 0) { + pr_err("Error in adding PHY into rhashtable: %d\n", ret); + goto failed_final_insert; + } + phy->rht_inserted = true; + hwsim_mcast_new_radio(info, phy); return idx; +failed_final_insert: + debugfs_remove_recursive(phy->debugfs); + ieee802154_unregister_hw(phy->hw); err_subscribe: ieee802154_unregister_hw(phy->hw); err_reg: @@ -1011,6 +1543,7 @@ static void hwsim_del(struct hwsim_phy *phy) ieee802154_unregister_hw(phy->hw); ieee802154_free_hw(phy->hw); + class_destroy(hwsim_class); } static int hwsim_probe(struct platform_device *pdev) @@ -1018,13 +1551,13 @@ static int hwsim_probe(struct platform_device *pdev) struct hwsim_phy *phy, *tmp; int err, i; - for (i = 0; i < 2; i++) { + for (i = 0; i < radios; 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"); + dev_info(&pdev->dev, "Added %d mac802154 hwsim hardware radios\n", radios); return 0; err_slave: @@ -1053,40 +1586,420 @@ static struct platform_driver mac802154hwsim_driver = { }, }; -static __init int hwsim_init_module(void) +static void remove_user_radios(u32 portid) +{ + struct hwsim_phy *entry, *tmp; + LIST_HEAD(list); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry_safe(entry, tmp, &hwsim_phys, list) { + if (entry->destroy_on_close && entry->portid == portid) { + list_move(&entry->list, &list); + rhashtable_remove_fast(&hwsim_radios_rht, &entry->rht, + hwsim_rht_params); + hwsim_radios_generation++; + } + } + spin_unlock_bh(&hwsim_radio_lock); + + list_for_each_entry_safe(entry, tmp, &list, list) { + list_del(&entry->list); + hwsim_del_radio(entry, wpan_phy_name(entry->hw->phy), + NULL); + } +} + +static int mac802154_hwsim_netlink_notify(struct notifier_block *nb, + unsigned long state, + void *_notify) +{ + struct netlink_notify *notify = _notify; + + if (state != NETLINK_URELEASE) + return NOTIFY_DONE; + + remove_user_radios(notify->portid); + + if (notify->portid == hwsim_net_get_wmediumd(notify->net)) { + printk(KERN_INFO "mac802154_hwsim: wmediumd released netlink" + " socket, switching to perfect channel medium\n"); + hwsim_register_wmediumd(notify->net, 0); + } + return NOTIFY_DONE; + +} + +static void __net_exit hwsim_exit_net(struct net *net) +{ + struct hwsim_phy *data, *tmp; + LIST_HEAD(list); + + spin_lock_bh(&hwsim_radio_lock); + list_for_each_entry_safe(data, tmp, &hwsim_phys, list) { + if (!net_eq(wpan_phy_net(data->hw->phy), net)) + continue; + + /* Radios created in init_net are returned to init_net. */ + if (data->netgroup == hwsim_net_get_netgroup(&init_net)) + continue; + + list_move(&data->list, &list); + rhashtable_remove_fast(&hwsim_radios_rht, &data->rht, + hwsim_rht_params); + hwsim_radios_generation++; + } + spin_unlock_bh(&hwsim_radio_lock); + + list_for_each_entry_safe(data, tmp, &list, list) { + list_del(&data->list); + hwsim_del_radio(data, + wpan_phy_name(data->hw->phy), + NULL); + } + + ida_free(&hwsim_netgroup_ida, hwsim_net_get_netgroup(net)); +} + +#if IS_REACHABLE(CONFIG_VIRTIO) + +static void hwsim_virtio_tx_done(struct virtqueue *vq) +{ + unsigned int len; + struct sk_buff *skb; + unsigned long flags; + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + while ((skb = virtqueue_get_buf(vq, &len))) + dev_kfree_skb_irq(skb); + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); +} + +static int hwsim_virtio_handle_cmd(struct sk_buff *skb) +{ + struct nlmsghdr *nlh; + struct genlmsghdr *gnlh; + struct nlattr *tb[MAC802154_HWSIM_ATTR_MAX + 1]; + struct genl_info info = {}; + int err; + + nlh = nlmsg_hdr(skb); + gnlh = nlmsg_data(nlh); + + if (skb->len < nlh->nlmsg_len) + return -EINVAL; + + err = genlmsg_parse(nlh, &hwsim_genl_family, tb, MAC802154_HWSIM_ATTR_MAX, + hwsim_genl_policy, NULL); + if (err) { + pr_err_ratelimited("hwsim: genlmsg_parse returned %d\n", err); + return err; + } + + info.attrs = tb; + + switch (gnlh->cmd) { + /*case MAC802154_HWSIM_CMD_FRAME: + hwsim_cloned_frame_received_nl(skb, &info); + break; + case MAC802154_HWSIM_CMD_TX_INFO_FRAME: + hwsim_tx_info_frame_received_nl(skb, &info); + break; + case HWSIM_CMD_REPORT_PMSR: + hwsim_pmsr_report_nl(skb, &info); + break;*/ + default: + pr_err_ratelimited("hwsim: invalid cmd: %d\n", gnlh->cmd); + return -EPROTO; + } + return 0; +} + +static void hwsim_virtio_rx_work(struct work_struct *work) +{ + struct virtqueue *vq; + unsigned int len; + struct sk_buff *skb; + struct scatterlist sg[1]; + int err; + unsigned long flags; + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + if (!hwsim_virtio_enabled) + goto out_unlock; + + skb = virtqueue_get_buf(hwsim_vqs[HWSIM_VQ_RX], &len); + if (!skb) + goto out_unlock; + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + + skb->data = skb->head; + skb_reset_tail_pointer(skb); + skb_put(skb, len); + hwsim_virtio_handle_cmd(skb); + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + if (!hwsim_virtio_enabled) { + dev_kfree_skb_irq(skb); + goto out_unlock; + } + vq = hwsim_vqs[HWSIM_VQ_RX]; + sg_init_one(sg, skb->head, skb_end_offset(skb)); + err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_ATOMIC); + if (WARN(err, "virtqueue_add_inbuf returned %d\n", err)) + dev_kfree_skb_irq(skb); + else + virtqueue_kick(vq); + schedule_work(&hwsim_virtio_rx); + +out_unlock: + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); +} + +static void hwsim_virtio_rx_done(struct virtqueue *vq) +{ + schedule_work(&hwsim_virtio_rx); +} + +static int init_vqs(struct virtio_device *vdev) +{ + struct virtqueue_info vqs_info[HWSIM_NUM_VQS] = { + [HWSIM_VQ_TX] = { "tx", hwsim_virtio_tx_done }, + [HWSIM_VQ_RX] = { "rx", hwsim_virtio_rx_done }, + }; + + return virtio_find_vqs(vdev, HWSIM_NUM_VQS, + hwsim_vqs, vqs_info, NULL); +} + +static int fill_vq(struct virtqueue *vq) +{ + int i, err; + struct sk_buff *skb; + struct scatterlist sg[1]; + + for (i = 0; i < virtqueue_get_vring_size(vq); i++) { + skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + sg_init_one(sg, skb->head, skb_end_offset(skb)); + err = virtqueue_add_inbuf(vq, sg, 1, skb, GFP_KERNEL); + if (err) { + nlmsg_free(skb); + return err; + } + } + virtqueue_kick(vq); + return 0; +} + +static void remove_vqs(struct virtio_device *vdev) +{ + int i; + + virtio_reset_device(vdev); + + for (i = 0; i < ARRAY_SIZE(hwsim_vqs); i++) { + struct virtqueue *vq = hwsim_vqs[i]; + struct sk_buff *skb; + + while ((skb = virtqueue_detach_unused_buf(vq))) + nlmsg_free(skb); + } + + vdev->config->del_vqs(vdev); +} + +static int hwsim_virtio_probe(struct virtio_device *vdev) +{ + int err; + unsigned long flags; + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + if (hwsim_virtio_enabled) { + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + return -EEXIST; + } + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + + err = init_vqs(vdev); + if (err) + return err; + + virtio_device_ready(vdev); + + err = fill_vq(hwsim_vqs[HWSIM_VQ_RX]); + if (err) + goto out_remove; + + spin_lock_irqsave(&hwsim_virtio_lock, flags); + hwsim_virtio_enabled = true; + spin_unlock_irqrestore(&hwsim_virtio_lock, flags); + + schedule_work(&hwsim_virtio_rx); + return 0; + +out_remove: + remove_vqs(vdev); + return err; +} + +static void hwsim_virtio_remove(struct virtio_device *vdev) +{ + hwsim_virtio_enabled = false; + + cancel_work_sync(&hwsim_virtio_rx); + + remove_vqs(vdev); +} + +/* MAC802154_HWSIM virtio device id table */ +static const struct virtio_device_id id_table[] = { + { 42, VIRTIO_DEV_ANY_ID }, + { 0 } +}; +MODULE_DEVICE_TABLE(virtio, id_table); + +static struct virtio_driver virtio_hwsim = { + .driver.name = KBUILD_MODNAME, + .id_table = id_table, + .probe = hwsim_virtio_probe, + .remove = hwsim_virtio_remove, +}; + +static int hwsim_register_virtio_driver(void) +{ + return register_virtio_driver(&virtio_hwsim); +} + +static void hwsim_unregister_virtio_driver(void) +{ + unregister_virtio_driver(&virtio_hwsim); +} +#else +static inline int hwsim_register_virtio_driver(void) +{ + return 0; +} + +static inline void hwsim_unregister_virtio_driver(void) +{ +} +#endif + +static struct notifier_block hwsim_netlink_notifier = { + .notifier_call = mac802154_hwsim_netlink_notify, +}; + +static int __init hwsim_init_netlink(void) { int rc; + printk(KERN_INFO "mac802154_hwsim: initializing netlink\n"); + rc = genl_register_family(&hwsim_genl_family); if (rc) - return rc; + goto failure; + + rc = netlink_register_notifier(&hwsim_netlink_notifier); + if (rc) { + genl_unregister_family(&hwsim_genl_family); + goto failure; + } + + return 0; + +failure: + pr_debug("mac802154_hwsim: error occurred in %s\n", __func__); + return -EINVAL; +} + +static __net_init int hwsim_init_net(struct net *net) +{ + return hwsim_net_set_netgroup(net); +} + +static struct pernet_operations hwsim_net_ops = { + .init = hwsim_init_net, + .exit = hwsim_exit_net, + .id = &hwsim_net_id, + .size = sizeof(struct hwsim_net), +}; + +static void hwsim_exit_netlink(void) +{ + /* unregister the notifier */ + netlink_unregister_notifier(&hwsim_netlink_notifier); + /* unregister the family */ + genl_unregister_family(&hwsim_genl_family); +} + +static __init int hwsim_init_module(void) +{ + int rc, err; + + if (radios < 0) + return -EINVAL; + + err = rhashtable_init(&hwsim_radios_rht, &hwsim_rht_params); + if (err) + return err; + + err = register_pernet_device(&hwsim_net_ops); + if (err) + goto out_free_rht; + + err = hwsim_init_netlink(); + if (err) + goto out_unregister_driver; + + err = hwsim_register_virtio_driver(); + if (err) + goto out_exit_netlink; + + hwsim_class = class_create("mac802154_hwsim"); + if (IS_ERR(hwsim_class)) { + err = PTR_ERR(hwsim_class); + goto out_exit_virtio; + } mac802154hwsim_dev = platform_device_register_simple("mac802154_hwsim", -1, NULL, 0); if (IS_ERR(mac802154hwsim_dev)) { rc = PTR_ERR(mac802154hwsim_dev); - goto platform_dev; + goto out_unregister_driver; } rc = platform_driver_register(&mac802154hwsim_driver); if (rc < 0) - goto platform_drv; + goto out_unregister_pernet; return 0; -platform_drv: - platform_device_unregister(mac802154hwsim_dev); -platform_dev: - genl_unregister_family(&hwsim_genl_family); +out_exit_virtio: + hwsim_unregister_virtio_driver(); +out_exit_netlink: + hwsim_exit_netlink(); +out_unregister_pernet: + unregister_pernet_device(&hwsim_net_ops); return rc; +out_unregister_driver: + platform_driver_unregister(&mac802154hwsim_driver); +out_free_rht: + rhashtable_destroy(&hwsim_radios_rht); + return err; } static __exit void hwsim_remove_module(void) { - genl_unregister_family(&hwsim_genl_family); + pr_debug("mac80211_hwsim: unregister radios\n"); + hwsim_unregister_virtio_driver(); + hwsim_exit_netlink(); + rhashtable_destroy(&hwsim_radios_rht); platform_driver_unregister(&mac802154hwsim_driver); platform_device_unregister(mac802154hwsim_dev); + unregister_pernet_device(&hwsim_net_ops); } module_init(hwsim_init_module); -module_exit(hwsim_remove_module); +module_exit(hwsim_remove_module); \ No newline at end of file diff --git a/drivers/net/ieee802154/mac802154_hwsim.h b/drivers/net/ieee802154/mac802154_hwsim.h index 6c6e30e38..b1d83ff75 100644 --- a/drivers/net/ieee802154/mac802154_hwsim.h +++ b/drivers/net/ieee802154/mac802154_hwsim.h @@ -1,8 +1,31 @@ #ifndef __MAC802154_HWSIM_H #define __MAC802154_HWSIM_H -/* mac802154 hwsim netlink commands + +#define IEEE802154_MAX_FRAME_LEN 127 +#define IEEE802154_MIN_HDR_LEN 3 +//#define IEEE802154_MAX_DATA_LEN (IEEE802154_PHY_FRAME_LEN - IEEE802154_MAX_HEADER_LEN) +/** + * enum hwsim_tx_control_flags - flags to describe transmission info/status + * + * These flags are used to give the wmediumd extra information in order to + * modify its behavior for each frame + * + * @HWSIM_TX_CTL_REQ_TX_STATUS: require TX status callback for this frame. + * @HWSIM_TX_CTL_NO_ACK: tell the wmediumd not to wait for an ack + * @HWSIM_TX_STAT_ACK: Frame was acknowledged * + */ +enum hwsim_tx_control_flags { + MAC802154_HWSIM_TX_CTL_REQ_TX_STATUS = BIT(0), + MAC802154_HWSIM_TX_CTL_NO_ACK = BIT(1), + MAC802154_HWSIM_TX_STAT_ACK = BIT(2), +}; + + +/* mac802154 hwsim netlink commands + * @HWSIM_CMD_REGISTER: request to register and received all broadcasted + * frames by any mac802154_hwsim radio device. * @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 @@ -28,6 +51,9 @@ enum { MAC802154_HWSIM_CMD_SET_EDGE, MAC802154_HWSIM_CMD_DEL_EDGE, MAC802154_HWSIM_CMD_NEW_EDGE, + MAC802154_HWSIM_CMD_REGISTER, + MAC802154_HWSIM_CMD_FRAME, + MAC802154_HWSIM_CMD_TX_INFO_FRAME, __MAC802154_HWSIM_CMD_MAX, }; @@ -48,6 +74,13 @@ enum { MAC802154_HWSIM_ATTR_RADIO_ID, MAC802154_HWSIM_ATTR_RADIO_EDGE, MAC802154_HWSIM_ATTR_RADIO_EDGES, + MAC802154_HWSIM_ATTR_COOKIE, + MAC802154_HWSIM_ATTR_PAD, + MAC802154_HWSIM_ATTR_FRAME, + MAC802154_HWSIM_ATTR_ADDR_TRANSMITTER, + MAC802154_HWSIM_ATTR_ADDR_RECEIVER, + MAC802154_HWSIM_ATTR_TX_INFO, + MAC802154_HWSIM_ATTR_FLAGS, __MAC802154_HWSIM_ATTR_MAX, }; @@ -70,4 +103,24 @@ enum { #define MAC802154_HWSIM_EDGE_ATTR_MAX (__MAC802154_HWSIM_EDGE_ATTR_MAX - 1) -#endif /* __MAC802154_HWSIM_H */ +/** + * DOC: Frame transmission support over virtio + * + * Frame transmission is also supported over virtio to allow communication + * with external entities. + */ + +/** + * enum hwsim_vqs - queues for virtio frame transmission + * + * @HWSIM_VQ_TX: send frames to external entity + * @HWSIM_VQ_RX: receive frames and transmission info reports + * @HWSIM_NUM_VQS: enum limit + */ +enum hwsim_vqs { + HWSIM_VQ_TX, + HWSIM_VQ_RX, + HWSIM_NUM_VQS, +}; + +#endif /* __MAC802154_HWSIM_H */ \ No newline at end of file -- 2.43.0