Index: ieee80211.c =================================================================== --- ieee80211.c (revision 358490) +++ ieee80211.c (working copy) @@ -1239,9 +1239,10 @@ return (ENOBUFS); #if 0 - printf("%s: %d: ieee=%d, freq=%d, flags=0x%08x\n", + printf("%s: %d: maxchans %d, ieee=%d, freq=%d, flags=0x%08x\n", __func__, *nchans, + maxchans, ieee, freq, flags); @@ -1273,9 +1274,10 @@ return (ENOBUFS); #if 0 - printf("%s: %d: flags=0x%08x\n", + printf("%s: %d: maxchans %d: flags=0x%08x\n", __func__, *nchans, + maxchans, flags); #endif @@ -1403,6 +1405,8 @@ error = addchan(chans, maxchans, nchans, ieee, freq, maxregpower, flags[0] | chan_flags); + if (error != 0) + return (error); for (i = 1; flags[i] != 0 && error == 0; i++) { error = copychan_prev(chans, maxchans, nchans, flags[i] | chan_flags); Index: ieee80211_crypto_ccmp.c =================================================================== --- ieee80211_crypto_ccmp.c (revision 358490) +++ ieee80211_crypto_ccmp.c (working copy) @@ -302,6 +302,10 @@ /* * Ok to update rsc now. + * + * XXX Note - for AMSDU driver/hardware decaped frames they will have + * the same PN (or none? Should check) so we should skip bumping the + * PN. */ if (! ((rxs != NULL) && (rxs->c_pktflags & IEEE80211_RX_F_IV_STRIP))) { k->wk_keyrsc[tid] = pn; Index: ieee80211_crypto_tkip.c =================================================================== --- ieee80211_crypto_tkip.c (revision 358490) +++ ieee80211_crypto_tkip.c (working copy) @@ -441,6 +441,11 @@ * Ok to update rsc now that MIC has been verified. */ tid = ieee80211_gettid(wh); + + /* + * Note - check CCMP comment - don't do this for sub-frames of + * a decap'd AMSDU frame from driver/hardware. + */ k->wk_keyrsc[tid] = ctx->rx_rsc; finish: Index: ieee80211_ddb.c =================================================================== --- ieee80211_ddb.c (revision 358490) +++ ieee80211_ddb.c (working copy) @@ -209,6 +209,7 @@ static void _db_show_rxampdu(const char *sep, int ix, const struct ieee80211_rx_ampdu *rap) { + struct mbuf *m; int i; db_printf("%srxampdu[%d]: %p flags 0x%x tid %u\n", @@ -219,10 +220,15 @@ db_printf("%s age %d nframes %d\n", sep, rap->rxa_age, rap->rxa_nframes); for (i = 0; i < IEEE80211_AGGR_BAWMAX; i++) - if (rap->rxa_m[i] != NULL) - db_printf("%s m[%2u:%4u] %p\n", sep, i, - IEEE80211_SEQ_ADD(rap->rxa_start, i), - rap->rxa_m[i]); + if (mbufq_len(&rap->rxa_mq[i]) > 0) { + db_printf("%s m[%2u:%4u] ", sep, i, + IEEE80211_SEQ_ADD(rap->rxa_start, i)); + STAILQ_FOREACH(m, &rap->rxa_mq[i].mq_head, + m_stailqpkt) { + db_printf(" %p", m); + } + db_printf("\n"); + } } static void Index: ieee80211_ht.c =================================================================== --- ieee80211_ht.c (revision 358490) +++ ieee80211_ht.c (working copy) @@ -517,6 +517,22 @@ return m; /* last delivered by caller */ } +static void +ampdu_rx_purge_slot(struct ieee80211_rx_ampdu *rap, int i) +{ + struct mbuf *m; + + /* Walk the queue, removing frames as appropriate */ + while (mbufq_len(&rap->rxa_mq[i]) != 0) { + m = mbufq_dequeue(&rap->rxa_mq[i]); + if (m == NULL) + break; + rap->rxa_qbytes -= m->m_pkthdr.len; + rap->rxa_qframes--; + m_freem(m); + } +} + /* * Add the given frame to the current RX reorder slot. * @@ -528,16 +544,94 @@ ampdu_rx_add_slot(struct ieee80211_rx_ampdu *rap, int off, int tid, ieee80211_seq rxseq, struct ieee80211_node *ni, - struct mbuf *m) + struct mbuf *m, + const struct ieee80211_rx_stats *rxs) { + const struct ieee80211_rx_stats *rxs_final = NULL; struct ieee80211vap *vap = ni->ni_vap; + int toss_dup; +#define PROCESS 0 /* caller should process frame */ +#define CONSUMED 1 /* frame consumed, caller does nothing */ - if (rap->rxa_m[off] == NULL) { - rap->rxa_m[off] = m; + /* + * Figure out if this is a duplicate frame for the given slot. + * + * We're assuming that the driver will hand us all the frames + * for a given AMSDU decap pass and if we get /a/ frame + * for an AMSDU decap then we'll get all of them. + * + * The tricksy bit is that we don't know when the /end/ of + * the decap pass is, because we aren't tracking state here + * per-slot to know that we've finished receiving the frame list. + * + * The driver sets RX_F_AMSDU and RX_F_AMSDU_MORE to tell us + * what's going on; so ideally we'd just check the frame at the + * end of the reassembly slot to see if its F_AMSDU w/ no F_AMSDU_MORE - + * that means we've received the whole AMSDU decap pass. + */ + + /* + * Get the rxs of the final mbuf in the slot, if one exists. + */ + if (mbufq_len(&rap->rxa_mq[off]) != 0) { + rxs_final = ieee80211_get_rx_params_ptr(mbufq_last(&rap->rxa_mq[off])); + } + + /* Default to tossing the duplicate frame */ + toss_dup = 1; + + /* + * Check to see if the final frame has F_AMSDU and F_AMSDU set, AND + * this frame has F_AMSDU set (MORE or otherwise.) That's a sign + * that more can come. + */ + + if ((rxs != NULL) && (rxs_final != NULL) && + ieee80211_check_rxseq_amsdu(rxs) && + ieee80211_check_rxseq_amsdu(rxs_final)) { + if (! ieee80211_check_rxseq_amsdu_more(rxs_final)) { + /* + * amsdu_more() returning 0 means "it's not the + * final frame" so we can append more + * frames here. + */ + toss_dup = 0; + } + } + + /* + * If the list is empty OR we have determined we can put more + * driver decap'ed AMSDU frames in here, then insert. + */ + if ((mbufq_len(&rap->rxa_mq[off]) == 0) || (toss_dup == 0)) { + if (mbufq_enqueue(&rap->rxa_mq[off], m) != 0) { + IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, + ni->ni_macaddr, + "a-mpdu queue fail", + "seqno %u tid %u BA win <%u:%u> off=%d, qlen=%d, maxqlen=%d", + rxseq, tid, rap->rxa_start, + IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1), + off, + mbufq_len(&rap->rxa_mq[off]), + rap->rxa_mq[off].mq_maxlen); + /* XXX error count */ + m_freem(m); + return CONSUMED; + } rap->rxa_qframes++; rap->rxa_qbytes += m->m_pkthdr.len; vap->iv_stats.is_ampdu_rx_reorder++; - return (0); + /* + * Statistics for AMSDU decap. + */ + if (rxs != NULL && ieee80211_check_rxseq_amsdu(rxs)) { + if (ieee80211_check_rxseq_amsdu_more(rxs)) { + /* more=1, AMSDU, end of batch */ + IEEE80211_NODE_STAT(ni, rx_amsdu_more_end); + } else { + IEEE80211_NODE_STAT(ni, rx_amsdu_more); + } + } } else { IEEE80211_DISCARD_MAC(vap, IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, @@ -545,28 +639,29 @@ "seqno %u tid %u BA win <%u:%u>", rxseq, tid, rap->rxa_start, IEEE80211_SEQ_ADD(rap->rxa_start, rap->rxa_wnd-1)); + if (rxs != NULL) { + IEEE80211_DISCARD_MAC(vap, + IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, + ni->ni_macaddr, "a-mpdu duplicate", + "seqno %d tid %u pktflags 0x%08x\n", + rxseq, tid, rxs->c_pktflags); + } + if (rxs_final != NULL) { + IEEE80211_DISCARD_MAC(vap, + IEEE80211_MSG_INPUT | IEEE80211_MSG_11N, + ni->ni_macaddr, "a-mpdu duplicate", + "final: pktflags 0x%08x\n", + rxs_final->c_pktflags); + } vap->iv_stats.is_rx_dup++; IEEE80211_NODE_STAT(ni, rx_dup); m_freem(m); - return (-1); } + return CONSUMED; +#undef CONSUMED +#undef PROCESS } -static void -ampdu_rx_purge_slot(struct ieee80211_rx_ampdu *rap, int i) -{ - struct mbuf *m; - - m = rap->rxa_m[i]; - if (m == NULL) - return; - - rap->rxa_m[i] = NULL; - rap->rxa_qbytes -= m->m_pkthdr.len; - rap->rxa_qframes--; - m_freem(m); -} - /* * Purge all frames in the A-MPDU re-order queue. */ @@ -585,6 +680,18 @@ rap->rxa_qbytes, rap->rxa_qframes)); } +static void +ieee80211_ampdu_rx_init_rap(struct ieee80211_node *ni, + struct ieee80211_rx_ampdu *rap) +{ + int i; + + /* XXX TODO: ensure the queues are empty */ + memset(rap, 0, sizeof(*rap)); + for (i = 0; i < IEEE80211_AGGR_BAWMAX; i++) + mbufq_init(&rap->rxa_mq[i], 256); +} + /* * Start A-MPDU rx/re-order processing for the specified TID. */ @@ -601,7 +708,7 @@ */ ampdu_rx_purge(rap); } - memset(rap, 0, sizeof(*rap)); + ieee80211_ampdu_rx_init_rap(ni, rap); rap->rxa_wnd = (bufsiz == 0) ? IEEE80211_AGGR_BAWMAX : min(bufsiz, IEEE80211_AGGR_BAWMAX); rap->rxa_start = MS(baseqctl, IEEE80211_BASEQ_START); @@ -630,7 +737,8 @@ ampdu_rx_purge(rap); } - memset(rap, 0, sizeof(*rap)); + ieee80211_ampdu_rx_init_rap(ni, rap); + rap->rxa_wnd = (baw== 0) ? IEEE80211_AGGR_BAWMAX : min(baw, IEEE80211_AGGR_BAWMAX); if (seq == -1) { @@ -698,18 +806,20 @@ int i) { struct mbuf *m; + int n = 0; - if (rap->rxa_m[i] == NULL) - return (0); + while (mbufq_len(&rap->rxa_mq[i]) != 0) { + m = mbufq_dequeue(&rap->rxa_mq[i]); + if (m == NULL) + break; + n++; - m = rap->rxa_m[i]; - rap->rxa_m[i] = NULL; - rap->rxa_qbytes -= m->m_pkthdr.len; - rap->rxa_qframes--; + rap->rxa_qbytes -= m->m_pkthdr.len; + rap->rxa_qframes--; - ampdu_dispatch(ni, m); - - return (1); + ampdu_dispatch(ni, m); + } + return (n); } static void @@ -718,22 +828,22 @@ { struct ieee80211vap *vap = ni->ni_vap; + /* + * If frames remain, copy the mbuf pointers down so + * they correspond to the offsets in the new window. + */ if (rap->rxa_qframes != 0) { int n = rap->rxa_qframes, j; - - if (winstart != -1) { + for (j = i+1; j < rap->rxa_wnd; j++) { /* - * NB: in window-sliding mode, loop assumes i > 0 - * and/or rxa_m[0] is NULL + * Concat the list contents over, which will + * blank the source list for us. */ - KASSERT(rap->rxa_m[0] == NULL, - ("%s: BA window slot 0 occupied", __func__)); - } - for (j = i+1; j < rap->rxa_wnd; j++) { - if (rap->rxa_m[j] != NULL) { - rap->rxa_m[j-i] = rap->rxa_m[j]; - rap->rxa_m[j] = NULL; - if (--n == 0) + if (mbufq_len(&rap->rxa_mq[j]) != 0) { + n = n - mbufq_len(&rap->rxa_mq[j]); + mbufq_concat(&rap->rxa_mq[j-i], &rap->rxa_mq[j]); + KASSERT(n >= 0, ("%s: n < 0 (%d)", __func__, n)); + if (n == 0) break; } } @@ -758,18 +868,18 @@ ampdu_rx_dispatch(struct ieee80211_rx_ampdu *rap, struct ieee80211_node *ni) { struct ieee80211vap *vap = ni->ni_vap; - int i; + int i, r, r2; /* flush run of frames */ + r2 = 0; for (i = 1; i < rap->rxa_wnd; i++) { - if (ampdu_dispatch_slot(rap, ni, i) == 0) + r = ampdu_dispatch_slot(rap, ni, i); + if (r == 0) break; + r2 += r; } - /* - * If frames remain, copy the mbuf pointers down so - * they correspond to the offsets in the new window. - */ + /* move up frames */ ampdu_rx_moveup(rap, ni, i, -1); /* @@ -777,7 +887,14 @@ * reflect the frames just dispatched. */ rap->rxa_start = IEEE80211_SEQ_ADD(rap->rxa_start, i); - vap->iv_stats.is_ampdu_rx_oor += i; + vap->iv_stats.is_ampdu_rx_oor += r2; + + IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, + "%s: moved slot up %d slots to start at %d (%d frames)\n", + __func__, + i, + rap->rxa_start, + r2); } /* @@ -786,7 +903,6 @@ static void ampdu_rx_flush(struct ieee80211_node *ni, struct ieee80211_rx_ampdu *rap) { - struct ieee80211vap *vap = ni->ni_vap; int i, r; for (i = 0; i < rap->rxa_wnd; i++) { @@ -793,8 +909,15 @@ r = ampdu_dispatch_slot(rap, ni, i); if (r == 0) continue; - vap->iv_stats.is_ampdu_rx_oor += r; + ni->ni_vap->iv_stats.is_ampdu_rx_oor += r; + IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, + "%s: moved slot up %d slots to start at %d (%d frames)\n", + __func__, + 1, + rap->rxa_start, + r); + if (rap->rxa_qframes == 0) break; } @@ -822,14 +945,23 @@ */ seqno = rap->rxa_start; for (i = 0; i < rap->rxa_wnd; i++) { - r = ampdu_dispatch_slot(rap, ni, i); - if (r == 0) { + if ((r = mbufq_len(&rap->rxa_mq[i])) != 0) { + (void) ampdu_dispatch_slot(rap, ni, i); + } else { if (!IEEE80211_SEQ_BA_BEFORE(seqno, winstart)) break; } vap->iv_stats.is_ampdu_rx_oor += r; seqno = IEEE80211_SEQ_INC(seqno); + + IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, ni, + "%s: moved slot up %d slots to start at %d (%d frames)\n", + __func__, + 1, + seqno, + r); } + /* * If frames remain, copy the mbuf pointers down so * they correspond to the offsets in the new window. @@ -852,6 +984,16 @@ * this frame completes a run, flush any pending frames. We * return 1 if the frame is consumed. A 0 is returned if * the frame should be processed normally by the caller. + * + * XXX TODO: handle hardware decap'ed A-MSDU frames that are + * pretending to be MPDU's. This may mean that we have to + * buffer not a single mbuf, but a list of mbufs for the same + * MSDU. The receive path may not (yet) have enough metadata + * to accurately track A-MSDU boundaries, so this is a tricky + * one. (Eg, maybe we should keep a sequence number per MPDU + * so MSDUs effectively have a separate sequence number; + * or expect the hardware will hand us all MSDUs in one rush + * so we can push a list A-MSDUs up in one list? Not sure yet.) */ int ieee80211_ampdu_reorder(struct ieee80211_node *ni, struct mbuf *m, @@ -933,7 +1075,7 @@ /* * Dispatch as many packets as we can. */ - KASSERT(rap->rxa_m[0] == NULL, ("unexpected dup")); + KASSERT((mbufq_len(&rap->rxa_mq[0]) == 0), ("unexpected dup")); ampdu_dispatch(ni, m); ampdu_rx_dispatch(rap, ni); return CONSUMED; @@ -941,8 +1083,25 @@ /* * In order; advance window and notify * caller to dispatch directly. + * + * Note - if this is a hardware AMSDU decap'ed frame + * then don't increment the pointer, we have more frames + * coming. */ - rap->rxa_start = IEEE80211_SEQ_INC(rxseq); + if ((rxs != NULL) && ieee80211_check_rxseq_amsdu(rxs)) { + /* Hardware/driver decap'ed AMSDU, do further checks */ + if (ieee80211_check_rxseq_amsdu_more(rxs)) { + /* No more; finish */ + rap->rxa_start = IEEE80211_SEQ_INC(rxseq); + IEEE80211_NODE_STAT(ni, rx_amsdu_more_end); + } else { + IEEE80211_NODE_STAT(ni, rx_amsdu_more); + } + } else { + /* Not driver/hardware decap'ed AMSDU; bump tracking */ + rap->rxa_start = IEEE80211_SEQ_INC(rxseq); + } + return PROCESS; } } @@ -997,8 +1156,7 @@ } /* save packet - this consumes, no matter what */ - ampdu_rx_add_slot(rap, off, tid, rxseq, ni, m); - + ampdu_rx_add_slot(rap, off, tid, rxseq, ni, m, rxs); return CONSUMED; } if (off < IEEE80211_SEQ_BA_RANGE) { @@ -1162,8 +1320,19 @@ tap->txa_ni = ni; ieee80211_txampdu_init_pps(tap); /* NB: further initialization deferred */ + + /* + * RX initialisation - initialise the per-slot + * reorder buffer list + */ + ieee80211_ampdu_rx_init_rap(ni, &ni->ni_rx_ampdu[tid]); } ni->ni_flags |= IEEE80211_NODE_HT | IEEE80211_NODE_AMPDU; + IEEE80211_NOTE(ni->ni_vap, IEEE80211_MSG_11N, + ni, + "%s: finished (%p)", + __func__, + ni); } /* Index: ieee80211_ht.h =================================================================== --- ieee80211_ht.h (revision 358490) +++ ieee80211_ht.h (working copy) @@ -33,6 +33,15 @@ * 802.11n protocol implementation definitions. */ +/* + * XXX TODO: this will break userland code referencing these headers. + * The definitions and routines should be split. + * + * Yes, this is what's needed for mbufq; maybe that's also something + * to fix. Sigh. + */ +#include + #define IEEE80211_AGGR_BAWMAX 64 /* max block ack window size */ /* threshold for aging overlapping non-HT bss */ #define IEEE80211_NONHT_PRESENT_AGE msecs_to_ticks(60*1000) @@ -160,7 +169,7 @@ uint16_t rxa_wnd; /* BA window size */ int rxa_age; /* age of oldest frame in window */ int rxa_nframes; /* frames since ADDBA */ - struct mbuf *rxa_m[IEEE80211_AGGR_BAWMAX]; + struct mbufq rxa_mq[IEEE80211_AGGR_BAWMAX]; void *rxa_private; uint64_t rxa_pad[3]; }; Index: ieee80211_ioctl.c =================================================================== --- ieee80211_ioctl.c (revision 358490) +++ ieee80211_ioctl.c (working copy) @@ -715,8 +715,14 @@ dc->dc_vhtcaps = ic->ic_vhtcaps; ci = &dc->dc_chaninfo; ic->ic_getradiocaps(ic, maxchans, &ci->ic_nchans, ci->ic_chans); + + /* + * XXX TODO: this assertion is wrong; we should warn and error out + * as maxchans comes from userland! + */ KASSERT(ci->ic_nchans <= maxchans, ("nchans %d maxchans %d", ci->ic_nchans, maxchans)); + ieee80211_sort_channels(ci->ic_chans, ci->ic_nchans); error = copyout(dc, ireq->i_data, IEEE80211_DEVCAPS_SPACE(dc)); IEEE80211_FREE(dc, M_TEMP);