forked from Minki/linux
This pull request got a bit bigger than I wanted, due to
needing to reshuffle and fix some bugs. I merged mac80211 to get the right base for some of these changes. * new mac80211 API for upcoming driver changes: EOSP handling, key iteration * scan abort changes allowing to cancel an ongoing scan * VHT IBSS 80+80 MHz support * re-enable full AP client state tracking after fixes * various small fixes (that weren't relevant for mac80211) * various cleanups -----BEGIN PGP SIGNATURE----- iQIcBAABCgAGBQJWZVw7AAoJEGt7eEactAAdQcgP/1bOBBKgCHWZ8xhqmhLIPPUP AgkkyBcjCbSOWyE1axm5WQZM+fQvyGAcYsnhsK7h0Wy5Jvv6goNYhxkoD3L5lAKC LkiiqokTpLx1Em6Iugn1sdgag8q7EquYYQN+hOEOWtp32pTsx3/pDglCtGu0SX1N eystHEAu6mzPezat99M4s80fRlfBop3yaUuL5XopQFGtU37zfUgoXJB3BoXgxNjK XyD22jtPDreDMndZ9ugfvMaiq3iKRBhKXqgGb3SqMaStIyRK8zAkHb5jg3CllMeq bEsz4Rb4r+vtm2AVsUMWjfd/upQKwPwuvdvCvv4AQCO+aR9Rm+tR/wnnD4Gtnek5 zPQ6XWt/0V4CKGl+W9shnDSA1DZ3hTijJlaGsK+RUqEtdq903lEP7fc2GsSvlund jXHfOExieuZOToKWTKpmNGsCw6fjJaGXNd/iLWo5VGAZS2X+JLmFZ94g43a6zOGZ s1Gz4F3tz4u4Bd26NAK2Z6CQRvDS4OOyLIjl9vpB9Fk/9nQx3f7WD8aBTRuCVAtG U2sFEUscz3rkdct30Gvkjm3ovmgc4pomTDvOpmNIsSCi2ygzGWHbEvSrrHdIjzVy KDcvRs6bRtCL/WxaaEIk46M6+6aKlSnZytPLl7vkNnvxXuEF7GYdnNVSUbSH9Nte XzT4+rZRiqyPZEGhBekw =+5dd -----END PGP SIGNATURE----- Merge tag 'mac80211-next-for-davem-2015-12-07' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211-next Johannes Berg says: ==================== This pull request got a bit bigger than I wanted, due to needing to reshuffle and fix some bugs. I merged mac80211 to get the right base for some of these changes. * new mac80211 API for upcoming driver changes: EOSP handling, key iteration * scan abort changes allowing to cancel an ongoing scan * VHT IBSS 80+80 MHz support * re-enable full AP client state tracking after fixes * various small fixes (that weren't relevant for mac80211) * various cleanups ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
commit
ad9360b3e5
@ -495,6 +495,9 @@ struct mac80211_hwsim_data {
|
||||
const struct ieee80211_regdomain *regd;
|
||||
|
||||
struct ieee80211_channel *tmp_chan;
|
||||
struct ieee80211_channel *roc_chan;
|
||||
u32 roc_duration;
|
||||
struct delayed_work roc_start;
|
||||
struct delayed_work roc_done;
|
||||
struct delayed_work hw_scan;
|
||||
struct cfg80211_scan_request *hw_scan_request;
|
||||
@ -514,6 +517,7 @@ struct mac80211_hwsim_data {
|
||||
bool ps_poll_pending;
|
||||
struct dentry *debugfs;
|
||||
|
||||
uintptr_t pending_cookie;
|
||||
struct sk_buff_head pending; /* packets pending */
|
||||
/*
|
||||
* Only radios in the same group can communicate together (the
|
||||
@ -810,6 +814,9 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw,
|
||||
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_skb);
|
||||
struct ieee80211_rate *txrate = ieee80211_get_tx_rate(hw, info);
|
||||
|
||||
if (WARN_ON(!txrate))
|
||||
return;
|
||||
|
||||
if (!netif_running(hwsim_mon))
|
||||
return;
|
||||
|
||||
@ -960,6 +967,7 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
|
||||
unsigned int hwsim_flags = 0;
|
||||
int i;
|
||||
struct hwsim_tx_rate tx_attempts[IEEE80211_TX_MAX_RATES];
|
||||
uintptr_t cookie;
|
||||
|
||||
if (data->ps != PS_DISABLED)
|
||||
hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM);
|
||||
@ -1018,7 +1026,10 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw,
|
||||
goto nla_put_failure;
|
||||
|
||||
/* We create a cookie to identify this skb */
|
||||
if (nla_put_u64(skb, HWSIM_ATTR_COOKIE, (unsigned long) my_skb))
|
||||
data->pending_cookie++;
|
||||
cookie = data->pending_cookie;
|
||||
info->rate_driver_data[0] = (void *)cookie;
|
||||
if (nla_put_u64(skb, HWSIM_ATTR_COOKIE, cookie))
|
||||
goto nla_put_failure;
|
||||
|
||||
genlmsg_end(skb, msg_head);
|
||||
@ -1247,6 +1258,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
|
||||
{
|
||||
struct mac80211_hwsim_data *data = hw->priv;
|
||||
struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
|
||||
struct ieee80211_hdr *hdr = (void *)skb->data;
|
||||
struct ieee80211_chanctx_conf *chanctx_conf;
|
||||
struct ieee80211_channel *channel;
|
||||
bool ack;
|
||||
@ -1292,6 +1304,22 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
|
||||
ARRAY_SIZE(txi->control.rates));
|
||||
|
||||
txi->rate_driver_data[0] = channel;
|
||||
|
||||
if (skb->len >= 24 + 8 &&
|
||||
ieee80211_is_probe_resp(hdr->frame_control)) {
|
||||
/* fake header transmission time */
|
||||
struct ieee80211_mgmt *mgmt;
|
||||
struct ieee80211_rate *txrate;
|
||||
u64 ts;
|
||||
|
||||
mgmt = (struct ieee80211_mgmt *)skb->data;
|
||||
txrate = ieee80211_get_tx_rate(hw, txi);
|
||||
ts = mac80211_hwsim_get_tsf_raw();
|
||||
mgmt->u.probe_resp.timestamp =
|
||||
cpu_to_le64(ts + data->tsf_offset +
|
||||
24 * 8 * 10 / txrate->bitrate);
|
||||
}
|
||||
|
||||
mac80211_hwsim_monitor_rx(hw, skb, channel);
|
||||
|
||||
/* wmediumd mode check */
|
||||
@ -1871,7 +1899,8 @@ static void hw_scan_work(struct work_struct *work)
|
||||
req->channels[hwsim->scan_chan_idx]->center_freq);
|
||||
|
||||
hwsim->tmp_chan = req->channels[hwsim->scan_chan_idx];
|
||||
if (hwsim->tmp_chan->flags & IEEE80211_CHAN_NO_IR ||
|
||||
if (hwsim->tmp_chan->flags & (IEEE80211_CHAN_NO_IR |
|
||||
IEEE80211_CHAN_RADAR) ||
|
||||
!req->n_ssids) {
|
||||
dwell = 120;
|
||||
} else {
|
||||
@ -1987,6 +2016,23 @@ static void mac80211_hwsim_sw_scan_complete(struct ieee80211_hw *hw,
|
||||
mutex_unlock(&hwsim->mutex);
|
||||
}
|
||||
|
||||
static void hw_roc_start(struct work_struct *work)
|
||||
{
|
||||
struct mac80211_hwsim_data *hwsim =
|
||||
container_of(work, struct mac80211_hwsim_data, roc_start.work);
|
||||
|
||||
mutex_lock(&hwsim->mutex);
|
||||
|
||||
wiphy_debug(hwsim->hw->wiphy, "hwsim ROC begins\n");
|
||||
hwsim->tmp_chan = hwsim->roc_chan;
|
||||
ieee80211_ready_on_channel(hwsim->hw);
|
||||
|
||||
ieee80211_queue_delayed_work(hwsim->hw, &hwsim->roc_done,
|
||||
msecs_to_jiffies(hwsim->roc_duration));
|
||||
|
||||
mutex_unlock(&hwsim->mutex);
|
||||
}
|
||||
|
||||
static void hw_roc_done(struct work_struct *work)
|
||||
{
|
||||
struct mac80211_hwsim_data *hwsim =
|
||||
@ -2014,16 +2060,14 @@ static int mac80211_hwsim_roc(struct ieee80211_hw *hw,
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
hwsim->tmp_chan = chan;
|
||||
hwsim->roc_chan = chan;
|
||||
hwsim->roc_duration = duration;
|
||||
mutex_unlock(&hwsim->mutex);
|
||||
|
||||
wiphy_debug(hw->wiphy, "hwsim ROC (%d MHz, %d ms)\n",
|
||||
chan->center_freq, duration);
|
||||
ieee80211_queue_delayed_work(hw, &hwsim->roc_start, HZ/50);
|
||||
|
||||
ieee80211_ready_on_channel(hw);
|
||||
|
||||
ieee80211_queue_delayed_work(hw, &hwsim->roc_done,
|
||||
msecs_to_jiffies(duration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -2031,6 +2075,7 @@ static int mac80211_hwsim_croc(struct ieee80211_hw *hw)
|
||||
{
|
||||
struct mac80211_hwsim_data *hwsim = hw->priv;
|
||||
|
||||
cancel_delayed_work_sync(&hwsim->roc_start);
|
||||
cancel_delayed_work_sync(&hwsim->roc_done);
|
||||
|
||||
mutex_lock(&hwsim->mutex);
|
||||
@ -2375,6 +2420,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
|
||||
hw->wiphy->n_iface_combinations = ARRAY_SIZE(hwsim_if_comb);
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&data->roc_start, hw_roc_start);
|
||||
INIT_DELAYED_WORK(&data->roc_done, hw_roc_done);
|
||||
INIT_DELAYED_WORK(&data->hw_scan, hw_scan_work);
|
||||
|
||||
@ -2411,6 +2457,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
|
||||
NL80211_FEATURE_STATIC_SMPS |
|
||||
NL80211_FEATURE_DYNAMIC_SMPS |
|
||||
NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR;
|
||||
wiphy_ext_feature_set(hw->wiphy, NL80211_EXT_FEATURE_VHT_IBSS);
|
||||
|
||||
/* ask mac80211 to reserve space for magic */
|
||||
hw->vif_data_size = sizeof(struct hwsim_vif_priv);
|
||||
@ -2710,7 +2757,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2,
|
||||
struct mac80211_hwsim_data *data2;
|
||||
struct ieee80211_tx_info *txi;
|
||||
struct hwsim_tx_rate *tx_attempts;
|
||||
unsigned long ret_skb_ptr;
|
||||
u64 ret_skb_cookie;
|
||||
struct sk_buff *skb, *tmp;
|
||||
const u8 *src;
|
||||
unsigned int hwsim_flags;
|
||||
@ -2728,7 +2775,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2,
|
||||
|
||||
src = (void *)nla_data(info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]);
|
||||
hwsim_flags = nla_get_u32(info->attrs[HWSIM_ATTR_FLAGS]);
|
||||
ret_skb_ptr = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]);
|
||||
ret_skb_cookie = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]);
|
||||
|
||||
data2 = get_hwsim_data_ref_from_addr(src);
|
||||
if (!data2)
|
||||
@ -2736,7 +2783,12 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2,
|
||||
|
||||
/* look for the skb matching the cookie passed back from user */
|
||||
skb_queue_walk_safe(&data2->pending, skb, tmp) {
|
||||
if ((unsigned long)skb == ret_skb_ptr) {
|
||||
u64 skb_cookie;
|
||||
|
||||
txi = IEEE80211_SKB_CB(skb);
|
||||
skb_cookie = (u64)(uintptr_t)txi->rate_driver_data[0];
|
||||
|
||||
if (skb_cookie == ret_skb_cookie) {
|
||||
skb_unlink(skb, &data2->pending);
|
||||
found = true;
|
||||
break;
|
||||
@ -2827,10 +2879,25 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2,
|
||||
|
||||
/* A frame is received from user space */
|
||||
memset(&rx_status, 0, sizeof(rx_status));
|
||||
/* TODO: Check ATTR_FREQ if it exists, and maybe throw away off-channel
|
||||
* packets?
|
||||
*/
|
||||
rx_status.freq = data2->channel->center_freq;
|
||||
if (info->attrs[HWSIM_ATTR_FREQ]) {
|
||||
/* throw away off-channel packets, but allow both the temporary
|
||||
* ("hw" scan/remain-on-channel) and regular channel, since the
|
||||
* internal datapath also allows this
|
||||
*/
|
||||
mutex_lock(&data2->mutex);
|
||||
rx_status.freq = nla_get_u32(info->attrs[HWSIM_ATTR_FREQ]);
|
||||
|
||||
if (rx_status.freq != data2->channel->center_freq &&
|
||||
(!data2->tmp_chan ||
|
||||
rx_status.freq != data2->tmp_chan->center_freq)) {
|
||||
mutex_unlock(&data2->mutex);
|
||||
goto out;
|
||||
}
|
||||
mutex_unlock(&data2->mutex);
|
||||
} else {
|
||||
rx_status.freq = data2->channel->center_freq;
|
||||
}
|
||||
|
||||
rx_status.band = data2->channel->band;
|
||||
rx_status.rate_idx = nla_get_u32(info->attrs[HWSIM_ATTR_RX_RATE]);
|
||||
rx_status.signal = nla_get_u32(info->attrs[HWSIM_ATTR_SIGNAL]);
|
||||
|
@ -2321,6 +2321,8 @@ struct cfg80211_qos_map {
|
||||
* the driver, and will be valid until passed to cfg80211_scan_done().
|
||||
* For scan results, call cfg80211_inform_bss(); you can call this outside
|
||||
* the scan/scan_done bracket too.
|
||||
* @abort_scan: Tell the driver to abort an ongoing scan. The driver shall
|
||||
* indicate the status of the scan through cfg80211_scan_done().
|
||||
*
|
||||
* @auth: Request to authenticate with the specified peer
|
||||
* (invoked with the wireless_dev mutex held)
|
||||
@ -2593,6 +2595,7 @@ struct cfg80211_ops {
|
||||
|
||||
int (*scan)(struct wiphy *wiphy,
|
||||
struct cfg80211_scan_request *request);
|
||||
void (*abort_scan)(struct wiphy *wiphy, struct wireless_dev *wdev);
|
||||
|
||||
int (*auth)(struct wiphy *wiphy, struct net_device *dev,
|
||||
struct cfg80211_auth_request *req);
|
||||
@ -5173,8 +5176,11 @@ size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
|
||||
* buffer starts, which may be @ielen if the entire (remainder)
|
||||
* of the buffer should be used.
|
||||
*/
|
||||
size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
|
||||
const u8 *ids, int n_ids, size_t offset);
|
||||
static inline size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
|
||||
const u8 *ids, int n_ids, size_t offset)
|
||||
{
|
||||
return ieee80211_ie_split_ric(ies, ielen, ids, n_ids, NULL, 0, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* cfg80211_report_wowlan_wakeup - report wakeup from WoWLAN
|
||||
|
@ -1321,11 +1321,15 @@ struct ieee80211_channel_switch {
|
||||
* interface. This flag should be set during interface addition,
|
||||
* but may be set/cleared as late as authentication to an AP. It is
|
||||
* only valid for managed/station mode interfaces.
|
||||
* @IEEE80211_VIF_GET_NOA_UPDATE: request to handle NOA attributes
|
||||
* and send P2P_PS notification to the driver if NOA changed, even
|
||||
* this is not pure P2P vif.
|
||||
*/
|
||||
enum ieee80211_vif_flags {
|
||||
IEEE80211_VIF_BEACON_FILTER = BIT(0),
|
||||
IEEE80211_VIF_SUPPORTS_CQM_RSSI = BIT(1),
|
||||
IEEE80211_VIF_SUPPORTS_UAPSD = BIT(2),
|
||||
IEEE80211_VIF_GET_NOA_UPDATE = BIT(3),
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1901,6 +1905,11 @@ struct ieee80211_txq {
|
||||
* @IEEE80211_HW_BEACON_TX_STATUS: The device/driver provides TX status
|
||||
* for sent beacons.
|
||||
*
|
||||
* @IEEE80211_HW_NEEDS_UNIQUE_STA_ADDR: Hardware (or driver) requires that each
|
||||
* station has a unique address, i.e. each station entry can be identified
|
||||
* by just its MAC address; this prevents, for example, the same station
|
||||
* from connecting to two virtual AP interfaces at the same time.
|
||||
*
|
||||
* @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
|
||||
*/
|
||||
enum ieee80211_hw_flags {
|
||||
@ -1936,6 +1945,7 @@ enum ieee80211_hw_flags {
|
||||
IEEE80211_HW_TDLS_WIDER_BW,
|
||||
IEEE80211_HW_SUPPORTS_AMSDU_IN_AMPDU,
|
||||
IEEE80211_HW_BEACON_TX_STATUS,
|
||||
IEEE80211_HW_NEEDS_UNIQUE_STA_ADDR,
|
||||
|
||||
/* keep last, obviously */
|
||||
NUM_IEEE80211_HW_FLAGS
|
||||
@ -4862,6 +4872,28 @@ void ieee80211_sta_block_awake(struct ieee80211_hw *hw,
|
||||
*/
|
||||
void ieee80211_sta_eosp(struct ieee80211_sta *pubsta);
|
||||
|
||||
/**
|
||||
* ieee80211_send_eosp_nullfunc - ask mac80211 to send NDP with EOSP
|
||||
* @pubsta: the station
|
||||
* @tid: the tid of the NDP
|
||||
*
|
||||
* Sometimes the device understands that it needs to close
|
||||
* the Service Period unexpectedly. This can happen when
|
||||
* sending frames that are filling holes in the BA window.
|
||||
* In this case, the device can ask mac80211 to send a
|
||||
* Nullfunc frame with EOSP set. When that happens, the
|
||||
* driver must have called ieee80211_sta_set_buffered() to
|
||||
* let mac80211 know that there are no buffered frames any
|
||||
* more, otherwise mac80211 will get the more_data bit wrong.
|
||||
* The low level driver must have made sure that the frame
|
||||
* will be sent despite the station being in power-save.
|
||||
* Mac80211 won't call allow_buffered_frames().
|
||||
* Note that calling this function, doesn't exempt the driver
|
||||
* from closing the EOSP properly, it will still have to call
|
||||
* ieee80211_sta_eosp when the NDP is sent.
|
||||
*/
|
||||
void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid);
|
||||
|
||||
/**
|
||||
* ieee80211_iter_keys - iterate keys programmed into the device
|
||||
* @hw: pointer obtained from ieee80211_alloc_hw()
|
||||
@ -4889,6 +4921,30 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw,
|
||||
void *data),
|
||||
void *iter_data);
|
||||
|
||||
/**
|
||||
* ieee80211_iter_keys_rcu - iterate keys programmed into the device
|
||||
* @hw: pointer obtained from ieee80211_alloc_hw()
|
||||
* @vif: virtual interface to iterate, may be %NULL for all
|
||||
* @iter: iterator function that will be called for each key
|
||||
* @iter_data: custom data to pass to the iterator function
|
||||
*
|
||||
* This function can be used to iterate all the keys known to
|
||||
* mac80211, even those that weren't previously programmed into
|
||||
* the device. Note that due to locking reasons, keys of station
|
||||
* in removal process will be skipped.
|
||||
*
|
||||
* This function requires being called in an RCU critical section,
|
||||
* and thus iter must be atomic.
|
||||
*/
|
||||
void ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
void (*iter)(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
struct ieee80211_key_conf *key,
|
||||
void *data),
|
||||
void *iter_data);
|
||||
|
||||
/**
|
||||
* ieee80211_iter_chan_contexts_atomic - iterate channel contexts
|
||||
* @hw: pointre obtained from ieee80211_alloc_hw().
|
||||
|
@ -820,6 +820,10 @@
|
||||
* as an event to indicate changes for devices with wiphy-specific regdom
|
||||
* management.
|
||||
*
|
||||
* @NL80211_CMD_ABORT_SCAN: Stop an ongoing scan. Returns -ENOENT if a scan is
|
||||
* not running. The driver indicates the status of the scan through
|
||||
* cfg80211_scan_done().
|
||||
*
|
||||
* @NL80211_CMD_MAX: highest used command number
|
||||
* @__NL80211_CMD_AFTER_LAST: internal use
|
||||
*/
|
||||
@ -1006,6 +1010,8 @@ enum nl80211_commands {
|
||||
|
||||
NL80211_CMD_WIPHY_REG_CHANGE,
|
||||
|
||||
NL80211_CMD_ABORT_SCAN,
|
||||
|
||||
/* add new commands above here */
|
||||
|
||||
/* used to define NL80211_CMD_MAX below */
|
||||
@ -1764,8 +1770,9 @@ enum nl80211_commands {
|
||||
* over all channels.
|
||||
*
|
||||
* @NL80211_ATTR_SCHED_SCAN_DELAY: delay before the first cycle of a
|
||||
* scheduled scan (or a WoWLAN net-detect scan) is started, u32
|
||||
* in seconds.
|
||||
* scheduled scan is started. Or the delay before a WoWLAN
|
||||
* net-detect scan is started, counting from the moment the
|
||||
* system is suspended. This value is a u32, in seconds.
|
||||
|
||||
* @NL80211_ATTR_REG_INDOOR: flag attribute, if set indicates that the device
|
||||
* is operating in an indoor environment.
|
||||
|
@ -1216,16 +1216,6 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
|
||||
if (!sta)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* defaults -- if userspace wants something else we'll
|
||||
* change it accordingly in sta_apply_parameters()
|
||||
*/
|
||||
if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)) &&
|
||||
!(params->sta_flags_set & (BIT(NL80211_STA_FLAG_AUTHENTICATED) |
|
||||
BIT(NL80211_STA_FLAG_ASSOCIATED)))) {
|
||||
sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
|
||||
sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
|
||||
}
|
||||
if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
|
||||
sta->sta.tdls = true;
|
||||
|
||||
@ -1994,6 +1984,11 @@ static int ieee80211_scan(struct wiphy *wiphy,
|
||||
return ieee80211_request_scan(sdata, req);
|
||||
}
|
||||
|
||||
static void ieee80211_abort_scan(struct wiphy *wiphy, struct wireless_dev *wdev)
|
||||
{
|
||||
ieee80211_scan_cancel(wiphy_priv(wiphy));
|
||||
}
|
||||
|
||||
static int
|
||||
ieee80211_sched_scan_start(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
@ -2509,294 +2504,6 @@ static int ieee80211_set_bitrate_mask(struct wiphy *wiphy,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool ieee80211_coalesce_started_roc(struct ieee80211_local *local,
|
||||
struct ieee80211_roc_work *new_roc,
|
||||
struct ieee80211_roc_work *cur_roc)
|
||||
{
|
||||
unsigned long now = jiffies;
|
||||
unsigned long remaining = cur_roc->hw_start_time +
|
||||
msecs_to_jiffies(cur_roc->duration) -
|
||||
now;
|
||||
|
||||
if (WARN_ON(!cur_roc->started || !cur_roc->hw_begun))
|
||||
return false;
|
||||
|
||||
/* if it doesn't fit entirely, schedule a new one */
|
||||
if (new_roc->duration > jiffies_to_msecs(remaining))
|
||||
return false;
|
||||
|
||||
ieee80211_handle_roc_started(new_roc);
|
||||
|
||||
/* add to dependents so we send the expired event properly */
|
||||
list_add_tail(&new_roc->list, &cur_roc->dependents);
|
||||
return true;
|
||||
}
|
||||
|
||||
static u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local)
|
||||
{
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
local->roc_cookie_counter++;
|
||||
|
||||
/* wow, you wrapped 64 bits ... more likely a bug */
|
||||
if (WARN_ON(local->roc_cookie_counter == 0))
|
||||
local->roc_cookie_counter++;
|
||||
|
||||
return local->roc_cookie_counter;
|
||||
}
|
||||
|
||||
static int ieee80211_start_roc_work(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata,
|
||||
struct ieee80211_channel *channel,
|
||||
unsigned int duration, u64 *cookie,
|
||||
struct sk_buff *txskb,
|
||||
enum ieee80211_roc_type type)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp;
|
||||
bool queued = false;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (local->use_chanctx && !local->ops->remain_on_channel)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
roc = kzalloc(sizeof(*roc), GFP_KERNEL);
|
||||
if (!roc)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* If the duration is zero, then the driver
|
||||
* wouldn't actually do anything. Set it to
|
||||
* 10 for now.
|
||||
*
|
||||
* TODO: cancel the off-channel operation
|
||||
* when we get the SKB's TX status and
|
||||
* the wait time was zero before.
|
||||
*/
|
||||
if (!duration)
|
||||
duration = 10;
|
||||
|
||||
roc->chan = channel;
|
||||
roc->duration = duration;
|
||||
roc->req_duration = duration;
|
||||
roc->frame = txskb;
|
||||
roc->type = type;
|
||||
roc->sdata = sdata;
|
||||
INIT_DELAYED_WORK(&roc->work, ieee80211_sw_roc_work);
|
||||
INIT_LIST_HEAD(&roc->dependents);
|
||||
|
||||
/*
|
||||
* cookie is either the roc cookie (for normal roc)
|
||||
* or the SKB (for mgmt TX)
|
||||
*/
|
||||
if (!txskb) {
|
||||
roc->cookie = ieee80211_mgmt_tx_cookie(local);
|
||||
*cookie = roc->cookie;
|
||||
} else {
|
||||
roc->mgmt_tx_cookie = *cookie;
|
||||
}
|
||||
|
||||
/* if there's one pending or we're scanning, queue this one */
|
||||
if (!list_empty(&local->roc_list) ||
|
||||
local->scanning || ieee80211_is_radar_required(local))
|
||||
goto out_check_combine;
|
||||
|
||||
/* if not HW assist, just queue & schedule work */
|
||||
if (!local->ops->remain_on_channel) {
|
||||
ieee80211_queue_delayed_work(&local->hw, &roc->work, 0);
|
||||
goto out_queue;
|
||||
}
|
||||
|
||||
/* otherwise actually kick it off here (for error handling) */
|
||||
|
||||
ret = drv_remain_on_channel(local, sdata, channel, duration, type);
|
||||
if (ret) {
|
||||
kfree(roc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
roc->started = true;
|
||||
goto out_queue;
|
||||
|
||||
out_check_combine:
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp->chan != channel || tmp->sdata != sdata)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Extend this ROC if possible:
|
||||
*
|
||||
* If it hasn't started yet, just increase the duration
|
||||
* and add the new one to the list of dependents.
|
||||
* If the type of the new ROC has higher priority, modify the
|
||||
* type of the previous one to match that of the new one.
|
||||
*/
|
||||
if (!tmp->started) {
|
||||
list_add_tail(&roc->list, &tmp->dependents);
|
||||
tmp->duration = max(tmp->duration, roc->duration);
|
||||
tmp->type = max(tmp->type, roc->type);
|
||||
queued = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If it has already started, it's more difficult ... */
|
||||
if (local->ops->remain_on_channel) {
|
||||
/*
|
||||
* In the offloaded ROC case, if it hasn't begun, add
|
||||
* this new one to the dependent list to be handled
|
||||
* when the master one begins. If it has begun,
|
||||
* check if it fits entirely within the existing one,
|
||||
* in which case it will just be dependent as well.
|
||||
* Otherwise, schedule it by itself.
|
||||
*/
|
||||
if (!tmp->hw_begun) {
|
||||
list_add_tail(&roc->list, &tmp->dependents);
|
||||
queued = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ieee80211_coalesce_started_roc(local, roc, tmp))
|
||||
queued = true;
|
||||
} else if (del_timer_sync(&tmp->work.timer)) {
|
||||
unsigned long new_end;
|
||||
|
||||
/*
|
||||
* In the software ROC case, cancel the timer, if
|
||||
* that fails then the finish work is already
|
||||
* queued/pending and thus we queue the new ROC
|
||||
* normally, if that succeeds then we can extend
|
||||
* the timer duration and TX the frame (if any.)
|
||||
*/
|
||||
|
||||
list_add_tail(&roc->list, &tmp->dependents);
|
||||
queued = true;
|
||||
|
||||
new_end = jiffies + msecs_to_jiffies(roc->duration);
|
||||
|
||||
/* ok, it was started & we canceled timer */
|
||||
if (time_after(new_end, tmp->work.timer.expires))
|
||||
mod_timer(&tmp->work.timer, new_end);
|
||||
else
|
||||
add_timer(&tmp->work.timer);
|
||||
|
||||
ieee80211_handle_roc_started(roc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
out_queue:
|
||||
if (!queued)
|
||||
list_add_tail(&roc->list, &local->roc_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ieee80211_remain_on_channel(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev,
|
||||
struct ieee80211_channel *chan,
|
||||
unsigned int duration,
|
||||
u64 *cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
ret = ieee80211_start_roc_work(local, sdata, chan,
|
||||
duration, cookie, NULL,
|
||||
IEEE80211_ROC_TYPE_NORMAL);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ieee80211_cancel_roc(struct ieee80211_local *local,
|
||||
u64 cookie, bool mgmt_tx)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp, *found = NULL;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
|
||||
struct ieee80211_roc_work *dep, *tmp2;
|
||||
|
||||
list_for_each_entry_safe(dep, tmp2, &roc->dependents, list) {
|
||||
if (!mgmt_tx && dep->cookie != cookie)
|
||||
continue;
|
||||
else if (mgmt_tx && dep->mgmt_tx_cookie != cookie)
|
||||
continue;
|
||||
/* found dependent item -- just remove it */
|
||||
list_del(&dep->list);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
ieee80211_roc_notify_destroy(dep, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!mgmt_tx && roc->cookie != cookie)
|
||||
continue;
|
||||
else if (mgmt_tx && roc->mgmt_tx_cookie != cookie)
|
||||
continue;
|
||||
|
||||
found = roc;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* We found the item to cancel, so do that. Note that it
|
||||
* may have dependents, which we also cancel (and send
|
||||
* the expired signal for.) Not doing so would be quite
|
||||
* tricky here, but we may need to fix it later.
|
||||
*/
|
||||
|
||||
if (local->ops->remain_on_channel) {
|
||||
if (found->started) {
|
||||
ret = drv_cancel_remain_on_channel(local);
|
||||
if (WARN_ON_ONCE(ret)) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
list_del(&found->list);
|
||||
|
||||
if (found->started)
|
||||
ieee80211_start_next_roc(local);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
ieee80211_roc_notify_destroy(found, true);
|
||||
} else {
|
||||
/* work may be pending so use it all the time */
|
||||
found->abort = true;
|
||||
ieee80211_queue_delayed_work(&local->hw, &found->work, 0);
|
||||
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
/* work will clean up etc */
|
||||
flush_delayed_work(&found->work);
|
||||
WARN_ON(!found->to_be_freed);
|
||||
kfree(found);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev,
|
||||
u64 cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
|
||||
return ieee80211_cancel_roc(local, cookie, false);
|
||||
}
|
||||
|
||||
static int ieee80211_start_radar_detection(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
struct cfg80211_chan_def *chandef,
|
||||
@ -3267,9 +2974,21 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
||||
return err;
|
||||
}
|
||||
|
||||
static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local,
|
||||
struct sk_buff *skb, u64 *cookie,
|
||||
gfp_t gfp)
|
||||
u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local)
|
||||
{
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
local->roc_cookie_counter++;
|
||||
|
||||
/* wow, you wrapped 64 bits ... more likely a bug */
|
||||
if (WARN_ON(local->roc_cookie_counter == 0))
|
||||
local->roc_cookie_counter++;
|
||||
|
||||
return local->roc_cookie_counter;
|
||||
}
|
||||
|
||||
int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb,
|
||||
u64 *cookie, gfp_t gfp)
|
||||
{
|
||||
unsigned long spin_flags;
|
||||
struct sk_buff *ack_skb;
|
||||
@ -3277,7 +2996,7 @@ static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local,
|
||||
|
||||
ack_skb = skb_copy(skb, gfp);
|
||||
if (!ack_skb)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irqsave(&local->ack_status_lock, spin_flags);
|
||||
id = idr_alloc(&local->ack_status_frames, ack_skb,
|
||||
@ -3286,7 +3005,7 @@ static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local,
|
||||
|
||||
if (id < 0) {
|
||||
kfree_skb(ack_skb);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
IEEE80211_SKB_CB(skb)->ack_frame_id = id;
|
||||
@ -3294,204 +3013,7 @@ static struct sk_buff *ieee80211_make_ack_skb(struct ieee80211_local *local,
|
||||
*cookie = ieee80211_mgmt_tx_cookie(local);
|
||||
IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie;
|
||||
|
||||
return ack_skb;
|
||||
}
|
||||
|
||||
static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
||||
struct cfg80211_mgmt_tx_params *params,
|
||||
u64 *cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
struct sk_buff *skb, *ack_skb;
|
||||
struct sta_info *sta;
|
||||
const struct ieee80211_mgmt *mgmt = (void *)params->buf;
|
||||
bool need_offchan = false;
|
||||
u32 flags;
|
||||
int ret;
|
||||
u8 *data;
|
||||
|
||||
if (params->dont_wait_for_ack)
|
||||
flags = IEEE80211_TX_CTL_NO_ACK;
|
||||
else
|
||||
flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX |
|
||||
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
||||
|
||||
if (params->no_cck)
|
||||
flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
|
||||
|
||||
switch (sdata->vif.type) {
|
||||
case NL80211_IFTYPE_ADHOC:
|
||||
if (!sdata->vif.bss_conf.ibss_joined)
|
||||
need_offchan = true;
|
||||
/* fall through */
|
||||
#ifdef CONFIG_MAC80211_MESH
|
||||
case NL80211_IFTYPE_MESH_POINT:
|
||||
if (ieee80211_vif_is_mesh(&sdata->vif) &&
|
||||
!sdata->u.mesh.mesh_id_len)
|
||||
need_offchan = true;
|
||||
/* fall through */
|
||||
#endif
|
||||
case NL80211_IFTYPE_AP:
|
||||
case NL80211_IFTYPE_AP_VLAN:
|
||||
case NL80211_IFTYPE_P2P_GO:
|
||||
if (sdata->vif.type != NL80211_IFTYPE_ADHOC &&
|
||||
!ieee80211_vif_is_mesh(&sdata->vif) &&
|
||||
!rcu_access_pointer(sdata->bss->beacon))
|
||||
need_offchan = true;
|
||||
if (!ieee80211_is_action(mgmt->frame_control) ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_PUBLIC ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT)
|
||||
break;
|
||||
rcu_read_lock();
|
||||
sta = sta_info_get(sdata, mgmt->da);
|
||||
rcu_read_unlock();
|
||||
if (!sta)
|
||||
return -ENOLINK;
|
||||
break;
|
||||
case NL80211_IFTYPE_STATION:
|
||||
case NL80211_IFTYPE_P2P_CLIENT:
|
||||
sdata_lock(sdata);
|
||||
if (!sdata->u.mgd.associated ||
|
||||
(params->offchan && params->wait &&
|
||||
local->ops->remain_on_channel &&
|
||||
memcmp(sdata->u.mgd.associated->bssid,
|
||||
mgmt->bssid, ETH_ALEN)))
|
||||
need_offchan = true;
|
||||
sdata_unlock(sdata);
|
||||
break;
|
||||
case NL80211_IFTYPE_P2P_DEVICE:
|
||||
need_offchan = true;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* configurations requiring offchan cannot work if no channel has been
|
||||
* specified
|
||||
*/
|
||||
if (need_offchan && !params->chan)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
/* Check if the operating channel is the requested channel */
|
||||
if (!need_offchan) {
|
||||
struct ieee80211_chanctx_conf *chanctx_conf;
|
||||
|
||||
rcu_read_lock();
|
||||
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
|
||||
|
||||
if (chanctx_conf) {
|
||||
need_offchan = params->chan &&
|
||||
(params->chan !=
|
||||
chanctx_conf->def.chan);
|
||||
} else if (!params->chan) {
|
||||
ret = -EINVAL;
|
||||
rcu_read_unlock();
|
||||
goto out_unlock;
|
||||
} else {
|
||||
need_offchan = true;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
if (need_offchan && !params->offchan) {
|
||||
ret = -EBUSY;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
skb = dev_alloc_skb(local->hw.extra_tx_headroom + params->len);
|
||||
if (!skb) {
|
||||
ret = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
skb_reserve(skb, local->hw.extra_tx_headroom);
|
||||
|
||||
data = skb_put(skb, params->len);
|
||||
memcpy(data, params->buf, params->len);
|
||||
|
||||
/* Update CSA counters */
|
||||
if (sdata->vif.csa_active &&
|
||||
(sdata->vif.type == NL80211_IFTYPE_AP ||
|
||||
sdata->vif.type == NL80211_IFTYPE_MESH_POINT ||
|
||||
sdata->vif.type == NL80211_IFTYPE_ADHOC) &&
|
||||
params->n_csa_offsets) {
|
||||
int i;
|
||||
struct beacon_data *beacon = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
||||
beacon = rcu_dereference(sdata->u.ap.beacon);
|
||||
else if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
|
||||
beacon = rcu_dereference(sdata->u.ibss.presp);
|
||||
else if (ieee80211_vif_is_mesh(&sdata->vif))
|
||||
beacon = rcu_dereference(sdata->u.mesh.beacon);
|
||||
|
||||
if (beacon)
|
||||
for (i = 0; i < params->n_csa_offsets; i++)
|
||||
data[params->csa_offsets[i]] =
|
||||
beacon->csa_current_counter;
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
IEEE80211_SKB_CB(skb)->flags = flags;
|
||||
|
||||
skb->dev = sdata->dev;
|
||||
|
||||
if (!params->dont_wait_for_ack) {
|
||||
/* make a copy to preserve the frame contents
|
||||
* in case of encryption.
|
||||
*/
|
||||
ack_skb = ieee80211_make_ack_skb(local, skb, cookie,
|
||||
GFP_KERNEL);
|
||||
if (IS_ERR(ack_skb)) {
|
||||
ret = PTR_ERR(ack_skb);
|
||||
kfree_skb(skb);
|
||||
goto out_unlock;
|
||||
}
|
||||
} else {
|
||||
/* Assign a dummy non-zero cookie, it's not sent to
|
||||
* userspace in this case but we rely on its value
|
||||
* internally in the need_offchan case to distinguish
|
||||
* mgmt-tx from remain-on-channel.
|
||||
*/
|
||||
*cookie = 0xffffffff;
|
||||
}
|
||||
|
||||
if (!need_offchan) {
|
||||
ieee80211_tx_skb(sdata, skb);
|
||||
ret = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN |
|
||||
IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
|
||||
if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL))
|
||||
IEEE80211_SKB_CB(skb)->hw_queue =
|
||||
local->hw.offchannel_tx_hw_queue;
|
||||
|
||||
/* This will handle all kinds of coalescing and immediate TX */
|
||||
ret = ieee80211_start_roc_work(local, sdata, params->chan,
|
||||
params->wait, cookie, skb,
|
||||
IEEE80211_ROC_TYPE_MGMT_TX);
|
||||
if (ret)
|
||||
kfree_skb(skb);
|
||||
out_unlock:
|
||||
mutex_unlock(&local->mtx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev,
|
||||
u64 cookie)
|
||||
{
|
||||
struct ieee80211_local *local = wiphy_priv(wiphy);
|
||||
|
||||
return ieee80211_cancel_roc(local, cookie, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ieee80211_mgmt_frame_register(struct wiphy *wiphy,
|
||||
@ -3569,7 +3091,7 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
struct ieee80211_qos_hdr *nullfunc;
|
||||
struct sk_buff *skb, *ack_skb;
|
||||
struct sk_buff *skb;
|
||||
int size = sizeof(*nullfunc);
|
||||
__le16 fc;
|
||||
bool qos;
|
||||
@ -3637,10 +3159,9 @@ static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev,
|
||||
if (qos)
|
||||
nullfunc->qos_ctrl = cpu_to_le16(7);
|
||||
|
||||
ack_skb = ieee80211_make_ack_skb(local, skb, cookie, GFP_ATOMIC);
|
||||
if (IS_ERR(ack_skb)) {
|
||||
ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_ATOMIC);
|
||||
if (ret) {
|
||||
kfree_skb(skb);
|
||||
ret = PTR_ERR(ack_skb);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
@ -3842,6 +3363,7 @@ const struct cfg80211_ops mac80211_config_ops = {
|
||||
.suspend = ieee80211_suspend,
|
||||
.resume = ieee80211_resume,
|
||||
.scan = ieee80211_scan,
|
||||
.abort_scan = ieee80211_abort_scan,
|
||||
.sched_scan_start = ieee80211_sched_scan_start,
|
||||
.sched_scan_stop = ieee80211_sched_scan_stop,
|
||||
.auth = ieee80211_auth,
|
||||
|
@ -125,6 +125,7 @@ static const char *hw_flag_names[NUM_IEEE80211_HW_FLAGS + 1] = {
|
||||
FLAG(TDLS_WIDER_BW),
|
||||
FLAG(SUPPORTS_AMSDU_IN_AMPDU),
|
||||
FLAG(BEACON_TX_STATUS),
|
||||
FLAG(NEEDS_UNIQUE_STA_ADDR),
|
||||
|
||||
/* keep last for the build bug below */
|
||||
(void *)0x1
|
||||
|
@ -428,6 +428,7 @@ static void ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata,
|
||||
chandef.width = sdata->u.ibss.chandef.width;
|
||||
break;
|
||||
case NL80211_CHAN_WIDTH_80:
|
||||
case NL80211_CHAN_WIDTH_80P80:
|
||||
case NL80211_CHAN_WIDTH_160:
|
||||
chandef = sdata->u.ibss.chandef;
|
||||
chandef.chan = cbss->channel;
|
||||
|
@ -325,19 +325,15 @@ struct mesh_preq_queue {
|
||||
|
||||
struct ieee80211_roc_work {
|
||||
struct list_head list;
|
||||
struct list_head dependents;
|
||||
|
||||
struct delayed_work work;
|
||||
|
||||
struct ieee80211_sub_if_data *sdata;
|
||||
|
||||
struct ieee80211_channel *chan;
|
||||
|
||||
bool started, abort, hw_begun, notified;
|
||||
bool to_be_freed;
|
||||
bool on_channel;
|
||||
|
||||
unsigned long hw_start_time;
|
||||
unsigned long start_time;
|
||||
|
||||
u32 duration, req_duration;
|
||||
struct sk_buff *frame;
|
||||
@ -1335,6 +1331,7 @@ struct ieee80211_local {
|
||||
/*
|
||||
* Remain-on-channel support
|
||||
*/
|
||||
struct delayed_work roc_work;
|
||||
struct list_head roc_list;
|
||||
struct work_struct hw_roc_start, hw_roc_done;
|
||||
unsigned long hw_roc_start_time;
|
||||
@ -1483,6 +1480,10 @@ void ieee80211_bss_info_change_notify(struct ieee80211_sub_if_data *sdata,
|
||||
void ieee80211_configure_filter(struct ieee80211_local *local);
|
||||
u32 ieee80211_reset_erp_info(struct ieee80211_sub_if_data *sdata);
|
||||
|
||||
u64 ieee80211_mgmt_tx_cookie(struct ieee80211_local *local);
|
||||
int ieee80211_attach_ack_skb(struct ieee80211_local *local, struct sk_buff *skb,
|
||||
u64 *cookie, gfp_t gfp);
|
||||
|
||||
/* STA code */
|
||||
void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata);
|
||||
int ieee80211_mgd_auth(struct ieee80211_sub_if_data *sdata,
|
||||
@ -1577,16 +1578,22 @@ int ieee80211_request_sched_scan_stop(struct ieee80211_local *local);
|
||||
void ieee80211_sched_scan_end(struct ieee80211_local *local);
|
||||
void ieee80211_sched_scan_stopped_work(struct work_struct *work);
|
||||
|
||||
/* off-channel helpers */
|
||||
/* off-channel/mgmt-tx */
|
||||
void ieee80211_offchannel_stop_vifs(struct ieee80211_local *local);
|
||||
void ieee80211_offchannel_return(struct ieee80211_local *local);
|
||||
void ieee80211_roc_setup(struct ieee80211_local *local);
|
||||
void ieee80211_start_next_roc(struct ieee80211_local *local);
|
||||
void ieee80211_roc_purge(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata);
|
||||
void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
|
||||
void ieee80211_sw_roc_work(struct work_struct *work);
|
||||
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);
|
||||
int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
|
||||
struct ieee80211_channel *chan,
|
||||
unsigned int duration, u64 *cookie);
|
||||
int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev, u64 cookie);
|
||||
int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
||||
struct cfg80211_mgmt_tx_params *params, u64 *cookie);
|
||||
int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev, u64 cookie);
|
||||
|
||||
/* channel switch handling */
|
||||
void ieee80211_csa_finalize_work(struct work_struct *work);
|
||||
|
@ -4,6 +4,7 @@
|
||||
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
|
||||
* Copyright 2007-2008 Johannes Berg <johannes@sipsolutions.net>
|
||||
* Copyright 2013-2014 Intel Mobile Communications GmbH
|
||||
* Copyright 2015 Intel Deutschland GmbH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
@ -320,7 +321,7 @@ static void ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
|
||||
return;
|
||||
|
||||
if (new)
|
||||
list_add_tail(&new->list, &sdata->key_list);
|
||||
list_add_tail_rcu(&new->list, &sdata->key_list);
|
||||
|
||||
WARN_ON(new && old && new->conf.keyidx != old->conf.keyidx);
|
||||
|
||||
@ -368,7 +369,7 @@ static void ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
|
||||
}
|
||||
|
||||
if (old)
|
||||
list_del(&old->list);
|
||||
list_del_rcu(&old->list);
|
||||
}
|
||||
|
||||
struct ieee80211_key *
|
||||
@ -592,8 +593,8 @@ static void ieee80211_key_destroy(struct ieee80211_key *key,
|
||||
return;
|
||||
|
||||
/*
|
||||
* Synchronize so the TX path can no longer be using
|
||||
* this key before we free/remove it.
|
||||
* Synchronize so the TX path and rcu key iterators
|
||||
* can no longer be using this key before we free/remove it.
|
||||
*/
|
||||
synchronize_net();
|
||||
|
||||
@ -744,6 +745,53 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw,
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_iter_keys);
|
||||
|
||||
static void
|
||||
_ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
|
||||
struct ieee80211_sub_if_data *sdata,
|
||||
void (*iter)(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
struct ieee80211_key_conf *key,
|
||||
void *data),
|
||||
void *iter_data)
|
||||
{
|
||||
struct ieee80211_key *key;
|
||||
|
||||
list_for_each_entry_rcu(key, &sdata->key_list, list) {
|
||||
/* skip keys of station in removal process */
|
||||
if (key->sta && key->sta->removed)
|
||||
continue;
|
||||
if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
|
||||
continue;
|
||||
|
||||
iter(hw, &sdata->vif,
|
||||
key->sta ? &key->sta->sta : NULL,
|
||||
&key->conf, iter_data);
|
||||
}
|
||||
}
|
||||
|
||||
void ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
void (*iter)(struct ieee80211_hw *hw,
|
||||
struct ieee80211_vif *vif,
|
||||
struct ieee80211_sta *sta,
|
||||
struct ieee80211_key_conf *key,
|
||||
void *data),
|
||||
void *iter_data)
|
||||
{
|
||||
struct ieee80211_local *local = hw_to_local(hw);
|
||||
struct ieee80211_sub_if_data *sdata;
|
||||
|
||||
if (vif) {
|
||||
sdata = vif_to_sdata(vif);
|
||||
_ieee80211_iter_keys_rcu(hw, sdata, iter, iter_data);
|
||||
} else {
|
||||
list_for_each_entry_rcu(sdata, &local->interfaces, list)
|
||||
_ieee80211_iter_keys_rcu(hw, sdata, iter, iter_data);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_iter_keys_rcu);
|
||||
|
||||
static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
|
||||
struct list_head *keys)
|
||||
{
|
||||
|
@ -541,7 +541,8 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
|
||||
NL80211_FEATURE_HT_IBSS |
|
||||
NL80211_FEATURE_VIF_TXPOWER |
|
||||
NL80211_FEATURE_MAC_ON_CREATE |
|
||||
NL80211_FEATURE_USERSPACE_MPM;
|
||||
NL80211_FEATURE_USERSPACE_MPM |
|
||||
NL80211_FEATURE_FULL_AP_CLIENT_STATE;
|
||||
|
||||
if (!ops->hw_scan)
|
||||
wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN |
|
||||
@ -1148,6 +1149,7 @@ void ieee80211_unregister_hw(struct ieee80211_hw *hw)
|
||||
|
||||
rtnl_unlock();
|
||||
|
||||
cancel_delayed_work_sync(&local->roc_work);
|
||||
cancel_work_sync(&local->restart_work);
|
||||
cancel_work_sync(&local->reconfig_filter);
|
||||
cancel_work_sync(&local->tdls_chsw_work);
|
||||
|
@ -968,8 +968,8 @@ int mesh_path_send_to_gates(struct mesh_path *mpath)
|
||||
copy = true;
|
||||
} else {
|
||||
mpath_dbg(sdata,
|
||||
"Not forwarding %p (flags %#x)\n",
|
||||
gate->mpath, gate->mpath->flags);
|
||||
"Not forwarding to %pM (flags %#x)\n",
|
||||
gate->mpath->dst, gate->mpath->flags);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1930,7 +1930,8 @@ static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
|
||||
|
||||
sdata->u.mgd.flags |= IEEE80211_STA_RESET_SIGNAL_AVE;
|
||||
|
||||
if (sdata->vif.p2p) {
|
||||
if (sdata->vif.p2p ||
|
||||
sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) {
|
||||
const struct cfg80211_bss_ies *ies;
|
||||
|
||||
rcu_read_lock();
|
||||
@ -3458,7 +3459,8 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
|
||||
}
|
||||
}
|
||||
|
||||
if (sdata->vif.p2p) {
|
||||
if (sdata->vif.p2p ||
|
||||
sdata->vif.driver_flags & IEEE80211_VIF_GET_NOA_UPDATE) {
|
||||
struct ieee80211_p2p_noa_attr noa = {};
|
||||
int ret;
|
||||
|
||||
|
@ -187,11 +187,80 @@ void ieee80211_offchannel_return(struct ieee80211_local *local)
|
||||
false);
|
||||
}
|
||||
|
||||
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc)
|
||||
static void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc)
|
||||
{
|
||||
if (roc->notified)
|
||||
/* was never transmitted */
|
||||
if (roc->frame) {
|
||||
cfg80211_mgmt_tx_status(&roc->sdata->wdev, roc->mgmt_tx_cookie,
|
||||
roc->frame->data, roc->frame->len,
|
||||
false, GFP_KERNEL);
|
||||
ieee80211_free_txskb(&roc->sdata->local->hw, roc->frame);
|
||||
}
|
||||
|
||||
if (!roc->mgmt_tx_cookie)
|
||||
cfg80211_remain_on_channel_expired(&roc->sdata->wdev,
|
||||
roc->cookie, roc->chan,
|
||||
GFP_KERNEL);
|
||||
|
||||
list_del(&roc->list);
|
||||
kfree(roc);
|
||||
}
|
||||
|
||||
static unsigned long ieee80211_end_finished_rocs(struct ieee80211_local *local,
|
||||
unsigned long now)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp;
|
||||
long remaining_dur_min = LONG_MAX;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
|
||||
long remaining;
|
||||
|
||||
if (!roc->started)
|
||||
break;
|
||||
|
||||
remaining = roc->start_time +
|
||||
msecs_to_jiffies(roc->duration) -
|
||||
now;
|
||||
|
||||
/* In case of HW ROC, it is possible that the HW finished the
|
||||
* ROC session before the actual requested time. In such a case
|
||||
* end the ROC session (disregarding the remaining time).
|
||||
*/
|
||||
if (roc->abort || roc->hw_begun || remaining <= 0)
|
||||
ieee80211_roc_notify_destroy(roc);
|
||||
else
|
||||
remaining_dur_min = min(remaining_dur_min, remaining);
|
||||
}
|
||||
|
||||
return remaining_dur_min;
|
||||
}
|
||||
|
||||
static bool ieee80211_recalc_sw_work(struct ieee80211_local *local,
|
||||
unsigned long now)
|
||||
{
|
||||
long dur = ieee80211_end_finished_rocs(local, now);
|
||||
|
||||
if (dur == LONG_MAX)
|
||||
return false;
|
||||
|
||||
mod_delayed_work(local->workqueue, &local->roc_work, dur);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc,
|
||||
unsigned long start_time)
|
||||
{
|
||||
struct ieee80211_local *local = roc->sdata->local;
|
||||
|
||||
if (WARN_ON(roc->notified))
|
||||
return;
|
||||
|
||||
roc->start_time = start_time;
|
||||
roc->started = true;
|
||||
roc->hw_begun = true;
|
||||
|
||||
if (roc->mgmt_tx_cookie) {
|
||||
if (!WARN_ON(!roc->frame)) {
|
||||
ieee80211_tx_skb_tid_band(roc->sdata, roc->frame, 7,
|
||||
@ -205,40 +274,26 @@ void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc)
|
||||
}
|
||||
|
||||
roc->notified = true;
|
||||
|
||||
if (!local->ops->remain_on_channel)
|
||||
ieee80211_recalc_sw_work(local, start_time);
|
||||
}
|
||||
|
||||
static void ieee80211_hw_roc_start(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_local *local =
|
||||
container_of(work, struct ieee80211_local, hw_roc_start);
|
||||
struct ieee80211_roc_work *roc, *dep, *tmp;
|
||||
struct ieee80211_roc_work *roc;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
if (list_empty(&local->roc_list))
|
||||
goto out_unlock;
|
||||
list_for_each_entry(roc, &local->roc_list, list) {
|
||||
if (!roc->started)
|
||||
break;
|
||||
|
||||
roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
|
||||
list);
|
||||
|
||||
if (!roc->started)
|
||||
goto out_unlock;
|
||||
|
||||
roc->hw_begun = true;
|
||||
roc->hw_start_time = local->hw_roc_start_time;
|
||||
|
||||
ieee80211_handle_roc_started(roc);
|
||||
list_for_each_entry_safe(dep, tmp, &roc->dependents, list) {
|
||||
ieee80211_handle_roc_started(dep);
|
||||
|
||||
if (dep->duration > roc->duration) {
|
||||
u32 dur = dep->duration;
|
||||
dep->duration = dur - roc->duration;
|
||||
roc->duration = dur;
|
||||
list_move(&dep->list, &roc->list);
|
||||
}
|
||||
ieee80211_handle_roc_started(roc, local->hw_roc_start_time);
|
||||
}
|
||||
out_unlock:
|
||||
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
||||
@ -254,34 +309,40 @@ void ieee80211_ready_on_channel(struct ieee80211_hw *hw)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee80211_ready_on_channel);
|
||||
|
||||
void ieee80211_start_next_roc(struct ieee80211_local *local)
|
||||
static void _ieee80211_start_next_roc(struct ieee80211_local *local)
|
||||
{
|
||||
struct ieee80211_roc_work *roc;
|
||||
struct ieee80211_roc_work *roc, *tmp;
|
||||
enum ieee80211_roc_type type;
|
||||
u32 min_dur, max_dur;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (list_empty(&local->roc_list)) {
|
||||
ieee80211_run_deferred_scan(local);
|
||||
if (WARN_ON(list_empty(&local->roc_list)))
|
||||
return;
|
||||
}
|
||||
|
||||
roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
|
||||
list);
|
||||
|
||||
if (WARN_ON_ONCE(roc->started))
|
||||
if (WARN_ON(roc->started))
|
||||
return;
|
||||
|
||||
min_dur = roc->duration;
|
||||
max_dur = roc->duration;
|
||||
type = roc->type;
|
||||
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp == roc)
|
||||
continue;
|
||||
if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
|
||||
break;
|
||||
max_dur = max(tmp->duration, max_dur);
|
||||
min_dur = min(tmp->duration, min_dur);
|
||||
type = max(tmp->type, type);
|
||||
}
|
||||
|
||||
if (local->ops->remain_on_channel) {
|
||||
int ret, duration = roc->duration;
|
||||
|
||||
/* XXX: duplicated, see ieee80211_start_roc_work() */
|
||||
if (!duration)
|
||||
duration = 10;
|
||||
|
||||
ret = drv_remain_on_channel(local, roc->sdata, roc->chan,
|
||||
duration, roc->type);
|
||||
|
||||
roc->started = true;
|
||||
int ret = drv_remain_on_channel(local, roc->sdata, roc->chan,
|
||||
max_dur, type);
|
||||
|
||||
if (ret) {
|
||||
wiphy_warn(local->hw.wiphy,
|
||||
@ -290,74 +351,24 @@ void ieee80211_start_next_roc(struct ieee80211_local *local)
|
||||
* queue the work struct again to avoid recursion
|
||||
* when multiple failures occur
|
||||
*/
|
||||
ieee80211_remain_on_channel_expired(&local->hw);
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp->sdata != roc->sdata ||
|
||||
tmp->chan != roc->chan)
|
||||
break;
|
||||
tmp->started = true;
|
||||
tmp->abort = true;
|
||||
}
|
||||
ieee80211_queue_work(&local->hw, &local->hw_roc_done);
|
||||
return;
|
||||
}
|
||||
|
||||
/* we'll notify about the start once the HW calls back */
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
|
||||
break;
|
||||
tmp->started = true;
|
||||
}
|
||||
} else {
|
||||
/* delay it a bit */
|
||||
ieee80211_queue_delayed_work(&local->hw, &roc->work,
|
||||
round_jiffies_relative(HZ/2));
|
||||
}
|
||||
}
|
||||
|
||||
void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free)
|
||||
{
|
||||
struct ieee80211_roc_work *dep, *tmp;
|
||||
|
||||
if (WARN_ON(roc->to_be_freed))
|
||||
return;
|
||||
|
||||
/* was never transmitted */
|
||||
if (roc->frame) {
|
||||
cfg80211_mgmt_tx_status(&roc->sdata->wdev,
|
||||
(unsigned long)roc->frame,
|
||||
roc->frame->data, roc->frame->len,
|
||||
false, GFP_KERNEL);
|
||||
kfree_skb(roc->frame);
|
||||
}
|
||||
|
||||
if (!roc->mgmt_tx_cookie)
|
||||
cfg80211_remain_on_channel_expired(&roc->sdata->wdev,
|
||||
roc->cookie, roc->chan,
|
||||
GFP_KERNEL);
|
||||
|
||||
list_for_each_entry_safe(dep, tmp, &roc->dependents, list)
|
||||
ieee80211_roc_notify_destroy(dep, true);
|
||||
|
||||
if (free)
|
||||
kfree(roc);
|
||||
else
|
||||
roc->to_be_freed = true;
|
||||
}
|
||||
|
||||
void ieee80211_sw_roc_work(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_roc_work *roc =
|
||||
container_of(work, struct ieee80211_roc_work, work.work);
|
||||
struct ieee80211_sub_if_data *sdata = roc->sdata;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
bool started, on_channel;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
if (roc->to_be_freed)
|
||||
goto out_unlock;
|
||||
|
||||
if (roc->abort)
|
||||
goto finish;
|
||||
|
||||
if (WARN_ON(list_empty(&local->roc_list)))
|
||||
goto out_unlock;
|
||||
|
||||
if (WARN_ON(roc != list_first_entry(&local->roc_list,
|
||||
struct ieee80211_roc_work,
|
||||
list)))
|
||||
goto out_unlock;
|
||||
|
||||
if (!roc->started) {
|
||||
struct ieee80211_roc_work *dep;
|
||||
|
||||
WARN_ON(local->use_chanctx);
|
||||
|
||||
/* If actually operating on the desired channel (with at least
|
||||
* 20 MHz channel width) don't stop all the operations but still
|
||||
* treat it as though the ROC operation started properly, so
|
||||
@ -377,27 +388,72 @@ void ieee80211_sw_roc_work(struct work_struct *work)
|
||||
ieee80211_hw_config(local, 0);
|
||||
}
|
||||
|
||||
/* tell userspace or send frame */
|
||||
ieee80211_handle_roc_started(roc);
|
||||
list_for_each_entry(dep, &roc->dependents, list)
|
||||
ieee80211_handle_roc_started(dep);
|
||||
ieee80211_queue_delayed_work(&local->hw, &local->roc_work,
|
||||
msecs_to_jiffies(min_dur));
|
||||
|
||||
/* if it was pure TX, just finish right away */
|
||||
if (!roc->duration)
|
||||
goto finish;
|
||||
/* tell userspace or send frame(s) */
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp->sdata != roc->sdata || tmp->chan != roc->chan)
|
||||
break;
|
||||
|
||||
roc->started = true;
|
||||
ieee80211_queue_delayed_work(&local->hw, &roc->work,
|
||||
msecs_to_jiffies(roc->duration));
|
||||
tmp->on_channel = roc->on_channel;
|
||||
ieee80211_handle_roc_started(tmp, jiffies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ieee80211_start_next_roc(struct ieee80211_local *local)
|
||||
{
|
||||
struct ieee80211_roc_work *roc;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (list_empty(&local->roc_list)) {
|
||||
ieee80211_run_deferred_scan(local);
|
||||
return;
|
||||
}
|
||||
|
||||
roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
|
||||
list);
|
||||
|
||||
if (WARN_ON_ONCE(roc->started))
|
||||
return;
|
||||
|
||||
if (local->ops->remain_on_channel) {
|
||||
_ieee80211_start_next_roc(local);
|
||||
} else {
|
||||
/* finish this ROC */
|
||||
finish:
|
||||
list_del(&roc->list);
|
||||
started = roc->started;
|
||||
on_channel = roc->on_channel;
|
||||
ieee80211_roc_notify_destroy(roc, !roc->abort);
|
||||
/* delay it a bit */
|
||||
ieee80211_queue_delayed_work(&local->hw, &local->roc_work,
|
||||
round_jiffies_relative(HZ/2));
|
||||
}
|
||||
}
|
||||
|
||||
if (started && !on_channel) {
|
||||
static void __ieee80211_roc_work(struct ieee80211_local *local)
|
||||
{
|
||||
struct ieee80211_roc_work *roc;
|
||||
bool on_channel;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (WARN_ON(local->ops->remain_on_channel))
|
||||
return;
|
||||
|
||||
roc = list_first_entry_or_null(&local->roc_list,
|
||||
struct ieee80211_roc_work, list);
|
||||
if (!roc)
|
||||
return;
|
||||
|
||||
if (!roc->started) {
|
||||
WARN_ON(local->use_chanctx);
|
||||
_ieee80211_start_next_roc(local);
|
||||
} else {
|
||||
on_channel = roc->on_channel;
|
||||
if (ieee80211_recalc_sw_work(local, jiffies))
|
||||
return;
|
||||
|
||||
/* careful - roc pointer became invalid during recalc */
|
||||
|
||||
if (!on_channel) {
|
||||
ieee80211_flush_queues(local, NULL, false);
|
||||
|
||||
local->tmp_channel = NULL;
|
||||
@ -407,14 +463,17 @@ void ieee80211_sw_roc_work(struct work_struct *work)
|
||||
}
|
||||
|
||||
ieee80211_recalc_idle(local);
|
||||
|
||||
if (started)
|
||||
ieee80211_start_next_roc(local);
|
||||
else if (list_empty(&local->roc_list))
|
||||
ieee80211_run_deferred_scan(local);
|
||||
ieee80211_start_next_roc(local);
|
||||
}
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
static void ieee80211_roc_work(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_local *local =
|
||||
container_of(work, struct ieee80211_local, roc_work.work);
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
__ieee80211_roc_work(local);
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
||||
@ -422,27 +481,14 @@ static void ieee80211_hw_roc_done(struct work_struct *work)
|
||||
{
|
||||
struct ieee80211_local *local =
|
||||
container_of(work, struct ieee80211_local, hw_roc_done);
|
||||
struct ieee80211_roc_work *roc;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
if (list_empty(&local->roc_list))
|
||||
goto out_unlock;
|
||||
|
||||
roc = list_first_entry(&local->roc_list, struct ieee80211_roc_work,
|
||||
list);
|
||||
|
||||
if (!roc->started)
|
||||
goto out_unlock;
|
||||
|
||||
list_del(&roc->list);
|
||||
|
||||
ieee80211_roc_notify_destroy(roc, true);
|
||||
ieee80211_end_finished_rocs(local, jiffies);
|
||||
|
||||
/* if there's another roc, start it now */
|
||||
ieee80211_start_next_roc(local);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
||||
@ -456,10 +502,472 @@ void ieee80211_remain_on_channel_expired(struct ieee80211_hw *hw)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ieee80211_remain_on_channel_expired);
|
||||
|
||||
static bool
|
||||
ieee80211_coalesce_hw_started_roc(struct ieee80211_local *local,
|
||||
struct ieee80211_roc_work *new_roc,
|
||||
struct ieee80211_roc_work *cur_roc)
|
||||
{
|
||||
unsigned long now = jiffies;
|
||||
unsigned long remaining;
|
||||
|
||||
if (WARN_ON(!cur_roc->started))
|
||||
return false;
|
||||
|
||||
/* if it was scheduled in the hardware, but not started yet,
|
||||
* we can only combine if the older one had a longer duration
|
||||
*/
|
||||
if (!cur_roc->hw_begun && new_roc->duration > cur_roc->duration)
|
||||
return false;
|
||||
|
||||
remaining = cur_roc->start_time +
|
||||
msecs_to_jiffies(cur_roc->duration) -
|
||||
now;
|
||||
|
||||
/* if it doesn't fit entirely, schedule a new one */
|
||||
if (new_roc->duration > jiffies_to_msecs(remaining))
|
||||
return false;
|
||||
|
||||
/* add just after the current one so we combine their finish later */
|
||||
list_add(&new_roc->list, &cur_roc->list);
|
||||
|
||||
/* if the existing one has already begun then let this one also
|
||||
* begin, otherwise they'll both be marked properly by the work
|
||||
* struct that runs once the driver notifies us of the beginning
|
||||
*/
|
||||
if (cur_roc->hw_begun)
|
||||
ieee80211_handle_roc_started(new_roc, now);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ieee80211_start_roc_work(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata,
|
||||
struct ieee80211_channel *channel,
|
||||
unsigned int duration, u64 *cookie,
|
||||
struct sk_buff *txskb,
|
||||
enum ieee80211_roc_type type)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp;
|
||||
bool queued = false, combine_started = true;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&local->mtx);
|
||||
|
||||
if (local->use_chanctx && !local->ops->remain_on_channel)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
roc = kzalloc(sizeof(*roc), GFP_KERNEL);
|
||||
if (!roc)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* If the duration is zero, then the driver
|
||||
* wouldn't actually do anything. Set it to
|
||||
* 10 for now.
|
||||
*
|
||||
* TODO: cancel the off-channel operation
|
||||
* when we get the SKB's TX status and
|
||||
* the wait time was zero before.
|
||||
*/
|
||||
if (!duration)
|
||||
duration = 10;
|
||||
|
||||
roc->chan = channel;
|
||||
roc->duration = duration;
|
||||
roc->req_duration = duration;
|
||||
roc->frame = txskb;
|
||||
roc->type = type;
|
||||
roc->sdata = sdata;
|
||||
|
||||
/*
|
||||
* cookie is either the roc cookie (for normal roc)
|
||||
* or the SKB (for mgmt TX)
|
||||
*/
|
||||
if (!txskb) {
|
||||
roc->cookie = ieee80211_mgmt_tx_cookie(local);
|
||||
*cookie = roc->cookie;
|
||||
} else {
|
||||
roc->mgmt_tx_cookie = *cookie;
|
||||
}
|
||||
|
||||
/* if there's no need to queue, handle it immediately */
|
||||
if (list_empty(&local->roc_list) &&
|
||||
!local->scanning && !ieee80211_is_radar_required(local)) {
|
||||
/* if not HW assist, just queue & schedule work */
|
||||
if (!local->ops->remain_on_channel) {
|
||||
list_add_tail(&roc->list, &local->roc_list);
|
||||
ieee80211_queue_delayed_work(&local->hw,
|
||||
&local->roc_work, 0);
|
||||
} else {
|
||||
/* otherwise actually kick it off here
|
||||
* (for error handling)
|
||||
*/
|
||||
ret = drv_remain_on_channel(local, sdata, channel,
|
||||
duration, type);
|
||||
if (ret) {
|
||||
kfree(roc);
|
||||
return ret;
|
||||
}
|
||||
roc->started = true;
|
||||
list_add_tail(&roc->list, &local->roc_list);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* otherwise handle queueing */
|
||||
|
||||
list_for_each_entry(tmp, &local->roc_list, list) {
|
||||
if (tmp->chan != channel || tmp->sdata != sdata)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Extend this ROC if possible: If it hasn't started, add
|
||||
* just after the new one to combine.
|
||||
*/
|
||||
if (!tmp->started) {
|
||||
list_add(&roc->list, &tmp->list);
|
||||
queued = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!combine_started)
|
||||
continue;
|
||||
|
||||
if (!local->ops->remain_on_channel) {
|
||||
/* If there's no hardware remain-on-channel, and
|
||||
* doing so won't push us over the maximum r-o-c
|
||||
* we allow, then we can just add the new one to
|
||||
* the list and mark it as having started now.
|
||||
* If it would push over the limit, don't try to
|
||||
* combine with other started ones (that haven't
|
||||
* been running as long) but potentially sort it
|
||||
* with others that had the same fate.
|
||||
*/
|
||||
unsigned long now = jiffies;
|
||||
u32 elapsed = jiffies_to_msecs(now - tmp->start_time);
|
||||
struct wiphy *wiphy = local->hw.wiphy;
|
||||
u32 max_roc = wiphy->max_remain_on_channel_duration;
|
||||
|
||||
if (elapsed + roc->duration > max_roc) {
|
||||
combine_started = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
list_add(&roc->list, &tmp->list);
|
||||
queued = true;
|
||||
roc->on_channel = tmp->on_channel;
|
||||
ieee80211_handle_roc_started(roc, now);
|
||||
break;
|
||||
}
|
||||
|
||||
queued = ieee80211_coalesce_hw_started_roc(local, roc, tmp);
|
||||
if (queued)
|
||||
break;
|
||||
/* if it wasn't queued, perhaps it can be combined with
|
||||
* another that also couldn't get combined previously,
|
||||
* but no need to check for already started ones, since
|
||||
* that can't work.
|
||||
*/
|
||||
combine_started = false;
|
||||
}
|
||||
|
||||
if (!queued)
|
||||
list_add_tail(&roc->list, &local->roc_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev,
|
||||
struct ieee80211_channel *chan,
|
||||
unsigned int duration, u64 *cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
ret = ieee80211_start_roc_work(local, sdata, chan,
|
||||
duration, cookie, NULL,
|
||||
IEEE80211_ROC_TYPE_NORMAL);
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ieee80211_cancel_roc(struct ieee80211_local *local,
|
||||
u64 cookie, bool mgmt_tx)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp, *found = NULL;
|
||||
int ret;
|
||||
|
||||
if (!cookie)
|
||||
return -ENOENT;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
|
||||
if (!mgmt_tx && roc->cookie != cookie)
|
||||
continue;
|
||||
else if (mgmt_tx && roc->mgmt_tx_cookie != cookie)
|
||||
continue;
|
||||
|
||||
found = roc;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (!found->started) {
|
||||
ieee80211_roc_notify_destroy(found);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (local->ops->remain_on_channel) {
|
||||
ret = drv_cancel_remain_on_channel(local);
|
||||
if (WARN_ON_ONCE(ret)) {
|
||||
mutex_unlock(&local->mtx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* TODO:
|
||||
* if multiple items were combined here then we really shouldn't
|
||||
* cancel them all - we should wait for as much time as needed
|
||||
* for the longest remaining one, and only then cancel ...
|
||||
*/
|
||||
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
|
||||
if (!roc->started)
|
||||
break;
|
||||
if (roc == found)
|
||||
found = NULL;
|
||||
ieee80211_roc_notify_destroy(roc);
|
||||
}
|
||||
|
||||
/* that really must not happen - it was started */
|
||||
WARN_ON(found);
|
||||
|
||||
ieee80211_start_next_roc(local);
|
||||
} else {
|
||||
/* go through work struct to return to the operating channel */
|
||||
found->abort = true;
|
||||
mod_delayed_work(local->workqueue, &local->roc_work, 0);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev, u64 cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
|
||||
return ieee80211_cancel_roc(local, cookie, false);
|
||||
}
|
||||
|
||||
int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
|
||||
struct cfg80211_mgmt_tx_params *params, u64 *cookie)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
struct sk_buff *skb;
|
||||
struct sta_info *sta;
|
||||
const struct ieee80211_mgmt *mgmt = (void *)params->buf;
|
||||
bool need_offchan = false;
|
||||
u32 flags;
|
||||
int ret;
|
||||
u8 *data;
|
||||
|
||||
if (params->dont_wait_for_ack)
|
||||
flags = IEEE80211_TX_CTL_NO_ACK;
|
||||
else
|
||||
flags = IEEE80211_TX_INTFL_NL80211_FRAME_TX |
|
||||
IEEE80211_TX_CTL_REQ_TX_STATUS;
|
||||
|
||||
if (params->no_cck)
|
||||
flags |= IEEE80211_TX_CTL_NO_CCK_RATE;
|
||||
|
||||
switch (sdata->vif.type) {
|
||||
case NL80211_IFTYPE_ADHOC:
|
||||
if (!sdata->vif.bss_conf.ibss_joined)
|
||||
need_offchan = true;
|
||||
/* fall through */
|
||||
#ifdef CONFIG_MAC80211_MESH
|
||||
case NL80211_IFTYPE_MESH_POINT:
|
||||
if (ieee80211_vif_is_mesh(&sdata->vif) &&
|
||||
!sdata->u.mesh.mesh_id_len)
|
||||
need_offchan = true;
|
||||
/* fall through */
|
||||
#endif
|
||||
case NL80211_IFTYPE_AP:
|
||||
case NL80211_IFTYPE_AP_VLAN:
|
||||
case NL80211_IFTYPE_P2P_GO:
|
||||
if (sdata->vif.type != NL80211_IFTYPE_ADHOC &&
|
||||
!ieee80211_vif_is_mesh(&sdata->vif) &&
|
||||
!rcu_access_pointer(sdata->bss->beacon))
|
||||
need_offchan = true;
|
||||
if (!ieee80211_is_action(mgmt->frame_control) ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_PUBLIC ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_SELF_PROTECTED ||
|
||||
mgmt->u.action.category == WLAN_CATEGORY_SPECTRUM_MGMT)
|
||||
break;
|
||||
rcu_read_lock();
|
||||
sta = sta_info_get(sdata, mgmt->da);
|
||||
rcu_read_unlock();
|
||||
if (!sta)
|
||||
return -ENOLINK;
|
||||
break;
|
||||
case NL80211_IFTYPE_STATION:
|
||||
case NL80211_IFTYPE_P2P_CLIENT:
|
||||
sdata_lock(sdata);
|
||||
if (!sdata->u.mgd.associated ||
|
||||
(params->offchan && params->wait &&
|
||||
local->ops->remain_on_channel &&
|
||||
memcmp(sdata->u.mgd.associated->bssid,
|
||||
mgmt->bssid, ETH_ALEN)))
|
||||
need_offchan = true;
|
||||
sdata_unlock(sdata);
|
||||
break;
|
||||
case NL80211_IFTYPE_P2P_DEVICE:
|
||||
need_offchan = true;
|
||||
break;
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* configurations requiring offchan cannot work if no channel has been
|
||||
* specified
|
||||
*/
|
||||
if (need_offchan && !params->chan)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
|
||||
/* Check if the operating channel is the requested channel */
|
||||
if (!need_offchan) {
|
||||
struct ieee80211_chanctx_conf *chanctx_conf;
|
||||
|
||||
rcu_read_lock();
|
||||
chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
|
||||
|
||||
if (chanctx_conf) {
|
||||
need_offchan = params->chan &&
|
||||
(params->chan !=
|
||||
chanctx_conf->def.chan);
|
||||
} else if (!params->chan) {
|
||||
ret = -EINVAL;
|
||||
rcu_read_unlock();
|
||||
goto out_unlock;
|
||||
} else {
|
||||
need_offchan = true;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
if (need_offchan && !params->offchan) {
|
||||
ret = -EBUSY;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
skb = dev_alloc_skb(local->hw.extra_tx_headroom + params->len);
|
||||
if (!skb) {
|
||||
ret = -ENOMEM;
|
||||
goto out_unlock;
|
||||
}
|
||||
skb_reserve(skb, local->hw.extra_tx_headroom);
|
||||
|
||||
data = skb_put(skb, params->len);
|
||||
memcpy(data, params->buf, params->len);
|
||||
|
||||
/* Update CSA counters */
|
||||
if (sdata->vif.csa_active &&
|
||||
(sdata->vif.type == NL80211_IFTYPE_AP ||
|
||||
sdata->vif.type == NL80211_IFTYPE_MESH_POINT ||
|
||||
sdata->vif.type == NL80211_IFTYPE_ADHOC) &&
|
||||
params->n_csa_offsets) {
|
||||
int i;
|
||||
struct beacon_data *beacon = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
||||
beacon = rcu_dereference(sdata->u.ap.beacon);
|
||||
else if (sdata->vif.type == NL80211_IFTYPE_ADHOC)
|
||||
beacon = rcu_dereference(sdata->u.ibss.presp);
|
||||
else if (ieee80211_vif_is_mesh(&sdata->vif))
|
||||
beacon = rcu_dereference(sdata->u.mesh.beacon);
|
||||
|
||||
if (beacon)
|
||||
for (i = 0; i < params->n_csa_offsets; i++)
|
||||
data[params->csa_offsets[i]] =
|
||||
beacon->csa_current_counter;
|
||||
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
IEEE80211_SKB_CB(skb)->flags = flags;
|
||||
|
||||
skb->dev = sdata->dev;
|
||||
|
||||
if (!params->dont_wait_for_ack) {
|
||||
/* make a copy to preserve the frame contents
|
||||
* in case of encryption.
|
||||
*/
|
||||
ret = ieee80211_attach_ack_skb(local, skb, cookie, GFP_KERNEL);
|
||||
if (ret) {
|
||||
kfree_skb(skb);
|
||||
goto out_unlock;
|
||||
}
|
||||
} else {
|
||||
/* Assign a dummy non-zero cookie, it's not sent to
|
||||
* userspace in this case but we rely on its value
|
||||
* internally in the need_offchan case to distinguish
|
||||
* mgmt-tx from remain-on-channel.
|
||||
*/
|
||||
*cookie = 0xffffffff;
|
||||
}
|
||||
|
||||
if (!need_offchan) {
|
||||
ieee80211_tx_skb(sdata, skb);
|
||||
ret = 0;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_CTL_TX_OFFCHAN |
|
||||
IEEE80211_TX_INTFL_OFFCHAN_TX_OK;
|
||||
if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL))
|
||||
IEEE80211_SKB_CB(skb)->hw_queue =
|
||||
local->hw.offchannel_tx_hw_queue;
|
||||
|
||||
/* This will handle all kinds of coalescing and immediate TX */
|
||||
ret = ieee80211_start_roc_work(local, sdata, params->chan,
|
||||
params->wait, cookie, skb,
|
||||
IEEE80211_ROC_TYPE_MGMT_TX);
|
||||
if (ret)
|
||||
ieee80211_free_txskb(&local->hw, skb);
|
||||
out_unlock:
|
||||
mutex_unlock(&local->mtx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ieee80211_mgmt_tx_cancel_wait(struct wiphy *wiphy,
|
||||
struct wireless_dev *wdev, u64 cookie)
|
||||
{
|
||||
struct ieee80211_local *local = wiphy_priv(wiphy);
|
||||
|
||||
return ieee80211_cancel_roc(local, cookie, true);
|
||||
}
|
||||
|
||||
void ieee80211_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);
|
||||
INIT_DELAYED_WORK(&local->roc_work, ieee80211_roc_work);
|
||||
INIT_LIST_HEAD(&local->roc_list);
|
||||
}
|
||||
|
||||
@ -467,36 +975,27 @@ void ieee80211_roc_purge(struct ieee80211_local *local,
|
||||
struct ieee80211_sub_if_data *sdata)
|
||||
{
|
||||
struct ieee80211_roc_work *roc, *tmp;
|
||||
LIST_HEAD(tmp_list);
|
||||
bool work_to_do = false;
|
||||
|
||||
mutex_lock(&local->mtx);
|
||||
list_for_each_entry_safe(roc, tmp, &local->roc_list, list) {
|
||||
if (sdata && roc->sdata != sdata)
|
||||
continue;
|
||||
|
||||
if (roc->started && local->ops->remain_on_channel) {
|
||||
/* can race, so ignore return value */
|
||||
drv_cancel_remain_on_channel(local);
|
||||
}
|
||||
|
||||
list_move_tail(&roc->list, &tmp_list);
|
||||
roc->abort = true;
|
||||
}
|
||||
mutex_unlock(&local->mtx);
|
||||
|
||||
list_for_each_entry_safe(roc, tmp, &tmp_list, list) {
|
||||
if (local->ops->remain_on_channel) {
|
||||
list_del(&roc->list);
|
||||
ieee80211_roc_notify_destroy(roc, true);
|
||||
if (roc->started) {
|
||||
if (local->ops->remain_on_channel) {
|
||||
/* can race, so ignore return value */
|
||||
drv_cancel_remain_on_channel(local);
|
||||
ieee80211_roc_notify_destroy(roc);
|
||||
} else {
|
||||
roc->abort = true;
|
||||
work_to_do = true;
|
||||
}
|
||||
} else {
|
||||
ieee80211_queue_delayed_work(&local->hw, &roc->work, 0);
|
||||
|
||||
/* work will clean up etc */
|
||||
flush_delayed_work(&roc->work);
|
||||
WARN_ON(!roc->to_be_freed);
|
||||
kfree(roc);
|
||||
ieee80211_roc_notify_destroy(roc);
|
||||
}
|
||||
}
|
||||
|
||||
WARN_ON_ONCE(!list_empty(&tmp_list));
|
||||
if (work_to_do)
|
||||
__ieee80211_roc_work(local);
|
||||
mutex_unlock(&local->mtx);
|
||||
}
|
||||
|
@ -661,8 +661,7 @@ static void ieee80211_parse_qos(struct ieee80211_rx_data *rx)
|
||||
static void ieee80211_verify_alignment(struct ieee80211_rx_data *rx)
|
||||
{
|
||||
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
||||
WARN_ONCE((unsigned long)rx->skb->data & 1,
|
||||
"unaligned packet at 0x%p\n", rx->skb->data);
|
||||
WARN_ON_ONCE((unsigned long)rx->skb->data & 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Copyright 2002-2005, Instant802 Networks, Inc.
|
||||
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
|
||||
* Copyright 2013-2014 Intel Mobile Communications GmbH
|
||||
* Copyright (C) 2015 Intel Deutschland GmbH
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
@ -435,6 +436,19 @@ static int sta_info_insert_check(struct sta_info *sta)
|
||||
is_multicast_ether_addr(sta->sta.addr)))
|
||||
return -EINVAL;
|
||||
|
||||
/* Strictly speaking this isn't necessary as we hold the mutex, but
|
||||
* the rhashtable code can't really deal with that distinction. We
|
||||
* do require the mutex for correctness though.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
lockdep_assert_held(&sdata->local->sta_mtx);
|
||||
if (ieee80211_hw_check(&sdata->local->hw, NEEDS_UNIQUE_STA_ADDR) &&
|
||||
ieee80211_find_sta_by_ifaddr(&sdata->local->hw, sta->addr, NULL)) {
|
||||
rcu_read_unlock();
|
||||
return -ENOTUNIQ;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -554,14 +568,15 @@ int sta_info_insert_rcu(struct sta_info *sta) __acquires(RCU)
|
||||
|
||||
might_sleep();
|
||||
|
||||
mutex_lock(&local->sta_mtx);
|
||||
|
||||
err = sta_info_insert_check(sta);
|
||||
if (err) {
|
||||
mutex_unlock(&local->sta_mtx);
|
||||
rcu_read_lock();
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
mutex_lock(&local->sta_mtx);
|
||||
|
||||
err = sta_info_insert_finish(sta);
|
||||
if (err)
|
||||
goto out_free;
|
||||
@ -868,6 +883,7 @@ static int __must_check __sta_info_destroy_part1(struct sta_info *sta)
|
||||
}
|
||||
|
||||
list_del_rcu(&sta->list);
|
||||
sta->removed = true;
|
||||
|
||||
drv_sta_pre_rcu_remove(local, sta->sdata, sta);
|
||||
|
||||
@ -1230,11 +1246,11 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
|
||||
ieee80211_check_fast_xmit(sta);
|
||||
}
|
||||
|
||||
static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,
|
||||
struct sta_info *sta, int tid,
|
||||
static void ieee80211_send_null_response(struct sta_info *sta, int tid,
|
||||
enum ieee80211_frame_release_type reason,
|
||||
bool call_driver)
|
||||
bool call_driver, bool more_data)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
struct ieee80211_qos_hdr *nullfunc;
|
||||
struct sk_buff *skb;
|
||||
@ -1274,9 +1290,13 @@ static void ieee80211_send_null_response(struct ieee80211_sub_if_data *sdata,
|
||||
if (qos) {
|
||||
nullfunc->qos_ctrl = cpu_to_le16(tid);
|
||||
|
||||
if (reason == IEEE80211_FRAME_RELEASE_UAPSD)
|
||||
if (reason == IEEE80211_FRAME_RELEASE_UAPSD) {
|
||||
nullfunc->qos_ctrl |=
|
||||
cpu_to_le16(IEEE80211_QOS_CTL_EOSP);
|
||||
if (more_data)
|
||||
nullfunc->frame_control |=
|
||||
cpu_to_le16(IEEE80211_FCTL_MOREDATA);
|
||||
}
|
||||
}
|
||||
|
||||
info = IEEE80211_SKB_CB(skb);
|
||||
@ -1323,22 +1343,48 @@ static int find_highest_prio_tid(unsigned long tids)
|
||||
return fls(tids) - 1;
|
||||
}
|
||||
|
||||
/* Indicates if the MORE_DATA bit should be set in the last
|
||||
* frame obtained by ieee80211_sta_ps_get_frames.
|
||||
* Note that driver_release_tids is relevant only if
|
||||
* reason = IEEE80211_FRAME_RELEASE_PSPOLL
|
||||
*/
|
||||
static bool
|
||||
ieee80211_sta_ps_more_data(struct sta_info *sta, u8 ignored_acs,
|
||||
enum ieee80211_frame_release_type reason,
|
||||
unsigned long driver_release_tids)
|
||||
{
|
||||
int ac;
|
||||
|
||||
/* If the driver has data on more than one TID then
|
||||
* certainly there's more data if we release just a
|
||||
* single frame now (from a single TID). This will
|
||||
* only happen for PS-Poll.
|
||||
*/
|
||||
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL &&
|
||||
hweight16(driver_release_tids) > 1)
|
||||
return true;
|
||||
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
|
||||
if (ignored_acs & BIT(ac))
|
||||
continue;
|
||||
|
||||
if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
|
||||
!skb_queue_empty(&sta->ps_tx_buf[ac]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
int n_frames, u8 ignored_acs,
|
||||
enum ieee80211_frame_release_type reason)
|
||||
ieee80211_sta_ps_get_frames(struct sta_info *sta, int n_frames, u8 ignored_acs,
|
||||
enum ieee80211_frame_release_type reason,
|
||||
struct sk_buff_head *frames,
|
||||
unsigned long *driver_release_tids)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
bool more_data = false;
|
||||
int ac;
|
||||
unsigned long driver_release_tids = 0;
|
||||
struct sk_buff_head frames;
|
||||
|
||||
/* Service or PS-Poll period starts */
|
||||
set_sta_flag(sta, WLAN_STA_SP);
|
||||
|
||||
__skb_queue_head_init(&frames);
|
||||
|
||||
/* Get response frame(s) and more data bit for the last one. */
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
|
||||
@ -1352,26 +1398,13 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
/* if we already have frames from software, then we can't also
|
||||
* release from hardware queues
|
||||
*/
|
||||
if (skb_queue_empty(&frames)) {
|
||||
driver_release_tids |= sta->driver_buffered_tids & tids;
|
||||
driver_release_tids |= sta->txq_buffered_tids & tids;
|
||||
if (skb_queue_empty(frames)) {
|
||||
*driver_release_tids |=
|
||||
sta->driver_buffered_tids & tids;
|
||||
*driver_release_tids |= sta->txq_buffered_tids & tids;
|
||||
}
|
||||
|
||||
if (driver_release_tids) {
|
||||
/* If the driver has data on more than one TID then
|
||||
* certainly there's more data if we release just a
|
||||
* single frame now (from a single TID). This will
|
||||
* only happen for PS-Poll.
|
||||
*/
|
||||
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL &&
|
||||
hweight16(driver_release_tids) > 1) {
|
||||
more_data = true;
|
||||
driver_release_tids =
|
||||
BIT(find_highest_prio_tid(
|
||||
driver_release_tids));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (!*driver_release_tids) {
|
||||
struct sk_buff *skb;
|
||||
|
||||
while (n_frames > 0) {
|
||||
@ -1385,20 +1418,44 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
if (!skb)
|
||||
break;
|
||||
n_frames--;
|
||||
__skb_queue_tail(&frames, skb);
|
||||
__skb_queue_tail(frames, skb);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have more frames buffered on this AC, then set the
|
||||
* more-data bit and abort the loop since we can't send more
|
||||
* data from other ACs before the buffered frames from this.
|
||||
/* If we have more frames buffered on this AC, then abort the
|
||||
* loop since we can't send more data from other ACs before
|
||||
* the buffered frames from this.
|
||||
*/
|
||||
if (!skb_queue_empty(&sta->tx_filtered[ac]) ||
|
||||
!skb_queue_empty(&sta->ps_tx_buf[ac])) {
|
||||
more_data = true;
|
||||
!skb_queue_empty(&sta->ps_tx_buf[ac]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
int n_frames, u8 ignored_acs,
|
||||
enum ieee80211_frame_release_type reason)
|
||||
{
|
||||
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
||||
struct ieee80211_local *local = sdata->local;
|
||||
unsigned long driver_release_tids = 0;
|
||||
struct sk_buff_head frames;
|
||||
bool more_data;
|
||||
|
||||
/* Service or PS-Poll period starts */
|
||||
set_sta_flag(sta, WLAN_STA_SP);
|
||||
|
||||
__skb_queue_head_init(&frames);
|
||||
|
||||
ieee80211_sta_ps_get_frames(sta, n_frames, ignored_acs, reason,
|
||||
&frames, &driver_release_tids);
|
||||
|
||||
more_data = ieee80211_sta_ps_more_data(sta, ignored_acs, reason, driver_release_tids);
|
||||
|
||||
if (reason == IEEE80211_FRAME_RELEASE_PSPOLL)
|
||||
driver_release_tids =
|
||||
BIT(find_highest_prio_tid(driver_release_tids));
|
||||
|
||||
if (skb_queue_empty(&frames) && !driver_release_tids) {
|
||||
int tid;
|
||||
@ -1421,7 +1478,7 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
/* This will evaluate to 1, 3, 5 or 7. */
|
||||
tid = 7 - ((ffs(~ignored_acs) - 1) << 1);
|
||||
|
||||
ieee80211_send_null_response(sdata, sta, tid, reason, true);
|
||||
ieee80211_send_null_response(sta, tid, reason, true, false);
|
||||
} else if (!driver_release_tids) {
|
||||
struct sk_buff_head pending;
|
||||
struct sk_buff *skb;
|
||||
@ -1521,8 +1578,8 @@ ieee80211_sta_ps_deliver_response(struct sta_info *sta,
|
||||
|
||||
if (need_null)
|
||||
ieee80211_send_null_response(
|
||||
sdata, sta, find_highest_prio_tid(tids),
|
||||
reason, false);
|
||||
sta, find_highest_prio_tid(tids),
|
||||
reason, false, false);
|
||||
|
||||
sta_info_recalc_tim(sta);
|
||||
} else {
|
||||
@ -1660,6 +1717,22 @@ void ieee80211_sta_eosp(struct ieee80211_sta *pubsta)
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_sta_eosp);
|
||||
|
||||
void ieee80211_send_eosp_nullfunc(struct ieee80211_sta *pubsta, int tid)
|
||||
{
|
||||
struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
|
||||
enum ieee80211_frame_release_type reason;
|
||||
bool more_data;
|
||||
|
||||
trace_api_send_eosp_nullfunc(sta->local, pubsta, tid);
|
||||
|
||||
reason = IEEE80211_FRAME_RELEASE_UAPSD;
|
||||
more_data = ieee80211_sta_ps_more_data(sta, ~sta->sta.uapsd_queues,
|
||||
reason, 0);
|
||||
|
||||
ieee80211_send_null_response(sta, tid, reason, false, more_data);
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_send_eosp_nullfunc);
|
||||
|
||||
void ieee80211_sta_set_buffered(struct ieee80211_sta *pubsta,
|
||||
u8 tid, bool buffered)
|
||||
{
|
||||
|
@ -367,6 +367,7 @@ DECLARE_EWMA(signal, 1024, 8)
|
||||
* @mesh: mesh STA information
|
||||
* @debugfs: debug filesystem info
|
||||
* @dead: set to true when sta is unlinked
|
||||
* @removed: set to true when sta is being removed from sta_list
|
||||
* @uploaded: set to true when sta is uploaded to the driver
|
||||
* @sta: station information we share with the driver
|
||||
* @sta_state: duplicates information about station state (for debug)
|
||||
@ -412,6 +413,7 @@ struct sta_info {
|
||||
u16 listen_interval;
|
||||
|
||||
bool dead;
|
||||
bool removed;
|
||||
|
||||
bool uploaded;
|
||||
|
||||
|
@ -2027,6 +2027,31 @@ TRACE_EVENT(api_eosp,
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(api_send_eosp_nullfunc,
|
||||
TP_PROTO(struct ieee80211_local *local,
|
||||
struct ieee80211_sta *sta,
|
||||
u8 tid),
|
||||
|
||||
TP_ARGS(local, sta, tid),
|
||||
|
||||
TP_STRUCT__entry(
|
||||
LOCAL_ENTRY
|
||||
STA_ENTRY
|
||||
__field(u8, tid)
|
||||
),
|
||||
|
||||
TP_fast_assign(
|
||||
LOCAL_ASSIGN;
|
||||
STA_ASSIGN;
|
||||
__entry->tid = tid;
|
||||
),
|
||||
|
||||
TP_printk(
|
||||
LOCAL_PR_FMT STA_PR_FMT " tid:%d",
|
||||
LOCAL_PR_ARG, STA_PR_ARG, __entry->tid
|
||||
)
|
||||
);
|
||||
|
||||
TRACE_EVENT(api_sta_set_buffered,
|
||||
TP_PROTO(struct ieee80211_local *local,
|
||||
struct ieee80211_sta *sta,
|
||||
|
@ -1431,7 +1431,7 @@ static bool __ieee80211_tx(struct ieee80211_local *local,
|
||||
info->hw_queue =
|
||||
vif->hw_queue[skb_get_queue_mapping(skb)];
|
||||
} else if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL)) {
|
||||
dev_kfree_skb(skb);
|
||||
ieee80211_purge_tx_queue(&local->hw, skbs);
|
||||
return true;
|
||||
} else
|
||||
vif = NULL;
|
||||
|
@ -288,10 +288,13 @@ static void __ieee80211_wake_queue(struct ieee80211_hw *hw, int queue,
|
||||
if (!test_bit(reason, &local->queue_stop_reasons[queue]))
|
||||
return;
|
||||
|
||||
if (!refcounted)
|
||||
if (!refcounted) {
|
||||
local->q_stop_reasons[queue][reason] = 0;
|
||||
else
|
||||
} else {
|
||||
local->q_stop_reasons[queue][reason]--;
|
||||
if (WARN_ON(local->q_stop_reasons[queue][reason] < 0))
|
||||
local->q_stop_reasons[queue][reason] = 0;
|
||||
}
|
||||
|
||||
if (local->q_stop_reasons[queue][reason] == 0)
|
||||
__clear_bit(reason, &local->queue_stop_reasons[queue]);
|
||||
|
@ -416,13 +416,6 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
|
||||
void cfg80211_process_rdev_events(struct cfg80211_registered_device *rdev);
|
||||
void cfg80211_process_wdev_events(struct wireless_dev *wdev);
|
||||
|
||||
int cfg80211_can_use_iftype_chan(struct cfg80211_registered_device *rdev,
|
||||
struct wireless_dev *wdev,
|
||||
enum nl80211_iftype iftype,
|
||||
struct ieee80211_channel *chan,
|
||||
enum cfg80211_chan_mode chanmode,
|
||||
u8 radar_detect);
|
||||
|
||||
/**
|
||||
* cfg80211_chandef_dfs_usable - checks if chandef is DFS usable
|
||||
* @wiphy: the wiphy to validate against
|
||||
|
@ -311,8 +311,8 @@ static int lib80211_ccmp_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
|
||||
}
|
||||
keyidx >>= 6;
|
||||
if (key->key_idx != keyidx) {
|
||||
printk(KERN_DEBUG "CCMP: RX tkey->key_idx=%d frame "
|
||||
"keyidx=%d priv=%p\n", key->key_idx, keyidx, priv);
|
||||
net_dbg_ratelimited("CCMP: RX tkey->key_idx=%d frame keyidx=%d\n",
|
||||
key->key_idx, keyidx);
|
||||
return -6;
|
||||
}
|
||||
if (!key->key_set) {
|
||||
|
@ -434,8 +434,8 @@ static int lib80211_tkip_decrypt(struct sk_buff *skb, int hdr_len, void *priv)
|
||||
}
|
||||
keyidx >>= 6;
|
||||
if (tkey->key_idx != keyidx) {
|
||||
printk(KERN_DEBUG "TKIP: RX tkey->key_idx=%d frame "
|
||||
"keyidx=%d priv=%p\n", tkey->key_idx, keyidx, priv);
|
||||
net_dbg_ratelimited("TKIP: RX tkey->key_idx=%d frame keyidx=%d\n",
|
||||
tkey->key_idx, keyidx);
|
||||
return -6;
|
||||
}
|
||||
if (!tkey->key_set) {
|
||||
|
@ -4256,8 +4256,8 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
|
||||
* station. Include these parameters here and will check them in
|
||||
* cfg80211_check_station_change().
|
||||
*/
|
||||
if (info->attrs[NL80211_ATTR_PEER_AID])
|
||||
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_PEER_AID]);
|
||||
if (info->attrs[NL80211_ATTR_STA_AID])
|
||||
params.aid = nla_get_u16(info->attrs[NL80211_ATTR_STA_AID]);
|
||||
|
||||
if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
|
||||
params.listen_interval =
|
||||
@ -4359,6 +4359,8 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
|
||||
struct net_device *dev = info->user_ptr[1];
|
||||
struct station_parameters params;
|
||||
u8 *mac_addr = NULL;
|
||||
u32 auth_assoc = BIT(NL80211_STA_FLAG_AUTHENTICATED) |
|
||||
BIT(NL80211_STA_FLAG_ASSOCIATED);
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
|
||||
@ -4470,11 +4472,24 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
|
||||
/* allow authenticated/associated only if driver handles it */
|
||||
if (!(rdev->wiphy.features &
|
||||
NL80211_FEATURE_FULL_AP_CLIENT_STATE) &&
|
||||
params.sta_flags_mask &
|
||||
(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
|
||||
BIT(NL80211_STA_FLAG_ASSOCIATED)))
|
||||
params.sta_flags_mask & auth_assoc)
|
||||
return -EINVAL;
|
||||
|
||||
/* Older userspace, or userspace wanting to be compatible with
|
||||
* !NL80211_FEATURE_FULL_AP_CLIENT_STATE, will not set the auth
|
||||
* and assoc flags in the mask, but assumes the station will be
|
||||
* added as associated anyway since this was the required driver
|
||||
* behaviour before NL80211_FEATURE_FULL_AP_CLIENT_STATE was
|
||||
* introduced.
|
||||
* In order to not bother drivers with this quirk in the API
|
||||
* set the flags in both the mask and set for new stations in
|
||||
* this case.
|
||||
*/
|
||||
if (!(params.sta_flags_mask & auth_assoc)) {
|
||||
params.sta_flags_mask |= auth_assoc;
|
||||
params.sta_flags_set |= auth_assoc;
|
||||
}
|
||||
|
||||
/* must be last in here for error handling */
|
||||
params.vlan = get_vlan(info, rdev);
|
||||
if (IS_ERR(params.vlan))
|
||||
@ -5997,6 +6012,24 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
|
||||
return err;
|
||||
}
|
||||
|
||||
static int nl80211_abort_scan(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct cfg80211_registered_device *rdev = info->user_ptr[0];
|
||||
struct wireless_dev *wdev = info->user_ptr[1];
|
||||
|
||||
if (!rdev->ops->abort_scan)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (rdev->scan_msg)
|
||||
return 0;
|
||||
|
||||
if (!rdev->scan_req)
|
||||
return -ENOENT;
|
||||
|
||||
rdev_abort_scan(rdev, wdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
nl80211_parse_sched_scan_plans(struct wiphy *wiphy, int n_plans,
|
||||
struct cfg80211_sched_scan_request *request,
|
||||
@ -6507,8 +6540,7 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
|
||||
if (WARN_ON(!cac_time_ms))
|
||||
cac_time_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
|
||||
|
||||
err = rdev->ops->start_radar_detection(&rdev->wiphy, dev, &chandef,
|
||||
cac_time_ms);
|
||||
err = rdev_start_radar_detection(rdev, dev, &chandef, cac_time_ms);
|
||||
if (!err) {
|
||||
wdev->chandef = chandef;
|
||||
wdev->cac_started = true;
|
||||
@ -7571,7 +7603,7 @@ static int nl80211_set_mcast_rate(struct sk_buff *skb, struct genl_info *info)
|
||||
if (!nl80211_parse_mcast_rate(rdev, mcast_rate, nla_rate))
|
||||
return -EINVAL;
|
||||
|
||||
err = rdev->ops->set_mcast_rate(&rdev->wiphy, dev, mcast_rate);
|
||||
err = rdev_set_mcast_rate(rdev, dev, mcast_rate);
|
||||
|
||||
return err;
|
||||
}
|
||||
@ -9716,7 +9748,7 @@ static int nl80211_set_coalesce(struct sk_buff *skb, struct genl_info *info)
|
||||
|
||||
if (!info->attrs[NL80211_ATTR_COALESCE_RULE]) {
|
||||
cfg80211_rdev_free_coalesce(rdev);
|
||||
rdev->ops->set_coalesce(&rdev->wiphy, NULL);
|
||||
rdev_set_coalesce(rdev, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -9744,7 +9776,7 @@ static int nl80211_set_coalesce(struct sk_buff *skb, struct genl_info *info)
|
||||
i++;
|
||||
}
|
||||
|
||||
err = rdev->ops->set_coalesce(&rdev->wiphy, &new_coalesce);
|
||||
err = rdev_set_coalesce(rdev, &new_coalesce);
|
||||
if (err)
|
||||
goto error;
|
||||
|
||||
@ -10945,6 +10977,14 @@ static const struct genl_ops nl80211_ops[] = {
|
||||
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
||||
NL80211_FLAG_NEED_RTNL,
|
||||
},
|
||||
{
|
||||
.cmd = NL80211_CMD_ABORT_SCAN,
|
||||
.doit = nl80211_abort_scan,
|
||||
.policy = nl80211_policy,
|
||||
.flags = GENL_ADMIN_PERM,
|
||||
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
|
||||
NL80211_FLAG_NEED_RTNL,
|
||||
},
|
||||
{
|
||||
.cmd = NL80211_CMD_GET_SCAN,
|
||||
.policy = nl80211_policy,
|
||||
|
@ -29,6 +29,9 @@ int __cfg80211_join_ocb(struct cfg80211_registered_device *rdev,
|
||||
if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_OCB)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!rdev->ops->join_ocb)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (WARN_ON(!setup->chandef.chan))
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -427,6 +427,14 @@ static inline int rdev_scan(struct cfg80211_registered_device *rdev,
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline void rdev_abort_scan(struct cfg80211_registered_device *rdev,
|
||||
struct wireless_dev *wdev)
|
||||
{
|
||||
trace_rdev_abort_scan(&rdev->wiphy, wdev);
|
||||
rdev->ops->abort_scan(&rdev->wiphy, wdev);
|
||||
trace_rdev_return_void(&rdev->wiphy);
|
||||
}
|
||||
|
||||
static inline int rdev_auth(struct cfg80211_registered_device *rdev,
|
||||
struct net_device *dev,
|
||||
struct cfg80211_auth_request *req)
|
||||
@ -1020,4 +1028,47 @@ rdev_tdls_cancel_channel_switch(struct cfg80211_registered_device *rdev,
|
||||
trace_rdev_return_void(&rdev->wiphy);
|
||||
}
|
||||
|
||||
static inline int
|
||||
rdev_start_radar_detection(struct cfg80211_registered_device *rdev,
|
||||
struct net_device *dev,
|
||||
struct cfg80211_chan_def *chandef,
|
||||
u32 cac_time_ms)
|
||||
{
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
trace_rdev_start_radar_detection(&rdev->wiphy, dev, chandef,
|
||||
cac_time_ms);
|
||||
if (rdev->ops->start_radar_detection)
|
||||
ret = rdev->ops->start_radar_detection(&rdev->wiphy, dev,
|
||||
chandef, cac_time_ms);
|
||||
trace_rdev_return_int(&rdev->wiphy, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int
|
||||
rdev_set_mcast_rate(struct cfg80211_registered_device *rdev,
|
||||
struct net_device *dev,
|
||||
int mcast_rate[IEEE80211_NUM_BANDS])
|
||||
{
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
trace_rdev_set_mcast_rate(&rdev->wiphy, dev, mcast_rate);
|
||||
if (rdev->ops->set_mcast_rate)
|
||||
ret = rdev->ops->set_mcast_rate(&rdev->wiphy, dev, mcast_rate);
|
||||
trace_rdev_return_int(&rdev->wiphy, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int
|
||||
rdev_set_coalesce(struct cfg80211_registered_device *rdev,
|
||||
struct cfg80211_coalesce *coalesce)
|
||||
{
|
||||
int ret = -ENOTSUPP;
|
||||
|
||||
trace_rdev_set_coalesce(&rdev->wiphy, coalesce);
|
||||
if (rdev->ops->set_coalesce)
|
||||
ret = rdev->ops->set_coalesce(&rdev->wiphy, coalesce);
|
||||
trace_rdev_return_int(&rdev->wiphy, ret);
|
||||
return ret;
|
||||
}
|
||||
#endif /* __CFG80211_RDEV_OPS */
|
||||
|
@ -1052,7 +1052,7 @@ static u32 map_regdom_flags(u32 rd_flags)
|
||||
}
|
||||
|
||||
static const struct ieee80211_reg_rule *
|
||||
freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
|
||||
freq_reg_info_regd(u32 center_freq,
|
||||
const struct ieee80211_regdomain *regd, u32 bw)
|
||||
{
|
||||
int i;
|
||||
@ -1097,7 +1097,7 @@ __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw)
|
||||
u32 bw;
|
||||
|
||||
for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) {
|
||||
reg_rule = freq_reg_info_regd(wiphy, center_freq, regd, bw);
|
||||
reg_rule = freq_reg_info_regd(center_freq, regd, bw);
|
||||
if (!IS_ERR(reg_rule))
|
||||
return reg_rule;
|
||||
}
|
||||
@ -1166,6 +1166,41 @@ static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd,
|
||||
const struct ieee80211_reg_rule *reg_rule,
|
||||
const struct ieee80211_channel *chan)
|
||||
{
|
||||
const struct ieee80211_freq_range *freq_range = NULL;
|
||||
u32 max_bandwidth_khz, bw_flags = 0;
|
||||
|
||||
freq_range = ®_rule->freq_range;
|
||||
|
||||
max_bandwidth_khz = freq_range->max_bandwidth_khz;
|
||||
/* Check if auto calculation requested */
|
||||
if (reg_rule->flags & NL80211_RRF_AUTO_BW)
|
||||
max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
|
||||
|
||||
/* If we get a reg_rule we can assume that at least 5Mhz fit */
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(10)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(20)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(10))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(20))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(40))
|
||||
bw_flags |= IEEE80211_CHAN_NO_HT40;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(80))
|
||||
bw_flags |= IEEE80211_CHAN_NO_80MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(160))
|
||||
bw_flags |= IEEE80211_CHAN_NO_160MHZ;
|
||||
return bw_flags;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note that right now we assume the desired channel bandwidth
|
||||
* is always 20 MHz for each individual channel (HT40 uses 20 MHz
|
||||
@ -1178,11 +1213,9 @@ static void handle_channel(struct wiphy *wiphy,
|
||||
u32 flags, bw_flags = 0;
|
||||
const struct ieee80211_reg_rule *reg_rule = NULL;
|
||||
const struct ieee80211_power_rule *power_rule = NULL;
|
||||
const struct ieee80211_freq_range *freq_range = NULL;
|
||||
struct wiphy *request_wiphy = NULL;
|
||||
struct regulatory_request *lr = get_last_request();
|
||||
const struct ieee80211_regdomain *regd;
|
||||
u32 max_bandwidth_khz;
|
||||
|
||||
request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
|
||||
|
||||
@ -1223,31 +1256,7 @@ static void handle_channel(struct wiphy *wiphy,
|
||||
chan_reg_rule_print_dbg(regd, chan, reg_rule);
|
||||
|
||||
power_rule = ®_rule->power_rule;
|
||||
freq_range = ®_rule->freq_range;
|
||||
|
||||
max_bandwidth_khz = freq_range->max_bandwidth_khz;
|
||||
/* Check if auto calculation requested */
|
||||
if (reg_rule->flags & NL80211_RRF_AUTO_BW)
|
||||
max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
|
||||
|
||||
/* If we get a reg_rule we can assume that at least 5Mhz fit */
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(10)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(20)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(10))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(20))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(40))
|
||||
bw_flags |= IEEE80211_CHAN_NO_HT40;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(80))
|
||||
bw_flags |= IEEE80211_CHAN_NO_80MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(160))
|
||||
bw_flags |= IEEE80211_CHAN_NO_160MHZ;
|
||||
bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
|
||||
|
||||
if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
|
||||
request_wiphy && request_wiphy == wiphy &&
|
||||
@ -1760,13 +1769,10 @@ static void handle_channel_custom(struct wiphy *wiphy,
|
||||
u32 bw_flags = 0;
|
||||
const struct ieee80211_reg_rule *reg_rule = NULL;
|
||||
const struct ieee80211_power_rule *power_rule = NULL;
|
||||
const struct ieee80211_freq_range *freq_range = NULL;
|
||||
u32 max_bandwidth_khz;
|
||||
u32 bw;
|
||||
|
||||
for (bw = MHZ_TO_KHZ(20); bw >= MHZ_TO_KHZ(5); bw = bw / 2) {
|
||||
reg_rule = freq_reg_info_regd(wiphy,
|
||||
MHZ_TO_KHZ(chan->center_freq),
|
||||
reg_rule = freq_reg_info_regd(MHZ_TO_KHZ(chan->center_freq),
|
||||
regd, bw);
|
||||
if (!IS_ERR(reg_rule))
|
||||
break;
|
||||
@ -1787,31 +1793,7 @@ static void handle_channel_custom(struct wiphy *wiphy,
|
||||
chan_reg_rule_print_dbg(regd, chan, reg_rule);
|
||||
|
||||
power_rule = ®_rule->power_rule;
|
||||
freq_range = ®_rule->freq_range;
|
||||
|
||||
max_bandwidth_khz = freq_range->max_bandwidth_khz;
|
||||
/* Check if auto calculation requested */
|
||||
if (reg_rule->flags & NL80211_RRF_AUTO_BW)
|
||||
max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
|
||||
|
||||
/* If we get a reg_rule we can assume that at least 5Mhz fit */
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(10)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
|
||||
MHZ_TO_KHZ(20)))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(10))
|
||||
bw_flags |= IEEE80211_CHAN_NO_10MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(20))
|
||||
bw_flags |= IEEE80211_CHAN_NO_20MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(40))
|
||||
bw_flags |= IEEE80211_CHAN_NO_HT40;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(80))
|
||||
bw_flags |= IEEE80211_CHAN_NO_80MHZ;
|
||||
if (max_bandwidth_khz < MHZ_TO_KHZ(160))
|
||||
bw_flags |= IEEE80211_CHAN_NO_160MHZ;
|
||||
bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
|
||||
|
||||
chan->dfs_state_entered = jiffies;
|
||||
chan->dfs_state = NL80211_DFS_USABLE;
|
||||
|
@ -623,12 +623,24 @@ DECLARE_EVENT_CLASS(station_add_change,
|
||||
__field(u32, sta_flags_set)
|
||||
__field(u32, sta_modify_mask)
|
||||
__field(int, listen_interval)
|
||||
__field(u16, capability)
|
||||
__field(u16, aid)
|
||||
__field(u8, plink_action)
|
||||
__field(u8, plink_state)
|
||||
__field(u8, uapsd_queues)
|
||||
__field(u8, max_sp)
|
||||
__field(u8, opmode_notif)
|
||||
__field(bool, opmode_notif_used)
|
||||
__array(u8, ht_capa, (int)sizeof(struct ieee80211_ht_cap))
|
||||
__array(u8, vht_capa, (int)sizeof(struct ieee80211_vht_cap))
|
||||
__array(char, vlan, IFNAMSIZ)
|
||||
__dynamic_array(u8, supported_rates,
|
||||
params->supported_rates_len)
|
||||
__dynamic_array(u8, ext_capab, params->ext_capab_len)
|
||||
__dynamic_array(u8, supported_channels,
|
||||
params->supported_channels_len)
|
||||
__dynamic_array(u8, supported_oper_classes,
|
||||
params->supported_oper_classes_len)
|
||||
),
|
||||
TP_fast_assign(
|
||||
WIPHY_ASSIGN;
|
||||
@ -646,9 +658,35 @@ DECLARE_EVENT_CLASS(station_add_change,
|
||||
if (params->ht_capa)
|
||||
memcpy(__entry->ht_capa, params->ht_capa,
|
||||
sizeof(struct ieee80211_ht_cap));
|
||||
memset(__entry->vht_capa, 0, sizeof(struct ieee80211_vht_cap));
|
||||
if (params->vht_capa)
|
||||
memcpy(__entry->vht_capa, params->vht_capa,
|
||||
sizeof(struct ieee80211_vht_cap));
|
||||
memset(__entry->vlan, 0, sizeof(__entry->vlan));
|
||||
if (params->vlan)
|
||||
memcpy(__entry->vlan, params->vlan->name, IFNAMSIZ);
|
||||
if (params->supported_rates && params->supported_rates_len)
|
||||
memcpy(__get_dynamic_array(supported_rates),
|
||||
params->supported_rates,
|
||||
params->supported_rates_len);
|
||||
if (params->ext_capab && params->ext_capab_len)
|
||||
memcpy(__get_dynamic_array(ext_capab),
|
||||
params->ext_capab,
|
||||
params->ext_capab_len);
|
||||
if (params->supported_channels &&
|
||||
params->supported_channels_len)
|
||||
memcpy(__get_dynamic_array(supported_channels),
|
||||
params->supported_channels,
|
||||
params->supported_channels_len);
|
||||
if (params->supported_oper_classes &&
|
||||
params->supported_oper_classes_len)
|
||||
memcpy(__get_dynamic_array(supported_oper_classes),
|
||||
params->supported_oper_classes,
|
||||
params->supported_oper_classes_len);
|
||||
__entry->max_sp = params->max_sp;
|
||||
__entry->capability = params->capability;
|
||||
__entry->opmode_notif = params->opmode_notif;
|
||||
__entry->opmode_notif_used = params->opmode_notif_used;
|
||||
),
|
||||
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", station mac: " MAC_PR_FMT
|
||||
", station flags mask: %u, station flags set: %u, "
|
||||
@ -2818,6 +2856,71 @@ TRACE_EVENT(cfg80211_stop_iface,
|
||||
WIPHY_PR_ARG, WDEV_PR_ARG)
|
||||
);
|
||||
|
||||
TRACE_EVENT(rdev_start_radar_detection,
|
||||
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
|
||||
struct cfg80211_chan_def *chandef,
|
||||
u32 cac_time_ms),
|
||||
TP_ARGS(wiphy, netdev, chandef, cac_time_ms),
|
||||
TP_STRUCT__entry(
|
||||
WIPHY_ENTRY
|
||||
NETDEV_ENTRY
|
||||
CHAN_DEF_ENTRY
|
||||
__field(u32, cac_time_ms)
|
||||
),
|
||||
TP_fast_assign(
|
||||
WIPHY_ASSIGN;
|
||||
NETDEV_ASSIGN;
|
||||
CHAN_DEF_ASSIGN(chandef);
|
||||
__entry->cac_time_ms = cac_time_ms;
|
||||
),
|
||||
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", " CHAN_DEF_PR_FMT
|
||||
", cac_time_ms=%u",
|
||||
WIPHY_PR_ARG, NETDEV_PR_ARG, CHAN_DEF_PR_ARG,
|
||||
__entry->cac_time_ms)
|
||||
);
|
||||
|
||||
TRACE_EVENT(rdev_set_mcast_rate,
|
||||
TP_PROTO(struct wiphy *wiphy, struct net_device *netdev,
|
||||
int mcast_rate[IEEE80211_NUM_BANDS]),
|
||||
TP_ARGS(wiphy, netdev, mcast_rate),
|
||||
TP_STRUCT__entry(
|
||||
WIPHY_ENTRY
|
||||
NETDEV_ENTRY
|
||||
__array(int, mcast_rate, IEEE80211_NUM_BANDS)
|
||||
),
|
||||
TP_fast_assign(
|
||||
WIPHY_ASSIGN;
|
||||
NETDEV_ASSIGN;
|
||||
memcpy(__entry->mcast_rate, mcast_rate,
|
||||
sizeof(int) * IEEE80211_NUM_BANDS);
|
||||
),
|
||||
TP_printk(WIPHY_PR_FMT ", " NETDEV_PR_FMT ", "
|
||||
"mcast_rates [2.4GHz=0x%x, 5.2GHz=0x%x, 60GHz=0x%x]",
|
||||
WIPHY_PR_ARG, NETDEV_PR_ARG,
|
||||
__entry->mcast_rate[IEEE80211_BAND_2GHZ],
|
||||
__entry->mcast_rate[IEEE80211_BAND_5GHZ],
|
||||
__entry->mcast_rate[IEEE80211_BAND_60GHZ])
|
||||
);
|
||||
|
||||
TRACE_EVENT(rdev_set_coalesce,
|
||||
TP_PROTO(struct wiphy *wiphy, struct cfg80211_coalesce *coalesce),
|
||||
TP_ARGS(wiphy, coalesce),
|
||||
TP_STRUCT__entry(
|
||||
WIPHY_ENTRY
|
||||
__field(int, n_rules)
|
||||
),
|
||||
TP_fast_assign(
|
||||
WIPHY_ASSIGN;
|
||||
__entry->n_rules = coalesce ? coalesce->n_rules : 0;
|
||||
),
|
||||
TP_printk(WIPHY_PR_FMT ", n_rules=%d",
|
||||
WIPHY_PR_ARG, __entry->n_rules)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(wiphy_wdev_evt, rdev_abort_scan,
|
||||
TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev),
|
||||
TP_ARGS(wiphy, wdev)
|
||||
);
|
||||
#endif /* !__RDEV_OPS_TRACE || TRACE_HEADER_MULTI_READ */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
|
@ -1325,13 +1325,6 @@ size_t ieee80211_ie_split_ric(const u8 *ies, size_t ielen,
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_ie_split_ric);
|
||||
|
||||
size_t ieee80211_ie_split(const u8 *ies, size_t ielen,
|
||||
const u8 *ids, int n_ids, size_t offset)
|
||||
{
|
||||
return ieee80211_ie_split_ric(ies, ielen, ids, n_ids, NULL, 0, offset);
|
||||
}
|
||||
EXPORT_SYMBOL(ieee80211_ie_split);
|
||||
|
||||
bool ieee80211_operating_class_to_band(u8 operating_class,
|
||||
enum ieee80211_band *band)
|
||||
{
|
||||
@ -1620,120 +1613,6 @@ int cfg80211_check_combinations(struct wiphy *wiphy,
|
||||
}
|
||||
EXPORT_SYMBOL(cfg80211_check_combinations);
|
||||
|
||||
int cfg80211_can_use_iftype_chan(struct cfg80211_registered_device *rdev,
|
||||
struct wireless_dev *wdev,
|
||||
enum nl80211_iftype iftype,
|
||||
struct ieee80211_channel *chan,
|
||||
enum cfg80211_chan_mode chanmode,
|
||||
u8 radar_detect)
|
||||
{
|
||||
struct wireless_dev *wdev_iter;
|
||||
int num[NUM_NL80211_IFTYPES];
|
||||
struct ieee80211_channel
|
||||
*used_channels[CFG80211_MAX_NUM_DIFFERENT_CHANNELS];
|
||||
struct ieee80211_channel *ch;
|
||||
enum cfg80211_chan_mode chmode;
|
||||
int num_different_channels = 0;
|
||||
int total = 1;
|
||||
int i;
|
||||
|
||||
ASSERT_RTNL();
|
||||
|
||||
if (WARN_ON(hweight32(radar_detect) > 1))
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON(iftype >= NUM_NL80211_IFTYPES))
|
||||
return -EINVAL;
|
||||
|
||||
/* Always allow software iftypes */
|
||||
if (rdev->wiphy.software_iftypes & BIT(iftype)) {
|
||||
if (radar_detect)
|
||||
return -EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(num, 0, sizeof(num));
|
||||
memset(used_channels, 0, sizeof(used_channels));
|
||||
|
||||
num[iftype] = 1;
|
||||
|
||||
/* TODO: We'll probably not need this anymore, since this
|
||||
* should only be called with CHAN_MODE_UNDEFINED. There are
|
||||
* still a couple of pending calls where other chanmodes are
|
||||
* used, but we should get rid of them.
|
||||
*/
|
||||
switch (chanmode) {
|
||||
case CHAN_MODE_UNDEFINED:
|
||||
break;
|
||||
case CHAN_MODE_SHARED:
|
||||
WARN_ON(!chan);
|
||||
used_channels[0] = chan;
|
||||
num_different_channels++;
|
||||
break;
|
||||
case CHAN_MODE_EXCLUSIVE:
|
||||
num_different_channels++;
|
||||
break;
|
||||
}
|
||||
|
||||
list_for_each_entry(wdev_iter, &rdev->wdev_list, list) {
|
||||
if (wdev_iter == wdev)
|
||||
continue;
|
||||
if (wdev_iter->iftype == NL80211_IFTYPE_P2P_DEVICE) {
|
||||
if (!wdev_iter->p2p_started)
|
||||
continue;
|
||||
} else if (wdev_iter->netdev) {
|
||||
if (!netif_running(wdev_iter->netdev))
|
||||
continue;
|
||||
} else {
|
||||
WARN_ON(1);
|
||||
}
|
||||
|
||||
if (rdev->wiphy.software_iftypes & BIT(wdev_iter->iftype))
|
||||
continue;
|
||||
|
||||
/*
|
||||
* We may be holding the "wdev" mutex, but now need to lock
|
||||
* wdev_iter. This is OK because once we get here wdev_iter
|
||||
* is not wdev (tested above), but we need to use the nested
|
||||
* locking for lockdep.
|
||||
*/
|
||||
mutex_lock_nested(&wdev_iter->mtx, 1);
|
||||
__acquire(wdev_iter->mtx);
|
||||
cfg80211_get_chan_state(wdev_iter, &ch, &chmode, &radar_detect);
|
||||
wdev_unlock(wdev_iter);
|
||||
|
||||
switch (chmode) {
|
||||
case CHAN_MODE_UNDEFINED:
|
||||
break;
|
||||
case CHAN_MODE_SHARED:
|
||||
for (i = 0; i < CFG80211_MAX_NUM_DIFFERENT_CHANNELS; i++)
|
||||
if (!used_channels[i] || used_channels[i] == ch)
|
||||
break;
|
||||
|
||||
if (i == CFG80211_MAX_NUM_DIFFERENT_CHANNELS)
|
||||
return -EBUSY;
|
||||
|
||||
if (used_channels[i] == NULL) {
|
||||
used_channels[i] = ch;
|
||||
num_different_channels++;
|
||||
}
|
||||
break;
|
||||
case CHAN_MODE_EXCLUSIVE:
|
||||
num_different_channels++;
|
||||
break;
|
||||
}
|
||||
|
||||
num[wdev_iter->iftype]++;
|
||||
total++;
|
||||
}
|
||||
|
||||
if (total == 1 && !radar_detect)
|
||||
return 0;
|
||||
|
||||
return cfg80211_check_combinations(&rdev->wiphy, num_different_channels,
|
||||
radar_detect, num);
|
||||
}
|
||||
|
||||
int ieee80211_get_ratemask(struct ieee80211_supported_band *sband,
|
||||
const u8 *rates, unsigned int n_rates,
|
||||
u32 *mask)
|
||||
|
Loading…
Reference in New Issue
Block a user