ieee802154: Add support for user scanning requests
authorMiquel Raynal <miquel.raynal@bootlin.com>
Tue, 3 Jan 2023 16:56:39 +0000 (17:56 +0100)
committerStefan Schmidt <stefan@datenfreihafen.org>
Tue, 3 Jan 2023 18:31:03 +0000 (19:31 +0100)
The ieee802154 layer should be able to scan a set of channels in order
to look for beacons advertizing PANs. Supporting this involves adding
two user commands: triggering scans and aborting scans. The user should
also be notified when a new beacon is received and also upon scan
termination.

A scan request structure is created to list the requirements and to be
accessed asynchronously when changing channels or receiving beacons.

Mac layers may now implement the ->trigger_scan() and ->abort_scan()
hooks.

Co-developed-by: David Girault <david.girault@qorvo.com>
Signed-off-by: David Girault <david.girault@qorvo.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
Acked-by: Alexander Aring <aahringo@redhat.com>
Link: https://lore.kernel.org/r/20230103165644.432209-2-miquel.raynal@bootlin.com
Signed-off-by: Stefan Schmidt <stefan@datenfreihafen.org>
include/linux/ieee802154.h
include/net/cfg802154.h
include/net/nl802154.h
net/ieee802154/nl802154.c
net/ieee802154/nl802154.h
net/ieee802154/rdev-ops.h
net/ieee802154/trace.h

index 0303eb84d59610dff89d21eacc5875519821c196..b22e4147d3346e7d7722ad81d37c5ba1907fc97f 100644 (file)
@@ -44,6 +44,9 @@
 #define IEEE802154_SHORT_ADDR_LEN      2
 #define IEEE802154_PAN_ID_LEN          2
 
+/* Duration in superframe order */
+#define IEEE802154_MAX_SCAN_DURATION   14
+#define IEEE802154_ACTIVE_SCAN_DURATION        15
 #define IEEE802154_LIFS_PERIOD         40
 #define IEEE802154_SIFS_PERIOD         12
 #define IEEE802154_MAX_SIFS_FRAME_SIZE 18
index d09c393d229fd03e3f155ea687750fcad961ec8b..76d4f95e9974c7fca871fc1897ef4acfc7c35fe7 100644 (file)
@@ -18,6 +18,7 @@
 
 struct wpan_phy;
 struct wpan_phy_cca;
+struct cfg802154_scan_request;
 
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
 struct ieee802154_llsec_device_key;
@@ -67,6 +68,10 @@ struct cfg802154_ops {
                                struct wpan_dev *wpan_dev, bool mode);
        int     (*set_ackreq_default)(struct wpan_phy *wpan_phy,
                                      struct wpan_dev *wpan_dev, bool ackreq);
+       int     (*trigger_scan)(struct wpan_phy *wpan_phy,
+                               struct cfg802154_scan_request *request);
+       int     (*abort_scan)(struct wpan_phy *wpan_phy,
+                             struct wpan_dev *wpan_dev);
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
        void    (*get_llsec_table)(struct wpan_phy *wpan_phy,
                                   struct wpan_dev *wpan_dev,
@@ -278,6 +283,26 @@ struct ieee802154_coord_desc {
        bool gts_permit;
 };
 
+/**
+ * struct cfg802154_scan_request - Scan request
+ *
+ * @type: type of scan to be performed
+ * @page: page on which to perform the scan
+ * @channels: channels in te %page to be scanned
+ * @duration: time spent on each channel, calculated with:
+ *            aBaseSuperframeDuration * (2 ^ duration + 1)
+ * @wpan_dev: the wpan device on which to perform the scan
+ * @wpan_phy: the wpan phy on which to perform the scan
+ */
+struct cfg802154_scan_request {
+       enum nl802154_scan_types type;
+       u8 page;
+       u32 channels;
+       u8 duration;
+       struct wpan_dev *wpan_dev;
+       struct wpan_phy *wpan_phy;
+};
+
 struct ieee802154_llsec_key_id {
        u8 mode;
        u8 id;
index b79a89d5207c8abbabd8933079f6d5c162347eef..c267fa1c5aac417002e207e900716a0a846d3ea2 100644 (file)
@@ -73,6 +73,9 @@ enum nl802154_commands {
        NL802154_CMD_DEL_SEC_LEVEL,
 
        NL802154_CMD_SCAN_EVENT,
+       NL802154_CMD_TRIGGER_SCAN,
+       NL802154_CMD_ABORT_SCAN,
+       NL802154_CMD_SCAN_DONE,
 
        /* add new commands above here */
 
@@ -134,6 +137,13 @@ enum nl802154_attrs {
        NL802154_ATTR_NETNS_FD,
 
        NL802154_ATTR_COORDINATOR,
+       NL802154_ATTR_SCAN_TYPE,
+       NL802154_ATTR_SCAN_FLAGS,
+       NL802154_ATTR_SCAN_CHANNELS,
+       NL802154_ATTR_SCAN_PREAMBLE_CODES,
+       NL802154_ATTR_SCAN_MEAN_PRF,
+       NL802154_ATTR_SCAN_DURATION,
+       NL802154_ATTR_SCAN_DONE_REASON,
 
        /* add attributes here, update the policy in nl802154.c */
 
@@ -259,6 +269,54 @@ enum nl802154_coord {
        NL802154_COORD_MAX,
 };
 
+/**
+ * enum nl802154_scan_types - Scan types
+ *
+ * @__NL802154_SCAN_INVALID: scan type number 0 is reserved
+ * @NL802154_SCAN_ED: An ED scan allows a device to obtain a measure of the peak
+ *     energy in each requested channel
+ * @NL802154_SCAN_ACTIVE: Locate any coordinator transmitting Beacon frames using
+ *     a Beacon Request command
+ * @NL802154_SCAN_PASSIVE: Locate any coordinator transmitting Beacon frames
+ * @NL802154_SCAN_ORPHAN: Relocate coordinator following a loss of synchronisation
+ * @NL802154_SCAN_ENHANCED_ACTIVE: Same as Active using Enhanced Beacon Request
+ *     command instead of Beacon Request command
+ * @NL802154_SCAN_RIT_PASSIVE: Passive scan for RIT Data Request command frames
+ *     instead of Beacon frames
+ * @NL802154_SCAN_ATTR_MAX: Maximum SCAN attribute number
+ */
+enum nl802154_scan_types {
+       __NL802154_SCAN_INVALID,
+       NL802154_SCAN_ED,
+       NL802154_SCAN_ACTIVE,
+       NL802154_SCAN_PASSIVE,
+       NL802154_SCAN_ORPHAN,
+       NL802154_SCAN_ENHANCED_ACTIVE,
+       NL802154_SCAN_RIT_PASSIVE,
+
+       /* keep last */
+       NL802154_SCAN_ATTR_MAX,
+};
+
+/**
+ * enum nl802154_scan_done_reasons - End of scan reasons
+ *
+ * @__NL802154_SCAN_DONE_REASON_INVALID: scan done reason number 0 is reserved.
+ * @NL802154_SCAN_DONE_REASON_FINISHED: The scan just finished naturally after
+ *     going through all the requested and possible (complex) channels.
+ * @NL802154_SCAN_DONE_REASON_ABORTED: The scan was aborted upon user request.
+ *     a Beacon Request command
+ * @NL802154_SCAN_DONE_REASON_MAX: Maximum scan done reason attribute number.
+ */
+enum nl802154_scan_done_reasons {
+       __NL802154_SCAN_DONE_REASON_INVALID,
+       NL802154_SCAN_DONE_REASON_FINISHED,
+       NL802154_SCAN_DONE_REASON_ABORTED,
+
+       /* keep last */
+       NL802154_SCAN_DONE_REASON_MAX,
+};
+
 /**
  * enum nl802154_cca_modes - cca modes
  *
index 248ad5e4696944d0a8fe3396aa15ea2b62197184..11aa693af4492942c49521206f96a87d8d3eaa29 100644 (file)
@@ -221,6 +221,13 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
 
        [NL802154_ATTR_COORDINATOR] = { .type = NLA_NESTED },
 
+       [NL802154_ATTR_SCAN_TYPE] = { .type = NLA_U8 },
+       [NL802154_ATTR_SCAN_CHANNELS] = { .type = NLA_U32 },
+       [NL802154_ATTR_SCAN_PREAMBLE_CODES] = { .type = NLA_U64 },
+       [NL802154_ATTR_SCAN_MEAN_PRF] = { .type = NLA_U8 },
+       [NL802154_ATTR_SCAN_DURATION] = { .type = NLA_U8 },
+       [NL802154_ATTR_SCAN_DONE_REASON] = { .type = NLA_U8 },
+
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
        [NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, },
        [NL802154_ATTR_SEC_OUT_LEVEL] = { .type = NLA_U32, },
@@ -1384,6 +1391,203 @@ int nl802154_scan_event(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
 }
 EXPORT_SYMBOL_GPL(nl802154_scan_event);
 
+static int nl802154_trigger_scan(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg802154_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+       struct wpan_phy *wpan_phy = &rdev->wpan_phy;
+       struct cfg802154_scan_request *request;
+       u8 type;
+       int err;
+
+       /* Monitors are not allowed to perform scans */
+       if (wpan_dev->iftype == NL802154_IFTYPE_MONITOR)
+               return -EPERM;
+
+       request = kzalloc(sizeof(*request), GFP_KERNEL);
+       if (!request)
+               return -ENOMEM;
+
+       request->wpan_dev = wpan_dev;
+       request->wpan_phy = wpan_phy;
+
+       type = nla_get_u8(info->attrs[NL802154_ATTR_SCAN_TYPE]);
+       switch (type) {
+       case NL802154_SCAN_PASSIVE:
+               request->type = type;
+               break;
+       default:
+               pr_err("Unsupported scan type: %d\n", type);
+               err = -EINVAL;
+               goto free_request;
+       }
+
+       if (info->attrs[NL802154_ATTR_PAGE]) {
+               request->page = nla_get_u8(info->attrs[NL802154_ATTR_PAGE]);
+               if (request->page > IEEE802154_MAX_PAGE) {
+                       pr_err("Invalid page %d > %d\n",
+                              request->page, IEEE802154_MAX_PAGE);
+                       err = -EINVAL;
+                       goto free_request;
+               }
+       } else {
+               /* Use current page by default */
+               request->page = wpan_phy->current_page;
+       }
+
+       if (info->attrs[NL802154_ATTR_SCAN_CHANNELS]) {
+               request->channels = nla_get_u32(info->attrs[NL802154_ATTR_SCAN_CHANNELS]);
+               if (request->channels >= BIT(IEEE802154_MAX_CHANNEL + 1)) {
+                       pr_err("Invalid channels bitfield %x ≥ %lx\n",
+                              request->channels,
+                              BIT(IEEE802154_MAX_CHANNEL + 1));
+                       err = -EINVAL;
+                       goto free_request;
+               }
+       } else {
+               /* Scan all supported channels by default */
+               request->channels = wpan_phy->supported.channels[request->page];
+       }
+
+       if (info->attrs[NL802154_ATTR_SCAN_PREAMBLE_CODES] ||
+           info->attrs[NL802154_ATTR_SCAN_MEAN_PRF]) {
+               pr_err("Preamble codes and mean PRF not supported yet\n");
+               err = -EINVAL;
+               goto free_request;
+       }
+
+       if (info->attrs[NL802154_ATTR_SCAN_DURATION]) {
+               request->duration = nla_get_u8(info->attrs[NL802154_ATTR_SCAN_DURATION]);
+               if (request->duration > IEEE802154_MAX_SCAN_DURATION) {
+                       pr_err("Duration is out of range\n");
+                       err = -EINVAL;
+                       goto free_request;
+               }
+       } else {
+               /* Use maximum duration order by default */
+               request->duration = IEEE802154_MAX_SCAN_DURATION;
+       }
+
+       if (wpan_dev->netdev)
+               dev_hold(wpan_dev->netdev);
+
+       err = rdev_trigger_scan(rdev, request);
+       if (err) {
+               pr_err("Failure starting scanning (%d)\n", err);
+               goto free_device;
+       }
+
+       return 0;
+
+free_device:
+       if (wpan_dev->netdev)
+               dev_put(wpan_dev->netdev);
+free_request:
+       kfree(request);
+
+       return err;
+}
+
+static int nl802154_prep_scan_msg(struct sk_buff *msg,
+                                 struct cfg802154_registered_device *rdev,
+                                 struct wpan_dev *wpan_dev, u32 portid,
+                                 u32 seq, int flags, u8 cmd, u8 arg)
+{
+       void *hdr;
+
+       hdr = nl802154hdr_put(msg, portid, seq, flags, cmd);
+       if (!hdr)
+               return -ENOBUFS;
+
+       if (nla_put_u32(msg, NL802154_ATTR_WPAN_PHY, rdev->wpan_phy_idx))
+               goto nla_put_failure;
+
+       if (wpan_dev->netdev &&
+           nla_put_u32(msg, NL802154_ATTR_IFINDEX, wpan_dev->netdev->ifindex))
+               goto nla_put_failure;
+
+       if (nla_put_u64_64bit(msg, NL802154_ATTR_WPAN_DEV,
+                             wpan_dev_id(wpan_dev), NL802154_ATTR_PAD))
+               goto nla_put_failure;
+
+       if (cmd == NL802154_CMD_SCAN_DONE &&
+           nla_put_u8(msg, NL802154_ATTR_SCAN_DONE_REASON, arg))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+
+       return -EMSGSIZE;
+}
+
+static int nl802154_send_scan_msg(struct cfg802154_registered_device *rdev,
+                                 struct wpan_dev *wpan_dev, u8 cmd, u8 arg)
+{
+       struct sk_buff *msg;
+       int ret;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       ret = nl802154_prep_scan_msg(msg, rdev, wpan_dev, 0, 0, 0, cmd, arg);
+       if (ret < 0) {
+               nlmsg_free(msg);
+               return ret;
+       }
+
+       return genlmsg_multicast_netns(&nl802154_fam,
+                                      wpan_phy_net(&rdev->wpan_phy), msg, 0,
+                                      NL802154_MCGRP_SCAN, GFP_KERNEL);
+}
+
+int nl802154_scan_started(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev)
+{
+       struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(wpan_phy);
+       int err;
+
+       /* Ignore errors when there are no listeners */
+       err = nl802154_send_scan_msg(rdev, wpan_dev, NL802154_CMD_TRIGGER_SCAN, 0);
+       if (err == -ESRCH)
+               err = 0;
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(nl802154_scan_started);
+
+int nl802154_scan_done(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+                      enum nl802154_scan_done_reasons reason)
+{
+       struct cfg802154_registered_device *rdev = wpan_phy_to_rdev(wpan_phy);
+       int err;
+
+       /* Ignore errors when there are no listeners */
+       err = nl802154_send_scan_msg(rdev, wpan_dev, NL802154_CMD_SCAN_DONE, reason);
+       if (err == -ESRCH)
+               err = 0;
+
+       if (wpan_dev->netdev)
+               dev_put(wpan_dev->netdev);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(nl802154_scan_done);
+
+static int nl802154_abort_scan(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg802154_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+
+       /* Resources are released in the notification helper above */
+       return rdev_abort_scan(rdev, wpan_dev);
+}
+
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
 static const struct nla_policy nl802154_dev_addr_policy[NL802154_DEV_ADDR_ATTR_MAX + 1] = {
        [NL802154_DEV_ADDR_ATTR_PAN_ID] = { .type = NLA_U16 },
@@ -2474,6 +2678,22 @@ static const struct genl_ops nl802154_ops[] = {
                .internal_flags = NL802154_FLAG_NEED_NETDEV |
                                  NL802154_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL802154_CMD_TRIGGER_SCAN,
+               .doit = nl802154_trigger_scan,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL802154_FLAG_NEED_NETDEV |
+                                 NL802154_FLAG_CHECK_NETDEV_UP |
+                                 NL802154_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL802154_CMD_ABORT_SCAN,
+               .doit = nl802154_abort_scan,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL802154_FLAG_NEED_NETDEV |
+                                 NL802154_FLAG_CHECK_NETDEV_UP |
+                                 NL802154_FLAG_NEED_RTNL,
+       },
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
        {
                .cmd = NL802154_CMD_SET_SEC_PARAMS,
index 89b805500032a72b8a3a85dfbc28976c1634bcf3..cfa7134be747caca326e82ab4e1c74151b142bcc 100644 (file)
@@ -6,5 +6,8 @@ int nl802154_init(void);
 void nl802154_exit(void);
 int nl802154_scan_event(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
                        struct ieee802154_coord_desc *desc);
+int nl802154_scan_started(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev);
+int nl802154_scan_done(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev,
+                      enum nl802154_scan_done_reasons reason);
 
 #endif /* __IEEE802154_NL802154_H */
index 598f5af497753916ce8add52c7b88b4dbd807adf..e171d74c32515f66a23a54bb1c282eac69f1d1eb 100644 (file)
@@ -209,6 +209,34 @@ rdev_set_ackreq_default(struct cfg802154_registered_device *rdev,
        return ret;
 }
 
+static inline int rdev_trigger_scan(struct cfg802154_registered_device *rdev,
+                                   struct cfg802154_scan_request *request)
+{
+       int ret;
+
+       if (!rdev->ops->trigger_scan)
+               return -EOPNOTSUPP;
+
+       trace_802154_rdev_trigger_scan(&rdev->wpan_phy, request);
+       ret = rdev->ops->trigger_scan(&rdev->wpan_phy, request);
+       trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+       return ret;
+}
+
+static inline int rdev_abort_scan(struct cfg802154_registered_device *rdev,
+                                 struct wpan_dev *wpan_dev)
+{
+       int ret;
+
+       if (!rdev->ops->abort_scan)
+               return -EOPNOTSUPP;
+
+       trace_802154_rdev_abort_scan(&rdev->wpan_phy, wpan_dev);
+       ret = rdev->ops->abort_scan(&rdev->wpan_phy, wpan_dev);
+       trace_802154_rdev_return_int(&rdev->wpan_phy, ret);
+       return ret;
+}
+
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
 /* TODO this is already a nl802154, so move into ieee802154 */
 static inline void
index 19c2e5d60e765d50f96bb02a033241351627dcf4..e5405f737ded55e570da8bca52849e8ed31d155a 100644 (file)
@@ -295,6 +295,46 @@ TRACE_EVENT(802154_rdev_set_ackreq_default,
                WPAN_DEV_PR_ARG, BOOL_TO_STR(__entry->ackreq))
 );
 
+TRACE_EVENT(802154_rdev_trigger_scan,
+       TP_PROTO(struct wpan_phy *wpan_phy,
+                struct cfg802154_scan_request *request),
+       TP_ARGS(wpan_phy, request),
+       TP_STRUCT__entry(
+               WPAN_PHY_ENTRY
+               __field(u8, page)
+               __field(u32, channels)
+               __field(u8, duration)
+       ),
+       TP_fast_assign(
+               WPAN_PHY_ASSIGN;
+               __entry->page = request->page;
+               __entry->channels = request->channels;
+               __entry->duration = request->duration;
+       ),
+       TP_printk(WPAN_PHY_PR_FMT ", scan, page: %d, channels: %x, duration %d",
+                 WPAN_PHY_PR_ARG, __entry->page, __entry->channels, __entry->duration)
+);
+
+DECLARE_EVENT_CLASS(802154_wdev_template,
+       TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev),
+       TP_ARGS(wpan_phy, wpan_dev),
+       TP_STRUCT__entry(
+               WPAN_PHY_ENTRY
+               WPAN_DEV_ENTRY
+       ),
+       TP_fast_assign(
+               WPAN_PHY_ASSIGN;
+               WPAN_DEV_ASSIGN;
+       ),
+       TP_printk(WPAN_PHY_PR_FMT ", " WPAN_DEV_PR_FMT,
+                 WPAN_PHY_PR_ARG, WPAN_DEV_PR_ARG)
+);
+
+DEFINE_EVENT(802154_wdev_template, 802154_rdev_abort_scan,
+       TP_PROTO(struct wpan_phy *wpan_phy, struct wpan_dev *wpan_dev),
+       TP_ARGS(wpan_phy, wpan_dev)
+);
+
 TRACE_EVENT(802154_rdev_return_int,
        TP_PROTO(struct wpan_phy *wpan_phy, int ret),
        TP_ARGS(wpan_phy, ret),