diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /drivers/net/ieee802154/mac802154_hwsim.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'drivers/net/ieee802154/mac802154_hwsim.c')
-rw-r--r-- | drivers/net/ieee802154/mac802154_hwsim.c | 1092 |
1 files changed, 1092 insertions, 0 deletions
diff --git a/drivers/net/ieee802154/mac802154_hwsim.c b/drivers/net/ieee802154/mac802154_hwsim.c new file mode 100644 index 000000000..8445c2189 --- /dev/null +++ b/drivers/net/ieee802154/mac802154_hwsim.c @@ -0,0 +1,1092 @@ +// 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; + 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; + 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_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 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: + 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); |