git: d99eb8230eb7 - main - rtwn: change the USB TX transfers to only do one pending transfer per endpoint
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 21 Nov 2024 01:57:03 UTC
The branch main has been updated by adrian:
URL: https://cgit.FreeBSD.org/src/commit/?id=d99eb8230eb717ab0b2eba948614d0f2f2b5dd2b
commit d99eb8230eb717ab0b2eba948614d0f2f2b5dd2b
Author: Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2024-11-12 04:48:12 +0000
Commit: Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2024-11-21 01:56:56 +0000
rtwn: change the USB TX transfers to only do one pending transfer per endpoint
I found I was getting constant device timeouts when doing anything
more complicated than a single SSH on laptop with RTL8811AU.
After digging into it, i found a variety of fun situations, including
traffic stalls that would recover w/ a shorter (1 second) USB transfer
timeout. However, the big one is a straight up hang of any TX endpoint
until the NIC was reset. The RX side kept going just fine; only the
TX endpoints would hang.
Reproducing it was easy - just start up a couple of traffic streams
on different WME AC's - eg a best effort + bulk transfer, like
browsing the web and doing an ssh clone - throw in a ping -i 0.1
to your gateway, and it would very quickly hit device timeouts every
couple of seconds.
I put everything into a single TX EP and the hangs went away.
Well, mostly.
So after some MORE digging, I found that this driver isn't checking
if the transfers are going into the correct EPs for the packet
WME access category / 802.11 TID; and would frequently be able
to schedule multiple transfers into the same endpoint.
Then there's a second problem - there's an array of endpoints
used for setting up the USB device, with .endpoint = UE_ADDR_ANY,
however they're also being setup with the same endpoint configured
in multiple transfer configs. Eg, a NIC with 3 or 4 bulk TX endpoints
will configure the BK and BE endpoints with the same physical endpoint
ID. This also leads to timed out transfers.
My /guess/ was that the firmware isn't happy with one or both of the
above, and so I solved both.
* drop the USB transfer timeout to 1 second, not 5 seconds -
that way we'll either get a 1 second traffic pause and USB transfer
failure, or a 5 second device timeout. Having both the TX timeout
and the USB transfer timeout made recovery from a USB transfer
timeout (without a NIC reset) almost impossible.
* enforce one transfer per endpoint;
* separate pending/active buffer tracking per endpoint;
* each endpoint now has its own TX callback to make sure the queue /
end point ID is known;
* and only frames from a given endpoint pending queue is going
into the active queue and into that endpoint.
* Finally, create a local wme2qid array and populate it with the
endpoint mapping that ensures unique physical endpoint use.
Locally tested:
* rtl8812AU, 11n STA mode
* rtl8192EU, 11n STA mode (with diffs to fix the channel config / power
timeouts.)
Differential Revision: https://reviews.freebsd.org/D47522
---
sys/dev/rtwn/if_rtwnvar.h | 2 +-
sys/dev/rtwn/usb/rtwn_usb_attach.c | 26 ++++++----
sys/dev/rtwn/usb/rtwn_usb_ep.c | 23 ++++++---
sys/dev/rtwn/usb/rtwn_usb_tx.c | 100 ++++++++++++++++++++++++++++++-------
sys/dev/rtwn/usb/rtwn_usb_tx.h | 5 +-
sys/dev/rtwn/usb/rtwn_usb_var.h | 12 +++--
6 files changed, 129 insertions(+), 39 deletions(-)
diff --git a/sys/dev/rtwn/if_rtwnvar.h b/sys/dev/rtwn/if_rtwnvar.h
index 6a44b7b73902..f4c6d7ee64b4 100644
--- a/sys/dev/rtwn/if_rtwnvar.h
+++ b/sys/dev/rtwn/if_rtwnvar.h
@@ -32,7 +32,7 @@
#define RTWN_MACID_VALID 0x8000
#define RTWN_MACID_LIMIT 128
-#define RTWN_TX_TIMEOUT 5000 /* ms */
+#define RTWN_TX_TIMEOUT 1000 /* ms */
#define RTWN_MAX_EPOUT 4
#define RTWN_PORT_COUNT 2
diff --git a/sys/dev/rtwn/usb/rtwn_usb_attach.c b/sys/dev/rtwn/usb/rtwn_usb_attach.c
index 71798ffc14f9..4958939a768a 100644
--- a/sys/dev/rtwn/usb/rtwn_usb_attach.c
+++ b/sys/dev/rtwn/usb/rtwn_usb_attach.c
@@ -156,10 +156,12 @@ rtwn_usb_alloc_tx_list(struct rtwn_softc *sc)
if (error != 0)
return (error);
- STAILQ_INIT(&uc->uc_tx_active);
- STAILQ_INIT(&uc->uc_tx_inactive);
- STAILQ_INIT(&uc->uc_tx_pending);
+ for (i = RTWN_BULK_TX_FIRST; i < RTWN_BULK_EP_COUNT; i++) {
+ STAILQ_INIT(&uc->uc_tx_active[i]);
+ STAILQ_INIT(&uc->uc_tx_pending[i]);
+ }
+ STAILQ_INIT(&uc->uc_tx_inactive);
for (i = 0; i < RTWN_USB_TX_LIST_COUNT; i++)
STAILQ_INSERT_HEAD(&uc->uc_tx_inactive, &uc->uc_tx[i], next);
@@ -207,23 +209,29 @@ static void
rtwn_usb_free_tx_list(struct rtwn_softc *sc)
{
struct rtwn_usb_softc *uc = RTWN_USB_SOFTC(sc);
+ int i;
rtwn_usb_free_list(sc, uc->uc_tx, RTWN_USB_TX_LIST_COUNT);
- STAILQ_INIT(&uc->uc_tx_active);
+ for (i = RTWN_BULK_TX_FIRST; i < RTWN_BULK_EP_COUNT; i++) {
+ STAILQ_INIT(&uc->uc_tx_active[i]);
+ STAILQ_INIT(&uc->uc_tx_pending[i]);
+ }
STAILQ_INIT(&uc->uc_tx_inactive);
- STAILQ_INIT(&uc->uc_tx_pending);
}
static void
rtwn_usb_reset_lists(struct rtwn_softc *sc, struct ieee80211vap *vap)
{
struct rtwn_usb_softc *uc = RTWN_USB_SOFTC(sc);
+ int i;
RTWN_ASSERT_LOCKED(sc);
- rtwn_usb_reset_tx_list(uc, &uc->uc_tx_active, vap);
- rtwn_usb_reset_tx_list(uc, &uc->uc_tx_pending, vap);
+ for (i = RTWN_BULK_TX_FIRST; i < RTWN_BULK_EP_COUNT; i++) {
+ rtwn_usb_reset_tx_list(uc, &uc->uc_tx_active[i], vap);
+ rtwn_usb_reset_tx_list(uc, &uc->uc_tx_pending[i], vap);
+ }
if (vap == NULL) {
rtwn_usb_reset_rx_list(uc);
sc->qfullmsk = 0;
@@ -295,7 +303,7 @@ rtwn_usb_abort_xfers(struct rtwn_softc *sc)
/* abort any pending transfers */
RTWN_UNLOCK(sc);
- for (i = 0; i < RTWN_N_TRANSFER; i++)
+ for (i = 0; i < RTWN_BULK_EP_COUNT; i++)
usbd_transfer_drain(uc->uc_xfer[i]);
RTWN_LOCK(sc);
}
@@ -432,7 +440,7 @@ rtwn_usb_detach(device_t self)
rtwn_usb_free_rx_list(sc);
/* Detach all USB transfers. */
- usbd_transfer_unsetup(uc->uc_xfer, RTWN_N_TRANSFER);
+ usbd_transfer_unsetup(uc->uc_xfer, RTWN_BULK_EP_COUNT);
rtwn_detach_private(sc);
mtx_destroy(&sc->sc_mtx);
diff --git a/sys/dev/rtwn/usb/rtwn_usb_ep.c b/sys/dev/rtwn/usb/rtwn_usb_ep.c
index 0848a45a9f86..f9b0672324fe 100644
--- a/sys/dev/rtwn/usb/rtwn_usb_ep.c
+++ b/sys/dev/rtwn/usb/rtwn_usb_ep.c
@@ -55,7 +55,7 @@
#include <dev/rtwn/rtl8192c/usb/r92cu_reg.h>
-static const struct usb_config rtwn_config_common[RTWN_N_TRANSFER] = {
+static const struct usb_config rtwn_config_common[RTWN_BULK_EP_COUNT] = {
[RTWN_BULK_RX] = {
.type = UE_BULK,
.endpoint = UE_ADDR_ANY,
@@ -76,7 +76,7 @@ static const struct usb_config rtwn_config_common[RTWN_N_TRANSFER] = {
.pipe_bof = 1,
.force_short_xfer = 1,
},
- .callback = rtwn_bulk_tx_callback,
+ .callback = rtwn_bulk_tx_callback_be,
.timeout = RTWN_TX_TIMEOUT, /* ms */
},
[RTWN_BULK_TX_BK] = {
@@ -89,7 +89,7 @@ static const struct usb_config rtwn_config_common[RTWN_N_TRANSFER] = {
.pipe_bof = 1,
.force_short_xfer = 1,
},
- .callback = rtwn_bulk_tx_callback,
+ .callback = rtwn_bulk_tx_callback_bk,
.timeout = RTWN_TX_TIMEOUT, /* ms */
},
[RTWN_BULK_TX_VI] = {
@@ -102,7 +102,7 @@ static const struct usb_config rtwn_config_common[RTWN_N_TRANSFER] = {
.pipe_bof = 1,
.force_short_xfer = 1
},
- .callback = rtwn_bulk_tx_callback,
+ .callback = rtwn_bulk_tx_callback_vi,
.timeout = RTWN_TX_TIMEOUT, /* ms */
},
[RTWN_BULK_TX_VO] = {
@@ -115,7 +115,7 @@ static const struct usb_config rtwn_config_common[RTWN_N_TRANSFER] = {
.pipe_bof = 1,
.force_short_xfer = 1
},
- .callback = rtwn_bulk_tx_callback,
+ .callback = rtwn_bulk_tx_callback_vo,
.timeout = RTWN_TX_TIMEOUT, /* ms */
},
};
@@ -200,22 +200,33 @@ rtwn_usb_setup_endpoints(struct rtwn_usb_softc *uc)
/* NB: keep in sync with rtwn_dma_init(). */
rtwn_config[RTWN_BULK_TX_VO].endpoint = addr[0];
+ uc->wme2qid[WME_AC_VO] = RTWN_BULK_TX_VO;
switch (uc->ntx) {
case 4:
case 3:
rtwn_config[RTWN_BULK_TX_BE].endpoint = addr[2];
rtwn_config[RTWN_BULK_TX_BK].endpoint = addr[2];
rtwn_config[RTWN_BULK_TX_VI].endpoint = addr[1];
+ uc->wme2qid[WME_AC_BE] = RTWN_BULK_TX_BE;
+ uc->wme2qid[WME_AC_BK] = RTWN_BULK_TX_BE;
+ uc->wme2qid[WME_AC_VI] = RTWN_BULK_TX_VI;
break;
case 2:
rtwn_config[RTWN_BULK_TX_BE].endpoint = addr[1];
rtwn_config[RTWN_BULK_TX_BK].endpoint = addr[1];
rtwn_config[RTWN_BULK_TX_VI].endpoint = addr[0];
+ uc->wme2qid[WME_AC_BE] = RTWN_BULK_TX_VI;
+ uc->wme2qid[WME_AC_BK] = RTWN_BULK_TX_VI;
+ uc->wme2qid[WME_AC_VI] = RTWN_BULK_TX_VO;
break;
case 1:
rtwn_config[RTWN_BULK_TX_BE].endpoint = addr[0];
rtwn_config[RTWN_BULK_TX_BK].endpoint = addr[0];
rtwn_config[RTWN_BULK_TX_VI].endpoint = addr[0];
+
+ uc->wme2qid[WME_AC_BE] = RTWN_BULK_TX_VO;
+ uc->wme2qid[WME_AC_BK] = RTWN_BULK_TX_VO;
+ uc->wme2qid[WME_AC_VI] = RTWN_BULK_TX_VO;
break;
default:
KASSERT(0, ("unhandled number of endpoints %d\n", uc->ntx));
@@ -225,7 +236,7 @@ rtwn_usb_setup_endpoints(struct rtwn_usb_softc *uc)
rtwn_config[RTWN_BULK_RX].bufsize =
uc->uc_rx_buf_size * RTWN_USB_RXBUFSZ_UNIT;
error = usbd_transfer_setup(uc->uc_udev, &iface_index,
- uc->uc_xfer, rtwn_config, RTWN_N_TRANSFER, uc, &sc->sc_mtx);
+ uc->uc_xfer, rtwn_config, RTWN_BULK_EP_COUNT, uc, &sc->sc_mtx);
free(rtwn_config, M_TEMP);
if (error) {
diff --git a/sys/dev/rtwn/usb/rtwn_usb_tx.c b/sys/dev/rtwn/usb/rtwn_usb_tx.c
index 0fb8632d9a16..86d41ed10d91 100644
--- a/sys/dev/rtwn/usb/rtwn_usb_tx.c
+++ b/sys/dev/rtwn/usb/rtwn_usb_tx.c
@@ -65,10 +65,6 @@ static struct rtwn_data * rtwn_usb_getbuf(struct rtwn_usb_softc *);
static void rtwn_usb_txeof(struct rtwn_usb_softc *,
struct rtwn_data *, int);
-static const uint8_t wme2qid[] =
- { RTWN_BULK_TX_BE, RTWN_BULK_TX_BK,
- RTWN_BULK_TX_VI, RTWN_BULK_TX_VO };
-
static struct rtwn_data *
_rtwn_usb_getbuf(struct rtwn_usb_softc *uc)
{
@@ -105,6 +101,7 @@ static void
rtwn_usb_txeof(struct rtwn_usb_softc *uc, struct rtwn_data *data, int status)
{
struct rtwn_softc *sc = &uc->uc_sc;
+ bool is_empty = true;
RTWN_ASSERT_LOCKED(sc);
@@ -120,42 +117,54 @@ rtwn_usb_txeof(struct rtwn_usb_softc *uc, struct rtwn_data *data, int status)
STAILQ_INSERT_TAIL(&uc->uc_tx_inactive, data, next);
sc->qfullmsk = 0;
+
#ifndef D4054
- if (STAILQ_EMPTY(&uc->uc_tx_active) && STAILQ_EMPTY(&uc->uc_tx_pending))
+ for (int i = RTWN_BULK_TX_FIRST; i < RTWN_BULK_EP_COUNT; i++) {
+ if (!STAILQ_EMPTY(&uc->uc_tx_active[i]) ||
+ !STAILQ_EMPTY(&uc->uc_tx_pending[i]))
+ is_empty = false;
+ }
+
+ if (is_empty)
sc->sc_tx_timer = 0;
else
sc->sc_tx_timer = 5;
#endif
}
-void
-rtwn_bulk_tx_callback(struct usb_xfer *xfer, usb_error_t error)
+static void
+rtwn_bulk_tx_callback_qid(struct usb_xfer *xfer, usb_error_t error, int qid)
{
struct rtwn_usb_softc *uc = usbd_xfer_softc(xfer);
struct rtwn_softc *sc = &uc->uc_sc;
struct rtwn_data *data;
+ bool do_is_empty_check = false;
+ int i;
+
+ RTWN_DPRINTF(sc, RTWN_DEBUG_XMIT,
+ "%s: called, qid=%d\n", __func__, qid);
RTWN_ASSERT_LOCKED(sc);
switch (USB_GET_STATE(xfer)){
case USB_ST_TRANSFERRED:
- data = STAILQ_FIRST(&uc->uc_tx_active);
+ data = STAILQ_FIRST(&uc->uc_tx_active[qid]);
if (data == NULL)
goto tr_setup;
- STAILQ_REMOVE_HEAD(&uc->uc_tx_active, next);
+ STAILQ_REMOVE_HEAD(&uc->uc_tx_active[qid], next);
rtwn_usb_txeof(uc, data, 0);
/* FALLTHROUGH */
case USB_ST_SETUP:
tr_setup:
- data = STAILQ_FIRST(&uc->uc_tx_pending);
+ data = STAILQ_FIRST(&uc->uc_tx_pending[qid]);
if (data == NULL) {
RTWN_DPRINTF(sc, RTWN_DEBUG_XMIT,
"%s: empty pending queue\n", __func__);
- sc->sc_tx_n_active = 0;
+ do_is_empty_check = true;
goto finish;
}
- STAILQ_REMOVE_HEAD(&uc->uc_tx_pending, next);
- STAILQ_INSERT_TAIL(&uc->uc_tx_active, data, next);
+ STAILQ_REMOVE_HEAD(&uc->uc_tx_pending[qid], next);
+ STAILQ_INSERT_TAIL(&uc->uc_tx_active[qid], data, next);
/*
* Note: if this is a beacon frame, ensure that it will go
@@ -169,11 +178,17 @@ tr_setup:
sc->sc_tx_n_active++;
break;
default:
- data = STAILQ_FIRST(&uc->uc_tx_active);
+ data = STAILQ_FIRST(&uc->uc_tx_active[qid]);
if (data == NULL)
goto tr_setup;
- STAILQ_REMOVE_HEAD(&uc->uc_tx_active, next);
+ STAILQ_REMOVE_HEAD(&uc->uc_tx_active[qid], next);
rtwn_usb_txeof(uc, data, 1);
+ if (error != 0)
+ device_printf(sc->sc_dev,
+ "%s: called; txeof qid=%d, error=%s\n",
+ __func__,
+ qid,
+ usbd_errstr(error));
if (error != USB_ERR_CANCELLED) {
usbd_xfer_set_stall(xfer);
goto tr_setup;
@@ -181,6 +196,19 @@ tr_setup:
break;
}
finish:
+
+ /*
+ * Clear sc_tx_n_active if all the pending transfers are 0.
+ *
+ * This is currently a crutch because net80211 doesn't provide
+ * a way to defer all the FF checks or one of the FF checks.
+ * Eventually this should just be tracked per-endpoint.
+ */
+ for (i = RTWN_BULK_TX_FIRST; i < RTWN_BULK_EP_COUNT; i++)
+ if (STAILQ_FIRST(&uc->uc_tx_pending[i]) != NULL)
+ do_is_empty_check = false;
+ if (do_is_empty_check)
+ sc->sc_tx_n_active = 0;
#ifdef IEEE80211_SUPPORT_SUPERG
/*
* If the TX active queue drops below a certain
@@ -210,6 +238,34 @@ finish:
rtwn_start(sc);
}
+void
+rtwn_bulk_tx_callback_be(struct usb_xfer *xfer, usb_error_t error)
+{
+
+ rtwn_bulk_tx_callback_qid(xfer, error, RTWN_BULK_TX_BE);
+}
+
+void
+rtwn_bulk_tx_callback_bk(struct usb_xfer *xfer, usb_error_t error)
+{
+
+ rtwn_bulk_tx_callback_qid(xfer, error, RTWN_BULK_TX_BK);
+}
+
+void
+rtwn_bulk_tx_callback_vi(struct usb_xfer *xfer, usb_error_t error)
+{
+
+ rtwn_bulk_tx_callback_qid(xfer, error, RTWN_BULK_TX_VI);
+}
+
+void
+rtwn_bulk_tx_callback_vo(struct usb_xfer *xfer, usb_error_t error)
+{
+
+ rtwn_bulk_tx_callback_qid(xfer, error, RTWN_BULK_TX_VO);
+}
+
static void
rtwn_usb_tx_checksum(struct rtwn_tx_desc_common *txd)
{
@@ -226,6 +282,7 @@ rtwn_usb_tx_start(struct rtwn_softc *sc, struct ieee80211_node *ni,
struct rtwn_data *data;
struct usb_xfer *xfer;
uint16_t ac;
+ int qid = 0;
RTWN_ASSERT_LOCKED(sc);
@@ -236,17 +293,23 @@ rtwn_usb_tx_start(struct rtwn_softc *sc, struct ieee80211_node *ni,
if (data == NULL)
return (ENOBUFS);
+ /* TODO: should really get a consistent AC/TID, ath(4) style */
ac = M_WME_GETAC(m);
switch (type) {
case IEEE80211_FC0_TYPE_CTL:
case IEEE80211_FC0_TYPE_MGT:
- xfer = uc->uc_xfer[RTWN_BULK_TX_VO];
+ qid = RTWN_BULK_TX_VO;
break;
default:
- xfer = uc->uc_xfer[wme2qid[ac]];
+ qid = uc->wme2qid[ac];
break;
}
+ xfer = uc->uc_xfer[qid];
+
+ RTWN_DPRINTF(sc, RTWN_DEBUG_XMIT,
+ "%s: called, ac=%d, qid=%d, xfer=%p\n",
+ __func__, ac, qid, xfer);
txd = (struct rtwn_tx_desc_common *)tx_desc;
txd->pktlen = htole16(m->m_pkthdr.len);
@@ -264,6 +327,7 @@ rtwn_usb_tx_start(struct rtwn_softc *sc, struct ieee80211_node *ni,
data->buflen = m->m_pkthdr.len + sc->txdesc_len;
data->id = id;
data->ni = ni;
+ data->qid = qid;
if (data->ni != NULL) {
data->m = m;
#ifndef D4054
@@ -271,7 +335,7 @@ rtwn_usb_tx_start(struct rtwn_softc *sc, struct ieee80211_node *ni,
#endif
}
- STAILQ_INSERT_TAIL(&uc->uc_tx_pending, data, next);
+ STAILQ_INSERT_TAIL(&uc->uc_tx_pending[qid], data, next);
if (STAILQ_EMPTY(&uc->uc_tx_inactive))
sc->qfullmsk = 1;
diff --git a/sys/dev/rtwn/usb/rtwn_usb_tx.h b/sys/dev/rtwn/usb/rtwn_usb_tx.h
index 7b762cc01a00..193103f32707 100644
--- a/sys/dev/rtwn/usb/rtwn_usb_tx.h
+++ b/sys/dev/rtwn/usb/rtwn_usb_tx.h
@@ -17,7 +17,10 @@
#ifndef RTWN_USB_TX_H
#define RTWN_USB_TX_H
-void rtwn_bulk_tx_callback(struct usb_xfer *, usb_error_t);
+void rtwn_bulk_tx_callback_bk(struct usb_xfer *, usb_error_t);
+void rtwn_bulk_tx_callback_be(struct usb_xfer *, usb_error_t);
+void rtwn_bulk_tx_callback_vi(struct usb_xfer *, usb_error_t);
+void rtwn_bulk_tx_callback_vo(struct usb_xfer *, usb_error_t);
int rtwn_usb_tx_start(struct rtwn_softc *, struct ieee80211_node *,
struct mbuf *, uint8_t *, uint8_t, int);
diff --git a/sys/dev/rtwn/usb/rtwn_usb_var.h b/sys/dev/rtwn/usb/rtwn_usb_var.h
index bad697bfa1db..646dde66aeab 100644
--- a/sys/dev/rtwn/usb/rtwn_usb_var.h
+++ b/sys/dev/rtwn/usb/rtwn_usb_var.h
@@ -37,6 +37,7 @@ struct rtwn_data {
uint8_t *buf;
/* 'id' is meaningful for beacons only */
int id;
+ int qid;
uint16_t buflen;
struct mbuf *m;
struct ieee80211_node *ni;
@@ -50,15 +51,16 @@ enum {
RTWN_BULK_TX_BK, /* = WME_AC_BK */
RTWN_BULK_TX_VI, /* = WME_AC_VI */
RTWN_BULK_TX_VO, /* = WME_AC_VO */
- RTWN_N_TRANSFER = 5,
+ RTWN_BULK_EP_COUNT = 5,
};
#define RTWN_EP_QUEUES RTWN_BULK_RX
+#define RTWN_BULK_TX_FIRST RTWN_BULK_TX_BE
struct rtwn_usb_softc {
struct rtwn_softc uc_sc; /* must be the first */
struct usb_device *uc_udev;
- struct usb_xfer *uc_xfer[RTWN_N_TRANSFER];
+ struct usb_xfer *uc_xfer[RTWN_BULK_EP_COUNT];
struct rtwn_data uc_rx[RTWN_USB_RX_LIST_COUNT];
rtwn_datahead uc_rx_active;
@@ -70,14 +72,16 @@ struct rtwn_usb_softc {
int uc_rx_off;
struct rtwn_data uc_tx[RTWN_USB_TX_LIST_COUNT];
- rtwn_datahead uc_tx_active;
+ rtwn_datahead uc_tx_active[RTWN_BULK_EP_COUNT];
rtwn_datahead uc_tx_inactive;
- rtwn_datahead uc_tx_pending;
+ rtwn_datahead uc_tx_pending[RTWN_BULK_EP_COUNT];
int (*uc_align_rx)(int, int);
int ntx;
int tx_agg_desc_num;
+
+ uint8_t wme2qid[4];
};
#define RTWN_USB_SOFTC(sc) ((struct rtwn_usb_softc *)(sc))