[RFC PATCH wireless] wifi: mt76: mt7925: get the survey airtime from the firmware
Chris
hoopyfrood42 at gmail.com
Fri Oct 23 15:34:01 PDT 2026
On Fri, Oct 23, 2026 at 2:06 PM Devin Wittmayer
<lucid_duck at justthetip.ca> wrote:
>
> Busy time in the mt7925 survey is bogus. The register it comes from
> doesn't work on this chip, so every update adds the same large number
> and busy time soon outgrows the time spent on the channel. Transmit
> and receive time come out far too low as well.
>
> Ask the firmware for all three instead, the way mt7996 does.
>
> The MT7927 counts 5 and 6 GHz on a second band that the survey never
> read, so read both bands there and only add the one the channel is on.
>
> Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips")
> Fixes: 05fd14885303 ("wifi: mt76: mt7925: sync MT7927 BSS band assignment")
> Reported-by: Chris Kelly <hoopyfrood42 at gmail.com>
> Closes: https://lore.kernel.org/linux-wireless/CAHaeJsT8O8Nhq351hFp3NAL8e0iU85N-LBdYZ9nYHFHVy7t_hQ@mail.gmail.com/
> Signed-off-by: Devin Wittmayer <lucid_duck at justthetip.ca>
> ---
> Chris, here's the RFC you asked about. Busy time now follows the load
> and stays under the time on the channel, hopping every half second too.
>
> It's an RFC because the offsets aren't documented. On an A9000:
>
> 19 busy time, including the card's own transmit
> 20 receive time
> 28 transmit time
>
> The MT7927 counts 5 and 6 GHz on a second band, which keeps counting
> while unused, so only the band in use is added.
>
> Busy as a share of time on the channel, A9000 on 2.4 GHz next to an
> MT7921AU:
>
> before after MT7921AU
> idle 4229% 4.2% 1.4%
> 100 Mbit/s 4317% 37.3% 33.7%
> saturated 4346% 79.5% 77.4%
>
> Tested on the A9000 (Arch 6.18 and 7.2, Ubuntu 7.3, wireless-next with
> lockdep) and on a PCIe MT7925 and MT7927 (Fedora 7.2). mt7921 keeps its
> registers, which work on my MT7921AU.
>
> Could you try it on your A9000 against the Wi-Spy?
>
> .../net/wireless/mediatek/mt76/mt7925/mac.c | 42 +++++++++++
> .../net/wireless/mediatek/mt76/mt7925/main.c | 1 +
> .../net/wireless/mediatek/mt76/mt7925/mcu.c | 70 +++++++++++++++++++
> .../net/wireless/mediatek/mt76/mt7925/mcu.h | 17 +++++
> .../wireless/mediatek/mt76/mt7925/mt7925.h | 3 +
> .../net/wireless/mediatek/mt76/mt7925/pci.c | 2 +-
> .../net/wireless/mediatek/mt76/mt7925/usb.c | 2 +-
> drivers/net/wireless/mediatek/mt76/mt792x.h | 1 +
> 8 files changed, 136 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> index 7af996abe294..4ec3712fe614 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
> @@ -1470,6 +1470,48 @@ void mt7925_queue_rx_skb(struct mt76_dev *mdev, enum mt76_rxq_id q,
> }
> EXPORT_SYMBOL_GPL(mt7925_queue_rx_skb);
>
> +void mt7925_mac_update_chan_time(struct mt792x_phy *phy, bool reset)
> +{
> + struct ieee80211_channel *chan = phy->mt76->chandef.chan;
> + struct mt76_channel_state *state = phy->mt76->chan_state;
> + struct mt792x_dev *dev = phy->dev;
> + int i, n_bands = 1, band = 0;
> +
> + /* The MT7927 counts 2.4 GHz on band 0 and 5/6 GHz on band 1, and
> + * the band not in use keeps counting, so read both every time and
> + * only add the band the channel is on.
> + */
> + if (is_mt7927(&dev->mt76)) {
> + n_bands = ARRAY_SIZE(phy->state_ts);
> + if (chan)
> + band = mt7927_band_idx(chan->band);
> + }
> +
> + for (i = 0; i < n_bands; i++) {
> + bool add = !reset && i == band;
> + u32 bss_rx;
> +
> + bss_rx = mt76_get_field(dev, MT_MIB_SDR37(i),
> + MT_MIB_SDR37_RXTIME_MASK);
> + if (add)
> + state->cc_bss_rx += bss_rx;
> +
> + mt7925_mcu_get_chan_mib_info(phy, i, add);
> + }
> +}
> +
> +void mt7925_update_channel(struct mt76_phy *mphy)
> +{
> + struct mt792x_dev *dev = container_of(mphy->dev, struct mt792x_dev, mt76);
> +
> + if (mt76_connac_pm_wake(mphy, &dev->pm))
> + return;
> +
> + mt7925_mac_update_chan_time(mphy->priv, false);
> + mt76_connac_power_save_sched(mphy, &dev->pm);
> +}
> +EXPORT_SYMBOL_GPL(mt7925_update_channel);
> +
> static void
> mt7925_vif_connect_iter(void *priv, u8 *mac,
> struct ieee80211_vif *vif)
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> index 22cc5b49da0a..393c225c19e4 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
> @@ -351,6 +351,7 @@ int __mt7925_start(struct mt792x_phy *phy)
> return err;
>
> mt792x_mac_reset_counters(phy);
> + mt7925_mac_update_chan_time(phy, true);
> set_bit(MT76_STATE_RUNNING, &mphy->state);
>
> ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work,
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> index f299a3379adb..b741a4880cfe 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
> @@ -1057,6 +1057,76 @@ int mt7925_mcu_get_temperature(struct mt792x_phy *phy)
> return temperature;
> }
>
> +int mt7925_mcu_get_chan_mib_info(struct mt792x_phy *phy, u8 band, bool add)
> +{
> + enum {
> + IDX_BUSY_TIME,
> + IDX_TX_TIME,
> + IDX_RX_TIME,
> + IDX_NUM
> + };
> + struct {
> + struct {
> + u8 band;
> + u8 __rsv[3];
> + } hdr;
> + struct {
> + __le16 tag;
> + __le16 len;
> + __le32 offs;
> + } data[IDX_NUM];
> + } __packed req = {
> + .hdr.band = band,
> + };
> + static const u32 offs[] = {
> + [IDX_BUSY_TIME] = UNI_MIB_BUSY_TIME,
> + [IDX_TX_TIME] = UNI_MIB_TX_TIME,
> + [IDX_RX_TIME] = UNI_MIB_RX_TIME,
> + };
> + struct mt76_channel_state *state = phy->mt76->chan_state;
> + struct mt76_channel_state *state_ts = &phy->state_ts[band];
> + struct mt792x_dev *dev = phy->dev;
> + struct mt7925_mcu_mib *res;
> + struct sk_buff *skb;
> + int i, ret;
> +
> + for (i = 0; i < IDX_NUM; i++) {
> + req.data[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
> + req.data[i].len = cpu_to_le16(sizeof(req.data[i]));
> + req.data[i].offs = cpu_to_le32(offs[i]);
> + }
> +
> + ret = mt76_mcu_send_and_get_msg(&dev->mt76,
> + MCU_WM_UNI_CMD_QUERY(GET_MIB_INFO),
> + &req, sizeof(req), true, &skb);
> + if (ret)
> + return ret;
> + if (skb->len < sizeof(req.hdr) + IDX_NUM * sizeof(*res)) {
> + dev_kfree_skb(skb);
> + return -EINVAL;
> + }
> +
> + skb_pull(skb, sizeof(req.hdr));
> + res = (struct mt7925_mcu_mib *)skb->data;
> +
> + if (!add)
> + goto out;
> +
> +#define __res_u64(s) le64_to_cpu(res[s].data)
> + state->cc_busy += __res_u64(IDX_BUSY_TIME) - state_ts->cc_busy;
> + state->cc_tx += __res_u64(IDX_TX_TIME) - state_ts->cc_tx;
> + state->cc_rx += __res_u64(IDX_RX_TIME) - state_ts->cc_rx;
> +out:
> + state_ts->cc_busy = __res_u64(IDX_BUSY_TIME);
> + state_ts->cc_tx = __res_u64(IDX_TX_TIME);
> + state_ts->cc_rx = __res_u64(IDX_RX_TIME);
> +#undef __res_u64
> +
> + dev_kfree_skb(skb);
> +
> + return 0;
> +}
> +
> static void
> mt7925_mcu_parse_phy_cap(struct mt792x_dev *dev, char *data)
> {
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
> index 11f9eac13ffc..f3e892dbb74c 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
> @@ -57,6 +57,23 @@ struct mt7925_mcu_reg_event {
> __le32 val;
> } __packed;
>
> +enum {
> + UNI_CMD_MIB_DATA,
> +};
> +
> +enum {
> + UNI_MIB_BUSY_TIME = 19,
> + UNI_MIB_RX_TIME = 20,
> + UNI_MIB_TX_TIME = 28,
> +};
> +
> +struct mt7925_mcu_mib {
> + __le16 tag;
> + __le16 len;
> + __le32 offs;
> + __le64 data;
> +} __packed;
> +
> struct mt7925_mcu_ant_id_config {
> u8 ant_id[4];
> } __packed;
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
> index 636efe038392..706e67e8ec42 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
> @@ -375,6 +375,9 @@ int mt7925_mcu_set_sniffer(struct mt792x_dev *dev, struct ieee80211_vif *vif,
> int mt7925_mcu_config_sniffer(struct mt792x_vif *vif,
> struct ieee80211_chanctx_conf *ctx);
> int mt7925_mcu_get_temperature(struct mt792x_phy *phy);
> +int mt7925_mcu_get_chan_mib_info(struct mt792x_phy *phy, u8 band, bool add);
> +void mt7925_mac_update_chan_time(struct mt792x_phy *phy, bool reset);
> +void mt7925_update_channel(struct mt76_phy *mphy);
>
> int mt7925_usb_sdio_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
> enum mt76_txq_id qid, struct mt76_wcid *wcid,
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> index 0d477dc1086c..939a46c0428b 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
> @@ -604,7 +604,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
> .sta_add = mt7925_mac_sta_add,
> .sta_event = mt7925_mac_sta_event,
> .sta_remove = mt7925_mac_sta_remove,
> - .update_survey = mt792x_update_channel,
> + .update_survey = mt7925_update_channel,
> };
> static const struct mt792x_hif_ops mt7925_pcie_ops = {
> .init_reset = mt7925e_init_reset,
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
> index f97f787b2198..2d3bf035fdfc 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
> @@ -174,7 +174,7 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
> .sta_add = mt7925_mac_sta_add,
> .sta_event = mt7925_mac_sta_event,
> .sta_remove = mt7925_mac_sta_remove,
> - .update_survey = mt792x_update_channel,
> + .update_survey = mt7925_update_channel,
> };
> static const struct mt792x_hif_ops hif_ops = {
> .mcu_init = mt7925u_mcu_init,
> diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
> index a7d71e007da0..3ee513258d00 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt792x.h
> +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
> @@ -221,6 +221,7 @@ struct mt792x_phy {
> u32 ampdu_ref;
>
> struct mt76_mib_stats mib;
> + struct mt76_channel_state state_ts[2];
>
> u8 sta_work_count;
> u8 clc_chan_conf;
> --
> 2.56.0
>
On Fri, Oct 23, 2026 at 2:05 PM Devin Wittmayer
<lucid_duck at justthetip.ca> wrote:
> Chris, here's the RFC you asked about. Busy time now follows the load
> and stays under the time on the channel, hopping every half second too.
[...]
> Could you try it on your A9000 against the Wi-Spy?
Thanks, Devin. It works here.
A9000 (mt7925u on USB 2.0), Ubuntu 7.0.0-38-generic, WM firmware
20260605184805 from Ubuntu's linux-firmware 20260319 (not the
2026-10-20 release), built out of tree from Ubuntu's mt76 source on
top of your survey v2. Ubuntu's 7.0 has no MT7927, so I dropped the
is_mt7927() block and the chan local it used; the rest is as sent.
Monitor mode, no association, 98 channels at 500 ms dwell, survey read
every ~5 s, 102 minutes: 8596 per-channel updates, none with busy over
active time, none going backwards. Quiet 6 GHz channels read 0.1-0.3%.
Without the patch the same setup gave 6013% on one channel. No MCU
timeouts or resets.
Load was a phone on a 2.4 GHz AP on channel 1 playing 2160p video.
Busy as a share of time on the channel, next to the Wi-Spy DBx2's RF
occupancy (bins 6 dB over its floor):
hopping locked on ch 1
busy Wi-Spy busy Wi-Spy
normal air 41.9% 71.4% 50.8% 69.1%
2160p video 57.2% 85.5% 69.4% 85.0%
The Wi-Spy counts any energy, so it reads higher on 2.4 GHz, but the
two move together, and channels 2-5 rose with the load on both. On
5 GHz the Wi-Spy is the weaker of the two (3-16% on busy channels where
busy reads 13-32%), and busy stays above the airtime of the frames I
can decode there, as it should.
One thing on offset 20: in monitor mode it doesn't look like all of
rx. On busy 5 GHz channels it mostly reads under 2% while the frames I
decode alone take 13-19% (ch 149: 1.6% vs 19.1%), yet on 2.4 GHz
channels 2-4, between the networks on 1 and 6, it is nearly all of
busy (22-25%) with almost nothing decodable there. It does follow load
(ch 1 locked: 29% -> 55%). Busy doesn't depend on it, but MediaTek may
know what it counts. tx stays 0, as it should with nothing sent.
Tested-by: Chris Kelly <hoopyfrood42 at gmail.com>
Chris
More information about the Linux-mediatek
mailing list