ath9k: fix powersave frame filtering/buffering in AP mode
This patch fixes a long standing issue of pending packets in the queue being sent (and retransmitted many times) to sleeping stations. This was made worse by aggregation through driver-internal retransmitting of A-MDPU subframes. Previously the hardware tx filter was cleared unconditionally for every single packet - with this patch it uses the IEEE80211_TX_CTL_CLEAR_PS_FILT for unaggregated frames. A sta_notify driver op is added to stop aggregation for stations when they enter powersave mode. Subframes stay buffered inside the driver, to ensure that the BlockAck window keeps a sane state. Since the driver uses software aggregation, the clearing of the tx filter needs to be handled by the driver instead of mac80211 for aggregated frames. Signed-off-by: Felix Fietkau <nbd@openwrt.org> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
8e22ad323f
commit
5519541d5a
8 changed files with 145 additions and 15 deletions
|
@ -290,7 +290,6 @@ static void ar9002_hw_set11n_txdesc(struct ath_hw *ah, void *ds,
|
||||||
| (flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0)
|
| (flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0)
|
||||||
| SM(txPower, AR_XmitPower)
|
| SM(txPower, AR_XmitPower)
|
||||||
| (flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0)
|
| (flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0)
|
||||||
| (flags & ATH9K_TXDESC_CLRDMASK ? AR_ClrDestMask : 0)
|
|
||||||
| (flags & ATH9K_TXDESC_INTREQ ? AR_TxIntrReq : 0)
|
| (flags & ATH9K_TXDESC_INTREQ ? AR_TxIntrReq : 0)
|
||||||
| (keyIx != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0);
|
| (keyIx != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0);
|
||||||
|
|
||||||
|
@ -311,6 +310,16 @@ static void ar9002_hw_set11n_txdesc(struct ath_hw *ah, void *ds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ar9002_hw_set_clrdmask(struct ath_hw *ah, void *ds, bool val)
|
||||||
|
{
|
||||||
|
struct ar5416_desc *ads = AR5416DESC(ds);
|
||||||
|
|
||||||
|
if (val)
|
||||||
|
ads->ds_ctl0 |= AR_ClrDestMask;
|
||||||
|
else
|
||||||
|
ads->ds_ctl0 &= ~AR_ClrDestMask;
|
||||||
|
}
|
||||||
|
|
||||||
static void ar9002_hw_set11n_ratescenario(struct ath_hw *ah, void *ds,
|
static void ar9002_hw_set11n_ratescenario(struct ath_hw *ah, void *ds,
|
||||||
void *lastds,
|
void *lastds,
|
||||||
u32 durUpdateEn, u32 rtsctsRate,
|
u32 durUpdateEn, u32 rtsctsRate,
|
||||||
|
@ -448,4 +457,5 @@ void ar9002_hw_attach_mac_ops(struct ath_hw *ah)
|
||||||
ops->set11n_aggr_last = ar9002_hw_set11n_aggr_last;
|
ops->set11n_aggr_last = ar9002_hw_set11n_aggr_last;
|
||||||
ops->clr11n_aggr = ar9002_hw_clr11n_aggr;
|
ops->clr11n_aggr = ar9002_hw_clr11n_aggr;
|
||||||
ops->set11n_burstduration = ar9002_hw_set11n_burstduration;
|
ops->set11n_burstduration = ar9002_hw_set11n_burstduration;
|
||||||
|
ops->set_clrdmask = ar9002_hw_set_clrdmask;
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,6 @@ static void ar9003_hw_set11n_txdesc(struct ath_hw *ah, void *ds,
|
||||||
| (flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0)
|
| (flags & ATH9K_TXDESC_VMF ? AR_VirtMoreFrag : 0)
|
||||||
| SM(txpower, AR_XmitPower)
|
| SM(txpower, AR_XmitPower)
|
||||||
| (flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0)
|
| (flags & ATH9K_TXDESC_VEOL ? AR_VEOL : 0)
|
||||||
| (flags & ATH9K_TXDESC_CLRDMASK ? AR_ClrDestMask : 0)
|
|
||||||
| (keyIx != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0)
|
| (keyIx != ATH9K_TXKEYIX_INVALID ? AR_DestIdxValid : 0)
|
||||||
| (flags & ATH9K_TXDESC_LOWRXCHAIN ? AR_LowRxChain : 0);
|
| (flags & ATH9K_TXDESC_LOWRXCHAIN ? AR_LowRxChain : 0);
|
||||||
|
|
||||||
|
@ -350,6 +349,16 @@ static void ar9003_hw_set11n_txdesc(struct ath_hw *ah, void *ds,
|
||||||
ads->ctl22 = 0;
|
ads->ctl22 = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ar9003_hw_set_clrdmask(struct ath_hw *ah, void *ds, bool val)
|
||||||
|
{
|
||||||
|
struct ar9003_txc *ads = (struct ar9003_txc *) ds;
|
||||||
|
|
||||||
|
if (val)
|
||||||
|
ads->ctl11 |= AR_ClrDestMask;
|
||||||
|
else
|
||||||
|
ads->ctl11 &= ~AR_ClrDestMask;
|
||||||
|
}
|
||||||
|
|
||||||
static void ar9003_hw_set11n_ratescenario(struct ath_hw *ah, void *ds,
|
static void ar9003_hw_set11n_ratescenario(struct ath_hw *ah, void *ds,
|
||||||
void *lastds,
|
void *lastds,
|
||||||
u32 durUpdateEn, u32 rtsctsRate,
|
u32 durUpdateEn, u32 rtsctsRate,
|
||||||
|
@ -510,6 +519,7 @@ void ar9003_hw_attach_mac_ops(struct ath_hw *hw)
|
||||||
ops->set11n_aggr_last = ar9003_hw_set11n_aggr_last;
|
ops->set11n_aggr_last = ar9003_hw_set11n_aggr_last;
|
||||||
ops->clr11n_aggr = ar9003_hw_clr11n_aggr;
|
ops->clr11n_aggr = ar9003_hw_clr11n_aggr;
|
||||||
ops->set11n_burstduration = ar9003_hw_set11n_burstduration;
|
ops->set11n_burstduration = ar9003_hw_set11n_burstduration;
|
||||||
|
ops->set_clrdmask = ar9003_hw_set_clrdmask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ath9k_hw_set_rx_bufsize(struct ath_hw *ah, u16 buf_size)
|
void ath9k_hw_set_rx_bufsize(struct ath_hw *ah, u16 buf_size)
|
||||||
|
|
|
@ -200,6 +200,7 @@ struct ath_atx_ac {
|
||||||
int sched;
|
int sched;
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct list_head tid_q;
|
struct list_head tid_q;
|
||||||
|
bool clear_ps_filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ath_frame_info {
|
struct ath_frame_info {
|
||||||
|
@ -257,6 +258,8 @@ struct ath_node {
|
||||||
struct ath_atx_ac ac[WME_NUM_AC];
|
struct ath_atx_ac ac[WME_NUM_AC];
|
||||||
u16 maxampdu;
|
u16 maxampdu;
|
||||||
u8 mpdudensity;
|
u8 mpdudensity;
|
||||||
|
|
||||||
|
bool sleeping;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define AGGR_CLEANUP BIT(1)
|
#define AGGR_CLEANUP BIT(1)
|
||||||
|
@ -338,6 +341,9 @@ int ath_tx_aggr_start(struct ath_softc *sc, struct ieee80211_sta *sta,
|
||||||
void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
|
void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
|
||||||
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
|
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid);
|
||||||
|
|
||||||
|
void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an);
|
||||||
|
bool ath_tx_aggr_sleep(struct ath_softc *sc, struct ath_node *an);
|
||||||
|
|
||||||
/********/
|
/********/
|
||||||
/* VIFs */
|
/* VIFs */
|
||||||
/********/
|
/********/
|
||||||
|
|
|
@ -122,6 +122,11 @@ static inline void ath9k_hw_set11n_burstduration(struct ath_hw *ah, void *ds,
|
||||||
ath9k_hw_ops(ah)->set11n_burstduration(ah, ds, burstDuration);
|
ath9k_hw_ops(ah)->set11n_burstduration(ah, ds, burstDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void ath9k_hw_set_clrdmask(struct ath_hw *ah, void *ds, bool val)
|
||||||
|
{
|
||||||
|
ath9k_hw_ops(ah)->set_clrdmask(ah, ds, val);
|
||||||
|
}
|
||||||
|
|
||||||
/* Private hardware call ops */
|
/* Private hardware call ops */
|
||||||
|
|
||||||
/* PHY ops */
|
/* PHY ops */
|
||||||
|
|
|
@ -626,6 +626,7 @@ struct ath_hw_ops {
|
||||||
void (*clr11n_aggr)(struct ath_hw *ah, void *ds);
|
void (*clr11n_aggr)(struct ath_hw *ah, void *ds);
|
||||||
void (*set11n_burstduration)(struct ath_hw *ah, void *ds,
|
void (*set11n_burstduration)(struct ath_hw *ah, void *ds,
|
||||||
u32 burstDuration);
|
u32 burstDuration);
|
||||||
|
void (*set_clrdmask)(struct ath_hw *ah, void *ds, bool val);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ath_nf_limits {
|
struct ath_nf_limits {
|
||||||
|
|
|
@ -239,7 +239,6 @@ struct ath_desc {
|
||||||
void *ds_vdata;
|
void *ds_vdata;
|
||||||
} __packed __aligned(4);
|
} __packed __aligned(4);
|
||||||
|
|
||||||
#define ATH9K_TXDESC_CLRDMASK 0x0001
|
|
||||||
#define ATH9K_TXDESC_NOACK 0x0002
|
#define ATH9K_TXDESC_NOACK 0x0002
|
||||||
#define ATH9K_TXDESC_RTSENA 0x0004
|
#define ATH9K_TXDESC_RTSENA 0x0004
|
||||||
#define ATH9K_TXDESC_CTSENA 0x0008
|
#define ATH9K_TXDESC_CTSENA 0x0008
|
||||||
|
|
|
@ -1749,6 +1749,27 @@ static int ath9k_sta_remove(struct ieee80211_hw *hw,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ath9k_sta_notify(struct ieee80211_hw *hw,
|
||||||
|
struct ieee80211_vif *vif,
|
||||||
|
enum sta_notify_cmd cmd,
|
||||||
|
struct ieee80211_sta *sta)
|
||||||
|
{
|
||||||
|
struct ath_softc *sc = hw->priv;
|
||||||
|
struct ath_node *an = (struct ath_node *) sta->drv_priv;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case STA_NOTIFY_SLEEP:
|
||||||
|
an->sleeping = true;
|
||||||
|
if (ath_tx_aggr_sleep(sc, an))
|
||||||
|
ieee80211_sta_set_tim(sta);
|
||||||
|
break;
|
||||||
|
case STA_NOTIFY_AWAKE:
|
||||||
|
an->sleeping = false;
|
||||||
|
ath_tx_aggr_wakeup(sc, an);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int ath9k_conf_tx(struct ieee80211_hw *hw, u16 queue,
|
static int ath9k_conf_tx(struct ieee80211_hw *hw, u16 queue,
|
||||||
const struct ieee80211_tx_queue_params *params)
|
const struct ieee80211_tx_queue_params *params)
|
||||||
{
|
{
|
||||||
|
@ -2230,6 +2251,7 @@ struct ieee80211_ops ath9k_ops = {
|
||||||
.configure_filter = ath9k_configure_filter,
|
.configure_filter = ath9k_configure_filter,
|
||||||
.sta_add = ath9k_sta_add,
|
.sta_add = ath9k_sta_add,
|
||||||
.sta_remove = ath9k_sta_remove,
|
.sta_remove = ath9k_sta_remove,
|
||||||
|
.sta_notify = ath9k_sta_notify,
|
||||||
.conf_tx = ath9k_conf_tx,
|
.conf_tx = ath9k_conf_tx,
|
||||||
.bss_info_changed = ath9k_bss_info_changed,
|
.bss_info_changed = ath9k_bss_info_changed,
|
||||||
.set_key = ath9k_set_key,
|
.set_key = ath9k_set_key,
|
||||||
|
|
|
@ -357,6 +357,7 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
|
||||||
struct ath_frame_info *fi;
|
struct ath_frame_info *fi;
|
||||||
int nframes;
|
int nframes;
|
||||||
u8 tidno;
|
u8 tidno;
|
||||||
|
bool clear_filter;
|
||||||
|
|
||||||
skb = bf->bf_mpdu;
|
skb = bf->bf_mpdu;
|
||||||
hdr = (struct ieee80211_hdr *)skb->data;
|
hdr = (struct ieee80211_hdr *)skb->data;
|
||||||
|
@ -441,22 +442,24 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
|
||||||
/* transmit completion */
|
/* transmit completion */
|
||||||
acked_cnt++;
|
acked_cnt++;
|
||||||
} else {
|
} else {
|
||||||
if (!(tid->state & AGGR_CLEANUP) && retry) {
|
if ((tid->state & AGGR_CLEANUP) || !retry) {
|
||||||
if (fi->retries < ATH_MAX_SW_RETRIES) {
|
|
||||||
ath_tx_set_retry(sc, txq, bf->bf_mpdu);
|
|
||||||
txpending = 1;
|
|
||||||
} else {
|
|
||||||
bf->bf_state.bf_type |= BUF_XRETRY;
|
|
||||||
txfail = 1;
|
|
||||||
sendbar = 1;
|
|
||||||
txfail_cnt++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
/*
|
||||||
* cleanup in progress, just fail
|
* cleanup in progress, just fail
|
||||||
* the un-acked sub-frames
|
* the un-acked sub-frames
|
||||||
*/
|
*/
|
||||||
txfail = 1;
|
txfail = 1;
|
||||||
|
} else if (fi->retries < ATH_MAX_SW_RETRIES) {
|
||||||
|
if (!(ts->ts_status & ATH9K_TXERR_FILT) ||
|
||||||
|
!an->sleeping)
|
||||||
|
ath_tx_set_retry(sc, txq, bf->bf_mpdu);
|
||||||
|
|
||||||
|
clear_filter = true;
|
||||||
|
txpending = 1;
|
||||||
|
} else {
|
||||||
|
bf->bf_state.bf_type |= BUF_XRETRY;
|
||||||
|
txfail = 1;
|
||||||
|
sendbar = 1;
|
||||||
|
txfail_cnt++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,6 +499,7 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
|
||||||
!txfail, sendbar);
|
!txfail, sendbar);
|
||||||
} else {
|
} else {
|
||||||
/* retry the un-acked ones */
|
/* retry the un-acked ones */
|
||||||
|
ath9k_hw_set_clrdmask(sc->sc_ah, bf->bf_desc, false);
|
||||||
if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)) {
|
if (!(sc->sc_ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)) {
|
||||||
if (bf->bf_next == NULL && bf_last->bf_stale) {
|
if (bf->bf_next == NULL && bf_last->bf_stale) {
|
||||||
struct ath_buf *tbf;
|
struct ath_buf *tbf;
|
||||||
|
@ -546,7 +550,12 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
|
||||||
|
|
||||||
/* prepend un-acked frames to the beginning of the pending frame queue */
|
/* prepend un-acked frames to the beginning of the pending frame queue */
|
||||||
if (!list_empty(&bf_pending)) {
|
if (!list_empty(&bf_pending)) {
|
||||||
|
if (an->sleeping)
|
||||||
|
ieee80211_sta_set_tim(sta);
|
||||||
|
|
||||||
spin_lock_bh(&txq->axq_lock);
|
spin_lock_bh(&txq->axq_lock);
|
||||||
|
if (clear_filter)
|
||||||
|
tid->ac->clear_ps_filter = true;
|
||||||
list_splice(&bf_pending, &tid->buf_q);
|
list_splice(&bf_pending, &tid->buf_q);
|
||||||
ath_tx_queue_tid(txq, tid);
|
ath_tx_queue_tid(txq, tid);
|
||||||
spin_unlock_bh(&txq->axq_lock);
|
spin_unlock_bh(&txq->axq_lock);
|
||||||
|
@ -816,6 +825,11 @@ static void ath_tx_sched_aggr(struct ath_softc *sc, struct ath_txq *txq,
|
||||||
bf = list_first_entry(&bf_q, struct ath_buf, list);
|
bf = list_first_entry(&bf_q, struct ath_buf, list);
|
||||||
bf->bf_lastbf = list_entry(bf_q.prev, struct ath_buf, list);
|
bf->bf_lastbf = list_entry(bf_q.prev, struct ath_buf, list);
|
||||||
|
|
||||||
|
if (tid->ac->clear_ps_filter) {
|
||||||
|
tid->ac->clear_ps_filter = false;
|
||||||
|
ath9k_hw_set_clrdmask(sc->sc_ah, bf->bf_desc, true);
|
||||||
|
}
|
||||||
|
|
||||||
/* if only one frame, send as non-aggregate */
|
/* if only one frame, send as non-aggregate */
|
||||||
if (bf == bf->bf_lastbf) {
|
if (bf == bf->bf_lastbf) {
|
||||||
fi = get_frame_info(bf->bf_mpdu);
|
fi = get_frame_info(bf->bf_mpdu);
|
||||||
|
@ -896,6 +910,67 @@ void ath_tx_aggr_stop(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid)
|
||||||
ath_tx_flush_tid(sc, txtid);
|
ath_tx_flush_tid(sc, txtid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ath_tx_aggr_sleep(struct ath_softc *sc, struct ath_node *an)
|
||||||
|
{
|
||||||
|
struct ath_atx_tid *tid;
|
||||||
|
struct ath_atx_ac *ac;
|
||||||
|
struct ath_txq *txq;
|
||||||
|
bool buffered = false;
|
||||||
|
int tidno;
|
||||||
|
|
||||||
|
for (tidno = 0, tid = &an->tid[tidno];
|
||||||
|
tidno < WME_NUM_TID; tidno++, tid++) {
|
||||||
|
|
||||||
|
if (!tid->sched)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ac = tid->ac;
|
||||||
|
txq = ac->txq;
|
||||||
|
|
||||||
|
spin_lock_bh(&txq->axq_lock);
|
||||||
|
|
||||||
|
if (!list_empty(&tid->buf_q))
|
||||||
|
buffered = true;
|
||||||
|
|
||||||
|
tid->sched = false;
|
||||||
|
list_del(&tid->list);
|
||||||
|
|
||||||
|
if (ac->sched) {
|
||||||
|
ac->sched = false;
|
||||||
|
list_del(&ac->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_bh(&txq->axq_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffered;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ath_tx_aggr_wakeup(struct ath_softc *sc, struct ath_node *an)
|
||||||
|
{
|
||||||
|
struct ath_atx_tid *tid;
|
||||||
|
struct ath_atx_ac *ac;
|
||||||
|
struct ath_txq *txq;
|
||||||
|
int tidno;
|
||||||
|
|
||||||
|
for (tidno = 0, tid = &an->tid[tidno];
|
||||||
|
tidno < WME_NUM_TID; tidno++, tid++) {
|
||||||
|
|
||||||
|
ac = tid->ac;
|
||||||
|
txq = ac->txq;
|
||||||
|
|
||||||
|
spin_lock_bh(&txq->axq_lock);
|
||||||
|
ac->clear_ps_filter = true;
|
||||||
|
|
||||||
|
if (!list_empty(&tid->buf_q) && !tid->paused) {
|
||||||
|
ath_tx_queue_tid(txq, tid);
|
||||||
|
ath_txq_schedule(sc, txq);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock_bh(&txq->axq_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid)
|
void ath_tx_aggr_resume(struct ath_softc *sc, struct ieee80211_sta *sta, u16 tid)
|
||||||
{
|
{
|
||||||
struct ath_atx_tid *txtid;
|
struct ath_atx_tid *txtid;
|
||||||
|
@ -1491,7 +1566,6 @@ static int setup_tx_flags(struct sk_buff *skb)
|
||||||
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
|
||||||
int flags = 0;
|
int flags = 0;
|
||||||
|
|
||||||
flags |= ATH9K_TXDESC_CLRDMASK; /* needed for crypto errors */
|
|
||||||
flags |= ATH9K_TXDESC_INTREQ;
|
flags |= ATH9K_TXDESC_INTREQ;
|
||||||
|
|
||||||
if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK)
|
if (tx_info->flags & IEEE80211_TX_CTL_NO_ACK)
|
||||||
|
@ -1754,6 +1828,9 @@ static void ath_tx_start_dma(struct ath_softc *sc, struct ath_buf *bf,
|
||||||
if (txctl->paprd)
|
if (txctl->paprd)
|
||||||
bf->bf_state.bfs_paprd_timestamp = jiffies;
|
bf->bf_state.bfs_paprd_timestamp = jiffies;
|
||||||
|
|
||||||
|
if (tx_info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT)
|
||||||
|
ath9k_hw_set_clrdmask(sc->sc_ah, bf->bf_desc, true);
|
||||||
|
|
||||||
ath_tx_send_normal(sc, txctl->txq, tid, &bf_head);
|
ath_tx_send_normal(sc, txctl->txq, tid, &bf_head);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue