--- /dev/null +++ b/ath/ath_wprobe.c @@ -0,0 +1,433 @@ +#include +#include + +atomic_t cleanup_tasks = ATOMIC_INIT(0); + +enum wp_node_val { + WP_NODE_RSSI, + WP_NODE_SIGNAL, + WP_NODE_RX_RATE, + WP_NODE_TX_RATE, + WP_NODE_RETRANSMIT_200, + WP_NODE_RETRANSMIT_400, + WP_NODE_RETRANSMIT_800, + WP_NODE_RETRANSMIT_1600, +}; + +enum wp_global_val { + WP_GLOBAL_NOISE, + WP_GLOBAL_PHY_BUSY, + WP_GLOBAL_PHY_RX, + WP_GLOBAL_PHY_TX, + WP_GLOBAL_FRAMES, + WP_GLOBAL_PROBEREQ, +}; + +static struct wprobe_item ath_wprobe_globals[] = { + [WP_GLOBAL_NOISE] = { + .name = "noise", + .type = WPROBE_VAL_S16, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_GLOBAL_PHY_BUSY] = { + .name = "phy_busy", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_GLOBAL_PHY_RX] = { + .name = "phy_rx", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_GLOBAL_PHY_TX] = { + .name = "phy_tx", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_GLOBAL_FRAMES] = { + .name = "frames", + .type = WPROBE_VAL_U32, + }, + [WP_GLOBAL_PROBEREQ] = { + .name = "probereq", + .type = WPROBE_VAL_U32, + }, +}; + +static struct wprobe_item ath_wprobe_link[] = { + [WP_NODE_RSSI] = { + .name = "rssi", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_SIGNAL] = { + .name = "signal", + .type = WPROBE_VAL_S16, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_RX_RATE] = { + .name = "rx_rate", + .type = WPROBE_VAL_U16, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_TX_RATE] = { + .name = "tx_rate", + .type = WPROBE_VAL_U16, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_RETRANSMIT_200] = { + .name = "retransmit_200", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_RETRANSMIT_400] = { + .name = "retransmit_400", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_RETRANSMIT_800] = { + .name = "retransmit_800", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, + [WP_NODE_RETRANSMIT_1600] = { + .name = "retransmit_1600", + .type = WPROBE_VAL_U8, + .flags = WPROBE_F_KEEPSTAT + }, +}; + +#define AR5K_MIBC 0x0040 +#define AR5K_MIBC_FREEZE (1 << 1) +#define AR5K_TXFC 0x80ec +#define AR5K_RXFC 0x80f0 +#define AR5K_RXCLEAR 0x80f4 +#define AR5K_CYCLES 0x80f8 + +#define READ_CLR(_ah, _reg) \ + ({ u32 __val = OS_REG_READ(_ah, _reg); OS_REG_WRITE(_ah, _reg, 0); __val; }) + +static bool +wprobe_disabled(void) +{ + return (!wprobe_add_iface || IS_ERR(wprobe_add_iface)); +} + +static int +ath_wprobe_sync(struct wprobe_iface *dev, struct wprobe_link *l, struct wprobe_value *val, bool measure) +{ + struct ath_vap *avp = container_of(dev, struct ath_vap, av_wpif); + struct ieee80211vap *vap = &avp->av_vap; + struct ieee80211com *ic = vap->iv_ic; + struct ath_softc *sc = ic->ic_dev->priv; + struct ath_hal *ah = sc->sc_ah; + u32 cc, busy, rx, tx; + s16 noise; + + if (l) + goto out; + + OS_REG_WRITE(ah, AR5K_MIBC, AR5K_MIBC_FREEZE); + cc = READ_CLR(ah, AR5K_CYCLES); + busy = READ_CLR(ah, AR5K_RXCLEAR); + rx = READ_CLR(ah, AR5K_RXFC); + tx = READ_CLR(ah, AR5K_TXFC); + OS_REG_WRITE(ah, AR5K_MIBC, 0); + noise = ath_hal_get_channel_noise(sc->sc_ah, &(sc->sc_curchan)); + ic->ic_channoise = noise; + + WPROBE_FILL_BEGIN(val, ath_wprobe_globals); + if (cc & 0xf0000000) { + /* scale down if the counters are near max */ + cc >>= 8; + busy >>= 8; + rx >>= 8; + tx >>= 8; + } + if (ah->ah_macType < 5212) + goto phy_skip; + if (!cc) + goto phy_skip; + if (busy > cc) + goto phy_skip; + if (rx > cc) + goto phy_skip; + if (tx > cc) + goto phy_skip; + busy = (busy * 100) / cc; + rx = (rx * 100) / cc; + tx = (tx * 100) / cc; + WPROBE_SET(WP_GLOBAL_PHY_BUSY, U8, busy); + WPROBE_SET(WP_GLOBAL_PHY_RX, U8, rx); + WPROBE_SET(WP_GLOBAL_PHY_TX, U8, tx); + WPROBE_SET(WP_GLOBAL_FRAMES, U32, avp->av_rxframes); + WPROBE_SET(WP_GLOBAL_PROBEREQ, U32, avp->av_rxprobereq); + +phy_skip: + WPROBE_SET(WP_GLOBAL_NOISE, S16, noise); + WPROBE_FILL_END(); + +out: + return 0; +} + +#undef AR5K_TXFC +#undef AR5K_RXFC +#undef AR5K_RXCLEAR +#undef AR5K_CYCLES +#undef AR5K_MIBC +#undef AR5K_MIBC_FREEZE +#undef READ_CLR + +static const struct wprobe_iface ath_wprobe_dev = { + .link_items = ath_wprobe_link, + .n_link_items = ARRAY_SIZE(ath_wprobe_link), + .global_items = ath_wprobe_globals, + .n_global_items = ARRAY_SIZE(ath_wprobe_globals), + .sync_data = ath_wprobe_sync, +}; + +static int +ath_lookup_rateval(struct ieee80211_node *ni, int rate) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = vap->iv_ic; + struct ath_softc *sc = ic->ic_dev->priv; + const HAL_RATE_TABLE *rt = sc->sc_currates; + + if ((!rt) || (rate < 0) || (rate >= ARRAY_SIZE(sc->sc_hwmap))) + return -1; + + rate = sc->sc_hwmap[rate].ieeerate; + rate = sc->sc_rixmap[rate & IEEE80211_RATE_VAL]; + if ((rate < 0) || (rate >= rt->rateCount)) + return -1; + + return rt->info[rate].rateKbps; +} + +static void +ath_wprobe_report_rx(struct ieee80211vap *vap, struct ath_rx_status *rs, struct sk_buff *skb) +{ + const struct ieee80211_frame *wh = (struct ieee80211_frame *)skb->data; + struct wprobe_wlan_hdr hdr; + struct ath_vap *avp; + int hdrsize; + + if (wprobe_disabled()) + return; + + avp = ATH_VAP(vap); + avp->av_rxframes++; + if (wh->i_fc[0] == (IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_PROBE_REQ)) + avp->av_rxprobereq++; + + memset(&hdr, 0, sizeof(hdr)); + hdr.len = skb->len; + hdr.snr = rs->rs_rssi; + hdr.type = WPROBE_PKT_RX; + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) + hdrsize = sizeof(struct ieee80211_ctlframe_addr2); + else + hdrsize = ieee80211_hdrsize(skb->data); + wprobe_add_frame(&avp->av_wpif, &hdr, skb->data, hdrsize + 0x42); +} + + +static void +ath_node_sample_rx(struct ieee80211_node *ni, struct ath_rx_status *rs) +{ + struct ath_node *an = ATH_NODE(ni); + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = vap->iv_ic; + struct wprobe_link *l = &an->an_wplink; + struct wprobe_value *v = l->val; + unsigned long flags; + int rate; + + if (wprobe_disabled() || !an->an_wplink_active || !l->val) + return; + + rate = ath_lookup_rateval(ni, rs->rs_rate); + + spin_lock_irqsave(&l->iface->lock, flags); + WPROBE_FILL_BEGIN(v, ath_wprobe_link); + WPROBE_SET(WP_NODE_RSSI, U8, rs->rs_rssi); + WPROBE_SET(WP_NODE_SIGNAL, S16, ic->ic_channoise + rs->rs_rssi); + if ((rate > 0) && (rate <= 600000)) + WPROBE_SET(WP_NODE_RX_RATE, U16, rate); + WPROBE_FILL_END(); + wprobe_update_stats(l->iface, l); + spin_unlock_irqrestore(&l->iface->lock, flags); +} + +static void +ath_wprobe_report_tx(struct ieee80211vap *vap, struct ath_tx_status *ts, struct sk_buff *skb) +{ + const struct ieee80211_frame *wh = (struct ieee80211_frame *)skb->data; + struct wprobe_wlan_hdr hdr; + struct ath_vap *avp; + int hdrsize; + + if (wprobe_disabled()) + return; + + avp = ATH_VAP(vap); + + memset(&hdr, 0, sizeof(hdr)); + hdr.len = skb->len; + hdr.snr = ts->ts_rssi; + hdr.type = WPROBE_PKT_TX; + if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) + hdrsize = sizeof(struct ieee80211_ctlframe_addr2); + else + hdrsize = ieee80211_hdrsize(skb->data); + wprobe_add_frame(&avp->av_wpif, &hdr, skb->data, hdrsize + 0x42); +} + + + +static void +ath_node_sample_tx(struct ieee80211_node *ni, struct ath_tx_status *ts, struct sk_buff *skb) +{ + struct ath_node *an = ATH_NODE(ni); + struct ieee80211vap *vap = ni->ni_vap; + struct ieee80211com *ic = vap->iv_ic; + struct wprobe_link *l = &an->an_wplink; + struct wprobe_value *v = l->val; + unsigned long flags; + int rate, rexmit_counter; + int len = skb->len; + + if (wprobe_disabled() || !an->an_wplink_active || !l->val) + return; + + ath_wprobe_report_tx(vap, ts, skb); + rate = ath_lookup_rateval(ni, ts->ts_rate); + + spin_lock_irqsave(&l->iface->lock, flags); + WPROBE_FILL_BEGIN(v, ath_wprobe_link); + WPROBE_SET(WP_NODE_RSSI, U8, ts->ts_rssi); + WPROBE_SET(WP_NODE_SIGNAL, S16, ic->ic_channoise + ts->ts_rssi); + + if (len <= 200) + rexmit_counter = WP_NODE_RETRANSMIT_200; + else if (len <= 400) + rexmit_counter = WP_NODE_RETRANSMIT_400; + else if (len <= 800) + rexmit_counter = WP_NODE_RETRANSMIT_800; + else + rexmit_counter = WP_NODE_RETRANSMIT_1600; + WPROBE_SET(rexmit_counter, U8, ts->ts_longretry); + + if ((rate > 0) && (rate <= 600000)) + WPROBE_SET(WP_NODE_TX_RATE, U16, rate); + WPROBE_FILL_END(); + wprobe_update_stats(l->iface, l); + spin_unlock_irqrestore(&l->iface->lock, flags); +} + +static void +ath_wprobe_node_join(struct ieee80211vap *vap, struct ieee80211_node *ni) +{ + struct wprobe_iface *dev; + struct wprobe_link *l; + struct ath_vap *avp; + struct ath_node *an = ATH_NODE(ni); + + if (wprobe_disabled() || an->an_wplink_active) + return; + + avp = ATH_VAP(vap); + dev = &avp->av_wpif; + l = &an->an_wplink; + + ieee80211_ref_node(ni); + wprobe_add_link(dev, l, ni->ni_macaddr); + an->an_wplink_active = 1; +} + +static void +ath_wprobe_do_node_leave(struct work_struct *work) +{ + struct ath_node *an = container_of(work, struct ath_node, an_destroy); + struct ieee80211_node *ni = &an->an_node; + struct ieee80211vap *vap = ni->ni_vap; + struct wprobe_iface *dev; + struct wprobe_link *l; + struct ath_vap *avp; + + avp = ATH_VAP(vap); + dev = &avp->av_wpif; + l = &an->an_wplink; + + wprobe_remove_link(dev, l); + ieee80211_unref_node(&ni); + atomic_dec(&cleanup_tasks); +} + +static void +ath_wprobe_node_leave(struct ieee80211vap *vap, struct ieee80211_node *ni) +{ + struct ath_node *an = ATH_NODE(ni); + + if (wprobe_disabled() || !an->an_wplink_active) + return; + + atomic_inc(&cleanup_tasks); + an->an_wplink_active = 0; + IEEE80211_INIT_WORK(&an->an_destroy, ath_wprobe_do_node_leave); + schedule_work(&an->an_destroy); +} + +static void +ath_init_wprobe_dev(struct ath_vap *avp) +{ + struct ieee80211vap *vap = &avp->av_vap; + struct wprobe_iface *dev = &avp->av_wpif; + + if (wprobe_disabled() || (vap->iv_opmode == IEEE80211_M_WDS)) + return; + + memcpy(dev, &ath_wprobe_dev, sizeof(struct wprobe_iface)); + dev->addr = vap->iv_myaddr; + dev->name = vap->iv_dev->name; + wprobe_add_iface(dev); +} + +static void +ath_remove_wprobe_dev(struct ath_vap *avp) +{ + struct ieee80211vap *vap = &avp->av_vap; + struct ieee80211com *ic = vap->iv_ic; + struct ieee80211_node *ni; + struct wprobe_iface *dev = &avp->av_wpif; + struct wprobe_link *l; + struct ath_node *an; + unsigned long flags; + + if (wprobe_disabled() || (vap->iv_opmode == IEEE80211_M_WDS)) + return; + +restart: + rcu_read_lock(); + list_for_each_entry_rcu(l, &dev->links, list) { + an = container_of(l, struct ath_node, an_wplink); + + if (!an->an_wplink_active) + continue; + + ni = &an->an_node; + ath_wprobe_node_leave(vap, ni); + rcu_read_unlock(); + goto restart; + } + rcu_read_unlock(); + + /* wait for the cleanup tasks to finish */ + while (atomic_read(&cleanup_tasks) != 0) { + schedule(); + } + + wprobe_remove_iface(dev); +} --- a/ath/if_ath.c +++ b/ath/if_ath.c @@ -400,6 +400,7 @@ static int countrycode = -1; static int maxvaps = -1; static int outdoor = -1; static int xchanmode = -1; +#include "ath_wprobe.c" static int beacon_cal = 1; static const struct ath_hw_detect generic_hw_info = { @@ -1525,6 +1526,7 @@ ath_vap_create(struct ieee80211com *ic, ath_hal_intrset(ah, sc->sc_imask); } + ath_init_wprobe_dev(avp); return vap; } @@ -1606,6 +1608,7 @@ ath_vap_delete(struct ieee80211vap *vap) decrease = 0; ieee80211_vap_detach(vap); + ath_remove_wprobe_dev(ATH_VAP(vap)); /* NB: memory is reclaimed through dev->destructor callback */ if (decrease) sc->sc_nvaps--; @@ -5940,6 +5943,7 @@ ath_node_cleanup(struct ieee80211_node * /* Clean up node-specific rate things - this currently appears to * always be a no-op */ sc->sc_rc->ops->node_cleanup(sc, ATH_NODE(ni)); + ath_wprobe_node_leave(ni->ni_vap, ni); ATH_NODE_UAPSD_LOCK_IRQ(an); #ifdef IEEE80211_DEBUG_REFCNT @@ -7010,6 +7014,8 @@ drop_micfail: goto lookup_slowpath; } ATH_RSSI_LPF(ATH_NODE(ni)->an_avgrssi, rs->rs_rssi); + ath_node_sample_rx(ni, rs); + ath_wprobe_report_rx(ni->ni_vap, rs, skb); type = ieee80211_input(ni->ni_vap, ni, skb, rs->rs_rssi, bf->bf_tsf); ieee80211_unref_node(&ni); } else { @@ -7024,15 +7030,21 @@ lookup_slowpath: else vap = ieee80211_find_rxvap(ic, wh->i_addr1); - if (vap) + if (vap) { + ath_wprobe_report_rx(vap, rs, skb); ni = ieee80211_find_rxnode(ic, vap, wh); - else + } else { + TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) { + ath_wprobe_report_rx(vap, rs, skb); + } ni = NULL; + } if (ni != NULL) { ieee80211_keyix_t keyix; ATH_RSSI_LPF(ATH_NODE(ni)->an_avgrssi, rs->rs_rssi); + ath_node_sample_rx(ni, rs); type = ieee80211_input(vap, ni, skb, rs->rs_rssi, bf->bf_tsf); /* * If the station has a key cache slot assigned @@ -8612,6 +8624,7 @@ ath_tx_processq(struct ath_softc *sc, st sc->sc_stats.ast_tx_rssi = ts->ts_rssi; ATH_RSSI_LPF(an->an_halstats.ns_avgtxrssi, ts->ts_rssi); + ath_node_sample_tx(&an->an_node, ts, bf->bf_skb); if (bf->bf_skb->priority == WME_AC_VO || bf->bf_skb->priority == WME_AC_VI) ni->ni_ic->ic_wme.wme_hipri_traffic++; @@ -10111,6 +10124,7 @@ ath_newassoc(struct ieee80211_node *ni, struct ath_softc *sc = ic->ic_dev->priv; sc->sc_rc->ops->newassoc(sc, ATH_NODE(ni), isnew); + ath_wprobe_node_join(ni->ni_vap, ni); /* are we supporting compression? */ if (!(vap->iv_ath_cap & ni->ni_ath_flags & IEEE80211_NODE_COMP)) --- a/ath/if_athvar.h +++ b/ath/if_athvar.h @@ -46,6 +46,7 @@ #include "ah_desc.h" #include "ah_os.h" #include "if_athioctl.h" +#include #include "net80211/ieee80211.h" /* XXX for WME_NUM_AC */ #include #include @@ -352,6 +353,9 @@ typedef STAILQ_HEAD(, ath_buf) ath_bufhe /* driver-specific node state */ struct ath_node { struct ieee80211_node an_node; /* base class */ + struct wprobe_link an_wplink; + uint8_t an_wplink_active; + struct work_struct an_destroy; u_int16_t an_decomp_index; /* decompression mask index */ u_int32_t an_avgrssi; /* average rssi over all rx frames */ u_int8_t an_prevdatarix; /* rate ix of last data frame */ @@ -521,6 +525,9 @@ struct ath_vap { #else unsigned int av_beacon_alloc; #endif + struct wprobe_iface av_wpif; + u32 av_rxframes; + u32 av_rxprobereq; }; #define ATH_VAP(_v) ((struct ath_vap *)(_v))