/**
  * enum ieee80211_chanctx_change - change flag for channel context
  * @IEEE80211_CHANCTX_CHANGE_CHANNEL_TYPE: The channel type was changed
+ * @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
  */
 enum ieee80211_chanctx_change {
        IEEE80211_CHANCTX_CHANGE_CHANNEL_TYPE   = BIT(0),
+       IEEE80211_CHANCTX_CHANGE_RX_CHAINS      = BIT(1),
 };
 
 /**
  *
  * @channel: the channel to tune to
  * @channel_type: the channel (HT) type
+ * @rx_chains_static: The number of RX chains that must always be
+ *     active on the channel to receive MIMO transmissions
+ * @rx_chains_dynamic: The number of RX chains that must be enabled
+ *     after RTS/CTS handshake to receive SMPS MIMO transmissions;
+ *     this will always be >= @rx_chains_always.
  * @drv_priv: data area for driver use, will always be aligned to
  *     sizeof(void *), size is determined in hw information.
  */
        struct ieee80211_channel *channel;
        enum nl80211_channel_type channel_type;
 
+       u8 rx_chains_static, rx_chains_dynamic;
+
        u8 drv_priv[0] __attribute__((__aligned__(sizeof(void *))));
 };
 
  * @IEEE80211_CONF_CHANGE_RETRY_LIMITS: retry limits changed
  * @IEEE80211_CONF_CHANGE_IDLE: Idle flag changed
  * @IEEE80211_CONF_CHANGE_SMPS: Spatial multiplexing powersave mode changed
+ *     Note that this is only valid if channel contexts are not used,
+ *     otherwise each channel context has the number of chains listed.
  */
 enum ieee80211_conf_changed {
        IEEE80211_CONF_CHANGE_SMPS              = BIT(1),
  *
  * @smps_mode: spatial multiplexing powersave mode; note that
  *     %IEEE80211_SMPS_STATIC is used when the device is not
- *     configured for an HT channel
+ *     configured for an HT channel.
+ *     Note that this is only valid if channel contexts are not used,
+ *     otherwise each channel context has the number of chains listed.
  */
 struct ieee80211_conf {
        u32 flags;
 
        if (old)
                return -EALREADY;
 
+       /* TODO: make hostapd tell us what it wants */
+       sdata->smps_mode = IEEE80211_SMPS_OFF;
+       sdata->needed_rx_chains = sdata->local->rx_chains;
+
        err = ieee80211_vif_use_channel(sdata, params->channel,
                                        params->channel_type,
                                        IEEE80211_CHANCTX_SHARED);
        if (err)
                return err;
 
+       /* can mesh use other SMPS modes? */
+       sdata->smps_mode = IEEE80211_SMPS_OFF;
+       sdata->needed_rx_chains = sdata->local->rx_chains;
+
        err = ieee80211_vif_use_channel(sdata, setup->channel,
                                        setup->channel_type,
                                        IEEE80211_CHANCTX_SHARED);
 
        /*
         * If not associated, or current association is not an HT
-        * association, there's no need to send an action frame.
+        * association, there's no need to do anything, just store
+        * the new value until we associate.
         */
        if (!sdata->u.mgd.associated ||
-           sdata->vif.bss_conf.channel_type == NL80211_CHAN_NO_HT) {
-               ieee80211_recalc_smps(sdata->local);
+           sdata->vif.bss_conf.channel_type == NL80211_CHAN_NO_HT)
                return 0;
-       }
 
        ap = sdata->u.mgd.associated->bssid;
 
 
 
        ctx->conf.channel = channel;
        ctx->conf.channel_type = channel_type;
+       ctx->conf.rx_chains_static = 1;
+       ctx->conf.rx_chains_dynamic = 1;
        ctx->mode = mode;
 
        if (!local->use_chanctx) {
 
        drv_unassign_vif_chanctx(local, sdata, ctx);
 
-       if (ctx->refcount > 0)
+       if (ctx->refcount > 0) {
                ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
+               ieee80211_recalc_smps_chanctx(local, ctx);
+       }
 }
 
 static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
                ieee80211_free_chanctx(local, ctx);
 }
 
+void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
+                                  struct ieee80211_chanctx *chanctx)
+{
+       struct ieee80211_sub_if_data *sdata;
+       u8 rx_chains_static, rx_chains_dynamic;
+
+       lockdep_assert_held(&local->chanctx_mtx);
+
+       rx_chains_static = 1;
+       rx_chains_dynamic = 1;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+               u8 needed_static, needed_dynamic;
+
+               if (!ieee80211_sdata_running(sdata))
+                       continue;
+
+               if (rcu_access_pointer(sdata->vif.chanctx_conf) !=
+                                               &chanctx->conf)
+                       continue;
+
+               switch (sdata->vif.type) {
+               case NL80211_IFTYPE_P2P_DEVICE:
+                       continue;
+               case NL80211_IFTYPE_STATION:
+                       if (!sdata->u.mgd.associated)
+                               continue;
+                       break;
+               case NL80211_IFTYPE_AP_VLAN:
+                       continue;
+               case NL80211_IFTYPE_AP:
+               case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_WDS:
+               case NL80211_IFTYPE_MESH_POINT:
+                       break;
+               default:
+                       WARN_ON_ONCE(1);
+               }
+
+               switch (sdata->smps_mode) {
+               default:
+                       WARN_ONCE(1, "Invalid SMPS mode %d\n",
+                                 sdata->smps_mode);
+                       /* fall through */
+               case IEEE80211_SMPS_OFF:
+                       needed_static = sdata->needed_rx_chains;
+                       needed_dynamic = sdata->needed_rx_chains;
+                       break;
+               case IEEE80211_SMPS_DYNAMIC:
+                       needed_static = 1;
+                       needed_dynamic = sdata->needed_rx_chains;
+                       break;
+               case IEEE80211_SMPS_STATIC:
+                       needed_static = 1;
+                       needed_dynamic = 1;
+                       break;
+               }
+
+               rx_chains_static = max(rx_chains_static, needed_static);
+               rx_chains_dynamic = max(rx_chains_dynamic, needed_dynamic);
+       }
+       rcu_read_unlock();
+
+       if (!local->use_chanctx) {
+               if (rx_chains_static > 1)
+                       local->smps_mode = IEEE80211_SMPS_OFF;
+               else if (rx_chains_dynamic > 1)
+                       local->smps_mode = IEEE80211_SMPS_DYNAMIC;
+               else
+                       local->smps_mode = IEEE80211_SMPS_STATIC;
+               ieee80211_hw_config(local, 0);
+       }
+
+       if (rx_chains_static == chanctx->conf.rx_chains_static &&
+           rx_chains_dynamic == chanctx->conf.rx_chains_dynamic)
+               return;
+
+       chanctx->conf.rx_chains_static = rx_chains_static;
+       chanctx->conf.rx_chains_dynamic = rx_chains_dynamic;
+       drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RX_CHAINS);
+}
+
 int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
                              struct ieee80211_channel *channel,
                              enum nl80211_channel_type channel_type,
                goto out;
        }
 
+       ieee80211_recalc_smps_chanctx(local, ctx);
  out:
        mutex_unlock(&local->chanctx_mtx);
        return ret;
 
 
        return snprintf(buf, buflen, "request: %s\nused: %s\n",
                        smps_modes[sdata->u.mgd.req_smps],
-                       smps_modes[sdata->u.mgd.ap_smps]);
+                       smps_modes[sdata->smps_mode]);
 }
 
 static ssize_t ieee80211_if_parse_smps(struct ieee80211_sub_if_data *sdata,
 
        changed |= BSS_CHANGED_HT;
        ieee80211_bss_info_change_notify(sdata, changed);
 
+       sdata->smps_mode = IEEE80211_SMPS_OFF;
+       sdata->needed_rx_chains = sdata->local->rx_chains;
+
        ieee80211_queue_work(&sdata->local->hw, &sdata->work);
 
        return 0;
 
        bool powersave; /* powersave requested for this iface */
        bool broken_ap; /* AP is broken -- turn off powersave */
        enum ieee80211_smps_mode req_smps, /* requested smps mode */
-                                ap_smps, /* smps mode AP thinks we're in */
                                 driver_smps_mode; /* smps mode request */
 
        struct work_struct request_smps_work;
 
        struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
 
+       /* used to reconfigure hardware SM PS */
+       struct work_struct recalc_smps;
+
        struct work_struct work;
        struct sk_buff_head skb_queue;
 
        bool arp_filter_state;
 
+       u8 needed_rx_chains;
+       enum ieee80211_smps_mode smps_mode;
+
        /*
         * AP this belongs to: self in AP mode and
         * corresponding AP in VLAN mode, NULL for
        /* used for uploading changed mc list */
        struct work_struct reconfig_filter;
 
-       /* used to reconfigure hardware SM PS */
-       struct work_struct recalc_smps;
-
        /* aggregated multicast list */
        struct netdev_hw_addr_list mc_list;
 
        /* wowlan is enabled -- don't reconfig on resume */
        bool wowlan;
 
+       /* number of RX chains the hardware has */
+       u8 rx_chains;
+
        int tx_headroom; /* required headroom for hardware/radiotap */
 
        /* Tasklet and skb queue to process calls from IRQ mode. All frames
 void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid);
 void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid);
 
+u8 ieee80211_mcs_to_chains(const struct ieee80211_mcs_info *mcs);
+
 /* Spectrum management */
 void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata,
                                       struct ieee80211_mgmt *mgmt,
                            enum ieee80211_band band, u32 *basic_rates);
 int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata,
                             enum ieee80211_smps_mode smps_mode);
-void ieee80211_recalc_smps(struct ieee80211_local *local);
+void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata);
 
 size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
                          const u8 *ids, int n_ids, size_t offset);
                          enum ieee80211_chanctx_mode mode);
 void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata);
 
+void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local,
+                                  struct ieee80211_chanctx *chanctx);
+
 #ifdef CONFIG_MAC80211_NOINLINE
 #define debug_noinline noinline
 #else
 
        del_timer_sync(&local->dynamic_ps_timer);
        cancel_work_sync(&local->dynamic_ps_enable_work);
 
+       cancel_work_sync(&sdata->recalc_smps);
+
        /* APs need special treatment */
        if (sdata->vif.type == NL80211_IFTYPE_AP) {
                struct ieee80211_sub_if_data *vlan, *tmpsdata;
        }
 }
 
+static void ieee80211_recalc_smps_work(struct work_struct *work)
+{
+       struct ieee80211_sub_if_data *sdata =
+               container_of(work, struct ieee80211_sub_if_data, recalc_smps);
+
+       ieee80211_recalc_smps(sdata);
+}
 
 /*
  * Helper function to initialise an interface to a specific type.
 
        skb_queue_head_init(&sdata->skb_queue);
        INIT_WORK(&sdata->work, ieee80211_iface_work);
+       INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
 
        switch (type) {
        case NL80211_IFTYPE_P2P_GO:
 
 }
 EXPORT_SYMBOL(ieee80211_restart_hw);
 
-static void ieee80211_recalc_smps_work(struct work_struct *work)
-{
-       struct ieee80211_local *local =
-               container_of(work, struct ieee80211_local, recalc_smps);
-
-       ieee80211_recalc_smps(local);
-}
-
 #ifdef CONFIG_INET
 static int ieee80211_ifa_changed(struct notifier_block *nb,
                                 unsigned long data, void *arg)
        INIT_WORK(&local->restart_work, ieee80211_restart_work);
 
        INIT_WORK(&local->reconfig_filter, ieee80211_reconfig_filter);
-       INIT_WORK(&local->recalc_smps, ieee80211_recalc_smps_work);
        local->smps_mode = IEEE80211_SMPS_OFF;
 
        INIT_WORK(&local->dynamic_ps_enable_work,
        if (hw->max_report_rates == 0)
                hw->max_report_rates = hw->max_rates;
 
+       local->rx_chains = 1;
+
        /*
         * generic code guarantees at least one band,
         * set this very early because much code assumes
                        max_bitrates = sband->n_bitrates;
                supp_ht = supp_ht || sband->ht_cap.ht_supported;
                supp_vht = supp_vht || sband->vht_cap.vht_supported;
+
+               if (sband->ht_cap.ht_supported)
+                       local->rx_chains =
+                               max(ieee80211_mcs_to_chains(&sband->ht_cap.mcs),
+                                   local->rx_chains);
+
+               /* TODO: consider VHT for RX chains, hopefully it's the same */
        }
 
        local->int_scan_req = kzalloc(sizeof(*local->int_scan_req) +
 
 
        if (!(ifmgd->flags & IEEE80211_STA_DISABLE_11N))
                ieee80211_add_ht_ie(sdata, skb, assoc_data->ap_ht_param,
-                                   sband, chan, ifmgd->ap_smps);
+                                   sband, chan, sdata->smps_mode);
 
        if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))
                ieee80211_add_vht_ie(sdata, skb, sband);
        ieee80211_recalc_ps(local, -1);
        mutex_unlock(&local->iflist_mtx);
 
-       ieee80211_recalc_smps(local);
+       ieee80211_recalc_smps(sdata);
        ieee80211_recalc_ps_vif(sdata);
 
        netif_tx_start_all_queues(sdata->dev);
        }
 
        if (ht_oper) {
+               const u8 *ht_cap_ie;
+               const struct ieee80211_ht_cap *ht_cap;
+               u8 chains = 1;
+
                channel_type = NL80211_CHAN_HT20;
 
                if (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) {
                                break;
                        }
                }
+
+               ht_cap_ie = cfg80211_find_ie(WLAN_EID_HT_CAPABILITY,
+                                            cbss->information_elements,
+                                            cbss->len_information_elements);
+               if (ht_cap_ie && ht_cap_ie[1] >= sizeof(*ht_cap)) {
+                       ht_cap = (void *)(ht_cap_ie + 2);
+                       chains = ieee80211_mcs_to_chains(&ht_cap->mcs);
+               }
+               sdata->needed_rx_chains = min(chains, local->rx_chains);
+       } else {
+               sdata->needed_rx_chains = 1;
        }
 
+       /* will change later if needed */
+       sdata->smps_mode = IEEE80211_SMPS_OFF;
+
        ieee80211_vif_release_channel(sdata);
        return ieee80211_vif_use_channel(sdata, cbss->channel, channel_type,
                                         IEEE80211_CHANCTX_SHARED);
 
        if (ifmgd->req_smps == IEEE80211_SMPS_AUTOMATIC) {
                if (ifmgd->powersave)
-                       ifmgd->ap_smps = IEEE80211_SMPS_DYNAMIC;
+                       sdata->smps_mode = IEEE80211_SMPS_DYNAMIC;
                else
-                       ifmgd->ap_smps = IEEE80211_SMPS_OFF;
+                       sdata->smps_mode = IEEE80211_SMPS_OFF;
        } else
-               ifmgd->ap_smps = ifmgd->req_smps;
+               sdata->smps_mode = ifmgd->req_smps;
 
        assoc_data->capability = req->bss->capability;
        assoc_data->wmm = bss->wmm_used &&
 
        }
 
        if (ieee80211_is_action(mgmt->frame_control) &&
-           sdata->vif.type == NL80211_IFTYPE_STATION &&
            mgmt->u.action.category == WLAN_CATEGORY_HT &&
-           mgmt->u.action.u.ht_smps.action == WLAN_HT_ACTION_SMPS) {
+           mgmt->u.action.u.ht_smps.action == WLAN_HT_ACTION_SMPS &&
+           sdata->vif.type == NL80211_IFTYPE_STATION &&
+           ieee80211_sdata_running(sdata)) {
                /*
                 * This update looks racy, but isn't -- if we come
                 * here we've definitely got a station that we're
                 * talking to, and on a managed interface that can
                 * only be the AP. And the only other place updating
-                * this variable is before we're associated.
+                * this variable in managed mode is before association.
                 */
                switch (mgmt->u.action.u.ht_smps.smps_control) {
                case WLAN_HT_SMPS_CONTROL_DYNAMIC:
-                       sta->sdata->u.mgd.ap_smps = IEEE80211_SMPS_DYNAMIC;
+                       sdata->smps_mode = IEEE80211_SMPS_DYNAMIC;
                        break;
                case WLAN_HT_SMPS_CONTROL_STATIC:
-                       sta->sdata->u.mgd.ap_smps = IEEE80211_SMPS_STATIC;
+                       sdata->smps_mode = IEEE80211_SMPS_STATIC;
                        break;
                case WLAN_HT_SMPS_CONTROL_DISABLED:
                default: /* shouldn't happen since we don't send that */
-                       sta->sdata->u.mgd.ap_smps = IEEE80211_SMPS_OFF;
+                       sdata->smps_mode = IEEE80211_SMPS_OFF;
                        break;
                }
 
-               ieee80211_queue_work(&local->hw, &local->recalc_smps);
+               ieee80211_queue_work(&local->hw, &sdata->recalc_smps);
        }
 }
 
 
 #define VIF_PR_ARG     __get_str(vif_name), __entry->vif_type, __entry->p2p ? "/p2p" : ""
 
 #define CHANCTX_ENTRY  __field(int, freq)                                      \
-                       __field(int, chantype)
+                       __field(int, chantype)                                  \
+                       __field(u8, rx_chains_static)                           \
+                       __field(u8, rx_chains_dynamic)
 #define CHANCTX_ASSIGN __entry->freq = ctx->conf.channel->center_freq;         \
-                       __entry->chantype = ctx->conf.channel_type
-#define CHANCTX_PR_FMT " freq:%d MHz chantype:%d"
-#define CHANCTX_PR_ARG __entry->freq, __entry->chantype
+                       __entry->chantype = ctx->conf.channel_type;             \
+                       __entry->rx_chains_static = ctx->conf.rx_chains_static; \
+                       __entry->rx_chains_dynamic = ctx->conf.rx_chains_dynamic
+#define CHANCTX_PR_FMT " freq:%d MHz chantype:%d chains:%d/%d"
+#define CHANCTX_PR_ARG __entry->freq, __entry->chantype,                       \
+                       __entry->rx_chains_static, __entry->rx_chains_dynamic
 
 
 
 
 }
 EXPORT_SYMBOL_GPL(ieee80211_resume_disconnect);
 
-static int check_mgd_smps(struct ieee80211_if_managed *ifmgd,
-                         enum ieee80211_smps_mode *smps_mode)
+void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata)
 {
-       if (ifmgd->associated) {
-               *smps_mode = ifmgd->ap_smps;
-
-               if (*smps_mode == IEEE80211_SMPS_AUTOMATIC) {
-                       if (ifmgd->powersave)
-                               *smps_mode = IEEE80211_SMPS_DYNAMIC;
-                       else
-                               *smps_mode = IEEE80211_SMPS_OFF;
-               }
-
-               return 1;
-       }
-
-       return 0;
-}
-
-void ieee80211_recalc_smps(struct ieee80211_local *local)
-{
-       struct ieee80211_sub_if_data *sdata;
-       enum ieee80211_smps_mode smps_mode = IEEE80211_SMPS_OFF;
-       int count = 0;
-
-       mutex_lock(&local->iflist_mtx);
-
-       /*
-        * This function could be improved to handle multiple
-        * interfaces better, but right now it makes any
-        * non-station interfaces force SM PS to be turned
-        * off. If there are multiple station interfaces it
-        * could also use the best possible mode, e.g. if
-        * one is in static and the other in dynamic then
-        * dynamic is ok.
-        */
-
-       list_for_each_entry(sdata, &local->interfaces, list) {
-               if (!ieee80211_sdata_running(sdata))
-                       continue;
-               if (sdata->vif.type == NL80211_IFTYPE_P2P_DEVICE)
-                       continue;
-               if (sdata->vif.type != NL80211_IFTYPE_STATION)
-                       goto set;
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *chanctx_conf;
+       struct ieee80211_chanctx *chanctx;
 
-               count += check_mgd_smps(&sdata->u.mgd, &smps_mode);
+       mutex_lock(&local->chanctx_mtx);
 
-               if (count > 1) {
-                       smps_mode = IEEE80211_SMPS_OFF;
-                       break;
-               }
-       }
+       chanctx_conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+                                       lockdep_is_held(&local->chanctx_mtx));
 
-       if (smps_mode == local->smps_mode)
+       if (WARN_ON_ONCE(!chanctx_conf))
                goto unlock;
 
- set:
-       local->smps_mode = smps_mode;
-       /* changed flag is auto-detected for this */
-       ieee80211_hw_config(local, 0);
+       chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+       ieee80211_recalc_smps_chanctx(local, chanctx);
  unlock:
-       mutex_unlock(&local->iflist_mtx);
+       mutex_unlock(&local->chanctx_mtx);
 }
 
 static bool ieee80211_id_in_list(const u8 *ids, int n_ids, u8 id)
        return ifmgd->ave_beacon_signal;
 }
 EXPORT_SYMBOL_GPL(ieee80211_ave_rssi);
+
+u8 ieee80211_mcs_to_chains(const struct ieee80211_mcs_info *mcs)
+{
+       if (!mcs)
+               return 1;
+
+       /* TODO: consider rx_highest */
+
+       if (mcs->rx_mask[3])
+               return 4;
+       if (mcs->rx_mask[2])
+               return 3;
+       if (mcs->rx_mask[1])
+               return 2;
+       return 1;
+}