diff --git a/sys/compat/linuxkpi/common/include/linux/skbuff.h b/sys/compat/linuxkpi/common/include/linux/skbuff.h --- a/sys/compat/linuxkpi/common/include/linux/skbuff.h +++ b/sys/compat/linuxkpi/common/include/linux/skbuff.h @@ -706,6 +706,8 @@ SKB_TRACE(q); while ((skb = __skb_dequeue(q)) != NULL) kfree_skb(skb); + WARN_ONCE(skb_queue_len(q) != 0, "%s: queue %p not empty: %u", + __func__, q, skb_queue_len(q)); } static inline void diff --git a/sys/compat/linuxkpi/common/src/linux_80211.c b/sys/compat/linuxkpi/common/src/linux_80211.c --- a/sys/compat/linuxkpi/common/src/linux_80211.c +++ b/sys/compat/linuxkpi/common/src/linux_80211.c @@ -3815,7 +3815,20 @@ */ skb = dev_alloc_skb(hw->extra_tx_headroom + m->m_pkthdr.len); if (skb == NULL) { - ic_printf(ic, "ERROR %s: skb alloc failed\n", __func__); + static uint8_t skb_alloc_failures = 0; + + if (skb_alloc_failures++ == 0) { + int tid; + + sta = LSTA_TO_STA(lsta); + ic_printf(ic, "ERROR %s: skb alloc failed %d + %d, lsta %p sta %p ni %p\n", + __func__, hw->extra_tx_headroom, m->m_pkthdr.len, lsta, sta, ni); + for (tid = 0; tid < nitems(sta->txq); tid++) { + ltxq = TXQ_TO_LTXQ(sta->txq[tid]); + ic_printf(ic, " tid %d ltxq %p seen_dequeue %d stopped %d skb_queue_len %u\n", + tid, ltxq, ltxq->seen_dequeue, ltxq-> stopped, skb_queue_len(<xq->skbq)); + } + } ieee80211_free_node(ni); m_freem(m); return; diff --git a/sys/contrib/dev/rtw88/main.h b/sys/contrib/dev/rtw88/main.h --- a/sys/contrib/dev/rtw88/main.h +++ b/sys/contrib/dev/rtw88/main.h @@ -735,6 +735,9 @@ struct sk_buff_head queue; atomic_t sn; struct timer_list purge_timer; +#if defined(__FreeBSD__) + int firstqtime; +#endif }; struct rtw_ra_report { diff --git a/sys/contrib/dev/rtw88/tx.c b/sys/contrib/dev/rtw88/tx.c --- a/sys/contrib/dev/rtw88/tx.c +++ b/sys/contrib/dev/rtw88/tx.c @@ -192,7 +192,25 @@ spin_lock_irqsave(&tx_report->q_lock, flags); qlen = skb_queue_len(&tx_report->queue); if (qlen > 0) +#if defined(__linux__) skb_queue_purge(&tx_report->queue); +#elif defined(__FreeBSD__) + { + struct sk_buff *skb; + + /* + * We only free too old entries; all the others still have + * a chance to be picked up by rtw_tx_report_handle(). + */ + while ((skb = __skb_dequeue(&tx_report->queue)) != NULL && + time_after(tx_report->firstqtime + RTW_TX_PROBE_TIMEOUT, skb->ip_summed)) + kfree_skb(skb); + + } + /* Reset the firstqtime so any next packets modifies the timer. */ + tx_report->firstqtime = 0; +#endif + spin_unlock_irqrestore(&tx_report->q_lock, flags); /* @@ -212,6 +230,9 @@ struct rtw_tx_report *tx_report = &rtwdev->tx_report; unsigned long flags; u8 *drv_data; +#if defined(__FreeBSD__) + uint32_t qlen; +#endif /* pass sn to tx report handler through driver data */ drv_data = (u8 *)IEEE80211_SKB_CB(skb)->status.status_driver_data; @@ -219,9 +240,33 @@ spin_lock_irqsave(&tx_report->q_lock, flags); __skb_queue_tail(&tx_report->queue, skb); +#if defined(__linux__) spin_unlock_irqrestore(&tx_report->q_lock, flags); mod_timer(&tx_report->purge_timer, jiffies + RTW_TX_PROBE_TIMEOUT); +#elif defined(__FreeBSD__) + /* + * ip_summed is unused in rtw88 so abuse it to test if this helps + * finding / preventing the skb leak; this is possible in + * FreeBSD at the moment as both the field and jiffies are int. + */ + skb->ip_summed = jiffies; + + /* + * Initialize the first time (after the queue is emopty). + * rtw_tx_report_purge_timer() will update the "firstqtime" + * whenever it finishes clearing the expired entries. + * Should get us into a rolling cleanup and never possibly + * queue endlessly. + */ + qlen = skb_queue_len(&tx_report->queue); + if (qlen == 1 || tx_report->firstqtime == 0) + tx_report->firstqtime = jiffies; + spin_unlock_irqrestore(&tx_report->q_lock, flags); + + if (time_before(jiffies, tx_report->firstqtime + RTW_TX_PROBE_TIMEOUT)) + mod_timer(&tx_report->purge_timer, jiffies + RTW_TX_PROBE_TIMEOUT); +#endif } EXPORT_SYMBOL(rtw_tx_report_enqueue);