]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - net/mac80211/util.c
mac80211: add helper functions for tracking P2P NoA state
[mirror_ubuntu-artful-kernel.git] / net / mac80211 / util.c
index 69e4ef5348a0e916b76fa87de9d83402ec99a5e4..df00f1978a77574857a9e8cf5c37cad66ce3e487 100644 (file)
@@ -76,7 +76,7 @@ u8 *ieee80211_get_bssid(struct ieee80211_hdr *hdr, size_t len,
        }
 
        if (ieee80211_is_ctl(fc)) {
-               if(ieee80211_is_pspoll(fc))
+               if (ieee80211_is_pspoll(fc))
                        return hdr->addr1;
 
                if (ieee80211_is_back_req(fc)) {
@@ -300,9 +300,6 @@ void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue)
                if (!sdata->dev)
                        continue;
 
-               if (test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
-                       continue;
-
                if (sdata->vif.cab_queue != IEEE80211_INVAL_HW_QUEUE &&
                    local->queue_stop_reasons[sdata->vif.cab_queue] != 0)
                        continue;
@@ -567,18 +564,15 @@ void ieee80211_flush_queues(struct ieee80211_local *local,
                                        IEEE80211_QUEUE_STOP_REASON_FLUSH);
 }
 
-void ieee80211_iterate_active_interfaces(
-       struct ieee80211_hw *hw, u32 iter_flags,
-       void (*iterator)(void *data, u8 *mac,
-                        struct ieee80211_vif *vif),
-       void *data)
+static void __iterate_active_interfaces(struct ieee80211_local *local,
+                                       u32 iter_flags,
+                                       void (*iterator)(void *data, u8 *mac,
+                                               struct ieee80211_vif *vif),
+                                       void *data)
 {
-       struct ieee80211_local *local = hw_to_local(hw);
        struct ieee80211_sub_if_data *sdata;
 
-       mutex_lock(&local->iflist_mtx);
-
-       list_for_each_entry(sdata, &local->interfaces, list) {
+       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
                switch (sdata->vif.type) {
                case NL80211_IFTYPE_MONITOR:
                        if (!(sdata->u.mntr_flags & MONITOR_FLAG_ACTIVE))
@@ -597,13 +591,25 @@ void ieee80211_iterate_active_interfaces(
                                 &sdata->vif);
        }
 
-       sdata = rcu_dereference_protected(local->monitor_sdata,
-                                         lockdep_is_held(&local->iflist_mtx));
+       sdata = rcu_dereference_check(local->monitor_sdata,
+                                     lockdep_is_held(&local->iflist_mtx) ||
+                                     lockdep_rtnl_is_held());
        if (sdata &&
            (iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL ||
             sdata->flags & IEEE80211_SDATA_IN_DRIVER))
                iterator(data, sdata->vif.addr, &sdata->vif);
+}
+
+void ieee80211_iterate_active_interfaces(
+       struct ieee80211_hw *hw, u32 iter_flags,
+       void (*iterator)(void *data, u8 *mac,
+                        struct ieee80211_vif *vif),
+       void *data)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
 
+       mutex_lock(&local->iflist_mtx);
+       __iterate_active_interfaces(local, iter_flags, iterator, data);
        mutex_unlock(&local->iflist_mtx);
 }
 EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces);
@@ -615,38 +621,37 @@ void ieee80211_iterate_active_interfaces_atomic(
        void *data)
 {
        struct ieee80211_local *local = hw_to_local(hw);
-       struct ieee80211_sub_if_data *sdata;
 
        rcu_read_lock();
+       __iterate_active_interfaces(local, iter_flags, iterator, data);
+       rcu_read_unlock();
+}
+EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_atomic);
 
-       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
-               switch (sdata->vif.type) {
-               case NL80211_IFTYPE_MONITOR:
-                       if (!(sdata->u.mntr_flags & MONITOR_FLAG_ACTIVE))
-                               continue;
-                       break;
-               case NL80211_IFTYPE_AP_VLAN:
-                       continue;
-               default:
-                       break;
-               }
-               if (!(iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL) &&
-                   !(sdata->flags & IEEE80211_SDATA_IN_DRIVER))
-                       continue;
-               if (ieee80211_sdata_running(sdata))
-                       iterator(data, sdata->vif.addr,
-                                &sdata->vif);
-       }
+void ieee80211_iterate_active_interfaces_rtnl(
+       struct ieee80211_hw *hw, u32 iter_flags,
+       void (*iterator)(void *data, u8 *mac,
+                        struct ieee80211_vif *vif),
+       void *data)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
 
-       sdata = rcu_dereference(local->monitor_sdata);
-       if (sdata &&
-           (iter_flags & IEEE80211_IFACE_ITER_RESUME_ALL ||
-            sdata->flags & IEEE80211_SDATA_IN_DRIVER))
-               iterator(data, sdata->vif.addr, &sdata->vif);
+       ASSERT_RTNL();
 
-       rcu_read_unlock();
+       __iterate_active_interfaces(local, iter_flags, iterator, data);
 }
-EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_atomic);
+EXPORT_SYMBOL_GPL(ieee80211_iterate_active_interfaces_rtnl);
+
+struct ieee80211_vif *wdev_to_ieee80211_vif(struct wireless_dev *wdev)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+
+       if (!ieee80211_sdata_running(sdata) ||
+           !(sdata->flags & IEEE80211_SDATA_IN_DRIVER))
+               return NULL;
+       return &sdata->vif;
+}
+EXPORT_SYMBOL_GPL(wdev_to_ieee80211_vif);
 
 /*
  * Nothing should have been stuffed into the workqueue during
@@ -746,6 +751,7 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
                case WLAN_EID_TIMEOUT_INTERVAL:
                case WLAN_EID_SECONDARY_CHANNEL_OFFSET:
                case WLAN_EID_WIDE_BW_CHANNEL_SWITCH:
+               case WLAN_EID_CHAN_SWITCH_PARAM:
                /*
                 * not listing WLAN_EID_CHANNEL_SWITCH_WRAPPER -- it seems possible
                 * that if the content gets bigger it might be needed more than once
@@ -911,6 +917,14 @@ u32 ieee802_11_parse_elems_crc(const u8 *start, size_t len, bool action,
                        }
                        elems->sec_chan_offs = (void *)pos;
                        break;
+               case WLAN_EID_CHAN_SWITCH_PARAM:
+                       if (elen !=
+                           sizeof(*elems->mesh_chansw_params_ie)) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+                       elems->mesh_chansw_params_ie = (void *)pos;
+                       break;
                case WLAN_EID_WIDE_BW_CHANNEL_SWITCH:
                        if (!action ||
                            elen != sizeof(*elems->wide_bw_chansw_ie)) {
@@ -1007,14 +1021,21 @@ void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata,
         */
        enable_qos = (sdata->vif.type != NL80211_IFTYPE_STATION);
 
-       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
-               /* Set defaults according to 802.11-2007 Table 7-37 */
-               aCWmax = 1023;
-               if (use_11b)
-                       aCWmin = 31;
-               else
-                       aCWmin = 15;
+       /* Set defaults according to 802.11-2007 Table 7-37 */
+       aCWmax = 1023;
+       if (use_11b)
+               aCWmin = 31;
+       else
+               aCWmin = 15;
+
+       /* Confiure old 802.11b/g medium access rules. */
+       qparam.cw_max = aCWmax;
+       qparam.cw_min = aCWmin;
+       qparam.txop = 0;
+       qparam.aifs = 2;
 
+       for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
+               /* Update if QoS is enabled. */
                if (enable_qos) {
                        switch (ac) {
                        case IEEE80211_AC_BK:
@@ -1050,12 +1071,6 @@ void ieee80211_set_wmm_default(struct ieee80211_sub_if_data *sdata,
                                qparam.aifs = 2;
                                break;
                        }
-               } else {
-                       /* Confiure old 802.11b/g medium access rules. */
-                       qparam.cw_max = aCWmax;
-                       qparam.cw_min = aCWmin;
-                       qparam.txop = 0;
-                       qparam.aifs = 2;
                }
 
                qparam.uapsd = false;
@@ -1084,8 +1099,8 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_mgmt *mgmt;
        int err;
 
-       skb = dev_alloc_skb(local->hw.extra_tx_headroom +
-                           sizeof(*mgmt) + 6 + extra_len);
+       /* 24 + 6 = header + auth_algo + auth_transaction + status_code */
+       skb = dev_alloc_skb(local->hw.extra_tx_headroom + 24 + 6 + extra_len);
        if (!skb)
                return;
 
@@ -1447,6 +1462,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
        struct sta_info *sta;
        int res, i;
        bool reconfig_due_to_wowlan = false;
+       struct ieee80211_sub_if_data *sched_scan_sdata;
+       bool sched_scan_stopped = false;
 
 #ifdef CONFIG_PM
        if (local->suspended)
@@ -1750,6 +1767,27 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 #else
        WARN_ON(1);
 #endif
+
+       /*
+        * Reconfigure sched scan if it was interrupted by FW restart or
+        * suspend.
+        */
+       mutex_lock(&local->mtx);
+       sched_scan_sdata = rcu_dereference_protected(local->sched_scan_sdata,
+                                               lockdep_is_held(&local->mtx));
+       if (sched_scan_sdata && local->sched_scan_req)
+               /*
+                * Sched scan stopped, but we don't want to report it. Instead,
+                * we're trying to reschedule.
+                */
+               if (__ieee80211_request_sched_scan_start(sched_scan_sdata,
+                                                        local->sched_scan_req))
+                       sched_scan_stopped = true;
+       mutex_unlock(&local->mtx);
+
+       if (sched_scan_stopped)
+               cfg80211_sched_scan_stopped(local->hw.wiphy);
+
        return 0;
 }
 
@@ -1800,6 +1838,26 @@ void ieee80211_recalc_smps(struct ieee80211_sub_if_data *sdata)
        mutex_unlock(&local->chanctx_mtx);
 }
 
+void ieee80211_recalc_min_chandef(struct ieee80211_sub_if_data *sdata)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *chanctx_conf;
+       struct ieee80211_chanctx *chanctx;
+
+       mutex_lock(&local->chanctx_mtx);
+
+       chanctx_conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+                                       lockdep_is_held(&local->chanctx_mtx));
+
+       if (WARN_ON_ONCE(!chanctx_conf))
+               goto unlock;
+
+       chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+       ieee80211_recalc_chanctx_min_def(local, chanctx);
+ unlock:
+       mutex_unlock(&local->chanctx_mtx);
+}
+
 static bool ieee80211_id_in_list(const u8 *ids, int n_ids, u8 id)
 {
        int i;
@@ -2255,36 +2313,43 @@ u64 ieee80211_calculate_rx_timestamp(struct ieee80211_local *local,
 void ieee80211_dfs_cac_cancel(struct ieee80211_local *local)
 {
        struct ieee80211_sub_if_data *sdata;
+       struct cfg80211_chan_def chandef;
 
+       mutex_lock(&local->mtx);
        mutex_lock(&local->iflist_mtx);
        list_for_each_entry(sdata, &local->interfaces, list) {
-               cancel_delayed_work_sync(&sdata->dfs_cac_timer_work);
+               /* it might be waiting for the local->mtx, but then
+                * by the time it gets it, sdata->wdev.cac_started
+                * will no longer be true
+                */
+               cancel_delayed_work(&sdata->dfs_cac_timer_work);
 
                if (sdata->wdev.cac_started) {
+                       chandef = sdata->vif.bss_conf.chandef;
                        ieee80211_vif_release_channel(sdata);
                        cfg80211_cac_event(sdata->dev,
+                                          &chandef,
                                           NL80211_RADAR_CAC_ABORTED,
                                           GFP_KERNEL);
                }
        }
        mutex_unlock(&local->iflist_mtx);
+       mutex_unlock(&local->mtx);
 }
 
 void ieee80211_dfs_radar_detected_work(struct work_struct *work)
 {
        struct ieee80211_local *local =
                container_of(work, struct ieee80211_local, radar_detected_work);
-       struct cfg80211_chan_def chandef;
+       struct cfg80211_chan_def chandef = local->hw.conf.chandef;
 
        ieee80211_dfs_cac_cancel(local);
 
        if (local->use_chanctx)
                /* currently not handled */
                WARN_ON(1);
-       else {
-               chandef = local->hw.conf.chandef;
+       else
                cfg80211_radar_event(local->hw.wiphy, &chandef, GFP_KERNEL);
-       }
 }
 
 void ieee80211_radar_detected(struct ieee80211_hw *hw)
@@ -2296,3 +2361,376 @@ void ieee80211_radar_detected(struct ieee80211_hw *hw)
        ieee80211_queue_work(hw, &local->radar_detected_work);
 }
 EXPORT_SYMBOL(ieee80211_radar_detected);
+
+u32 ieee80211_chandef_downgrade(struct cfg80211_chan_def *c)
+{
+       u32 ret;
+       int tmp;
+
+       switch (c->width) {
+       case NL80211_CHAN_WIDTH_20:
+               c->width = NL80211_CHAN_WIDTH_20_NOHT;
+               ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
+               break;
+       case NL80211_CHAN_WIDTH_40:
+               c->width = NL80211_CHAN_WIDTH_20;
+               c->center_freq1 = c->chan->center_freq;
+               ret = IEEE80211_STA_DISABLE_40MHZ |
+                     IEEE80211_STA_DISABLE_VHT;
+               break;
+       case NL80211_CHAN_WIDTH_80:
+               tmp = (30 + c->chan->center_freq - c->center_freq1)/20;
+               /* n_P40 */
+               tmp /= 2;
+               /* freq_P40 */
+               c->center_freq1 = c->center_freq1 - 20 + 40 * tmp;
+               c->width = NL80211_CHAN_WIDTH_40;
+               ret = IEEE80211_STA_DISABLE_VHT;
+               break;
+       case NL80211_CHAN_WIDTH_80P80:
+               c->center_freq2 = 0;
+               c->width = NL80211_CHAN_WIDTH_80;
+               ret = IEEE80211_STA_DISABLE_80P80MHZ |
+                     IEEE80211_STA_DISABLE_160MHZ;
+               break;
+       case NL80211_CHAN_WIDTH_160:
+               /* n_P20 */
+               tmp = (70 + c->chan->center_freq - c->center_freq1)/20;
+               /* n_P80 */
+               tmp /= 4;
+               c->center_freq1 = c->center_freq1 - 40 + 80 * tmp;
+               c->width = NL80211_CHAN_WIDTH_80;
+               ret = IEEE80211_STA_DISABLE_80P80MHZ |
+                     IEEE80211_STA_DISABLE_160MHZ;
+               break;
+       default:
+       case NL80211_CHAN_WIDTH_20_NOHT:
+               WARN_ON_ONCE(1);
+               c->width = NL80211_CHAN_WIDTH_20_NOHT;
+               ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
+               break;
+       case NL80211_CHAN_WIDTH_5:
+       case NL80211_CHAN_WIDTH_10:
+               WARN_ON_ONCE(1);
+               /* keep c->width */
+               ret = IEEE80211_STA_DISABLE_HT | IEEE80211_STA_DISABLE_VHT;
+               break;
+       }
+
+       WARN_ON_ONCE(!cfg80211_chandef_valid(c));
+
+       return ret;
+}
+
+/*
+ * Returns true if smps_mode_new is strictly more restrictive than
+ * smps_mode_old.
+ */
+bool ieee80211_smps_is_restrictive(enum ieee80211_smps_mode smps_mode_old,
+                                  enum ieee80211_smps_mode smps_mode_new)
+{
+       if (WARN_ON_ONCE(smps_mode_old == IEEE80211_SMPS_AUTOMATIC ||
+                        smps_mode_new == IEEE80211_SMPS_AUTOMATIC))
+               return false;
+
+       switch (smps_mode_old) {
+       case IEEE80211_SMPS_STATIC:
+               return false;
+       case IEEE80211_SMPS_DYNAMIC:
+               return smps_mode_new == IEEE80211_SMPS_STATIC;
+       case IEEE80211_SMPS_OFF:
+               return smps_mode_new != IEEE80211_SMPS_OFF;
+       default:
+               WARN_ON(1);
+       }
+
+       return false;
+}
+
+int ieee80211_send_action_csa(struct ieee80211_sub_if_data *sdata,
+                             struct cfg80211_csa_settings *csa_settings)
+{
+       struct sk_buff *skb;
+       struct ieee80211_mgmt *mgmt;
+       struct ieee80211_local *local = sdata->local;
+       int freq;
+       int hdr_len = offsetof(struct ieee80211_mgmt, u.action.u.chan_switch) +
+                              sizeof(mgmt->u.action.u.chan_switch);
+       u8 *pos;
+
+       if (sdata->vif.type != NL80211_IFTYPE_ADHOC &&
+           sdata->vif.type != NL80211_IFTYPE_MESH_POINT)
+               return -EOPNOTSUPP;
+
+       skb = dev_alloc_skb(local->tx_headroom + hdr_len +
+                           5 + /* channel switch announcement element */
+                           3 + /* secondary channel offset element */
+                           8); /* mesh channel switch parameters element */
+       if (!skb)
+               return -ENOMEM;
+
+       skb_reserve(skb, local->tx_headroom);
+       mgmt = (struct ieee80211_mgmt *)skb_put(skb, hdr_len);
+       memset(mgmt, 0, hdr_len);
+       mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+                                         IEEE80211_STYPE_ACTION);
+
+       eth_broadcast_addr(mgmt->da);
+       memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN);
+       if (ieee80211_vif_is_mesh(&sdata->vif)) {
+               memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN);
+       } else {
+               struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
+               memcpy(mgmt->bssid, ifibss->bssid, ETH_ALEN);
+       }
+       mgmt->u.action.category = WLAN_CATEGORY_SPECTRUM_MGMT;
+       mgmt->u.action.u.chan_switch.action_code = WLAN_ACTION_SPCT_CHL_SWITCH;
+       pos = skb_put(skb, 5);
+       *pos++ = WLAN_EID_CHANNEL_SWITCH;                       /* EID */
+       *pos++ = 3;                                             /* IE length */
+       *pos++ = csa_settings->block_tx ? 1 : 0;                /* CSA mode */
+       freq = csa_settings->chandef.chan->center_freq;
+       *pos++ = ieee80211_frequency_to_channel(freq);          /* channel */
+       *pos++ = csa_settings->count;                           /* count */
+
+       if (csa_settings->chandef.width == NL80211_CHAN_WIDTH_40) {
+               enum nl80211_channel_type ch_type;
+
+               skb_put(skb, 3);
+               *pos++ = WLAN_EID_SECONDARY_CHANNEL_OFFSET;     /* EID */
+               *pos++ = 1;                                     /* IE length */
+               ch_type = cfg80211_get_chandef_type(&csa_settings->chandef);
+               if (ch_type == NL80211_CHAN_HT40PLUS)
+                       *pos++ = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+               else
+                       *pos++ = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+       }
+
+       if (ieee80211_vif_is_mesh(&sdata->vif)) {
+               struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
+
+               skb_put(skb, 8);
+               *pos++ = WLAN_EID_CHAN_SWITCH_PARAM;            /* EID */
+               *pos++ = 6;                                     /* IE length */
+               *pos++ = sdata->u.mesh.mshcfg.dot11MeshTTL;     /* Mesh TTL */
+               *pos = 0x00;    /* Mesh Flag: Tx Restrict, Initiator, Reason */
+               *pos |= WLAN_EID_CHAN_SWITCH_PARAM_INITIATOR;
+               *pos++ |= csa_settings->block_tx ?
+                         WLAN_EID_CHAN_SWITCH_PARAM_TX_RESTRICT : 0x00;
+               put_unaligned_le16(WLAN_REASON_MESH_CHAN, pos); /* Reason Cd */
+               pos += 2;
+               put_unaligned_le16(ifmsh->pre_value, pos);/* Precedence Value */
+               pos += 2;
+       }
+
+       ieee80211_tx_skb(sdata, skb);
+       return 0;
+}
+
+bool ieee80211_cs_valid(const struct ieee80211_cipher_scheme *cs)
+{
+       return !(cs == NULL || cs->cipher == 0 ||
+                cs->hdr_len < cs->pn_len + cs->pn_off ||
+                cs->hdr_len <= cs->key_idx_off ||
+                cs->key_idx_shift > 7 ||
+                cs->key_idx_mask == 0);
+}
+
+bool ieee80211_cs_list_valid(const struct ieee80211_cipher_scheme *cs, int n)
+{
+       int i;
+
+       /* Ensure we have enough iftype bitmap space for all iftype values */
+       WARN_ON((NUM_NL80211_IFTYPES / 8 + 1) > sizeof(cs[0].iftype));
+
+       for (i = 0; i < n; i++)
+               if (!ieee80211_cs_valid(&cs[i]))
+                       return false;
+
+       return true;
+}
+
+const struct ieee80211_cipher_scheme *
+ieee80211_cs_get(struct ieee80211_local *local, u32 cipher,
+                enum nl80211_iftype iftype)
+{
+       const struct ieee80211_cipher_scheme *l = local->hw.cipher_schemes;
+       int n = local->hw.n_cipher_schemes;
+       int i;
+       const struct ieee80211_cipher_scheme *cs = NULL;
+
+       for (i = 0; i < n; i++) {
+               if (l[i].cipher == cipher) {
+                       cs = &l[i];
+                       break;
+               }
+       }
+
+       if (!cs || !(cs->iftype & BIT(iftype)))
+               return NULL;
+
+       return cs;
+}
+
+int ieee80211_cs_headroom(struct ieee80211_local *local,
+                         struct cfg80211_crypto_settings *crypto,
+                         enum nl80211_iftype iftype)
+{
+       const struct ieee80211_cipher_scheme *cs;
+       int headroom = IEEE80211_ENCRYPT_HEADROOM;
+       int i;
+
+       for (i = 0; i < crypto->n_ciphers_pairwise; i++) {
+               cs = ieee80211_cs_get(local, crypto->ciphers_pairwise[i],
+                                     iftype);
+
+               if (cs && headroom < cs->hdr_len)
+                       headroom = cs->hdr_len;
+       }
+
+       cs = ieee80211_cs_get(local, crypto->cipher_group, iftype);
+       if (cs && headroom < cs->hdr_len)
+               headroom = cs->hdr_len;
+
+       return headroom;
+}
+
+static bool
+ieee80211_extend_noa_desc(struct ieee80211_noa_data *data, u32 tsf, int i)
+{
+       s32 end = data->desc[i].start + data->desc[i].duration - (tsf + 1);
+       int skip;
+
+       if (end > 0)
+               return false;
+
+       /* End time is in the past, check for repetitions */
+       skip = DIV_ROUND_UP(-end, data->desc[i].interval);
+       if (data->count[i] < 255) {
+               if (data->count[i] <= skip) {
+                       data->count[i] = 0;
+                       return false;
+               }
+
+               data->count[i] -= skip;
+       }
+
+       data->desc[i].start += skip * data->desc[i].interval;
+
+       return true;
+}
+
+static bool
+ieee80211_extend_absent_time(struct ieee80211_noa_data *data, u32 tsf,
+                            s32 *offset)
+{
+       bool ret = false;
+       int i;
+
+       for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) {
+               s32 cur;
+
+               if (!data->count[i])
+                       continue;
+
+               if (ieee80211_extend_noa_desc(data, tsf + *offset, i))
+                       ret = true;
+
+               cur = data->desc[i].start - tsf;
+               if (cur > *offset)
+                       continue;
+
+               cur = data->desc[i].start + data->desc[i].duration - tsf;
+               if (cur > *offset)
+                       *offset = cur;
+       }
+
+       return ret;
+}
+
+static u32
+ieee80211_get_noa_absent_time(struct ieee80211_noa_data *data, u32 tsf)
+{
+       s32 offset = 0;
+       int tries = 0;
+       /*
+        * arbitrary limit, used to avoid infinite loops when combined NoA
+        * descriptors cover the full time period.
+        */
+       int max_tries = 5;
+
+       ieee80211_extend_absent_time(data, tsf, &offset);
+       do {
+               if (!ieee80211_extend_absent_time(data, tsf, &offset))
+                       break;
+
+               tries++;
+       } while (tries < max_tries);
+
+       return offset;
+}
+
+void ieee80211_update_p2p_noa(struct ieee80211_noa_data *data, u32 tsf)
+{
+       u32 next_offset = BIT(31) - 1;
+       int i;
+
+       data->absent = 0;
+       data->has_next_tsf = false;
+       for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) {
+               s32 start;
+
+               if (!data->count[i])
+                       continue;
+
+               ieee80211_extend_noa_desc(data, tsf, i);
+               start = data->desc[i].start - tsf;
+               if (start <= 0)
+                       data->absent |= BIT(i);
+
+               if (next_offset > start)
+                       next_offset = start;
+
+               data->has_next_tsf = true;
+       }
+
+       if (data->absent)
+               next_offset = ieee80211_get_noa_absent_time(data, tsf);
+
+       data->next_tsf = tsf + next_offset;
+}
+EXPORT_SYMBOL(ieee80211_update_p2p_noa);
+
+int ieee80211_parse_p2p_noa(const struct ieee80211_p2p_noa_attr *attr,
+                           struct ieee80211_noa_data *data, u32 tsf)
+{
+       int ret = 0;
+       int i;
+
+       memset(data, 0, sizeof(*data));
+
+       for (i = 0; i < IEEE80211_P2P_NOA_DESC_MAX; i++) {
+               const struct ieee80211_p2p_noa_desc *desc = &attr->desc[i];
+
+               if (!desc->count || !desc->duration)
+                       continue;
+
+               data->count[i] = desc->count;
+               data->desc[i].start = le32_to_cpu(desc->start_time);
+               data->desc[i].duration = le32_to_cpu(desc->duration);
+               data->desc[i].interval = le32_to_cpu(desc->interval);
+
+               if (data->count[i] > 1 &&
+                   data->desc[i].interval < data->desc[i].duration)
+                       continue;
+
+               ieee80211_extend_noa_desc(data, tsf, i);
+               ret++;
+       }
+
+       if (ret)
+               ieee80211_update_p2p_noa(data, tsf);
+
+       return ret;
+}
+EXPORT_SYMBOL(ieee80211_parse_p2p_noa);