* RE: [PATCH] wifi: rtw89: retry efuse physical map dump on transient failure
From: Ping-Ke Shih @ 2026-03-11 3:05 UTC (permalink / raw)
To: Christian Hewitt
Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <CC1F2CDA-830F-4351-A855-8C921B148F8D@gmail.com>
Christian Hewitt <christianshewitt@gmail.com> wrote:
>
> > On 9 Mar 2026, at 6:35 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
> >
> > Christian Hewitt <christianshewitt@gmail.com> wrote:
> >>
> >>> On 2 Mar 2026, at 10:04 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
> >>>
> >>> Christian Hewitt <christianshewitt@gmail.com> wrote:
> >>>>> On 2 Mar 2026, at 9:47 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
> >>>>>
> >>>>> Christian Hewitt <christianshewitt@gmail.com> wrote:
> >>>>>> On Radxa Rock 5B with a RTL8852BE combo WiFi/BT card, the efuse
> >>>>>> physical map dump intermittently fails with -EBUSY during probe.
> >>>>>> The failure occurs in rtw89_dump_physical_efuse_map_ddv() where
> >>>>>> read_poll_timeout_atomic() times out waiting for the B_AX_EF_RDY
> >>>>>> bit after 1 second.
> >>>>>
> >>>>> I'm checking internally how we handle this case.
> >
> > Sorry for the late.
> >
> > We encountered WiFi/BT reading efuse at the same time causing similar
> > problem as yours. The workaround is like yours, which adds timeout
> > time.
> >
> >>>>>
> >>>>> [...]
> >>>>>
> >>>>>>
> >>>>>> For context, firmware also fails (and recovers) sometimes:
> >>>>>
> >>>>> Did you mean this doesn't always happen? sometimes?
> >>>>
> >>>> It’s another intermittent behaviour observed on this board (and not
> >>>> related to the issue this patch targets). It occurs less frequently
> >>>> than the efuse issue and the existing retry mechanism in the driver
> >>>> ensures firmware load always succeeds.
> >
> > This might be the same cause due to reading efuse in firmware.
> >
> > Though we can add more timeout and retry times as workaround, I wonder
> > if you can control loading time of WiFi and BT kernel modules?
> >
> > More, can you do experiment that you load BT module first, and then load
> > WiFi module after 10 seconds (choose a large number intentionally, or
> > even larger)?
>
> https://paste.libreelec.tv/charmed-turkey.sh
>
> I’ve run the above script ^ which removes the wifi and bt modules in
> sequence then reloads them in the reverse order with a delay between
> bt and wifi modules loading, then checks for error messages. Over 200
> test cycles with a 10s delay all were clean (no errors). I also ran
> cycles with a 2 second delay and 0 second delay before starting wifi
> module load and those were clear too. I guess that proves sequencing
> avoids the efuse contention issue? - although it’s not possible in
> the real-world so not sure there’s huge value in knowing that :)
Thanks for the experiments.
Still want to know is it possible to change sequence/time of loading
kernel modules at boot time from system level? I mean can you adjust
the sequence in the Rock 5B board?
In addition, did below messages not appear in these experiments?
[ 7.864148] rtw89_8852be 0002:21:00.0: fw security fail
[ 7.864154] rtw89_8852be 0002:21:00.0: download firmware fail
Ping-Ke
^ permalink raw reply
* RE: [PATCH v3 19/21] wifi: rtl8xxxu: add hw crypto support for AP mode
From: Ping-Ke Shih @ 2026-03-11 2:38 UTC (permalink / raw)
To: Bitterblue Smith, Martin Kaistra, linux-wireless@vger.kernel.org
Cc: Jes Sorensen, Sebastian Andrzej Siewior
In-Reply-To: <61d5c08b-d924-42ad-8a56-ba3ddb970297@gmail.com>
Bitterblue Smith <rtl8821cerfe2@gmail.com> wrote:
> On 25/02/2026 04:28, Ping-Ke Shih wrote:
> > Martin Kaistra <martin.kaistra@linutronix.de> wrote:
> >> Am 21.02.26 um 23:09 schrieb Bitterblue Smith:
> >>> On 22/12/2023 12:14, Martin Kaistra wrote:
> >>>> Add a custom function for allocating entries in the sec cam. This allows
> >>>> us to store multiple keys with the same keyidx.
> >>>>
> >>>> The maximum number of sec cam entries for 8188f is 16 according to the
> >>>> vendor driver. Add the number to rtl8xxxu_fileops, so that other chips
> >>>> which might support more entries, can set a different number there.
> >>>>
> >>>> Set the bssid as mac address for group keys instead of just using the
> >>>> ethernet broadcast address and use BIT(6) in the sec cam ctrl entry
> >>>> for differentiating them from pairwise keys like in the vendor driver.
> >>>>
> >>>> Add the TXDESC_EN_DESC_ID bit and the hw_key_idx to tx
> >>>> broadcast/multicast packets in AP mode.
> >>>>
> >>>> Finally, allow the usage of rtl8xxxu_set_key() for AP mode.
> >>>>
> >>>> Signed-off-by: Martin Kaistra <martin.kaistra@linutronix.de>
> >>>> Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>
> >>>> ---
> >>>> .../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 5 ++
> >>>> .../realtek/rtl8xxxu/rtl8xxxu_8188f.c | 1 +
> >>>> .../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 48 +++++++++++++++----
> >>>> 3 files changed, 44 insertions(+), 10 deletions(-)
> >>>>
> >>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>>> index c5e6d8f7d26bd..62e6318bc0924 100644
> >>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>>> @@ -498,6 +498,7 @@ struct rtl8xxxu_txdesc40 {
> >>>> #define DESC_RATE_ID_SHIFT 16
> >>>> #define DESC_RATE_ID_MASK 0xf
> >>>> #define TXDESC_NAVUSEHDR BIT(20)
> >>>> +#define TXDESC_EN_DESC_ID BIT(21)
> >>>> #define TXDESC_SEC_RC4 0x00400000
> >>>> #define TXDESC_SEC_AES 0x00c00000
> >>>> #define TXDESC_PKT_OFFSET_SHIFT 26
> >>>> @@ -1775,6 +1776,7 @@ struct rtl8xxxu_cfo_tracking {
> >>>> #define RTL8XXXU_MAX_MAC_ID_NUM 128
> >>>> #define RTL8XXXU_BC_MC_MACID 0
> >>>> #define RTL8XXXU_BC_MC_MACID1 1
> >>>> +#define RTL8XXXU_MAX_SEC_CAM_NUM 64
> >>>>
> >>>> struct rtl8xxxu_priv {
> >>>> struct ieee80211_hw *hw;
> >>>> @@ -1908,6 +1910,7 @@ struct rtl8xxxu_priv {
> >>>> char led_name[32];
> >>>> struct led_classdev led_cdev;
> >>>> DECLARE_BITMAP(mac_id_map, RTL8XXXU_MAX_MAC_ID_NUM);
> >>>> + DECLARE_BITMAP(cam_map, RTL8XXXU_MAX_SEC_CAM_NUM);
> >>>> };
> >>>>
> >>>> struct rtl8xxxu_sta_info {
> >>>> @@ -1919,6 +1922,7 @@ struct rtl8xxxu_sta_info {
> >>>>
> >>>> struct rtl8xxxu_vif {
> >>>> int port_num;
> >>>> + u8 hw_key_idx;
> >>>> };
> >>>>
> >>>> struct rtl8xxxu_rx_urb {
> >>>> @@ -1993,6 +1997,7 @@ struct rtl8xxxu_fileops {
> >>>> u16 max_aggr_num;
> >>>> u8 supports_ap:1;
> >>>> u16 max_macid_num;
> >>>> + u16 max_sec_cam_num;
> >>>> u32 adda_1t_init;
> >>>> u32 adda_1t_path_on;
> >>>> u32 adda_2t_path_on_a;
> >>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
> >> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
> >>>> index 1e1c8fa194cb8..574a5fe951543 100644
> >>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
> >>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
> >>>> @@ -1751,6 +1751,7 @@ struct rtl8xxxu_fileops rtl8188fu_fops = {
> >>>> .max_aggr_num = 0x0c14,
> >>>> .supports_ap = 1,
> >>>> .max_macid_num = 16,
> >>>> + .max_sec_cam_num = 16,
> >>>> .adda_1t_init = 0x03c00014,
> >>>> .adda_1t_path_on = 0x03c00014,
> >>>> .trxff_boundary = 0x3f7f,
> >>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
> >> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
> >>>> index ecf54eb8dba61..7aafae9fe76b8 100644
> >>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
> >>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
> >>>> @@ -4559,8 +4559,10 @@ static void rtl8xxxu_cam_write(struct rtl8xxxu_priv
> *priv,
> >>>> * This is a bit of a hack - the lower bits of the cipher
> >>>> * suite selector happens to match the cipher index in the CAM
> >>>> */
> >>>> - addr = key->keyidx << CAM_CMD_KEY_SHIFT;
> >>>> + addr = key->hw_key_idx << CAM_CMD_KEY_SHIFT;
> >>>> ctrl = (key->cipher & 0x0f) << 2 | key->keyidx | CAM_WRITE_VALID;
> >>>> + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
> >>>> + ctrl |= BIT(6);
> >>>>
> >>>> for (j = 5; j >= 0; j--) {
> >>>> switch (j) {
> >>>> @@ -5546,13 +5548,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
> >>>> struct rtl8xxxu_tx_urb *tx_urb;
> >>>> struct ieee80211_sta *sta = NULL;
> >>>> struct ieee80211_vif *vif = tx_info->control.vif;
> >>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
> >>>> struct device *dev = &priv->udev->dev;
> >>>> u32 queue, rts_rate;
> >>>> u16 pktlen = skb->len;
> >>>> int tx_desc_size = priv->fops->tx_desc_size;
> >>>> u8 macid;
> >>>> int ret;
> >>>> - bool ampdu_enable, sgi = false, short_preamble = false;
> >>>> + bool ampdu_enable, sgi = false, short_preamble = false, bmc = false;
> >>>>
> >>>> if (skb_headroom(skb) < tx_desc_size) {
> >>>> dev_warn(dev,
> >>>> @@ -5594,10 +5597,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
> >>>> tx_desc->txdw0 =
> >>>> TXDESC_OWN | TXDESC_FIRST_SEGMENT |
> TXDESC_LAST_SEGMENT;
> >>>> if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) ||
> >>>> - is_broadcast_ether_addr(ieee80211_get_DA(hdr)))
> >>>> + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) {
> >>>> tx_desc->txdw0 |= TXDESC_BROADMULTICAST;
> >>>> + bmc = true;
> >>>> + }
> >>>> +
> >>>>
> >>>> tx_desc->txdw1 = cpu_to_le32(queue << TXDESC_QUEUE_SHIFT);
> >>>> + macid = rtl8xxxu_get_macid(priv, sta);
> >>>>
> >>>> if (tx_info->control.hw_key) {
> >>>> switch (tx_info->control.hw_key->cipher) {
> >>>> @@ -5612,6 +5619,10 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
> >>>> default:
> >>>> break;
> >>>> }
> >>>> + if (bmc && rtlvif->hw_key_idx != 0xff) {
> >>>> + tx_desc->txdw1 |= TXDESC_EN_DESC_ID;
> >>>> + macid = rtlvif->hw_key_idx;
> >>>> + }
> >>>> }
> >>>>
> >>>> /* (tx_info->flags & IEEE80211_TX_CTL_AMPDU) && */
> >>>> @@ -5655,7 +5666,6 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
> >>>> else
> >>>> rts_rate = 0;
> >>>>
> >>>> - macid = rtl8xxxu_get_macid(priv, sta);
> >>>> priv->fops->fill_txdesc(hw, hdr, tx_info, tx_desc, sgi,
> short_preamble,
> >>>> ampdu_enable, rts_rate, macid);
> >>>>
> >>>> @@ -6667,6 +6677,7 @@ static int rtl8xxxu_add_interface(struct ieee80211_hw
> *hw,
> >>>>
> >>>> priv->vifs[port_num] = vif;
> >>>> rtlvif->port_num = port_num;
> >>>> + rtlvif->hw_key_idx = 0xff;
> >>>>
> >>>> rtl8xxxu_set_linktype(priv, vif->type, port_num);
> >>>> ether_addr_copy(priv->mac_addr, vif->addr);
> >>>> @@ -6843,11 +6854,19 @@ static int rtl8xxxu_set_rts_threshold(struct
> ieee80211_hw *hw, u32 rts)
> >>>> return 0;
> >>>> }
> >>>>
> >>>> +static int rtl8xxxu_get_free_sec_cam(struct ieee80211_hw *hw)
> >>>> +{
> >>>> + struct rtl8xxxu_priv *priv = hw->priv;
> >>>> +
> >>>> + return find_first_zero_bit(priv->cam_map,
> priv->fops->max_sec_cam_num);
> >>>> +}
> >>>> +
> >>>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd
> cmd,
> >>>> struct ieee80211_vif *vif,
> >>>> struct ieee80211_sta *sta,
> >>>> struct ieee80211_key_conf *key)
> >>>> {
> >>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
> >>>> struct rtl8xxxu_priv *priv = hw->priv;
> >>>> struct device *dev = &priv->udev->dev;
> >>>> u8 mac_addr[ETH_ALEN];
> >>>> @@ -6859,9 +6878,6 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw,
> enum set_key_cmd cmd,
> >>>> dev_dbg(dev, "%s: cmd %02x, cipher %08x, index %i\n",
> >>>> __func__, cmd, key->cipher, key->keyidx);
> >>>>
> >>>> - if (vif->type != NL80211_IFTYPE_STATION)
> >>>> - return -EOPNOTSUPP;
> >>>> -
> >>>> if (key->keyidx > 3)
> >>>> return -EOPNOTSUPP;
> >>>>
> >>>> @@ -6885,7 +6901,7 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw,
> enum set_key_cmd cmd,
> >>>> ether_addr_copy(mac_addr, sta->addr);
> >>>> } else {
> >>>> dev_dbg(dev, "%s: group key\n", __func__);
> >>>> - eth_broadcast_addr(mac_addr);
> >>>> + ether_addr_copy(mac_addr, vif->bss_conf.bssid);
> >>>> }
> >>>>
> >>>> val16 = rtl8xxxu_read16(priv, REG_CR);
> >>>> @@ -6899,16 +6915,28 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw,
> enum set_key_cmd cmd,
> >>>>
> >>>> switch (cmd) {
> >>>> case SET_KEY:
> >>>> - key->hw_key_idx = key->keyidx;
> >>>> +
> >>>> + retval = rtl8xxxu_get_free_sec_cam(hw);
> >>>> + if (retval < 0)
> >>>> + return -EOPNOTSUPP;
> >>>> +
> >>>> + key->hw_key_idx = retval;
> >>>> +
> >>>> + if (vif->type == NL80211_IFTYPE_AP && !(key->flags &
> IEEE80211_KEY_FLAG_PAIRWISE))
> >>>> + rtlvif->hw_key_idx = key->hw_key_idx;
> >>>> +
> >>>> key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
> >>>> rtl8xxxu_cam_write(priv, key, mac_addr);
> >>>> + set_bit(key->hw_key_idx, priv->cam_map);
> >>>
> >>> Hi Martin,
> >>
> >> Hi Bitterblue,
> >>
> >>>
> >>> It turns out RTL8188CUS and RTL8192CU don't like this patch, specifically
> >>> when we use iwd. After the WPA2 handshake no more data is transmitted.
> >>>
> >>> Before this patch, key->hw_key_idx was the same as key->keyidx. After
> >>> this patch, when we use iwd, the group key is installed first. It has
> >>> key->keyidx = 1, but it gets key->hw_key_idx = 0. The pairwise key is
> >>> installed second. It has key->keyidx = 0, but it gets key->hw_key_idx = 1.
> >>> Both keyidx and hw_key_idx are passed to the chip in rtl8xxxu_cam_write().
> >>>
> >>> It's fine with wpa_supplicant. wpa_supplicant installs the pairwise key
> >>> first, with key->keyidx = 0, then the group key, with key->keyidx = 1.
> >>>
> >>> This patch imitating rtw88 makes the old chips work again with iwd.
> >>> What do you think?
> >>
> >> So you reserve the first 4 entries for group keys and use key->keyidx as
> >> key->hw_key_idx directly for those, right? Does that work if 2 virtual
> >> interfaces are used at the same time?
> >> I will do some tests in the next days, but I suspect this be an issue.
> >
> > Ah. I'm not aware that rtl8xxxu can support 2 virtual interfaces, and
> > only RTL8188FU declares .supports_concurrent = 1, so maybe we can have
> > special handling for this chip?
>
> I would like to enable supports_concurrent for more chips, if I can test
> this feature.
>
> >
> > I'd share some information about security CAM for reference. Since data is
> > quite old (10+ years), I can't guarantee all are correct.
> >
> > 1. default key switch in rtl8xxxu_set_key()
> > SEC_CFG_TXBC_USE_DEFKEY and SEC_CFG_RXBC_USE_DEFKEY mean that
> > broadcast/multicast packets use default key (CAM entries 0~3 for
> > keyidx 0~3).
> > For two interfaces case, that'd a problem, so maybe we should use
> > software encryption/decryption for non-pairwise key.
> >
> > val8 = SEC_CFG_TX_SEC_ENABLE | SEC_CFG_TXBC_USE_DEFKEY |
> > SEC_CFG_RX_SEC_ENABLE | SEC_CFG_RXBC_USE_DEFKEY;
> > val8 |= SEC_CFG_TX_USE_DEFKEY | SEC_CFG_RX_USE_DEFKEY;
> > rtl8xxxu_write8(priv, REG_SECURITY_CFG, val8);
> >
> > 2. group key (GK) field of security CAM in rtl8xxxu_cam_write()
> > The group key field of security CAM is BIT(6) which isn't supported
> > by earlier chips (sorry I have no data about the exact chips).
> >
>
> BIT 6 is used in the rtl8188eu driver, and not used in the rtl8192cu driver,
> so I assume it's just the older RTL8188CU/RTL8192CU and RTL8723AU that don't
> support it.
>
> > If a chip can support group key field, the CAM layout with default key
> > enabled for two interfaces (STA-1+AP-1) can be
> >
> > STA-1 <-> AP-10
> > AP-1 <-> STA-20
> >
> > CAM GK MAC KEY
> > 0
> > 1 1 00: AP-1 (GTK) // should be on entry 0~3 depends on key_idx
> > 2
> > 3
> > 4
> > 5 1 AP-10 AP-10 (GTK) // can be on any entry after 4
> > 6
> >
>
> What are the numbers 1, 10, and 20 in "AP-1", "AP-10", "STA-20" ?
Just represent different roles.
For rtl8xxxu, two interfaces are STA-1 and AP-1, and STA-1 connect to AP-10,
and AP-1 is connected from STA-20.
I don't add pairwise key to above example, because it is simple, just one
entry to a PTK. Instead, I only list group key in the example.
For AP role (AP-1), it can send broadcast packets to many stations, so
use default key.
(but it should not receive a broadcast packet from stations)
For station role (STA-1), it should receive broadcast packets from
specific AP-10, so add an entry with AP-10's MAC address.
>
> > (pairwise key can be on any entry after 4)
> >
> > if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
> > ctrl |= BIT(6);
> >
> > My perspective are
> > 1. currently default key is enabled, so we should treat CAM entry 0~3 as
> > special cases. That means rtl8xxxu_get_free_sec_cam() should be modified
> > as Bitterblue's version.
> >
> > 2. For two interfaces, I guess RTL8188FU can support GK bit, so it'd be worth
> > to try the method of example 2 above.
> >
> > If we want earlier chips can support two interfaces, I think we should
> > disable default key.
>
> Indeed, the rtl8192cu driver (out of kernel) doesn't set SEC_CFG_TXBC_USE_DEFKEY
> and SEC_CFG_RXBC_USE_DEFKEY when concurrent mode is enabled.
>
> > Seemingly, it is not possible that using hardware
> > crypto when operating one interface and then switching to use software>
> crypto when operating two interfaces. That means broadcast/multicast
> > packets should be by software crypto entirely if we want to support two
> > interfaces.
> >
> > 3. I think rtw88 has similar problem for two interfaces.
> >
> >>
> >>>
> >>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c
> >> b/drivers/net/wireless/realtek/rtl8xxxu/core.c
> >>> index ee278f0548e4..f7b35655bec5 100644
> >>> --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c
> >>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c
> >>> @@ -6942,7 +6942,8 @@ static int rtl8xxxu_get_free_sec_cam(struct
> ieee80211_hw *hw)
> >>> {
> >>> struct rtl8xxxu_priv *priv = hw->priv;
> >>>
> >>> - return find_first_zero_bit(priv->cam_map,
> priv->fops->max_sec_cam_num);
> >>> + return find_next_zero_bit(priv->cam_map,
> priv->fops->max_sec_cam_num,
> >>> + RTL8XXXU_SEC_DEFAULT_KEY_NUM);
> >>> }
> >>>
> >>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd
> cmd,
> >>> @@ -6999,12 +7000,15 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw,
> enum set_key_cmd cmd,
> >>>
> >>> switch (cmd) {
> >>> case SET_KEY:
> >>> + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
> >>> + retval = rtl8xxxu_get_free_sec_cam(hw);
> >>> + if (retval < 0)
> >>> + return -EOPNOTSUPP;
> >>>
> >>> - retval = rtl8xxxu_get_free_sec_cam(hw);
> >>> - if (retval < 0)
> >>> - return -EOPNOTSUPP;
> >>> -
> >>> - key->hw_key_idx = retval;
> >>> + key->hw_key_idx = retval;
> >>> + } else {
> >>> + key->hw_key_idx = key->keyidx;
> >>> + }
> >>>
> >>> if (vif->type == NL80211_IFTYPE_AP && !(key->flags &
> IEEE80211_KEY_FLAG_PAIRWISE))
> >>> rtlvif->hw_key_idx = key->hw_key_idx;
> >>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>> index 4b05dba22e67..188f4bbe99cd 100644
> >>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
> >>> @@ -1788,6 +1788,7 @@ struct rtl8xxxu_cfo_tracking {
> >>> #define RTL8XXXU_BC_MC_MACID 0
> >>> #define RTL8XXXU_BC_MC_MACID1 1
> >>> #define RTL8XXXU_MAX_SEC_CAM_NUM 64
> >>> +#define RTL8XXXU_SEC_DEFAULT_KEY_NUM 4
> >>>
> >>> struct rtl8xxxu_priv {
> >>> struct ieee80211_hw *hw;
> >
^ permalink raw reply
* RE: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Ping-Ke Shih @ 2026-03-11 2:22 UTC (permalink / raw)
To: Ping-Ke Shih, LB F
Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <792645eed36041f0b3df951f1b28a08a@realtek.com>
Ping-Ke Shih <pkshih@realtek.com> wrote:
>
> LB F <goainwo@gmail.com> wrote:
> >
> > Hi Ping-Ke,
> >
> > Thank you for the incredibly fast response and assistance!
> >
> > > Can you dig kernel log (by netconsole or ramoops) if something useful?
> > > I'd like to know this is hardware level freeze or kernel can capture something
> > wrong.
> >
> > I managed to pull a call trace from a historic journald log just
> > before the system hung. The kernel gets trapped in an IRQ thread
> > inside `rtw_pci_interrupt_threadfn`, calling up into `mac80211`
> > `ieee80211_rx_list` before everything freezes. Here is the relevant
> > snippet:
> >
> > ```text
> > Call Trace:
> > <IRQ>
> > ? __alloc_skb+0x23a/0x2a0
> > ? __alloc_skb+0x10c/0x2a0
> > ? __pfx_irq_thread_fn+0x10/0x10
> > [ ... truncated module list ... ]
> > Tainted: G W I 6.19.6-2-cachyos #1 PREEMPT(full)
> > Hardware name: HP HP Notebook/81F0, BIOS F.50 11/20/2020
> > RIP: 0010:ieee80211_rx_list+0x1012/0x1020 [mac80211]
> > CPU: 2 UID: 0 PID: 765 Comm: irq/56-rtw88_pc
> > rtw_pci_interrupt_threadfn+0x239/0x310 [rtw88_pci]
> > ```
> >
> > It behaves exactly like a PCIe bus deadlock or a hardware fault that
> > eventually brings down the CPU handling the IRQ.
>
> I wonder if there is a malformed data, causing this trace and the leads
> kernel freezes. If we can do validation on RX data before calling
> ieee80211_rx_list(), maybe trace disappears and everything will be fine?
> Even no need workaround.
>
> >
> > > Are these totally needed to workaround the problem? Or disable_aspm is enough?
> > > I'd list them in order of power consumption impact:
> > > 1. disable_aspm=y
> > > 2. disable_lps_deep=y
> > > 3. disable WiFi power save
> >
> > To verify which parameters are strictly necessary, I performed
> > isolated testing today. I ensured no other modprobe configs were
> > active, rebuilt the initramfs, and manually enforced that
> > `wifi.powersave` was active via `iw dev wlan0 set power_save on`
> > during all tests (as the OS power management profiles were defaulting
> > it to off, which initially masked the issue).
> >
> > I tested each workaround individually across multiple sleep/wake
> > cycles and active usage:
> >
> > **Test 1 (ASPM Disabled, LPS Deep Enabled):**
> > - Kernel parameters: `rtw88_pci disable_aspm=y` (and `rtw88_core
> > disable_lps_deep=n`)
> > - Result: Stable. No freezes were observed during usage or transitions
> > into/out of S3 sleep while power saving was enforced.
> >
> > **Test 2 (ASPM Enabled, LPS Deep Disabled):**
> > - Kernel parameters: `rtw88_core disable_lps_deep=y` (and `rtw88_pci
> > disable_aspm=n`)
> > - Result: Stable. No freezes were observed under the same forced power
> > save conditions.
> >
> > **Conclusion:** It appears we do not need both workarounds
> > simultaneously for this specific hardware. Using only `disable_aspm=y`
> > seems to be sufficient to prevent the system freeze. Given your note
> > about the power consumption impact ranking, this looks like the
> > optimal path forward.
>
> Let's test my RFT patch to disable ASPM then.
>
> >
> > > But what does 'deadlock' mean? As I know NAPI poll is scheduled by ISR,
> > > and going to receive packets. The rx_no_aspm workaround is to forcely turn
> > > off ASPM during this period.
> >
> > By "deadlock" I meant a hardware-level bus lockup. It seems the
> > physical RTL8821CE chip itself crashes or hangs the system's PCIe bus
> > when trying to negotiate waking up from ASPM L1 while simultaneously
> > existing in `LPS_DEEP_MODE_LCLK`. The `rx_no_aspm` workaround in NAPI
> > helps during active Rx decoding, but the laptop often freezes while
> > completely idle, presumably when the AP sends a basic beacon, the chip
> > attempts to leave LPS Deep + L1, and the hardware simply gives up and
> > halts the system.
>
> I think this is your perspective and induction, right? Did you measure
> real hardware signals?
>
> My point is that if this is a hardware-level bus lockup, let's apply
> quirk. If some malformed data causing kernel hangs, I'd add sanity check
> on RX data, but I don't actually know what we should check for now.
>
> >
> > > We have not modified RTL8821CE for a long time, so I'd add workaround
> > > to specific platform as mentioned above.
> >
> > Adding a DMI/platform quirk specifically for this laptop to disable
> > ASPM would be wonderful and deeply appreciated. I agree it is safer
> > than touching the global flags for hardware that is functioning
> > correctly out in the wild.
> >
> > Here is the exact identifying information for my system:
> >
> > System Vendor: HP
> > Product Name: HP Notebook
> > SKU Number: P3S95EA#ACB
> > Family: 103C_5335KV
> > PCI ID: 10ec:c821
> > Subsystem ID: 103c:831a
> >
> > I am completely ready to test any patch or quirk you send my way.
> > Thank you so much for your time and helping track this down!
>
> I sent a RFT [1] for test. Please check if it works on your HP notebook.
> If you check rtw88 log, you can see I added similar patch 5 years ago,
> and replaced by preferred the change of "rtwpci->rx_no_aspm", which I
> think it can only resolve problem on partial notebooks though....
>
> [1]
> https://lore.kernel.org/linux-wireless/20260311020816.7065-1-pkshih@realtek.
> com/T/#u
Forgot to say. Could you share your full name for me as a reporter
in commit message?
^ permalink raw reply
* RE: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Ping-Ke Shih @ 2026-03-11 2:20 UTC (permalink / raw)
To: LB F; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CALdGYqSz3SNzoSjUQvK6FgTc2Xkac52=T5A7Lt=d+nxAXGgJVw@mail.gmail.com>
LB F <goainwo@gmail.com> wrote:
>
> Hi Ping-Ke,
>
> Thank you for your guidance. To provide you with the cleanest possible
> diagnostic data, we devised a strict testing environment:
>
> 1. **Live USB Environment:** We booted a completely fresh Live USB of
> CachyOS (Kernel 6.19.6) to eliminate any potential interference from
> installed software, TLP profiles, or custom NetworkManager
> configurations.
> 2. **Aggressive Local Logging:** Because the system freeze physically
> locks the PCIe bus and disables the Wi-Fi adapter instantly, using
> `netconsole` was impossible (the network drops microseconds before the
> freeze).
>
> To overcome this, we wrote an "aggressive logger" script that pipes
> `dmesg -w` directly to an independent FAT32 USB drive while issuing a
> `sync` command twice a second. This bypassed RAM caching and
> physically burned the logs to the drive right up to the moment of the
> hard freeze. The script we used was:
>
> ```bash
> #!/bin/bash
> LOG_FILE="/run/media/liveuser/LOGS/kernel_freeze.log"
> dmesg -w > "$LOG_FILE" &
> while true; do
> sync
> sleep 0.5
> done
> ```
>
> 3. No workarounds (`disable_aspm=n`, `disable_lps_deep=n`) were active
> in this test. We manually enabled power saving (`iw dev wlan0 set
> power_save on`) and triggered the freeze via typical web browsing.
>
> Here are the precise, unadulterated logs showing the adapter
> successfully connecting to the network, sitting idle for about 10
> seconds (presumably entering power-saving states), and then suffering
> a fatal firmware lockup right before the PCIe bus froze:
>
> ```
> [ 304.709201] audit: type=1111 ... op=connection-add-activate ...
> name="Andrey_5G" ...
> [ 305.617785] wlan0: authenticate with 6c:68:a4:1c:97:5b ...
> [ 305.660333] wlan0: authenticated
> [ 305.661661] wlan0: associate with 6c:68:a4:1c:97:5b (try 1/3)
> [ 305.663404] wlan0: associated
> [ 305.719997] wlan0: Limiting TX power to 30 (30 - 0) dBm as
> advertised by 6c:68:a4:1c:97:5b
> ... (~10 seconds of idle network time) ...
> [ 316.907114] rtw88_8821ce 0000:13:00.0: failed to send h2c command
> [ 316.911190] rtw88_8821ce 0000:13:00.0: failed to send h2c command
> [ 316.921504] rtw88_8821ce 0000:13:00.0: coex request time out
> ...
> [ 349.630952] rtw88_8821ce 0000:13:00.0: failed to send h2c command
> [ 349.635023] rtw88_8821ce 0000:13:00.0: failed to send h2c command
> [ 357.811235] rtw88_8821ce 0000:13:00.0: firmware failed to leave lps state
> [ 359.797238] rtw88_8821ce 0000:13:00.0: firmware failed to leave lps state
> ... (repeats indefinitely until hard reset) ...
> ```
Just want to clarify that these logs only appear in test 3, right?
No these logs in test 1/2.
>
> As the logs clearly demonstrate, the adapter authenticates perfectly
> but the firmware explicitly fails to leave the LPS state after a brief
> idle period, dropping all H2C commands immediately before the
> system-wide hard freeze begins.
>
> We will upload the full, unabridged `.log` file to our Bugzilla thread
> (Bug 221195) momentarily, but we wanted to provide you with this exact
> 'smoking gun' trace right away to help identify the root cause.
>
> Please let us know if this information is helpful or if there are any
> specific module patches or further tests you would like us to perform
> to assist with debugging.
Thanks for your detail tests and logs. With this kind of hardware problem,
to dig the cause, we need real hardware and hardware scope to measure
signals. I'd apply quirk or some validations on RX path. That'd be a
better way.
Ping-Ke
^ permalink raw reply
* RE: [BUG] wifi: rtw88: Hard system freeze on RTL8821CE when power_save is enabled (LPS/ASPM conflict)
From: Ping-Ke Shih @ 2026-03-11 2:15 UTC (permalink / raw)
To: LB F; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <CALdGYqQb=Vt0jjqW7k8RGMV1gczL0cg-26cHgCm3MmzBjezGMQ@mail.gmail.com>
LB F <goainwo@gmail.com> wrote:
>
> Hi Ping-Ke,
>
> Thank you for the incredibly fast response and assistance!
>
> > Can you dig kernel log (by netconsole or ramoops) if something useful?
> > I'd like to know this is hardware level freeze or kernel can capture something
> wrong.
>
> I managed to pull a call trace from a historic journald log just
> before the system hung. The kernel gets trapped in an IRQ thread
> inside `rtw_pci_interrupt_threadfn`, calling up into `mac80211`
> `ieee80211_rx_list` before everything freezes. Here is the relevant
> snippet:
>
> ```text
> Call Trace:
> <IRQ>
> ? __alloc_skb+0x23a/0x2a0
> ? __alloc_skb+0x10c/0x2a0
> ? __pfx_irq_thread_fn+0x10/0x10
> [ ... truncated module list ... ]
> Tainted: G W I 6.19.6-2-cachyos #1 PREEMPT(full)
> Hardware name: HP HP Notebook/81F0, BIOS F.50 11/20/2020
> RIP: 0010:ieee80211_rx_list+0x1012/0x1020 [mac80211]
> CPU: 2 UID: 0 PID: 765 Comm: irq/56-rtw88_pc
> rtw_pci_interrupt_threadfn+0x239/0x310 [rtw88_pci]
> ```
>
> It behaves exactly like a PCIe bus deadlock or a hardware fault that
> eventually brings down the CPU handling the IRQ.
I wonder if there is a malformed data, causing this trace and the leads
kernel freezes. If we can do validation on RX data before calling
ieee80211_rx_list(), maybe trace disappears and everything will be fine?
Even no need workaround.
>
> > Are these totally needed to workaround the problem? Or disable_aspm is enough?
> > I'd list them in order of power consumption impact:
> > 1. disable_aspm=y
> > 2. disable_lps_deep=y
> > 3. disable WiFi power save
>
> To verify which parameters are strictly necessary, I performed
> isolated testing today. I ensured no other modprobe configs were
> active, rebuilt the initramfs, and manually enforced that
> `wifi.powersave` was active via `iw dev wlan0 set power_save on`
> during all tests (as the OS power management profiles were defaulting
> it to off, which initially masked the issue).
>
> I tested each workaround individually across multiple sleep/wake
> cycles and active usage:
>
> **Test 1 (ASPM Disabled, LPS Deep Enabled):**
> - Kernel parameters: `rtw88_pci disable_aspm=y` (and `rtw88_core
> disable_lps_deep=n`)
> - Result: Stable. No freezes were observed during usage or transitions
> into/out of S3 sleep while power saving was enforced.
>
> **Test 2 (ASPM Enabled, LPS Deep Disabled):**
> - Kernel parameters: `rtw88_core disable_lps_deep=y` (and `rtw88_pci
> disable_aspm=n`)
> - Result: Stable. No freezes were observed under the same forced power
> save conditions.
>
> **Conclusion:** It appears we do not need both workarounds
> simultaneously for this specific hardware. Using only `disable_aspm=y`
> seems to be sufficient to prevent the system freeze. Given your note
> about the power consumption impact ranking, this looks like the
> optimal path forward.
Let's test my RFT patch to disable ASPM then.
>
> > But what does 'deadlock' mean? As I know NAPI poll is scheduled by ISR,
> > and going to receive packets. The rx_no_aspm workaround is to forcely turn
> > off ASPM during this period.
>
> By "deadlock" I meant a hardware-level bus lockup. It seems the
> physical RTL8821CE chip itself crashes or hangs the system's PCIe bus
> when trying to negotiate waking up from ASPM L1 while simultaneously
> existing in `LPS_DEEP_MODE_LCLK`. The `rx_no_aspm` workaround in NAPI
> helps during active Rx decoding, but the laptop often freezes while
> completely idle, presumably when the AP sends a basic beacon, the chip
> attempts to leave LPS Deep + L1, and the hardware simply gives up and
> halts the system.
I think this is your perspective and induction, right? Did you measure
real hardware signals?
My point is that if this is a hardware-level bus lockup, let's apply
quirk. If some malformed data causing kernel hangs, I'd add sanity check
on RX data, but I don't actually know what we should check for now.
>
> > We have not modified RTL8821CE for a long time, so I'd add workaround
> > to specific platform as mentioned above.
>
> Adding a DMI/platform quirk specifically for this laptop to disable
> ASPM would be wonderful and deeply appreciated. I agree it is safer
> than touching the global flags for hardware that is functioning
> correctly out in the wild.
>
> Here is the exact identifying information for my system:
>
> System Vendor: HP
> Product Name: HP Notebook
> SKU Number: P3S95EA#ACB
> Family: 103C_5335KV
> PCI ID: 10ec:c821
> Subsystem ID: 103c:831a
>
> I am completely ready to test any patch or quirk you send my way.
> Thank you so much for your time and helping track this down!
I sent a RFT [1] for test. Please check if it works on your HP notebook.
If you check rtw88 log, you can see I added similar patch 5 years ago,
and replaced by preferred the change of "rtwpci->rx_no_aspm", which I
think it can only resolve problem on partial notebooks though....
[1] https://lore.kernel.org/linux-wireless/20260311020816.7065-1-pkshih@realtek.com/T/#u
Ping-Ke
^ permalink raw reply
* [RFT rtw-next] wifi: rtw88: add quirks to disable PCI ASPM for HP P3S95EA#ACB
From: Ping-Ke Shih @ 2026-03-11 2:08 UTC (permalink / raw)
To: linux-wireless; +Cc: goainwo
On an HP laptop (P3S95EA#ACB) equipped with a Realtek RTL8821CE 802.11ac
PCIe adapter (PCI ID: 10ec:c821), the system experiences a hard lockup
(complete freeze of the UI and kernel, sysrq doesn't work, requires
holding the power button) when the WiFi adapter enters the power
saving state.
Add a quirk to disable ASPM.
Reported-by: LB F <goainwo@gmail.com>
Closes: https://lore.kernel.org/linux-wireless/CALdGYqSQ1Ko2TTBhUizMu_FvLMUAuQfFrVwS10n_C-LSQJQQkQ@mail.gmail.com/
Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
drivers/net/wireless/realtek/rtw88/pci.c | 30 ++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c
index ec0a45bfb670..0b60a0ce96a1 100644
--- a/drivers/net/wireless/realtek/rtw88/pci.c
+++ b/drivers/net/wireless/realtek/rtw88/pci.c
@@ -2,6 +2,7 @@
/* Copyright(c) 2018-2019 Realtek Corporation
*/
+#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include "main.h"
@@ -1744,6 +1745,34 @@ const struct pci_error_handlers rtw_pci_err_handler = {
};
EXPORT_SYMBOL(rtw_pci_err_handler);
+enum rtw88_quirk_dis_pci_caps {
+ QUIRK_DIS_PCI_CAP_ASPM,
+};
+
+static int disable_pci_caps(const struct dmi_system_id *dmi)
+{
+ uintptr_t dis_caps = (uintptr_t)dmi->driver_data;
+
+ if (dis_caps & BIT(QUIRK_DIS_PCI_CAP_ASPM))
+ rtw_pci_disable_aspm = true;
+
+ return 1;
+}
+
+static const struct dmi_system_id rtw88_pci_quirks[] = {
+ {
+ .callback = disable_pci_caps,
+ .ident = "HP Notebook - P3S95EA#ACB",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "HP"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "HP Notebook"),
+ DMI_MATCH(DMI_PRODUCT_SKU, "P3S95EA#ACB"),
+ },
+ .driver_data = (void *)BIT(QUIRK_DIS_PCI_CAP_ASPM),
+ },
+ {}
+};
+
int rtw_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
@@ -1808,6 +1837,7 @@ int rtw_pci_probe(struct pci_dev *pdev,
bridge && bridge->vendor == PCI_VENDOR_ID_INTEL)
rtwpci->rx_no_aspm = true;
+ dmi_check_system(rtw88_pci_quirks);
rtw_pci_phy_cfg(rtwdev);
ret = rtw_register_hw(rtwdev, hw);
--
2.25.1
^ permalink raw reply related
* Re: [PATCH 56/61] clk: Prefer IS_ERR_OR_NULL over manual NULL check
From: Chen-Yu Tsai @ 2026-03-11 2:07 UTC (permalink / raw)
To: Philipp Hahn
Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Michael Turquette, Stephen Boyd,
Daniel Lezcano, Thomas Gleixner
In-Reply-To: <20260310-b4-is_err_or_null-v1-56-bd63b656022d@avm.de>
On Tue, Mar 10, 2026 at 9:57 PM Philipp Hahn <phahn-oss@avm.de> wrote:
>
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Semantich change: Previously the code only printed the warning on error,
> but not when the pointer was NULL. Now the warning is printed in both
> cases!
>
> Change found with coccinelle.
>
> To: Michael Turquette <mturquette@baylibre.com>
> To: Stephen Boyd <sboyd@kernel.org>
> To: Daniel Lezcano <daniel.lezcano@kernel.org>
> To: Thomas Gleixner <tglx@kernel.org>
> Cc: linux-clk@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> ---
> drivers/clk/clk.c | 4 ++--
> drivers/clocksource/timer-pxa.c | 2 +-
> 2 files changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index 47093cda9df32223c1120c3710261296027c4cd3..35146e3869a7dd93741d10b7223d4488a9216ed1 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -4558,7 +4558,7 @@ void clk_unregister(struct clk *clk)
> unsigned long flags;
> const struct clk_ops *ops;
>
> - if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
> + if (WARN_ON_ONCE(IS_ERR_OR_NULL(clk)))
> return;
>
> clk_debug_unregister(clk->core);
> @@ -4744,7 +4744,7 @@ void __clk_put(struct clk *clk)
> {
> struct module *owner;
>
> - if (!clk || WARN_ON_ONCE(IS_ERR(clk)))
> + if (WARN_ON_ONCE(IS_ERR_OR_NULL(clk)))
clk_get_optional() returns NULL if the clk isn't present.
Drivers would just pass this to clk_put(). Your change here would cause
this pattern to emit a very big warning.
I don't think this change should be landed.
ChenYu
> return;
>
> clk_prepare_lock();
> diff --git a/drivers/clocksource/timer-pxa.c b/drivers/clocksource/timer-pxa.c
> index 7ad0e5adb2ffac4125c34710fc67f4b45f30331d..f65fb0b7fc318b766227e5e7a4c0fb08ba11c8f9 100644
> --- a/drivers/clocksource/timer-pxa.c
> +++ b/drivers/clocksource/timer-pxa.c
> @@ -218,7 +218,7 @@ void __init pxa_timer_nodt_init(int irq, void __iomem *base)
>
> timer_base = base;
> clk = clk_get(NULL, "OSTIMER0");
> - if (clk && !IS_ERR(clk)) {
> + if (!IS_ERR_OR_NULL(clk)) {
> clk_prepare_enable(clk);
> pxa_timer_common_init(irq, clk_get_rate(clk));
> } else {
>
> --
> 2.43.0
>
>
^ permalink raw reply
* [PATCH] wifi: b43: kzalloc + kcalloc to kzalloc_flex
From: Rosen Penev @ 2026-03-11 0:47 UTC (permalink / raw)
To: linux-wireless
Cc: Kees Cook, Gustavo A. R. Silva, open list:B43 WIRELESS DRIVER,
open list,
open list:KERNEL HARDENING (not covered by other areas):Keyword:b__counted_by(_le|_be)?b
Simplifies allocation and allows using __counted_by for extra runtime
analysis.
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
drivers/net/wireless/broadcom/b43/dma.c | 18 ++++++++----------
drivers/net/wireless/broadcom/b43/dma.h | 4 ++--
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/drivers/net/wireless/broadcom/b43/dma.c b/drivers/net/wireless/broadcom/b43/dma.c
index 3a8df7a18042..05da6987a845 100644
--- a/drivers/net/wireless/broadcom/b43/dma.c
+++ b/drivers/net/wireless/broadcom/b43/dma.c
@@ -837,18 +837,19 @@ struct b43_dmaring *b43_setup_dmaring(struct b43_wldev *dev,
struct b43_dmaring *ring;
int i, err;
dma_addr_t dma_test;
+ size_t nr_slots;
- ring = kzalloc_obj(*ring);
+ if (for_tx)
+ nr_slots = B43_TXRING_SLOTS;
+ else
+ nr_slots = B43_RXRING_SLOTS;
+
+ ring = kzalloc_flex(*ring, meta, nr_slots);
if (!ring)
goto out;
- ring->nr_slots = B43_RXRING_SLOTS;
- if (for_tx)
- ring->nr_slots = B43_TXRING_SLOTS;
+ ring->nr_slots = nr_slots;
- ring->meta = kzalloc_objs(struct b43_dmadesc_meta, ring->nr_slots);
- if (!ring->meta)
- goto err_kfree_ring;
for (i = 0; i < ring->nr_slots; i++)
ring->meta->skb = B43_DMA_PTR_POISON;
@@ -943,8 +944,6 @@ struct b43_dmaring *b43_setup_dmaring(struct b43_wldev *dev,
err_kfree_txhdr_cache:
kfree(ring->txhdr_cache);
err_kfree_meta:
- kfree(ring->meta);
- err_kfree_ring:
kfree(ring);
ring = NULL;
goto out;
@@ -1004,7 +1003,6 @@ static void b43_destroy_dmaring(struct b43_dmaring *ring,
free_ringmemory(ring);
kfree(ring->txhdr_cache);
- kfree(ring->meta);
kfree(ring);
}
diff --git a/drivers/net/wireless/broadcom/b43/dma.h b/drivers/net/wireless/broadcom/b43/dma.h
index c2a357219d4b..f9f65bbe2d76 100644
--- a/drivers/net/wireless/broadcom/b43/dma.h
+++ b/drivers/net/wireless/broadcom/b43/dma.h
@@ -228,8 +228,6 @@ struct b43_dmaring {
const struct b43_dma_ops *ops;
/* Kernel virtual base address of the ring memory. */
void *descbase;
- /* Meta data about all descriptors. */
- struct b43_dmadesc_meta *meta;
/* Cache of TX headers for each TX frame.
* This is to avoid an allocation on each TX.
* This is NULL for an RX ring.
@@ -273,6 +271,8 @@ struct b43_dmaring {
/* Statistics: Total number of TX plus all retries. */
u64 nr_total_packet_tries;
#endif /* CONFIG_B43_DEBUG */
+ /* Meta data about all descriptors. */
+ struct b43_dmadesc_meta meta[] __counted_by(nr_slots);
};
static inline u32 b43_dma_read(struct b43_dmaring *ring, u16 offset)
--
2.53.0
^ permalink raw reply related
* Re: [BUG] wifi: mt76: mt7925u: probe with driver mt7925u failed with error -110
From: Sean Wang @ 2026-03-11 0:47 UTC (permalink / raw)
To: Nick
Cc: linux-wireless, Felix Fietkau, Lorenzo Bianconi, Ryder Lee,
Shayne Chen, Sean Wang, Deren Wu, Leon Yen
In-Reply-To: <CAFktD2dbdccWFodfFNya_XnbKro-O+BKrs4cZRNa0uPrK6FFfQ@mail.gmail.com>
On Mon, Mar 9, 2026 at 7:31 PM Nick <morrownr@gmail.com> wrote:
>
> On Fri, Mar 6, 2026 at 6:46 PM Sean Wang <sean.wang@kernel.org> wrote:
> >
> > Hi Nick,
> >
> > Thanks for the detailed report this is very helpful. I don't have an
> > MT7925 USB device to detail look into the issue, but based on the
> > trace I wrote a small change to try a different MT7925 reset sequence.
> > please help test.
> >
> > --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
> > +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
> > @@ -234,6 +234,36 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume)
> > }
> > EXPORT_SYMBOL_GPL(mt792xu_dma_init);
> >
> > +static int mt7925u_wfsys_reset(struct mt792x_dev *dev)
> > +{
> > + u32 val;
> > + int i;
> > +
> > +#define MT7925_CBTOP_RGU_WF_SUBSYS_RST 0x70028600
> > +#define MT7925_WFSYS_SW_INIT_DONE_ADDR 0x184c1604
> > +#define MT7925_WFSYS_SW_INIT_DONE 0x00001d1e
> > +
> > + val = mt792xu_uhw_rr(&dev->mt76, MT7925_CBTOP_RGU_WF_SUBSYS_RST);
> > + val |= MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH;
> > + mt792xu_uhw_wr(&dev->mt76, MT7925_CBTOP_RGU_WF_SUBSYS_RST, val);
> > +
> > + msleep(20);
> > +
> > + val = mt792xu_uhw_rr(&dev->mt76, MT7925_CBTOP_RGU_WF_SUBSYS_RST);
> > + val &= ~MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH;
> > + mt792xu_uhw_wr(&dev->mt76, MT7925_CBTOP_RGU_WF_SUBSYS_RST, val);
> > +
> > + for (i = 0; i < MT792x_WFSYS_INIT_RETRY_COUNT; i++) {
> > + val = mt792xu_uhw_rr(&dev->mt76, MT7925_WFSYS_SW_INIT_DONE_ADDR);
> > + if (val == MT7925_WFSYS_SW_INIT_DONE)
> > + return 0;
> > +
> > + msleep(100);
> > + }
> > +
> > + return -ETIMEDOUT;
> > +}
> > +
> > int mt792xu_wfsys_reset(struct mt792x_dev *dev)
> > {
> > u32 val;
> > @@ -241,6 +271,9 @@ int mt792xu_wfsys_reset(struct mt792x_dev *dev)
> >
> > mt792xu_epctl_rst_opt(dev, false);
> >
> > + if (is_mt7925(&dev->mt76))
> > + return mt7925u_wfsys_reset(dev);
> > +
> > val = mt792xu_uhw_rr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST);
> >
> >
>
> Sean,
>
> Testing complete. Results are good. You can show the following:
>
> Tested-by: Nick Morrow <morrownr@gmail.com>
> Tested-by: Satadru Pramanik <satadru@gmail.com>
>
> Thank you for working on this issue. If it is possible for your patch to go
> directly in to be in one of the next rc's, that would be great and setting
> it to backport would also be much appreciated.
>
> Nick Morrow
>
Hi Nick,
Thanks for testing!
I’ve already sent the patches upstream with a few small cosmetic
tweaks. If you have a chance to test the latest version again, that
would be great. If it still looks good, you can just reply with the
Tested-by tags below. I think we can let Johannes know if it is okay
to wireless.git directly I also CC’d stable so the fix can make its
way into the stable trees.
Sean
> > On Fri, Mar 6, 2026 at 1:24 PM Nick <morrownr@gmail.com> wrote:
> > >
> > > > > Reference: https://github.com/morrownr/USB-WiFi/issues/688#issuecomment-3999038526
> > > > >
> > > > > The above thread is rather lengthy as we have been working on this
> > > > > issue since Dec. 25.
> > > > >
> > > > > Testing with a Netgear A9000 USB WiFi adapter (mt7925u driver). Kernel
> > > > > 7.0 rc2 and a x86_64 system. Additional testing with older kernels was
> > > > > also performed with the same results.
> > > > >
> > > > > Problem description:
> > > > >
> > > > > Cold boot shows the adapter coming up and operating normally.
> > > > >
> > > > > Removing the adapter from the USB port and replacing it shows the
> > > > > adapter coming up and operating normally.
> > > > >
> > > > > A warm reboot does not provide a WiFi interface and shows the
> > > > > following in the system log:
> > > > >
> > > > > mt7925u 2-3.2:1.0: probe with driver mt7925u failed with error -110
> > > > >
> > > > > Using the commands rmmod and modeprobe do not provide a WiFi interface.
> > > > >
> > > > > Thoughts: The problem likely is not the module teardown. The problem
> > > > > seems to be that the firmware (or the mt7925u driver) leaves the
> > > > > adapter in a strange state such that a power cycle of the adapter
> > > > > hardware is needed before the mt7925u driver can properly initialize
> > > > > it a second time.
> > > > >
> > > >
> > > > Hi Nick,
> > > >
> > > > Could you enable debug logs and check whether the driver can still
> > > > read the correct chip ID after a warm reboot, and at which step the
> > > > initialization fails before the -110 error? Thanks for continuing to
> > > > test and gather this useful information.
> > > >
> > > > Sean
> > >
> > > Hi Sean,
> > >
> > > Github user @exct has performed the testing requested and provides the
> > > following report:
> > >
> > > mt7925u Probe Failure Debug Report
> > > Adapter: Netgear A9000 (USB ID 0846:9072, MediaTek MT7925)
> > > Kernel: 6.19.6-2-cachyos
> > >
> > > Summary
> > > The driver can read the correct chip ID after a warm reload. The
> > > failure occurs before firmware is loaded, inside
> > > mt792xu_wfsys_reset(), which times out waiting for the WiFi subsystem
> > > to reinitialize. -ETIMEDOUT (-110) is returned and the probe aborts.
> > >
> > > Findings
> > >
> > > Question: Chip ID readable after warm reload?
> > > Answer: Yes — MT_HW_CHIPID = 0x7925, MT_HW_REV = 0x8a00
> > >
> > > Question: Which step fails?
> > > Answer: mt792xu_wfsys_reset() — WFSYS_INIT_DONE never asserted
> > >
> > > Question: Does it reach mt792xu_mcu_power_on()?
> > > Answer: No
> > >
> > > Question: Does it reach mt7925_run_firmware()?
> > > Answer: No
> > >
> > > Probe Sequence Trace
> > >
> > > mt7925u_probe()
> > > ├─ mt76_alloc_device() OK
> > > ├─ __mt76u_init() OK
> > > ├─ read MT_HW_CHIPID (0x70010200) → 0x00007925 ✓
> > > ├─ read MT_HW_REV (0x70010204) → 0x00008a00 ✓
> > > ├─ read MT_CONN_ON_MISC (0x7c0600f0) → 0x00000003 ← FW_N9_RDY is SET
> > > ├─ enters mt792xu_wfsys_reset() ← triggered because FW_N9_RDY = 1
> > > │ ├─ write MT_CBTOP_RGU_WF_SUBSYS_RST (assert reset)
> > > │ ├─ write MT_CBTOP_RGU_WF_SUBSYS_RST (deassert reset)
> > > │ ├─ poll MT_UDMA_CONN_INFRA_STATUS (0x74000a20) → 0x00000000
> > > │ ├─ poll MT_UDMA_CONN_INFRA_STATUS (0x74000a20) → 0x00000000
> > > │ └─ ... timeout after ~212 ms → return -ETIMEDOUT
> > > └─ goto error: (mt76u_queues_deinit + mt76_free_device)
> > >
> > > Root Cause
> > >
> > > After rmmod, the firmware leaves FW_N9_RDY asserted in
> > > MT_CONN_ON_MISC. On re-probe, the driver correctly detects this and
> > > calls mt792xu_wfsys_reset() to recover. However, the WiFi subsystem
> > > never signals completion — WFSYS_INIT_DONE in
> > > MT_UDMA_CONN_INFRA_STATUS (reg 0x74000a20) stays 0x00000000 throughout
> > > the retry loop. The subsystem is stuck in a state that the software
> > > reset path cannot recover from. Only a full USB power cycle clears it.
> > >
> > > Hope this helps,
> > >
> > > Nick
^ permalink raw reply
* [PATCH 2/2] wifi: mt76: mt792x: fix mt7925u USB WFSYS reset handling
From: Sean Wang @ 2026-03-11 0:28 UTC (permalink / raw)
To: nbd, lorenzo.bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang, stable
In-Reply-To: <20260311002825.15502-1-sean.wang@kernel.org>
From: Sean Wang <sean.wang@mediatek.com>
mt7925u uses different reset/status registers from mt7921u. Reusing the
mt7921u register set causes the WFSYS reset to fail.
Add a chip-specific descriptor in mt792xu_wfsys_reset() to select the
correct registers and fix mt7925u failing to initialize after a warm
reboot.
Fixes: d28e1a48952e ("wifi: mt76: mt792x: introduce mt792x-usb module")
Cc: stable@vger.kernel.org
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 4 ++++
drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 13 ++++++++++++-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h
index 7ddde9286861..d2a8b2b0df32 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h
@@ -392,6 +392,10 @@
#define MT_CBTOP_RGU_WF_SUBSYS_RST MT_CBTOP_RGU(0x600)
#define MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH BIT(0)
+#define MT7925_CBTOP_RGU_WF_SUBSYS_RST 0x70028600
+#define MT7925_WFSYS_INIT_DONE_ADDR 0x184c1604
+#define MT7925_WFSYS_INIT_DONE 0x00001d1e
+
#define MT_HW_BOUND 0x70010020
#define MT_HW_CHIPID 0x70010200
#define MT_HW_REV 0x70010204
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index a92e872226cf..47827d1c5ccb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -224,6 +224,15 @@ static const struct mt792xu_wfsys_desc mt7921_wfsys_desc = {
.need_status_sel = true,
};
+static const struct mt792xu_wfsys_desc mt7925_wfsys_desc = {
+ .rst_reg = MT7925_CBTOP_RGU_WF_SUBSYS_RST,
+ .done_reg = MT7925_WFSYS_INIT_DONE_ADDR,
+ .done_mask = U32_MAX,
+ .done_val = MT7925_WFSYS_INIT_DONE,
+ .delay_ms = 20,
+ .need_status_sel = false,
+};
+
int mt792xu_dma_init(struct mt792x_dev *dev, bool resume)
{
int err;
@@ -254,7 +263,9 @@ EXPORT_SYMBOL_GPL(mt792xu_dma_init);
int mt792xu_wfsys_reset(struct mt792x_dev *dev)
{
- const struct mt792xu_wfsys_desc *desc = &mt7921_wfsys_desc;
+ const struct mt792xu_wfsys_desc *desc = is_mt7925(&dev->mt76) ?
+ &mt7925_wfsys_desc :
+ &mt7921_wfsys_desc;
u32 val;
int i;
--
2.43.0
^ permalink raw reply related
* [PATCH 1/2] wifi: mt76: mt792x: describe USB WFSYS reset with a descriptor
From: Sean Wang @ 2026-03-11 0:28 UTC (permalink / raw)
To: nbd, lorenzo.bianconi; +Cc: linux-wireless, linux-mediatek, Sean Wang
From: Sean Wang <sean.wang@mediatek.com>
Prepare mt792xu_wfsys_reset() for chips that share the same USB WFSYS
reset flow but use different register definitions.
This is a pure refactor of the current mt7921u path and keeps the reset
sequence unchanged.
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt792x_usb.c | 40 +++++++++++++++----
1 file changed, 32 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
index 552808458138..a92e872226cf 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c
@@ -206,6 +206,24 @@ static void mt792xu_epctl_rst_opt(struct mt792x_dev *dev, bool reset)
mt792xu_uhw_wr(&dev->mt76, MT_SSUSB_EPCTL_CSR_EP_RST_OPT, val);
}
+struct mt792xu_wfsys_desc {
+ u32 rst_reg;
+ u32 done_reg;
+ u32 done_mask;
+ u32 done_val;
+ u32 delay_ms;
+ bool need_status_sel;
+};
+
+static const struct mt792xu_wfsys_desc mt7921_wfsys_desc = {
+ .rst_reg = MT_CBTOP_RGU_WF_SUBSYS_RST,
+ .done_reg = MT_UDMA_CONN_INFRA_STATUS,
+ .done_mask = MT_UDMA_CONN_WFSYS_INIT_DONE,
+ .done_val = MT_UDMA_CONN_WFSYS_INIT_DONE,
+ .delay_ms = 0,
+ .need_status_sel = true,
+};
+
int mt792xu_dma_init(struct mt792x_dev *dev, bool resume)
{
int err;
@@ -236,25 +254,31 @@ EXPORT_SYMBOL_GPL(mt792xu_dma_init);
int mt792xu_wfsys_reset(struct mt792x_dev *dev)
{
+ const struct mt792xu_wfsys_desc *desc = &mt7921_wfsys_desc;
u32 val;
int i;
mt792xu_epctl_rst_opt(dev, false);
- val = mt792xu_uhw_rr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST);
+ val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg);
val |= MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH;
- mt792xu_uhw_wr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST, val);
+ mt792xu_uhw_wr(&dev->mt76, desc->rst_reg, val);
- usleep_range(10, 20);
+ if (desc->delay_ms)
+ msleep(desc->delay_ms);
+ else
+ usleep_range(10, 20);
- val = mt792xu_uhw_rr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST);
+ val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg);
val &= ~MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH;
- mt792xu_uhw_wr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST, val);
+ mt792xu_uhw_wr(&dev->mt76, desc->rst_reg, val);
+
+ if (desc->need_status_sel)
+ mt792xu_uhw_wr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS_SEL, 0);
- mt792xu_uhw_wr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS_SEL, 0);
for (i = 0; i < MT792x_WFSYS_INIT_RETRY_COUNT; i++) {
- val = mt792xu_uhw_rr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS);
- if (val & MT_UDMA_CONN_WFSYS_INIT_DONE)
+ val = mt792xu_uhw_rr(&dev->mt76, desc->done_reg);
+ if ((val & desc->done_mask) == desc->done_val)
break;
msleep(100);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH 38/61] net: Prefer IS_ERR_OR_NULL over manual NULL check
From: Russell King (Oracle) @ 2026-03-11 0:16 UTC (permalink / raw)
To: Philipp Hahn
Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Igor Russkikh, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Pavan Chebbi, Michael Chan, Potnuri Bharat Teja, Tony Nguyen,
Przemek Kitszel, Taras Chornyi, Maxime Coquelin, Alexandre Torgue,
Iyappan Subramanian, Keyur Chudgar, Quan Nguyen, Heiner Kallweit
In-Reply-To: <20260310-b4-is_err_or_null-v1-38-bd63b656022d@avm.de>
On Tue, Mar 10, 2026 at 12:49:04PM +0100, Philipp Hahn wrote:
> diff --git a/drivers/net/mdio/mdio-xgene.c b/drivers/net/mdio/mdio-xgene.c
> index a8f91a4b7fed0927ee14e408000cd3a2bfb9b09a..09b30b563295c6085dc1358ac361301e5cf6b2a8 100644
> --- a/drivers/net/mdio/mdio-xgene.c
> +++ b/drivers/net/mdio/mdio-xgene.c
> @@ -265,7 +265,7 @@ struct phy_device *xgene_enet_phy_register(struct mii_bus *bus, int phy_addr)
> struct phy_device *phy_dev;
>
> phy_dev = get_phy_device(bus, phy_addr, false);
> - if (!phy_dev || IS_ERR(phy_dev))
> + if (IS_ERR_OR_NULL(phy_dev))
As noted in reply to your cover message, the check for NULL here is
incorrect - get_phy_device() returns either a valid pointer or an
error pointer, but never NULL.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: Russell King (Oracle) @ 2026-03-11 0:09 UTC (permalink / raw)
To: Philipp Hahn
Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs, Julia Lawall, Nicolas Palix, Chris Mason,
David Sterba, Ilya Dryomov, Alex Markuze, Viacheslav Dubeyko,
Theodore Ts'o, Andreas Dilger, Steve French, Paulo Alcantara,
Ronnie Sahlberg, Shyam Prasad N, Tom Talpey, Bharath SM,
Eric Van Hensbergen, Latchesar Ionkov, Dominique Martinet,
Christian Schoenebeck, Gao Xiang, Chao Yu, Yue Hu, Jeffle Xu,
Sandeep Dhavale, Hongbo Li, Chunhai Guo, Miklos Szeredi,
Konstantin Komarov, Andreas Gruenbacher, Kees Cook, Tony Luck,
Guilherme G. Piccoli, Jan Kara, Phillip Lougher, Alexander Viro,
Christian Brauner, Jan Kara, Steven Rostedt, Masami Hiramatsu,
Mathieu Desnoyers, Tejun Heo, David Vernet, Andrea Righi,
Changwoo Min, Ingo Molnar, Peter Zijlstra, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Ben Segall, Mel Gorman,
Valentin Schneider, Luis Chamberlain, Petr Pavlu, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, Sylwester Nawrocki, Liam Girdwood,
Mark Brown, Jaroslav Kysela, Takashi Iwai, Max Filippov,
Paolo Bonzini, John Johansen, Paul Moore, James Morris,
Serge E. Hallyn, Andrew Morton, Alasdair Kergon, Mike Snitzer,
Mikulas Patocka, Benjamin Marzinski, David S. Miller, David Ahern,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Marcel Holtmann, Johan Hedberg, Luiz Augusto von Dentz,
Alexei Starovoitov, Daniel Borkmann, Jesper Dangaard Brouer,
John Fastabend, Stanislav Fomichev, Jamal Hadi Salim, Jiri Pirko,
Marcelo Ricardo Leitner, Xin Long, Trond Myklebust,
Anna Schumaker, Chuck Lever, Jeff Layton, NeilBrown,
Olga Kornievskaia, Dai Ngo, Jon Maloy, Johannes Berg,
Catalin Marinas, John Crispin, Thomas Bogendoerfer,
Yoshinori Sato, Rich Felker, John Paul Adrian Glaubitz,
Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
Jonas Karlman, Jernej Skrabec, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Zhenyu Wang,
Zhi Wang, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
Tvrtko Ursulin, Alex Deucher, Christian König, Sandy Huang,
Heiko Stübner, Andy Yan, Igor Russkikh, Andrew Lunn,
Pavan Chebbi, Michael Chan, Potnuri Bharat Teja, Tony Nguyen,
Przemek Kitszel, Taras Chornyi, Maxime Coquelin, Alexandre Torgue,
Iyappan Subramanian, Keyur Chudgar, Quan Nguyen, Heiner Kallweit,
Marc Zyngier, Thomas Gleixner, Andrew Lunn, Gregory Clement,
Sebastian Hesselbarth, Vinod Koul, Linus Walleij, Ulf Hansson,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Martin K. Petersen,
Eduardo Valentin, Keerthy, Rafael J. Wysocki, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Alex Williamson, Mark Greer,
Miquel Raynal, Richard Weinberger, Vignesh Raghavendra,
Shuah Khan, Kieran Bingham, Mauro Carvalho Chehab, Joerg Roedel,
Will Deacon, Robin Murphy, Lee Jones, Pavel Machek, Dave Penkler,
K. Y. Srinivasan, Haiyang Zhang, Wei Liu, Dexuan Cui, Long Li,
Justin Sanders, Jens Axboe, Georgi Djakov, Michael Turquette,
Stephen Boyd, Philipp Zabel, Borislav Petkov, Dave Hansen, x86,
H. Peter Anvin, Pali Rohár, Dmitry Torokhov
In-Reply-To: <20260310-b4-is_err_or_null-v1-0-bd63b656022d@avm.de>
On Tue, Mar 10, 2026 at 12:48:26PM +0100, Philipp Hahn wrote:
> While doing some static code analysis I stumbled over a common pattern,
> where IS_ERR() is combined with a NULL check. For that there is
> IS_ERR_OR_NULL().
One thing you need to check for each of these cases is whether the tests
are actually correct.
There are certainly cases amongst those that you have identified where
the check for NULL is redundant.
For example, get_phy_device() never returns NULL, yet in your netdev
patch, you have at least one instance where the return value of
get_phy_device() is checked for NULL and IS_ERR() which you then
turn into IS_ERR_OR_NULL(). Instead, the NULL check should be dropped
(as a separate patch.)
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* Re: [PATCH v3 19/21] wifi: rtl8xxxu: add hw crypto support for AP mode
From: Bitterblue Smith @ 2026-03-10 23:33 UTC (permalink / raw)
To: Martin Kaistra, linux-wireless
Cc: Jes Sorensen, Ping-Ke Shih, Sebastian Andrzej Siewior
In-Reply-To: <b8e9ad0c-c148-40a2-b114-5558e74942b0@linutronix.de>
On 24/02/2026 17:43, Martin Kaistra wrote:
> Am 21.02.26 um 23:09 schrieb Bitterblue Smith:
>> On 22/12/2023 12:14, Martin Kaistra wrote:
>>> Add a custom function for allocating entries in the sec cam. This allows
>>> us to store multiple keys with the same keyidx.
>>>
>>> The maximum number of sec cam entries for 8188f is 16 according to the
>>> vendor driver. Add the number to rtl8xxxu_fileops, so that other chips
>>> which might support more entries, can set a different number there.
>>>
>>> Set the bssid as mac address for group keys instead of just using the
>>> ethernet broadcast address and use BIT(6) in the sec cam ctrl entry
>>> for differentiating them from pairwise keys like in the vendor driver.
>>>
>>> Add the TXDESC_EN_DESC_ID bit and the hw_key_idx to tx
>>> broadcast/multicast packets in AP mode.
>>>
>>> Finally, allow the usage of rtl8xxxu_set_key() for AP mode.
>>>
>>> Signed-off-by: Martin Kaistra <martin.kaistra@linutronix.de>
>>> Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>
>>> ---
>>> .../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 5 ++
>>> .../realtek/rtl8xxxu/rtl8xxxu_8188f.c | 1 +
>>> .../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 48 +++++++++++++++----
>>> 3 files changed, 44 insertions(+), 10 deletions(-)
>>>
>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> index c5e6d8f7d26bd..62e6318bc0924 100644
>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> @@ -498,6 +498,7 @@ struct rtl8xxxu_txdesc40 {
>>> #define DESC_RATE_ID_SHIFT 16
>>> #define DESC_RATE_ID_MASK 0xf
>>> #define TXDESC_NAVUSEHDR BIT(20)
>>> +#define TXDESC_EN_DESC_ID BIT(21)
>>> #define TXDESC_SEC_RC4 0x00400000
>>> #define TXDESC_SEC_AES 0x00c00000
>>> #define TXDESC_PKT_OFFSET_SHIFT 26
>>> @@ -1775,6 +1776,7 @@ struct rtl8xxxu_cfo_tracking {
>>> #define RTL8XXXU_MAX_MAC_ID_NUM 128
>>> #define RTL8XXXU_BC_MC_MACID 0
>>> #define RTL8XXXU_BC_MC_MACID1 1
>>> +#define RTL8XXXU_MAX_SEC_CAM_NUM 64
>>> struct rtl8xxxu_priv {
>>> struct ieee80211_hw *hw;
>>> @@ -1908,6 +1910,7 @@ struct rtl8xxxu_priv {
>>> char led_name[32];
>>> struct led_classdev led_cdev;
>>> DECLARE_BITMAP(mac_id_map, RTL8XXXU_MAX_MAC_ID_NUM);
>>> + DECLARE_BITMAP(cam_map, RTL8XXXU_MAX_SEC_CAM_NUM);
>>> };
>>> struct rtl8xxxu_sta_info {
>>> @@ -1919,6 +1922,7 @@ struct rtl8xxxu_sta_info {
>>> struct rtl8xxxu_vif {
>>> int port_num;
>>> + u8 hw_key_idx;
>>> };
>>> struct rtl8xxxu_rx_urb {
>>> @@ -1993,6 +1997,7 @@ struct rtl8xxxu_fileops {
>>> u16 max_aggr_num;
>>> u8 supports_ap:1;
>>> u16 max_macid_num;
>>> + u16 max_sec_cam_num;
>>> u32 adda_1t_init;
>>> u32 adda_1t_path_on;
>>> u32 adda_2t_path_on_a;
>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>> index 1e1c8fa194cb8..574a5fe951543 100644
>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>> @@ -1751,6 +1751,7 @@ struct rtl8xxxu_fileops rtl8188fu_fops = {
>>> .max_aggr_num = 0x0c14,
>>> .supports_ap = 1,
>>> .max_macid_num = 16,
>>> + .max_sec_cam_num = 16,
>>> .adda_1t_init = 0x03c00014,
>>> .adda_1t_path_on = 0x03c00014,
>>> .trxff_boundary = 0x3f7f,
>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>> index ecf54eb8dba61..7aafae9fe76b8 100644
>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>> @@ -4559,8 +4559,10 @@ static void rtl8xxxu_cam_write(struct rtl8xxxu_priv *priv,
>>> * This is a bit of a hack - the lower bits of the cipher
>>> * suite selector happens to match the cipher index in the CAM
>>> */
>>> - addr = key->keyidx << CAM_CMD_KEY_SHIFT;
>>> + addr = key->hw_key_idx << CAM_CMD_KEY_SHIFT;
>>> ctrl = (key->cipher & 0x0f) << 2 | key->keyidx | CAM_WRITE_VALID;
>>> + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>>> + ctrl |= BIT(6);
>>> for (j = 5; j >= 0; j--) {
>>> switch (j) {
>>> @@ -5546,13 +5548,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>> struct rtl8xxxu_tx_urb *tx_urb;
>>> struct ieee80211_sta *sta = NULL;
>>> struct ieee80211_vif *vif = tx_info->control.vif;
>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
>>> struct device *dev = &priv->udev->dev;
>>> u32 queue, rts_rate;
>>> u16 pktlen = skb->len;
>>> int tx_desc_size = priv->fops->tx_desc_size;
>>> u8 macid;
>>> int ret;
>>> - bool ampdu_enable, sgi = false, short_preamble = false;
>>> + bool ampdu_enable, sgi = false, short_preamble = false, bmc = false;
>>> if (skb_headroom(skb) < tx_desc_size) {
>>> dev_warn(dev,
>>> @@ -5594,10 +5597,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>> tx_desc->txdw0 =
>>> TXDESC_OWN | TXDESC_FIRST_SEGMENT | TXDESC_LAST_SEGMENT;
>>> if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) ||
>>> - is_broadcast_ether_addr(ieee80211_get_DA(hdr)))
>>> + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) {
>>> tx_desc->txdw0 |= TXDESC_BROADMULTICAST;
>>> + bmc = true;
>>> + }
>>> +
>>> tx_desc->txdw1 = cpu_to_le32(queue << TXDESC_QUEUE_SHIFT);
>>> + macid = rtl8xxxu_get_macid(priv, sta);
>>> if (tx_info->control.hw_key) {
>>> switch (tx_info->control.hw_key->cipher) {
>>> @@ -5612,6 +5619,10 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>> default:
>>> break;
>>> }
>>> + if (bmc && rtlvif->hw_key_idx != 0xff) {
>>> + tx_desc->txdw1 |= TXDESC_EN_DESC_ID;
>>> + macid = rtlvif->hw_key_idx;
>>> + }
>>> }
>>> /* (tx_info->flags & IEEE80211_TX_CTL_AMPDU) && */
>>> @@ -5655,7 +5666,6 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>> else
>>> rts_rate = 0;
>>> - macid = rtl8xxxu_get_macid(priv, sta);
>>> priv->fops->fill_txdesc(hw, hdr, tx_info, tx_desc, sgi, short_preamble,
>>> ampdu_enable, rts_rate, macid);
>>> @@ -6667,6 +6677,7 @@ static int rtl8xxxu_add_interface(struct ieee80211_hw *hw,
>>> priv->vifs[port_num] = vif;
>>> rtlvif->port_num = port_num;
>>> + rtlvif->hw_key_idx = 0xff;
>>> rtl8xxxu_set_linktype(priv, vif->type, port_num);
>>> ether_addr_copy(priv->mac_addr, vif->addr);
>>> @@ -6843,11 +6854,19 @@ static int rtl8xxxu_set_rts_threshold(struct ieee80211_hw *hw, u32 rts)
>>> return 0;
>>> }
>>> +static int rtl8xxxu_get_free_sec_cam(struct ieee80211_hw *hw)
>>> +{
>>> + struct rtl8xxxu_priv *priv = hw->priv;
>>> +
>>> + return find_first_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num);
>>> +}
>>> +
>>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>> struct ieee80211_vif *vif,
>>> struct ieee80211_sta *sta,
>>> struct ieee80211_key_conf *key)
>>> {
>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
>>> struct rtl8xxxu_priv *priv = hw->priv;
>>> struct device *dev = &priv->udev->dev;
>>> u8 mac_addr[ETH_ALEN];
>>> @@ -6859,9 +6878,6 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>> dev_dbg(dev, "%s: cmd %02x, cipher %08x, index %i\n",
>>> __func__, cmd, key->cipher, key->keyidx);
>>> - if (vif->type != NL80211_IFTYPE_STATION)
>>> - return -EOPNOTSUPP;
>>> -
>>> if (key->keyidx > 3)
>>> return -EOPNOTSUPP;
>>> @@ -6885,7 +6901,7 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>> ether_addr_copy(mac_addr, sta->addr);
>>> } else {
>>> dev_dbg(dev, "%s: group key\n", __func__);
>>> - eth_broadcast_addr(mac_addr);
>>> + ether_addr_copy(mac_addr, vif->bss_conf.bssid);
>>> }
>>> val16 = rtl8xxxu_read16(priv, REG_CR);
>>> @@ -6899,16 +6915,28 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>> switch (cmd) {
>>> case SET_KEY:
>>> - key->hw_key_idx = key->keyidx;
>>> +
>>> + retval = rtl8xxxu_get_free_sec_cam(hw);
>>> + if (retval < 0)
>>> + return -EOPNOTSUPP;
>>> +
>>> + key->hw_key_idx = retval;
>>> +
>>> + if (vif->type == NL80211_IFTYPE_AP && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>>> + rtlvif->hw_key_idx = key->hw_key_idx;
>>> +
>>> key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
>>> rtl8xxxu_cam_write(priv, key, mac_addr);
>>> + set_bit(key->hw_key_idx, priv->cam_map);
>>
>> Hi Martin,
>
> Hi Bitterblue,
>
>>
>> It turns out RTL8188CUS and RTL8192CU don't like this patch, specifically
>> when we use iwd. After the WPA2 handshake no more data is transmitted.
>>
>> Before this patch, key->hw_key_idx was the same as key->keyidx. After
>> this patch, when we use iwd, the group key is installed first. It has
>> key->keyidx = 1, but it gets key->hw_key_idx = 0. The pairwise key is
>> installed second. It has key->keyidx = 0, but it gets key->hw_key_idx = 1.
>> Both keyidx and hw_key_idx are passed to the chip in rtl8xxxu_cam_write().
>>
>> It's fine with wpa_supplicant. wpa_supplicant installs the pairwise key
>> first, with key->keyidx = 0, then the group key, with key->keyidx = 1.
>>
>> This patch imitating rtw88 makes the old chips work again with iwd.
>> What do you think?
>
> So you reserve the first 4 entries for group keys and use key->keyidx as key->hw_key_idx directly for those, right? Does that work if 2 virtual interfaces are used at the same time?
Yes. I don't know if that works with 2 interfaces. How can I test?
I never tried to use 2 interfaces at the same time.
> I will do some tests in the next days, but I suspect this be an issue.
>
>>
>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c b/drivers/net/wireless/realtek/rtl8xxxu/core.c
>> index ee278f0548e4..f7b35655bec5 100644
>> --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c
>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c
>> @@ -6942,7 +6942,8 @@ static int rtl8xxxu_get_free_sec_cam(struct ieee80211_hw *hw)
>> {
>> struct rtl8xxxu_priv *priv = hw->priv;
>> - return find_first_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num);
>> + return find_next_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num,
>> + RTL8XXXU_SEC_DEFAULT_KEY_NUM);
>> }
>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>> @@ -6999,12 +7000,15 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>> switch (cmd) {
>> case SET_KEY:
>> + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
>> + retval = rtl8xxxu_get_free_sec_cam(hw);
>> + if (retval < 0)
>> + return -EOPNOTSUPP;
>> - retval = rtl8xxxu_get_free_sec_cam(hw);
>> - if (retval < 0)
>> - return -EOPNOTSUPP;
>> -
>> - key->hw_key_idx = retval;
>> + key->hw_key_idx = retval;
>> + } else {
>> + key->hw_key_idx = key->keyidx;
>> + }
>> if (vif->type == NL80211_IFTYPE_AP && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>> rtlvif->hw_key_idx = key->hw_key_idx;
>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>> index 4b05dba22e67..188f4bbe99cd 100644
>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>> @@ -1788,6 +1788,7 @@ struct rtl8xxxu_cfo_tracking {
>> #define RTL8XXXU_BC_MC_MACID 0
>> #define RTL8XXXU_BC_MC_MACID1 1
>> #define RTL8XXXU_MAX_SEC_CAM_NUM 64
>> +#define RTL8XXXU_SEC_DEFAULT_KEY_NUM 4
>> struct rtl8xxxu_priv {
>> struct ieee80211_hw *hw;
>
^ permalink raw reply
* Re: [PATCH v3 19/21] wifi: rtl8xxxu: add hw crypto support for AP mode
From: Bitterblue Smith @ 2026-03-10 23:32 UTC (permalink / raw)
To: Ping-Ke Shih, Martin Kaistra, linux-wireless@vger.kernel.org
Cc: Jes Sorensen, Sebastian Andrzej Siewior
In-Reply-To: <64617033bcdc445091186070350a0d20@realtek.com>
On 25/02/2026 04:28, Ping-Ke Shih wrote:
> Martin Kaistra <martin.kaistra@linutronix.de> wrote:
>> Am 21.02.26 um 23:09 schrieb Bitterblue Smith:
>>> On 22/12/2023 12:14, Martin Kaistra wrote:
>>>> Add a custom function for allocating entries in the sec cam. This allows
>>>> us to store multiple keys with the same keyidx.
>>>>
>>>> The maximum number of sec cam entries for 8188f is 16 according to the
>>>> vendor driver. Add the number to rtl8xxxu_fileops, so that other chips
>>>> which might support more entries, can set a different number there.
>>>>
>>>> Set the bssid as mac address for group keys instead of just using the
>>>> ethernet broadcast address and use BIT(6) in the sec cam ctrl entry
>>>> for differentiating them from pairwise keys like in the vendor driver.
>>>>
>>>> Add the TXDESC_EN_DESC_ID bit and the hw_key_idx to tx
>>>> broadcast/multicast packets in AP mode.
>>>>
>>>> Finally, allow the usage of rtl8xxxu_set_key() for AP mode.
>>>>
>>>> Signed-off-by: Martin Kaistra <martin.kaistra@linutronix.de>
>>>> Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>
>>>> ---
>>>> .../net/wireless/realtek/rtl8xxxu/rtl8xxxu.h | 5 ++
>>>> .../realtek/rtl8xxxu/rtl8xxxu_8188f.c | 1 +
>>>> .../wireless/realtek/rtl8xxxu/rtl8xxxu_core.c | 48 +++++++++++++++----
>>>> 3 files changed, 44 insertions(+), 10 deletions(-)
>>>>
>>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>>> index c5e6d8f7d26bd..62e6318bc0924 100644
>>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>>> @@ -498,6 +498,7 @@ struct rtl8xxxu_txdesc40 {
>>>> #define DESC_RATE_ID_SHIFT 16
>>>> #define DESC_RATE_ID_MASK 0xf
>>>> #define TXDESC_NAVUSEHDR BIT(20)
>>>> +#define TXDESC_EN_DESC_ID BIT(21)
>>>> #define TXDESC_SEC_RC4 0x00400000
>>>> #define TXDESC_SEC_AES 0x00c00000
>>>> #define TXDESC_PKT_OFFSET_SHIFT 26
>>>> @@ -1775,6 +1776,7 @@ struct rtl8xxxu_cfo_tracking {
>>>> #define RTL8XXXU_MAX_MAC_ID_NUM 128
>>>> #define RTL8XXXU_BC_MC_MACID 0
>>>> #define RTL8XXXU_BC_MC_MACID1 1
>>>> +#define RTL8XXXU_MAX_SEC_CAM_NUM 64
>>>>
>>>> struct rtl8xxxu_priv {
>>>> struct ieee80211_hw *hw;
>>>> @@ -1908,6 +1910,7 @@ struct rtl8xxxu_priv {
>>>> char led_name[32];
>>>> struct led_classdev led_cdev;
>>>> DECLARE_BITMAP(mac_id_map, RTL8XXXU_MAX_MAC_ID_NUM);
>>>> + DECLARE_BITMAP(cam_map, RTL8XXXU_MAX_SEC_CAM_NUM);
>>>> };
>>>>
>>>> struct rtl8xxxu_sta_info {
>>>> @@ -1919,6 +1922,7 @@ struct rtl8xxxu_sta_info {
>>>>
>>>> struct rtl8xxxu_vif {
>>>> int port_num;
>>>> + u8 hw_key_idx;
>>>> };
>>>>
>>>> struct rtl8xxxu_rx_urb {
>>>> @@ -1993,6 +1997,7 @@ struct rtl8xxxu_fileops {
>>>> u16 max_aggr_num;
>>>> u8 supports_ap:1;
>>>> u16 max_macid_num;
>>>> + u16 max_sec_cam_num;
>>>> u32 adda_1t_init;
>>>> u32 adda_1t_path_on;
>>>> u32 adda_2t_path_on_a;
>>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>>> index 1e1c8fa194cb8..574a5fe951543 100644
>>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_8188f.c
>>>> @@ -1751,6 +1751,7 @@ struct rtl8xxxu_fileops rtl8188fu_fops = {
>>>> .max_aggr_num = 0x0c14,
>>>> .supports_ap = 1,
>>>> .max_macid_num = 16,
>>>> + .max_sec_cam_num = 16,
>>>> .adda_1t_init = 0x03c00014,
>>>> .adda_1t_path_on = 0x03c00014,
>>>> .trxff_boundary = 0x3f7f,
>>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>>> index ecf54eb8dba61..7aafae9fe76b8 100644
>>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu_core.c
>>>> @@ -4559,8 +4559,10 @@ static void rtl8xxxu_cam_write(struct rtl8xxxu_priv *priv,
>>>> * This is a bit of a hack - the lower bits of the cipher
>>>> * suite selector happens to match the cipher index in the CAM
>>>> */
>>>> - addr = key->keyidx << CAM_CMD_KEY_SHIFT;
>>>> + addr = key->hw_key_idx << CAM_CMD_KEY_SHIFT;
>>>> ctrl = (key->cipher & 0x0f) << 2 | key->keyidx | CAM_WRITE_VALID;
>>>> + if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>>>> + ctrl |= BIT(6);
>>>>
>>>> for (j = 5; j >= 0; j--) {
>>>> switch (j) {
>>>> @@ -5546,13 +5548,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>>> struct rtl8xxxu_tx_urb *tx_urb;
>>>> struct ieee80211_sta *sta = NULL;
>>>> struct ieee80211_vif *vif = tx_info->control.vif;
>>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
>>>> struct device *dev = &priv->udev->dev;
>>>> u32 queue, rts_rate;
>>>> u16 pktlen = skb->len;
>>>> int tx_desc_size = priv->fops->tx_desc_size;
>>>> u8 macid;
>>>> int ret;
>>>> - bool ampdu_enable, sgi = false, short_preamble = false;
>>>> + bool ampdu_enable, sgi = false, short_preamble = false, bmc = false;
>>>>
>>>> if (skb_headroom(skb) < tx_desc_size) {
>>>> dev_warn(dev,
>>>> @@ -5594,10 +5597,14 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>>> tx_desc->txdw0 =
>>>> TXDESC_OWN | TXDESC_FIRST_SEGMENT | TXDESC_LAST_SEGMENT;
>>>> if (is_multicast_ether_addr(ieee80211_get_DA(hdr)) ||
>>>> - is_broadcast_ether_addr(ieee80211_get_DA(hdr)))
>>>> + is_broadcast_ether_addr(ieee80211_get_DA(hdr))) {
>>>> tx_desc->txdw0 |= TXDESC_BROADMULTICAST;
>>>> + bmc = true;
>>>> + }
>>>> +
>>>>
>>>> tx_desc->txdw1 = cpu_to_le32(queue << TXDESC_QUEUE_SHIFT);
>>>> + macid = rtl8xxxu_get_macid(priv, sta);
>>>>
>>>> if (tx_info->control.hw_key) {
>>>> switch (tx_info->control.hw_key->cipher) {
>>>> @@ -5612,6 +5619,10 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>>> default:
>>>> break;
>>>> }
>>>> + if (bmc && rtlvif->hw_key_idx != 0xff) {
>>>> + tx_desc->txdw1 |= TXDESC_EN_DESC_ID;
>>>> + macid = rtlvif->hw_key_idx;
>>>> + }
>>>> }
>>>>
>>>> /* (tx_info->flags & IEEE80211_TX_CTL_AMPDU) && */
>>>> @@ -5655,7 +5666,6 @@ static void rtl8xxxu_tx(struct ieee80211_hw *hw,
>>>> else
>>>> rts_rate = 0;
>>>>
>>>> - macid = rtl8xxxu_get_macid(priv, sta);
>>>> priv->fops->fill_txdesc(hw, hdr, tx_info, tx_desc, sgi, short_preamble,
>>>> ampdu_enable, rts_rate, macid);
>>>>
>>>> @@ -6667,6 +6677,7 @@ static int rtl8xxxu_add_interface(struct ieee80211_hw *hw,
>>>>
>>>> priv->vifs[port_num] = vif;
>>>> rtlvif->port_num = port_num;
>>>> + rtlvif->hw_key_idx = 0xff;
>>>>
>>>> rtl8xxxu_set_linktype(priv, vif->type, port_num);
>>>> ether_addr_copy(priv->mac_addr, vif->addr);
>>>> @@ -6843,11 +6854,19 @@ static int rtl8xxxu_set_rts_threshold(struct ieee80211_hw *hw, u32 rts)
>>>> return 0;
>>>> }
>>>>
>>>> +static int rtl8xxxu_get_free_sec_cam(struct ieee80211_hw *hw)
>>>> +{
>>>> + struct rtl8xxxu_priv *priv = hw->priv;
>>>> +
>>>> + return find_first_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num);
>>>> +}
>>>> +
>>>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>>> struct ieee80211_vif *vif,
>>>> struct ieee80211_sta *sta,
>>>> struct ieee80211_key_conf *key)
>>>> {
>>>> + struct rtl8xxxu_vif *rtlvif = (struct rtl8xxxu_vif *)vif->drv_priv;
>>>> struct rtl8xxxu_priv *priv = hw->priv;
>>>> struct device *dev = &priv->udev->dev;
>>>> u8 mac_addr[ETH_ALEN];
>>>> @@ -6859,9 +6878,6 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>>> dev_dbg(dev, "%s: cmd %02x, cipher %08x, index %i\n",
>>>> __func__, cmd, key->cipher, key->keyidx);
>>>>
>>>> - if (vif->type != NL80211_IFTYPE_STATION)
>>>> - return -EOPNOTSUPP;
>>>> -
>>>> if (key->keyidx > 3)
>>>> return -EOPNOTSUPP;
>>>>
>>>> @@ -6885,7 +6901,7 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>>> ether_addr_copy(mac_addr, sta->addr);
>>>> } else {
>>>> dev_dbg(dev, "%s: group key\n", __func__);
>>>> - eth_broadcast_addr(mac_addr);
>>>> + ether_addr_copy(mac_addr, vif->bss_conf.bssid);
>>>> }
>>>>
>>>> val16 = rtl8xxxu_read16(priv, REG_CR);
>>>> @@ -6899,16 +6915,28 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>>>
>>>> switch (cmd) {
>>>> case SET_KEY:
>>>> - key->hw_key_idx = key->keyidx;
>>>> +
>>>> + retval = rtl8xxxu_get_free_sec_cam(hw);
>>>> + if (retval < 0)
>>>> + return -EOPNOTSUPP;
>>>> +
>>>> + key->hw_key_idx = retval;
>>>> +
>>>> + if (vif->type == NL80211_IFTYPE_AP && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>>>> + rtlvif->hw_key_idx = key->hw_key_idx;
>>>> +
>>>> key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
>>>> rtl8xxxu_cam_write(priv, key, mac_addr);
>>>> + set_bit(key->hw_key_idx, priv->cam_map);
>>>
>>> Hi Martin,
>>
>> Hi Bitterblue,
>>
>>>
>>> It turns out RTL8188CUS and RTL8192CU don't like this patch, specifically
>>> when we use iwd. After the WPA2 handshake no more data is transmitted.
>>>
>>> Before this patch, key->hw_key_idx was the same as key->keyidx. After
>>> this patch, when we use iwd, the group key is installed first. It has
>>> key->keyidx = 1, but it gets key->hw_key_idx = 0. The pairwise key is
>>> installed second. It has key->keyidx = 0, but it gets key->hw_key_idx = 1.
>>> Both keyidx and hw_key_idx are passed to the chip in rtl8xxxu_cam_write().
>>>
>>> It's fine with wpa_supplicant. wpa_supplicant installs the pairwise key
>>> first, with key->keyidx = 0, then the group key, with key->keyidx = 1.
>>>
>>> This patch imitating rtw88 makes the old chips work again with iwd.
>>> What do you think?
>>
>> So you reserve the first 4 entries for group keys and use key->keyidx as
>> key->hw_key_idx directly for those, right? Does that work if 2 virtual
>> interfaces are used at the same time?
>> I will do some tests in the next days, but I suspect this be an issue.
>
> Ah. I'm not aware that rtl8xxxu can support 2 virtual interfaces, and
> only RTL8188FU declares .supports_concurrent = 1, so maybe we can have
> special handling for this chip?
I would like to enable supports_concurrent for more chips, if I can test
this feature.
>
> I'd share some information about security CAM for reference. Since data is
> quite old (10+ years), I can't guarantee all are correct.
>
> 1. default key switch in rtl8xxxu_set_key()
> SEC_CFG_TXBC_USE_DEFKEY and SEC_CFG_RXBC_USE_DEFKEY mean that
> broadcast/multicast packets use default key (CAM entries 0~3 for
> keyidx 0~3).
> For two interfaces case, that'd a problem, so maybe we should use
> software encryption/decryption for non-pairwise key.
>
> val8 = SEC_CFG_TX_SEC_ENABLE | SEC_CFG_TXBC_USE_DEFKEY |
> SEC_CFG_RX_SEC_ENABLE | SEC_CFG_RXBC_USE_DEFKEY;
> val8 |= SEC_CFG_TX_USE_DEFKEY | SEC_CFG_RX_USE_DEFKEY;
> rtl8xxxu_write8(priv, REG_SECURITY_CFG, val8);
>
> 2. group key (GK) field of security CAM in rtl8xxxu_cam_write()
> The group key field of security CAM is BIT(6) which isn't supported
> by earlier chips (sorry I have no data about the exact chips).
>
BIT 6 is used in the rtl8188eu driver, and not used in the rtl8192cu driver,
so I assume it's just the older RTL8188CU/RTL8192CU and RTL8723AU that don't
support it.
> If a chip can support group key field, the CAM layout with default key
> enabled for two interfaces (STA-1+AP-1) can be
>
> STA-1 <-> AP-10
> AP-1 <-> STA-20
>
> CAM GK MAC KEY
> 0
> 1 1 00: AP-1 (GTK) // should be on entry 0~3 depends on key_idx
> 2
> 3
> 4
> 5 1 AP-10 AP-10 (GTK) // can be on any entry after 4
> 6
>
What are the numbers 1, 10, and 20 in "AP-1", "AP-10", "STA-20" ?
> (pairwise key can be on any entry after 4)
>
> if (!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
> ctrl |= BIT(6);
>
> My perspective are
> 1. currently default key is enabled, so we should treat CAM entry 0~3 as
> special cases. That means rtl8xxxu_get_free_sec_cam() should be modified
> as Bitterblue's version.
>
> 2. For two interfaces, I guess RTL8188FU can support GK bit, so it'd be worth
> to try the method of example 2 above.
>
> If we want earlier chips can support two interfaces, I think we should
> disable default key.
Indeed, the rtl8192cu driver (out of kernel) doesn't set SEC_CFG_TXBC_USE_DEFKEY
and SEC_CFG_RXBC_USE_DEFKEY when concurrent mode is enabled.
> Seemingly, it is not possible that using hardware
> crypto when operating one interface and then switching to use software> crypto when operating two interfaces. That means broadcast/multicast
> packets should be by software crypto entirely if we want to support two
> interfaces.
>
> 3. I think rtw88 has similar problem for two interfaces.
>
>>
>>>
>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/core.c
>> b/drivers/net/wireless/realtek/rtl8xxxu/core.c
>>> index ee278f0548e4..f7b35655bec5 100644
>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/core.c
>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/core.c
>>> @@ -6942,7 +6942,8 @@ static int rtl8xxxu_get_free_sec_cam(struct ieee80211_hw *hw)
>>> {
>>> struct rtl8xxxu_priv *priv = hw->priv;
>>>
>>> - return find_first_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num);
>>> + return find_next_zero_bit(priv->cam_map, priv->fops->max_sec_cam_num,
>>> + RTL8XXXU_SEC_DEFAULT_KEY_NUM);
>>> }
>>>
>>> static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>> @@ -6999,12 +7000,15 @@ static int rtl8xxxu_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
>>>
>>> switch (cmd) {
>>> case SET_KEY:
>>> + if (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) {
>>> + retval = rtl8xxxu_get_free_sec_cam(hw);
>>> + if (retval < 0)
>>> + return -EOPNOTSUPP;
>>>
>>> - retval = rtl8xxxu_get_free_sec_cam(hw);
>>> - if (retval < 0)
>>> - return -EOPNOTSUPP;
>>> -
>>> - key->hw_key_idx = retval;
>>> + key->hw_key_idx = retval;
>>> + } else {
>>> + key->hw_key_idx = key->keyidx;
>>> + }
>>>
>>> if (vif->type == NL80211_IFTYPE_AP && !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
>>> rtlvif->hw_key_idx = key->hw_key_idx;
>>> diff --git a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>> b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> index 4b05dba22e67..188f4bbe99cd 100644
>>> --- a/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> +++ b/drivers/net/wireless/realtek/rtl8xxxu/rtl8xxxu.h
>>> @@ -1788,6 +1788,7 @@ struct rtl8xxxu_cfo_tracking {
>>> #define RTL8XXXU_BC_MC_MACID 0
>>> #define RTL8XXXU_BC_MC_MACID1 1
>>> #define RTL8XXXU_MAX_SEC_CAM_NUM 64
>>> +#define RTL8XXXU_SEC_DEFAULT_KEY_NUM 4
>>>
>>> struct rtl8xxxu_priv {
>>> struct ieee80211_hw *hw;
>
^ permalink raw reply
* Re: [PATCH rtw-next] wifi: rtw89: usb: Rx aggregation for RTL8832CU/RTL8851BU
From: Bitterblue Smith @ 2026-03-10 22:16 UTC (permalink / raw)
To: Ping-Ke Shih, linux-wireless; +Cc: mh_chen, isaiah
In-Reply-To: <20260309085819.25174-1-pkshih@realtek.com>
On 09/03/2026 10:58, Ping-Ke Shih wrote:
> From: Shin-Yi Lin <isaiah@realtek.com>
>
> USB RX Aggregation is a performance optimization technique used
> in USB network devices to increase throughput.
>
> Instead of sending every received network packet to the host computer
> individually, the device hardware groups multiple smaller packets
> into a single, large USB Bulk Transfer.
>
> * toAP/toNB use iperf3 respectively.
>
> With BE6000 - iperf3 tcp 10 pair (to another NB)
>
> RTL8832CU-USB3.0
> before after
> TX 941 941
> RX 847 919
>
> RTL8832CU-USB2.0
> before after
> TX 864 877
> RX 864 902
I wonder if these numbers are actually from a different scenario?
USB 2.0 can't go that fast.
>
> RTL8851BU
> before after
> TX 115 114
> RX 295 306
>
> Signed-off-by: Shin-Yi Lin <isaiah@realtek.com>
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
> ---
> This one is to add USB RX aggregation to improve performance. The other
> one is TX aggregation, which we are working on.
That is wonderful news.
> ---
> .../net/wireless/realtek/rtw89/rtw8851bu.c | 1 +
> .../net/wireless/realtek/rtw89/rtw8852au.c | 1 +
> .../net/wireless/realtek/rtw89/rtw8852bu.c | 1 +
> .../net/wireless/realtek/rtw89/rtw8852cu.c | 1 +
> drivers/net/wireless/realtek/rtw89/usb.c | 84 ++++++++++++++++---
> drivers/net/wireless/realtek/rtw89/usb.h | 12 +++
> 6 files changed, 87 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
> index 959d62aefdd8..6a8d31544314 100644
> --- a/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
> +++ b/drivers/net/wireless/realtek/rtw89/rtw8851bu.c
> @@ -15,6 +15,7 @@ static const struct rtw89_usb_info rtw8851b_usb_info = {
> .usb3_mac_npi_config_intf_0 = R_AX_USB3_MAC_NPI_CONFIG_INTF_0,
> .usb_endpoint_0 = R_AX_USB_ENDPOINT_0,
> .usb_endpoint_2 = R_AX_USB_ENDPOINT_2,
> + .rx_agg_alignment = 8,
> .bulkout_id = {
> [RTW89_DMA_ACH0] = 3,
> [RTW89_DMA_ACH1] = 4,
> diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852au.c b/drivers/net/wireless/realtek/rtw89/rtw8852au.c
> index ccdbcc178c2a..4cced4619b7d 100644
> --- a/drivers/net/wireless/realtek/rtw89/rtw8852au.c
> +++ b/drivers/net/wireless/realtek/rtw89/rtw8852au.c
> @@ -15,6 +15,7 @@ static const struct rtw89_usb_info rtw8852a_usb_info = {
> .usb3_mac_npi_config_intf_0 = R_AX_USB3_MAC_NPI_CONFIG_INTF_0,
> .usb_endpoint_0 = R_AX_USB_ENDPOINT_0,
> .usb_endpoint_2 = R_AX_USB_ENDPOINT_2,
> + .rx_agg_alignment = 8,
> .bulkout_id = {
> [RTW89_DMA_ACH0] = 3,
> [RTW89_DMA_ACH2] = 5,
> diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
> index 84cd3ec971f9..37111fed276f 100644
> --- a/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
> +++ b/drivers/net/wireless/realtek/rtw89/rtw8852bu.c
> @@ -15,6 +15,7 @@ static const struct rtw89_usb_info rtw8852b_usb_info = {
> .usb3_mac_npi_config_intf_0 = R_AX_USB3_MAC_NPI_CONFIG_INTF_0,
> .usb_endpoint_0 = R_AX_USB_ENDPOINT_0,
> .usb_endpoint_2 = R_AX_USB_ENDPOINT_2,
> + .rx_agg_alignment = 8,
> .bulkout_id = {
> [RTW89_DMA_ACH0] = 3,
> [RTW89_DMA_ACH1] = 4,
> diff --git a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
> index 3b9825c92a0d..0c5aebaed873 100644
> --- a/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
> +++ b/drivers/net/wireless/realtek/rtw89/rtw8852cu.c
> @@ -15,6 +15,7 @@ static const struct rtw89_usb_info rtw8852c_usb_info = {
> .usb3_mac_npi_config_intf_0 = R_AX_USB3_MAC_NPI_CONFIG_INTF_0_V1,
> .usb_endpoint_0 = R_AX_USB_ENDPOINT_0_V1,
> .usb_endpoint_2 = R_AX_USB_ENDPOINT_2_V1,
> + .rx_agg_alignment = 8,
> .bulkout_id = {
> [RTW89_DMA_ACH0] = 3,
> [RTW89_DMA_ACH2] = 5,
> diff --git a/drivers/net/wireless/realtek/rtw89/usb.c b/drivers/net/wireless/realtek/rtw89/usb.c
> index da1b7ce8089e..4482ce61592b 100644
> --- a/drivers/net/wireless/realtek/rtw89/usb.c
> +++ b/drivers/net/wireless/realtek/rtw89/usb.c
> @@ -408,11 +408,14 @@ static int rtw89_usb_ops_tx_write(struct rtw89_dev *rtwdev,
> static void rtw89_usb_rx_handler(struct work_struct *work)
> {
> struct rtw89_usb *rtwusb = container_of(work, struct rtw89_usb, rx_work);
> + const struct rtw89_usb_info *info = rtwusb->info;
> struct rtw89_dev *rtwdev = rtwusb->rtwdev;
> struct rtw89_rx_desc_info desc_info;
> + s32 aligned_offset, remaining;
> struct sk_buff *rx_skb;
> struct sk_buff *skb;
> u32 pkt_offset;
> + u8 *pkt_ptr;
> int limit;
>
> for (limit = 0; limit < 200; limit++) {
> @@ -425,23 +428,38 @@ static void rtw89_usb_rx_handler(struct work_struct *work)
> goto free_or_reuse;
> }
>
> - memset(&desc_info, 0, sizeof(desc_info));
> - rtw89_chip_query_rxdesc(rtwdev, &desc_info, rx_skb->data, 0);
> + pkt_ptr = rx_skb->data;
> + remaining = rx_skb->len;
>
> - skb = rtw89_alloc_skb_for_rx(rtwdev, desc_info.pkt_size);
> - if (!skb) {
> - rtw89_debug(rtwdev, RTW89_DBG_HCI,
> - "failed to allocate RX skb of size %u\n",
> - desc_info.pkt_size);
> - goto free_or_reuse;
> - }
> + do {
> + memset(&desc_info, 0, sizeof(desc_info));
> + rtw89_chip_query_rxdesc(rtwdev, &desc_info, pkt_ptr, 0);
>
> - pkt_offset = desc_info.offset + desc_info.rxd_len;
> + pkt_offset = desc_info.offset + desc_info.rxd_len;
> + if (remaining < (pkt_offset + desc_info.pkt_size)) {
> + rtw89_debug(rtwdev, RTW89_DBG_HCI,
> + "Failed to get remaining RX pkt %u > %u\n",
> + pkt_offset + desc_info.pkt_size, remaining);
> + goto free_or_reuse;
> + }
>
> - skb_put_data(skb, rx_skb->data + pkt_offset,
> - desc_info.pkt_size);
> + skb = rtw89_alloc_skb_for_rx(rtwdev, desc_info.pkt_size);
> + if (!skb) {
> + rtw89_debug(rtwdev, RTW89_DBG_HCI,
> + "failed to allocate RX skb of size %u\n",
> + desc_info.pkt_size);
> + goto free_or_reuse;
> + }
> +
> + skb_put_data(skb, pkt_ptr + pkt_offset, desc_info.pkt_size);
> + rtw89_core_rx(rtwdev, &desc_info, skb);
>
> - rtw89_core_rx(rtwdev, &desc_info, skb);
> + /* next frame */
> + pkt_offset += desc_info.pkt_size;
> + aligned_offset = ALIGN(pkt_offset, info->rx_agg_alignment);
> + pkt_ptr += aligned_offset;
> + remaining -= aligned_offset;
> + } while (remaining > 0);
>
> free_or_reuse:
> if (skb_queue_len(&rtwusb->rx_free_queue) >= RTW89_USB_RX_SKB_NUM)
> @@ -745,6 +763,44 @@ static int rtw89_usb_ops_mac_pre_deinit(struct rtw89_dev *rtwdev)
> return 0; /* Nothing to do. */
> }
>
> +static void usb_rx_agg_cfg_v1(struct rtw89_dev *rtwdev)
Maybe give the new functions the usual "rtw89_" prefix?
> +{
> + const u32 rxagg_0 = FIELD_PREP_CONST(B_AX_RXAGG_0_EN, 1) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_NUM_TH, 0) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_TIME_32US_TH, 32) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_BUF_SZ_4K, 5);
> +
> + rtw89_write32(rtwdev, R_AX_RXAGG_0, rxagg_0);
> +}
> +
> +static void usb_rx_agg_cfg_v2(struct rtw89_dev *rtwdev)
> +{
> + const u32 rxagg_0 = FIELD_PREP_CONST(B_AX_RXAGG_0_EN, 1) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_NUM_TH, 255) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_TIME_32US_TH, 32) |
> + FIELD_PREP_CONST(B_AX_RXAGG_0_BUF_SZ_K, 20);
> +
> + rtw89_write32(rtwdev, R_AX_RXAGG_0_V1, rxagg_0);
> + rtw89_write32(rtwdev, R_AX_RXAGG_1_V1, 0x1F);
> +}
> +
> +static void usb_rx_agg_cfg(struct rtw89_dev *rtwdev)
> +{
> + switch (rtwdev->chip->chip_id) {
> + case RTL8851B:
> + case RTL8852A:
> + case RTL8852B:
> + usb_rx_agg_cfg_v1(rtwdev);
> + break;
> + case RTL8852C:
> + usb_rx_agg_cfg_v2(rtwdev);
> + break;
> + default:
> + rtw89_warn(rtwdev, "%s: USB RX agg not support\n", __func__);
> + return;
> + }
The subject only mentions RTL8832CU and RTL8851BU, but looks like you
implemented it for every chip currently supported.
> +}
> +
> static int rtw89_usb_ops_mac_post_init(struct rtw89_dev *rtwdev)
> {
> struct rtw89_usb *rtwusb = rtw89_usb_priv(rtwdev);
> @@ -773,6 +829,8 @@ static int rtw89_usb_ops_mac_post_init(struct rtw89_dev *rtwdev)
> rtw89_write8(rtwdev, info->usb_endpoint_2 + 1, NUMP);
> }
>
> + usb_rx_agg_cfg(rtwdev);
> +
> return 0;
> }
>
> diff --git a/drivers/net/wireless/realtek/rtw89/usb.h b/drivers/net/wireless/realtek/rtw89/usb.h
> index 203ec8e993e9..afc62c1f687f 100644
> --- a/drivers/net/wireless/realtek/rtw89/usb.h
> +++ b/drivers/net/wireless/realtek/rtw89/usb.h
> @@ -20,6 +20,17 @@
> #define RTW89_MAX_ENDPOINT_NUM 9
> #define RTW89_MAX_BULKOUT_NUM 7
>
> +#define R_AX_RXAGG_0_V1 0x6000
> +#define B_AX_RXAGG_0_EN BIT(31)
> +#define B_AX_RXAGG_0_NUM_TH GENMASK(23, 16)
> +#define B_AX_RXAGG_0_TIME_32US_TH GENMASK(15, 8)
> +#define B_AX_RXAGG_0_BUF_SZ_K GENMASK(7, 0)
Is it missing a number before the letter K ?
> +
> +#define R_AX_RXAGG_1_V1 0x6004
> +
> +#define R_AX_RXAGG_0 0x8900
> +#define B_AX_RXAGG_0_BUF_SZ_4K GENMASK(7, 0)
> +
> struct rtw89_usb_info {
> u32 usb_host_request_2;
> u32 usb_wlan0_1;
> @@ -27,6 +38,7 @@ struct rtw89_usb_info {
> u32 usb3_mac_npi_config_intf_0;
> u32 usb_endpoint_0;
> u32 usb_endpoint_2;
> + u8 rx_agg_alignment;
> u8 bulkout_id[RTW89_DMA_CH_NUM];
> };
>
>
> base-commit: 039cd522dc70151da13329a5e3ae19b1736f468a
^ permalink raw reply
* Re: 6.18.13 iwlwifi deadlock allocating cma while work-item is active.
From: Tejun Heo @ 2026-03-10 19:48 UTC (permalink / raw)
To: Ben Greear
Cc: Johannes Berg, linux-wireless, Miriam Rachel, linux-mm,
linux-kernel
In-Reply-To: <abB1b098e83bLT5v@slm.duckdns.org>
On Tue, Mar 10, 2026 at 09:47:59AM -1000, Tejun Heo wrote:
> Hello,
>
> On Tue, Mar 10, 2026 at 12:18:49PM -0700, Ben Greear wrote:
> ...
> > But could this work-item (re)initialization also explain work-queue system going
> > weird? Just using kthreads, which 'fixes' the problem for me,
> > really shouldn't make a difference to the code below, so probably
> > it is not related?
>
> Oh, re-initing can deifnitely corrupt things. Workqueue shares work list
> across all work items sharing the pool, so the blast radius can be bigger.
> ie. It'd be *possible* for kthread_worker to get lucky.
BTW, if you enable CONFIG_DEBUG_OBJECTS_WORK, re-init should trigger a dump.
Thanks.
--
tejun
^ permalink raw reply
* Re: 6.18.13 iwlwifi deadlock allocating cma while work-item is active.
From: Tejun Heo @ 2026-03-10 19:47 UTC (permalink / raw)
To: Ben Greear
Cc: Johannes Berg, linux-wireless, Miriam Rachel, linux-mm,
linux-kernel
In-Reply-To: <729164a1-9dd4-c9a4-f092-d93d775257e0@candelatech.com>
Hello,
On Tue, Mar 10, 2026 at 12:18:49PM -0700, Ben Greear wrote:
...
> But could this work-item (re)initialization also explain work-queue system going
> weird? Just using kthreads, which 'fixes' the problem for me,
> really shouldn't make a difference to the code below, so probably
> it is not related?
Oh, re-initing can deifnitely corrupt things. Workqueue shares work list
across all work items sharing the pool, so the blast radius can be bigger.
ie. It'd be *possible* for kthread_worker to get lucky.
Thanks.
--
tejun
^ permalink raw reply
* Re: 6.18.13 iwlwifi deadlock allocating cma while work-item is active.
From: Ben Greear @ 2026-03-10 19:18 UTC (permalink / raw)
To: Tejun Heo
Cc: Johannes Berg, linux-wireless, Miriam Rachel, linux-mm,
linux-kernel
In-Reply-To: <5b9b93df8774810a43fceb359906604b@kernel.org>
On 3/10/26 11:06, Tejun Heo wrote:
> Hello,
>
> Thanks for the detailed dump. One thing that doesn't look right is the
> number of pending work items on pool 22 (CPU 5). The pool reports 2 idle
> workers, yet there are 7+ work items sitting in the pending list across
> multiple workqueues. If the pool were making forward progress, those items
> would have been picked up by the idle workers. So, the pool itself seems to
> be stuck for some reason, and the cfg80211 mutex stall may be a consequence
> rather than the cause.
>
> Let's try using drgn on the crash dump. I'm attaching a prompt that you can
> feed to Claude (or any LLM with tool access to drgn). It contains workqueue
> internals documentation, drgn code snippets, and a systematic investigation
> procedure. The idea is:
>
> 1. Generate the crash dump when the deadlock is happening:
>
> echo c > /proc/sysrq-trigger
>
> 2. After the crash kernel boots, create the dump file:
>
> makedumpfile -c -d 31 /proc/vmcore /tmp/vmcore.dmp
>
> 3. Feed the attached prompt to Claude with drgn access to the dump. It
> should produce a Markdown report with its findings that you can post
> back here.
>
> This is a bit experimental, so let's see whether it works. Either way, the
> report should at least give us concrete data points to work with.
>
> Thanks.
Thanks for that. It will probably be a few days before I flip back to debugging
that lockup as I'm trying to get something ready for our internal release (using
kthread work-around).
While working on another bug, I found evidence (but not proof yet), that this code below
can be called multiple times for the same object. The bug I'm tracking is that this
may be the cause of list corruption (my debugging logs and work-arounds are in the method below).
But could this work-item (re)initialization also explain work-queue system going
weird? Just using kthreads, which 'fixes' the problem for me,
really shouldn't make a difference to the code below, so probably
it is not related?
void ieee80211_link_init(struct ieee80211_sub_if_data *sdata,
int link_id,
struct ieee80211_link_data *link,
struct ieee80211_bss_conf *link_conf)
{
struct ieee80211_local *local = sdata->local;
bool deflink = link_id < 0;
lockdep_assert_wiphy(local->hw.wiphy);
if (link_id < 0)
link_id = 0;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
struct ieee80211_sub_if_data *ap_bss;
struct ieee80211_bss_conf *ap_bss_conf;
ap_bss = container_of(sdata->bss,
struct ieee80211_sub_if_data, u.ap);
ap_bss_conf = sdata_dereference(ap_bss->vif.link_conf[link_id],
ap_bss);
memcpy(link_conf, ap_bss_conf, sizeof(*link_conf));
}
link->sdata = sdata;
link->link_id = link_id;
link->conf = link_conf;
link_conf->link_id = link_id;
link_conf->vif = &sdata->vif;
link->ap_power_level = IEEE80211_UNSET_POWER_LEVEL;
link->user_power_level = sdata->local->user_power_level;
link_conf->txpower = INT_MIN;
wiphy_work_init(&link->csa.finalize_work,
ieee80211_csa_finalize_work);
wiphy_work_init(&link->color_change_finalize_work,
ieee80211_color_change_finalize_work);
wiphy_delayed_work_init(&link->color_collision_detect_work,
ieee80211_color_collision_detection_work);
/* I see some sort of list corruption where links don't get removed from chanctx
* lists. I think if we are in a list while here, that could cause it. deflink
* appears to have chance of doing that. So, remove from list first if
* it is indeed in one.
*/
if (WARN_ON_ONCE((link->assigned_chanctx_list.next != LIST_POISON1)
&& (link->assigned_chanctx_list.next != link->assigned_chanctx_list.prev)
&& (link->assigned_chanctx_list.next))) {
sdata_err(sdata, "link-init: %d called while already in an assigned-chan-ctx list, clearing.\n",
link_id);
list_del(&link->assigned_chanctx_list);
}
if (WARN_ON_ONCE((link->reserved_chanctx_list.next != LIST_POISON1)
&& (link->reserved_chanctx_list.next != link->reserved_chanctx_list.prev)
&& (link->reserved_chanctx_list.next))) {
sdata_err(sdata, "link-init: %d called while already in a reserved-chan-ctx list, clearing.\n",
link_id);
list_del(&link->reserved_chanctx_list);
}
INIT_LIST_HEAD(&link->assigned_chanctx_list);
INIT_LIST_HEAD(&link->reserved_chanctx_list);
wiphy_delayed_work_init(&link->dfs_cac_timer_work,
ieee80211_dfs_cac_timer_work);
if (!deflink) {
switch (sdata->vif.type) {
case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_AP_VLAN:
ether_addr_copy(link_conf->addr,
sdata->wdev.links[link_id].addr);
link_conf->bssid = link_conf->addr;
WARN_ON(!(sdata->wdev.valid_links & BIT(link_id)));
break;
case NL80211_IFTYPE_STATION:
/* station sets the bssid in ieee80211_mgd_setup_link */
break;
default:
WARN_ON(1);
}
ieee80211_link_debugfs_add(link);
}
rcu_assign_pointer(sdata->vif.link_conf[link_id], link_conf);
rcu_assign_pointer(sdata->link[link_id], link);
}
Thanks,
Ben
--
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc http://www.candelatech.com
^ permalink raw reply
* Re: [PATCH 00/61] treewide: Use IS_ERR_OR_NULL over manual NULL check - refactor
From: Kuan-Wei Chiu @ 2026-03-10 18:40 UTC (permalink / raw)
To: Philipp Hahn
Cc: amd-gfx, apparmor, bpf, ceph-devel, cocci, dm-devel, dri-devel,
gfs2, intel-gfx, intel-wired-lan, iommu, kvm, linux-arm-kernel,
linux-block, linux-bluetooth, linux-btrfs, linux-cifs, linux-clk,
linux-erofs, linux-ext4, linux-fsdevel, linux-gpio, linux-hyperv,
linux-input, linux-kernel, linux-leds, linux-media, linux-mips,
linux-mm, linux-modules, linux-mtd, linux-nfs, linux-omap,
linux-phy, linux-pm, linux-rockchip, linux-s390, linux-scsi,
linux-sctp, linux-security-module, linux-sh, linux-sound,
linux-stm32, linux-trace-kernel, linux-usb, linux-wireless,
netdev, ntfs3, samba-technical, sched-ext, target-devel,
tipc-discussion, v9fs
In-Reply-To: <20260310-b4-is_err_or_null-v1-0-bd63b656022d@avm.de>
Hi Philipp,
On Tue, Mar 10, 2026 at 12:48:26PM +0100, Philipp Hahn wrote:
> While doing some static code analysis I stumbled over a common pattern,
> where IS_ERR() is combined with a NULL check. For that there is
> IS_ERR_OR_NULL().
>
> I've written a Coccinelle patch to find and patch those instances.
> The patches follow grouped by subsystem.
>
> Patches 55-58 may be dropped as they have a (minor?) semantic change:
> They use WARN_ON() or WARN_ON_ONCE(), but only in the IS_ERR() path, not
> for the NULL check. Iff it is okay to print the warning also for NULL,
> then the patches can be applied.
>
> While generating the patch set `checkpatch` complained about mixing
> [un]likely() with IS_ERR_OR_NULL(), which already uses likely()
> internally. I found and fixed several locations, where that combination
> has been used.
Thanks for the patchset. However, I think we need a explanation for why
switching to IS_ERR_OR_NULL() is an improvement over the existing code.
IMHO, the necessity of IS_ERR_OR_NULL() often highlights a confusing or
flawed API design. It usually implies that the caller is unsure whether
a failure results in an error pointer or a NULL pointer. Rather than
doing a treewide conversion of this pattern, I believe it would be much
more meaningful to review these instances case-by-case and fix the
underlying APIs or caller logic instead.
Additionally, a treewide refactoring like this has the practical
drawback of creating unnecessary merge conflicts when backporting to
stable trees.
Regards,
Kuan-Wei
^ permalink raw reply
* Re: [PATCH 03/61] ceph: Prefer IS_ERR_OR_NULL over manual NULL check
From: Viacheslav Dubeyko @ 2026-03-10 18:13 UTC (permalink / raw)
To: dm-devel@lists.linux.dev, phahn-oss@avm.de,
intel-wired-lan@lists.osuosl.org, linux-erofs@lists.ozlabs.org,
linux-security-module@vger.kernel.org, kvm@vger.kernel.org,
linux-sctp@vger.kernel.org, linux-pm@vger.kernel.org,
apparmor@lists.ubuntu.com, linux-ext4@vger.kernel.org,
amd-gfx@lists.freedesktop.org, linux-clk@vger.kernel.org,
linux-mips@vger.kernel.org, linux-media@vger.kernel.org,
netdev@vger.kernel.org, iommu@lists.linux.dev,
linux-block@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-fsdevel@vger.kernel.org, linux-usb@vger.kernel.org,
sched-ext@lists.linux.dev, linux-btrfs@vger.kernel.org,
linux-bluetooth@vger.kernel.org, linux-s390@vger.kernel.org,
samba-technical@lists.samba.org, intel-gfx@lists.freedesktop.org,
linux-trace-kernel@vger.kernel.org, ntfs3@lists.linux.dev,
linux-phy@lists.infradead.org, v9fs@lists.linux.dev,
ceph-devel@vger.kernel.org, tipc-discussion@lists.sourceforge.net,
linux-mtd@lists.infradead.org, linux-scsi@vger.kernel.org,
target-devel@vger.kernel.org, linux-gpio@vger.kernel.org,
cocci@inria.fr, linux-sh@vger.kernel.org,
linux-rockchip@lists.infradead.org,
linux-stm32@st-md-mailman.stormreply.com,
linux-cifs@vger.kernel.org, linux-modules@vger.kernel.org,
linux-sound@vger.kernel.org, bpf@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, linux-input@vger.kernel.org,
linux-leds@vger.kernel.org, dri-devel@lists.freedesktop.org,
linux-hyperv@vger.kernel.org, linux-mm@kvack.org,
linux-nfs@vger.kernel.org, gfs2@lists.linux.dev,
linux-wireless@vger.kernel.org, linux-omap@vger.kernel.org
Cc: idryomov@gmail.com, Alex Markuze, slava@dubeyko.com
In-Reply-To: <20260310-b4-is_err_or_null-v1-3-bd63b656022d@avm.de>
On Tue, 2026-03-10 at 12:48 +0100, Philipp Hahn wrote:
> Prefer using IS_ERR_OR_NULL() over using IS_ERR() and a manual NULL
> check.
>
> Change generated with coccinelle.
>
> To: Ilya Dryomov <idryomov@gmail.com>
> To: Alex Markuze <amarkuze@redhat.com>
> To: Viacheslav Dubeyko <slava@dubeyko.com>
> Cc: ceph-devel@vger.kernel.org
> Cc: linux-kernel@vger.kernel.org
> Signed-off-by: Philipp Hahn <phahn-oss@avm.de>
> ---
> fs/ceph/dir.c | 2 +-
> fs/ceph/snap.c | 2 +-
> 2 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
> index 86d7aa594ea99335af3e91a95c0a418fdc1b8a8a..934250748ae4fd4c148fd27bdf91175047c2877d 100644
> --- a/fs/ceph/dir.c
> +++ b/fs/ceph/dir.c
> @@ -889,7 +889,7 @@ int ceph_handle_notrace_create(struct inode *dir, struct dentry *dentry)
> {
> struct dentry *result = ceph_lookup(dir, dentry, 0);
>
> - if (result && !IS_ERR(result)) {
> + if (!IS_ERR_OR_NULL(result)) {
> /*
> * We created the item, then did a lookup, and found
> * it was already linked to another inode we already
> diff --git a/fs/ceph/snap.c b/fs/ceph/snap.c
> index 52b4c2684f922bfed39550311e793bfe3622cd26..528ad581be160713f91416115659e2dc6f259576 100644
> --- a/fs/ceph/snap.c
> +++ b/fs/ceph/snap.c
> @@ -902,7 +902,7 @@ int ceph_update_snap_trace(struct ceph_mds_client *mdsc,
> bad:
> err = -EIO;
> fail:
> - if (realm && !IS_ERR(realm))
> + if (!IS_ERR_OR_NULL(realm))
> ceph_put_snap_realm(mdsc, realm);
> if (first_realm)
> ceph_put_snap_realm(mdsc, first_realm);
Looks good.
Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
Thanks,
Slava.
^ permalink raw reply
* Re: 6.18.13 iwlwifi deadlock allocating cma while work-item is active.
From: Tejun Heo @ 2026-03-10 18:06 UTC (permalink / raw)
To: Ben Greear
Cc: Johannes Berg, linux-wireless, Miriam Rachel, linux-mm,
linux-kernel
In-Reply-To: <bba74cab-7305-a052-7e1c-7a7736ba4531@candelatech.com>
[-- Attachment #1: Type: text/plain, Size: 1255 bytes --]
Hello,
Thanks for the detailed dump. One thing that doesn't look right is the
number of pending work items on pool 22 (CPU 5). The pool reports 2 idle
workers, yet there are 7+ work items sitting in the pending list across
multiple workqueues. If the pool were making forward progress, those items
would have been picked up by the idle workers. So, the pool itself seems to
be stuck for some reason, and the cfg80211 mutex stall may be a consequence
rather than the cause.
Let's try using drgn on the crash dump. I'm attaching a prompt that you can
feed to Claude (or any LLM with tool access to drgn). It contains workqueue
internals documentation, drgn code snippets, and a systematic investigation
procedure. The idea is:
1. Generate the crash dump when the deadlock is happening:
echo c > /proc/sysrq-trigger
2. After the crash kernel boots, create the dump file:
makedumpfile -c -d 31 /proc/vmcore /tmp/vmcore.dmp
3. Feed the attached prompt to Claude with drgn access to the dump. It
should produce a Markdown report with its findings that you can post
back here.
This is a bit experimental, so let's see whether it works. Either way, the
report should at least give us concrete data points to work with.
Thanks.
--
tejun
[-- Attachment #2: wq-drgn-prompt.txt --]
[-- Type: text/plain, Size: 27606 bytes --]
# Workqueue Lockup Investigation with drgn
You are investigating a Linux kernel workqueue lockup using drgn on a crash
dump. The system reported a workqueue pool stall on CPU 5 with `reg_todo`
[cfg80211] stuck for ~57500 seconds. Your job is to determine the root cause.
## HOW TO RUN DRGN
```bash
# Install drgn (if not already installed):
# pip3 install drgn
# OR on Fedora: dnf install drgn
# Run drgn on a crash dump:
drgn -c /path/to/vmcore
# If symbols aren't found automatically, point to the vmlinux:
drgn -c /path/to/vmcore -s /path/to/vmlinux
# For modules, point to the module directory:
drgn -c /path/to/vmcore -s /path/to/vmlinux -s /lib/modules/$(uname -r)/
# Inside drgn, 'prog' is the program object. You can run Python interactively
# or pass a script with -e or -s:
drgn -c /path/to/vmcore -s /path/to/vmlinux -e 'print(prog["jiffies"])'
```
All code blocks in this document are Python code to run inside the drgn
interactive shell or via `-e`.
## METHODOLOGY — READ THIS FIRST
**CRITICAL RULES — violating these will produce wrong conclusions:**
1. **NEVER jump on any specific lead without concrete evidence.** Do not
assume you know the answer from the dmesg alone. The dmesg gives you a
starting point, not a conclusion.
2. **Draw conclusions if and only if the hard facts support them.** Every
claim you make must be backed by specific drgn output — an address, a
value, a stack trace. If you cannot show the evidence, say "I don't have
evidence for this" and move on.
3. **Present results and thought process with specific, concrete evidence.**
Show the drgn commands you ran and the relevant output. Then explain what
that output means. Evidence first, interpretation second.
4. **Think holistically — do NOT separate workqueue stall from lock stalls.**
A stuck workqueue pool can CAUSE what looks like deadlocks elsewhere.
Work items that are expected to run but cannot (because the pool is
stuck) will stall anything waiting on their completion. A mutex holder
might be waiting for a work item that will never run. What looks like a
"deadlock" might actually be a consequence of the pool stall, not the
cause. Always consider both directions of causality.
5. **Check everything systematically.** Do not skip steps because you think
you already know the answer. Complete Phase 1 fully before moving to
Phase 2. If the pool has multiple pending work items that are not being
processed, the pool IS stuck — do not dismiss this as "a transient
snapshot."
## WORKQUEUE ARCHITECTURE
### Overview
The Linux workqueue subsystem processes deferred work using kernel threads
(workers) organized into pools:
- **worker_pool**: A group of kernel threads (workers) that share a worklist.
Each CPU has two standard pools: pool[0] (normal priority) and pool[1]
(high priority). Unbound pools serve work not tied to a specific CPU.
- **workqueue_struct**: A named workqueue (e.g., "events",
"events_power_efficient"). Each workqueue connects to pools via
pool_workqueue (pwq) structures — one pwq per pool the workqueue uses.
Multiple workqueues share the same underlying pool.
- **pool_workqueue (pwq)**: Links a workqueue to a pool. Tracks nr_active
(how many work items from this workqueue are active in the pool) and
enforces max_active limits. Work items exceeding max_active go to
pwq->inactive_works instead of pool->worklist.
- **worker**: A kernel thread that picks work from pool->worklist and
executes it. Workers are either idle (on pool->idle_list) or busy
(in pool->busy_hash, executing a work item).
### Concurrency Management (CMWQ)
For bound (per-CPU) pools, the workqueue uses a concurrency management
protocol based on `pool->nr_running`:
- **nr_running** counts workers actively running on CPU (not sleeping, not
idle, not marked CPU_INTENSIVE).
- When a worker sleeps (e.g., waiting on a mutex), the scheduler calls
`wq_worker_sleeping()` which decrements nr_running. If nr_running hits 0
and there is pending work, `kick_pool()` wakes an idle worker.
- When a worker wakes up, `wq_worker_running()` increments nr_running.
- The decision functions:
- `need_more_worker(pool)`: `!list_empty(&pool->worklist) && !pool->nr_running`
— need a worker if work is pending AND nobody is running.
- `may_start_working(pool)`: `pool->nr_idle` — can proceed only if there
are idle workers remaining (so there's always a reserve).
- `keep_working(pool)`: `!list_empty(&pool->worklist) && pool->nr_running <= 1`
— current worker keeps going if work pending and it's the only runner.
- **Key insight**: If nr_running > 0, the pool assumes someone is handling
work and does NOT wake idle workers, even if work is pending. A stuck
nr_running > 0 with no worker actually on CPU would prevent all forward
progress.
### Worker Lifecycle and the 2-Idle-Worker Invariant
The worker_thread() main loop:
1. Wake up, leave idle state (nr_idle--)
2. Check `need_more_worker()` — if no work or nr_running > 0, go to sleep
3. Check `may_start_working()` — if nr_idle == 0, become manager and create
new workers before proceeding
4. Clear PREP flag, enter concurrency management (nr_running++)
5. Process work items from pool->worklist in a loop
6. When done, enter idle state (nr_idle++) and sleep
**The 2-idle-worker invariant**: The pool maintains at least 2 idle workers
(enforced by `too_many_workers()` which only culls when nr_idle > 2). This
ensures that when one idle worker wakes to process work (step 1: nr_idle--),
there is still at least one idle worker remaining. If nr_idle hits 0, the
woken worker must become the "manager" and create new workers before it can
process any work.
**Worker creation** (`create_worker()`): Allocates memory with GFP_KERNEL
and calls `kthread_create_on_node()`. Both operations can stall indefinitely
if the system is under memory pressure and reclaim is not making progress.
GFP_KERNEL allocations will not fail — they block in the allocator waiting
for pages. If memory reclaim is broken for any reason, `create_worker()` will
hang forever. This would prevent the pool from recovering if it runs out of
idle workers.
**Mayday/rescuer mechanism**: If `create_worker()` cannot make progress, the
pool's mayday_timer fires and sends distress signals to workqueues that have
WQ_MEM_RECLAIM set. Those workqueues have a dedicated rescuer thread that
can process their work items without needing new workers. However, regular
workqueues like "events" do NOT have rescuers — if they run out of workers,
they are stuck.
### Watchdog
The pool watchdog checks whether pool->watchdog_ts has advanced. watchdog_ts
is updated each time a worker picks up a new work item from the worklist.
If watchdog_ts hasn't advanced for wq_watchdog_thresh seconds (default 30),
the pool is considered stalled. The "hung=Ns" in the stall warning shows
`jiffies - pool->watchdog_ts` converted to seconds.
## DATA STRUCTURE REFERENCE (for drgn)
### Accessing Pools
```python
from drgn.helpers.linux.percpu import per_cpu
from drgn.helpers.linux.list import list_for_each_entry
from drgn import Object, cast
# Per-CPU normal-priority pool for CPU N:
pool = per_cpu(prog["cpu_worker_pools"], cpu)[0]
# Per-CPU high-priority pool for CPU N:
pool = per_cpu(prog["cpu_worker_pools"], cpu)[1]
```
### worker_pool fields
```
pool.cpu — int, associated CPU (-1 for unbound)
pool.id — int, pool ID
pool.nr_running — int, workers currently running on CPU
pool.nr_workers — int, total workers
pool.nr_idle — int, currently idle workers
pool.worklist — list_head, pending work items
pool.idle_list — list_head, idle workers
pool.workers — list_head, all workers (iterate via "node" member)
pool.busy_hash — hashtable of busy workers
pool.manager — struct worker *, current manager (or NULL)
pool.flags — uint: POOL_MANAGER_ACTIVE=0x2, POOL_DISASSOCIATED=0x4
pool.watchdog_ts — unsigned long, jiffies of last forward progress
pool.cpu_stall — bool, set by watchdog when stalled
```
### worker fields (iterate via pool.workers, link member "node")
```
worker.task — struct task_struct *, the kthread
worker.current_work — struct work_struct *, work being executed (NULL if idle)
worker.current_func — work_func_t, function of current work
worker.current_pwq — struct pool_workqueue *, pwq of current work
worker.current_at — u64, ktime at start of current work
worker.sleeping — int, 1 if worker went to sleep (decremented nr_running)
worker.flags — uint: WORKER_DIE=0x2, WORKER_IDLE=0x4, WORKER_PREP=0x8,
WORKER_CPU_INTENSIVE=0x40, WORKER_UNBOUND=0x80
worker.id — int, worker ID (shows in task name as kworker/CPU:ID)
worker.last_active — unsigned long, jiffies of last activity
worker.pool — struct worker_pool *, associated pool
worker.scheduled — list_head, scheduled works for this worker
```
### work_struct fields (iterate via pool.worklist, link member "entry")
```
work.data — atomic_long_t, encodes pwq pointer + flags
work.func — work_func_t, the function to execute
work.entry — list_head, linkage in worklist
# Extracting pwq from work->data:
data = work.data.counter.value_()
WORK_STRUCT_PWQ_BIT = 1 << 2
WORK_STRUCT_PWQ_SHIFT = 8 # bits 0-7 are flags, bits 8+ are pwq pointer
if data & WORK_STRUCT_PWQ_BIT:
pwq_addr = data & ~((1 << WORK_STRUCT_PWQ_SHIFT) - 1)
pwq = Object(prog, "struct pool_workqueue", address=pwq_addr)
wq_name = pwq.wq.name.string_().decode()
```
### pool_workqueue fields
```
pwq.pool — struct worker_pool *, the pool
pwq.wq — struct workqueue_struct *, the workqueue
pwq.nr_active — int, active work items from this wq in this pool
pwq.inactive_works — list_head, work items waiting for nr_active < max_active
pwq.stats[] — u64 array: [0]=STARTED, [1]=COMPLETED, [2]=CPU_TIME,
[3]=CPU_INTENSIVE, [4]=CM_WAKEUP, [5]=REPATRIATED,
[6]=MAYDAY, [7]=RESCUED
```
### workqueue_struct fields
```
wq.name — char[], workqueue name
wq.flags — uint, WQ_UNBOUND=0x2, WQ_FREEZABLE=0x4,
WQ_MEM_RECLAIM=0x8, WQ_HIGHPRI=0x10
wq.max_active — int, max concurrent work items per pwq
wq.cpu_pwq — per-cpu pointer to pwqs (for bound workqueues)
wq.pwqs — list_head, all pwqs (iterate via "pwqs_node")
wq.rescuer — struct worker * (non-NULL if WQ_MEM_RECLAIM)
```
### Mutex inspection
```python
# struct mutex has an owner field (atomic_long_t)
# Low 3 bits are flags, remaining bits are task_struct pointer
owner_val = mutex.owner.counter.value_()
owner_ptr = owner_val & ~0x7
if owner_ptr:
owner_task = Object(prog, "struct task_struct", address=owner_ptr)
print(f"mutex owner: {owner_task.comm.string_().decode()} "
f"pid={owner_task.pid.value_()}")
for frame in prog.stack_trace(owner_task):
print(f" {frame}")
```
### Stack traces
```python
# IMPORTANT: prog.stack_trace() takes a task_struct or PID, NOT a CPU number
# By task_struct pointer:
for frame in prog.stack_trace(task):
print(frame)
# By PID:
for frame in prog.stack_trace(pid_number):
print(frame)
```
### Jiffies time delta
```python
jiffies = prog["jiffies"].value_()
hz = 1000 # CONFIG_HZ, usually 1000 on x86 — verify with the kernel config
delta_jiffies = jiffies - pool.watchdog_ts.value_()
delta_seconds = delta_jiffies / hz
```
## INVESTIGATION PROCEDURE
### Phase 1: Is the workqueue pool stuck?
The dmesg says pool 22 (cpus=5, normal priority) is hung for ~57500s with
multiple pending work items. If a pool has pending work items that are not
being processed, the pool IS stuck. Do not dismiss pending items as "about
to be run" — at 57500s, anything pending is stuck.
Your goal in this phase is to determine WHY the pool is stuck: is it a
concurrency management state bug (nr_running wrong), a worker shortage
(no idle workers, can't create new ones), or something else?
**Step 1.1: Pool overview**
```python
cpu = 5
pool = per_cpu(prog["cpu_worker_pools"], cpu)[0]
print(f"Pool {pool.id.value_()} on CPU {pool.cpu.value_()}")
print(f" nr_running: {pool.nr_running.value_()}")
print(f" nr_workers: {pool.nr_workers.value_()}")
print(f" nr_idle: {pool.nr_idle.value_()}")
print(f" flags: 0x{pool.flags.value_():x}")
print(f" cpu_stall: {pool.cpu_stall.value_()}")
print(f" manager: {pool.manager}")
jiffies = prog["jiffies"].value_()
wts = pool.watchdog_ts.value_()
print(f" watchdog_ts: {wts} (jiffies={jiffies}, delta={jiffies - wts})")
print(f" worklist empty: {pool.worklist.next.value_() == pool.worklist.address_of_().value_()}")
```
**What to look for:**
- **nr_running**: If > 0 but no worker is actually executing on CPU, the
concurrency management thinks someone is running when nobody is. This
would prevent idle workers from being woken. Check every worker to verify
whether nr_running matches reality.
- **nr_idle**: If 0 and there's pending work, the pool has no reserve
workers. Check if a manager is active trying to create one — and if so,
what the manager is stuck on (likely a GFP_KERNEL allocation that won't
return).
- **nr_workers**: Compare to nr_idle to see how many are busy.
- **flags**: Check POOL_MANAGER_ACTIVE (0x2) — is someone trying to create
workers?
**Step 1.2: Enumerate ALL workers**
```python
WORKER_DIE = 0x2
WORKER_IDLE = 0x4
WORKER_PREP = 0x8
WORKER_CPU_INTENSIVE = 0x40
WORKER_UNBOUND = 0x80
for worker in list_for_each_entry("struct worker",
pool.workers.address_of_(), "node"):
task = worker.task
pid = task.pid.value_()
flags = worker.flags.value_()
state = []
if flags & WORKER_DIE: state.append("DIE")
if flags & WORKER_IDLE: state.append("IDLE")
if flags & WORKER_PREP: state.append("PREP")
if flags & WORKER_CPU_INTENSIVE: state.append("CPU_INTENSIVE")
if flags & WORKER_UNBOUND: state.append("UNBOUND")
if not state: state.append("RUNNING")
cur = worker.current_work
func_name = str(worker.current_func) if int(cur) else "(none)"
sleeping = worker.sleeping.value_()
last_active = worker.last_active.value_()
print(f" worker {worker.id.value_()}: pid={pid}, "
f"flags=0x{flags:x} [{','.join(state)}], "
f"sleeping={sleeping}, last_active={last_active}, "
f"current_func={func_name}")
# Stack trace for ALL non-idle workers:
if not (flags & WORKER_IDLE):
try:
print(f" Stack trace:")
for frame in prog.stack_trace(task):
print(f" {frame}")
except Exception as e:
print(f" (stack trace failed: {e})")
# Also check idle workers' task state — are they truly sleeping idle?
else:
tstate = task.__state.value_() if hasattr(task, '__state') else task.state.value_()
print(f" task state: {tstate}")
```
**What to look for:**
- For each non-idle worker: What is it doing? Is it sleeping on a lock?
Is it stuck in an allocation? Is it the manager trying to create workers?
- For idle workers: Are they in TASK_IDLE state as expected? If not,
something is wrong.
- Cross-check: Does the count of non-IDLE, non-PREP, non-CPU_INTENSIVE
workers match nr_running? If not, the concurrency management state is
inconsistent.
- Check worker.sleeping for non-idle workers: if sleeping==1, the worker
went through wq_worker_sleeping() and decremented nr_running. If all
non-idle workers have sleeping==1, nr_running should be 0.
**Step 1.3: Check pending work items**
```python
count = 0
for work in list_for_each_entry("struct work_struct",
pool.worklist.address_of_(), "entry"):
data = work.data.counter.value_()
func = str(work.func)
wq_name = "?"
if data & (1 << 2): # WORK_STRUCT_PWQ_BIT
pwq_addr = data & ~((1 << 8) - 1)
try:
pwq = Object(prog, "struct pool_workqueue", address=pwq_addr)
wq_name = pwq.wq.name.string_().decode()
except:
wq_name = f"(pwq@{pwq_addr:#x})"
print(f" [{count}] func={func}, wq={wq_name}")
count += 1
print(f"Total pending: {count}")
```
These are work items waiting to be executed. If there are multiple items and
idle workers exist, something is preventing the workers from picking them up.
Correlate with nr_running and worker states from Step 1.2.
**Step 1.4: Check pwq statistics**
Find pwqs associated with this pool and check started vs completed counts:
```python
# Iterate all workqueues and find pwqs for this pool
pool_id = pool.id.value_()
for wq in list_for_each_entry("struct workqueue_struct",
prog["workqueues"].address_of_(), "list"):
try:
pwq = per_cpu(wq.cpu_pwq, cpu)
if pwq.pool.id.value_() == pool_id:
started = pwq.stats[0].value_()
completed = pwq.stats[1].value_()
nr_active = pwq.nr_active.value_()
if started > 0 or nr_active > 0:
print(f" wq '{wq.name.string_().decode()}': "
f"started={started}, completed={completed}, "
f"in_flight={started-completed}, "
f"nr_active={nr_active}, "
f"max_active={wq.max_active.value_()}")
except:
pass # unbound workqueues don't have cpu_pwq
```
**Step 1.5: Determine the stall mechanism**
Based on the above evidence, determine WHICH of these scenarios applies:
**Scenario A — nr_running stuck > 0**: nr_running is positive but no worker
is actually executing on CPU. All non-idle workers have sleeping==1
(decremented nr_running) or are in PREP state, yet nr_running hasn't
reached 0. This prevents kick_pool() from waking idle workers. This would
be a concurrency management bug.
**Scenario B — No idle workers, manager stuck**: nr_idle is 0,
POOL_MANAGER_ACTIVE is set, and the manager worker is stuck trying to create
a new worker (blocked in GFP_KERNEL allocation or kthread_create). The pool
cannot make progress because there are no idle workers to wake and creating
new ones is blocked. Check the manager's stack trace to see where it's stuck.
If it's in the page allocator, this suggests system-wide memory pressure
where reclaim is not working.
**Scenario C — Workers all blocked on locks**: There are workers processing
work items, but every single one is sleeping on a mutex/lock. nr_running
correctly went to 0, idle workers were woken, but they too picked up work
items that immediately blocked. Eventually all workers are blocked and
no idle workers remain.
**Scenario D — Something else**: If none of the above, describe exactly what
you see and what doesn't add up.
### Phase 2: Investigate the broader stall
**IMPORTANT**: Do not assume the lock that reg_todo is waiting on is the
"root cause." The pool being stuck can CAUSE lock stalls elsewhere. Consider:
- If work item X is expected to release lock L, and work item X is pending
on a stuck pool, then anything waiting on lock L will appear deadlocked —
but the real cause is the pool stall, not a lock ordering bug.
- The cfg80211 reg_todo waiting on a mutex might be a VICTIM, not the cause.
The mutex holder might itself be waiting for something that depends on the
stuck pool.
**Step 2.1: Identify what the stuck worker(s) are waiting on**
For each non-idle worker found in Step 1.2, examine its stack trace. If it's
in `__mutex_lock`, `__rwsem_down_*`, `schedule_preempt_disabled`, or similar,
identify which lock:
```python
# For a worker stuck in __mutex_lock, try to extract the lock:
for frame in prog.stack_trace(stuck_task):
if "mutex_lock" in str(frame):
try:
lock = frame["lock"]
print(f" Mutex at: {lock}")
owner_val = lock.owner.counter.value_()
owner_ptr = owner_val & ~0x7
if owner_ptr:
owner = Object(prog, "struct task_struct",
address=owner_ptr)
print(f" Owner: {owner.comm.string_().decode()} "
f"pid={owner.pid.value_()}")
print(f" Owner stack:")
for f in prog.stack_trace(owner):
print(f" {f}")
else:
print(f" No owner (owner_val=0x{owner_val:x})")
except Exception as e:
print(f" (couldn't extract lock: {e})")
```
**Step 2.2: Follow the dependency chain**
For each lock owner found above:
1. Is the owner running, sleeping, or in D state?
2. If sleeping — what is it waiting on? Another lock? A completion? I/O?
3. If waiting on another lock — find THAT lock's owner and repeat.
4. At each step: is the waited-on resource something that depends on a
work item running? If so, check which pool/workqueue that work item
would run on — is THAT pool also stuck?
Continue until you find either:
- A cycle (A→B→C→A) — true deadlock
- A task waiting on something that depends on the stuck pool — the pool
stall is the root cause, the "deadlock" is a symptom
- A task waiting on something unrelated (I/O, userspace, etc.)
**Step 2.3: Check other pools and CPUs**
The stall might not be limited to pool 22. Check all CPU pools:
```python
import os
nr_cpus = int(prog["nr_cpu_ids"])
for cpu in range(nr_cpus):
pool = per_cpu(prog["cpu_worker_pools"], cpu)[0]
nr_r = pool.nr_running.value_()
nr_w = pool.nr_workers.value_()
nr_i = pool.nr_idle.value_()
wts = pool.watchdog_ts.value_()
delta = (jiffies - wts) / 1000
empty = pool.worklist.next.value_() == pool.worklist.address_of_().value_()
if not empty or delta > 30:
print(f"CPU {cpu}: pool {pool.id.value_()}, nr_running={nr_r}, "
f"nr_workers={nr_w}, nr_idle={nr_i}, "
f"hung={delta:.0f}s, worklist_empty={empty}")
```
**Step 2.4: Check for system-wide memory pressure**
If the pool stall involves a manager stuck in allocation:
```python
# Check memory state
from drgn.helpers.linux.percpu import per_cpu
try:
for node in range(int(prog["nr_online_nodes"])):
# Check per-node free pages
pass # Use /proc/meminfo equivalent via drgn
except:
pass
# Check if any workers are stuck in page allocation:
from drgn.helpers.linux.pid import for_each_task
alloc_stuck = []
for task in for_each_task(prog):
tstate = task.__state.value_() if hasattr(task, '__state') else task.state.value_()
if tstate != 0: # not TASK_RUNNING
try:
for frame in prog.stack_trace(task):
if "alloc_pages" in str(frame) or "__alloc_pages" in str(frame):
alloc_stuck.append(
f"{task.comm.string_().decode()} pid={task.pid.value_()}")
break
except:
pass
if alloc_stuck:
print(f"Tasks stuck in page allocation: {len(alloc_stuck)}")
for t in alloc_stuck[:20]:
print(f" {t}")
```
**Step 2.5: Check all D-state tasks for patterns**
```python
from drgn.helpers.linux.pid import for_each_task
d_state_tasks = []
for task in for_each_task(prog):
tstate = task.__state.value_() if hasattr(task, '__state') else task.state.value_()
if tstate == 2: # TASK_UNINTERRUPTIBLE
comm = task.comm.string_().decode()
pid = task.pid.value_()
try:
frames = [str(f) for f in prog.stack_trace(task)]
# Categorize by what they're stuck on
category = "unknown"
for f in frames:
if "mutex_lock" in f: category = "mutex"; break
if "rwsem" in f: category = "rwsem"; break
if "alloc_pages" in f: category = "alloc"; break
if "wait_for_completion" in f: category = "completion"; break
if "worker_thread" in f: category = "wq_idle"; break
d_state_tasks.append((category, comm, pid, frames[0] if frames else "?"))
except:
d_state_tasks.append(("error", comm, pid, "?"))
# Group by category
from collections import Counter
cats = Counter(cat for cat, _, _, _ in d_state_tasks)
print(f"D-state tasks by category: {dict(cats)}")
print(f"Total D-state tasks: {len(d_state_tasks)}")
for cat, comm, pid, top_frame in sorted(d_state_tasks)[:30]:
print(f" [{cat}] {comm} pid={pid}: {top_frame}")
```
## REPORTING
Present your findings in this order:
1. **Pool State** — Hard facts from Phase 1: nr_running, nr_idle,
nr_workers, pending work count, watchdog delta. For each worker: state,
current function, sleeping flag, stack trace. These are raw facts — state
them without interpretation first.
2. **Stall Mechanism** — Based on the pool state, which scenario (A/B/C/D)
applies and why. Cite specific values: "nr_running=1 but only worker is
in __mutex_lock with sleeping=1, so nr_running should be 0" or
"nr_idle=0, POOL_MANAGER_ACTIVE=1, manager pid=X stuck in __alloc_pages."
3. **Dependency Analysis** — The lock/wait chain from Phase 2. For each
link: who is waiting, on what, who holds it, what are they doing. Note
explicitly whether any link depends on a work item that would run on the
stuck pool.
4. **Root Cause** — What started the stall. This might be:
- A workqueue concurrency management bug (nr_running inconsistency)
- A worker creation failure (memory pressure preventing GFP_KERNEL allocs)
- A lock ordering issue in cfg80211/networking that caused workers to
all block
- Something else entirely
State this with evidence. If you cannot determine root cause with
certainty, say so and list the candidates with evidence for/against each.
5. **Cascade Effects** — What other subsystems are stalled as a consequence,
and through what mechanism (blocked on same lock, waiting for stuck work
item, etc.).
For EVERY claim, cite the specific drgn output. If you cannot support a
claim, explicitly say so and mark it as hypothesis.
## OUTPUT FORMAT
Write your complete findings as a Markdown file. The file should be
self-contained and suitable for posting as a reply to the bug report on
LKML. Structure it as follows:
```markdown
# Workqueue Lockup Analysis — CPU 5 Pool
## 1. Pool State
(raw facts: nr_running, nr_idle, nr_workers, flags, watchdog delta)
### Workers
(table or list of every worker: id, pid, state, current_func, sleeping,
stack trace for non-idle workers)
### Pending Work Items
(list of all pending work items on the worklist with function and workqueue)
### PWQ Statistics
(started/completed counts for active pwqs on this pool)
## 2. Stall Mechanism
(which scenario applies and WHY, with specific values cited as evidence)
## 3. Lock / Dependency Analysis
(the chain: who waits on what, who holds it, what are THEY waiting on —
with addresses and stack traces at each step. note whether any link depends
on a work item on the stuck pool.)
## 4. Other Pools and System-Wide State
(any other stuck pools, D-state task summary, memory pressure indicators)
## 5. Root Cause
(what started the stall, with evidence. or candidates if uncertain.)
## 6. Cascade Effects
(what else is broken as a consequence)
```
Save this file and tell the user where it is so they can attach it to their
reply.
^ permalink raw reply
* Re: [PATCH v3] wifi: ath12k: fix endianness handling for SRNG ring pointer accesses
From: Jeff Johnson @ 2026-03-10 17:55 UTC (permalink / raw)
To: Alexander Wilhelm, Jeff Johnson
Cc: Baochen Qiang, ath12k, linux-wireless, linux-kernel
In-Reply-To: <20260119073923.764245-1-alexander.wilhelm@westermo.com>
On 1/18/2026 11:39 PM, Alexander Wilhelm wrote:
> The SRNG head and tail ring pointers are stored in device memory as
> little-endian values. On big-endian systems, direct dereferencing of these
> pointers leads to incorrect values being read or written, causing ring
> management issues and potentially breaking data flow.
>
> This patch ensures all accesses to SRNG ring pointers use the appropriate
> endianness conversions. This affects both read and write paths for source
> and destination rings, as well as debug output. The changes guarantee
> correct operation on both little- and big-endian architectures.
>
> Signed-off-by: Alexander Wilhelm <alexander.wilhelm@westermo.com>
> ---
> Changes in v3:
> - Rebase on latest 'ath' master
> - Use always 'le32_to_cpu()' macro for conversions
>
> Changes in v2:
> - Set '__le32 *' type for 'hp_addr/tp_addr' in both 'dst_ring' and 'src_ring'
> ---
> drivers/net/wireless/ath/ath12k/hal.c | 37 +++++++++++++++------------
> drivers/net/wireless/ath/ath12k/hal.h | 8 +++---
> 2 files changed, 25 insertions(+), 20 deletions(-)
I really hope to get your changes in.
With your changes sparse reports:
drivers/net/wireless/ath/ath12k/wifi7/hal.c:322:50: warning: incorrect type in assignment (different base types)
drivers/net/wireless/ath/ath12k/wifi7/hal.c:322:50: expected restricted __le32 [usertype] *hp_addr
drivers/net/wireless/ath/ath12k/wifi7/hal.c:322:50: got unsigned int [usertype] *
drivers/net/wireless/ath/ath12k/wifi7/hal.c:333:50: warning: incorrect type in assignment (different base types)
drivers/net/wireless/ath/ath12k/wifi7/hal.c:333:50: expected restricted __le32 [usertype] *tp_addr
drivers/net/wireless/ath/ath12k/wifi7/hal.c:333:50: got unsigned int [usertype] *
drivers/net/wireless/ath/ath12k/wifi7/hal.c:387:42: warning: incorrect type in assignment (different base types)
drivers/net/wireless/ath/ath12k/wifi7/hal.c:387:42: expected restricted __le32 [usertype] *tp_addr
drivers/net/wireless/ath/ath12k/wifi7/hal.c:387:42: got unsigned int [usertype] *
drivers/net/wireless/ath/ath12k/wifi7/hal.c:390:42: warning: incorrect type in assignment (different base types)
drivers/net/wireless/ath/ath12k/wifi7/hal.c:390:42: expected restricted __le32 [usertype] *hp_addr
drivers/net/wireless/ath/ath12k/wifi7/hal.c:390:42: got unsigned int [usertype] *
I'm also getting checkpatch issues:
drivers/net/wireless/ath/ath12k/hal.c:503: Lines should not end with a '('
drivers/net/wireless/ath/ath12k/hal.c:531: Lines should not end with a '('
drivers/net/wireless/ath/ath12k/hal.c:551: Lines should not end with a '('
Can you address these issues?
/jeff
^ permalink raw reply
* Re: [PATCH] wifi: rtw89: retry efuse physical map dump on transient failure
From: Christian Hewitt @ 2026-03-10 17:16 UTC (permalink / raw)
To: Ping-Ke Shih
Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
linux-kernel@vger.kernel.org
In-Reply-To: <5ad1b7d20d1745bab0638d15731e7ccd@realtek.com>
> On 9 Mar 2026, at 6:35 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
>
> Christian Hewitt <christianshewitt@gmail.com> wrote:
>>
>>> On 2 Mar 2026, at 10:04 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
>>>
>>> Christian Hewitt <christianshewitt@gmail.com> wrote:
>>>>> On 2 Mar 2026, at 9:47 am, Ping-Ke Shih <pkshih@realtek.com> wrote:
>>>>>
>>>>> Christian Hewitt <christianshewitt@gmail.com> wrote:
>>>>>> On Radxa Rock 5B with a RTL8852BE combo WiFi/BT card, the efuse
>>>>>> physical map dump intermittently fails with -EBUSY during probe.
>>>>>> The failure occurs in rtw89_dump_physical_efuse_map_ddv() where
>>>>>> read_poll_timeout_atomic() times out waiting for the B_AX_EF_RDY
>>>>>> bit after 1 second.
>>>>>
>>>>> I'm checking internally how we handle this case.
>
> Sorry for the late.
>
> We encountered WiFi/BT reading efuse at the same time causing similar
> problem as yours. The workaround is like yours, which adds timeout
> time.
>
>>>>>
>>>>> [...]
>>>>>
>>>>>>
>>>>>> For context, firmware also fails (and recovers) sometimes:
>>>>>
>>>>> Did you mean this doesn't always happen? sometimes?
>>>>
>>>> It’s another intermittent behaviour observed on this board (and not
>>>> related to the issue this patch targets). It occurs less frequently
>>>> than the efuse issue and the existing retry mechanism in the driver
>>>> ensures firmware load always succeeds.
>
> This might be the same cause due to reading efuse in firmware.
>
> Though we can add more timeout and retry times as workaround, I wonder
> if you can control loading time of WiFi and BT kernel modules?
>
> More, can you do experiment that you load BT module first, and then load
> WiFi module after 10 seconds (choose a large number intentionally, or
> even larger)?
https://paste.libreelec.tv/charmed-turkey.sh
I’ve run the above script ^ which removes the wifi and bt modules in
sequence then reloads them in the reverse order with a delay between
bt and wifi modules loading, then checks for error messages. Over 200
test cycles with a 10s delay all were clean (no errors). I also ran
cycles with a 2 second delay and 0 second delay before starting wifi
module load and those were clear too. I guess that proves sequencing
avoids the efuse contention issue? - although it’s not possible in
the real-world so not sure there’s huge value in knowing that :)
Christian
^ permalink raw reply
* Re: [PATCH v3 0/3] Replace uses of system_wq and system_unbound_wq
From: Marco Crivellari @ 2026-03-10 16:37 UTC (permalink / raw)
To: linux-kernel, linux-wireless
Cc: Tejun Heo, Lai Jiangshan, Frederic Weisbecker,
Sebastian Andrzej Siewior, Michal Hocko, Miri Korenblit,
Johannes Berg
In-Reply-To: <20260210143332.206146-1-marco.crivellari@suse.com>
On Tue, Feb 10, 2026 at 3:33 PM Marco Crivellari
<marco.crivellari@suse.com> wrote:
> [...]
> Marco Crivellari (3):
> wifi: iwlwifi: replace use of system_unbound_wq with system_dfl_wq
> wifi: iwlwifi: fw: replace use of system_unbound_wq with system_dfl_wq
> wifi: iwlwifi: mvm: replace use of system_wq with system_percpu_wq
>
> drivers/net/wireless/intel/iwlwifi/fw/dbg.c | 4 ++--
> drivers/net/wireless/intel/iwlwifi/iwl-trans.h | 2 +-
> drivers/net/wireless/intel/iwlwifi/mvm/tdls.c | 6 +++---
> 3 files changed, 6 insertions(+), 6 deletions(-)
Hi,
Gentle ping.
Thanks!
--
Marco Crivellari
L3 Support Engineer
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox