From 910db36ae5b1b95cd34f1f95a1d8f9cdcec3f416 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 29 Jun 2026 10:57:11 +0200 Subject: [PATCH] wifi: mt76: mt7996: HW ATF support Add hardware airtime fairness (ATF) support for mt7996, using the MAC's weighted DWRR scheduler (VoW) to balance TX airtime between stations, in the same way as the mt7915 implementation. This supplements mac80211's software airtime fairness rather than replacing it: software ATF keeps scheduling the host TX queues, while the hardware scheduler enforces fairness on the offloaded path where the host has no airtime accounting, such as when WED is active. A station's TX airtime weight is taken from mac80211 through the sta_set_airtime_weight op and applied to the hardware scheduler directly; the per-AC DWRR quantum levels and the station's BSS group index are derived on the fly, mapping the weight logarithmically onto the global hardware quantum table. ATF and WATF are enabled by default and can be toggled together at runtime through the "vow_atf" debugfs file. Signed-off-by: Benjamin Lin Signed-off-by: Felix Fietkau --- mt7996/debugfs.c | 36 ++++++++++ mt7996/init.c | 15 ++++ mt7996/main.c | 32 +++++++++ mt7996/mcu.c | 182 +++++++++++++++++++++++++++++++++++++++++++---- mt7996/mcu.h | 1 + mt7996/mt7996.h | 54 ++++++++++++++ 6 files changed, 308 insertions(+), 12 deletions(-) diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c index b85c3e54..cd5d07e6 100644 --- a/mt7996/debugfs.c +++ b/mt7996/debugfs.c @@ -870,6 +870,41 @@ mt7996_rf_regval_set(void *data, u64 val) DEFINE_DEBUGFS_ATTRIBUTE(fops_rf_regval, mt7996_rf_regval_get, mt7996_rf_regval_set, "0x%08llx\n"); +static int mt7996_vow_atf_set(void *data, u64 val) +{ + struct mt7996_dev *dev = data; + struct mt7996_phy *phy; + int ret; + + dev->vow_atf_en = !!val; + + mt7996_for_each_phy(dev, phy) { + ret = mt7996_mcu_set_vow_drr_ctrl(dev, phy->mt76->band_idx, NULL, + NULL, + VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND, 0); + if (ret) + return ret; + + ret = mt7996_mcu_set_vow_feature_ctrl(phy); + if (ret) + return ret; + } + + return 0; +} + +static int mt7996_vow_atf_get(void *data, u64 *val) +{ + struct mt7996_dev *dev = data; + + *val = dev->vow_atf_en; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(fops_vow_atf, mt7996_vow_atf_get, mt7996_vow_atf_set, + "%lld\n"); + int mt7996_init_debugfs(struct mt7996_dev *dev) { struct dentry *dir; @@ -894,6 +929,7 @@ int mt7996_init_debugfs(struct mt7996_dev *dev) debugfs_create_devm_seqfile(dev->mt76.dev, "twt_stats", dir, mt7996_twt_stats); debugfs_create_file("rf_regval", 0600, dir, dev, &fops_rf_regval); + debugfs_create_file("vow_atf", 0600, dir, dev, &fops_vow_atf); debugfs_create_u32("dfs_hw_pattern", 0400, dir, &dev->hw_pattern); debugfs_create_file("radar_trigger", 0200, dir, dev, diff --git a/mt7996/init.c b/mt7996/init.c index 2bf3ddd8..5b68e1a7 100644 --- a/mt7996/init.c +++ b/mt7996/init.c @@ -611,6 +611,7 @@ static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev) void mt7996_mac_init(struct mt7996_dev *dev) { #define HIF_TXD_V2_1 0x21 + struct mt7996_phy *phy; int i, rx_path_type; mt76_clear(dev, MT_MDP_DCR2, MT_MDP_DCR2_RX_TRANS_SHORT); @@ -657,6 +658,9 @@ void mt7996_mac_init(struct mt7996_dev *dev) for (i = MT_BAND0; i <= MT_BAND2; i++) mt7996_mac_init_band(dev, i); + mt7996_for_each_phy(dev, phy) + mt7996_vow_init(phy); + mt7996_mac_init_basic_rates(dev); } @@ -680,6 +684,17 @@ int mt7996_txbf_init(struct mt7996_dev *dev) return mt7996_mcu_set_txbf(dev, BF_HW_EN_UPDATE); } +void mt7996_vow_init(struct mt7996_phy *phy) +{ + phy->dev->vow_atf_en = true; + + mt7996_mcu_set_vow_drr_ctrl(phy->dev, phy->mt76->band_idx, NULL, NULL, + VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND, 0); + mt7996_mcu_set_vow_drr_ctrl(phy->dev, phy->mt76->band_idx, NULL, NULL, + VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL, 0); + mt7996_mcu_set_vow_feature_ctrl(phy); +} + static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) { struct mt7996_phy *phy; diff --git a/mt7996/main.c b/mt7996/main.c index 560fe30d..93ff2914 100644 --- a/mt7996/main.c +++ b/mt7996/main.c @@ -1995,6 +1995,37 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return 0; } +static void mt7996_sta_set_airtime_weight(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, u16 weight) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct ieee80211_link_sta *link_sta; + unsigned int link_id; + + mutex_lock(&dev->mt76.mutex); + + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt7996_sta_link *msta_link; + struct mt7996_vif_link *link; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + msta_link = mt7996_sta_link_protected(dev, msta, link_id); + if (!msta_link || !msta_link->wcid.sta) + continue; + + mt7996_mcu_set_vow_drr_ctrl(dev, link->mt76.band_idx, + &msta_link->wcid, &link->mt76, + VOW_DRR_CTRL_STA_ALL, weight); + } + + mutex_unlock(&dev->mt76.mutex); +} + static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, @@ -2553,6 +2584,7 @@ const struct ieee80211_ops mt7996_ops = { .sta_statistics = mt7996_sta_statistics, .sta_set_4addr = mt7996_sta_set_4addr, .sta_set_decap_offload = mt7996_sta_set_decap_offload, + .sta_set_airtime_weight = mt7996_sta_set_airtime_weight, .add_twt_setup = mt7996_mac_add_twt_setup, .twt_teardown_request = mt7996_twt_teardown_request, #ifdef CONFIG_MAC80211_DEBUGFS diff --git a/mt7996/mcu.c b/mt7996/mcu.c index c0b9b1bf..1bd32c00 100644 --- a/mt7996/mcu.c +++ b/mt7996/mcu.c @@ -2639,33 +2639,191 @@ error_unlock: return ret; } -static int -mt7996_mcu_add_group(struct mt7996_dev *dev, struct mt7996_vif_link *link, - struct mt76_wcid *wcid) +/* Assignment of the STA BSS group index aligns FW. Each band has its own BSS + * group bitmap space. + * 0..3: BSS 0..3 + * 4..18: BSS 0x11..0x1f + */ +static u8 mt7996_vow_sta_bss_grp(struct mt76_vif_link *mvif) { -#define MT_STA_BSS_GROUP 1 + u8 omac_idx = mvif->omac_idx; + + if (omac_idx <= HW_BSSID_MAX) + return omac_idx; + + /* Extended BSS */ + return HW_BSSID_MAX + omac_idx - EXT_BSSID_START; +} + +int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_dev *dev, u8 band_idx, + struct mt76_wcid *wcid, struct mt76_vif_link *mvif, + enum vow_drr_ctrl_id id, u16 weight) +{ + u32 val = 0; struct { u8 __rsv1[4]; __le16 tag; __le16 len; __le16 wlan_idx; - u8 __rsv2[2]; - __le32 action; - __le32 val; - u8 __rsv3[8]; + u8 band_idx; + u8 wmm_idx; + __le32 ctrl_id; + union { + __le32 val; + u8 drr_quantum[VOW_DRR_QUANTUM_NUM]; + }; + u8 __rsv2[3]; + u8 omac_idx; } __packed req = { .tag = cpu_to_le16(UNI_VOW_DRR_CTRL), .len = cpu_to_le16(sizeof(req) - 4), - .action = cpu_to_le32(MT_STA_BSS_GROUP), - .val = cpu_to_le32(link->mt76.idx % 16), - .wlan_idx = cpu_to_le16(wcid->idx), + .wlan_idx = cpu_to_le16(wcid ? wcid->idx : 0), + .band_idx = band_idx, + .wmm_idx = mvif ? mvif->wmm_idx : 0, + .ctrl_id = cpu_to_le32(id), + .omac_idx = mvif ? mvif->omac_idx : 0, }; + switch (id) { + case VOW_DRR_CTRL_STA_ALL: + val |= FIELD_PREP(VOW_DRR_STA_BSS_GRP_MASK, + mt7996_vow_sta_bss_grp(mvif)); + val |= FIELD_PREP(VOW_DRR_STA_AC0_QNTM_MASK, + mt76_connac_vow_dwrr_quantum(weight, IEEE80211_AC_BK)); + val |= FIELD_PREP(VOW_DRR_STA_AC1_QNTM_MASK, + mt76_connac_vow_dwrr_quantum(weight, IEEE80211_AC_BE)); + val |= FIELD_PREP(VOW_DRR_STA_AC2_QNTM_MASK, + mt76_connac_vow_dwrr_quantum(weight, IEEE80211_AC_VI)); + val |= FIELD_PREP(VOW_DRR_STA_AC3_QNTM_MASK, + mt76_connac_vow_dwrr_quantum(weight, IEEE80211_AC_VO)); + req.val = cpu_to_le32(val); + break; + case VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND: + req.val = cpu_to_le32(dev->vow_atf_en ? VOW_MAX_DEFICIT_ON : + VOW_MAX_DEFICIT_OFF); + break; + case VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL: { + static const u8 quantum[VOW_DRR_QUANTUM_NUM] = VOW_DRR_QUANTUM_TABLE; + + memcpy(req.drr_quantum, quantum, sizeof(quantum)); + break; + } + case VOW_DRR_CTRL_STA_PAUSE: + /* req.val 0 keeps the station unpaused */ + break; + default: + return -EINVAL; + } + return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(VOW), &req, sizeof(req), true); } +int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy) +{ + bool atf_en = phy->dev->vow_atf_en; + u16 apply, ctrl; + u32 atf_ctrl, sch_ctrl; + struct { + u8 __rsv1[4]; + + __le16 tag; + __le16 len; + + /* DW0 */ + __le16 apply_bwc_enable_per_grp; + __le16 apply; + + /* DW1 */ + __le16 apply_bwc_check_time_token_per_grp; + __le16 __rsv5; + + /* DW2 */ + __le16 apply_bwc_check_len_token_per_grp; + __le16 __rsv6; + + /* DW3 */ + u8 band_idx; + u8 __rsv7[3]; + + /* DW4 */ + __le32 __rsv8; + + /* DW5 */ + __le16 bwc_enable_per_grp; + __le16 ctrl; + + /* DW6 */ + __le16 bwc_check_time_token_per_grp; + __le16 __rsv12; + + /* DW7 */ + __le16 bwc_check_len_token_per_grp; + __le16 __rsv13; + + /* DW8 */ + __le32 atf_ctrl; + + /* DW9 */ + __le32 sch_ctrl; + } __packed req = { + .tag = cpu_to_le16(UNI_VOW_FEATURE_CTRL), + .len = cpu_to_le16(sizeof(req) - 4), + .apply_bwc_enable_per_grp = cpu_to_le16(0xffff), + .apply_bwc_check_time_token_per_grp = cpu_to_le16(0xffff), + .apply_bwc_check_len_token_per_grp = cpu_to_le16(0xffff), + .band_idx = phy->mt76->band_idx, + .bwc_enable_per_grp = cpu_to_le16(0xffff), + }; + + /* apply every field this command configures */ + apply = VOW_FEATURE_APPLY_REFILL_PERIOD | VOW_FEATURE_BAND1_SEARCH_RULE | + VOW_FEATURE_BAND0_SEARCH_RULE | VOW_FEATURE_WATF_EN | + VOW_FEATURE_GRP_NO_CHANGE_IN_TXOP | VOW_FEATURE_ATF_EN | + VOW_FEATURE_BWC_TOKEN_REFILL_EN | VOW_FEATURE_BWC_EN; + + ctrl = FIELD_PREP(VOW_FEATURE_REFILL_PERIOD, VOW_REFILL_PERIOD_32US) | + FIELD_PREP(VOW_FEATURE_BAND1_SEARCH_RULE, VOW_SEARCH_WMM_FIRST) | + FIELD_PREP(VOW_FEATURE_BAND0_SEARCH_RULE, VOW_SEARCH_WMM_FIRST) | + VOW_FEATURE_GRP_NO_CHANGE_IN_TXOP | VOW_FEATURE_BWC_TOKEN_REFILL_EN; + if (atf_en) + ctrl |= VOW_FEATURE_WATF_EN | VOW_FEATURE_ATF_EN; + + atf_ctrl = VOW_ATF_APPLY_KEEP_QUANTUM | VOW_ATF_KEEP_QUANTUM | + VOW_ATF_APPLY_VOW_CTRL | VOW_ATF_VOW_CTRL_VAL | + /* reset the DRR table when SER occurs */ + FIELD_PREP(VOW_ATF_VOW_CTRL_BIT, 26); + + sch_ctrl = VOW_SCH_APPLY_CTRL | + FIELD_PREP(VOW_SCH_TYPE, VOW_SCH_TYPE_FOLLOW_POLICY) | + FIELD_PREP(VOW_SCH_POLICY, VOW_SCH_POLICY_SRR); + + req.apply = cpu_to_le16(apply); + req.ctrl = cpu_to_le16(ctrl); + req.atf_ctrl = cpu_to_le32(atf_ctrl); + req.sch_ctrl = cpu_to_le32(sch_ctrl); + + return mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(VOW), &req, + sizeof(req), true); +} + +static int +mt7996_mcu_sta_init_vow(struct mt7996_dev *dev, struct mt76_vif_link *mvif, + struct mt76_wcid *wcid) +{ + int ret; + + ret = mt7996_mcu_set_vow_drr_ctrl(dev, mvif->band_idx, wcid, mvif, + VOW_DRR_CTRL_STA_PAUSE, 0); + if (ret) + return ret; + + return mt7996_mcu_set_vow_drr_ctrl(dev, mvif->band_idx, wcid, mvif, + VOW_DRR_CTRL_STA_ALL, + IEEE80211_DEFAULT_AIRTIME_WEIGHT); +} + int mt7996_mcu_mld_reconf_stop_link(struct mt7996_dev *dev, struct ieee80211_vif *vif, u16 removed_links) @@ -2867,7 +3025,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, } } - ret = mt7996_mcu_add_group(dev, link, wcid); + ret = mt7996_mcu_sta_init_vow(dev, &link->mt76, wcid); if (ret) { dev_kfree_skb(skb); return ret; diff --git a/mt7996/mcu.h b/mt7996/mcu.h index 55d42c9e..39a209f2 100644 --- a/mt7996/mcu.h +++ b/mt7996/mcu.h @@ -985,6 +985,7 @@ enum { enum { UNI_VOW_DRR_CTRL, + UNI_VOW_FEATURE_CTRL, UNI_VOW_RX_AT_AIRTIME_EN = 0x0b, UNI_VOW_RX_AT_AIRTIME_CLR_EN = 0x0e, }; diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h index 0d648852..91d94e66 100644 --- a/mt7996/mt7996.h +++ b/mt7996/mt7996.h @@ -240,6 +240,53 @@ struct mt7996_twt_flow { DECLARE_EWMA(avg_signal, 10, 8) +enum { + VOW_SEARCH_AC_FIRST, + VOW_SEARCH_WMM_FIRST, +}; + +enum { + VOW_SCH_TYPE_FOLLOW_POLICY, + VOW_SCH_TYPE_FOLLOW_HW, +}; + +enum { + VOW_SCH_POLICY_SRR, /* Shared Round-Robin */ + VOW_SCH_POLICY_WRR, /* Weighted Round-Robin */ +}; + +/* VOW feature control (UNI_VOW_FEATURE_CTRL) word fields. The apply mask (DW0) + * and the control word (DW5) share these bit positions. + */ +#define VOW_FEATURE_APPLY_REFILL_PERIOD BIT(0) +#define VOW_FEATURE_REFILL_PERIOD GENMASK(2, 0) +#define VOW_FEATURE_BAND1_SEARCH_RULE BIT(4) +#define VOW_FEATURE_BAND0_SEARCH_RULE BIT(5) +#define VOW_FEATURE_WATF_EN BIT(9) +#define VOW_FEATURE_GRP_NO_CHANGE_IN_TXOP BIT(12) +#define VOW_FEATURE_ATF_EN BIT(13) +#define VOW_FEATURE_BWC_TOKEN_REFILL_EN BIT(14) +#define VOW_FEATURE_BWC_EN BIT(15) + +/* ATF control word (DW8) */ +#define VOW_ATF_APPLY_KEEP_QUANTUM BIT(2) +#define VOW_ATF_KEEP_QUANTUM BIT(3) +#define VOW_ATF_APPLY_VOW_CTRL BIT(24) +#define VOW_ATF_VOW_CTRL_VAL BIT(25) +#define VOW_ATF_VOW_CTRL_BIT GENMASK(30, 26) + +/* Scheduler control word (DW9) */ +#define VOW_SCH_APPLY_CTRL BIT(6) +#define VOW_SCH_TYPE GENMASK(8, 7) +#define VOW_SCH_POLICY GENMASK(10, 9) + +enum vow_drr_ctrl_id { + VOW_DRR_CTRL_STA_ALL = 0x00, + VOW_DRR_CTRL_AIRTIME_DEFICIT_BOUND = 0x10, + VOW_DRR_CTRL_AIRTIME_QUANTUM_ALL = 0x28, + VOW_DRR_CTRL_STA_PAUSE = 0x30, +}; + struct mt7996_sta_link { struct mt76_wcid wcid; /* must be first */ @@ -519,6 +566,8 @@ struct mt7996_dev { u8 type:4; u8 fem:4; } var; + + bool vow_atf_en; }; enum { @@ -760,6 +809,11 @@ int mt7996_mcu_get_efuse_free_block(struct mt7996_dev *dev, u8 *block_num); int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap); int mt7996_mcu_set_ser(struct mt7996_dev *dev, u8 action, u8 set, u8 band); int mt7996_mcu_set_txbf(struct mt7996_dev *dev, u8 action); +int mt7996_mcu_set_vow_drr_ctrl(struct mt7996_dev *dev, u8 band_idx, + struct mt76_wcid *wcid, struct mt76_vif_link *mvif, + enum vow_drr_ctrl_id id, u16 weight); +int mt7996_mcu_set_vow_feature_ctrl(struct mt7996_phy *phy); +void mt7996_vow_init(struct mt7996_phy *phy); int mt7996_mcu_set_fcc5_lpn(struct mt7996_dev *dev, int val); int mt7996_mcu_set_pulse_th(struct mt7996_dev *dev, const struct mt7996_dfs_pulse *pulse);