view usr/src/uts/common/io/arn/arn_recv.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/byteorder.h>

#include "arn_core.h"

void
arn_setdefantenna(struct arn_softc *sc, uint32_t antenna)
{
	/* XXX block beacon interrupts */
	ath9k_hw_setantenna(sc->sc_ah, antenna);
	sc->sc_defant = (uint8_t)antenna; /* LINT */
	sc->sc_rxotherant = 0;
}

/*
 *  Extend 15-bit time stamp from rx descriptor to
 *  a full 64-bit TSF using the current h/w TSF.
 */

static uint64_t
arn_extend_tsf(struct arn_softc *sc, uint32_t rstamp)
{
	uint64_t tsf;

	tsf = ath9k_hw_gettsf64(sc->sc_ah);
	if ((tsf & 0x7fff) < rstamp)
		tsf -= 0x8000;
	return ((tsf & ~0x7fff) | rstamp);
}

static void
arn_opmode_init(struct arn_softc *sc)
{
	struct ath_hal *ah = sc->sc_ah;
	uint32_t rfilt;
	uint32_t mfilt[2];
	ieee80211com_t *ic = (ieee80211com_t *)sc;

	/* configure rx filter */
	rfilt = arn_calcrxfilter(sc);
	ath9k_hw_setrxfilter(ah, rfilt);

	/* configure bssid mask */
	if (ah->ah_caps.hw_caps & ATH9K_HW_CAP_BSSIDMASK)
		(void) ath9k_hw_setbssidmask(ah, sc->sc_bssidmask);

	/* configure operational mode */
	ath9k_hw_setopmode(ah);

	/* Handle any link-level address change. */
	(void) ath9k_hw_setmac(ah, sc->sc_myaddr);

	/* calculate and install multicast filter */
	mfilt[0] = ~((uint32_t)0); /* LINT */
	mfilt[1] = ~((uint32_t)0); /* LINT */

	ath9k_hw_setmcastfilter(ah, mfilt[0], mfilt[1]);

	ARN_DBG((ARN_DBG_RECV, "arn: arn_opmode_init(): "
	    "mode = %d RX filter 0x%x, MC filter %08x:%08x\n",
	    ic->ic_opmode, rfilt, mfilt[0], mfilt[1]));
}

/*
 * Calculate the receive filter according to the
 * operating mode and state:
 *
 * o always accept unicast, broadcast, and multicast traffic
 * o maintain current state of phy error reception (the hal
 *   may enable phy error frames for noise immunity work)
 * o probe request frames are accepted only when operating in
 *   hostap, adhoc, or monitor modes
 * o enable promiscuous mode according to the interface state
 * o accept beacons:
 * - when operating in adhoc mode so the 802.11 layer creates
 * node table entries for peers,
 * - when operating in station mode for collecting rssi data when
 * the station is otherwise quiet, or
 * - when operating as a repeater so we see repeater-sta beacons
 * - when scanning
 */

uint32_t
arn_calcrxfilter(struct arn_softc *sc)
{
#define	RX_FILTER_PRESERVE	(ATH9K_RX_FILTER_PHYERR |	\
	ATH9K_RX_FILTER_PHYRADAR)

	uint32_t rfilt;

	rfilt = (ath9k_hw_getrxfilter(sc->sc_ah) & RX_FILTER_PRESERVE) |
	    ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST |
	    ATH9K_RX_FILTER_MCAST;

	/* If not a STA, enable processing of Probe Requests */
	if (sc->sc_ah->ah_opmode != ATH9K_M_STA)
		rfilt |= ATH9K_RX_FILTER_PROBEREQ;

	/* Can't set HOSTAP into promiscous mode */
	if (((sc->sc_ah->ah_opmode != ATH9K_M_HOSTAP) &&
	    (sc->sc_promisc)) ||
	    (sc->sc_ah->ah_opmode == ATH9K_M_MONITOR)) {
		rfilt |= ATH9K_RX_FILTER_PROM;
		/* ??? To prevent from sending ACK */
		rfilt &= ~ATH9K_RX_FILTER_UCAST;
	}

	if (sc->sc_ah->ah_opmode == ATH9K_M_STA ||
	    sc->sc_ah->ah_opmode == ATH9K_M_IBSS)
		rfilt |= ATH9K_RX_FILTER_BEACON;

	/*
	 * If in HOSTAP mode, want to enable reception of PSPOLL
	 * frames & beacon frames
	 */
	if (sc->sc_ah->ah_opmode == ATH9K_M_HOSTAP)
		rfilt |= (ATH9K_RX_FILTER_BEACON | ATH9K_RX_FILTER_PSPOLL);

	return (rfilt);

#undef RX_FILTER_PRESERVE
}

int
arn_startrecv(struct arn_softc *sc)
{
	struct ath_hal *ah = sc->sc_ah;
	struct ath_buf *bf;

	/* clean up rx link firstly */
	sc->sc_rxlink = NULL;

	/* rx descriptor link set up */
	bf = list_head(&sc->sc_rxbuf_list);
	while (bf != NULL) {
		arn_rx_buf_link(sc, bf);
		bf = list_next(&sc->sc_rxbuf_list, bf);
	}

	bf = list_head(&sc->sc_rxbuf_list);

	ath9k_hw_putrxbuf(ah, bf->bf_daddr);
	ath9k_hw_rxena(ah);

	arn_opmode_init(sc);
	ath9k_hw_startpcureceive(ah);

	return (0);
}

boolean_t
arn_stoprecv(struct arn_softc *sc)
{
	struct ath_hal *ah = sc->sc_ah;
	boolean_t stopped;

	ath9k_hw_stoppcurecv(ah);
	ath9k_hw_setrxfilter(ah, 0);
	stopped = ath9k_hw_stopdmarecv(ah);

	/* 3ms is long enough for 1 frame ??? */
	drv_usecwait(3000);

	sc->sc_rxlink = NULL;

	return (stopped);
}

/*
 * Intercept management frames to collect beacon rssi data
 * and to do ibss merges.
 */

void
arn_recv_mgmt(struct ieee80211com *ic, mblk_t *mp, struct ieee80211_node *in,
    int subtype, int rssi, uint32_t rstamp)
{
	struct arn_softc *sc = (struct arn_softc *)ic;

	/*
	 * Call up first so subsequent work can use information
	 * potentially stored in the node (e.g. for ibss merge).
	 */
	sc->sc_recv_mgmt(ic, mp, in, subtype, rssi, rstamp);

	ARN_LOCK(sc);
	switch (subtype) {
	case IEEE80211_FC0_SUBTYPE_BEACON:
		/* update rssi statistics */
		if (sc->sc_bsync && in == ic->ic_bss &&
		    ic->ic_state == IEEE80211_S_RUN) {
			/*
			 * Resync beacon timers using the tsf of the beacon
			 * frame we just received.
			 */
			arn_beacon_config(sc);
		}
		/* FALLTHRU */
	case IEEE80211_FC0_SUBTYPE_PROBE_RESP:
		if (ic->ic_opmode == IEEE80211_M_IBSS &&
		    ic->ic_state == IEEE80211_S_RUN &&
		    (in->in_capinfo & IEEE80211_CAPINFO_IBSS)) {
			uint64_t tsf = arn_extend_tsf(sc, rstamp);
			/*
			 * Handle ibss merge as needed; check the tsf on the
			 * frame before attempting the merge.  The 802.11 spec
			 * says the station should change it's bssid to match
			 * the oldest station with the same ssid, where oldest
			 * is determined by the tsf.  Note that hardware
			 * reconfiguration happens through callback to
			 * ath_newstate as the state machine will go from
			 * RUN -> RUN when this happens.
			 */
			if (LE_64(in->in_tstamp.tsf) >= tsf) {
				ARN_DBG((ARN_DBG_BEACON, "arn: arn_recv_mgmt:"
				    "ibss merge, rstamp %u tsf %lu "
				    "tstamp %lu\n", rstamp, tsf,
				    in->in_tstamp.tsf));
				ARN_UNLOCK(sc);
				ARN_DBG((ARN_DBG_BEACON, "arn_recv_mgmt():"
				    "ibss_merge: rstamp=%d in_tstamp=%02x %02x"
				    " %02x %02x %02x %02x %02x %02x\n",
				    rstamp, in->in_tstamp.data[0],
				    in->in_tstamp.data[1],
				    in->in_tstamp.data[2],
				    in->in_tstamp.data[3],
				    in->in_tstamp.data[4],
				    in->in_tstamp.data[5],
				    in->in_tstamp.data[6],
				    in->in_tstamp.data[7]));
				(void) ieee80211_ibss_merge(in);
				return;
			}
		}
		break;
	}
	ARN_UNLOCK(sc);
}

static void
arn_printrxbuf(struct ath_buf *bf, int32_t done)
{
	struct ath_desc *ds = bf->bf_desc;
	const struct ath_rx_status *rs = &ds->ds_rxstat;

	ARN_DBG((ARN_DBG_RECV, "arn: R (%p %p) %08x %08x %08x "
	    "%08x %08x %08x %c\n",
	    ds, bf->bf_daddr,
	    ds->ds_link, ds->ds_data,
	    ds->ds_ctl0, ds->ds_ctl1,
	    ds->ds_hw[0], ds->ds_hw[1],
	    !done ? ' ' : (rs->rs_status == 0) ? '*' : '!'));
}

static void
arn_rx_handler(struct arn_softc *sc)
{
#define	PA2DESC(_sc, _pa) \
		((struct ath_desc *)((caddr_t)(_sc)->sc_desc + \
		((_pa) - (_sc)->sc_desc_dma.cookie.dmac_address)))

	ieee80211com_t *ic = (ieee80211com_t *)sc;
	struct ath_buf *bf;
	struct ath_hal *ah = sc->sc_ah;
	struct ath_desc *ds;
	struct ath_rx_status *rs;
	mblk_t *rx_mp;
	struct ieee80211_frame *wh;
	int32_t len, ngood, loop = 1;
	uint8_t phyerr;
	int status;
	struct ieee80211_node *in;

	ngood = 0;
	do {
		mutex_enter(&sc->sc_rxbuflock);
		bf = list_head(&sc->sc_rxbuf_list);
		if (bf == NULL) {
			ARN_DBG((ARN_DBG_RECV, "arn: arn_rx_handler(): "
			    "no buffer\n"));
			mutex_exit(&sc->sc_rxbuflock);
			break;
		}
		ASSERT(bf->bf_dma.cookie.dmac_address != NULL);
		ds = bf->bf_desc;
		if (ds->ds_link == bf->bf_daddr) {
			/*
			 * Never process the self-linked entry at the end,
			 * this may be met at heavy load.
			 */
			mutex_exit(&sc->sc_rxbuflock);
			break;
		}

		/*
		 * Must provide the virtual address of the current
		 * descriptor, the physical address, and the virtual
		 * address of the next descriptor in the h/w chain.
		 * This allows the HAL to look ahead to see if the
		 * hardware is done with a descriptor by checking the
		 * done bit in the following descriptor and the address
		 * of the current descriptor the DMA engine is working
		 * on.  All this is necessary because of our use of
		 * a self-linked list to avoid rx overruns.
		 */
		status = ath9k_hw_rxprocdesc(ah, ds,
		    bf->bf_daddr,
		    PA2DESC(sc, ds->ds_link), 0);
		if (status == EINPROGRESS) {
			mutex_exit(&sc->sc_rxbuflock);
			break;
		}
		list_remove(&sc->sc_rxbuf_list, bf);
		mutex_exit(&sc->sc_rxbuflock);

		rs = &ds->ds_rxstat;
		if (rs->rs_status != 0) {
			if (rs->rs_status & ATH9K_RXERR_CRC) {
				sc->sc_stats.ast_rx_crcerr++;
			}
			if (rs->rs_status & ATH9K_RXERR_FIFO) {
				sc->sc_stats.ast_rx_fifoerr++;
			}
			if (rs->rs_status & ATH9K_RXERR_DECRYPT) {
				sc->sc_stats.ast_rx_badcrypt++;
			}
			if (rs->rs_status & ATH9K_RXERR_PHY) {
				sc->sc_stats.ast_rx_phyerr++;
				phyerr = rs->rs_phyerr & 0x1f;
				sc->sc_stats.ast_rx_phy[phyerr]++;
			}
			goto rx_next;
		}
		len = rs->rs_datalen;

		/* less than sizeof(struct ieee80211_frame) */
		if (len < 20) {
			sc->sc_stats.ast_rx_tooshort++;
			goto rx_next;
		}

		if ((rx_mp = allocb(sc->sc_dmabuf_size, BPRI_MED)) == NULL) {
			arn_problem("arn: arn_rx_handler(): "
			    "allocing mblk buffer failed.\n");
			return;
		}

		ARN_DMA_SYNC(bf->bf_dma, DDI_DMA_SYNC_FORCPU);
		bcopy(bf->bf_dma.mem_va, rx_mp->b_rptr, len);

		rx_mp->b_wptr += len;
		wh = (struct ieee80211_frame *)rx_mp->b_rptr;

		if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) ==
		    IEEE80211_FC0_TYPE_CTL) {
			/*
			 * Ignore control frame received in promisc mode.
			 */
			freemsg(rx_mp);
			goto rx_next;
		}
		/* Remove the CRC at the end of IEEE80211 frame */
		rx_mp->b_wptr -= IEEE80211_CRC_LEN;

#ifdef DEBUG
		arn_printrxbuf(bf, status == 0);
#endif

		/*
		 * Locate the node for sender, track state, and then
		 * pass the (referenced) node up to the 802.11 layer
		 * for its use.
		 */
		in = ieee80211_find_rxnode(ic, wh);

		/*
		 * Send the frame to net80211 for processing
		 */
		(void) ieee80211_input(ic, rx_mp, in,
		    rs->rs_rssi, rs->rs_tstamp);

		/* release node */
		ieee80211_free_node(in);

		/*
		 * Arrange to update the last rx timestamp only for
		 * frames from our ap when operating in station mode.
		 * This assumes the rx key is always setup when associated.
		 */
		if (ic->ic_opmode == IEEE80211_M_STA &&
		    rs->rs_keyix != ATH9K_RXKEYIX_INVALID) {
			ngood++;
		}

		/*
		 * change the default rx antenna if rx diversity chooses the
		 * other antenna 3 times in a row.
		 */
		if (sc->sc_defant != ds->ds_rxstat.rs_antenna) {
			if (++sc->sc_rxotherant >= 3) {
				ath9k_hw_setantenna(sc->sc_ah,
				    ds->ds_rxstat.rs_antenna);
				sc->sc_defant = ds->ds_rxstat.rs_antenna;
				sc->sc_rxotherant = 0;
			}
		} else {
			sc->sc_rxotherant = 0;
		}

rx_next:
		mutex_enter(&sc->sc_rxbuflock);
		list_insert_tail(&sc->sc_rxbuf_list, bf);
		mutex_exit(&sc->sc_rxbuflock);
		arn_rx_buf_link(sc, bf);
	} while (loop);

	if (ngood)
		sc->sc_lastrx = ath9k_hw_gettsf64(ah);

#undef PA2DESC
}

uint_t
arn_softint_handler(caddr_t data)
{
	struct arn_softc *sc = (struct arn_softc *)data;

	ARN_LOCK(sc);

	if (sc->sc_rx_pend) {
		/* Soft interrupt for this driver */
		sc->sc_rx_pend = 0;
		ARN_UNLOCK(sc);
		arn_rx_handler(sc);
		return (DDI_INTR_CLAIMED);
	}

	ARN_UNLOCK(sc);

	return (DDI_INTR_UNCLAIMED);
}