view usr/src/uts/common/io/arn/arn_beacon.c @ 9999:d5e89571de4e

6814300 Need support for Atheros AR9280 and AR9281 6834736 Need support for new wireless chipset AR9285
author lin wang - Sun Microsystems - Beijing China <Wang.Lin@Sun.COM>
date Tue, 30 Jun 2009 15:05:56 +0800
parents
children e0a7b374d710
line wrap: on
line source

/*
 * 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);
}