mac80211: support more than one band in scan request
authorDavid Spinadel <david.spinadel@intel.com>
Wed, 5 Feb 2014 13:21:13 +0000 (15:21 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 25 Jun 2014 07:10:42 +0000 (09:10 +0200)
Some drivers (such as iwlmvm) can handle multiple bands in a single
HW scan request. Add a HW flag to indicate that the driver support
this. To hold the required data, create a separate structure for
HW scan request that holds cfg scan request and data about
different parts of the scan IEs.

As this changes the mac80211 API, update all drivers using it to
use the correct new function type/argument.

Signed-off-by: David Spinadel <david.spinadel@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
16 files changed:
drivers/net/wireless/at76c50x-usb.c
drivers/net/wireless/ath/ath10k/mac.c
drivers/net/wireless/cw1200/scan.c
drivers/net/wireless/cw1200/scan.h
drivers/net/wireless/iwlegacy/common.c
drivers/net/wireless/iwlegacy/common.h
drivers/net/wireless/iwlwifi/dvm/mac80211.c
drivers/net/wireless/iwlwifi/mvm/mac80211.c
drivers/net/wireless/mac80211_hwsim.c
drivers/net/wireless/ti/wl1251/main.c
drivers/net/wireless/ti/wlcore/main.c
include/net/mac80211.h
net/mac80211/driver-ops.h
net/mac80211/ieee80211_i.h
net/mac80211/scan.c
net/mac80211/util.c

index d48776e4f343e050787510923bc01e689a2bd9cc..334c2ece855a92af6576b01ec87cd47af58b8540 100644 (file)
@@ -1955,8 +1955,9 @@ static void at76_dwork_hw_scan(struct work_struct *work)
 
 static int at76_hw_scan(struct ieee80211_hw *hw,
                        struct ieee80211_vif *vif,
-                       struct cfg80211_scan_request *req)
+                       struct ieee80211_scan_request *hw_req)
 {
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct at76_priv *priv = hw->priv;
        struct at76_req_scan scan;
        u8 *ssid = NULL;
index a21080028c54eeedff71b9b00a327620d9780dc3..b8314a534972a6fc51fac13e69c3ac26f059c2dc 100644 (file)
@@ -3137,10 +3137,11 @@ exit:
 
 static int ath10k_hw_scan(struct ieee80211_hw *hw,
                          struct ieee80211_vif *vif,
-                         struct cfg80211_scan_request *req)
+                         struct ieee80211_scan_request *hw_req)
 {
        struct ath10k *ar = hw->priv;
        struct ath10k_vif *arvif = ath10k_vif_to_arvif(vif);
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct wmi_start_scan_arg arg;
        int ret = 0;
        int i;
index 9afcd4ce3368d37908a055df935f29cf4032f259..b2fb6c632092b202d484dcc6d1d73b2840ecf255 100644 (file)
@@ -53,9 +53,10 @@ static int cw1200_scan_start(struct cw1200_common *priv, struct wsm_scan *scan)
 
 int cw1200_hw_scan(struct ieee80211_hw *hw,
                   struct ieee80211_vif *vif,
-                  struct cfg80211_scan_request *req)
+                  struct ieee80211_scan_request *hw_req)
 {
        struct cw1200_common *priv = hw->priv;
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct wsm_template_frame frame = {
                .frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
        };
index 5a8296ccfa82c962fd0f07ddfec80cfcee01bb14..cc75459e5784d9e8e28a677ba39195e36413e500 100644 (file)
@@ -41,7 +41,7 @@ struct cw1200_scan {
 
 int cw1200_hw_scan(struct ieee80211_hw *hw,
                   struct ieee80211_vif *vif,
-                  struct cfg80211_scan_request *req);
+                  struct ieee80211_scan_request *hw_req);
 void cw1200_scan_work(struct work_struct *work);
 void cw1200_scan_timeout(struct work_struct *work);
 void cw1200_clear_recent_scan_work(struct work_struct *work);
index ecc674627e6e10b30a5a7b11ab3150c1ad42b37e..03de7467aecf7b63fef9ae007a15c13dbe5594f0 100644 (file)
@@ -1572,8 +1572,9 @@ il_scan_initiate(struct il_priv *il, struct ieee80211_vif *vif)
 
 int
 il_mac_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
-              struct cfg80211_scan_request *req)
+              struct ieee80211_scan_request *hw_req)
 {
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct il_priv *il = hw->priv;
        int ret;
 
index ea5c0f863c4ee35b2cf6738569a5cb3253553c12..5b972798bdffb66d6acd242e80765fc574ce83af 100644 (file)
@@ -1787,7 +1787,7 @@ int il_scan_cancel(struct il_priv *il);
 int il_scan_cancel_timeout(struct il_priv *il, unsigned long ms);
 void il_force_scan_end(struct il_priv *il);
 int il_mac_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
-                  struct cfg80211_scan_request *req);
+                  struct ieee80211_scan_request *hw_req);
 void il_internal_short_hw_scan(struct il_priv *il);
 int il_force_reset(struct il_priv *il, bool external);
 u16 il_fill_probe_req(struct il_priv *il, struct ieee80211_mgmt *frame,
index 29af7b51e3708788d02f4a1651205a348a5102dd..afb98f4fdaf3608afd3c9ab844ed34c8dff6851a 100644 (file)
@@ -1495,9 +1495,10 @@ static int iwlagn_mac_change_interface(struct ieee80211_hw *hw,
 
 static int iwlagn_mac_hw_scan(struct ieee80211_hw *hw,
                              struct ieee80211_vif *vif,
-                             struct cfg80211_scan_request *req)
+                             struct ieee80211_scan_request *hw_req)
 {
        struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
+       struct cfg80211_scan_request *req = &hw_req->req;
        int ret;
 
        IWL_DEBUG_MAC80211(priv, "enter\n");
index 7215f59801863d3b7d72398de8c96c7b73c3902b..4dc2e05f49ce840aa7735efe7ccee8ffd753b7c8 100644 (file)
@@ -1537,9 +1537,10 @@ static void iwl_mvm_bss_info_changed(struct ieee80211_hw *hw,
 
 static int iwl_mvm_mac_hw_scan(struct ieee80211_hw *hw,
                               struct ieee80211_vif *vif,
-                              struct cfg80211_scan_request *req)
+                              struct ieee80211_scan_request *hw_req)
 {
        struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
+       struct cfg80211_scan_request *req = &hw_req->req;
        int ret;
 
        if (req->n_channels == 0 || req->n_channels > MAX_NUM_SCAN_CHANNELS)
index 06a0722164dac7f8062d6cccaef8f03c6f2663ab..eba51460a5ded4b6456a18b946153c6a310bdb48 100644 (file)
@@ -1736,9 +1736,10 @@ static void hw_scan_work(struct work_struct *work)
 
 static int mac80211_hwsim_hw_scan(struct ieee80211_hw *hw,
                                  struct ieee80211_vif *vif,
-                                 struct cfg80211_scan_request *req)
+                                 struct ieee80211_scan_request *hw_req)
 {
        struct mac80211_hwsim_data *hwsim = hw->priv;
+       struct cfg80211_scan_request *req = &hw_req->req;
 
        mutex_lock(&hwsim->mutex);
        if (WARN_ON(hwsim->tmp_chan || hwsim->hw_scan_request)) {
index 4e782f18ae3431600a66923216faa536d42c46b6..38234851457e51cd22829a1ea25c7e26cc584c21 100644 (file)
@@ -991,8 +991,9 @@ out:
 
 static int wl1251_op_hw_scan(struct ieee80211_hw *hw,
                             struct ieee80211_vif *vif,
-                            struct cfg80211_scan_request *req)
+                            struct ieee80211_scan_request *hw_req)
 {
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct wl1251 *wl = hw->priv;
        struct sk_buff *skb;
        size_t ssid_len = 0;
index 3d6028e62750279299431ce56315bdf67fa1f8cc..e5ffb8b91dd4a92dadd173bb97c5d36d47ad3116 100644 (file)
@@ -3540,8 +3540,9 @@ out:
 
 static int wl1271_op_hw_scan(struct ieee80211_hw *hw,
                             struct ieee80211_vif *vif,
-                            struct cfg80211_scan_request *req)
+                            struct ieee80211_scan_request *hw_req)
 {
+       struct cfg80211_scan_request *req = &hw_req->req;
        struct wl1271 *wl = hw->priv;
        int ret;
        u8 *ssid = NULL;
index 18c2bdbaf9882a5180961a61892860e59321385e..9536ee3e22a936481fa9a92b47234dc8142afd5d 100644 (file)
@@ -768,6 +768,26 @@ struct ieee80211_sched_scan_ies {
        size_t len[IEEE80211_NUM_BANDS];
 };
 
+/**
+ * struct ieee80211_scan_ies - descriptors for different blocks of IEs
+ *
+ * This structure is used to point to different blocks of IEs in HW scan.
+ * These blocks contain the IEs passed by userspace and the ones generated
+ * by mac80211.
+ *
+ * @ies: pointers to band specific IEs.
+ * @len: lengths of band_specific IEs.
+ * @common_ies: IEs for all bands (especially vendor specific ones)
+ * @common_ie_len: length of the common_ies
+ */
+struct ieee80211_scan_ies {
+       const u8 *ies[IEEE80211_NUM_BANDS];
+       size_t len[IEEE80211_NUM_BANDS];
+       const u8 *common_ies;
+       size_t common_ie_len;
+};
+
+
 static inline struct ieee80211_tx_info *IEEE80211_SKB_CB(struct sk_buff *skb)
 {
        return (struct ieee80211_tx_info *)skb->cb;
@@ -1606,6 +1626,9 @@ struct ieee80211_tx_control {
  *     on single-channel hardware.  It can also be used as an
  *     optimization in certain channel switch cases with
  *     multi-channel.
+ *
+ * @IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS: The HW supports scanning on all bands
+ *     in one command, mac80211 doesn't have to run separate scans per band.
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_HAS_RATE_CONTROL                   = 1<<0,
@@ -1638,6 +1661,7 @@ enum ieee80211_hw_flags {
        IEEE80211_HW_SUPPORTS_HT_CCK_RATES              = 1<<27,
        IEEE80211_HW_CHANCTX_STA_CSA                    = 1<<28,
        IEEE80211_HW_CHANGE_RUNNING_CHANCTX             = 1<<29,
+       IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS           = 1<<30,
 };
 
 /**
@@ -1763,6 +1787,19 @@ struct ieee80211_hw {
        const struct ieee80211_cipher_scheme *cipher_schemes;
 };
 
+/**
+ * struct ieee80211_scan_request - hw scan request
+ *
+ * @ies: pointers different parts of IEs (in req.ie)
+ * @req: cfg80211 request.
+ */
+struct ieee80211_scan_request {
+       struct ieee80211_scan_ies ies;
+
+       /* Keep last */
+       struct cfg80211_scan_request req;
+};
+
 /**
  * wiphy_to_ieee80211_hw - return a mac80211 driver hw struct from a wiphy
  *
@@ -2874,7 +2911,7 @@ struct ieee80211_ops {
        void (*set_default_unicast_key)(struct ieee80211_hw *hw,
                                        struct ieee80211_vif *vif, int idx);
        int (*hw_scan)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
-                      struct cfg80211_scan_request *req);
+                      struct ieee80211_scan_request *req);
        void (*cancel_hw_scan)(struct ieee80211_hw *hw,
                               struct ieee80211_vif *vif);
        int (*sched_scan_start)(struct ieee80211_hw *hw,
index 2265bd7a44baed60084ad685b7ca7894c51c4fb1..faa0d90f6e80e927fbd9827682aa8c5e0c51e7f4 100644 (file)
@@ -314,7 +314,7 @@ static inline void drv_update_tkip_key(struct ieee80211_local *local,
 
 static inline int drv_hw_scan(struct ieee80211_local *local,
                              struct ieee80211_sub_if_data *sdata,
-                             struct cfg80211_scan_request *req)
+                             struct ieee80211_scan_request *req)
 {
        int ret;
 
index 6c80894298920477754220125119731d50efb93a..f88bd1659cde589e2c38646c000793df888b1e6a 100644 (file)
@@ -1152,7 +1152,8 @@ struct ieee80211_local {
        unsigned long scanning;
        struct cfg80211_ssid scan_ssid;
        struct cfg80211_scan_request *int_scan_req;
-       struct cfg80211_scan_request *scan_req, *hw_scan_req;
+       struct cfg80211_scan_request *scan_req;
+       struct ieee80211_scan_request *hw_scan_req;
        struct cfg80211_chan_def scan_chandef;
        enum ieee80211_band hw_scan_band;
        int scan_channel_idx;
@@ -1756,8 +1757,10 @@ void ieee80211_send_deauth_disassoc(struct ieee80211_sub_if_data *sdata,
                                    const u8 *bssid, u16 stype, u16 reason,
                                    bool send_frame, u8 *frame_buf);
 int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
-                            size_t buffer_len, const u8 *ie, size_t ie_len,
-                            enum ieee80211_band band, u32 rate_mask,
+                            size_t buffer_len,
+                            struct ieee80211_scan_ies *ie_desc,
+                            const u8 *ie, size_t ie_len,
+                            u8 bands_used, u32 *rate_masks,
                             struct cfg80211_chan_def *chandef);
 struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
                                          u8 *dst, u32 ratemask,
index f40661eb75b578dd2e409757331c085d36461723..116959e070d0104b61ebb642508e90155eede5fe 100644 (file)
@@ -235,38 +235,51 @@ static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
 {
        struct cfg80211_scan_request *req = local->scan_req;
        struct cfg80211_chan_def chandef;
-       enum ieee80211_band band;
+       u8 bands_used = 0;
        int i, ielen, n_chans;
 
        if (test_bit(SCAN_HW_CANCELLED, &local->scanning))
                return false;
 
-       do {
-               if (local->hw_scan_band == IEEE80211_NUM_BANDS)
-                       return false;
-
-               band = local->hw_scan_band;
-               n_chans = 0;
+       if (local->hw.flags & IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS) {
                for (i = 0; i < req->n_channels; i++) {
-                       if (req->channels[i]->band == band) {
-                               local->hw_scan_req->channels[n_chans] =
+                       local->hw_scan_req->req.channels[i] = req->channels[i];
+                       bands_used |= BIT(req->channels[i]->band);
+               }
+
+               n_chans = req->n_channels;
+       } else {
+               do {
+                       if (local->hw_scan_band == IEEE80211_NUM_BANDS)
+                               return false;
+
+                       n_chans = 0;
+
+                       for (i = 0; i < req->n_channels; i++) {
+                               if (req->channels[i]->band !=
+                                   local->hw_scan_band)
+                                       continue;
+                               local->hw_scan_req->req.channels[n_chans] =
                                                        req->channels[i];
                                n_chans++;
+                               bands_used |= BIT(req->channels[i]->band);
                        }
-               }
 
-               local->hw_scan_band++;
-       } while (!n_chans);
+                       local->hw_scan_band++;
+               } while (!n_chans);
+       }
 
-       local->hw_scan_req->n_channels = n_chans;
+       local->hw_scan_req->req.n_channels = n_chans;
        ieee80211_prepare_scan_chandef(&chandef, req->scan_width);
 
-       ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie,
+       ielen = ieee80211_build_preq_ies(local,
+                                        (u8 *)local->hw_scan_req->req.ie,
                                         local->hw_scan_ies_bufsize,
-                                        req->ie, req->ie_len, band,
-                                        req->rates[band], &chandef);
-       local->hw_scan_req->ie_len = ielen;
-       local->hw_scan_req->no_cck = req->no_cck;
+                                        &local->hw_scan_req->ies,
+                                        req->ie, req->ie_len,
+                                        bands_used, req->rates, &chandef);
+       local->hw_scan_req->req.ie_len = ielen;
+       local->hw_scan_req->req.no_cck = req->no_cck;
 
        return true;
 }
@@ -291,7 +304,9 @@ static void __ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
        if (WARN_ON(!local->scan_req))
                return;
 
-       if (hw_scan && !aborted && ieee80211_prep_hw_scan(local)) {
+       if (hw_scan && !aborted &&
+           !(local->hw.flags & IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS) &&
+           ieee80211_prep_hw_scan(local)) {
                int rc;
 
                rc = drv_hw_scan(local,
@@ -473,6 +488,21 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
                u8 *ies;
 
                local->hw_scan_ies_bufsize = local->scan_ies_len + req->ie_len;
+
+               if (local->hw.flags & IEEE80211_SINGLE_HW_SCAN_ON_ALL_BANDS) {
+                       int i, n_bands = 0;
+                       u8 bands_counted = 0;
+
+                       for (i = 0; i < req->n_channels; i++) {
+                               if (bands_counted & BIT(req->channels[i]->band))
+                                       continue;
+                               bands_counted |= BIT(req->channels[i]->band);
+                               n_bands++;
+                       }
+
+                       local->hw_scan_ies_bufsize *= n_bands;
+               }
+
                local->hw_scan_req = kmalloc(
                                sizeof(*local->hw_scan_req) +
                                req->n_channels * sizeof(req->channels[0]) +
@@ -480,13 +510,13 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
                if (!local->hw_scan_req)
                        return -ENOMEM;
 
-               local->hw_scan_req->ssids = req->ssids;
-               local->hw_scan_req->n_ssids = req->n_ssids;
+               local->hw_scan_req->req.ssids = req->ssids;
+               local->hw_scan_req->req.n_ssids = req->n_ssids;
                ies = (u8 *)local->hw_scan_req +
                        sizeof(*local->hw_scan_req) +
                        req->n_channels * sizeof(req->channels[0]);
-               local->hw_scan_req->ie = ies;
-               local->hw_scan_req->flags = req->flags;
+               local->hw_scan_req->req.ie = ies;
+               local->hw_scan_req->req.flags = req->flags;
 
                local->hw_scan_band = 0;
 
@@ -976,6 +1006,7 @@ int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_sched_scan_ies sched_scan_ies = {};
        struct cfg80211_chan_def chandef;
        int ret, i, iebufsz;
+       struct ieee80211_scan_ies dummy_ie_desc;
 
        iebufsz = local->scan_ies_len + req->ie_len;
 
@@ -985,6 +1016,8 @@ int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
                return -ENOTSUPP;
 
        for (i = 0; i < IEEE80211_NUM_BANDS; i++) {
+               u32 rate_masks[IEEE80211_NUM_BANDS] = {};
+
                if (!local->hw.wiphy->bands[i])
                        continue;
 
@@ -995,11 +1028,13 @@ int __ieee80211_request_sched_scan_start(struct ieee80211_sub_if_data *sdata,
                }
 
                ieee80211_prepare_scan_chandef(&chandef, req->scan_width);
+               rate_masks[i] = (u32) -1;
 
                sched_scan_ies.len[i] =
                        ieee80211_build_preq_ies(local, sched_scan_ies.ie[i],
-                                                iebufsz, req->ie, req->ie_len,
-                                                i, (u32) -1, &chandef);
+                                                iebufsz, &dummy_ie_desc,
+                                                req->ie, req->ie_len, BIT(i),
+                                                rate_masks, &chandef);
        }
 
        ret = drv_sched_scan_start(local, sdata, req, &sched_scan_ies);
index 42d448d765b48915d0a3c9766843737696ef751e..e314582012785d6c428b8d6b5928b49e769e3bd6 100644 (file)
@@ -1219,14 +1219,17 @@ void ieee80211_send_deauth_disassoc(struct ieee80211_sub_if_data *sdata,
        }
 }
 
-int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
-                            size_t buffer_len, const u8 *ie, size_t ie_len,
-                            enum ieee80211_band band, u32 rate_mask,
-                            struct cfg80211_chan_def *chandef)
+static int ieee80211_build_preq_ies_band(struct ieee80211_local *local,
+                                        u8 *buffer, size_t buffer_len,
+                                        const u8 *ie, size_t ie_len,
+                                        enum ieee80211_band band,
+                                        u32 rate_mask,
+                                        struct cfg80211_chan_def *chandef,
+                                        size_t *offset)
 {
        struct ieee80211_supported_band *sband;
        u8 *pos = buffer, *end = buffer + buffer_len;
-       size_t offset = 0, noffset;
+       size_t noffset;
        int supp_rates_len, i;
        u8 rates[32];
        int num_rates;
@@ -1234,6 +1237,8 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
        int shift;
        u32 rate_flags;
 
+       *offset = 0;
+
        sband = local->hw.wiphy->bands[band];
        if (WARN_ON_ONCE(!sband))
                return 0;
@@ -1272,12 +1277,12 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
                noffset = ieee80211_ie_split(ie, ie_len,
                                             before_extrates,
                                             ARRAY_SIZE(before_extrates),
-                                            offset);
-               if (end - pos < noffset - offset)
+                                            *offset);
+               if (end - pos < noffset - *offset)
                        goto out_err;
-               memcpy(pos, ie + offset, noffset - offset);
-               pos += noffset - offset;
-               offset = noffset;
+               memcpy(pos, ie + *offset, noffset - *offset);
+               pos += noffset - *offset;
+               *offset = noffset;
        }
 
        ext_rates_len = num_rates - supp_rates_len;
@@ -1311,12 +1316,12 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
                };
                noffset = ieee80211_ie_split(ie, ie_len,
                                             before_ht, ARRAY_SIZE(before_ht),
-                                            offset);
-               if (end - pos < noffset - offset)
+                                            *offset);
+               if (end - pos < noffset - *offset)
                        goto out_err;
-               memcpy(pos, ie + offset, noffset - offset);
-               pos += noffset - offset;
-               offset = noffset;
+               memcpy(pos, ie + *offset, noffset - *offset);
+               pos += noffset - *offset;
+               *offset = noffset;
        }
 
        if (sband->ht_cap.ht_supported) {
@@ -1351,12 +1356,12 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
                };
                noffset = ieee80211_ie_split(ie, ie_len,
                                             before_vht, ARRAY_SIZE(before_vht),
-                                            offset);
-               if (end - pos < noffset - offset)
+                                            *offset);
+               if (end - pos < noffset - *offset)
                        goto out_err;
-               memcpy(pos, ie + offset, noffset - offset);
-               pos += noffset - offset;
-               offset = noffset;
+               memcpy(pos, ie + *offset, noffset - *offset);
+               pos += noffset - *offset;
+               *offset = noffset;
        }
 
        if (sband->vht_cap.vht_supported) {
@@ -1366,21 +1371,54 @@ int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
                                                 sband->vht_cap.cap);
        }
 
-       /* add any remaining custom IEs */
-       if (ie && ie_len) {
-               noffset = ie_len;
-               if (end - pos < noffset - offset)
-                       goto out_err;
-               memcpy(pos, ie + offset, noffset - offset);
-               pos += noffset - offset;
-       }
-
        return pos - buffer;
  out_err:
        WARN_ONCE(1, "not enough space for preq IEs\n");
        return pos - buffer;
 }
 
+int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
+                            size_t buffer_len,
+                            struct ieee80211_scan_ies *ie_desc,
+                            const u8 *ie, size_t ie_len,
+                            u8 bands_used, u32 *rate_masks,
+                            struct cfg80211_chan_def *chandef)
+{
+       size_t pos = 0, old_pos = 0, custom_ie_offset = 0;
+       int i;
+
+       memset(ie_desc, 0, sizeof(*ie_desc));
+
+       for (i = 0; i < IEEE80211_NUM_BANDS; i++) {
+               if (bands_used & BIT(i)) {
+                       pos += ieee80211_build_preq_ies_band(local,
+                                                            buffer + pos,
+                                                            buffer_len - pos,
+                                                            ie, ie_len, i,
+                                                            rate_masks[i],
+                                                            chandef,
+                                                            &custom_ie_offset);
+                       ie_desc->ies[i] = buffer + old_pos;
+                       ie_desc->len[i] = pos - old_pos;
+                       old_pos = pos;
+               }
+       }
+
+       /* add any remaining custom IEs */
+       if (ie && ie_len) {
+               if (WARN_ONCE(buffer_len - pos < ie_len - custom_ie_offset,
+                             "not enough space for preq custom IEs\n"))
+                       return pos;
+               memcpy(buffer + pos, ie + custom_ie_offset,
+                      ie_len - custom_ie_offset);
+               ie_desc->common_ies = buffer + pos;
+               ie_desc->common_ie_len = ie_len - custom_ie_offset;
+               pos += ie_len - custom_ie_offset;
+       }
+
+       return pos;
+};
+
 struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
                                          u8 *dst, u32 ratemask,
                                          struct ieee80211_channel *chan,
@@ -1393,6 +1431,8 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
        struct sk_buff *skb;
        struct ieee80211_mgmt *mgmt;
        int ies_len;
+       u32 rate_masks[IEEE80211_NUM_BANDS] = {};
+       struct ieee80211_scan_ies dummy_ie_desc;
 
        /*
         * Do not send DS Channel parameter for directed probe requests
@@ -1410,10 +1450,11 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
        if (!skb)
                return NULL;
 
+       rate_masks[chan->band] = ratemask;
        ies_len = ieee80211_build_preq_ies(local, skb_tail_pointer(skb),
-                                          skb_tailroom(skb),
-                                          ie, ie_len, chan->band,
-                                          ratemask, &chandef);
+                                          skb_tailroom(skb), &dummy_ie_desc,
+                                          ie, ie_len, BIT(chan->band),
+                                          rate_masks, &chandef);
        skb_put(skb, ies_len);
 
        if (dst) {