]> Pileus Git - ~andy/linux/commitdiff
Merge branch 'for-john' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac802...
authorJohn W. Linville <linville@tuxdriver.com>
Fri, 9 Aug 2013 19:08:10 +0000 (15:08 -0400)
committerJohn W. Linville <linville@tuxdriver.com>
Fri, 9 Aug 2013 19:08:10 +0000 (15:08 -0400)
19 files changed:
include/linux/ieee80211.h
include/net/cfg80211.h
include/net/mac80211.h
include/uapi/linux/nl80211.h
net/mac80211/cfg.c
net/mac80211/chan.c
net/mac80211/debugfs_sta.c
net/mac80211/driver-ops.h
net/mac80211/ibss.c
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/led.c
net/mac80211/led.h
net/mac80211/status.c
net/mac80211/trace.h
net/mac80211/tx.c
net/wireless/nl80211.c
net/wireless/rdev-ops.h
net/wireless/trace.h

index b0dc87a2a376e2583d2d28363a0cdd13f859536c..b3ce299782afa70f29983a79367c79a2ace521cf 100644 (file)
@@ -1709,6 +1709,10 @@ enum ieee80211_eid {
        WLAN_EID_OPMODE_NOTIF = 199,
        WLAN_EID_WIDE_BW_CHANNEL_SWITCH = 194,
        WLAN_EID_CHANNEL_SWITCH_WRAPPER = 196,
+       WLAN_EID_EXTENDED_BSS_LOAD = 193,
+       WLAN_EID_VHT_TX_POWER_ENVELOPE = 195,
+       WLAN_EID_AID = 197,
+       WLAN_EID_QUIET_CHANNEL = 198,
 
        /* 802.11ad */
        WLAN_EID_NON_TX_BSSID_CAP =  83,
@@ -1860,6 +1864,11 @@ enum ieee80211_tdls_actioncode {
        WLAN_TDLS_DISCOVERY_REQUEST = 10,
 };
 
+/* Interworking capabilities are set in 7th bit of 4th byte of the
+ * @WLAN_EID_EXT_CAPABILITY information element
+ */
+#define WLAN_EXT_CAPA4_INTERWORKING_ENABLED    BIT(7)
+
 /*
  * TDLS capabililites to be enabled in the 5th byte of the
  * @WLAN_EID_EXT_CAPABILITY information element
index aeaf6dff6e05bd05627148eec841ce8a43bd4bf1..b7495c72061c8d4ac199bb36f080133f8255e06f 100644 (file)
@@ -665,6 +665,30 @@ struct cfg80211_ap_settings {
        bool radar_required;
 };
 
+/**
+ * struct cfg80211_csa_settings - channel switch settings
+ *
+ * Used for channel switch
+ *
+ * @chandef: defines the channel to use after the switch
+ * @beacon_csa: beacon data while performing the switch
+ * @counter_offset_beacon: offset for the counter within the beacon (tail)
+ * @counter_offset_presp: offset for the counter within the probe response
+ * @beacon_after: beacon data to be used on the new channel
+ * @radar_required: whether radar detection is required on the new channel
+ * @block_tx: whether transmissions should be blocked while changing
+ * @count: number of beacons until switch
+ */
+struct cfg80211_csa_settings {
+       struct cfg80211_chan_def chandef;
+       struct cfg80211_beacon_data beacon_csa;
+       u16 counter_offset_beacon, counter_offset_presp;
+       struct cfg80211_beacon_data beacon_after;
+       bool radar_required;
+       bool block_tx;
+       u8 count;
+};
+
 /**
  * enum station_parameters_apply_mask - station parameter values to apply
  * @STATION_PARAM_APPLY_UAPSD: apply new uAPSD parameters (uapsd_queues, max_sp)
@@ -2139,6 +2163,8 @@ struct cfg80211_update_ft_ies_params {
  * @crit_proto_stop: Indicates critical protocol no longer needs increased link
  *     reliability. This operation can not fail.
  * @set_coalesce: Set coalesce parameters.
+ *
+ * @channel_switch: initiate channel-switch procedure (with CSA)
  */
 struct cfg80211_ops {
        int     (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow);
@@ -2376,6 +2402,10 @@ struct cfg80211_ops {
                                   struct wireless_dev *wdev);
        int     (*set_coalesce)(struct wiphy *wiphy,
                                struct cfg80211_coalesce *coalesce);
+
+       int     (*channel_switch)(struct wiphy *wiphy,
+                                 struct net_device *dev,
+                                 struct cfg80211_csa_settings *params);
 };
 
 /*
@@ -2441,6 +2471,8 @@ struct cfg80211_ops {
  * @WIPHY_FLAG_OFFCHAN_TX: Device supports direct off-channel TX.
  * @WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL: Device supports remain-on-channel call.
  * @WIPHY_FLAG_SUPPORTS_5_10_MHZ: Device supports 5 MHz and 10 MHz channels.
+ * @WIPHY_FLAG_HAS_CHANNEL_SWITCH: Device supports channel switch in
+ *     beaconing mode (AP, IBSS, Mesh, ...).
  */
 enum wiphy_flags {
        WIPHY_FLAG_CUSTOM_REGULATORY            = BIT(0),
@@ -2465,6 +2497,7 @@ enum wiphy_flags {
        WIPHY_FLAG_OFFCHAN_TX                   = BIT(20),
        WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL        = BIT(21),
        WIPHY_FLAG_SUPPORTS_5_10_MHZ            = BIT(22),
+       WIPHY_FLAG_HAS_CHANNEL_SWITCH           = BIT(23),
 };
 
 /**
index 3124036285ebe6b048c2731dc8d51e4e58e7b7ad..9cda3728c2cbfc78d77efc765ff298718469108e 100644 (file)
@@ -152,11 +152,14 @@ struct ieee80211_low_level_stats {
  * @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
  * @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
  * @IEEE80211_CHANCTX_CHANGE_RADAR: radar detection flag changed
+ * @IEEE80211_CHANCTX_CHANGE_CHANNEL: switched to another operating channel,
+ *     this is used only with channel switching with CSA
  */
 enum ieee80211_chanctx_change {
        IEEE80211_CHANCTX_CHANGE_WIDTH          = BIT(0),
        IEEE80211_CHANCTX_CHANGE_RX_CHAINS      = BIT(1),
        IEEE80211_CHANCTX_CHANGE_RADAR          = BIT(2),
+       IEEE80211_CHANCTX_CHANGE_CHANNEL        = BIT(3),
 };
 
 /**
@@ -1084,6 +1087,7 @@ enum ieee80211_vif_flags {
  * @addr: address of this interface
  * @p2p: indicates whether this AP or STA interface is a p2p
  *     interface, i.e. a GO or p2p-sta respectively
+ * @csa_active: marks whether a channel switch is going on
  * @driver_flags: flags/capabilities the driver has for this interface,
  *     these need to be set (or cleared) when the interface is added
  *     or, if supported by the driver, the interface type is changed
@@ -1106,6 +1110,7 @@ struct ieee80211_vif {
        struct ieee80211_bss_conf bss_conf;
        u8 addr[ETH_ALEN];
        bool p2p;
+       bool csa_active;
 
        u8 cab_queue;
        u8 hw_queue[IEEE80211_NUM_ACS];
@@ -2637,6 +2642,16 @@ enum ieee80211_roc_type {
  * @ipv6_addr_change: IPv6 address assignment on the given interface changed.
  *     Currently, this is only called for managed or P2P client interfaces.
  *     This callback is optional; it must not sleep.
+ *
+ * @channel_switch_beacon: Starts a channel switch to a new channel.
+ *     Beacons are modified to include CSA or ECSA IEs before calling this
+ *     function. The corresponding count fields in these IEs must be
+ *     decremented, and when they reach zero the driver must call
+ *     ieee80211_csa_finish(). Drivers which use ieee80211_beacon_get()
+ *     get the csa counter decremented by mac80211, but must check if it is
+ *     zero using ieee80211_csa_is_complete() after the beacon has been
+ *     transmitted and then call ieee80211_csa_finish().
+ *
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw,
@@ -2824,6 +2839,9 @@ struct ieee80211_ops {
                                 struct ieee80211_vif *vif,
                                 struct inet6_dev *idev);
 #endif
+       void (*channel_switch_beacon)(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif *vif,
+                                     struct cfg80211_chan_def *chandef);
 };
 
 /**
@@ -3318,6 +3336,25 @@ static inline struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
        return ieee80211_beacon_get_tim(hw, vif, NULL, NULL);
 }
 
+/**
+ * ieee80211_csa_finish - notify mac80211 about channel switch
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * After a channel switch announcement was scheduled and the counter in this
+ * announcement hit zero, this function must be called by the driver to
+ * notify mac80211 that the channel can be changed.
+ */
+void ieee80211_csa_finish(struct ieee80211_vif *vif);
+
+/**
+ * ieee80211_csa_is_complete - find out if counters reached zero
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * This function returns whether the channel switch counters reached zero.
+ */
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif);
+
+
 /**
  * ieee80211_proberesp_get - retrieve a Probe Response template
  * @hw: pointer obtained from ieee80211_alloc_hw().
index eb68735b33180fac625f3ac7e38137dd0c0f588e..1f42bc3dcb9c544407eeb32968b41089c4a5917e 100644 (file)
  * @NL80211_CMD_GET_COALESCE: Get currently supported coalesce rules.
  * @NL80211_CMD_SET_COALESCE: Configure coalesce rules or clear existing rules.
  *
+ * @NL80211_CMD_CHANNEL_SWITCH: Perform a channel switch by announcing the
+ *     the new channel information (Channel Switch Announcement - CSA)
+ *     in the beacon for some time (as defined in the
+ *     %NL80211_ATTR_CH_SWITCH_COUNT parameter) and then change to the
+ *     new channel. Userspace provides the new channel information (using
+ *     %NL80211_ATTR_WIPHY_FREQ and the attributes determining channel
+ *     width). %NL80211_ATTR_CH_SWITCH_BLOCK_TX may be supplied to inform
+ *     other station that transmission must be blocked until the channel
+ *     switch is complete.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -841,6 +851,8 @@ enum nl80211_commands {
        NL80211_CMD_GET_COALESCE,
        NL80211_CMD_SET_COALESCE,
 
+       NL80211_CMD_CHANNEL_SWITCH,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
@@ -1469,6 +1481,18 @@ enum nl80211_commands {
  *
  * @NL80211_ATTR_COALESCE_RULE: Coalesce rule information.
  *
+ * @NL80211_ATTR_CH_SWITCH_COUNT: u32 attribute specifying the number of TBTT's
+ *     until the channel switch event.
+ * @NL80211_ATTR_CH_SWITCH_BLOCK_TX: flag attribute specifying that transmission
+ *     must be blocked on the current channel (before the channel switch
+ *     operation).
+ * @NL80211_ATTR_CSA_IES: Nested set of attributes containing the IE information
+ *     for the time while performing a channel switch.
+ * @NL80211_ATTR_CSA_C_OFF_BEACON: Offset of the channel switch counter
+ *     field in the beacons tail (%NL80211_ATTR_BEACON_TAIL).
+ * @NL80211_ATTR_CSA_C_OFF_PRESP: Offset of the channel switch counter
+ *     field in the probe response (%NL80211_ATTR_PROBE_RESP).
+ *
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
  */
@@ -1771,6 +1795,12 @@ enum nl80211_attrs {
 
        NL80211_ATTR_COALESCE_RULE,
 
+       NL80211_ATTR_CH_SWITCH_COUNT,
+       NL80211_ATTR_CH_SWITCH_BLOCK_TX,
+       NL80211_ATTR_CSA_IES,
+       NL80211_ATTR_CSA_C_OFF_BEACON,
+       NL80211_ATTR_CSA_C_OFF_PRESP,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
index 973594b229f400bbae825034d32f69b07c51c9f7..31fc2247bc372dffe9fade359f706863daa8b297 100644 (file)
@@ -862,8 +862,8 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
        return 0;
 }
 
-static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
-                                  struct cfg80211_beacon_data *params)
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+                           struct cfg80211_beacon_data *params)
 {
        struct beacon_data *new, *old;
        int new_head_len, new_tail_len;
@@ -1026,6 +1026,12 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
+       /* don't allow changing the beacon while CSA is in place - offset
+        * of channel switch counter may change
+        */
+       if (sdata->vif.csa_active)
+               return -EBUSY;
+
        old = rtnl_dereference(sdata->u.ap.beacon);
        if (!old)
                return -ENOENT;
@@ -1050,6 +1056,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
                return -ENOENT;
        old_probe_resp = rtnl_dereference(sdata->u.ap.probe_resp);
 
+       /* abort any running channel switch */
+       sdata->vif.csa_active = false;
+       cancel_work_sync(&sdata->csa_finalize_work);
+
        /* turn off carrier for this interface and dependent VLANs */
        list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
                netif_carrier_off(vlan->dev);
@@ -2777,6 +2787,178 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
        return 0;
 }
 
+static struct cfg80211_beacon_data *
+cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
+{
+       struct cfg80211_beacon_data *new_beacon;
+       u8 *pos;
+       int len;
+
+       len = beacon->head_len + beacon->tail_len + beacon->beacon_ies_len +
+             beacon->proberesp_ies_len + beacon->assocresp_ies_len +
+             beacon->probe_resp_len;
+
+       new_beacon = kzalloc(sizeof(*new_beacon) + len, GFP_KERNEL);
+       if (!new_beacon)
+               return NULL;
+
+       pos = (u8 *)(new_beacon + 1);
+       if (beacon->head_len) {
+               new_beacon->head_len = beacon->head_len;
+               new_beacon->head = pos;
+               memcpy(pos, beacon->head, beacon->head_len);
+               pos += beacon->head_len;
+       }
+       if (beacon->tail_len) {
+               new_beacon->tail_len = beacon->tail_len;
+               new_beacon->tail = pos;
+               memcpy(pos, beacon->tail, beacon->tail_len);
+               pos += beacon->tail_len;
+       }
+       if (beacon->beacon_ies_len) {
+               new_beacon->beacon_ies_len = beacon->beacon_ies_len;
+               new_beacon->beacon_ies = pos;
+               memcpy(pos, beacon->beacon_ies, beacon->beacon_ies_len);
+               pos += beacon->beacon_ies_len;
+       }
+       if (beacon->proberesp_ies_len) {
+               new_beacon->proberesp_ies_len = beacon->proberesp_ies_len;
+               new_beacon->proberesp_ies = pos;
+               memcpy(pos, beacon->proberesp_ies, beacon->proberesp_ies_len);
+               pos += beacon->proberesp_ies_len;
+       }
+       if (beacon->assocresp_ies_len) {
+               new_beacon->assocresp_ies_len = beacon->assocresp_ies_len;
+               new_beacon->assocresp_ies = pos;
+               memcpy(pos, beacon->assocresp_ies, beacon->assocresp_ies_len);
+               pos += beacon->assocresp_ies_len;
+       }
+       if (beacon->probe_resp_len) {
+               new_beacon->probe_resp_len = beacon->probe_resp_len;
+               beacon->probe_resp = pos;
+               memcpy(pos, beacon->probe_resp, beacon->probe_resp_len);
+               pos += beacon->probe_resp_len;
+       }
+
+       return new_beacon;
+}
+
+void ieee80211_csa_finalize_work(struct work_struct *work)
+{
+       struct ieee80211_sub_if_data *sdata =
+               container_of(work, struct ieee80211_sub_if_data,
+                            csa_finalize_work);
+       struct ieee80211_local *local = sdata->local;
+       int err, changed;
+
+       if (!ieee80211_sdata_running(sdata))
+               return;
+
+       if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP))
+               return;
+
+       sdata->radar_required = sdata->csa_radar_required;
+       err = ieee80211_vif_change_channel(sdata, &local->csa_chandef,
+                                          &changed);
+       if (WARN_ON(err < 0))
+               return;
+
+       err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon);
+       if (err < 0)
+               return;
+
+       changed |= err;
+       kfree(sdata->u.ap.next_beacon);
+       sdata->u.ap.next_beacon = NULL;
+       sdata->vif.csa_active = false;
+
+       ieee80211_wake_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_MAX_QUEUE_MAP,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+
+       ieee80211_bss_info_change_notify(sdata, changed);
+
+       cfg80211_ch_switch_notify(sdata->dev, &local->csa_chandef);
+}
+
+static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+                                   struct cfg80211_csa_settings *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *chanctx_conf;
+       struct ieee80211_chanctx *chanctx;
+       int err, num_chanctx;
+
+       if (!list_empty(&local->roc_list) || local->scanning)
+               return -EBUSY;
+
+       if (sdata->wdev.cac_started)
+               return -EBUSY;
+
+       if (cfg80211_chandef_identical(&params->chandef,
+                                      &sdata->vif.bss_conf.chandef))
+               return -EINVAL;
+
+       rcu_read_lock();
+       chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+       if (!chanctx_conf) {
+               rcu_read_unlock();
+               return -EBUSY;
+       }
+
+       /* don't handle for multi-VIF cases */
+       chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+       if (chanctx->refcount > 1) {
+               rcu_read_unlock();
+               return -EBUSY;
+       }
+       num_chanctx = 0;
+       list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
+               num_chanctx++;
+       rcu_read_unlock();
+
+       if (num_chanctx > 1)
+               return -EBUSY;
+
+       /* don't allow another channel switch if one is already active. */
+       if (sdata->vif.csa_active)
+               return -EBUSY;
+
+       /* only handle AP for now. */
+       switch (sdata->vif.type) {
+       case NL80211_IFTYPE_AP:
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       sdata->u.ap.next_beacon = cfg80211_beacon_dup(&params->beacon_after);
+       if (!sdata->u.ap.next_beacon)
+               return -ENOMEM;
+
+       sdata->csa_counter_offset_beacon = params->counter_offset_beacon;
+       sdata->csa_counter_offset_presp = params->counter_offset_presp;
+       sdata->csa_radar_required = params->radar_required;
+
+       if (params->block_tx)
+               ieee80211_stop_queues_by_reason(&local->hw,
+                               IEEE80211_MAX_QUEUE_MAP,
+                               IEEE80211_QUEUE_STOP_REASON_CSA);
+
+       err = ieee80211_assign_beacon(sdata, &params->beacon_csa);
+       if (err < 0)
+               return err;
+
+       local->csa_chandef = params->chandef;
+       sdata->vif.csa_active = true;
+
+       ieee80211_bss_info_change_notify(sdata, err);
+       drv_channel_switch_beacon(sdata, &params->chandef);
+
+       return 0;
+}
+
 static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
                             struct ieee80211_channel *chan, bool offchan,
                             unsigned int wait, const u8 *buf, size_t len,
@@ -3494,4 +3676,5 @@ struct cfg80211_ops mac80211_config_ops = {
        .get_et_strings = ieee80211_get_et_strings,
        .get_channel = ieee80211_cfg_get_channel,
        .start_radar_detection = ieee80211_start_radar_detection,
+       .channel_switch = ieee80211_channel_switch,
 };
index 03e8d2e3270e23f0e97a58595513b33fcb048cb3..3a4764b2869efffdbcc3f90a363cf3f8b095496c 100644 (file)
@@ -410,6 +410,64 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
        return ret;
 }
 
+int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+                                const struct cfg80211_chan_def *chandef,
+                                u32 *changed)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *conf;
+       struct ieee80211_chanctx *ctx;
+       int ret;
+       u32 chanctx_changed = 0;
+
+       /* should never be called if not performing a channel switch. */
+       if (WARN_ON(!sdata->vif.csa_active))
+               return -EINVAL;
+
+       if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
+                                    IEEE80211_CHAN_DISABLED))
+               return -EINVAL;
+
+       mutex_lock(&local->chanctx_mtx);
+       conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+                                        lockdep_is_held(&local->chanctx_mtx));
+       if (!conf) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ctx = container_of(conf, struct ieee80211_chanctx, conf);
+       if (ctx->refcount != 1) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (sdata->vif.bss_conf.chandef.width != chandef->width) {
+               chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH;
+               *changed |= BSS_CHANGED_BANDWIDTH;
+       }
+
+       sdata->vif.bss_conf.chandef = *chandef;
+       ctx->conf.def = *chandef;
+
+       chanctx_changed |= IEEE80211_CHANCTX_CHANGE_CHANNEL;
+       drv_change_chanctx(local, ctx, chanctx_changed);
+
+       if (!local->use_chanctx) {
+               local->_oper_chandef = *chandef;
+               ieee80211_hw_config(local, 0);
+       }
+
+       ieee80211_recalc_chanctx_chantype(local, ctx);
+       ieee80211_recalc_smps_chanctx(local, ctx);
+       ieee80211_recalc_radar_chanctx(local, ctx);
+
+       ret = 0;
+ out:
+       mutex_unlock(&local->chanctx_mtx);
+       return ret;
+}
+
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
                                   const struct cfg80211_chan_def *chandef,
                                   u32 *changed)
index 44e201d60a13991f0144954ac1dd1ebd00cb4672..19c54a44ed4793823713b717a86111d4dda0d57d 100644 (file)
@@ -455,6 +455,15 @@ void ieee80211_sta_debugfs_add(struct sta_info *sta)
        DEBUGFS_ADD_COUNTER(tx_retry_count, tx_retry_count);
        DEBUGFS_ADD_COUNTER(wep_weak_iv_count, wep_weak_iv_count);
 
+       if (sizeof(sta->driver_buffered_tids) == sizeof(u32))
+               debugfs_create_x32("driver_buffered_tids", 0400,
+                                  sta->debugfs.dir,
+                                  (u32 *)&sta->driver_buffered_tids);
+       else
+               debugfs_create_x64("driver_buffered_tids", 0400,
+                                  sta->debugfs.dir,
+                                  (u64 *)&sta->driver_buffered_tids);
+
        drv_sta_add_debugfs(local, sdata, &sta->sta, sta->debugfs.dir);
 }
 
index b931c96a596fb3e92e9effa845ab10f74144dfd1..b3ea11f3d526962ddd8190587f82d8d84134067c 100644 (file)
@@ -1072,4 +1072,17 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
 }
 #endif
 
+static inline void
+drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata,
+                         struct cfg80211_chan_def *chandef)
+{
+       struct ieee80211_local *local = sdata->local;
+
+       if (local->ops->channel_switch_beacon) {
+               trace_drv_channel_switch_beacon(local, sdata, chandef);
+               local->ops->channel_switch_beacon(&local->hw, &sdata->vif,
+                                                 chandef);
+       }
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index 5e6836c3aa4c108a6e1c6fe2430a95d0ec5faba0..e08387cdc8fdb26e374f56792d25a3305d7412b1 100644 (file)
@@ -30,6 +30,7 @@
 
 #define IEEE80211_IBSS_MERGE_INTERVAL (30 * HZ)
 #define IEEE80211_IBSS_INACTIVITY_LIMIT (60 * HZ)
+#define IEEE80211_IBSS_RSN_INACTIVITY_LIMIT (10 * HZ)
 
 #define IEEE80211_IBSS_MAX_STA_ENTRIES 128
 
@@ -740,6 +741,33 @@ static int ieee80211_sta_active_ibss(struct ieee80211_sub_if_data *sdata)
        return active;
 }
 
+static void ieee80211_ibss_sta_expire(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct sta_info *sta, *tmp;
+       unsigned long exp_time = IEEE80211_IBSS_INACTIVITY_LIMIT;
+       unsigned long exp_rsn_time = IEEE80211_IBSS_RSN_INACTIVITY_LIMIT;
+
+       mutex_lock(&local->sta_mtx);
+
+       list_for_each_entry_safe(sta, tmp, &local->sta_list, list) {
+               if (sdata != sta->sdata)
+                       continue;
+
+               if (time_after(jiffies, sta->last_rx + exp_time) ||
+                   (time_after(jiffies, sta->last_rx + exp_rsn_time) &&
+                    sta->sta_state != IEEE80211_STA_AUTHORIZED)) {
+                       sta_dbg(sta->sdata, "expiring inactive %sSTA %pM\n",
+                               sta->sta_state != IEEE80211_STA_AUTHORIZED ?
+                               "not authorized " : "", sta->sta.addr);
+
+                       WARN_ON(__sta_info_destroy(sta));
+               }
+       }
+
+       mutex_unlock(&local->sta_mtx);
+}
+
 /*
  * This function is called with state == IEEE80211_IBSS_MLME_JOINED
  */
@@ -754,7 +782,7 @@ static void ieee80211_sta_merge_ibss(struct ieee80211_sub_if_data *sdata)
        mod_timer(&ifibss->timer,
                  round_jiffies(jiffies + IEEE80211_IBSS_MERGE_INTERVAL));
 
-       ieee80211_sta_expire(sdata, IEEE80211_IBSS_INACTIVITY_LIMIT);
+       ieee80211_ibss_sta_expire(sdata);
 
        if (time_before(jiffies, ifibss->last_scan_completed +
                       IEEE80211_IBSS_MERGE_INTERVAL))
index 3d32df1fbc6de92a5eb1e02ba532ba39b5533573..e94c84050e9c214c3959088423f75ef444824dcd 100644 (file)
@@ -259,6 +259,8 @@ struct ieee80211_if_ap {
        struct beacon_data __rcu *beacon;
        struct probe_resp __rcu *probe_resp;
 
+       /* to be used after channel switch. */
+       struct cfg80211_beacon_data *next_beacon;
        struct list_head vlans;
 
        struct ps_data ps;
@@ -716,6 +718,11 @@ struct ieee80211_sub_if_data {
 
        struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
 
+       struct work_struct csa_finalize_work;
+       int csa_counter_offset_beacon;
+       int csa_counter_offset_presp;
+       bool csa_radar_required;
+
        /* used to reconfigure hardware SM PS */
        struct work_struct recalc_smps;
 
@@ -1094,7 +1101,6 @@ struct ieee80211_local {
        u32 dot11TransmittedFrameCount;
 
 #ifdef CONFIG_MAC80211_LEDS
-       int tx_led_counter, rx_led_counter;
        struct led_trigger *tx_led, *rx_led, *assoc_led, *radio_led;
        struct tpt_led_trigger *tpt_led_trigger;
        char tx_led_name[32], rx_led_name[32],
@@ -1373,6 +1379,9 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
 void ieee80211_sw_roc_work(struct work_struct *work);
 void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);
 
+/* channel switch handling */
+void ieee80211_csa_finalize_work(struct work_struct *work);
+
 /* interface handling */
 int ieee80211_iface_init(void);
 void ieee80211_iface_exit(void);
@@ -1394,6 +1403,8 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local);
 
 bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
 void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+                           struct cfg80211_beacon_data *params);
 
 static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
 {
@@ -1655,6 +1666,11 @@ int __must_check
 ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
                               const struct cfg80211_chan_def *chandef,
                               u32 *changed);
+/* NOTE: only use ieee80211_vif_change_channel() for channel switch */
+int __must_check
+ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+                            const struct cfg80211_chan_def *chandef,
+                            u32 *changed);
 void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata);
 void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata);
 void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
index 4c41c11958c8a9de28fcae05465f44bc6712ca45..7ca534bf4ceaef6d3125a6204d66bf29ced020ab 100644 (file)
@@ -274,6 +274,12 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
                        if (iftype == NL80211_IFTYPE_ADHOC &&
                            nsdata->vif.type == NL80211_IFTYPE_ADHOC)
                                return -EBUSY;
+                       /*
+                        * will not add another interface while any channel
+                        * switch is active.
+                        */
+                       if (nsdata->vif.csa_active)
+                               return -EBUSY;
 
                        /*
                         * The remaining checks are only performed for interfaces
@@ -804,6 +810,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
        cancel_work_sync(&local->dynamic_ps_enable_work);
 
        cancel_work_sync(&sdata->recalc_smps);
+       sdata->vif.csa_active = false;
+       cancel_work_sync(&sdata->csa_finalize_work);
 
        cancel_delayed_work_sync(&sdata->dfs_cac_timer_work);
 
@@ -1267,6 +1275,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
        skb_queue_head_init(&sdata->skb_queue);
        INIT_WORK(&sdata->work, ieee80211_iface_work);
        INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
+       INIT_WORK(&sdata->csa_finalize_work, ieee80211_csa_finalize_work);
 
        switch (type) {
        case NL80211_IFTYPE_P2P_GO:
index bcffa69031298f97ee99f50ade02a6e862c5a8a8..e2b836446af386dba692304bda48fd1dcb038c5b 100644 (file)
 #include <linux/export.h>
 #include "led.h"
 
+#define MAC80211_BLINK_DELAY 50 /* ms */
+
 void ieee80211_led_rx(struct ieee80211_local *local)
 {
+       unsigned long led_delay = MAC80211_BLINK_DELAY;
        if (unlikely(!local->rx_led))
                return;
-       if (local->rx_led_counter++ % 2 == 0)
-               led_trigger_event(local->rx_led, LED_OFF);
-       else
-               led_trigger_event(local->rx_led, LED_FULL);
+       led_trigger_blink_oneshot(local->rx_led, &led_delay, &led_delay, 0);
 }
 
-/* q is 1 if a packet was enqueued, 0 if it has been transmitted */
-void ieee80211_led_tx(struct ieee80211_local *local, int q)
+void ieee80211_led_tx(struct ieee80211_local *local)
 {
+       unsigned long led_delay = MAC80211_BLINK_DELAY;
        if (unlikely(!local->tx_led))
                return;
-       /* not sure how this is supposed to work ... */
-       local->tx_led_counter += 2*q-1;
-       if (local->tx_led_counter % 2 == 0)
-               led_trigger_event(local->tx_led, LED_OFF);
-       else
-               led_trigger_event(local->tx_led, LED_FULL);
+       led_trigger_blink_oneshot(local->tx_led, &led_delay, &led_delay, 0);
 }
 
 void ieee80211_led_assoc(struct ieee80211_local *local, bool associated)
index e0275d9befa8da1702da7246a18ec8c301333269..89f4344f13b973509344d2431960c4d51193d56e 100644 (file)
@@ -13,7 +13,7 @@
 
 #ifdef CONFIG_MAC80211_LEDS
 void ieee80211_led_rx(struct ieee80211_local *local);
-void ieee80211_led_tx(struct ieee80211_local *local, int q);
+void ieee80211_led_tx(struct ieee80211_local *local);
 void ieee80211_led_assoc(struct ieee80211_local *local,
                         bool associated);
 void ieee80211_led_radio(struct ieee80211_local *local,
@@ -27,7 +27,7 @@ void ieee80211_mod_tpt_led_trig(struct ieee80211_local *local,
 static inline void ieee80211_led_rx(struct ieee80211_local *local)
 {
 }
-static inline void ieee80211_led_tx(struct ieee80211_local *local, int q)
+static inline void ieee80211_led_tx(struct ieee80211_local *local)
 {
 }
 static inline void ieee80211_led_assoc(struct ieee80211_local *local,
index 6ad4c14385ef420bf039660ccce5b1a8672dd500..368837fe3b800e87f408039ef4d36e84bfaa7069 100644 (file)
@@ -235,7 +235,8 @@ static int ieee80211_tx_radiotap_len(struct ieee80211_tx_info *info)
 
        /* IEEE80211_RADIOTAP_RATE rate */
        if (info->status.rates[0].idx >= 0 &&
-           !(info->status.rates[0].flags & IEEE80211_TX_RC_MCS))
+           !(info->status.rates[0].flags & (IEEE80211_TX_RC_MCS |
+                                            IEEE80211_TX_RC_VHT_MCS)))
                len += 2;
 
        /* IEEE80211_RADIOTAP_TX_FLAGS */
@@ -244,16 +245,21 @@ static int ieee80211_tx_radiotap_len(struct ieee80211_tx_info *info)
        /* IEEE80211_RADIOTAP_DATA_RETRIES */
        len += 1;
 
-       /* IEEE80211_TX_RC_MCS */
-       if (info->status.rates[0].idx >= 0 &&
-           info->status.rates[0].flags & IEEE80211_TX_RC_MCS)
-               len += 3;
+       /* IEEE80211_RADIOTAP_MCS
+        * IEEE80211_RADIOTAP_VHT */
+       if (info->status.rates[0].idx >= 0) {
+               if (info->status.rates[0].flags & IEEE80211_TX_RC_MCS)
+                       len += 3;
+               else if (info->status.rates[0].flags & IEEE80211_TX_RC_VHT_MCS)
+                       len = ALIGN(len, 2) + 12;
+       }
 
        return len;
 }
 
 static void
-ieee80211_add_tx_radiotap_header(struct ieee80211_supported_band *sband,
+ieee80211_add_tx_radiotap_header(struct ieee80211_local *local,
+                                struct ieee80211_supported_band *sband,
                                 struct sk_buff *skb, int retry_count,
                                 int rtap_len, int shift)
 {
@@ -280,7 +286,8 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_supported_band *sband,
 
        /* IEEE80211_RADIOTAP_RATE */
        if (info->status.rates[0].idx >= 0 &&
-           !(info->status.rates[0].flags & IEEE80211_TX_RC_MCS)) {
+           !(info->status.rates[0].flags & (IEEE80211_TX_RC_MCS |
+                                            IEEE80211_TX_RC_VHT_MCS))) {
                u16 rate;
 
                rthdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_RATE);
@@ -310,9 +317,12 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_supported_band *sband,
        *pos = retry_count;
        pos++;
 
-       /* IEEE80211_TX_RC_MCS */
-       if (info->status.rates[0].idx >= 0 &&
-           info->status.rates[0].flags & IEEE80211_TX_RC_MCS) {
+       if (info->status.rates[0].idx < 0)
+               return;
+
+       /* IEEE80211_RADIOTAP_MCS
+        * IEEE80211_RADIOTAP_VHT */
+       if (info->status.rates[0].flags & IEEE80211_TX_RC_MCS) {
                rthdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_MCS);
                pos[0] = IEEE80211_RADIOTAP_MCS_HAVE_MCS |
                         IEEE80211_RADIOTAP_MCS_HAVE_GI |
@@ -325,8 +335,48 @@ ieee80211_add_tx_radiotap_header(struct ieee80211_supported_band *sband,
                        pos[1] |= IEEE80211_RADIOTAP_MCS_FMT_GF;
                pos[2] = info->status.rates[0].idx;
                pos += 3;
-       }
+       } else if (info->status.rates[0].flags & IEEE80211_TX_RC_VHT_MCS) {
+               u16 known = local->hw.radiotap_vht_details &
+                       (IEEE80211_RADIOTAP_VHT_KNOWN_GI |
+                        IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH);
+
+               rthdr->it_present |= cpu_to_le32(1 << IEEE80211_RADIOTAP_VHT);
+
+               /* required alignment from rthdr */
+               pos = (u8 *)rthdr + ALIGN(pos - (u8 *)rthdr, 2);
 
+               /* u16 known - IEEE80211_RADIOTAP_VHT_KNOWN_* */
+               put_unaligned_le16(known, pos);
+               pos += 2;
+
+               /* u8 flags - IEEE80211_RADIOTAP_VHT_FLAG_* */
+               if (info->status.rates[0].flags & IEEE80211_TX_RC_SHORT_GI)
+                       *pos |= IEEE80211_RADIOTAP_VHT_FLAG_SGI;
+               pos++;
+
+               /* u8 bandwidth */
+               if (info->status.rates[0].flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
+                       *pos = 1;
+               else if (info->status.rates[0].flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
+                       *pos = 4;
+               else if (info->status.rates[0].flags & IEEE80211_TX_RC_160_MHZ_WIDTH)
+                       *pos = 11;
+               else /* IEEE80211_TX_RC_{20_MHZ_WIDTH,FIXME:DUP_DATA} */
+                       *pos = 0;
+               pos++;
+
+               /* u8 mcs_nss[4] */
+               *pos = (ieee80211_rate_get_vht_mcs(&info->status.rates[0]) << 4) |
+                       ieee80211_rate_get_vht_nss(&info->status.rates[0]);
+               pos += 4;
+
+               /* u8 coding */
+               pos++;
+               /* u8 group_id */
+               pos++;
+               /* u16 partial_aid */
+               pos += 2;
+       }
 }
 
 static void ieee80211_report_used_skb(struct ieee80211_local *local,
@@ -564,7 +614,7 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
 
        rcu_read_unlock();
 
-       ieee80211_led_tx(local, 0);
+       ieee80211_led_tx(local);
 
        /* SNMP counters
         * Fragments are passed to low-level drivers as separate skbs, so these
@@ -631,8 +681,8 @@ void ieee80211_tx_status(struct ieee80211_hw *hw, struct sk_buff *skb)
                dev_kfree_skb(skb);
                return;
        }
-       ieee80211_add_tx_radiotap_header(sband, skb, retry_count, rtap_len,
-                                        shift);
+       ieee80211_add_tx_radiotap_header(local, sband, skb, retry_count,
+                                        rtap_len, shift);
 
        /* XXX: is this sufficient for BPF? */
        skb_set_mac_header(skb, 0);
index c215fafd7a2fc1ac3e206b4551c60de8d656ab24..1aba645882bd92abbf9e5a4cdd90f7544a173c4a 100644 (file)
@@ -1906,6 +1906,32 @@ TRACE_EVENT(api_radar_detected,
        )
 );
 
+TRACE_EVENT(drv_channel_switch_beacon,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct cfg80211_chan_def *chandef),
+
+       TP_ARGS(local, sdata, chandef),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               CHANDEF_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               CHANDEF_ASSIGN(chandef);
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT VIF_PR_FMT " channel switch to " CHANDEF_PR_FMT,
+               LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG
+       )
+);
+
+
 #ifdef CONFIG_MAC80211_MESSAGE_TRACING
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mac80211_msg
index be4d3caf4879b5eb09d6a49d524cb4faa81c2752..0e42322aa6b157e13d7ea8ee57d3ad6f03e08ac4 100644 (file)
@@ -1300,7 +1300,6 @@ static bool __ieee80211_tx(struct ieee80211_local *local,
                                    txpending);
 
        ieee80211_tpt_led_trig_tx(local, fc, led_len);
-       ieee80211_led_tx(local, 1);
 
        WARN_ON_ONCE(!skb_queue_empty(skbs));
 
@@ -2339,6 +2338,81 @@ static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
        return 0;
 }
 
+void ieee80211_csa_finish(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+       ieee80211_queue_work(&sdata->local->hw,
+                            &sdata->csa_finalize_work);
+}
+EXPORT_SYMBOL(ieee80211_csa_finish);
+
+static void ieee80211_update_csa(struct ieee80211_sub_if_data *sdata,
+                                struct beacon_data *beacon)
+{
+       struct probe_resp *resp;
+       int counter_offset_beacon = sdata->csa_counter_offset_beacon;
+       int counter_offset_presp = sdata->csa_counter_offset_presp;
+
+       /* warn if the driver did not check for/react to csa completeness */
+       if (WARN_ON(((u8 *)beacon->tail)[counter_offset_beacon] == 0))
+               return;
+
+       ((u8 *)beacon->tail)[counter_offset_beacon]--;
+
+       if (sdata->vif.type == NL80211_IFTYPE_AP &&
+           counter_offset_presp) {
+               rcu_read_lock();
+               resp = rcu_dereference(sdata->u.ap.probe_resp);
+
+               /* if nl80211 accepted the offset, this should not happen. */
+               if (WARN_ON(!resp)) {
+                       rcu_read_unlock();
+                       return;
+               }
+               resp->data[counter_offset_presp]--;
+               rcu_read_unlock();
+       }
+}
+
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct beacon_data *beacon = NULL;
+       u8 *beacon_data;
+       size_t beacon_data_len;
+       int counter_beacon = sdata->csa_counter_offset_beacon;
+       int ret = false;
+
+       if (!ieee80211_sdata_running(sdata))
+               return false;
+
+       rcu_read_lock();
+       if (vif->type == NL80211_IFTYPE_AP) {
+               struct ieee80211_if_ap *ap = &sdata->u.ap;
+
+               beacon = rcu_dereference(ap->beacon);
+               if (WARN_ON(!beacon || !beacon->tail))
+                       goto out;
+               beacon_data = beacon->tail;
+               beacon_data_len = beacon->tail_len;
+       } else {
+               WARN_ON(1);
+               goto out;
+       }
+
+       if (WARN_ON(counter_beacon > beacon_data_len))
+               goto out;
+
+       if (beacon_data[counter_beacon] == 0)
+               ret = true;
+ out:
+       rcu_read_unlock();
+
+       return ret;
+}
+EXPORT_SYMBOL(ieee80211_csa_is_complete);
+
 struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
                                         struct ieee80211_vif *vif,
                                         u16 *tim_offset, u16 *tim_length)
@@ -2369,6 +2443,9 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
                struct beacon_data *beacon = rcu_dereference(ap->beacon);
 
                if (beacon) {
+                       if (sdata->vif.csa_active)
+                               ieee80211_update_csa(sdata, beacon);
+
                        /*
                         * headroom, head length,
                         * tail length and maximum TIM length
index 587ff843cf944ef48a8eeb96d13bad1614ef22af..adf1e98f4c3ebadbb86e35c21f58f0994260b2b1 100644 (file)
@@ -349,6 +349,11 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
        [NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
                                  .len = IEEE80211_MAX_DATA_LEN },
        [NL80211_ATTR_PEER_AID] = { .type = NLA_U16 },
+       [NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
+       [NL80211_ATTR_CH_SWITCH_BLOCK_TX] = { .type = NLA_FLAG },
+       [NL80211_ATTR_CSA_IES] = { .type = NLA_NESTED },
+       [NL80211_ATTR_CSA_C_OFF_BEACON] = { .type = NLA_U16 },
+       [NL80211_ATTR_CSA_C_OFF_PRESP] = { .type = NLA_U16 },
 };
 
 /* policy for the key attributes */
@@ -1424,6 +1429,8 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
                if (state->split) {
                        CMD(crit_proto_start, CRIT_PROTOCOL_START);
                        CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
+                       if (dev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)
+                               CMD(channel_switch, CHANNEL_SWITCH);
                }
 
 #ifdef CONFIG_NL80211_TESTMODE
@@ -5615,6 +5622,111 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
        return err;
 }
 
+static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       struct cfg80211_csa_settings params;
+       /* csa_attrs is defined static to avoid waste of stack size - this
+        * function is called under RTNL lock, so this should not be a problem.
+        */
+       static struct nlattr *csa_attrs[NL80211_ATTR_MAX+1];
+       u8 radar_detect_width = 0;
+       int err;
+
+       if (!rdev->ops->channel_switch ||
+           !(rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH))
+               return -EOPNOTSUPP;
+
+       /* may add IBSS support later */
+       if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+           dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
+               return -EOPNOTSUPP;
+
+       memset(&params, 0, sizeof(params));
+
+       if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
+           !info->attrs[NL80211_ATTR_CH_SWITCH_COUNT])
+               return -EINVAL;
+
+       /* only important for AP, IBSS and mesh create IEs internally */
+       if (!info->attrs[NL80211_ATTR_CSA_IES])
+               return -EINVAL;
+
+       /* useless if AP is not running */
+       if (!wdev->beacon_interval)
+               return -EINVAL;
+
+       params.count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
+
+       err = nl80211_parse_beacon(info->attrs, &params.beacon_after);
+       if (err)
+               return err;
+
+       err = nla_parse_nested(csa_attrs, NL80211_ATTR_MAX,
+                              info->attrs[NL80211_ATTR_CSA_IES],
+                              nl80211_policy);
+       if (err)
+               return err;
+
+       err = nl80211_parse_beacon(csa_attrs, &params.beacon_csa);
+       if (err)
+               return err;
+
+       if (!csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON])
+               return -EINVAL;
+
+       params.counter_offset_beacon =
+               nla_get_u16(csa_attrs[NL80211_ATTR_CSA_C_OFF_BEACON]);
+       if (params.counter_offset_beacon >= params.beacon_csa.tail_len)
+               return -EINVAL;
+
+       /* sanity check - counters should be the same */
+       if (params.beacon_csa.tail[params.counter_offset_beacon] !=
+           params.count)
+               return -EINVAL;
+
+       if (csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]) {
+               params.counter_offset_presp =
+                       nla_get_u16(csa_attrs[NL80211_ATTR_CSA_C_OFF_PRESP]);
+               if (params.counter_offset_presp >=
+                   params.beacon_csa.probe_resp_len)
+                       return -EINVAL;
+
+               if (params.beacon_csa.probe_resp[params.counter_offset_presp] !=
+                   params.count)
+                       return -EINVAL;
+       }
+
+       err = nl80211_parse_chandef(rdev, info, &params.chandef);
+       if (err)
+               return err;
+
+       if (!cfg80211_reg_can_beacon(&rdev->wiphy, &params.chandef))
+               return -EINVAL;
+
+       err = cfg80211_chandef_dfs_required(wdev->wiphy, &params.chandef);
+       if (err < 0) {
+               return err;
+       } else if (err) {
+               radar_detect_width = BIT(params.chandef.width);
+               params.radar_required = true;
+       }
+
+       err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
+                                          params.chandef.chan,
+                                          CHAN_MODE_SHARED,
+                                          radar_detect_width);
+       if (err)
+               return err;
+
+       if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
+               params.block_tx = true;
+
+       return rdev_channel_switch(rdev, dev, &params);
+}
+
 static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb,
                            u32 seq, int flags,
                            struct cfg80211_registered_device *rdev,
@@ -9365,7 +9477,15 @@ static struct genl_ops nl80211_ops[] = {
                .flags = GENL_ADMIN_PERM,
                .internal_flags = NL80211_FLAG_NEED_WIPHY |
                                  NL80211_FLAG_NEED_RTNL,
-       }
+       },
+       {
+               .cmd = NL80211_CMD_CHANNEL_SWITCH,
+               .doit = nl80211_channel_switch,
+               .policy = nl80211_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+                                 NL80211_FLAG_NEED_RTNL,
+       },
 };
 
 static struct genl_multicast_group nl80211_mlme_mcgrp = {
index 9f15f0ac824dbb8c73ac88e2ce83f8505ea9a142..de870d4d0bccbdabfb12a37d07aeea1f15d853e8 100644 (file)
@@ -923,4 +923,16 @@ static inline void rdev_crit_proto_stop(struct cfg80211_registered_device *rdev,
        trace_rdev_return_void(&rdev->wiphy);
 }
 
+static inline int rdev_channel_switch(struct cfg80211_registered_device *rdev,
+                                     struct net_device *dev,
+                                     struct cfg80211_csa_settings *params)
+{
+       int ret;
+
+       trace_rdev_channel_switch(&rdev->wiphy, dev, params);
+       ret = rdev->ops->channel_switch(&rdev->wiphy, dev, params);
+       trace_rdev_return_int(&rdev->wiphy, ret);
+       return ret;
+}
+
 #endif /* __CFG80211_RDEV_OPS */
index 09af6eb426a80fcbda096eaaeef4af9522d63099..f0ebdcd394ef0d0686906e3a697bc8915474f2de 100644 (file)
@@ -1841,6 +1841,39 @@ TRACE_EVENT(rdev_crit_proto_stop,
                  WIPHY_PR_ARG, WDEV_PR_ARG)
 );
 
+TRACE_EVENT(rdev_channel_switch,
+       TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
+                struct cfg80211_csa_settings *params),
+       TP_ARGS(wiphy, netdev, params),
+       TP_STRUCT__entry(
+               WIPHY_ENTRY
+               NETDEV_ENTRY
+               CHAN_DEF_ENTRY
+               __field(u16, counter_offset_beacon)
+               __field(u16, counter_offset_presp)
+               __field(bool, radar_required)
+               __field(bool, block_tx)
+               __field(u8, count)
+       ),
+       TP_fast_assign(
+               WIPHY_ASSIGN;
+               NETDEV_ASSIGN;
+               CHAN_DEF_ASSIGN(&params->chandef);
+               __entry->counter_offset_beacon = params->counter_offset_beacon;
+               __entry->counter_offset_presp = params->counter_offset_presp;
+               __entry->radar_required = params->radar_required;
+               __entry->block_tx = params->block_tx;
+               __entry->count = params->count;
+       ),
+       TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT
+                 ", block_tx: %d, count: %u, radar_required: %d"
+                 ", counter offsets (beacon/presp): %u/%u",
+                 WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG,
+                 __entry->block_tx, __entry->count, __entry->radar_required,
+                 __entry->counter_offset_beacon,
+                 __entry->counter_offset_presp)
+);
+
 /*************************************************************
  *          cfg80211 exported functions traces              *
  *************************************************************/