Files
mt76/mt76_connac.h
T
Eason LaiandFelix Fietkau bd9cca0a3c wifi: mt76: mt7921: Add PCIe AER handler support to prevent system crash
When an AER error occurs and the bus is hung, the register reads return
0xFFFFFFFF, causing the DMA queue state to be corrupted and resulting in
an invalid memory access when accessing q->desc[] or q->entry[].

Unable to handle kernel paging request at virtual address
ffffffc01099eac0
pc : mt76_dma_add_buf+0x124/0x188 [mt76]
lr : mt76_dma_rx_fill+0x11c/0x1d8 [mt76]
sp : ffffffc016d9bbf0
x29: ffffffc016d9bc10 x28: 0000000000000000
x27: 0000000000000000 x26: ffffffb7855e50b8
x25: ffffffb80d04f000 x24: 0000000000000000
x23: 0000000000000ec0 x22: ffffffb796803648
x21: ffffffb796801f80 x20: ffffffb7968035f8
x19: 0000000000000ec0 x18: 0000000000000000
x17: 000000004ec00000 x16: 000000000ec00000
x15: ffffffc01099eac0 x14: 000000004ec00000
x13: 00000000ffc5a000 x12: ffffffc016d9bc32
x11: 00000000ffffffff x10: 0000000000000002
x9 : 0000000000000000 x8 : 000000000000b4ac
x7 : 0000000000000a20 x6 : ffffffb6c1806400
x5 : 0000000000000000 x4 : ffffffb80d04f000
x3 : 0000000000000000 x2 : 0000000000000001
x1 : 000000000ec04000 x0 : ffffffb7968035f8
Call trace:
 mt76_dma_add_buf+0x124/0x188 [mt76 (HASH:1029 4)]
 mt76_dma_rx_reset+0xe8/0xfc [mt76 (HASH:1029 4)]
 mt7921_wpdma_reset+0x188/0x1b0 [mt7921e (HASH:ee48 5)]
 mt7921e_mac_reset+0x128/0x418 [mt7921e (HASH:ee48 5)]
 mt7921_mac_reset_work+0xac/0x1a8 [mt7921_common (HASH:f721 6)]
 process_one_work+0x188/0x514
 worker_thread+0x12c/0x300
 kthread+0x140/0x1fc
 ret_from_fork+0x10/0x30

Fix the invalid memory access by validating the DMA index read from the
hardware before it is used as a queue index. An out-of-range value, such
as the 0xFFFFFFFF returned while the bus is hung, is now clamped so it can
no longer corrupt q->head or q->tail. In addition, check the bus_hung flag
in mt7921_mac_reset_work() before attempting the reset sequence, reject MCU
messages while the bus is hung, and install no-op bus operations when an
unrecoverable AER error is detected, preventing further invalid hardware
accesses.

Due to hardware limitations - such as the lack of a connected hardware
reset pin or the absence of host re-probe functionality - affected Wi-Fi
devices may not fully recover to a normal operational state after
certain errors, even with AER enabled.

Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets")
Co-developed-by: Sean Wang <[email protected]>
Signed-off-by: Sean Wang <[email protected]>
Co-developed-by: Jeff Hsu <[email protected]>
Signed-off-by: Jeff Hsu <[email protected]>
Signed-off-by: Eason Lai <[email protected]>
Co-developed-by: Michael Lo <[email protected]>
Link: https://patch.msgid.link/[email protected]
Signed-off-by: Felix Fietkau <[email protected]>
2026-07-21 17:56:43 +02:00

561 lines
13 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/* Copyright (C) 2020 MediaTek Inc. */
#ifndef __MT76_CONNAC_H
#define __MT76_CONNAC_H
#include "mt76.h"
enum rx_pkt_type {
PKT_TYPE_TXS,
PKT_TYPE_TXRXV,
PKT_TYPE_NORMAL,
PKT_TYPE_RX_DUP_RFB,
PKT_TYPE_RX_TMR,
PKT_TYPE_RETRIEVE,
PKT_TYPE_TXRX_NOTIFY,
PKT_TYPE_RX_EVENT,
PKT_TYPE_NORMAL_MCU,
PKT_TYPE_RX_FW_MONITOR = 0x0c,
PKT_TYPE_TXRX_NOTIFY_V0 = 0x18,
};
#define MT76_CONNAC_SCAN_IE_LEN 600
#define MT76_CONNAC_MAX_NUM_SCHED_SCAN_INTERVAL 10
#define MT76_CONNAC_MAX_TIME_SCHED_SCAN_INTERVAL U16_MAX
#define MT76_CONNAC_MAX_SCHED_SCAN_SSID 10
#define MT76_CONNAC_MAX_SCAN_MATCH 16
#define MT76_CONNAC_MAX_WMM_SETS 4
#define MT76_CONNAC_COREDUMP_TIMEOUT (HZ / 20)
#define MT76_CONNAC_COREDUMP_SZ (1300 * 1024)
#define MT_TXD_SIZE (8 * 4)
#define MT_USB_TXD_SIZE (MT_TXD_SIZE + 8 * 4)
#define MT_USB_HDR_SIZE 4
#define MT_USB_TAIL_SIZE 4
#define MT_SDIO_TXD_SIZE (MT_TXD_SIZE + 8 * 4)
#define MT_SDIO_TAIL_SIZE 8
#define MT_SDIO_HDR_SIZE 4
#define MT_MSDU_ID_VALID BIT(15)
#define MT_TXD_LEN_LAST BIT(15)
#define MT_TXD_LEN_MASK GENMASK(11, 0)
#define MT_TXD_LEN_MSDU_LAST BIT(14)
#define MT_TXD_LEN_AMSDU_LAST BIT(15)
/* PCIE part */
#define PCIE_AER_UNC_STATUS_OFFSET 0x204
#define PCIE_AER_UNC_MASK_OFFSET 0x208
#define PCIE_AER_CO_STATUS_OFFSET 0x210
enum {
CMD_CBW_20MHZ = IEEE80211_STA_RX_BW_20,
CMD_CBW_40MHZ = IEEE80211_STA_RX_BW_40,
CMD_CBW_80MHZ = IEEE80211_STA_RX_BW_80,
CMD_CBW_160MHZ = IEEE80211_STA_RX_BW_160,
CMD_CBW_10MHZ,
CMD_CBW_5MHZ,
CMD_CBW_8080MHZ,
CMD_CBW_320MHZ,
CMD_HE_MCS_BW80 = 0,
CMD_HE_MCS_BW160,
CMD_HE_MCS_BW8080,
CMD_HE_MCS_BW_NUM
};
enum {
HW_BSSID_0 = 0x0,
HW_BSSID_1,
HW_BSSID_2,
HW_BSSID_3,
HW_BSSID_MAX = HW_BSSID_3,
EXT_BSSID_START = 0x10,
EXT_BSSID_1,
EXT_BSSID_15 = 0x1f,
EXT_BSSID_MAX = EXT_BSSID_15,
REPEATER_BSSID_START = 0x20,
REPEATER_BSSID_MAX = 0x3f,
};
#define VOW_DRR_QUANTUM_NUM 8
/* Per-level DRR airtime quantum (unit: 256us). Halved from the hardware's
* default assignment so that scaling down from the mac80211-provided airtime
* weight maps onto the available levels.
*/
#define VOW_DRR_QUANTUM_TABLE { 3, 6, 8, 10, 12, 14, 16, 18 }
/* Airtime token refill period; the value is the log2 of the period in us. */
enum {
VOW_REFILL_PERIOD_1US,
VOW_REFILL_PERIOD_2US,
VOW_REFILL_PERIOD_4US,
VOW_REFILL_PERIOD_8US,
VOW_REFILL_PERIOD_16US,
VOW_REFILL_PERIOD_32US,
VOW_REFILL_PERIOD_64US,
VOW_REFILL_PERIOD_128US,
};
/* Max airtime deficit bound (unit: 256us): generous while ATF is enabled,
* minimal when disabled.
*/
#define VOW_MAX_DEFICIT_ON 64
#define VOW_MAX_DEFICIT_OFF 1
/* Layout of the per-station DRR config value (VOW_DRR_CTRL_STA_ALL). The AC
* quantum fields are in hardware (LMAC) order, i.e. AC0 = BK ... AC3 = VO.
*/
#define VOW_DRR_STA_BSS_GRP_MASK GENMASK(5, 0)
#define VOW_DRR_STA_AC0_QNTM_MASK GENMASK(10, 8)
#define VOW_DRR_STA_AC1_QNTM_MASK GENMASK(14, 12)
#define VOW_DRR_STA_AC2_QNTM_MASK GENMASK(18, 16)
#define VOW_DRR_STA_AC3_QNTM_MASK GENMASK(22, 20)
/* A station references one of the global DWRR quantum levels per AC. The
* mac80211 airtime weight spans a wider, finer range than the eight hardware
* levels and differs from them by orders of magnitude, so map it onto a quantum
* index logarithmically, centring the default weight (256) on index 3. Voice
* and video are biased down by two and one octaves respectively, giving
* latency-sensitive traffic smaller, more frequently refilled quanta.
*/
static inline u8 mt76_connac_vow_dwrr_quantum(u16 weight, u8 ac)
{
u32 w = weight;
int idx;
switch (ac) {
case IEEE80211_AC_VO:
w /= 4;
break;
case IEEE80211_AC_VI:
w /= 2;
break;
}
idx = 3 + ilog2(w) - ilog2(IEEE80211_DEFAULT_AIRTIME_WEIGHT);
return clamp(idx, 0, VOW_DRR_QUANTUM_NUM - 1);
}
struct mt76_connac_reg_map {
u32 phys;
u32 maps;
u32 size;
};
struct mt76_connac_pm {
bool enable:1;
bool enable_user:1;
bool ds_enable:1;
bool ds_enable_user:1;
bool suspended:1;
spinlock_t txq_lock;
struct {
struct mt76_wcid *wcid;
struct sk_buff *skb;
} tx_q[IEEE80211_NUM_ACS];
struct work_struct wake_work;
wait_queue_head_t wait;
struct {
spinlock_t lock;
u32 count;
} wake;
struct mutex mutex;
struct delayed_work ps_work;
unsigned long last_activity;
unsigned long idle_timeout;
struct {
unsigned long last_wake_event;
unsigned long awake_time;
unsigned long last_doze_event;
unsigned long doze_time;
unsigned int lp_wake;
} stats;
};
struct mt76_connac_coredump {
struct sk_buff_head msg_list;
struct delayed_work work;
unsigned long last_activity;
};
struct mt76_connac_sta_key_conf {
s8 keyidx;
u8 key[16];
};
#define MT_TXP_MAX_BUF_NUM 6
struct mt76_connac_fw_txp {
__le16 flags;
__le16 token;
u8 bss_idx;
__le16 rept_wds_wcid;
u8 nbuf;
__le32 buf[MT_TXP_MAX_BUF_NUM];
__le16 len[MT_TXP_MAX_BUF_NUM];
} __packed __aligned(4);
#define MT_HW_TXP_MAX_MSDU_NUM 4
#define MT_HW_TXP_MAX_BUF_NUM 4
struct mt76_connac_txp_ptr {
__le32 buf0;
__le16 len0;
__le16 len1;
__le32 buf1;
} __packed __aligned(4);
struct mt76_connac_hw_txp {
__le16 msdu_id[MT_HW_TXP_MAX_MSDU_NUM];
struct mt76_connac_txp_ptr ptr[MT_HW_TXP_MAX_BUF_NUM / 2];
} __packed __aligned(4);
struct mt76_connac_txp_common {
union {
struct mt76_connac_fw_txp fw;
struct mt76_connac_hw_txp hw;
};
};
struct mt76_connac_tx_free {
__le16 rx_byte_cnt;
__le16 ctrl;
__le32 txd;
} __packed __aligned(4);
extern const struct wiphy_wowlan_support mt76_connac_wowlan_support;
static inline bool is_connac3(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927 || mt76_chip(dev) == 0x7928;
}
static inline bool is_mt7925(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7925;
}
static inline bool is_mt7927(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7927;
}
static inline bool is_mt7928(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7928;
}
static inline bool is_320mhz_supported(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7927;
}
static inline bool is_mt7920(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7920;
}
static inline bool is_mt7902(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7902;
}
static inline bool is_mt7922(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7922;
}
static inline bool is_connac2(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7961 || is_mt7922(dev) || is_mt7920(dev) ||
is_mt7902(dev);
}
static inline bool is_mt7663(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7663;
}
static inline bool is_mt7915(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7915;
}
static inline bool is_mt7916(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7906;
}
static inline bool is_mt7981(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7981;
}
static inline bool is_mt7986(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7986;
}
static inline bool is_mt798x(struct mt76_dev *dev)
{
return is_mt7981(dev) || is_mt7986(dev);
}
static inline bool is_mt7996(struct mt76_dev *dev)
{
u16 chip = mt76_chip(dev);
return chip == 0x7990 || chip == 0x7991;
}
static inline bool is_mt7992(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7992;
}
static inline bool is_mt7990(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7993;
}
static inline bool is_mt799x(struct mt76_dev *dev)
{
return is_mt7996(dev) || is_mt7992(dev) || is_mt7990(dev);
}
static inline bool is_mt7622(struct mt76_dev *dev)
{
if (!IS_ENABLED(CONFIG_MT7622_WMAC))
return false;
return mt76_chip(dev) == 0x7622;
}
static inline bool is_mt7615(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7615 || mt76_chip(dev) == 0x7611;
}
static inline bool is_mt7611(struct mt76_dev *dev)
{
return mt76_chip(dev) == 0x7611;
}
static inline bool is_connac_v1(struct mt76_dev *dev)
{
return is_mt7615(dev) || is_mt7663(dev) || is_mt7622(dev);
}
static inline bool is_mt76_fw_txp(struct mt76_dev *dev)
{
switch (mt76_chip(dev)) {
case 0x7961:
case 0x7920:
case 0x7922:
case 0x7902:
case 0x7925:
case 0x7927:
case 0x7928:
case 0x7663:
case 0x7622:
return false;
default:
return true;
}
}
static inline u8 mt76_connac_chan_bw(struct cfg80211_chan_def *chandef)
{
static const u8 width_to_bw[] = {
[NL80211_CHAN_WIDTH_40] = CMD_CBW_40MHZ,
[NL80211_CHAN_WIDTH_80] = CMD_CBW_80MHZ,
[NL80211_CHAN_WIDTH_80P80] = CMD_CBW_8080MHZ,
[NL80211_CHAN_WIDTH_160] = CMD_CBW_160MHZ,
[NL80211_CHAN_WIDTH_5] = CMD_CBW_5MHZ,
[NL80211_CHAN_WIDTH_10] = CMD_CBW_10MHZ,
[NL80211_CHAN_WIDTH_20] = CMD_CBW_20MHZ,
[NL80211_CHAN_WIDTH_20_NOHT] = CMD_CBW_20MHZ,
[NL80211_CHAN_WIDTH_320] = CMD_CBW_320MHZ,
};
if (chandef->width >= ARRAY_SIZE(width_to_bw))
return 0;
return width_to_bw[chandef->width];
}
static inline u8 mt76_connac_lmac_mapping(u8 ac)
{
/* LMAC uses the reverse order of mac80211 AC indexes */
return 3 - ac;
}
static inline void *
mt76_connac_txwi_to_txp(struct mt76_dev *dev, struct mt76_txwi_cache *t)
{
u8 *txwi;
if (!t)
return NULL;
txwi = mt76_get_txwi_ptr(dev, t);
return (void *)(txwi + MT_TXD_SIZE);
}
static inline u8 mt76_connac_spe_idx(u8 antenna_mask)
{
static const u8 ant_to_spe[] = {0, 0, 1, 0, 3, 2, 4, 0,
9, 8, 6, 10, 16, 12, 18, 0};
if (antenna_mask >= sizeof(ant_to_spe))
return 0;
return ant_to_spe[antenna_mask];
}
static inline void mt76_connac_irq_enable(struct mt76_dev *dev, u32 mask)
{
mt76_set_irq_mask(dev, 0, 0, mask);
tasklet_schedule(&dev->irq_tasklet);
}
int mt76_connac_pm_wake(struct mt76_phy *phy, struct mt76_connac_pm *pm);
void mt76_connac_power_save_sched(struct mt76_phy *phy,
struct mt76_connac_pm *pm);
void mt76_connac_free_pending_tx_skbs(struct mt76_connac_pm *pm,
struct mt76_wcid *wcid);
static inline void mt76_connac_tx_cleanup(struct mt76_dev *dev)
{
dev->queue_ops->tx_cleanup(dev, dev->q_mcu[MT_MCUQ_WM], false);
dev->queue_ops->tx_cleanup(dev, dev->q_mcu[MT_MCUQ_WA], false);
}
static inline bool
mt76_connac_pm_ref(struct mt76_phy *phy, struct mt76_connac_pm *pm)
{
bool ret = false;
spin_lock_bh(&pm->wake.lock);
if (test_bit(MT76_STATE_PM, &phy->state))
goto out;
pm->wake.count++;
ret = true;
out:
spin_unlock_bh(&pm->wake.lock);
return ret;
}
static inline void
mt76_connac_pm_unref(struct mt76_phy *phy, struct mt76_connac_pm *pm)
{
spin_lock_bh(&pm->wake.lock);
pm->last_activity = jiffies;
if (--pm->wake.count == 0 &&
test_bit(MT76_STATE_MCU_RUNNING, &phy->state))
mt76_connac_power_save_sched(phy, pm);
spin_unlock_bh(&pm->wake.lock);
}
static inline bool
mt76_connac_skip_fw_pmctrl(struct mt76_phy *phy, struct mt76_connac_pm *pm)
{
struct mt76_dev *dev = phy->dev;
bool ret;
if (dev->token_count)
return true;
spin_lock_bh(&pm->wake.lock);
ret = pm->wake.count || test_and_set_bit(MT76_STATE_PM, &phy->state);
spin_unlock_bh(&pm->wake.lock);
return ret;
}
static inline void
mt76_connac_mutex_acquire(struct mt76_dev *dev, struct mt76_connac_pm *pm)
__acquires(&dev->mutex)
{
mutex_lock(&dev->mutex);
mt76_connac_pm_wake(&dev->phy, pm);
}
static inline void
mt76_connac_mutex_release(struct mt76_dev *dev, struct mt76_connac_pm *pm)
__releases(&dev->mutex)
{
mt76_connac_power_save_sched(&dev->phy, pm);
mutex_unlock(&dev->mutex);
}
void mt76_connac_gen_ppe_thresh(u8 *he_ppet, int nss, enum nl80211_band band);
int mt76_connac_init_tx_queues(struct mt76_phy *phy, int idx, int n_desc,
int ring_base, void *wed, u32 flags);
void mt76_connac_set_txpower_cur(struct mt76_phy *phy, s8 max_power);
s8 mt76_connac_get_rate_power_limit(struct mt76_phy *phy,
struct ieee80211_channel *chan,
struct mt76_power_limits *limits);
void mt76_connac_write_hw_txp(struct mt76_dev *dev,
struct mt76_tx_info *tx_info,
void *txp_ptr, u32 id);
void mt76_connac_txp_skb_unmap(struct mt76_dev *dev,
struct mt76_txwi_cache *txwi);
void mt76_connac_tx_complete_skb(struct mt76_dev *mdev,
struct mt76_queue_entry *e);
void mt76_connac_pm_queue_skb(struct ieee80211_hw *hw,
struct mt76_connac_pm *pm,
struct mt76_wcid *wcid,
struct sk_buff *skb);
void mt76_connac_pm_dequeue_skbs(struct mt76_phy *phy,
struct mt76_connac_pm *pm);
void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
struct sk_buff *skb, struct mt76_wcid *wcid,
struct ieee80211_key_conf *key, int pid,
enum mt76_txq_id qid, u32 changed);
u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy,
struct ieee80211_bss_conf *conf,
bool beacon, bool mcast);
bool mt76_connac2_mac_fill_txs(struct mt76_dev *dev, struct mt76_wcid *wcid,
__le32 *txs_data);
bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
int pid, __le32 *txs_data);
void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
struct sk_buff *skb,
__le32 *rxv, u32 mode);
int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
struct sk_buff *skb, u16 hdr_offset);
int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
struct mt76_rx_status *status,
struct ieee80211_supported_band *sband,
__le32 *rxv, u8 *mode);
void mt76_connac2_tx_check_aggr(struct ieee80211_sta *sta, __le32 *txwi);
void mt76_connac2_txwi_free(struct mt76_dev *dev, struct mt76_txwi_cache *t,
struct ieee80211_sta *sta,
struct list_head *free_list);
void mt76_connac2_tx_token_put(struct mt76_dev *dev);
/* connac3 */
void mt76_connac3_mac_decode_he_radiotap(struct sk_buff *skb, __le32 *rxv,
u8 mode);
void mt76_connac3_mac_decode_eht_radiotap(struct sk_buff *skb, __le32 *rxv,
u8 mode);
#endif /* __MT76_CONNAC_H */