arn_beacon.c revision dd1de3740722a4b99a74005255effebbd20a6d70
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2008 Atheros Communications Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/strsun.h>
#include <inet/common.h>
#include <inet/nd.h>
#include <inet/mi.h>
#include <inet/wifi_ioctl.h>
#include "arn_core.h"
/*
* This function will modify certain transmit queue properties depending on
* the operating mode of the station (AP or AdHoc). Parameters are AIFS
* settings and channel width min/max
*/
static int
arn_beaconq_config(struct arn_softc *sc)
{
struct ath_hal *ah = sc->sc_ah;
struct ath9k_tx_queue_info qi;
(void) ath9k_hw_get_txq_props(ah, sc->sc_beaconq, &qi);
if (sc->sc_ah->ah_opmode == ATH9K_M_HOSTAP) {
/* Always burst out beacon and CAB traffic. */
qi.tqi_aifs = 1;
qi.tqi_cwmin = 0;
qi.tqi_cwmax = 0;
} else {
/* Adhoc mode; important thing is to use 2x cwmin. */
qi.tqi_aifs = sc->sc_beacon_qi.tqi_aifs;
qi.tqi_cwmin = 2*sc->sc_beacon_qi.tqi_cwmin;
qi.tqi_cwmax = sc->sc_beacon_qi.tqi_cwmax;
}
if (!ath9k_hw_set_txq_props(ah, sc->sc_beaconq, &qi)) {
arn_problem("unable to update h/w beacon queue parameters\n");
return (0);
} else {
/* push to h/w */
(void) ath9k_hw_resettxqueue(ah, sc->sc_beaconq);
return (1);
}
}
/*
* Associates the beacon frame buffer with a transmit descriptor. Will set
* up all required antenna switch parameters, rate codes, and channel flags.
* Beacons are always sent out at the lowest rate, and are not retried.
*/
static void
arn_beacon_setup(struct arn_softc *sc, struct ath_buf *bf)
{
#define USE_SHPREAMBLE(_ic) \
(((_ic)->ic_flags & (IEEE80211_F_SHPREAMBLE | IEEE80211_F_USEBARKER))\
== IEEE80211_F_SHPREAMBLE)
mblk_t *mp = bf->bf_m;
struct ath_hal *ah = sc->sc_ah;
struct ath_desc *ds;
/* LINTED E_FUNC_SET_NOT_USED */
int flags, antenna = 0;
struct ath_rate_table *rt;
uint8_t rix, rate;
struct ath9k_11n_rate_series series[4];
int ctsrate = 0;
int ctsduration = 0;
/* set up descriptors */
ds = bf->bf_desc;
flags = ATH9K_TXDESC_NOACK;
if (sc->sc_ah->ah_opmode == ATH9K_M_IBSS &&
(ah->ah_caps.hw_caps & ATH9K_HW_CAP_VEOL)) {
ds->ds_link = bf->bf_daddr; /* self-linked */
flags |= ATH9K_TXDESC_VEOL;
/*
* Let hardware handle antenna switching.
*/
antenna = 0;
} else {
ds->ds_link = 0;
/*
* Switch antenna every 4 beacons.
* NB: assumes two antenna
*/
antenna = ((sc->ast_be_xmit / sc->sc_nbcnvaps) & 1 ? 2 : 1);
}
ds->ds_data = bf->bf_dma.cookie.dmac_address;
/*
* Calculate rate code.
* XXX everything at min xmit rate
*/
rix = 0;
rt = sc->hw_rate_table[sc->sc_curmode];
rate = rt->info[rix].ratecode;
if (sc->sc_flags & SC_OP_PREAMBLE_SHORT)
rate |= rt->info[rix].short_preamble;
ath9k_hw_set11n_txdesc(ah, ds,
MBLKL(mp) + IEEE80211_CRC_LEN, /* frame length */
ATH9K_PKT_TYPE_BEACON, /* Atheros packet type */
MAX_RATE_POWER, /* FIXME */
ATH9K_TXKEYIX_INVALID, /* no encryption */
ATH9K_KEY_TYPE_CLEAR, /* no encryption */
flags); /* no ack, veol for beacons */
/* NB: beacon's BufLen must be a multiple of 4 bytes */
(void) ath9k_hw_filltxdesc(ah, ds,
roundup(MBLKL(mp), 4), /* buffer length */
B_TRUE, /* first segment */
B_TRUE, /* last segment */
ds); /* first descriptor */
(void) memset(series, 0, sizeof (struct ath9k_11n_rate_series) * 4);
series[0].Tries = 1;
series[0].Rate = rate;
series[0].ChSel = sc->sc_tx_chainmask;
series[0].RateFlags = (ctsrate) ? ATH9K_RATESERIES_RTS_CTS : 0;
ath9k_hw_set11n_ratescenario(ah, ds, ds, 0,
ctsrate, ctsduration, series, 4, 0);
#undef USE_SHPREAMBLE
}
/*
* Startup beacon transmission for adhoc mode when they are sent entirely
* by the hardware using the self-linked descriptor + veol trick.
*/
static void
arn_beacon_start_adhoc(struct arn_softc *sc)
{
struct ath_buf *bf = list_head(&sc->sc_bcbuf_list);
struct ieee80211_node *in = bf->bf_in;
struct ieee80211com *ic = in->in_ic;
struct ath_hal *ah = sc->sc_ah;
mblk_t *mp;
mp = bf->bf_m;
if (ieee80211_beacon_update(ic, bf->bf_in, &sc->asc_boff, mp, 0))
bcopy(mp->b_rptr, bf->bf_dma.mem_va, MBLKL(mp));
/* Construct tx descriptor. */
arn_beacon_setup(sc, bf);
/*
* Stop any current dma and put the new frame on the queue.
* This should never fail since we check above that no frames
* are still pending on the queue.
*/
if (!ath9k_hw_stoptxdma(ah, sc->sc_beaconq)) {
arn_problem("ath: beacon queue %d did not stop?\n",
sc->sc_beaconq);
}
ARN_DMA_SYNC(bf->bf_dma, DDI_DMA_SYNC_FORDEV);
/* NB: caller is known to have already stopped tx dma */
(void) ath9k_hw_puttxbuf(ah, sc->sc_beaconq, bf->bf_daddr);
(void) ath9k_hw_txstart(ah, sc->sc_beaconq);
ARN_DBG((ARN_DBG_BEACON, "arn: arn_bstuck_process(): "
"TXDP%u = %llx (%p)\n", sc->sc_beaconq,
ito64(bf->bf_daddr), bf->bf_desc));
}
uint32_t
arn_beaconq_setup(struct ath_hal *ah)
{
struct ath9k_tx_queue_info qi;
(void) memset(&qi, 0, sizeof (qi));
qi.tqi_aifs = 1;
qi.tqi_cwmin = 0;
qi.tqi_cwmax = 0;
/* NB: don't enable any interrupts */
return (ath9k_hw_setuptxqueue(ah, ATH9K_TX_QUEUE_BEACON, &qi));
}
int
arn_beacon_alloc(struct arn_softc *sc, struct ieee80211_node *in)
{
ieee80211com_t *ic = in->in_ic;
struct ath_buf *bf;
mblk_t *mp;
mutex_enter(&sc->sc_bcbuflock);
bf = list_head(&sc->sc_bcbuf_list);
if (bf == NULL) {
arn_problem("arn: arn_beacon_alloc():"
"no dma buffers");
mutex_exit(&sc->sc_bcbuflock);
return (ENOMEM);
}
mp = ieee80211_beacon_alloc(ic, in, &sc->asc_boff);
if (mp == NULL) {
arn_problem("ath: arn_beacon_alloc():"
"cannot get mbuf\n");
mutex_exit(&sc->sc_bcbuflock);
return (ENOMEM);
}
ASSERT(mp->b_cont == NULL);
bf->bf_m = mp;
bcopy(mp->b_rptr, bf->bf_dma.mem_va, MBLKL(mp));
bf->bf_in = ieee80211_ref_node(in);
mutex_exit(&sc->sc_bcbuflock);
return (0);
}
void
arn_beacon_return(struct arn_softc *sc)
{
struct ath_buf *bf;
mutex_enter(&sc->sc_bcbuflock);
bf = list_head(&sc->sc_bcbuf_list);
while (bf != NULL) {
if (bf->bf_m != NULL) {
freemsg(bf->bf_m);
bf->bf_m = NULL;
}
if (bf->bf_in != NULL) {
ieee80211_free_node(bf->bf_in);
bf->bf_in = NULL;
}
bf = list_next(&sc->sc_bcbuf_list, bf);
}
mutex_exit(&sc->sc_bcbuflock);
}
void
arn_beacon_config(struct arn_softc *sc)
{
struct ath_hal *ah = sc->sc_ah;
struct ath_beacon_config conf;
ieee80211com_t *ic = (ieee80211com_t *)sc;
struct ieee80211_node *in = ic->ic_bss;
uint32_t nexttbtt, intval;
(void) memset(&conf, 0, sizeof (struct ath_beacon_config));
/* XXX fix me */
conf.beacon_interval = in->in_intval ?
in->in_intval : ATH_DEFAULT_BINTVAL;
conf.listen_interval = 1;
conf.dtim_period = conf.beacon_interval;
conf.dtim_count = 1;
conf.bmiss_timeout = ATH_DEFAULT_BMISS_LIMIT * conf.beacon_interval;
/* extract tstamp from last beacon and convert to TU */
// nexttbtt = TSF_TO_TU(sc->bc_tstamp >> 32, sc->bc_tstamp);
/* XXX fix me */
nexttbtt = (ARN_LE_READ_32(in->in_tstamp.data + 4) << 22) |
(ARN_LE_READ_32(in->in_tstamp.data) >> 10);
/* XXX conditionalize multi-bss support? */
if (sc->sc_ah->ah_opmode == ATH9K_M_HOSTAP) {
/*
* For multi-bss ap support beacons are either staggered
* evenly over N slots or burst together. For the former
* arrange for the SWBA to be delivered for each slot.
* Slots that are not occupied will generate nothing.
*/
/* NB: the beacon interval is kept internally in TU's */
intval = conf.beacon_interval & ATH9K_BEACON_PERIOD;
intval /= ATH_BCBUF; /* for staggered beacons */
} else {
intval = conf.beacon_interval & ATH9K_BEACON_PERIOD;
}
if (nexttbtt == 0) /* e.g. for ap mode */
nexttbtt = intval;
else if (intval) /* NB: can be 0 for monitor mode */
nexttbtt = roundup(nexttbtt, intval);
ARN_DBG((ARN_DBG_BEACON, "arn: arn_beacon_config(): "
"nexttbtt %u intval %u (%u)\n",
nexttbtt, intval, conf.beacon_interval));
/* Check for ATH9K_M_HOSTAP and sc_nostabeacons for WDS client */
if (sc->sc_ah->ah_opmode == ATH9K_M_STA) {
struct ath9k_beacon_state bs;
uint64_t tsf;
uint32_t tsftu;
int dtimperiod, dtimcount, sleepduration;
int cfpperiod, cfpcount;
/*
* Setup dtim and cfp parameters according to
* last beacon we received (which may be none).
*/
dtimperiod = conf.dtim_period;
if (dtimperiod <= 0) /* NB: 0 if not known */
dtimperiod = 1;
dtimcount = conf.dtim_count;
if (dtimcount >= dtimperiod) /* NB: sanity check */
dtimcount = 0;
cfpperiod = 1; /* NB: no PCF support yet */
cfpcount = 0;
sleepduration = conf.listen_interval * intval;
if (sleepduration <= 0)
sleepduration = intval;
#define FUDGE 2
/*
* Pull nexttbtt forward to reflect the current
* TSF and calculate dtim+cfp state for the result.
*/
tsf = ath9k_hw_gettsf64(ah);
tsftu = TSF_TO_TU(tsf>>32, tsf) + FUDGE;
do {
nexttbtt += intval;
if (--dtimcount < 0) {
dtimcount = dtimperiod - 1;
if (--cfpcount < 0)
cfpcount = cfpperiod - 1;
}
} while (nexttbtt < tsftu);
#undef FUDGE
(void) memset(&bs, 0, sizeof (bs));
bs.bs_intval = intval;
bs.bs_nexttbtt = nexttbtt;
bs.bs_dtimperiod = dtimperiod*intval;
bs.bs_nextdtim = bs.bs_nexttbtt + dtimcount*intval;
bs.bs_cfpperiod = cfpperiod*bs.bs_dtimperiod;
bs.bs_cfpnext = bs.bs_nextdtim + cfpcount*bs.bs_dtimperiod;
bs.bs_cfpmaxduration = 0;
/*
* Calculate the number of consecutive beacons to miss
* before taking a BMISS interrupt. The configuration
* is specified in TU so we only need calculate based
* on the beacon interval. Note that we clamp the
* result to at most 15 beacons.
*/
if (sleepduration > intval) {
bs.bs_bmissthreshold = conf.listen_interval *
ATH_DEFAULT_BMISS_LIMIT / 2;
} else {
bs.bs_bmissthreshold =
DIV_ROUND_UP(conf.bmiss_timeout, intval);
if (bs.bs_bmissthreshold > 15)
bs.bs_bmissthreshold = 15;
/* LINTED E_SUSPICIOUS_COMPARISON */
else if (bs.bs_bmissthreshold <= 0)
bs.bs_bmissthreshold = 1;
}
/*
* Calculate sleep duration. The configuration is
* given in ms. We insure a multiple of the beacon
* period is used. Also, if the sleep duration is
* greater than the DTIM period then it makes senses
* to make it a multiple of that.
*
* XXX fixed at 100ms
*/
bs.bs_sleepduration =
roundup(IEEE80211_MS_TO_TU(100), sleepduration);
if (bs.bs_sleepduration > bs.bs_dtimperiod)
bs.bs_sleepduration = bs.bs_dtimperiod;
ARN_DBG((ARN_DBG_BEACON, "arn: arn_beacon_config(): "
"tsf %llu "
"tsf:tu %u "
"intval %u "
"nexttbtt %u "
"dtim %u "
"nextdtim %u "
"bmiss %u "
"sleep %u "
"cfp:period %u "
"maxdur %u "
"next %u "
"timoffset %u\n",
(unsigned long long)tsf, tsftu,
bs.bs_intval,
bs.bs_nexttbtt,
bs.bs_dtimperiod,
bs.bs_nextdtim,
bs.bs_bmissthreshold,
bs.bs_sleepduration,
bs.bs_cfpperiod,
bs.bs_cfpmaxduration,
bs.bs_cfpnext,
bs.bs_timoffset));
(void) ath9k_hw_set_interrupts(ah, 0);
ath9k_hw_set_sta_beacon_timers(ah, &bs);
sc->sc_imask |= ATH9K_INT_BMISS;
(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);
} else {
uint64_t tsf;
uint32_t tsftu;
(void) ath9k_hw_set_interrupts(ah, 0);
if (nexttbtt == intval)
intval |= ATH9K_BEACON_RESET_TSF;
if (sc->sc_ah->ah_opmode == ATH9K_M_IBSS) {
/*
* Pull nexttbtt forward to reflect the current
* TSF
*/
#define FUDGE 2
if (!(intval & ATH9K_BEACON_RESET_TSF)) {
tsf = ath9k_hw_gettsf64(ah);
tsftu = TSF_TO_TU((uint32_t)(tsf>>32),
(uint32_t)tsf) + FUDGE;
do {
nexttbtt += intval;
} while (nexttbtt < tsftu);
}
#undef FUDGE
ARN_DBG((ARN_DBG_BEACON, "arn: arn_beacon_config(): "
"IBSS nexttbtt %u intval %u (%u)\n",
nexttbtt, intval & ~ATH9K_BEACON_RESET_TSF,
conf.beacon_interval));
/*
* In IBSS mode enable the beacon timers but only
* enable SWBA interrupts if we need to manually
* prepare beacon frames. Otherwise we use a
* self-linked tx descriptor and let the hardware
* deal with things.
*/
intval |= ATH9K_BEACON_ENA;
if (!(ah->ah_caps.hw_caps & ATH9K_HW_CAP_VEOL))
sc->sc_imask |= ATH9K_INT_SWBA;
(void) arn_beaconq_config(sc);
} else if (sc->sc_ah->ah_opmode == ATH9K_M_HOSTAP) {
/*
* In AP mode we enable the beacon timers and
* SWBA interrupts to prepare beacon frames.
*/
intval |= ATH9K_BEACON_ENA;
sc->sc_imask |= ATH9K_INT_SWBA; /* beacon prepare */
(void) arn_beaconq_config(sc);
}
ath9k_hw_beaconinit(ah, nexttbtt, intval);
sc->sc_bmisscount = 0;
(void) ath9k_hw_set_interrupts(ah, sc->sc_imask);
/*
* When using a self-linked beacon descriptor in
* ibss mode load it once here.
*/
if (sc->sc_ah->ah_opmode == ATH9K_M_IBSS &&
(ah->ah_caps.hw_caps & ATH9K_HW_CAP_VEOL))
arn_beacon_start_adhoc(sc);
}
sc->sc_bsync = 0;
}
void
ath_beacon_sync(struct arn_softc *sc)
{
/*
* Resync beacon timers using the tsf of the
* beacon frame we just received.
*/
arn_beacon_config(sc);
sc->sc_flags |= SC_OP_BEACONS;
}
void
arn_bmiss_proc(void *arg)
{
struct arn_softc *sc = (struct arn_softc *)arg;
ieee80211com_t *ic = (ieee80211com_t *)sc;
uint64_t tsf, lastrx;
uint_t bmisstimeout;
if (ic->ic_opmode != IEEE80211_M_STA ||
ic->ic_state != IEEE80211_S_RUN) {
return;
}
ARN_LOCK(sc);
lastrx = sc->sc_lastrx;
tsf = ath9k_hw_gettsf64(sc->sc_ah);
bmisstimeout = ic->ic_bmissthreshold * ic->ic_bss->in_intval * 1024;
ARN_DBG((ARN_DBG_BEACON, "arn_bmiss_proc():"
" tsf %llu, lastrx %llu (%lld), bmiss %u\n",
(unsigned long long)tsf, (unsigned long long)sc->sc_lastrx,
(long long)(tsf - lastrx), bmisstimeout));
ARN_UNLOCK(sc);
/* temp workaround */
if (tsf - lastrx > bmisstimeout)
ieee80211_beacon_miss(ic);
}