cfg80211: introduce critical protocol indication from user-space
Arend van Spriel [Thu, 18 Apr 2013 13:49:00 +0000 (15:49 +0200)]
Some protocols need a more reliable connection to complete
successful in reasonable time. This patch adds a user-space
API to indicate the wireless driver that a critical protocol
is about to commence and when it is done, using nl80211 primitives
NL80211_CMD_CRIT_PROTOCOL_START and NL80211_CRIT_PROTOCOL_STOP.

There can be only on critical protocol session started per
registered cfg80211 device.

The driver can support this by implementing the cfg80211 callbacks
.crit_proto_start() and .crit_proto_stop(). Examples of protocols
that can benefit from this are DHCP, EAPOL, APIPA. Exactly how the
link can/should be made more reliable is up to the driver. Things
to consider are avoid scanning, no multi-channel operations, and
alter coexistence schemes.

Reviewed-by: Pieter-Paul Giesberts <pieterpg@broadcom.com>
Reviewed-by: Franky (Zhenhui) Lin <frankyl@broadcom.com>
Signed-off-by: Arend van Spriel <arend@broadcom.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/wireless/core.h
net/wireless/mlme.c
net/wireless/nl80211.c
net/wireless/rdev-ops.h
net/wireless/trace.h

index dff96d8..26b5b69 100644 (file)
@@ -2002,6 +2002,12 @@ struct cfg80211_update_ft_ies_params {
  * @update_ft_ies: Provide updated Fast BSS Transition information to the
  *     driver. If the SME is in the driver/firmware, this information can be
  *     used in building Authentication and Reassociation Request frames.
+ *
+ * @crit_proto_start: Indicates a critical protocol needs more link reliability
+ *     for a given duration (milliseconds). The protocol is provided so the
+ *     driver can take the most appropriate actions.
+ * @crit_proto_stop: Indicates critical protocol no longer needs increased link
+ *     reliability. This operation can not fail.
  */
 struct cfg80211_ops {
        int     (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -2231,6 +2237,12 @@ struct cfg80211_ops {
                                         struct cfg80211_chan_def *chandef);
        int     (*update_ft_ies)(struct wiphy *wiphy, struct net_device *dev,
                                 struct cfg80211_update_ft_ies_params *ftie);
+       int     (*crit_proto_start)(struct wiphy *wiphy,
+                                   struct wireless_dev *wdev,
+                                   enum nl80211_crit_proto_id protocol,
+                                   u16 duration);
+       void    (*crit_proto_stop)(struct wiphy *wiphy,
+                                  struct wireless_dev *wdev);
 };
 
 /*
@@ -4137,6 +4149,17 @@ void cfg80211_report_wowlan_wakeup(struct wireless_dev *wdev,
                                   struct cfg80211_wowlan_wakeup *wakeup,
                                   gfp_t gfp);
 
+/**
+ * cfg80211_crit_proto_stopped() - indicate critical protocol stopped by driver.
+ *
+ * @wdev: the wireless device for which critical protocol is stopped.
+ *
+ * This function can be called by the driver to indicate it has reverted
+ * operation back to normal. One reason could be that the duration given
+ * by .crit_proto_start() has expired.
+ */
+void cfg80211_crit_proto_stopped(struct wireless_dev *wdev, gfp_t gfp);
+
 /* Logging, debugging and troubleshooting/diagnostic helpers. */
 
 /* wiphy_printk helpers, similar to dev_printk */
index 79da871..d1e48b5 100644 (file)
  *     with the relevant Information Elements. This event is used to report
  *     received FT IEs (MDIE, FTIE, RSN IE, TIE, RICIE).
  *
+ * @NL80211_CMD_CRIT_PROTOCOL_START: Indicates user-space will start running
+ *     a critical protocol that needs more reliability in the connection to
+ *     complete.
+ *
+ * @NL80211_CMD_CRIT_PROTOCOL_STOP: Indicates the connection reliability can
+ *     return back to normal.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -798,6 +805,9 @@ enum nl80211_commands {
        NL80211_CMD_UPDATE_FT_IES,
        NL80211_CMD_FT_EVENT,
 
+       NL80211_CMD_CRIT_PROTOCOL_START,
+       NL80211_CMD_CRIT_PROTOCOL_STOP,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
@@ -1414,6 +1424,11 @@ enum nl80211_commands {
  * @NL80211_ATTR_IE_RIC: Resource Information Container Information
  *     Element
  *
+ * @NL80211_ATTR_CRIT_PROT_ID: critical protocol identifier requiring increased
+ *     reliability, see &enum nl80211_crit_proto_id (u16).
+ * @NL80211_ATTR_MAX_CRIT_PROT_DURATION: duration in milliseconds in which
+ *      the connection should have increased reliability (u16).
+ *
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
  */
@@ -1709,6 +1724,9 @@ enum nl80211_attrs {
        NL80211_ATTR_MDID,
        NL80211_ATTR_IE_RIC,
 
+       NL80211_ATTR_CRIT_PROT_ID,
+       NL80211_ATTR_MAX_CRIT_PROT_DURATION,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
@@ -3682,4 +3700,25 @@ enum nl80211_protocol_features {
        NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP =     1 << 0,
 };
 
+/**
+ * enum nl80211_crit_proto_id - nl80211 critical protocol identifiers
+ *
+ * @NL80211_CRIT_PROTO_UNSPEC: protocol unspecified.
+ * @NL80211_CRIT_PROTO_DHCP: BOOTP or DHCPv6 protocol.
+ * @NL80211_CRIT_PROTO_EAPOL: EAPOL protocol.
+ * @NL80211_CRIT_PROTO_APIPA: APIPA protocol.
+ * @NUM_NL80211_CRIT_PROTO: must be kept last.
+ */
+enum nl80211_crit_proto_id {
+       NL80211_CRIT_PROTO_UNSPEC,
+       NL80211_CRIT_PROTO_DHCP,
+       NL80211_CRIT_PROTO_EAPOL,
+       NL80211_CRIT_PROTO_APIPA,
+       /* add other protocols before this one */
+       NUM_NL80211_CRIT_PROTO
+};
+
+/* maximum duration for critical protocol measures */
+#define NL80211_CRIT_PROTO_MAX_DURATION                5000 /* msec */
+
 #endif /* __LINUX_NL80211_H */
index 124e5e7..fd35dae 100644 (file)
@@ -88,6 +88,9 @@ struct cfg80211_registered_device {
 
        struct delayed_work dfs_update_channels_wk;
 
+       /* netlink port which started critical protocol (0 means not started) */
+       u32 crit_proto_nlportid;
+
        /* must be last because of the way we do wiphy_priv(),
         * and it should at least be aligned to NETDEV_ALIGN */
        struct wiphy wiphy __aligned(NETDEV_ALIGN);
index 390198b..0c7b7dd 100644 (file)
@@ -648,6 +648,11 @@ void cfg80211_mlme_unregister_socket(struct wireless_dev *wdev, u32 nlportid)
 
        spin_unlock_bh(&wdev->mgmt_registrations_lock);
 
+       if (nlportid && rdev->crit_proto_nlportid == nlportid) {
+               rdev->crit_proto_nlportid = 0;
+               rdev_crit_proto_stop(rdev, wdev);
+       }
+
        if (nlportid == wdev->ap_unexpected_nlportid)
                wdev->ap_unexpected_nlportid = 0;
 }
index 3abcbba..afa2838 100644 (file)
@@ -1424,6 +1424,10 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
                }
                CMD(start_p2p_device, START_P2P_DEVICE);
                CMD(set_mcast_rate, SET_MCAST_RATE);
+               if (split) {
+                       CMD(crit_proto_start, CRIT_PROTOCOL_START);
+                       CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
+               }
 
 #ifdef CONFIG_NL80211_TESTMODE
                CMD(testmode_cmd, TESTMODE);
@@ -8216,6 +8220,64 @@ static int nl80211_update_ft_ies(struct sk_buff *skb, struct genl_info *info)
        return rdev_update_ft_ies(rdev, dev, &ft_params);
 }
 
+static int nl80211_crit_protocol_start(struct sk_buff *skb,
+                                      struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct wireless_dev *wdev = info->user_ptr[1];
+       enum nl80211_crit_proto_id proto = NL80211_CRIT_PROTO_UNSPEC;
+       u16 duration;
+       int ret;
+
+       if (!rdev->ops->crit_proto_start)
+               return -EOPNOTSUPP;
+
+       if (WARN_ON(!rdev->ops->crit_proto_stop))
+               return -EINVAL;
+
+       if (rdev->crit_proto_nlportid)
+               return -EBUSY;
+
+       /* determine protocol if provided */
+       if (info->attrs[NL80211_ATTR_CRIT_PROT_ID])
+               proto = nla_get_u16(info->attrs[NL80211_ATTR_CRIT_PROT_ID]);
+
+       if (proto >= NUM_NL80211_CRIT_PROTO)
+               return -EINVAL;
+
+       /* timeout must be provided */
+       if (!info->attrs[NL80211_ATTR_MAX_CRIT_PROT_DURATION])
+               return -EINVAL;
+
+       duration =
+               nla_get_u16(info->attrs[NL80211_ATTR_MAX_CRIT_PROT_DURATION]);
+
+       if (duration > NL80211_CRIT_PROTO_MAX_DURATION)
+               return -ERANGE;
+
+       ret = rdev_crit_proto_start(rdev, wdev, proto, duration);
+       if (!ret)
+               rdev->crit_proto_nlportid = info->snd_portid;
+
+       return ret;
+}
+
+static int nl80211_crit_protocol_stop(struct sk_buff *skb,
+                                     struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct wireless_dev *wdev = info->user_ptr[1];
+
+       if (!rdev->ops->crit_proto_stop)
+               return -EOPNOTSUPP;
+
+       if (rdev->crit_proto_nlportid) {
+               rdev->crit_proto_nlportid = 0;
+               rdev_crit_proto_stop(rdev, wdev);
+       }
+       return 0;
+}
+
 #define NL80211_FLAG_NEED_WIPHY                0x01
 #define NL80211_FLAG_NEED_NETDEV       0x02
 #define NL80211_FLAG_NEED_RTNL         0x04
@@ -8905,6 +8967,22 @@ static struct genl_ops nl80211_ops[] = {
                .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
                                  NL80211_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL80211_CMD_CRIT_PROTOCOL_START,
+               .doit = nl80211_crit_protocol_start,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL80211_CMD_CRIT_PROTOCOL_STOP,
+               .doit = nl80211_crit_protocol_stop,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_WDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       }
 };
 
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
@@ -10650,6 +10728,45 @@ void cfg80211_ft_event(struct net_device *netdev,
 }
 EXPORT_SYMBOL(cfg80211_ft_event);
 
+void cfg80211_crit_proto_stopped(struct wireless_dev *wdev, gfp_t gfp)
+{
+       struct cfg80211_registered_device *rdev;
+       struct sk_buff *msg;
+       void *hdr;
+       u32 nlportid;
+
+       rdev = wiphy_to_dev(wdev->wiphy);
+       if (!rdev->crit_proto_nlportid)
+               return;
+
+       nlportid = rdev->crit_proto_nlportid;
+       rdev->crit_proto_nlportid = 0;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+       if (!msg)
+               return;
+
+       hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_CRIT_PROTOCOL_STOP);
+       if (!hdr)
+               goto nla_put_failure;
+
+       if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) ||
+           nla_put_u64(msg, NL80211_ATTR_WDEV, wdev_id(wdev)))
+               goto nla_put_failure;
+
+       genlmsg_end(msg, hdr);
+
+       genlmsg_unicast(wiphy_net(&rdev->wiphy), msg, nlportid);
+       return;
+
+ nla_put_failure:
+       if (hdr)
+               genlmsg_cancel(msg, hdr);
+       nlmsg_free(msg);
+
+}
+EXPORT_SYMBOL(cfg80211_crit_proto_stopped);
+
 /* initialisation/exit functions */
 
 int nl80211_init(void)
index d77e1c1..9f15f0a 100644 (file)
@@ -875,7 +875,7 @@ static inline void rdev_stop_p2p_device(struct cfg80211_registered_device *rdev,
        trace_rdev_stop_p2p_device(&rdev->wiphy, wdev);
        rdev->ops->stop_p2p_device(&rdev->wiphy, wdev);
        trace_rdev_return_void(&rdev->wiphy);
-}                                      
+}
 
 static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev,
                                   struct net_device *dev,
@@ -901,4 +901,26 @@ static inline int rdev_update_ft_ies(struct cfg80211_registered_device *rdev,
        return ret;
 }
 
+static inline int rdev_crit_proto_start(struct cfg80211_registered_device *rdev,
+                                       struct wireless_dev *wdev,
+                                       enum nl80211_crit_proto_id protocol,
+                                       u16 duration)
+{
+       int ret;
+
+       trace_rdev_crit_proto_start(&rdev->wiphy, wdev, protocol, duration);
+       ret = rdev->ops->crit_proto_start(&rdev->wiphy, wdev,
+                                         protocol, duration);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
+
+static inline void rdev_crit_proto_stop(struct cfg80211_registered_device *rdev,
+                                      struct wireless_dev *wdev)
+{
+       trace_rdev_crit_proto_stop(&rdev->wiphy, wdev);
+       rdev->ops->crit_proto_stop(&rdev->wiphy, wdev);
+       trace_rdev_return_void(&rdev->wiphy);
+}
+
 #endif /* __CFG80211_RDEV_OPS */
index 3c2033b..ecd4fce 100644 (file)
@@ -1806,6 +1806,41 @@ TRACE_EVENT(rdev_update_ft_ies,
                  WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->md)
 );
 
+TRACE_EVENT(rdev_crit_proto_start,
+       TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev,
+                enum nl80211_crit_proto_id protocol, u16 duration),
+       TP_ARGS(wiphy, wdev, protocol, duration),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               WDEV_ENTRY
+               __field(u16, proto)
+               __field(u16, duration)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               WDEV_ASSIGN;
+               __entry->proto = protocol;
+               __entry->duration = duration;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", proto=%x, duration=%u",
+                 WIPHY_PR_ARG, WDEV_PR_ARG, __entry->proto, __entry->duration)
+);
+
+TRACE_EVENT(rdev_crit_proto_stop,
+       TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev),
+       TP_ARGS(wiphy, wdev),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               WDEV_ENTRY
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               WDEV_ASSIGN;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT,
+                 WIPHY_PR_ARG, WDEV_PR_ARG)
+);
+
 /*************************************************************
  *          cfg80211 exported functions traces              *
  *************************************************************/