mac80211: implement hardware offload for remain-on-channel
This allows drivers to support remain-on-channel offload if they implement smarter timing or need to use a device implementation like iwlwifi. Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
parent
c96e96354a
commit
21f8358964
|
@ -365,6 +365,7 @@ enum mac80211_tx_control_flags {
|
|||
IEEE80211_TX_INTFL_NL80211_FRAME_TX = BIT(21),
|
||||
IEEE80211_TX_CTL_LDPC = BIT(22),
|
||||
IEEE80211_TX_CTL_STBC = BIT(23) | BIT(24),
|
||||
IEEE80211_TX_CTL_TX_OFFCHAN = BIT(25),
|
||||
};
|
||||
|
||||
#define IEEE80211_TX_CTL_STBC_SHIFT 23
|
||||
|
@ -1824,6 +1825,12 @@ struct ieee80211_ops {
|
|||
int (*napi_poll)(struct ieee80211_hw *hw, int budget);
|
||||
int (*set_antenna)(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant);
|
||||
int (*get_antenna)(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant);
|
||||
|
||||
int (*remain_on_channel)(struct ieee80211_hw *hw,
|
||||
struct ieee80211_channel *chan,
|
||||
enum nl80211_channel_type channel_type,
|
||||
int duration);
|
||||
int (*cancel_remain_on_channel)(struct ieee80211_hw *hw);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2729,6 +2736,18 @@ void ieee80211_request_smps(struct ieee80211_vif *vif,
|
|||
*/
|
||||
void ieee80211_key_removed(struct ieee80211_key_conf *key_conf);
|
||||
|
||||
/**
|
||||
* ieee80211_ready_on_channel - notification of remain-on-channel start
|
||||
* @hw: pointer as obtained from ieee80211_alloc_hw()
|
||||
*/
|
||||
void ieee80211_ready_on_channel(struct ieee80211_hw *hw);
|
||||
|
||||
/**
|
||||
* ieee80211_remain_on_channel_expired - remain_on_channel duration expired
|
||||
* @hw: pointer as obtained from ieee80211_alloc_hw()
|
||||
*/
|
||||
void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw);
|
||||
|
||||
/* Rate control API */
|
||||
|
||||
/**
|
||||
|
|
|
@ -1593,6 +1593,37 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int ieee80211_remain_on_channel_hw(struct ieee80211_local *local,
|
||||
struct net_device *dev,
|
||||
struct ieee80211_channel *chan,
|
||||
enum nl80211_channel_type chantype,
|
||||
unsigned int duration, u64 *cookie)
|
||||
{
|
||||
int ret;
|
||||
u32 random_cookie;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (local->hw_roc_cookie)
|
||||
return -EBUSY;
|
||||
/* must be nonzero */
|
||||
random_cookie = random32() | 1;
|
||||
|
||||
*cookie = random_cookie;
|
||||
local->hw_roc_dev = dev;
|
||||
local->hw_roc_cookie = random_cookie;
|
||||
local->hw_roc_channel = chan;
|
||||
local->hw_roc_channel_type = chantype;
|
||||
local->hw_roc_duration = duration;
|
||||
ret = drv_remain_on_channel(local, chan, chantype, duration);
|
||||
if (ret) {
|
||||
local->hw_roc_channel = NULL;
|
||||
local->hw_roc_cookie = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ieee80211_remain_on_channel(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
struct ieee80211_channel *chan,
|
||||
|
@ -1601,16 +1632,62 @@ static int ieee80211_remain_on_channel(struct wiphy *wiphy,
|
|||
u64 *cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
|
||||
if (local->ops->remain_on_channel) {
|
||||
int ret;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
ret = ieee80211_remain_on_channel_hw(local, dev,
|
||||
chan, channel_type,
|
||||
duration, cookie);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ieee80211_wk_remain_on_channel(sdata, chan, channel_type,
|
||||
duration, cookie);
|
||||
}
|
||||
|
||||
static int ieee80211_cancel_remain_on_channel_hw(struct ieee80211_local *local,
|
||||
u64 cookie)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (local->hw_roc_cookie != cookie)
|
||||
return -ENOENT;
|
||||
|
||||
ret = drv_cancel_remain_on_channel(local);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
local->hw_roc_cookie = 0;
|
||||
local->hw_roc_channel = NULL;
|
||||
|
||||
ieee80211_recalc_idle(local);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
u64 cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
|
||||
if (local->ops->cancel_remain_on_channel) {
|
||||
int ret;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
ret = ieee80211_cancel_remain_on_channel_hw(local, cookie);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ieee80211_wk_cancel_remain_on_channel(sdata, cookie);
|
||||
}
|
||||
|
@ -1662,6 +1739,12 @@ static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct net_device *dev,
|
|||
channel_type != local->_oper_channel_type))
|
||||
is_offchan = true;
|
||||
|
||||
if (chan == local->hw_roc_channel) {
|
||||
/* TODO: check channel type? */
|
||||
is_offchan = false;
|
||||
flags |= IEEE80211_TX_CTL_TX_OFFCHAN;
|
||||
}
|
||||
|
||||
if (is_offchan && !offchan)
|
||||
return -EBUSY;
|
||||
|
||||
|
|
|
@ -465,4 +465,34 @@ static inline int drv_get_antenna(struct ieee80211_local *local,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static inline int drv_remain_on_channel(struct ieee80211_local *local,
|
||||
struct ieee80211_channel *chan,
|
||||
enum nl80211_channel_type chantype,
|
||||
unsigned int duration)
|
||||
{
|
||||
int ret;
|
||||
|
||||
might_sleep();
|
||||
|
||||
trace_drv_remain_on_channel(local, chan, chantype, duration);
|
||||
ret = local->ops->remain_on_channel(&local->hw, chan, chantype,
|
||||
duration);
|
||||
trace_drv_return_int(local, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int drv_cancel_remain_on_channel(struct ieee80211_local *local)
|
||||
{
|
||||
int ret;
|
||||
|
||||
might_sleep();
|
||||
|
||||
trace_drv_cancel_remain_on_channel(local);
|
||||
ret = local->ops->cancel_remain_on_channel(&local->hw);
|
||||
trace_drv_return_int(local, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* __MAC80211_DRIVER_OPS */
|
||||
|
|
|
@ -933,6 +933,50 @@ TRACE_EVENT(drv_get_antenna,
|
|||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(drv_remain_on_channel,
|
||||
TP_PROTO(struct ieee80211_local *local, struct ieee80211_channel *chan,
|
||||
enum nl80211_channel_type chantype, unsigned int duration),
|
||||
|
||||
TP_ARGS(local, chan, chantype, duration),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
__field(int, center_freq)
|
||||
__field(int, channel_type)
|
||||
__field(unsigned int, duration)
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
__entry->center_freq = chan->center_freq;
|
||||
__entry->channel_type = chantype;
|
||||
__entry->duration = duration;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT " freq:%dMHz duration:%dms",
|
||||
LOCAL_PR_ARG, __entry->center_freq, __entry->duration
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(drv_cancel_remain_on_channel,
|
||||
TP_PROTO(struct ieee80211_local *local),
|
||||
|
||||
TP_ARGS(local),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT, LOCAL_PR_ARG
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Tracing for API calls that drivers call.
|
||||
*/
|
||||
|
@ -1170,6 +1214,42 @@ TRACE_EVENT(api_chswitch_done,
|
|||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(api_ready_on_channel,
|
||||
TP_PROTO(struct ieee80211_local *local),
|
||||
|
||||
TP_ARGS(local),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT, LOCAL_PR_ARG
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(api_remain_on_channel_expired,
|
||||
TP_PROTO(struct ieee80211_local *local),
|
||||
|
||||
TP_ARGS(local),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT, LOCAL_PR_ARG
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Tracing for internal functions
|
||||
* (which may also be called in response to driver calls)
|
||||
|
|
|
@ -951,6 +951,13 @@ struct ieee80211_local {
|
|||
} debugfs;
|
||||
#endif
|
||||
|
||||
struct ieee80211_channel *hw_roc_channel;
|
||||
struct net_device *hw_roc_dev;
|
||||
struct work_struct hw_roc_start, hw_roc_done;
|
||||
enum nl80211_channel_type hw_roc_channel_type;
|
||||
unsigned int hw_roc_duration;
|
||||
u32 hw_roc_cookie;
|
||||
|
||||
/* dummy netdev for use w/ NAPI */
|
||||
struct net_device napi_dev;
|
||||
|
||||
|
@ -1142,6 +1149,7 @@ void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local);
|
|||
void ieee80211_offchannel_stop_station(struct ieee80211_local *local);
|
||||
void ieee80211_offchannel_return(struct ieee80211_local *local,
|
||||
bool enable_beaconing);
|
||||
void ieee80211_hw_roc_setup(struct ieee80211_local *local);
|
||||
|
||||
/* interface handling */
|
||||
int ieee80211_iface_init(void);
|
||||
|
|
|
@ -1264,7 +1264,7 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local)
|
|||
{
|
||||
struct ieee80211_sub_if_data *sdata;
|
||||
int count = 0;
|
||||
bool working = false, scanning = false;
|
||||
bool working = false, scanning = false, hw_roc = false;
|
||||
struct ieee80211_work *wk;
|
||||
unsigned int led_trig_start = 0, led_trig_stop = 0;
|
||||
|
||||
|
@ -1308,6 +1308,9 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local)
|
|||
local->scan_sdata->vif.bss_conf.idle = false;
|
||||
}
|
||||
|
||||
if (local->hw_roc_channel)
|
||||
hw_roc = true;
|
||||
|
||||
list_for_each_entry(sdata, &local->interfaces, list) {
|
||||
if (sdata->old_idle == sdata->vif.bss_conf.idle)
|
||||
continue;
|
||||
|
@ -1316,7 +1319,7 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local)
|
|||
ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_IDLE);
|
||||
}
|
||||
|
||||
if (working || scanning)
|
||||
if (working || scanning || hw_roc)
|
||||
led_trig_start |= IEEE80211_TPT_LEDTRIG_FL_WORK;
|
||||
else
|
||||
led_trig_stop |= IEEE80211_TPT_LEDTRIG_FL_WORK;
|
||||
|
@ -1328,6 +1331,8 @@ u32 __ieee80211_recalc_idle(struct ieee80211_local *local)
|
|||
|
||||
ieee80211_mod_tpt_led_trig(local, led_trig_start, led_trig_stop);
|
||||
|
||||
if (hw_roc)
|
||||
return ieee80211_idle_off(local, "hw remain-on-channel");
|
||||
if (working)
|
||||
return ieee80211_idle_off(local, "working");
|
||||
if (scanning)
|
||||
|
|
|
@ -609,6 +609,8 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
|
|||
|
||||
ieee80211_led_names(local);
|
||||
|
||||
ieee80211_hw_roc_setup(local);
|
||||
|
||||
return local_to_hw(local);
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_alloc_hw);
|
||||
|
@ -753,7 +755,8 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
|
|||
}
|
||||
}
|
||||
|
||||
local->hw.wiphy->max_remain_on_channel_duration = 5000;
|
||||
if (!local->ops->remain_on_channel)
|
||||
local->hw.wiphy->max_remain_on_channel_duration = 5000;
|
||||
|
||||
result = wiphy_register(local->hw.wiphy);
|
||||
if (result < 0)
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
#include <net/mac80211.h>
|
||||
#include "ieee80211_i.h"
|
||||
#include "driver-trace.h"
|
||||
|
||||
/*
|
||||
* inform AP that we will go to sleep so that it will buffer the frames
|
||||
|
@ -190,3 +191,77 @@ void ieee80211_offchannel_return(struct ieee80211_local *local,
|
|||
}
|
||||
mutex_unlock(&local->iflist_mtx);
|
||||
}
|
||||
|
||||
static void ieee80211_hw_roc_start(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_local *local =
|
||||
container_of(work, struct ieee80211_local, hw_roc_start);
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
if (!local->hw_roc_channel) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return;
|
||||
}
|
||||
|
||||
ieee80211_recalc_idle(local);
|
||||
|
||||
cfg80211_ready_on_channel(local->hw_roc_dev, local->hw_roc_cookie,
|
||||
local->hw_roc_channel,
|
||||
local->hw_roc_channel_type,
|
||||
local->hw_roc_duration,
|
||||
GFP_KERNEL);
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
||||
void ieee80211_ready_on_channel(struct ieee80211_hw *hw)
|
||||
{
|
||||
struct ieee80211_local *local = hw_to_local(hw);
|
||||
|
||||
trace_api_ready_on_channel(local);
|
||||
|
||||
ieee80211_queue_work(hw, &local->hw_roc_start);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel);
|
||||
|
||||
static void ieee80211_hw_roc_done(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_local *local =
|
||||
container_of(work, struct ieee80211_local, hw_roc_done);
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
if (!local->hw_roc_channel) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return;
|
||||
}
|
||||
|
||||
cfg80211_remain_on_channel_expired(local->hw_roc_dev,
|
||||
local->hw_roc_cookie,
|
||||
local->hw_roc_channel,
|
||||
local->hw_roc_channel_type,
|
||||
GFP_KERNEL);
|
||||
|
||||
local->hw_roc_channel = NULL;
|
||||
local->hw_roc_cookie = 0;
|
||||
|
||||
ieee80211_recalc_idle(local);
|
||||
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
||||
void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw)
|
||||
{
|
||||
struct ieee80211_local *local = hw_to_local(hw);
|
||||
|
||||
trace_api_remain_on_channel_expired(local);
|
||||
|
||||
ieee80211_queue_work(hw, &local->hw_roc_done);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired);
|
||||
|
||||
void ieee80211_hw_roc_setup(struct ieee80211_local *local)
|
||||
{
|
||||
INIT_WORK(&local->hw_roc_start, ieee80211_hw_roc_start);
|
||||
INIT_WORK(&local->hw_roc_done, ieee80211_hw_roc_done);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue