summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/ath/ath9k/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/ath/ath9k/main.c')
-rw-r--r--drivers/net/wireless/ath/ath9k/main.c343
1 files changed, 225 insertions, 118 deletions
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index 4f18a6be0c7d..ebbbfc7a193b 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -54,7 +54,8 @@ u8 ath9k_parse_mpdudensity(u8 mpdudensity)
}
}
-static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq)
+static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq,
+ bool sw_pending)
{
bool pending = false;
@@ -65,6 +66,9 @@ static bool ath9k_has_pending_frames(struct ath_softc *sc, struct ath_txq *txq)
goto out;
}
+ if (!sw_pending)
+ goto out;
+
if (txq->mac80211_qnum >= 0) {
struct list_head *list;
@@ -229,8 +233,9 @@ static bool ath_complete_reset(struct ath_softc *sc, bool start)
ath9k_calculate_summary_state(sc, sc->cur_chan);
ath_startrecv(sc);
- ath9k_cmn_update_txpow(ah, sc->curtxpow,
- sc->cur_chan->txpower, &sc->curtxpow);
+ ath9k_cmn_update_txpow(ah, sc->cur_chan->cur_txpower,
+ sc->cur_chan->txpower,
+ &sc->cur_chan->cur_txpower);
clear_bit(ATH_OP_HW_RESET, &common->op_flags);
if (!sc->cur_chan->offchannel && start) {
@@ -270,7 +275,7 @@ static bool ath_complete_reset(struct ath_softc *sc, bool start)
return true;
}
-int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
+static int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
{
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah);
@@ -281,6 +286,7 @@ int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
__ath_cancel_work(sc);
tasklet_disable(&sc->intr_tq);
+ tasklet_disable(&sc->bcon_tasklet);
spin_lock_bh(&sc->sc_pcu_lock);
if (!sc->cur_chan->offchannel) {
@@ -326,6 +332,7 @@ int ath_reset_internal(struct ath_softc *sc, struct ath9k_channel *hchan)
out:
spin_unlock_bh(&sc->sc_pcu_lock);
+ tasklet_enable(&sc->bcon_tasklet);
tasklet_enable(&sc->intr_tq);
return r;
@@ -539,11 +546,10 @@ irqreturn_t ath_isr(int irq, void *dev)
sched = true;
/*
- * If a FATAL or RXORN interrupt is received, we have to reset the
- * chip immediately.
+ * If a FATAL interrupt is received, we have to reset the chip
+ * immediately.
*/
- if ((status & ATH9K_INT_FATAL) || ((status & ATH9K_INT_RXORN) &&
- !(ah->caps.hw_caps & ATH9K_HW_CAP_EDMA)))
+ if (status & ATH9K_INT_FATAL)
goto chip_reset;
if ((ah->config.hw_hang_checks & HW_BB_WATCHDOG) &&
@@ -598,17 +604,29 @@ chip_reset:
#undef SCHED_INTR
}
-int ath_reset(struct ath_softc *sc)
+/*
+ * This function is called when a HW reset cannot be deferred
+ * and has to be immediate.
+ */
+int ath_reset(struct ath_softc *sc, struct ath9k_channel *hchan)
{
+ struct ath_common *common = ath9k_hw_common(sc->sc_ah);
int r;
+ set_bit(ATH_OP_HW_RESET, &common->op_flags);
+
ath9k_ps_wakeup(sc);
- r = ath_reset_internal(sc, NULL);
+ r = ath_reset_internal(sc, hchan);
ath9k_ps_restore(sc);
return r;
}
+/*
+ * When a HW reset can be deferred, it is added to the
+ * hw_reset_work workqueue, but we set ATH_OP_HW_RESET before
+ * queueing.
+ */
void ath9k_queue_reset(struct ath_softc *sc, enum ath_reset_type type)
{
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
@@ -623,7 +641,9 @@ void ath_reset_work(struct work_struct *work)
{
struct ath_softc *sc = container_of(work, struct ath_softc, hw_reset_work);
- ath_reset(sc);
+ ath9k_ps_wakeup(sc);
+ ath_reset_internal(sc, NULL);
+ ath9k_ps_restore(sc);
}
/**********************/
@@ -707,7 +727,8 @@ static int ath9k_start(struct ieee80211_hw *hw)
if (ah->led_pin >= 0) {
ath9k_hw_cfg_output(ah, ah->led_pin,
AR_GPIO_OUTPUT_MUX_AS_OUTPUT);
- ath9k_hw_set_gpio(ah, ah->led_pin, 0);
+ ath9k_hw_set_gpio(ah, ah->led_pin,
+ (ah->config.led_active_high) ? 1 : 0);
}
/*
@@ -849,7 +870,8 @@ static void ath9k_stop(struct ieee80211_hw *hw)
spin_lock_bh(&sc->sc_pcu_lock);
if (ah->led_pin >= 0) {
- ath9k_hw_set_gpio(ah, ah->led_pin, 1);
+ ath9k_hw_set_gpio(ah, ah->led_pin,
+ (ah->config.led_active_high) ? 0 : 1);
ath9k_hw_cfg_gpio_input(ah, ah->led_pin);
}
@@ -1036,7 +1058,7 @@ static void ath9k_set_offchannel_state(struct ath_softc *sc)
eth_zero_addr(common->curbssid);
eth_broadcast_addr(common->bssidmask);
- ether_addr_copy(common->macaddr, vif->addr);
+ memcpy(common->macaddr, vif->addr, ETH_ALEN);
common->curaid = 0;
ah->opmode = vif->type;
ah->imask &= ~ATH9K_INT_SWBA;
@@ -1077,7 +1099,7 @@ void ath9k_calculate_summary_state(struct ath_softc *sc,
ath9k_calculate_iter_data(sc, ctx, &iter_data);
if (iter_data.has_hw_macaddr)
- ether_addr_copy(common->macaddr, iter_data.hw_macaddr);
+ memcpy(common->macaddr, iter_data.hw_macaddr, ETH_ALEN);
memcpy(common->bssidmask, iter_data.mask, ETH_ALEN);
ath_hw_setbssidmask(common);
@@ -1167,7 +1189,8 @@ static void ath9k_assign_hw_queues(struct ieee80211_hw *hw,
for (i = 0; i < IEEE80211_NUM_ACS; i++)
vif->hw_queue[i] = i;
- if (vif->type == NL80211_IFTYPE_AP)
+ if (vif->type == NL80211_IFTYPE_AP ||
+ vif->type == NL80211_IFTYPE_MESH_POINT)
vif->cab_queue = hw->queues - 2;
else
vif->cab_queue = IEEE80211_INVAL_HW_QUEUE;
@@ -1323,78 +1346,6 @@ static void ath9k_disable_ps(struct ath_softc *sc)
ath_dbg(common, PS, "PowerSave disabled\n");
}
-void ath9k_spectral_scan_trigger(struct ieee80211_hw *hw)
-{
- struct ath_softc *sc = hw->priv;
- struct ath_hw *ah = sc->sc_ah;
- struct ath_common *common = ath9k_hw_common(ah);
- u32 rxfilter;
-
- if (config_enabled(CONFIG_ATH9K_TX99))
- return;
-
- if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
- ath_err(common, "spectrum analyzer not implemented on this hardware\n");
- return;
- }
-
- ath9k_ps_wakeup(sc);
- rxfilter = ath9k_hw_getrxfilter(ah);
- ath9k_hw_setrxfilter(ah, rxfilter |
- ATH9K_RX_FILTER_PHYRADAR |
- ATH9K_RX_FILTER_PHYERR);
-
- /* TODO: usually this should not be neccesary, but for some reason
- * (or in some mode?) the trigger must be called after the
- * configuration, otherwise the register will have its values reset
- * (on my ar9220 to value 0x01002310)
- */
- ath9k_spectral_scan_config(hw, sc->spectral_mode);
- ath9k_hw_ops(ah)->spectral_scan_trigger(ah);
- ath9k_ps_restore(sc);
-}
-
-int ath9k_spectral_scan_config(struct ieee80211_hw *hw,
- enum spectral_mode spectral_mode)
-{
- struct ath_softc *sc = hw->priv;
- struct ath_hw *ah = sc->sc_ah;
- struct ath_common *common = ath9k_hw_common(ah);
-
- if (!ath9k_hw_ops(ah)->spectral_scan_trigger) {
- ath_err(common, "spectrum analyzer not implemented on this hardware\n");
- return -1;
- }
-
- switch (spectral_mode) {
- case SPECTRAL_DISABLED:
- sc->spec_config.enabled = 0;
- break;
- case SPECTRAL_BACKGROUND:
- /* send endless samples.
- * TODO: is this really useful for "background"?
- */
- sc->spec_config.endless = 1;
- sc->spec_config.enabled = 1;
- break;
- case SPECTRAL_CHANSCAN:
- case SPECTRAL_MANUAL:
- sc->spec_config.endless = 0;
- sc->spec_config.enabled = 1;
- break;
- default:
- return -1;
- }
-
- ath9k_ps_wakeup(sc);
- ath9k_hw_ops(ah)->spectral_scan_config(ah, &sc->spec_config);
- ath9k_ps_restore(sc);
-
- sc->spectral_mode = spectral_mode;
-
- return 0;
-}
-
static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
{
struct ath_softc *sc = hw->priv;
@@ -1455,8 +1406,9 @@ static int ath9k_config(struct ieee80211_hw *hw, u32 changed)
if (changed & IEEE80211_CONF_CHANGE_POWER) {
ath_dbg(common, CONFIG, "Set power: %d\n", conf->power_level);
sc->cur_chan->txpower = 2 * conf->power_level;
- ath9k_cmn_update_txpow(ah, sc->curtxpow,
- sc->cur_chan->txpower, &sc->curtxpow);
+ ath9k_cmn_update_txpow(ah, sc->cur_chan->cur_txpower,
+ sc->cur_chan->txpower,
+ &sc->cur_chan->cur_txpower);
}
mutex_unlock(&sc->mutex);
@@ -1553,6 +1505,40 @@ static int ath9k_sta_remove(struct ieee80211_hw *hw,
return 0;
}
+static int ath9k_sta_state(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ enum ieee80211_sta_state old_state,
+ enum ieee80211_sta_state new_state)
+{
+ struct ath_softc *sc = hw->priv;
+ struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+ int ret = 0;
+
+ if (old_state == IEEE80211_STA_AUTH &&
+ new_state == IEEE80211_STA_ASSOC) {
+ ret = ath9k_sta_add(hw, vif, sta);
+ ath_dbg(common, CONFIG,
+ "Add station: %pM\n", sta->addr);
+ } else if (old_state == IEEE80211_STA_ASSOC &&
+ new_state == IEEE80211_STA_AUTH) {
+ ret = ath9k_sta_remove(hw, vif, sta);
+ ath_dbg(common, CONFIG,
+ "Remove station: %pM\n", sta->addr);
+ }
+
+ if (ath9k_is_chanctx_enabled()) {
+ if (vif->type == NL80211_IFTYPE_STATION) {
+ if (old_state == IEEE80211_STA_ASSOC &&
+ new_state == IEEE80211_STA_AUTHORIZED)
+ ath_chanctx_event(sc, vif,
+ ATH_CHANCTX_EVENT_AUTHORIZED);
+ }
+ }
+
+ return ret;
+}
+
static void ath9k_sta_set_tx_filter(struct ath_hw *ah,
struct ath_node *an,
bool set)
@@ -1677,7 +1663,7 @@ static int ath9k_set_key(struct ieee80211_hw *hw,
key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
if (key->cipher == WLAN_CIPHER_SUITE_TKIP)
key->flags |= IEEE80211_KEY_FLAG_GENERATE_MMIC;
- if (sc->sc_ah->sw_mgmt_crypto &&
+ if (sc->sc_ah->sw_mgmt_crypto_tx &&
key->cipher == WLAN_CIPHER_SUITE_CCMP)
key->flags |= IEEE80211_KEY_FLAG_SW_MGMT_TX;
ret = 0;
@@ -1737,17 +1723,11 @@ static void ath9k_bss_info_changed(struct ieee80211_hw *hw,
ath_dbg(common, CONFIG, "BSSID %pM Changed ASSOC %d\n",
bss_conf->bssid, bss_conf->assoc);
- ether_addr_copy(avp->bssid, bss_conf->bssid);
+ memcpy(avp->bssid, bss_conf->bssid, ETH_ALEN);
avp->aid = bss_conf->aid;
avp->assoc = bss_conf->assoc;
ath9k_calculate_summary_state(sc, avp->chanctx);
-
- if (ath9k_is_chanctx_enabled()) {
- if (bss_conf->assoc)
- ath_chanctx_event(sc, vif,
- ATH_CHANCTX_EVENT_ASSOC);
- }
}
if (changed & BSS_CHANGED_IBSS) {
@@ -1843,6 +1823,7 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
u16 tid, u16 *ssn, u8 buf_size)
{
struct ath_softc *sc = hw->priv;
+ struct ath_common *common = ath9k_hw_common(sc->sc_ah);
bool flush = false;
int ret = 0;
@@ -1854,6 +1835,12 @@ static int ath9k_ampdu_action(struct ieee80211_hw *hw,
case IEEE80211_AMPDU_RX_STOP:
break;
case IEEE80211_AMPDU_TX_START:
+ if (ath9k_is_chanctx_enabled()) {
+ if (test_bit(ATH_OP_SCANNING, &common->op_flags)) {
+ ret = -EBUSY;
+ break;
+ }
+ }
ath9k_ps_wakeup(sc);
ret = ath_tx_aggr_start(sc, sta, tid, ssn);
if (!ret)
@@ -1967,7 +1954,8 @@ static void ath9k_set_coverage_class(struct ieee80211_hw *hw,
mutex_unlock(&sc->mutex);
}
-static bool ath9k_has_tx_pending(struct ath_softc *sc)
+static bool ath9k_has_tx_pending(struct ath_softc *sc,
+ bool sw_pending)
{
int i, npend = 0;
@@ -1975,7 +1963,8 @@ static bool ath9k_has_tx_pending(struct ath_softc *sc)
if (!ATH_TXQ_SETUP(sc, i))
continue;
- npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i]);
+ npend = ath9k_has_pending_frames(sc, &sc->tx.txq[i],
+ sw_pending);
if (npend)
break;
}
@@ -1987,18 +1976,38 @@ static void ath9k_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop)
{
struct ath_softc *sc = hw->priv;
+ struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+
+ if (ath9k_is_chanctx_enabled()) {
+ if (!test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags))
+ goto flush;
+ /*
+ * If MCC is active, extend the flush timeout
+ * and wait for the HW/SW queues to become
+ * empty. This needs to be done outside the
+ * sc->mutex lock to allow the channel scheduler
+ * to switch channel contexts.
+ *
+ * The vif queues have been stopped in mac80211,
+ * so there won't be any incoming frames.
+ */
+ __ath9k_flush(hw, queues, drop, true, true);
+ return;
+ }
+flush:
mutex_lock(&sc->mutex);
- __ath9k_flush(hw, queues, drop);
+ __ath9k_flush(hw, queues, drop, true, false);
mutex_unlock(&sc->mutex);
}
-void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
+void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop,
+ bool sw_pending, bool timeout_override)
{
struct ath_softc *sc = hw->priv;
struct ath_hw *ah = sc->sc_ah;
struct ath_common *common = ath9k_hw_common(ah);
- int timeout = HZ / 5; /* 200 ms */
+ int timeout;
bool drain_txq;
cancel_delayed_work_sync(&sc->tx_complete_work);
@@ -2013,7 +2022,17 @@ void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
return;
}
- if (wait_event_timeout(sc->tx_wait, !ath9k_has_tx_pending(sc),
+ spin_lock_bh(&sc->chan_lock);
+ if (timeout_override)
+ timeout = HZ / 5;
+ else
+ timeout = sc->cur_chan->flush_timeout;
+ spin_unlock_bh(&sc->chan_lock);
+
+ ath_dbg(common, CHAN_CTX,
+ "Flush timeout: %d\n", jiffies_to_msecs(timeout));
+
+ if (wait_event_timeout(sc->tx_wait, !ath9k_has_tx_pending(sc, sw_pending),
timeout) > 0)
drop = false;
@@ -2024,7 +2043,7 @@ void __ath9k_flush(struct ieee80211_hw *hw, u32 queues, bool drop)
spin_unlock_bh(&sc->sc_pcu_lock);
if (!drain_txq)
- ath_reset(sc);
+ ath_reset(sc, NULL);
ath9k_ps_restore(sc);
}
@@ -2036,7 +2055,7 @@ static bool ath9k_tx_frames_pending(struct ieee80211_hw *hw)
{
struct ath_softc *sc = hw->priv;
- return ath9k_has_tx_pending(sc);
+ return ath9k_has_tx_pending(sc, true);
}
static int ath9k_tx_last_beacon(struct ieee80211_hw *hw)
@@ -2167,14 +2186,17 @@ static int ath9k_get_antenna(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant)
return 0;
}
-static void ath9k_sw_scan_start(struct ieee80211_hw *hw)
+static void ath9k_sw_scan_start(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ const u8 *mac_addr)
{
struct ath_softc *sc = hw->priv;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
set_bit(ATH_OP_SCANNING, &common->op_flags);
}
-static void ath9k_sw_scan_complete(struct ieee80211_hw *hw)
+static void ath9k_sw_scan_complete(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif)
{
struct ath_softc *sc = hw->priv;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
@@ -2183,6 +2205,28 @@ static void ath9k_sw_scan_complete(struct ieee80211_hw *hw)
#ifdef CONFIG_ATH9K_CHANNEL_CONTEXT
+static void ath9k_cancel_pending_offchannel(struct ath_softc *sc)
+{
+ struct ath_common *common = ath9k_hw_common(sc->sc_ah);
+
+ if (sc->offchannel.roc_vif) {
+ ath_dbg(common, CHAN_CTX,
+ "%s: Aborting RoC\n", __func__);
+
+ del_timer_sync(&sc->offchannel.timer);
+ if (sc->offchannel.state >= ATH_OFFCHANNEL_ROC_START)
+ ath_roc_complete(sc, true);
+ }
+
+ if (test_bit(ATH_OP_SCANNING, &common->op_flags)) {
+ ath_dbg(common, CHAN_CTX,
+ "%s: Aborting HW scan\n", __func__);
+
+ del_timer_sync(&sc->offchannel.timer);
+ ath_scan_complete(sc, true);
+ }
+}
+
static int ath9k_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_scan_request *hw_req)
{
@@ -2313,7 +2357,6 @@ static int ath9k_add_chanctx(struct ieee80211_hw *hw,
conf->def.chan->center_freq);
ath_chanctx_set_channel(sc, ctx, &conf->def);
- ath_chanctx_event(sc, NULL, ATH_CHANCTX_EVENT_ASSIGN);
mutex_unlock(&sc->mutex);
return 0;
@@ -2370,6 +2413,8 @@ static int ath9k_assign_vif_chanctx(struct ieee80211_hw *hw,
struct ath_chanctx *ctx = ath_chanctx_get(conf);
int i;
+ ath9k_cancel_pending_offchannel(sc);
+
mutex_lock(&sc->mutex);
ath_dbg(common, CHAN_CTX,
@@ -2399,6 +2444,8 @@ static void ath9k_unassign_vif_chanctx(struct ieee80211_hw *hw,
struct ath_chanctx *ctx = ath_chanctx_get(conf);
int ac;
+ ath9k_cancel_pending_offchannel(sc);
+
mutex_lock(&sc->mutex);
ath_dbg(common, CHAN_CTX,
@@ -2422,7 +2469,11 @@ static void ath9k_mgd_prepare_tx(struct ieee80211_hw *hw,
struct ath_softc *sc = hw->priv;
struct ath_common *common = ath9k_hw_common(sc->sc_ah);
struct ath_vif *avp = (struct ath_vif *) vif->drv_priv;
+ struct ath_beacon_config *cur_conf;
+ struct ath_chanctx *go_ctx;
+ unsigned long timeout;
bool changed = false;
+ u32 beacon_int;
if (!test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags))
return;
@@ -2433,19 +2484,57 @@ static void ath9k_mgd_prepare_tx(struct ieee80211_hw *hw,
mutex_lock(&sc->mutex);
spin_lock_bh(&sc->chan_lock);
- if (sc->next_chan || (sc->cur_chan != avp->chanctx)) {
- sc->next_chan = avp->chanctx;
+ if (sc->next_chan || (sc->cur_chan != avp->chanctx))
changed = true;
+ spin_unlock_bh(&sc->chan_lock);
+
+ if (!changed)
+ goto out;
+
+ ath9k_cancel_pending_offchannel(sc);
+
+ go_ctx = ath_is_go_chanctx_present(sc);
+
+ if (go_ctx) {
+ /*
+ * Wait till the GO interface gets a chance
+ * to send out an NoA.
+ */
+ spin_lock_bh(&sc->chan_lock);
+ sc->sched.mgd_prepare_tx = true;
+ cur_conf = &go_ctx->beacon;
+ beacon_int = TU_TO_USEC(cur_conf->beacon_interval);
+ spin_unlock_bh(&sc->chan_lock);
+
+ timeout = usecs_to_jiffies(beacon_int * 2);
+ init_completion(&sc->go_beacon);
+
+ mutex_unlock(&sc->mutex);
+
+ if (wait_for_completion_timeout(&sc->go_beacon,
+ timeout) == 0) {
+ ath_dbg(common, CHAN_CTX,
+ "Failed to send new NoA\n");
+
+ spin_lock_bh(&sc->chan_lock);
+ sc->sched.mgd_prepare_tx = false;
+ spin_unlock_bh(&sc->chan_lock);
+ }
+
+ mutex_lock(&sc->mutex);
}
+
ath_dbg(common, CHAN_CTX,
- "%s: Set chanctx state to FORCE_ACTIVE, changed: %d\n",
- __func__, changed);
+ "%s: Set chanctx state to FORCE_ACTIVE for vif: %pM\n",
+ __func__, vif->addr);
+
+ spin_lock_bh(&sc->chan_lock);
+ sc->next_chan = avp->chanctx;
sc->sched.state = ATH_CHANCTX_STATE_FORCE_ACTIVE;
spin_unlock_bh(&sc->chan_lock);
- if (changed)
- ath_chanctx_set_next(sc, true);
-
+ ath_chanctx_set_next(sc, true);
+out:
mutex_unlock(&sc->mutex);
}
@@ -2468,6 +2557,24 @@ void ath9k_fill_chanctx_ops(void)
#endif
+static int ath9k_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ int *dbm)
+{
+ struct ath_softc *sc = hw->priv;
+ struct ath_vif *avp = (void *)vif->drv_priv;
+
+ mutex_lock(&sc->mutex);
+ if (avp->chanctx)
+ *dbm = avp->chanctx->cur_txpower;
+ else
+ *dbm = sc->cur_chan->cur_txpower;
+ mutex_unlock(&sc->mutex);
+
+ *dbm /= 2;
+
+ return 0;
+}
+
struct ieee80211_ops ath9k_ops = {
.tx = ath9k_tx,
.start = ath9k_start,
@@ -2477,8 +2584,7 @@ struct ieee80211_ops ath9k_ops = {
.remove_interface = ath9k_remove_interface,
.config = ath9k_config,
.configure_filter = ath9k_configure_filter,
- .sta_add = ath9k_sta_add,
- .sta_remove = ath9k_sta_remove,
+ .sta_state = ath9k_sta_state,
.sta_notify = ath9k_sta_notify,
.conf_tx = ath9k_conf_tx,
.bss_info_changed = ath9k_bss_info_changed,
@@ -2515,4 +2621,5 @@ struct ieee80211_ops ath9k_ops = {
#endif
.sw_scan_start = ath9k_sw_scan_start,
.sw_scan_complete = ath9k_sw_scan_complete,
+ .get_txpower = ath9k_get_txpower,
};
OpenPOWER on IntegriCloud