Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mac80211: add WMM admission control support

Use the currently existing APIs between mac80211 and the low
level driver to implement WMM admission control.

The low level driver needs to report the media time used by
each transmitted packet in ieee80211_tx_status. Based on that
information, mac80211 will modify the QoS parameters of the
admission controlled Access Category when the limit is
reached. Once the original QoS parameters can be restored,
mac80211 will do so.

One issue with this approach is that management frames will
also erroneously be downgraded, but the upside is that the
implementation is simple. In the future, it can be extended
to driver- or device-based implementations that are better.

Signed-off-by: Emmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>

+273 -24
+2 -1
include/net/mac80211.h
··· 739 739 u8 ampdu_ack_len; 740 740 u8 ampdu_len; 741 741 u8 antenna; 742 - void *status_driver_data[21 / sizeof(void *)]; 742 + u16 tx_time; 743 + void *status_driver_data[19 / sizeof(void *)]; 743 744 } status; 744 745 struct { 745 746 struct ieee80211_tx_rate driver_rates[
-5
net/mac80211/agg-tx.c
··· 149 149 rcu_assign_pointer(sta->ampdu_mlme.tid_tx[tid], tid_tx); 150 150 } 151 151 152 - static inline int ieee80211_ac_from_tid(int tid) 153 - { 154 - return ieee802_1d_to_ac[tid & 7]; 155 - } 156 - 157 152 /* 158 153 * When multiple aggregation sessions on multiple stations 159 154 * are being created/destroyed simultaneously, we need to
+73
net/mac80211/cfg.c
··· 20 20 #include "cfg.h" 21 21 #include "rate.h" 22 22 #include "mesh.h" 23 + #include "wme.h" 23 24 24 25 static struct wireless_dev *ieee80211_add_iface(struct wiphy *wiphy, 25 26 const char *name, ··· 3586 3585 return ret; 3587 3586 } 3588 3587 3588 + static int ieee80211_add_tx_ts(struct wiphy *wiphy, struct net_device *dev, 3589 + u8 tsid, const u8 *peer, u8 up, 3590 + u16 admitted_time) 3591 + { 3592 + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); 3593 + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 3594 + int ac = ieee802_1d_to_ac[up]; 3595 + 3596 + if (sdata->vif.type != NL80211_IFTYPE_STATION) 3597 + return -EOPNOTSUPP; 3598 + 3599 + if (!(sdata->wmm_acm & BIT(up))) 3600 + return -EINVAL; 3601 + 3602 + if (ifmgd->tx_tspec[ac].admitted_time) 3603 + return -EBUSY; 3604 + 3605 + if (admitted_time) { 3606 + ifmgd->tx_tspec[ac].admitted_time = 32 * admitted_time; 3607 + ifmgd->tx_tspec[ac].tsid = tsid; 3608 + ifmgd->tx_tspec[ac].up = up; 3609 + } 3610 + 3611 + return 0; 3612 + } 3613 + 3614 + static int ieee80211_del_tx_ts(struct wiphy *wiphy, struct net_device *dev, 3615 + u8 tsid, const u8 *peer) 3616 + { 3617 + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); 3618 + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 3619 + struct ieee80211_local *local = wiphy_priv(wiphy); 3620 + int ac; 3621 + 3622 + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { 3623 + struct ieee80211_sta_tx_tspec *tx_tspec = &ifmgd->tx_tspec[ac]; 3624 + 3625 + /* skip unused entries */ 3626 + if (!tx_tspec->admitted_time) 3627 + continue; 3628 + 3629 + if (tx_tspec->tsid != tsid) 3630 + continue; 3631 + 3632 + /* due to this new packets will be reassigned to non-ACM ACs */ 3633 + tx_tspec->up = -1; 3634 + 3635 + /* Make sure that all packets have been sent to avoid to 3636 + * restore the QoS params on packets that are still on the 3637 + * queues. 3638 + */ 3639 + synchronize_net(); 3640 + ieee80211_flush_queues(local, sdata); 3641 + 3642 + /* restore the normal QoS parameters 3643 + * (unconditionally to avoid races) 3644 + */ 3645 + tx_tspec->action = TX_TSPEC_ACTION_STOP_DOWNGRADE; 3646 + tx_tspec->downgraded = false; 3647 + ieee80211_sta_handle_tspec_ac_params(sdata); 3648 + 3649 + /* finally clear all the data */ 3650 + memset(tx_tspec, 0, sizeof(*tx_tspec)); 3651 + 3652 + return 0; 3653 + } 3654 + 3655 + return -ENOENT; 3656 + } 3657 + 3589 3658 const struct cfg80211_ops mac80211_config_ops = { 3590 3659 .add_virtual_intf = ieee80211_add_iface, 3591 3660 .del_virtual_intf = ieee80211_del_iface, ··· 3734 3663 .channel_switch = ieee80211_channel_switch, 3735 3664 .set_qos_map = ieee80211_set_qos_map, 3736 3665 .set_ap_chanwidth = ieee80211_set_ap_chanwidth, 3666 + .add_tx_ts = ieee80211_add_tx_ts, 3667 + .del_tx_ts = ieee80211_del_tx_ts, 3737 3668 };
+37 -1
net/mac80211/ieee80211_i.h
··· 399 399 u8 ie[]; 400 400 }; 401 401 402 + struct ieee80211_sta_tx_tspec { 403 + /* timestamp of the first packet in the time slice */ 404 + unsigned long time_slice_start; 405 + 406 + u32 admitted_time; /* in usecs, unlike over the air */ 407 + u8 tsid; 408 + s8 up; /* signed to be able to invalidate with -1 during teardown */ 409 + 410 + /* consumed TX time in microseconds in the time slice */ 411 + u32 consumed_tx_time; 412 + enum { 413 + TX_TSPEC_ACTION_NONE = 0, 414 + TX_TSPEC_ACTION_DOWNGRADE, 415 + TX_TSPEC_ACTION_STOP_DOWNGRADE, 416 + } action; 417 + bool downgraded; 418 + }; 419 + 402 420 struct ieee80211_if_managed { 403 421 struct timer_list timer; 404 422 struct timer_list conn_mon_timer; ··· 527 509 528 510 u8 tdls_peer[ETH_ALEN] __aligned(2); 529 511 struct delayed_work tdls_peer_del_work; 512 + 513 + /* WMM-AC TSPEC support */ 514 + struct ieee80211_sta_tx_tspec tx_tspec[IEEE80211_NUM_ACS]; 515 + /* Use a separate work struct so that we can do something here 516 + * while the sdata->work is flushing the queues, for example. 517 + * otherwise, in scenarios where we hardly get any traffic out 518 + * on the BE queue, but there's a lot of VO traffic, we might 519 + * get stuck in a downgraded situation and flush takes forever. 520 + */ 521 + struct delayed_work tx_tspec_wk; 530 522 }; 531 523 532 524 struct ieee80211_if_ibss { ··· 1487 1459 __le16 fc, bool acked); 1488 1460 void ieee80211_mgd_quiesce(struct ieee80211_sub_if_data *sdata); 1489 1461 void ieee80211_sta_restart(struct ieee80211_sub_if_data *sdata); 1462 + void ieee80211_sta_handle_tspec_ac_params(struct ieee80211_sub_if_data *sdata); 1490 1463 1491 1464 /* IBSS code */ 1492 1465 void ieee80211_ibss_notify_scan_completed(struct ieee80211_local *local); ··· 1792 1763 return true; 1793 1764 } 1794 1765 1766 + extern const int ieee802_1d_to_ac[8]; 1767 + 1768 + static inline int ieee80211_ac_from_tid(int tid) 1769 + { 1770 + return ieee802_1d_to_ac[tid & 7]; 1771 + } 1772 + 1795 1773 void ieee80211_dynamic_ps_enable_work(struct work_struct *work); 1796 1774 void ieee80211_dynamic_ps_disable_work(struct work_struct *work); 1797 1775 void ieee80211_dynamic_ps_timer(unsigned long data); ··· 1808 1772 void ieee80211_sta_rx_notify(struct ieee80211_sub_if_data *sdata, 1809 1773 struct ieee80211_hdr *hdr); 1810 1774 void ieee80211_sta_tx_notify(struct ieee80211_sub_if_data *sdata, 1811 - struct ieee80211_hdr *hdr, bool ack); 1775 + struct ieee80211_hdr *hdr, bool ack, u16 tx_time); 1812 1776 1813 1777 void ieee80211_wake_queues_by_reason(struct ieee80211_hw *hw, 1814 1778 unsigned long queues,
+139 -5
net/mac80211/mlme.c
··· 1606 1606 mutex_unlock(&sdata->local->mtx); 1607 1607 } 1608 1608 1609 + static bool 1610 + __ieee80211_sta_handle_tspec_ac_params(struct ieee80211_sub_if_data *sdata) 1611 + { 1612 + struct ieee80211_local *local = sdata->local; 1613 + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 1614 + bool ret; 1615 + int ac; 1616 + 1617 + if (local->hw.queues < IEEE80211_NUM_ACS) 1618 + return false; 1619 + 1620 + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { 1621 + struct ieee80211_sta_tx_tspec *tx_tspec = &ifmgd->tx_tspec[ac]; 1622 + int non_acm_ac; 1623 + unsigned long now = jiffies; 1624 + 1625 + if (tx_tspec->action == TX_TSPEC_ACTION_NONE && 1626 + tx_tspec->admitted_time && 1627 + time_after(now, tx_tspec->time_slice_start + HZ)) { 1628 + tx_tspec->consumed_tx_time = 0; 1629 + tx_tspec->time_slice_start = now; 1630 + 1631 + if (tx_tspec->downgraded) 1632 + tx_tspec->action = 1633 + TX_TSPEC_ACTION_STOP_DOWNGRADE; 1634 + } 1635 + 1636 + switch (tx_tspec->action) { 1637 + case TX_TSPEC_ACTION_STOP_DOWNGRADE: 1638 + /* take the original parameters */ 1639 + if (drv_conf_tx(local, sdata, ac, &sdata->tx_conf[ac])) 1640 + sdata_err(sdata, 1641 + "failed to set TX queue parameters for queue %d\n", 1642 + ac); 1643 + tx_tspec->action = TX_TSPEC_ACTION_NONE; 1644 + tx_tspec->downgraded = false; 1645 + ret = true; 1646 + break; 1647 + case TX_TSPEC_ACTION_DOWNGRADE: 1648 + if (time_after(now, tx_tspec->time_slice_start + HZ)) { 1649 + tx_tspec->action = TX_TSPEC_ACTION_NONE; 1650 + ret = true; 1651 + break; 1652 + } 1653 + /* downgrade next lower non-ACM AC */ 1654 + for (non_acm_ac = ac + 1; 1655 + non_acm_ac < IEEE80211_NUM_ACS; 1656 + non_acm_ac++) 1657 + if (!(sdata->wmm_acm & BIT(7 - 2 * non_acm_ac))) 1658 + break; 1659 + /* The loop will result in using BK even if it requires 1660 + * admission control, such configuration makes no sense 1661 + * and we have to transmit somehow - the AC selection 1662 + * does the same thing. 1663 + */ 1664 + if (drv_conf_tx(local, sdata, ac, 1665 + &sdata->tx_conf[non_acm_ac])) 1666 + sdata_err(sdata, 1667 + "failed to set TX queue parameters for queue %d\n", 1668 + ac); 1669 + tx_tspec->action = TX_TSPEC_ACTION_NONE; 1670 + ret = true; 1671 + schedule_delayed_work(&ifmgd->tx_tspec_wk, 1672 + tx_tspec->time_slice_start + HZ - now + 1); 1673 + break; 1674 + case TX_TSPEC_ACTION_NONE: 1675 + /* nothing now */ 1676 + break; 1677 + } 1678 + } 1679 + 1680 + return ret; 1681 + } 1682 + 1683 + void ieee80211_sta_handle_tspec_ac_params(struct ieee80211_sub_if_data *sdata) 1684 + { 1685 + if (__ieee80211_sta_handle_tspec_ac_params(sdata)) 1686 + ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_QOS); 1687 + } 1688 + 1689 + static void ieee80211_sta_handle_tspec_ac_params_wk(struct work_struct *work) 1690 + { 1691 + struct ieee80211_sub_if_data *sdata; 1692 + 1693 + sdata = container_of(work, struct ieee80211_sub_if_data, 1694 + u.mgd.tx_tspec_wk.work); 1695 + ieee80211_sta_handle_tspec_ac_params(sdata); 1696 + } 1697 + 1609 1698 /* MLME */ 1610 1699 static bool ieee80211_sta_wmm_params(struct ieee80211_local *local, 1611 1700 struct ieee80211_sub_if_data *sdata, ··· 1779 1690 params.uapsd = uapsd; 1780 1691 1781 1692 mlme_dbg(sdata, 1782 - "WMM queue=%d aci=%d acm=%d aifs=%d cWmin=%d cWmax=%d txop=%d uapsd=%d\n", 1693 + "WMM queue=%d aci=%d acm=%d aifs=%d cWmin=%d cWmax=%d txop=%d uapsd=%d, downgraded=%d\n", 1783 1694 queue, aci, acm, 1784 1695 params.aifs, params.cw_min, params.cw_max, 1785 - params.txop, params.uapsd); 1696 + params.txop, params.uapsd, 1697 + ifmgd->tx_tspec[queue].downgraded); 1786 1698 sdata->tx_conf[queue] = params; 1787 - if (drv_conf_tx(local, sdata, queue, &params)) 1699 + if (!ifmgd->tx_tspec[queue].downgraded && 1700 + drv_conf_tx(local, sdata, queue, &params)) 1788 1701 sdata_err(sdata, 1789 1702 "failed to set TX queue parameters for queue %d\n", 1790 1703 queue); ··· 2049 1958 } 2050 1959 mutex_unlock(&local->mtx); 2051 1960 1961 + /* existing TX TSPEC sessions no longer exist */ 1962 + memset(ifmgd->tx_tspec, 0, sizeof(ifmgd->tx_tspec)); 1963 + cancel_delayed_work_sync(&ifmgd->tx_tspec_wk); 1964 + 2052 1965 sdata->encrypt_headroom = IEEE80211_ENCRYPT_HEADROOM; 2053 1966 } 2054 1967 ··· 2105 2010 mutex_unlock(&local->mtx); 2106 2011 } 2107 2012 2108 - void ieee80211_sta_tx_notify(struct ieee80211_sub_if_data *sdata, 2109 - struct ieee80211_hdr *hdr, bool ack) 2013 + static void ieee80211_sta_tx_wmm_ac_notify(struct ieee80211_sub_if_data *sdata, 2014 + struct ieee80211_hdr *hdr, 2015 + u16 tx_time) 2110 2016 { 2017 + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 2018 + u16 tid = *ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_TID_MASK; 2019 + int ac = ieee80211_ac_from_tid(tid); 2020 + struct ieee80211_sta_tx_tspec *tx_tspec = &ifmgd->tx_tspec[ac]; 2021 + unsigned long now = jiffies; 2022 + 2023 + if (likely(!tx_tspec->admitted_time)) 2024 + return; 2025 + 2026 + if (time_after(now, tx_tspec->time_slice_start + HZ)) { 2027 + tx_tspec->consumed_tx_time = 0; 2028 + tx_tspec->time_slice_start = now; 2029 + 2030 + if (tx_tspec->downgraded) { 2031 + tx_tspec->action = TX_TSPEC_ACTION_STOP_DOWNGRADE; 2032 + schedule_delayed_work(&ifmgd->tx_tspec_wk, 0); 2033 + } 2034 + } 2035 + 2036 + if (tx_tspec->downgraded) 2037 + return; 2038 + 2039 + tx_tspec->consumed_tx_time += tx_time; 2040 + 2041 + if (tx_tspec->consumed_tx_time >= tx_tspec->admitted_time) { 2042 + tx_tspec->downgraded = true; 2043 + tx_tspec->action = TX_TSPEC_ACTION_DOWNGRADE; 2044 + schedule_delayed_work(&ifmgd->tx_tspec_wk, 0); 2045 + } 2046 + } 2047 + 2048 + void ieee80211_sta_tx_notify(struct ieee80211_sub_if_data *sdata, 2049 + struct ieee80211_hdr *hdr, bool ack, u16 tx_time) 2050 + { 2051 + ieee80211_sta_tx_wmm_ac_notify(sdata, hdr, tx_time); 2052 + 2111 2053 if (!ieee80211_is_data(hdr->frame_control)) 2112 2054 return; 2113 2055 ··· 3966 3834 (unsigned long) sdata); 3967 3835 setup_timer(&ifmgd->chswitch_timer, ieee80211_chswitch_timer, 3968 3836 (unsigned long) sdata); 3837 + INIT_DELAYED_WORK(&ifmgd->tx_tspec_wk, 3838 + ieee80211_sta_handle_tspec_ac_params_wk); 3969 3839 3970 3840 ifmgd->flags = 0; 3971 3841 ifmgd->powersave = sdata->wdev.ps;
+2 -1
net/mac80211/status.c
··· 704 704 705 705 if ((sta->sdata->vif.type == NL80211_IFTYPE_STATION) && 706 706 (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS)) 707 - ieee80211_sta_tx_notify(sta->sdata, (void *) skb->data, acked); 707 + ieee80211_sta_tx_notify(sta->sdata, (void *) skb->data, 708 + acked, info->status.tx_time); 708 709 709 710 if (local->hw.flags & IEEE80211_HW_REPORTS_TX_ACK_STATUS) { 710 711 if (info->flags & IEEE80211_TX_STAT_ACK) {
+20 -9
net/mac80211/wme.c
··· 54 54 } 55 55 56 56 static u16 ieee80211_downgrade_queue(struct ieee80211_sub_if_data *sdata, 57 - struct sk_buff *skb) 57 + struct sta_info *sta, struct sk_buff *skb) 58 58 { 59 + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 60 + 59 61 /* in case we are a client verify acm is not set for this ac */ 60 - while (unlikely(sdata->wmm_acm & BIT(skb->priority))) { 62 + while (sdata->wmm_acm & BIT(skb->priority)) { 63 + int ac = ieee802_1d_to_ac[skb->priority]; 64 + 65 + if (ifmgd->tx_tspec[ac].admitted_time && 66 + skb->priority == ifmgd->tx_tspec[ac].up) 67 + return ac; 68 + 61 69 if (wme_downgrade_ac(skb)) { 62 70 /* 63 71 * This should not really happen. The AP has marked all ··· 104 96 p = ieee80211_get_qos_ctl(hdr); 105 97 skb->priority = *p & IEEE80211_QOS_CTL_TAG1D_MASK; 106 98 107 - return ieee80211_downgrade_queue(sdata, skb); 99 + return ieee80211_downgrade_queue(sdata, NULL, skb); 108 100 } 109 101 110 102 /* Indicate which queue to use. */ ··· 116 108 const u8 *ra = NULL; 117 109 bool qos = false; 118 110 struct mac80211_qos_map *qos_map; 111 + u16 ret; 119 112 120 113 if (local->hw.queues < IEEE80211_NUM_ACS || skb->len < 6) { 121 114 skb->priority = 0; /* required for correct WPA/11i MIC */ ··· 157 148 if (sta) 158 149 qos = sta->sta.wme; 159 150 } 160 - rcu_read_unlock(); 161 151 162 152 if (!qos) { 163 153 skb->priority = 0; /* required for correct WPA/11i MIC */ 164 - return IEEE80211_AC_BE; 154 + ret = IEEE80211_AC_BE; 155 + goto out; 165 156 } 166 157 167 158 if (skb->protocol == sdata->control_port_protocol) { 168 159 skb->priority = 7; 169 - return ieee80211_downgrade_queue(sdata, skb); 160 + goto downgrade; 170 161 } 171 162 172 163 /* use the data classifier to determine what 802.1d tag the 173 164 * data frame has */ 174 - rcu_read_lock(); 175 165 qos_map = rcu_dereference(sdata->qos_map); 176 166 skb->priority = cfg80211_classify8021d(skb, qos_map ? 177 167 &qos_map->qos_map : NULL); 178 - rcu_read_unlock(); 179 168 180 - return ieee80211_downgrade_queue(sdata, skb); 169 + downgrade: 170 + ret = ieee80211_downgrade_queue(sdata, sta, skb); 171 + out: 172 + rcu_read_unlock(); 173 + return ret; 181 174 } 182 175 183 176 /**
-2
net/mac80211/wme.h
··· 13 13 #include <linux/netdevice.h> 14 14 #include "ieee80211_i.h" 15 15 16 - extern const int ieee802_1d_to_ac[8]; 17 - 18 16 u16 ieee80211_select_queue_80211(struct ieee80211_sub_if_data *sdata, 19 17 struct sk_buff *skb, 20 18 struct ieee80211_hdr *hdr);