*     well be on the operating channel
  * @SCAN_HW_SCANNING: The hardware is scanning for us, we have no way to
  *     determine if we are on the operating channel or not
- * @SCAN_OFF_CHANNEL: We're off our operating channel for scanning,
- *     gets only set in conjunction with SCAN_SW_SCANNING
  * @SCAN_COMPLETED: Set for our scan work function when the driver reported
  *     that the scan completed.
  * @SCAN_ABORTED: Set for our scan work function when the driver reported
 enum {
        SCAN_SW_SCANNING,
        SCAN_HW_SCANNING,
-       SCAN_OFF_CHANNEL,
        SCAN_COMPLETED,
        SCAN_ABORTED,
 };
                          struct ieee80211_bss *bss);
 
 /* off-channel helpers */
-void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local);
-void ieee80211_offchannel_stop_station(struct ieee80211_local *local);
+bool ieee80211_cfg_on_oper_channel(struct ieee80211_local *local);
+void ieee80211_offchannel_enable_all_ps(struct ieee80211_local *local,
+                                       bool tell_ap);
+void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local,
+                                   bool offchannel_ps_enable);
 void ieee80211_offchannel_return(struct ieee80211_local *local,
-                                bool enable_beaconing);
+                                bool enable_beaconing,
+                                bool offchannel_ps_disable);
 void ieee80211_hw_roc_setup(struct ieee80211_local *local);
 
 /* interface handling */
 
        ieee80211_configure_filter(local);
 }
 
+/*
+ * Returns true if we are logically configured to be on
+ * the operating channel AND the hardware-conf is currently
+ * configured on the operating channel.  Compares channel-type
+ * as well.
+ */
+bool ieee80211_cfg_on_oper_channel(struct ieee80211_local *local)
+{
+       struct ieee80211_channel *chan, *scan_chan;
+       enum nl80211_channel_type channel_type;
+
+       /* This logic needs to match logic in ieee80211_hw_config */
+       if (local->scan_channel) {
+               chan = local->scan_channel;
+               channel_type = NL80211_CHAN_NO_HT;
+       } else if (local->tmp_channel) {
+               chan = scan_chan = local->tmp_channel;
+               channel_type = local->tmp_channel_type;
+       } else {
+               chan = local->oper_channel;
+               channel_type = local->_oper_channel_type;
+       }
+
+       if (chan != local->oper_channel ||
+           channel_type != local->_oper_channel_type)
+               return false;
+
+       /* Check current hardware-config against oper_channel. */
+       if ((local->oper_channel != local->hw.conf.channel) ||
+           (local->_oper_channel_type != local->hw.conf.channel_type))
+               return false;
+
+       return true;
+}
+
 int ieee80211_hw_config(struct ieee80211_local *local, u32 changed)
 {
        struct ieee80211_channel *chan, *scan_chan;
 
        scan_chan = local->scan_channel;
 
+       /* If this off-channel logic ever changes,  ieee80211_on_oper_channel
+        * may need to change as well.
+        */
        offchannel_flag = local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
        if (scan_chan) {
                chan = scan_chan;
                channel_type = NL80211_CHAN_NO_HT;
-               local->hw.conf.flags |= IEEE80211_CONF_OFFCHANNEL;
-       } else if (local->tmp_channel &&
-                  local->oper_channel != local->tmp_channel) {
+       } else if (local->tmp_channel) {
                chan = scan_chan = local->tmp_channel;
                channel_type = local->tmp_channel_type;
-               local->hw.conf.flags |= IEEE80211_CONF_OFFCHANNEL;
        } else {
                chan = local->oper_channel;
                channel_type = local->_oper_channel_type;
-               local->hw.conf.flags &= ~IEEE80211_CONF_OFFCHANNEL;
        }
+
+       if (chan != local->oper_channel ||
+           channel_type != local->_oper_channel_type)
+               local->hw.conf.flags |= IEEE80211_CONF_OFFCHANNEL;
+       else
+               local->hw.conf.flags &= ~IEEE80211_CONF_OFFCHANNEL;
+
        offchannel_flag ^= local->hw.conf.flags & IEEE80211_CONF_OFFCHANNEL;
 
        if (offchannel_flag || chan != local->hw.conf.channel ||
 
        if (changed & BSS_CHANGED_BEACON_ENABLED) {
                if (local->quiescing || !ieee80211_sdata_running(sdata) ||
-                   test_bit(SCAN_SW_SCANNING, &local->scanning)) {
+                   test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state)) {
                        sdata->vif.bss_conf.enable_beacon = false;
                } else {
                        /*
 
 #include "driver-trace.h"
 
 /*
- * inform AP that we will go to sleep so that it will buffer the frames
- * while we scan
+ * Tell our hardware to disable PS.
+ * Optionally inform AP that we will go to sleep so that it will buffer
+ * the frames while we are doing off-channel work.  This is optional
+ * because we *may* be doing work on-operating channel, and want our
+ * hardware unconditionally awake, but still let the AP send us normal frames.
  */
-static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata)
+static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata,
+                                          bool tell_ap)
 {
        struct ieee80211_local *local = sdata->local;
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
                ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
        }
 
-       if (!(local->offchannel_ps_enabled) ||
-           !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK))
+       if (tell_ap && (!local->offchannel_ps_enabled ||
+                       !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK)))
                /*
                 * If power save was enabled, no need to send a nullfunc
                 * frame because AP knows that we are sleeping. But if the
                 * we are sleeping, let's just enable power save mode in
                 * hardware.
                 */
+               /* TODO:  Only set hardware if CONF_PS changed?
+                * TODO:  Should we set offchannel_ps_enabled to false?
+                */
                local->hw.conf.flags |= IEEE80211_CONF_PS;
                ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
        } else if (local->hw.conf.dynamic_ps_timeout > 0) {
        ieee80211_sta_reset_conn_monitor(sdata);
 }
 
-void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local)
+void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local,
+                                   bool offchannel_ps_enable)
 {
        struct ieee80211_sub_if_data *sdata;
 
+       /*
+        * notify the AP about us leaving the channel and stop all
+        * STA interfaces.
+        */
        mutex_lock(&local->iflist_mtx);
        list_for_each_entry(sdata, &local->interfaces, list) {
                if (!ieee80211_sdata_running(sdata))
                        continue;
 
-               /* disable beaconing */
+               if (sdata->vif.type != NL80211_IFTYPE_MONITOR)
+                       set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
+
+               /* Check to see if we should disable beaconing. */
                if (sdata->vif.type == NL80211_IFTYPE_AP ||
                    sdata->vif.type == NL80211_IFTYPE_ADHOC ||
                    sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
                        ieee80211_bss_info_change_notify(
                                sdata, BSS_CHANGED_BEACON_ENABLED);
 
-               /*
-                * only handle non-STA interfaces here, STA interfaces
-                * are handled in ieee80211_offchannel_stop_station(),
-                * e.g., from the background scan state machine.
-                *
-                * In addition, do not stop monitor interface to allow it to be
-                * used from user space controlled off-channel operations.
-                */
-               if (sdata->vif.type != NL80211_IFTYPE_STATION &&
-                   sdata->vif.type != NL80211_IFTYPE_MONITOR) {
-                       set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
+               if (sdata->vif.type != NL80211_IFTYPE_MONITOR) {
                        netif_tx_stop_all_queues(sdata->dev);
+                       if (offchannel_ps_enable &&
+                           (sdata->vif.type == NL80211_IFTYPE_STATION) &&
+                           sdata->u.mgd.associated)
+                               ieee80211_offchannel_ps_enable(sdata, true);
                }
        }
        mutex_unlock(&local->iflist_mtx);
 }
 
-void ieee80211_offchannel_stop_station(struct ieee80211_local *local)
+void ieee80211_offchannel_enable_all_ps(struct ieee80211_local *local,
+                                       bool tell_ap)
 {
        struct ieee80211_sub_if_data *sdata;
 
-       /*
-        * notify the AP about us leaving the channel and stop all STA interfaces
-        */
        mutex_lock(&local->iflist_mtx);
        list_for_each_entry(sdata, &local->interfaces, list) {
                if (!ieee80211_sdata_running(sdata))
                        continue;
 
-               if (sdata->vif.type == NL80211_IFTYPE_STATION) {
-                       set_bit(SDATA_STATE_OFFCHANNEL, &sdata->state);
-                       netif_tx_stop_all_queues(sdata->dev);
-                       if (sdata->u.mgd.associated)
-                               ieee80211_offchannel_ps_enable(sdata);
-               }
+               if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+                   sdata->u.mgd.associated)
+                       ieee80211_offchannel_ps_enable(sdata, tell_ap);
        }
        mutex_unlock(&local->iflist_mtx);
 }
 
 void ieee80211_offchannel_return(struct ieee80211_local *local,
-                                bool enable_beaconing)
+                                bool enable_beaconing,
+                                bool offchannel_ps_disable)
 {
        struct ieee80211_sub_if_data *sdata;
 
                        continue;
 
                /* Tell AP we're back */
-               if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+               if (offchannel_ps_disable &&
+                   sdata->vif.type == NL80211_IFTYPE_STATION) {
                        if (sdata->u.mgd.associated)
                                ieee80211_offchannel_ps_disable(sdata);
                }
                        netif_tx_wake_all_queues(sdata->dev);
                }
 
-               /* re-enable beaconing */
+               /* Check to see if we should re-enable beaconing */
                if (enable_beaconing &&
                    (sdata->vif.type == NL80211_IFTYPE_AP ||
                     sdata->vif.type == NL80211_IFTYPE_ADHOC ||
 
        if (likely(!(status->rx_flags & IEEE80211_RX_IN_SCAN)))
                return RX_CONTINUE;
 
-       if (test_bit(SCAN_HW_SCANNING, &local->scanning))
+       if (test_bit(SCAN_HW_SCANNING, &local->scanning) ||
+           test_bit(SCAN_SW_SCANNING, &local->scanning))
                return ieee80211_scan_rx(rx->sdata, skb);
 
-       if (test_bit(SCAN_SW_SCANNING, &local->scanning)) {
-               /* drop all the other packets during a software scan anyway */
-               if (ieee80211_scan_rx(rx->sdata, skb) != RX_QUEUED)
-                       dev_kfree_skb(skb);
-               return RX_QUEUED;
-       }
-
        /* scanning finished during invoking of handlers */
        I802_DEBUG_INC(local->rx_handlers_drop_passive_scan);
        return RX_DROP_UNUSABLE;
                local->dot11ReceivedFragmentCount++;
 
        if (unlikely(test_bit(SCAN_HW_SCANNING, &local->scanning) ||
-                    test_bit(SCAN_OFF_CHANNEL, &local->scanning)))
+                    test_bit(SCAN_SW_SCANNING, &local->scanning)))
                status->rx_flags |= IEEE80211_RX_IN_SCAN;
 
        if (ieee80211_is_mgmt(fc))
 
        if (bss)
                ieee80211_rx_bss_put(sdata->local, bss);
 
+       /* If we are on-operating-channel, and this packet is for the
+        * current channel, pass the pkt on up the stack so that
+        * the rest of the stack can make use of it.
+        */
+       if (ieee80211_cfg_on_oper_channel(sdata->local)
+           && (channel == sdata->local->oper_channel))
+               return RX_CONTINUE;
+
        dev_kfree_skb(skb);
        return RX_QUEUED;
 }
                                              bool was_hw_scan)
 {
        struct ieee80211_local *local = hw_to_local(hw);
+       bool on_oper_chan;
+       bool enable_beacons = false;
+
+       mutex_lock(&local->mtx);
+       on_oper_chan = ieee80211_cfg_on_oper_channel(local);
+
+       if (was_hw_scan || !on_oper_chan) {
+               if (WARN_ON(local->scan_channel))
+                       local->scan_channel = NULL;
+               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+       }
 
-       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
        if (!was_hw_scan) {
+               bool on_oper_chan2;
                ieee80211_configure_filter(local);
                drv_sw_scan_complete(local);
-               ieee80211_offchannel_return(local, true);
+               on_oper_chan2 = ieee80211_cfg_on_oper_channel(local);
+               /* We should always be on-channel at this point. */
+               WARN_ON(!on_oper_chan2);
+               if (on_oper_chan2 && (on_oper_chan != on_oper_chan2))
+                       enable_beacons = true;
+
+               ieee80211_offchannel_return(local, enable_beacons, true);
        }
 
-       mutex_lock(&local->mtx);
        ieee80211_recalc_idle(local);
        mutex_unlock(&local->mtx);
 
         */
        drv_sw_scan_start(local);
 
-       ieee80211_offchannel_stop_beaconing(local);
-
        local->leave_oper_channel_time = 0;
        local->next_scan_state = SCAN_DECISION;
        local->scan_channel_idx = 0;
 
-       drv_flush(local, false);
+       /* We always want to use off-channel PS, even if we
+        * are not really leaving oper-channel.  Don't
+        * tell the AP though, as long as we are on-channel.
+        */
+       ieee80211_offchannel_enable_all_ps(local, false);
 
        ieee80211_configure_filter(local);
 
        }
        mutex_unlock(&local->iflist_mtx);
 
-       if (local->scan_channel) {
+       next_chan = local->scan_req->channels[local->scan_channel_idx];
+
+       if (ieee80211_cfg_on_oper_channel(local)) {
+               /* We're currently on operating channel. */
+               if ((next_chan == local->oper_channel) &&
+                   (local->_oper_channel_type == NL80211_CHAN_NO_HT))
+                       /* We don't need to move off of operating channel. */
+                       local->next_scan_state = SCAN_SET_CHANNEL;
+               else
+                       /*
+                        * We do need to leave operating channel, as next
+                        * scan is somewhere else.
+                        */
+                       local->next_scan_state = SCAN_LEAVE_OPER_CHANNEL;
+       } else {
                /*
                 * we're currently scanning a different channel, let's
                 * see if we can scan another channel without interfering
                 *
                 * Otherwise switch back to the operating channel.
                 */
-               next_chan = local->scan_req->channels[local->scan_channel_idx];
 
                bad_latency = time_after(jiffies +
                                ieee80211_scan_get_channel_time(next_chan),
                        local->next_scan_state = SCAN_ENTER_OPER_CHANNEL;
                else
                        local->next_scan_state = SCAN_SET_CHANNEL;
-       } else {
-               /*
-                * we're on the operating channel currently, let's
-                * leave that channel now to scan another one
-                */
-               local->next_scan_state = SCAN_LEAVE_OPER_CHANNEL;
        }
 
        *next_delay = 0;
 static void ieee80211_scan_state_leave_oper_channel(struct ieee80211_local *local,
                                                    unsigned long *next_delay)
 {
-       ieee80211_offchannel_stop_station(local);
-
-       __set_bit(SCAN_OFF_CHANNEL, &local->scanning);
+       /* PS will already be in off-channel mode,
+        * we do that once at the beginning of scanning.
+        */
+       ieee80211_offchannel_stop_vifs(local, false);
 
        /*
         * What if the nullfunc frames didn't arrive?
 {
        /* switch back to the operating channel */
        local->scan_channel = NULL;
-       ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+       if (!ieee80211_cfg_on_oper_channel(local))
+               ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
 
        /*
-        * Only re-enable station mode interface now; beaconing will be
-        * re-enabled once the full scan has been completed.
+        * Re-enable vifs and beaconing.  Leave PS
+        * in off-channel state..will put that back
+        * on-channel at the end of scanning.
         */
-       ieee80211_offchannel_return(local, false);
-
-       __clear_bit(SCAN_OFF_CHANNEL, &local->scanning);
+       ieee80211_offchannel_return(local, true, false);
 
        *next_delay = HZ / 5;
        local->next_scan_state = SCAN_DECISION;
        chan = local->scan_req->channels[local->scan_channel_idx];
 
        local->scan_channel = chan;
-       if (ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL))
-               skip = 1;
+
+       /* Only call hw-config if we really need to change channels. */
+       if ((chan != local->hw.conf.channel) ||
+           (local->hw.conf.channel_type != NL80211_CHAN_NO_HT))
+               if (ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL))
+                       skip = 1;
 
        /* advance state machine to next channel/band */
        local->scan_channel_idx++;
 
        if (unlikely(info->flags & IEEE80211_TX_CTL_INJECTED))
                return TX_CONTINUE;
 
-       if (unlikely(test_bit(SCAN_OFF_CHANNEL, &tx->local->scanning)) &&
+       if (unlikely(test_bit(SCAN_SW_SCANNING, &tx->local->scanning)) &&
+           test_bit(SDATA_STATE_OFFCHANNEL, &tx->sdata->state) &&
            !ieee80211_is_probe_req(hdr->frame_control) &&
            !ieee80211_is_nullfunc(hdr->frame_control))
                /*
 
                }
 
                if (!started && !local->tmp_channel) {
-                       /*
-                        * TODO: could optimize this by leaving the
-                        *       station vifs in awake mode if they
-                        *       happen to be on the same channel as
-                        *       the requested channel
-                        */
-                       ieee80211_offchannel_stop_beaconing(local);
-                       ieee80211_offchannel_stop_station(local);
+                       bool on_oper_chan;
+                       bool tmp_chan_changed = false;
+                       bool on_oper_chan2;
+                       on_oper_chan = ieee80211_cfg_on_oper_channel(local);
+                       if (local->tmp_channel)
+                               if ((local->tmp_channel != wk->chan) ||
+                                   (local->tmp_channel_type != wk->chan_type))
+                                       tmp_chan_changed = true;
 
                        local->tmp_channel = wk->chan;
                        local->tmp_channel_type = wk->chan_type;
-                       ieee80211_hw_config(local, 0);
+                       /*
+                        * Leave the station vifs in awake mode if they
+                        * happen to be on the same channel as
+                        * the requested channel.
+                        */
+                       on_oper_chan2 = ieee80211_cfg_on_oper_channel(local);
+                       if (on_oper_chan != on_oper_chan2) {
+                               if (on_oper_chan2) {
+                                       /* going off oper channel, PS too */
+                                       ieee80211_offchannel_stop_vifs(local,
+                                                                      true);
+                                       ieee80211_hw_config(local, 0);
+                               } else {
+                                       /* going on channel, but leave PS
+                                        * off-channel. */
+                                       ieee80211_hw_config(local, 0);
+                                       ieee80211_offchannel_return(local,
+                                                                   true,
+                                                                   false);
+                               }
+                       } else if (tmp_chan_changed)
+                               /* Still off-channel, but on some other
+                                * channel, so update hardware.
+                                * PS should already be off-channel.
+                                */
+                               ieee80211_hw_config(local, 0);
+
                        started = true;
                        wk->timeout = jiffies;
                }
        }
 
        if (!remain_off_channel && local->tmp_channel) {
+               bool on_oper_chan = ieee80211_cfg_on_oper_channel(local);
                local->tmp_channel = NULL;
-               ieee80211_hw_config(local, 0);
-               ieee80211_offchannel_return(local, true);
+               /* If tmp_channel wasn't operating channel, then
+                * we need to go back on-channel.
+                * NOTE:  If we can ever be here while scannning,
+                * or if the hw_config() channel config logic changes,
+                * then we may need to do a more thorough check to see if
+                * we still need to do a hardware config.  Currently,
+                * we cannot be here while scanning, however.
+                */
+               if (ieee80211_cfg_on_oper_channel(local) && !on_oper_chan)
+                       ieee80211_hw_config(local, 0);
+
+               /* At the least, we need to disable offchannel_ps,
+                * so just go ahead and run the entire offchannel
+                * return logic here.  We *could* skip enabling
+                * beaconing if we were already on-oper-channel
+                * as a future optimization.
+                */
+               ieee80211_offchannel_return(local, true, true);
+
                /* give connection some time to breathe */
                run_again(local, jiffies + HZ/2);
        }