[net80211] Initial A-MSDU support for testing / evaluation

A-MSDU is another 11n aggregation mechanism where multiple ethernet
frames get LLC encapsulated (so they have a length field), padded,
and put in a single MPDU (802.11 MAC frame.)  This means it gets sent
out as a single frame, with a single seqno, it's acked as one frame, etc.

It turns out that, hah, atheros fast frames is almost but not quite
like this, so I'm reusing all of the current superg/fast-frames stuff
in order to actually transmit A-MSDU.  Yes, this means that A-MSDU
frames are also only aggregated two at a time, so it's not necessarily
a huge win, but it's better than nothing.

This doesn't do anything by default - the driver needs to say it does
A-MSDU as well as set the AMSDU software TX capability so this code path
gets exercised.

For now, the only driver that enables this is urtwn.  I'll enable it
for rsu at some point soon.
Tested:

* Add an amsdu encap path to aggregate two frames, same as the
  fast-frames path.

* Always do the superg init/teardown and node init/teardown stuff,
  regardless of whether the nodes are doing fast-frames (the ATH
  capability stuff.)  That way we can reuse it for amsdu.

* Don't do AMSDU for multicast/broadcast and EAPOL frames.

* If we're doing A-MPDU, then don't bother doing FF/A-MSDU.
  We can likely do both together, but I don't want to change
  behaviour.

* Teach the fast frames approx txtime logic to support the 11n
  rates.  But, since we don't currently have a full "current rate"
  support, assume it's HT20, long-gi, etc.  That way we overshoot
  on the TX time estimation, so we're always inside the requirements.
  (And we only aggregate two frames for now, so we're not really
  going to exceed that.)

* Drop the maximum FF age default down to 2ms, otherwise we end up
  with some very annoyingly large latencies.

TODO:

* We only aggregate two ethernet frames, so I'm not checking the max
  A-MSDU size.  But when it comes time to support >2 frames, we should
  obey that.

Tested:

* urtwn(4)
This commit is contained in:
Adrian Chadd 2016-04-06 01:21:51 +00:00
parent 678fec509d
commit 1c7b0c8456
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=297603
6 changed files with 309 additions and 95 deletions

View File

@ -65,6 +65,10 @@ struct ieee80211_tx_ampdu {
#define IEEE80211_AMPDU_RUNNING(tap) \
(((tap)->txa_flags & IEEE80211_AGGR_RUNNING) != 0)
/* return non-zero if AMPDU tx for the TID was NACKed */
#define IEEE80211_AMPDU_NACKED(tap)\
(!! ((tap)->txa_flags & IEEE80211_AGGR_NAK))
/* return non-zero if AMPDU tx for the TID is running or started */
#define IEEE80211_AMPDU_REQUESTED(tap) \
(((tap)->txa_flags & \

View File

@ -1014,8 +1014,8 @@ node_cleanup(struct ieee80211_node *ni)
if (ni->ni_flags & IEEE80211_NODE_HT)
ieee80211_ht_node_cleanup(ni);
#ifdef IEEE80211_SUPPORT_SUPERG
else if (ni->ni_ath_flags & IEEE80211_NODE_ATH)
ieee80211_ff_node_cleanup(ni);
/* Always do FF node cleanup; for A-MSDU */
ieee80211_ff_node_cleanup(ni);
#endif
#ifdef IEEE80211_SUPPORT_MESH
/*

View File

@ -191,51 +191,68 @@ ieee80211_vap_pkt_send_dest(struct ieee80211vap *vap, struct mbuf *m,
* otherwise unable to establish a BA stream.
*/
if ((ni->ni_flags & IEEE80211_NODE_AMPDU_TX) &&
(vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX) &&
(m->m_flags & M_EAPOL) == 0) {
int tid = WME_AC_TO_TID(M_WME_GETAC(m));
struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid];
(vap->iv_flags_ht & IEEE80211_FHT_AMPDU_TX)) {
if ((m->m_flags & M_EAPOL) == 0) {
int tid = WME_AC_TO_TID(M_WME_GETAC(m));
struct ieee80211_tx_ampdu *tap = &ni->ni_tx_ampdu[tid];
ieee80211_txampdu_count_packet(tap);
if (IEEE80211_AMPDU_RUNNING(tap)) {
/*
* Operational, mark frame for aggregation.
*
* XXX do tx aggregation here
*/
m->m_flags |= M_AMPDU_MPDU;
} else if (!IEEE80211_AMPDU_REQUESTED(tap) &&
ic->ic_ampdu_enable(ni, tap)) {
/*
* Not negotiated yet, request service.
*/
ieee80211_ampdu_request(ni, tap);
/* XXX hold frame for reply? */
ieee80211_txampdu_count_packet(tap);
if (IEEE80211_AMPDU_RUNNING(tap)) {
/*
* Operational, mark frame for aggregation.
*
* XXX do tx aggregation here
*/
m->m_flags |= M_AMPDU_MPDU;
} else if (!IEEE80211_AMPDU_REQUESTED(tap) &&
ic->ic_ampdu_enable(ni, tap)) {
/*
* Not negotiated yet, request service.
*/
ieee80211_ampdu_request(ni, tap);
/* XXX hold frame for reply? */
}
}
}
/*
* XXX If we aren't doing AMPDU TX then we /could/ do
* fast-frames encapsulation, however right now this
* output logic doesn't handle that case.
*
* So we'll be limited to "fast-frames" xmit for non-11n STA
* and "no fast frames" xmit for 11n STAs.
* It'd be nice to eventually test fast-frames out by
* gracefully falling from failing A-MPDU transmission
* (driver says no, fail to negotiate it with peer) to
* using fast-frames.
*
* Note: we can actually put A-MSDU's inside an A-MPDU,
* so hopefully we can figure out how to make that particular
* combination work right.
*/
#ifdef IEEE80211_SUPPORT_SUPERG
else if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_FF)) {
m = ieee80211_ff_check(ni, m);
if (m == NULL) {
/* NB: any ni ref held on stageq */
return (0);
/*
* Check for AMSDU/FF; queue for aggregation
*
* Note: we don't bother trying to do fast frames or
* A-MSDU encapsulation for 802.3 drivers. Now, we
* likely could do it for FF (because it's a magic
* atheros tunnel LLC type) but I don't think we're going
* to really need to. For A-MSDU we'd have to set the
* A-MSDU QoS bit in the wifi header, so we just plain
* can't do it.
*
* Strictly speaking, we could actually /do/ A-MSDU / FF
* with A-MPDU together which for certain circumstances
* is beneficial (eg A-MSDU of TCK ACKs.) However,
* I'll ignore that for now so existing behaviour is maintained.
* Later on it would be good to make "amsdu + ampdu" configurable.
*/
else if (__predict_true((vap->iv_caps & IEEE80211_C_8023ENCAP) == 0)) {
if ((! mcast) && ieee80211_amsdu_tx_ok(ni)) {
m = ieee80211_amsdu_check(ni, m);
if (m == NULL) {
/* NB: any ni ref held on stageq */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: amsdu_check queued frame\n",
__func__);
return (0);
}
} else if ((! mcast) && IEEE80211_ATH_CAP(vap, ni,
IEEE80211_NODE_FF)) {
m = ieee80211_ff_check(ni, m);
if (m == NULL) {
/* NB: any ni ref held on stageq */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: ff_check queued frame\n",
__func__);
return (0);
}
}
}
#endif /* IEEE80211_SUPPORT_SUPERG */
@ -1229,6 +1246,7 @@ ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni,
ieee80211_seq seqno;
int meshhdrsize, meshae;
uint8_t *qos;
int is_amsdu = 0;
IEEE80211_TX_LOCK_ASSERT(ic);
@ -1383,9 +1401,19 @@ ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni,
} else {
#ifdef IEEE80211_SUPPORT_SUPERG
/*
* Aggregated frame.
* Aggregated frame. Check if it's for AMSDU or FF.
*
* XXX TODO: IEEE80211_NODE_AMSDU* isn't implemented
* anywhere for some reason. But, since 11n requires
* AMSDU RX, we can just assume "11n" == "AMSDU".
*/
m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key);
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG, "%s: called; M_FF\n", __func__);
if (ieee80211_amsdu_tx_ok(ni)) {
m = ieee80211_amsdu_encap(vap, m, hdrspace + meshhdrsize, key);
is_amsdu = 1;
} else {
m = ieee80211_ff_encap(vap, m, hdrspace + meshhdrsize, key);
}
if (m == NULL)
#endif
goto bad;
@ -1521,6 +1549,13 @@ ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni,
qos[1] = 0;
wh->i_fc[0] |= IEEE80211_FC0_SUBTYPE_QOS;
/*
* If this is an A-MSDU then ensure we set the
* relevant field.
*/
if (is_amsdu)
qos[0] |= IEEE80211_QOS_AMSDU;
if ((m->m_flags & M_AMPDU_MPDU) == 0) {
/*
* NB: don't assign a sequence # to potential
@ -1544,6 +1579,14 @@ ieee80211_encap(struct ieee80211vap *vap, struct ieee80211_node *ni,
*(uint16_t *)wh->i_seq =
htole16(seqno << IEEE80211_SEQ_SEQ_SHIFT);
M_SEQNO_SET(m, seqno);
/*
* XXX TODO: we shouldn't allow EAPOL, etc that would
* be forced to be non-QoS traffic to be A-MSDU encapsulated.
*/
if (is_amsdu)
printf("%s: XXX ERROR: is_amsdu set; not QoS!\n",
__func__);
}

View File

@ -1703,12 +1703,8 @@ sta_recv_mgmt(struct ieee80211_node *ni, struct mbuf *m0, int subtype,
ieee80211_setup_basic_htrates(ni, htinfo);
ieee80211_node_setuptxparms(ni);
ieee80211_ratectl_node_init(ni);
} else {
#ifdef IEEE80211_SUPPORT_SUPERG
if (IEEE80211_ATH_CAP(vap, ni, IEEE80211_NODE_ATH))
ieee80211_ff_node_init(ni);
#endif
}
/*
* Configure state now that we are associated.
*

View File

@ -99,18 +99,22 @@ ieee80211_superg_attach(struct ieee80211com *ic)
{
struct ieee80211_superg *sg;
if (ic->ic_caps & IEEE80211_C_FF) {
sg = (struct ieee80211_superg *) IEEE80211_MALLOC(
sizeof(struct ieee80211_superg), M_80211_VAP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (sg == NULL) {
printf("%s: cannot allocate SuperG state block\n",
__func__);
return;
}
ic->ic_superg = sg;
sg = (struct ieee80211_superg *) IEEE80211_MALLOC(
sizeof(struct ieee80211_superg), M_80211_VAP,
IEEE80211_M_NOWAIT | IEEE80211_M_ZERO);
if (sg == NULL) {
printf("%s: cannot allocate SuperG state block\n",
__func__);
return;
}
ieee80211_ffagemax = msecs_to_ticks(150);
ic->ic_superg = sg;
/*
* Default to not being so aggressive for FF/AMSDU
* aging, otherwise we may hold a frame around
* for way too long before we expire it out.
*/
ieee80211_ffagemax = msecs_to_ticks(2);
}
void
@ -353,13 +357,145 @@ ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace,
goto bad;
}
m1->m_nextpkt = NULL;
/*
* Include fast frame headers in adjusting header layout.
* Adjust to include 802.11 header requirement.
*/
KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!"));
ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t));
m1 = ieee80211_mbuf_adjust(vap, hdrspace, key, m1);
if (m1 == NULL) {
printf("%s: failed initial mbuf_adjust\n", __func__);
/* NB: ieee80211_mbuf_adjust handles msgs+statistics */
m_freem(m2);
goto bad;
}
/*
* Copy second frame's Ethernet header out of line
* and adjust for possible padding in case there isn't room
* at the end of first frame.
*/
KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!"));
ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t));
m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2);
if (m2 == NULL) {
/* NB: ieee80211_mbuf_adjust handles msgs+statistics */
printf("%s: failed second \n", __func__);
goto bad;
}
/*
* Now do tunnel encapsulation. First, each
* frame gets a standard encapsulation.
*/
m1 = ieee80211_ff_encap1(vap, m1, &eh1);
if (m1 == NULL)
goto bad;
m2 = ieee80211_ff_encap1(vap, m2, &eh2);
if (m2 == NULL)
goto bad;
/*
* Pad leading frame to a 4-byte boundary. If there
* is space at the end of the first frame, put it
* there; otherwise prepend to the front of the second
* frame. We know doing the second will always work
* because we reserve space above. We prefer appending
* as this typically has better DMA alignment properties.
*/
for (m = m1; m->m_next != NULL; m = m->m_next)
;
pad = roundup2(m1->m_pkthdr.len, 4) - m1->m_pkthdr.len;
if (pad) {
if (M_TRAILINGSPACE(m) < pad) { /* prepend to second */
m2->m_data -= pad;
m2->m_len += pad;
m2->m_pkthdr.len += pad;
} else { /* append to first */
m->m_len += pad;
m1->m_pkthdr.len += pad;
}
}
/*
* A-MSDU's are just appended; the "I'm A-MSDU!" bit is in the
* QoS header.
*
* XXX optimize by prepending together
*/
m->m_next = m2; /* NB: last mbuf from above */
m1->m_pkthdr.len += m2->m_pkthdr.len;
M_PREPEND(m1, sizeof(uint32_t)+2, M_NOWAIT);
if (m1 == NULL) { /* XXX cannot happen */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: no space for tunnel header\n", __func__);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
memset(mtod(m1, void *), 0, sizeof(uint32_t)+2);
M_PREPEND(m1, sizeof(struct llc), M_NOWAIT);
if (m1 == NULL) { /* XXX cannot happen */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: no space for llc header\n", __func__);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
llc = mtod(m1, struct llc *);
llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP;
llc->llc_control = LLC_UI;
llc->llc_snap.org_code[0] = ATH_FF_SNAP_ORGCODE_0;
llc->llc_snap.org_code[1] = ATH_FF_SNAP_ORGCODE_1;
llc->llc_snap.org_code[2] = ATH_FF_SNAP_ORGCODE_2;
llc->llc_snap.ether_type = htons(ATH_FF_ETH_TYPE);
vap->iv_stats.is_ff_encap++;
return m1;
bad:
vap->iv_stats.is_ff_encapfail++;
if (m1 != NULL)
m_freem(m1);
if (m2 != NULL)
m_freem(m2);
return NULL;
}
/*
* A-MSDU encapsulation.
*
* This assumes just two frames for now, since we're borrowing the
* same queuing code and infrastructure as fast-frames.
*
* There must be two packets chained with m_nextpkt.
* We do header adjustment for each, and then concatenate the mbuf chains
* to form a single frame for transmission.
*/
struct mbuf *
ieee80211_amsdu_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace,
struct ieee80211_key *key)
{
struct mbuf *m2;
struct ether_header eh1, eh2;
struct mbuf *m;
int pad;
m2 = m1->m_nextpkt;
if (m2 == NULL) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: only one frame\n", __func__);
goto bad;
}
m1->m_nextpkt = NULL;
/*
* Include A-MSDU header in adjusting header layout.
*/
KASSERT(m1->m_len >= sizeof(eh1), ("no ethernet header!"));
ETHER_HEADER_COPY(&eh1, mtod(m1, caddr_t));
m1 = ieee80211_mbuf_adjust(vap,
hdrspace + sizeof(struct llc) + sizeof(uint32_t) + 2 +
hdrspace + sizeof(struct llc) + sizeof(uint32_t) +
sizeof(struct ether_header),
key, m1);
if (m1 == NULL) {
@ -376,9 +512,7 @@ ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace,
*/
KASSERT(m2->m_len >= sizeof(eh2), ("no ethernet header!"));
ETHER_HEADER_COPY(&eh2, mtod(m2, caddr_t));
m2 = ieee80211_mbuf_adjust(vap,
ATH_FF_MAX_HDR_PAD + sizeof(struct ether_header),
NULL, m2);
m2 = ieee80211_mbuf_adjust(vap, 4, NULL, m2);
if (m2 == NULL) {
/* NB: ieee80211_mbuf_adjust handles msgs+statistics */
goto bad;
@ -418,42 +552,16 @@ ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace,
}
/*
* Now, stick 'em together and prepend the tunnel headers;
* first the Atheros tunnel header (all zero for now) and
* then a special fast frame LLC.
*
* XXX optimize by prepending together
* Now, stick 'em together.
*/
m->m_next = m2; /* NB: last mbuf from above */
m1->m_pkthdr.len += m2->m_pkthdr.len;
M_PREPEND(m1, sizeof(uint32_t)+2, M_NOWAIT);
if (m1 == NULL) { /* XXX cannot happen */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: no space for tunnel header\n", __func__);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
memset(mtod(m1, void *), 0, sizeof(uint32_t)+2);
M_PREPEND(m1, sizeof(struct llc), M_NOWAIT);
if (m1 == NULL) { /* XXX cannot happen */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SUPERG,
"%s: no space for llc header\n", __func__);
vap->iv_stats.is_tx_nobuf++;
return NULL;
}
llc = mtod(m1, struct llc *);
llc->llc_dsap = llc->llc_ssap = LLC_SNAP_LSAP;
llc->llc_control = LLC_UI;
llc->llc_snap.org_code[0] = ATH_FF_SNAP_ORGCODE_0;
llc->llc_snap.org_code[1] = ATH_FF_SNAP_ORGCODE_1;
llc->llc_snap.org_code[2] = ATH_FF_SNAP_ORGCODE_2;
llc->llc_snap.ether_type = htons(ATH_FF_ETH_TYPE);
vap->iv_stats.is_ff_encap++;
vap->iv_stats.is_amsdu_encap++;
return m1;
bad:
vap->iv_stats.is_amsdu_encapfail++;
if (m1 != NULL)
m_freem(m1);
if (m2 != NULL)
@ -461,6 +569,7 @@ ieee80211_ff_encap(struct ieee80211vap *vap, struct mbuf *m1, int hdrspace,
return NULL;
}
static void
ff_transmit(struct ieee80211_node *ni, struct mbuf *m)
{
@ -605,6 +714,7 @@ ff_approx_txtime(struct ieee80211_node *ni,
struct ieee80211com *ic = ni->ni_ic;
struct ieee80211vap *vap = ni->ni_vap;
uint32_t framelen;
uint32_t frame_time;
/*
* Approximate the frame length to be transmitted. A swag to add
@ -621,7 +731,21 @@ ff_approx_txtime(struct ieee80211_node *ni,
framelen += 24;
if (m2 != NULL)
framelen += m2->m_pkthdr.len;
return ieee80211_compute_duration(ic->ic_rt, framelen, ni->ni_txrate, 0);
/*
* For now, we assume non-shortgi, 20MHz, just because I want to
* at least test 802.11n.
*/
if (ni->ni_txrate & IEEE80211_RATE_MCS)
frame_time = ieee80211_compute_duration_ht(framelen,
ni->ni_txrate,
IEEE80211_HT_RC_2_STREAMS(ni->ni_txrate),
0, /* isht40 */
0); /* isshortgi */
else
frame_time = ieee80211_compute_duration(ic->ic_rt, framelen,
ni->ni_txrate, 0);
return (frame_time);
}
/*
@ -753,6 +877,30 @@ ieee80211_ff_check(struct ieee80211_node *ni, struct mbuf *m)
return mstaged;
}
struct mbuf *
ieee80211_amsdu_check(struct ieee80211_node *ni, struct mbuf *m)
{
/*
* XXX TODO: actually enforce the node support
* and HTCAP requirements for the maximum A-MSDU
* size.
*/
/* First: software A-MSDU transmit? */
if (! ieee80211_amsdu_tx_ok(ni))
return (m);
/* Next - EAPOL? Nope, don't aggregate; we don't QoS encap them */
if (m->m_flags & (M_EAPOL | M_MCAST | M_BCAST))
return (m);
/* Next - needs to be a data frame, non-broadcast, etc */
if (ETHER_IS_MULTICAST(mtod(m, struct ether_header *)->ether_dhost))
return (m);
return (ieee80211_ff_check(ni, m));
}
void
ieee80211_ff_node_init(struct ieee80211_node *ni)
{

View File

@ -82,6 +82,27 @@ int ieee80211_parse_athparams(struct ieee80211_node *, uint8_t *,
void ieee80211_ff_node_init(struct ieee80211_node *);
void ieee80211_ff_node_cleanup(struct ieee80211_node *);
static inline int
ieee80211_amsdu_tx_ok(struct ieee80211_node *ni)
{
/* First: software A-MSDU transmit? */
if ((ni->ni_ic->ic_caps & IEEE80211_C_SWAMSDUTX) == 0)
return (0);
/* Next: does the VAP have AMSDU TX enabled? */
if ((ni->ni_vap->iv_flags_ht & IEEE80211_FHT_AMSDU_TX) == 0)
return (0);
/* Next: 11n node? (assumed that A-MSDU TX to HT nodes is ok */
if ((ni->ni_flags & IEEE80211_NODE_HT) == 0)
return (0);
/* ok, we can at least /do/ AMSDU to this node */
return (1);
}
struct mbuf * ieee80211_amsdu_check(struct ieee80211_node *ni, struct mbuf *m);
struct mbuf *ieee80211_ff_check(struct ieee80211_node *, struct mbuf *);
void ieee80211_ff_age(struct ieee80211com *, struct ieee80211_stageq *,
int quanta);
@ -122,6 +143,8 @@ ieee80211_ff_age_all(struct ieee80211com *ic, int quanta)
struct mbuf *ieee80211_ff_encap(struct ieee80211vap *, struct mbuf *,
int, struct ieee80211_key *);
struct mbuf * ieee80211_amsdu_encap(struct ieee80211vap *vap, struct mbuf *m1,
int hdrspace, struct ieee80211_key *key);
struct mbuf *ieee80211_ff_decap(struct ieee80211_node *, struct mbuf *);