/*
* Copyright 2010 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"
/*
* Setup and link descriptors.
*
* 11N: we can no longer afford to self link the last descriptor.
* MAC acknowledges BA status as long as it copies frames to host
* buffer (or rx fifo). This can incorrectly acknowledge packets
* to a sender if last desc is self-linked.
*/
void
arn_rx_buf_link(struct arn_softc *sc, struct ath_buf *bf)
{
struct ath_desc *ds;
ds = bf->bf_desc;
ds->ds_link = 0;
ds->ds_data = bf->bf_dma.cookie.dmac_address;
/* virtual addr of the beginning of the buffer. */
ds->ds_vdata = bf->bf_dma.mem_va;
/*
* setup rx descriptors. The bf_dma.alength here tells the H/W
* how much data it can DMA to us and that we are prepared
* to process
*/
(void) ath9k_hw_setuprxdesc(sc->sc_ah, ds,
bf->bf_dma.alength, /* buffer size */
0);
if (sc->sc_rxlink == NULL)
ath9k_hw_putrxbuf(sc->sc_ah, bf->bf_daddr);
else
*sc->sc_rxlink = bf->bf_daddr;
sc->sc_rxlink = &ds->ds_link;
ath9k_hw_rxena(sc->sc_ah);
}
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 int
arn_rx_prepare(struct ath_desc *ds, struct arn_softc *sc)
{
uint8_t phyerr;
if (ds->ds_rxstat.rs_more) {
/*
* Frame spans multiple descriptors; this cannot happen yet
* as we don't support jumbograms. If not in monitor mode,
* discard the frame. Enable this if you want to see
* error frames in Monitor mode.
*/
if (sc->sc_ah->ah_opmode != ATH9K_M_MONITOR)
goto rx_next;
} else if (ds->ds_rxstat.rs_status != 0) {
if (ds->ds_rxstat.rs_status & ATH9K_RXERR_CRC) {
sc->sc_stats.ast_rx_crcerr++;
goto rx_next; /* should ignore? */
}
if (ds->ds_rxstat.rs_status & ATH9K_RXERR_FIFO) {
sc->sc_stats.ast_rx_fifoerr++;
}
if (ds->ds_rxstat.rs_status & ATH9K_RXERR_PHY) {
sc->sc_stats.ast_rx_phyerr++;
phyerr = ds->ds_rxstat.rs_phyerr & 0x1f;
sc->sc_stats.ast_rx_phy[phyerr]++;
goto rx_next;
}
if (ds->ds_rxstat.rs_status & ATH9K_RXERR_DECRYPT) {
sc->sc_stats.ast_rx_badcrypt++;
}
/*
* Reject error frames with the exception of
* decryption and MIC failures. For monitor mode,
* we also ignore the CRC error.
*/
if (sc->sc_ah->ah_opmode == ATH9K_M_MONITOR) {
if (ds->ds_rxstat.rs_status &
~(ATH9K_RXERR_DECRYPT |
ATH9K_RXERR_MIC |
ATH9K_RXERR_CRC))
goto rx_next;
} else {
if (ds->ds_rxstat.rs_status &
~(ATH9K_RXERR_DECRYPT | ATH9K_RXERR_MIC)) {
goto rx_next;
}
}
}
return (1);
rx_next:
return (0);
}
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
}
/*
* When block ACK agreement has been set up between station and AP,
* Net80211 module will call this function to inform hardware about
* informations of this BA agreement.
* When AP wants to delete BA agreement that was originated by it,
* Net80211 modele will call this function to clean up relevant
* information in hardware.
*/
void
arn_ampdu_recv_action(struct ieee80211_node *in,
const uint8_t *frm,
const uint8_t *efrm)
{
struct ieee80211com *ic;
struct arn_softc *sc;
if ((in == NULL) || (frm == NULL) || (ic = in->in_ic) == NULL) {
ARN_DBG((ARN_DBG_FATAL,
"Unknown AMPDU action or NULL node index\n"));
return;
}
sc = (struct arn_softc *)ic;
if (!(sc->sc_flags & SC_OP_RXAGGR))
return;
else
sc->sc_recv_action(in, frm, efrm);
}
int
arn_startrecv(struct arn_softc *sc)
{
struct ath_hal *ah = sc->sc_ah;
struct ath_buf *bf;
/* rx descriptor link set up */
mutex_enter(&sc->sc_rxbuflock);
if (list_empty(&sc->sc_rxbuf_list))
goto start_recv;
/* clean up rx link firstly */
sc->sc_rxlink = NULL;
bf = list_head(&sc->sc_rxbuf_list);
while (bf != NULL) {
arn_rx_buf_link(sc, bf);
bf = list_next(&sc->sc_rxbuf_list, bf);
}
/* We could have deleted elements so the list may be empty now */
if (list_empty(&sc->sc_rxbuf_list))
goto start_recv;
bf = list_head(&sc->sc_rxbuf_list);
ath9k_hw_putrxbuf(ah, bf->bf_daddr);
ath9k_hw_rxena(ah);
start_recv:
mutex_exit(&sc->sc_rxbuflock);
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 = 0, loop = 1;
uint32_t subtype;
int status;
int last_rssi = ATH_RSSI_DUMMY_MARKER;
struct ath_node *an;
struct ieee80211_node *in;
uint32_t cur_signal;
#ifdef ARN_DBG_AMSDU
uint8_t qos;
#endif
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"));
sc->sc_rxlink = NULL;
mutex_exit(&sc->sc_rxbuflock);
break;
}
ASSERT(bf->bf_dma.cookie.dmac_address != NULL);
ds = bf->bf_desc;
/*
* 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) {
struct ath_buf *tbf;
struct ath_desc *tds;
if (list_is_last(&bf->bf_node, &sc->sc_rxbuf_list)) {
ARN_DBG((ARN_DBG_RECV, "arn: arn_rx_handler(): "
"List is in last! \n"));
sc->sc_rxlink = NULL;
break;
}
tbf = list_object(&sc->sc_rxbuf_list,
bf->bf_node.list_next);
/*
* On some hardware the descriptor status words could
* get corrupted, including the done bit. Because of
* this, check if the next descriptor's done bit is
* set or not.
*
* If the next descriptor's done bit is set, the current
* descriptor has been corrupted. Force s/w to discard
* this descriptor and continue...
*/
tds = tbf->bf_desc;
status = ath9k_hw_rxprocdesc(ah, tds, tbf->bf_daddr,
PA2DESC(sc, tds->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;
len = rs->rs_datalen;
/* less than sizeof(struct ieee80211_frame) */
if (len < 20) {
sc->sc_stats.ast_rx_tooshort++;
goto requeue;
}
/* The status portion of the descriptor could get corrupted. */
if (sc->rx_dmabuf_size < rs->rs_datalen) {
arn_problem("Requeued because of wrong rs_datalen\n");
goto requeue;
}
if (!arn_rx_prepare(ds, sc))
goto requeue;
if ((rx_mp = allocb(sc->rx_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 requeue;
}
/* Remove the CRC at the end of IEEE80211 frame */
rx_mp->b_wptr -= IEEE80211_CRC_LEN;
#ifdef DEBUG
arn_printrxbuf(bf, status == 0);
#endif
#ifdef ARN_DBG_AMSDU
if (IEEE80211_IS_DATA_QOS(wh)) {
if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) ==
IEEE80211_FC1_DIR_DSTODS)
qos = ((struct ieee80211_qosframe_addr4 *)
wh)->i_qos[0];
else
qos =
((struct ieee80211_qosframe *)wh)->i_qos[0];
if (qos & IEEE80211_QOS_AMSDU)
arn_dump_pkg((unsigned char *)bf->bf_dma.mem_va,
len, 1, 1);
}
#endif /* ARN_DBG_AMSDU */
/*
* 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);
an = ATH_NODE(in);
/*
* Theory for reporting quality:
*
* At a hardware RSSI of 45 you will be able to use
* MCS 7 reliably.
* At a hardware RSSI of 45 you will be able to use
* MCS 15 reliably.
* At a hardware RSSI of 35 you should be able use
* 54 Mbps reliably.
*
* MCS 7 is the highets MCS index usable by a 1-stream device.
* MCS 15 is the highest MCS index usable by a 2-stream device.
*
* All ath9k devices are either 1-stream or 2-stream.
*
* How many bars you see is derived from the qual reporting.
*
* A more elaborate scheme can be used here but it requires
* tables of SNR/throughput for each possible mode used. For
* the MCS table you can refer to the wireless wiki:
*
* http://wireless.kernel.org/en/developers/Documentation/
* ieee80211/802.11n
*/
if (ds->ds_rxstat.rs_rssi != ATH9K_RSSI_BAD &&
!ds->ds_rxstat.rs_moreaggr) {
/* LINTED: E_CONSTANT_CONDITION */
ATH_RSSI_LPF(an->last_rssi, ds->ds_rxstat.rs_rssi);
}
last_rssi = an->last_rssi;
if (last_rssi != ATH_RSSI_DUMMY_MARKER)
ds->ds_rxstat.rs_rssi = ATH_EP_RND(last_rssi,
ATH_RSSI_EP_MULTIPLIER);
if (ds->ds_rxstat.rs_rssi < 0)
ds->ds_rxstat.rs_rssi = 0;
if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) ==
IEEE80211_FC0_TYPE_MGT) {
subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
if (subtype == IEEE80211_FC0_SUBTYPE_BEACON)
sc->sc_halstats.ns_avgbrssi =
ds->ds_rxstat.rs_rssi;
}
/*
* signal (13-15) DLADM_WLAN_STRENGTH_EXCELLENT
* signal (10-12) DLADM_WLAN_STRENGTH_VERY_GOOD
* signal (6-9) DLADM_WLAN_STRENGTH_GOOD
* signal (3-5) DLADM_WLAN_STRENGTH_WEAK
* signal (0-2) DLADM_WLAN_STRENGTH_VERY_WEAK
*/
if (rs->rs_rssi == 0)
cur_signal = 0;
else if (rs->rs_rssi >= 45)
cur_signal = MAX_RSSI;
else
cur_signal = rs->rs_rssi * MAX_RSSI / 45 + 1;
/*
* Send the frame to net80211 for processing
*/
if (cur_signal <= 2 && ic->ic_state == IEEE80211_S_RUN) {
(void) ieee80211_input(ic, rx_mp, in,
(rs->rs_rssi + 10), rs->rs_tstamp);
}
else
(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;
}
requeue:
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);
}