Kalle Valo says:
[linux-block.git] / net / mac80211 / util.c
index b512cb37aafb77134b039218cf13520a5dbaa017..6f5407038459d03cc3ceaa4ac29a66c3da570a57 100644 (file)
@@ -288,6 +288,52 @@ __le16 ieee80211_ctstoself_duration(struct ieee80211_hw *hw,
 }
 EXPORT_SYMBOL(ieee80211_ctstoself_duration);
 
+static void wake_tx_push_queue(struct ieee80211_local *local,
+                              struct ieee80211_sub_if_data *sdata,
+                              struct ieee80211_txq *queue)
+{
+       int q = sdata->vif.hw_queue[queue->ac];
+       struct ieee80211_tx_control control = {
+               .sta = queue->sta,
+       };
+       struct sk_buff *skb;
+       unsigned long flags;
+       bool q_stopped;
+
+       while (1) {
+               spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
+               q_stopped = local->queue_stop_reasons[q];
+               spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
+
+               if (q_stopped)
+                       break;
+
+               skb = ieee80211_tx_dequeue(&local->hw, queue);
+               if (!skb)
+                       break;
+
+               drv_tx(local, &control, skb);
+       }
+}
+
+/* wake_tx_queue handler for driver not implementing a custom one*/
+void ieee80211_handle_wake_tx_queue(struct ieee80211_hw *hw,
+                                   struct ieee80211_txq *txq)
+{
+       struct ieee80211_local *local = hw_to_local(hw);
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->vif);
+       struct ieee80211_txq *queue;
+
+       /* Use ieee80211_next_txq() for airtime fairness accounting */
+       ieee80211_txq_schedule_start(hw, txq->ac);
+       while ((queue = ieee80211_next_txq(hw, txq->ac))) {
+               wake_tx_push_queue(local, sdata, queue);
+               ieee80211_return_txq(hw, queue, false);
+       }
+       ieee80211_txq_schedule_end(hw, txq->ac);
+}
+EXPORT_SYMBOL(ieee80211_handle_wake_tx_queue);
+
 static void __ieee80211_wake_txqs(struct ieee80211_sub_if_data *sdata, int ac)
 {
        struct ieee80211_local *local = sdata->local;
@@ -400,39 +446,6 @@ void ieee80211_wake_txqs(struct tasklet_struct *t)
        spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
 }
 
-void ieee80211_propagate_queue_wake(struct ieee80211_local *local, int queue)
-{
-       struct ieee80211_sub_if_data *sdata;
-       int n_acs = IEEE80211_NUM_ACS;
-
-       if (local->ops->wake_tx_queue)
-               return;
-
-       if (local->hw.queues < IEEE80211_NUM_ACS)
-               n_acs = 1;
-
-       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
-               int ac;
-
-               if (!sdata->dev)
-                       continue;
-
-               if (sdata->vif.cab_queue != IEEE80211_INVAL_HW_QUEUE &&
-                   local->queue_stop_reasons[sdata->vif.cab_queue] != 0)
-                       continue;
-
-               for (ac = 0; ac < n_acs; ac++) {
-                       int ac_queue = sdata->vif.hw_queue[ac];
-
-                       if (ac_queue == queue ||
-                           (sdata->vif.cab_queue == queue &&
-                            local->queue_stop_reasons[ac_queue] == 0 &&
-                            skb_queue_empty(&local->pending[ac_queue])))
-                               netif_wake_subqueue(sdata->dev, ac);
-               }
-       }
-}
-
 static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
                                   enum queue_stop_reason reason,
                                   bool refcounted,
@@ -463,11 +476,7 @@ static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
                /* someone still has this queue stopped */
                return;
 
-       if (skb_queue_empty(&local->pending[queue])) {
-               rcu_read_lock();
-               ieee80211_propagate_queue_wake(local, queue);
-               rcu_read_unlock();
-       } else
+       if (!skb_queue_empty(&local->pending[queue]))
                tasklet_schedule(&local->tx_pending_tasklet);
 
        /*
@@ -477,12 +486,10 @@ static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
         * release someone's lock, but it is fine because all the callers of
         * __ieee80211_wake_queue call it right before releasing the lock.
         */
-       if (local->ops->wake_tx_queue) {
-               if (reason == IEEE80211_QUEUE_STOP_REASON_DRIVER)
-                       tasklet_schedule(&local->wake_txqs_tasklet);
-               else
-                       _ieee80211_wake_txqs(local, flags);
-       }
+       if (reason == IEEE80211_QUEUE_STOP_REASON_DRIVER)
+               tasklet_schedule(&local->wake_txqs_tasklet);
+       else
+               _ieee80211_wake_txqs(local, flags);
 }
 
 void ieee80211_wake_queue_by_reason(struct ieee80211_hw *hw, int queue,
@@ -539,10 +546,6 @@ static void __ieee80211_stop_queue(struct ieee80211_hw *hw, int queue,
                for (ac = 0; ac < n_acs; ac++) {
                        if (sdata->vif.hw_queue[ac] == queue ||
                            sdata->vif.cab_queue == queue) {
-                               if (!local->ops->wake_tx_queue) {
-                                       netif_stop_subqueue(sdata->dev, ac);
-                                       continue;
-                               }
                                spin_lock(&local->fq.lock);
                                sdata->vif.txqs_stopped[ac] = true;
                                spin_unlock(&local->fq.lock);
@@ -1026,8 +1029,10 @@ ieee80211_parse_extension_element(u32 *crc,
                        elems->eht_operation = data;
                break;
        case WLAN_EID_EXT_EHT_MULTI_LINK:
-               if (ieee80211_mle_size_ok(data, len))
+               if (ieee80211_mle_size_ok(data, len)) {
                        elems->multi_link = (void *)data;
+                       elems->multi_link_len = len;
+               }
                break;
        }
 }
@@ -1499,6 +1504,145 @@ static size_t ieee802_11_find_bssid_profile(const u8 *start, size_t len,
        return found ? profile_len : 0;
 }
 
+static void ieee80211_defragment_element(struct ieee802_11_elems *elems,
+                                        void **elem_ptr, size_t *len,
+                                        size_t total_len, u8 frag_id)
+{
+       u8 *data = *elem_ptr, *pos, *start;
+       const struct element *elem;
+
+       /*
+        * Since 'data' points to the data of the element, not the element
+        * itself, allow 254 in case it was an extended element where the
+        * extended ID isn't part of the data we see here and thus not part of
+        * 'len' either.
+        */
+       if (!data || (*len != 254 && *len != 255))
+               return;
+
+       start = elems->scratch_pos;
+
+       if (WARN_ON(*len > (elems->scratch + elems->scratch_len -
+                           elems->scratch_pos)))
+               return;
+
+       memcpy(elems->scratch_pos, data, *len);
+       elems->scratch_pos += *len;
+
+       pos = data + *len;
+       total_len -= *len;
+       for_each_element(elem, pos, total_len) {
+               if (elem->id != frag_id)
+                       break;
+
+               if (WARN_ON(elem->datalen >
+                           (elems->scratch + elems->scratch_len -
+                            elems->scratch_pos)))
+                       return;
+
+               memcpy(elems->scratch_pos, elem->data, elem->datalen);
+               elems->scratch_pos += elem->datalen;
+
+               *len += elem->datalen;
+       }
+
+       *elem_ptr = start;
+}
+
+static void ieee80211_mle_get_sta_prof(struct ieee802_11_elems *elems,
+                                      u8 link_id)
+{
+       const struct ieee80211_multi_link_elem *ml = elems->multi_link;
+       size_t ml_len = elems->multi_link_len;
+       const struct element *sub;
+
+       if (!ml || !ml_len)
+               return;
+
+       if (le16_get_bits(ml->control, IEEE80211_ML_CONTROL_TYPE) !=
+           IEEE80211_ML_CONTROL_TYPE_BASIC)
+               return;
+
+       for_each_mle_subelement(sub, (u8 *)ml, ml_len) {
+               struct ieee80211_mle_per_sta_profile *prof = (void *)sub->data;
+               u16 control;
+
+               if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE)
+                       continue;
+
+               if (!ieee80211_mle_sta_prof_size_ok(sub->data, sub->datalen))
+                       return;
+
+               control = le16_to_cpu(prof->control);
+
+               if (link_id != u16_get_bits(control,
+                                           IEEE80211_MLE_STA_CONTROL_LINK_ID))
+                       continue;
+
+               if (!(control & IEEE80211_MLE_STA_CONTROL_COMPLETE_PROFILE))
+                       return;
+
+               elems->prof = prof;
+               elems->sta_prof_len = sub->datalen;
+
+               /* the sub element can be fragmented */
+               ieee80211_defragment_element(elems, (void **)&elems->prof,
+                                            &elems->sta_prof_len,
+                                            ml_len - (sub->data - (u8 *)ml),
+                                            IEEE80211_MLE_SUBELEM_FRAGMENT);
+               return;
+       }
+}
+
+static void ieee80211_mle_parse_link(struct ieee802_11_elems *elems,
+                                    struct ieee80211_elems_parse_params *params)
+{
+       struct ieee80211_mle_per_sta_profile *prof;
+       struct ieee80211_elems_parse_params sub = {
+               .action = params->action,
+               .from_ap = params->from_ap,
+               .link_id = -1,
+       };
+       const struct element *non_inherit = NULL;
+       const u8 *end;
+
+       if (params->link_id == -1)
+               return;
+
+       ieee80211_defragment_element(elems, (void **)&elems->multi_link,
+                                    &elems->multi_link_len,
+                                    elems->total_len - ((u8 *)elems->multi_link -
+                                                        elems->ie_start),
+                                    WLAN_EID_FRAGMENT);
+
+       ieee80211_mle_get_sta_prof(elems, params->link_id);
+       prof = elems->prof;
+
+       if (!prof)
+               return;
+
+       /* check if we have the 4 bytes for the fixed part in assoc response */
+       if (elems->sta_prof_len < sizeof(*prof) + prof->sta_info_len - 1 + 4) {
+               elems->prof = NULL;
+               elems->sta_prof_len = 0;
+               return;
+       }
+
+       /*
+        * Skip the capability information and the status code that are expected
+        * as part of the station profile in association response frames. Note
+        * the -1 is because the 'sta_info_len' is accounted to as part of the
+        * per-STA profile, but not part of the 'u8 variable[]' portion.
+        */
+       sub.start = prof->variable + prof->sta_info_len - 1 + 4;
+       end = (const u8 *)prof + elems->sta_prof_len;
+       sub.len = end - sub.start;
+
+       non_inherit = cfg80211_find_ext_elem(WLAN_EID_EXT_NON_INHERITANCE,
+                                            sub.start, sub.len);
+       _ieee802_11_parse_elems_full(&sub, elems, non_inherit);
+}
+
 struct ieee802_11_elems *
 ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
 {
@@ -1506,7 +1650,7 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
        const struct element *non_inherit = NULL;
        u8 *nontransmitted_profile;
        int nontransmitted_profile_len = 0;
-       size_t scratch_len = params->len;
+       size_t scratch_len = params->scratch_len ?: 3 * params->len;
 
        elems = kzalloc(sizeof(*elems) + scratch_len, GFP_ATOMIC);
        if (!elems)
@@ -1541,6 +1685,8 @@ ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params)
                _ieee802_11_parse_elems_full(&sub, elems, NULL);
        }
 
+       ieee80211_mle_parse_link(elems, params);
+
        if (elems->tim && !elems->parse_error) {
                const struct ieee80211_tim_ie *tim_ie = elems->tim;