mirror of
https://github.com/torvalds/linux.git
synced 2024-11-24 21:21:41 +00:00
43b7724487
mwifiex has recently started to see active development which is good news. rtw89 is also under active development and got several new features. Otherwise not really anything out of ordinary. We have one conflict in ath12k but that's easy to fix: https://lore.kernel.org/all/20240808104348.6846e064@canb.auug.org.au/ Major changes: mwifiex * support for up to ten Authentication and Key Management (AKM) suites * host MAC Sublayer Management Entity (MLME) client and AP mode support * WPA-PSK-SHA256 AKM suite support rtw88 * improve USB performance by aggregation rtw89 * Wi-Fi 6 chip RTL8852BE-VT support * WoWLAN net-detect support * hardware encryption in unicast management frames support * hardware rfkill support ath12k * DebugFS support for transmit DE stats * Make ASPM support hardware-dependent iwlwifi * channel puncturing for US/CAN from UEFI * bump FW API to 93 for BZ/SC devices -----BEGIN PGP SIGNATURE----- iQFFBAABCgAvFiEEiBjanGPFTz4PRfLobhckVSbrbZsFAmbYfCARHGt2YWxvQGtl cm5lbC5vcmcACgkQbhckVSbrbZt+Qwf/X9oQ4sf8jV6eOV7EhoWhIHnQadvo5YBZ ulBm8In0QGjEOVWkI7kXGabKP5jhne2lVIyP1eFfP2/td/A2yDWIuEeBfDQD6f4K aiUGAa1gs4ZtGKJBniw/ukflSqJlR99N2qBO5T/smDm3Nw/aC522SO7BoLTpoJDQ SuW4atFHMShXYf/vIrAA2yB9ok2yw/QM+27M9qjj6D7zzqsQxDl9wKGW+2v8KiSa rXXbfnwfaQP21CYv5xYbEPACSRSV5Dr0TNopivWYxmm9svjLzwFN2JM2fHPxBEDh wP6Ojp+Z32c1VbQtclLrwIQdlZ5yhU5MEDlVg5VLym9F83hv+oXTbA== =lgVx -----END PGP SIGNATURE----- Merge tag 'wireless-next-2024-09-04' of git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next Kalle Valo says: ==================== pull-request: wireless-next-2024-09-04 here's a pull request to net-next tree, more info below. Please let me know if there are any problems. ==================== Conflicts: drivers/net/wireless/ath/ath12k/hw.c38055789d1
("wifi: ath12k: use 128 bytes aligned iova in transmit path for WCN7850")8be12629b4
("wifi: ath12k: restore ASPM for supported hardwares only") https://lore.kernel.org/87msldyj97.fsf@kernel.org Link: https://patch.msgid.link/20240904153205.64C11C4CEC2@smtp.kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>
6279 lines
167 KiB
C
6279 lines
167 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright 2002-2005, Instant802 Networks, Inc.
|
|
* Copyright 2005-2006, Devicescape Software, Inc.
|
|
* Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
|
|
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
|
|
* Copyright 2013-2014 Intel Mobile Communications GmbH
|
|
* Copyright (C) 2018-2024 Intel Corporation
|
|
*
|
|
* Transmit and frame generation functions.
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/if_vlan.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/bitmap.h>
|
|
#include <linux/rcupdate.h>
|
|
#include <linux/export.h>
|
|
#include <net/net_namespace.h>
|
|
#include <net/ieee80211_radiotap.h>
|
|
#include <net/cfg80211.h>
|
|
#include <net/mac80211.h>
|
|
#include <net/codel.h>
|
|
#include <net/codel_impl.h>
|
|
#include <asm/unaligned.h>
|
|
#include <net/fq_impl.h>
|
|
#include <net/gso.h>
|
|
|
|
#include "ieee80211_i.h"
|
|
#include "driver-ops.h"
|
|
#include "led.h"
|
|
#include "mesh.h"
|
|
#include "wep.h"
|
|
#include "wpa.h"
|
|
#include "wme.h"
|
|
#include "rate.h"
|
|
|
|
/* misc utils */
|
|
|
|
static __le16 ieee80211_duration(struct ieee80211_tx_data *tx,
|
|
struct sk_buff *skb, int group_addr,
|
|
int next_frag_len)
|
|
{
|
|
int rate, mrate, erp, dur, i;
|
|
struct ieee80211_rate *txrate;
|
|
struct ieee80211_local *local = tx->local;
|
|
struct ieee80211_supported_band *sband;
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
u32 rate_flags = 0;
|
|
|
|
/* assume HW handles this */
|
|
if (tx->rate.flags & (IEEE80211_TX_RC_MCS | IEEE80211_TX_RC_VHT_MCS))
|
|
return 0;
|
|
|
|
rcu_read_lock();
|
|
chanctx_conf = rcu_dereference(tx->sdata->vif.bss_conf.chanctx_conf);
|
|
if (chanctx_conf)
|
|
rate_flags = ieee80211_chandef_rate_flags(&chanctx_conf->def);
|
|
rcu_read_unlock();
|
|
|
|
/* uh huh? */
|
|
if (WARN_ON_ONCE(tx->rate.idx < 0))
|
|
return 0;
|
|
|
|
sband = local->hw.wiphy->bands[info->band];
|
|
txrate = &sband->bitrates[tx->rate.idx];
|
|
|
|
erp = txrate->flags & IEEE80211_RATE_ERP_G;
|
|
|
|
/* device is expected to do this */
|
|
if (sband->band == NL80211_BAND_S1GHZ)
|
|
return 0;
|
|
|
|
/*
|
|
* data and mgmt (except PS Poll):
|
|
* - during CFP: 32768
|
|
* - during contention period:
|
|
* if addr1 is group address: 0
|
|
* if more fragments = 0 and addr1 is individual address: time to
|
|
* transmit one ACK plus SIFS
|
|
* if more fragments = 1 and addr1 is individual address: time to
|
|
* transmit next fragment plus 2 x ACK plus 3 x SIFS
|
|
*
|
|
* IEEE 802.11, 9.6:
|
|
* - control response frame (CTS or ACK) shall be transmitted using the
|
|
* same rate as the immediately previous frame in the frame exchange
|
|
* sequence, if this rate belongs to the PHY mandatory rates, or else
|
|
* at the highest possible rate belonging to the PHY rates in the
|
|
* BSSBasicRateSet
|
|
*/
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
if (ieee80211_is_ctl(hdr->frame_control)) {
|
|
/* TODO: These control frames are not currently sent by
|
|
* mac80211, but should they be implemented, this function
|
|
* needs to be updated to support duration field calculation.
|
|
*
|
|
* RTS: time needed to transmit pending data/mgmt frame plus
|
|
* one CTS frame plus one ACK frame plus 3 x SIFS
|
|
* CTS: duration of immediately previous RTS minus time
|
|
* required to transmit CTS and its SIFS
|
|
* ACK: 0 if immediately previous directed data/mgmt had
|
|
* more=0, with more=1 duration in ACK frame is duration
|
|
* from previous frame minus time needed to transmit ACK
|
|
* and its SIFS
|
|
* PS Poll: BIT(15) | BIT(14) | aid
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/* data/mgmt */
|
|
if (0 /* FIX: data/mgmt during CFP */)
|
|
return cpu_to_le16(32768);
|
|
|
|
if (group_addr) /* Group address as the destination - no ACK */
|
|
return 0;
|
|
|
|
/* Individual destination address:
|
|
* IEEE 802.11, Ch. 9.6 (after IEEE 802.11g changes)
|
|
* CTS and ACK frames shall be transmitted using the highest rate in
|
|
* basic rate set that is less than or equal to the rate of the
|
|
* immediately previous frame and that is using the same modulation
|
|
* (CCK or OFDM). If no basic rate set matches with these requirements,
|
|
* the highest mandatory rate of the PHY that is less than or equal to
|
|
* the rate of the previous frame is used.
|
|
* Mandatory rates for IEEE 802.11g PHY: 1, 2, 5.5, 11, 6, 12, 24 Mbps
|
|
*/
|
|
rate = -1;
|
|
/* use lowest available if everything fails */
|
|
mrate = sband->bitrates[0].bitrate;
|
|
for (i = 0; i < sband->n_bitrates; i++) {
|
|
struct ieee80211_rate *r = &sband->bitrates[i];
|
|
u32 flag;
|
|
|
|
if (r->bitrate > txrate->bitrate)
|
|
break;
|
|
|
|
if ((rate_flags & r->flags) != rate_flags)
|
|
continue;
|
|
|
|
if (tx->sdata->vif.bss_conf.basic_rates & BIT(i))
|
|
rate = r->bitrate;
|
|
|
|
switch (sband->band) {
|
|
case NL80211_BAND_2GHZ:
|
|
case NL80211_BAND_LC:
|
|
if (tx->sdata->deflink.operating_11g_mode)
|
|
flag = IEEE80211_RATE_MANDATORY_G;
|
|
else
|
|
flag = IEEE80211_RATE_MANDATORY_B;
|
|
break;
|
|
case NL80211_BAND_5GHZ:
|
|
case NL80211_BAND_6GHZ:
|
|
flag = IEEE80211_RATE_MANDATORY_A;
|
|
break;
|
|
default:
|
|
flag = 0;
|
|
WARN_ON(1);
|
|
break;
|
|
}
|
|
|
|
if (r->flags & flag)
|
|
mrate = r->bitrate;
|
|
}
|
|
if (rate == -1) {
|
|
/* No matching basic rate found; use highest suitable mandatory
|
|
* PHY rate */
|
|
rate = mrate;
|
|
}
|
|
|
|
/* Don't calculate ACKs for QoS Frames with NoAck Policy set */
|
|
if (ieee80211_is_data_qos(hdr->frame_control) &&
|
|
*(ieee80211_get_qos_ctl(hdr)) & IEEE80211_QOS_CTL_ACK_POLICY_NOACK)
|
|
dur = 0;
|
|
else
|
|
/* Time needed to transmit ACK
|
|
* (10 bytes + 4-byte FCS = 112 bits) plus SIFS; rounded up
|
|
* to closest integer */
|
|
dur = ieee80211_frame_duration(sband->band, 10, rate, erp,
|
|
tx->sdata->vif.bss_conf.use_short_preamble);
|
|
|
|
if (next_frag_len) {
|
|
/* Frame is fragmented: duration increases with time needed to
|
|
* transmit next fragment plus ACK and 2 x SIFS. */
|
|
dur *= 2; /* ACK + SIFS */
|
|
/* next fragment */
|
|
dur += ieee80211_frame_duration(sband->band, next_frag_len,
|
|
txrate->bitrate, erp,
|
|
tx->sdata->vif.bss_conf.use_short_preamble);
|
|
}
|
|
|
|
return cpu_to_le16(dur);
|
|
}
|
|
|
|
/* tx handlers */
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_dynamic_ps(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_local *local = tx->local;
|
|
struct ieee80211_if_managed *ifmgd;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
|
|
/* driver doesn't support power save */
|
|
if (!ieee80211_hw_check(&local->hw, SUPPORTS_PS))
|
|
return TX_CONTINUE;
|
|
|
|
/* hardware does dynamic power save */
|
|
if (ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS))
|
|
return TX_CONTINUE;
|
|
|
|
/* dynamic power save disabled */
|
|
if (local->hw.conf.dynamic_ps_timeout <= 0)
|
|
return TX_CONTINUE;
|
|
|
|
/* we are scanning, don't enable power save */
|
|
if (local->scanning)
|
|
return TX_CONTINUE;
|
|
|
|
if (!local->ps_sdata)
|
|
return TX_CONTINUE;
|
|
|
|
/* No point if we're going to suspend */
|
|
if (local->quiescing)
|
|
return TX_CONTINUE;
|
|
|
|
/* dynamic ps is supported only in managed mode */
|
|
if (tx->sdata->vif.type != NL80211_IFTYPE_STATION)
|
|
return TX_CONTINUE;
|
|
|
|
if (unlikely(info->flags & IEEE80211_TX_INTFL_OFFCHAN_TX_OK))
|
|
return TX_CONTINUE;
|
|
|
|
ifmgd = &tx->sdata->u.mgd;
|
|
|
|
/*
|
|
* Don't wakeup from power save if u-apsd is enabled, voip ac has
|
|
* u-apsd enabled and the frame is in voip class. This effectively
|
|
* means that even if all access categories have u-apsd enabled, in
|
|
* practise u-apsd is only used with the voip ac. This is a
|
|
* workaround for the case when received voip class packets do not
|
|
* have correct qos tag for some reason, due the network or the
|
|
* peer application.
|
|
*
|
|
* Note: ifmgd->uapsd_queues access is racy here. If the value is
|
|
* changed via debugfs, user needs to reassociate manually to have
|
|
* everything in sync.
|
|
*/
|
|
if ((ifmgd->flags & IEEE80211_STA_UAPSD_ENABLED) &&
|
|
(ifmgd->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO) &&
|
|
skb_get_queue_mapping(tx->skb) == IEEE80211_AC_VO)
|
|
return TX_CONTINUE;
|
|
|
|
if (local->hw.conf.flags & IEEE80211_CONF_PS) {
|
|
ieee80211_stop_queues_by_reason(&local->hw,
|
|
IEEE80211_MAX_QUEUE_MAP,
|
|
IEEE80211_QUEUE_STOP_REASON_PS,
|
|
false);
|
|
ifmgd->flags &= ~IEEE80211_STA_NULLFUNC_ACKED;
|
|
wiphy_work_queue(local->hw.wiphy,
|
|
&local->dynamic_ps_disable_work);
|
|
}
|
|
|
|
/* Don't restart the timer if we're not disassociated */
|
|
if (!ifmgd->associated)
|
|
return TX_CONTINUE;
|
|
|
|
mod_timer(&local->dynamic_ps_timer, jiffies +
|
|
msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx)
|
|
{
|
|
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
bool assoc = false;
|
|
|
|
if (unlikely(info->flags & IEEE80211_TX_CTL_INJECTED))
|
|
return TX_CONTINUE;
|
|
|
|
if (unlikely(test_bit(SCAN_SW_SCANNING, &tx->local->scanning)) &&
|
|
test_bit(SDATA_STATE_OFFCHANNEL, &tx->sdata->state) &&
|
|
!ieee80211_is_probe_req(hdr->frame_control) &&
|
|
!ieee80211_is_any_nullfunc(hdr->frame_control))
|
|
/*
|
|
* When software scanning only nullfunc frames (to notify
|
|
* the sleep state to the AP) and probe requests (for the
|
|
* active scan) are allowed, all other frames should not be
|
|
* sent and we should not get here, but if we do
|
|
* nonetheless, drop them to avoid sending them
|
|
* off-channel. See the link below and
|
|
* ieee80211_start_scan() for more.
|
|
*
|
|
* http://article.gmane.org/gmane.linux.kernel.wireless.general/30089
|
|
*/
|
|
return TX_DROP;
|
|
|
|
if (tx->sdata->vif.type == NL80211_IFTYPE_OCB)
|
|
return TX_CONTINUE;
|
|
|
|
if (tx->flags & IEEE80211_TX_PS_BUFFERED)
|
|
return TX_CONTINUE;
|
|
|
|
if (tx->sta)
|
|
assoc = test_sta_flag(tx->sta, WLAN_STA_ASSOC);
|
|
|
|
if (likely(tx->flags & IEEE80211_TX_UNICAST)) {
|
|
if (unlikely(!assoc &&
|
|
ieee80211_is_data(hdr->frame_control))) {
|
|
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
|
sdata_info(tx->sdata,
|
|
"dropped data frame to not associated station %pM\n",
|
|
hdr->addr1);
|
|
#endif
|
|
I802_DEBUG_INC(tx->local->tx_handlers_drop_not_assoc);
|
|
return TX_DROP;
|
|
}
|
|
} else if (unlikely(ieee80211_is_data(hdr->frame_control) &&
|
|
ieee80211_vif_get_num_mcast_if(tx->sdata) == 0)) {
|
|
/*
|
|
* No associated STAs - no need to send multicast
|
|
* frames.
|
|
*/
|
|
return TX_DROP;
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
/* This function is called whenever the AP is about to exceed the maximum limit
|
|
* of buffered frames for power saving STAs. This situation should not really
|
|
* happen often during normal operation, so dropping the oldest buffered packet
|
|
* from each queue should be OK to make some room for new frames. */
|
|
static void purge_old_ps_buffers(struct ieee80211_local *local)
|
|
{
|
|
int total = 0, purged = 0;
|
|
struct sk_buff *skb;
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct sta_info *sta;
|
|
|
|
list_for_each_entry_rcu(sdata, &local->interfaces, list) {
|
|
struct ps_data *ps;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
ps = &sdata->u.ap.ps;
|
|
else if (ieee80211_vif_is_mesh(&sdata->vif))
|
|
ps = &sdata->u.mesh.ps;
|
|
else
|
|
continue;
|
|
|
|
skb = skb_dequeue(&ps->bc_buf);
|
|
if (skb) {
|
|
purged++;
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
}
|
|
total += skb_queue_len(&ps->bc_buf);
|
|
}
|
|
|
|
/*
|
|
* Drop one frame from each station from the lowest-priority
|
|
* AC that has frames at all.
|
|
*/
|
|
list_for_each_entry_rcu(sta, &local->sta_list, list) {
|
|
int ac;
|
|
|
|
for (ac = IEEE80211_AC_BK; ac >= IEEE80211_AC_VO; ac--) {
|
|
skb = skb_dequeue(&sta->ps_tx_buf[ac]);
|
|
total += skb_queue_len(&sta->ps_tx_buf[ac]);
|
|
if (skb) {
|
|
purged++;
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
local->total_ps_buffered = total;
|
|
ps_dbg_hw(&local->hw, "PS buffers full - purged %d frames\n", purged);
|
|
}
|
|
|
|
static ieee80211_tx_result
|
|
ieee80211_tx_h_multicast_ps_buf(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
struct ps_data *ps;
|
|
|
|
/*
|
|
* broadcast/multicast frame
|
|
*
|
|
* If any of the associated/peer stations is in power save mode,
|
|
* the frame is buffered to be sent after DTIM beacon frame.
|
|
* This is done either by the hardware or us.
|
|
*/
|
|
|
|
/* powersaving STAs currently only in AP/VLAN/mesh mode */
|
|
if (tx->sdata->vif.type == NL80211_IFTYPE_AP ||
|
|
tx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
|
|
if (!tx->sdata->bss)
|
|
return TX_CONTINUE;
|
|
|
|
ps = &tx->sdata->bss->ps;
|
|
} else if (ieee80211_vif_is_mesh(&tx->sdata->vif)) {
|
|
ps = &tx->sdata->u.mesh.ps;
|
|
} else {
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
|
|
/* no buffering for ordered frames */
|
|
if (ieee80211_has_order(hdr->frame_control))
|
|
return TX_CONTINUE;
|
|
|
|
if (ieee80211_is_probe_req(hdr->frame_control))
|
|
return TX_CONTINUE;
|
|
|
|
if (ieee80211_hw_check(&tx->local->hw, QUEUE_CONTROL))
|
|
info->hw_queue = tx->sdata->vif.cab_queue;
|
|
|
|
/* no stations in PS mode and no buffered packets */
|
|
if (!atomic_read(&ps->num_sta_ps) && skb_queue_empty(&ps->bc_buf))
|
|
return TX_CONTINUE;
|
|
|
|
info->flags |= IEEE80211_TX_CTL_SEND_AFTER_DTIM;
|
|
|
|
/* device releases frame after DTIM beacon */
|
|
if (!ieee80211_hw_check(&tx->local->hw, HOST_BROADCAST_PS_BUFFERING))
|
|
return TX_CONTINUE;
|
|
|
|
/* buffered in mac80211 */
|
|
if (tx->local->total_ps_buffered >= TOTAL_MAX_TX_BUFFER)
|
|
purge_old_ps_buffers(tx->local);
|
|
|
|
if (skb_queue_len(&ps->bc_buf) >= AP_MAX_BC_BUFFER) {
|
|
ps_dbg(tx->sdata,
|
|
"BC TX buffer full - dropping the oldest frame\n");
|
|
ieee80211_free_txskb(&tx->local->hw, skb_dequeue(&ps->bc_buf));
|
|
} else
|
|
tx->local->total_ps_buffered++;
|
|
|
|
skb_queue_tail(&ps->bc_buf, tx->skb);
|
|
|
|
return TX_QUEUED;
|
|
}
|
|
|
|
static int ieee80211_use_mfp(__le16 fc, struct sta_info *sta,
|
|
struct sk_buff *skb)
|
|
{
|
|
if (!ieee80211_is_mgmt(fc))
|
|
return 0;
|
|
|
|
if (sta == NULL || !test_sta_flag(sta, WLAN_STA_MFP))
|
|
return 0;
|
|
|
|
if (!ieee80211_is_robust_mgmt_frame(skb))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static ieee80211_tx_result
|
|
ieee80211_tx_h_unicast_ps_buf(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct sta_info *sta = tx->sta;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
struct ieee80211_local *local = tx->local;
|
|
|
|
if (unlikely(!sta))
|
|
return TX_CONTINUE;
|
|
|
|
if (unlikely((test_sta_flag(sta, WLAN_STA_PS_STA) ||
|
|
test_sta_flag(sta, WLAN_STA_PS_DRIVER) ||
|
|
test_sta_flag(sta, WLAN_STA_PS_DELIVER)) &&
|
|
!(info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER))) {
|
|
int ac = skb_get_queue_mapping(tx->skb);
|
|
|
|
if (ieee80211_is_mgmt(hdr->frame_control) &&
|
|
!ieee80211_is_bufferable_mmpdu(tx->skb)) {
|
|
info->flags |= IEEE80211_TX_CTL_NO_PS_BUFFER;
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
ps_dbg(sta->sdata, "STA %pM aid %d: PS buffer for AC %d\n",
|
|
sta->sta.addr, sta->sta.aid, ac);
|
|
if (tx->local->total_ps_buffered >= TOTAL_MAX_TX_BUFFER)
|
|
purge_old_ps_buffers(tx->local);
|
|
|
|
/* sync with ieee80211_sta_ps_deliver_wakeup */
|
|
spin_lock(&sta->ps_lock);
|
|
/*
|
|
* STA woke up the meantime and all the frames on ps_tx_buf have
|
|
* been queued to pending queue. No reordering can happen, go
|
|
* ahead and Tx the packet.
|
|
*/
|
|
if (!test_sta_flag(sta, WLAN_STA_PS_STA) &&
|
|
!test_sta_flag(sta, WLAN_STA_PS_DRIVER) &&
|
|
!test_sta_flag(sta, WLAN_STA_PS_DELIVER)) {
|
|
spin_unlock(&sta->ps_lock);
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
if (skb_queue_len(&sta->ps_tx_buf[ac]) >= STA_MAX_TX_BUFFER) {
|
|
struct sk_buff *old = skb_dequeue(&sta->ps_tx_buf[ac]);
|
|
ps_dbg(tx->sdata,
|
|
"STA %pM TX buffer for AC %d full - dropping oldest frame\n",
|
|
sta->sta.addr, ac);
|
|
ieee80211_free_txskb(&local->hw, old);
|
|
} else
|
|
tx->local->total_ps_buffered++;
|
|
|
|
info->control.jiffies = jiffies;
|
|
info->control.vif = &tx->sdata->vif;
|
|
info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
|
|
info->flags &= ~IEEE80211_TX_TEMPORARY_FLAGS;
|
|
skb_queue_tail(&sta->ps_tx_buf[ac], tx->skb);
|
|
spin_unlock(&sta->ps_lock);
|
|
|
|
if (!timer_pending(&local->sta_cleanup))
|
|
mod_timer(&local->sta_cleanup,
|
|
round_jiffies(jiffies +
|
|
STA_INFO_CLEANUP_INTERVAL));
|
|
|
|
/*
|
|
* We queued up some frames, so the TIM bit might
|
|
* need to be set, recalculate it.
|
|
*/
|
|
sta_info_recalc_tim(sta);
|
|
|
|
return TX_QUEUED;
|
|
} else if (unlikely(test_sta_flag(sta, WLAN_STA_PS_STA))) {
|
|
ps_dbg(tx->sdata,
|
|
"STA %pM in PS mode, but polling/in SP -> send frame\n",
|
|
sta->sta.addr);
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_ps_buf(struct ieee80211_tx_data *tx)
|
|
{
|
|
if (unlikely(tx->flags & IEEE80211_TX_PS_BUFFERED))
|
|
return TX_CONTINUE;
|
|
|
|
if (tx->flags & IEEE80211_TX_UNICAST)
|
|
return ieee80211_tx_h_unicast_ps_buf(tx);
|
|
else
|
|
return ieee80211_tx_h_multicast_ps_buf(tx);
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_check_control_port_protocol(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
|
|
if (unlikely(tx->sdata->control_port_protocol == tx->skb->protocol)) {
|
|
if (tx->sdata->control_port_no_encrypt)
|
|
info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
|
|
info->control.flags |= IEEE80211_TX_CTRL_PORT_CTRL_PROTO;
|
|
info->flags |= IEEE80211_TX_CTL_USE_MINRATE;
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static struct ieee80211_key *
|
|
ieee80211_select_link_key(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_link_data *link;
|
|
unsigned int link_id;
|
|
|
|
link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK);
|
|
if (link_id == IEEE80211_LINK_UNSPECIFIED) {
|
|
link = &tx->sdata->deflink;
|
|
} else {
|
|
link = rcu_dereference(tx->sdata->link[link_id]);
|
|
if (!link)
|
|
return NULL;
|
|
}
|
|
|
|
if (ieee80211_is_group_privacy_action(tx->skb))
|
|
return rcu_dereference(link->default_multicast_key);
|
|
else if (ieee80211_is_mgmt(hdr->frame_control) &&
|
|
is_multicast_ether_addr(hdr->addr1) &&
|
|
ieee80211_is_robust_mgmt_frame(tx->skb))
|
|
return rcu_dereference(link->default_mgmt_key);
|
|
else if (is_multicast_ether_addr(hdr->addr1))
|
|
return rcu_dereference(link->default_multicast_key);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_key *key;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
|
|
if (unlikely(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) {
|
|
tx->key = NULL;
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
if (tx->sta &&
|
|
(key = rcu_dereference(tx->sta->ptk[tx->sta->ptk_idx])))
|
|
tx->key = key;
|
|
else if ((key = ieee80211_select_link_key(tx)))
|
|
tx->key = key;
|
|
else if (!is_multicast_ether_addr(hdr->addr1) &&
|
|
(key = rcu_dereference(tx->sdata->default_unicast_key)))
|
|
tx->key = key;
|
|
else
|
|
tx->key = NULL;
|
|
|
|
if (tx->key) {
|
|
bool skip_hw = false;
|
|
|
|
/* TODO: add threshold stuff again */
|
|
|
|
switch (tx->key->conf.cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
if (!ieee80211_is_data_present(hdr->frame_control))
|
|
tx->key = NULL;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_CCMP_256:
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
if (!ieee80211_is_data_present(hdr->frame_control) &&
|
|
!ieee80211_use_mfp(hdr->frame_control, tx->sta,
|
|
tx->skb) &&
|
|
!ieee80211_is_group_privacy_action(tx->skb))
|
|
tx->key = NULL;
|
|
else
|
|
skip_hw = (tx->key->conf.flags &
|
|
IEEE80211_KEY_FLAG_SW_MGMT_TX) &&
|
|
ieee80211_is_mgmt(hdr->frame_control);
|
|
break;
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
if (!ieee80211_is_mgmt(hdr->frame_control))
|
|
tx->key = NULL;
|
|
break;
|
|
}
|
|
|
|
if (unlikely(tx->key && tx->key->flags & KEY_FLAG_TAINTED &&
|
|
!ieee80211_is_deauth(hdr->frame_control)) &&
|
|
tx->skb->protocol != tx->sdata->control_port_protocol)
|
|
return TX_DROP;
|
|
|
|
if (!skip_hw && tx->key &&
|
|
tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
|
|
info->control.hw_key = &tx->key->conf;
|
|
} else if (ieee80211_is_data_present(hdr->frame_control) && tx->sta &&
|
|
test_sta_flag(tx->sta, WLAN_STA_USES_ENCRYPTION)) {
|
|
return TX_DROP;
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_hdr *hdr = (void *)tx->skb->data;
|
|
struct ieee80211_supported_band *sband;
|
|
u32 len;
|
|
struct ieee80211_tx_rate_control txrc;
|
|
struct ieee80211_sta_rates *ratetbl = NULL;
|
|
bool encap = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP;
|
|
bool assoc = false;
|
|
|
|
memset(&txrc, 0, sizeof(txrc));
|
|
|
|
sband = tx->local->hw.wiphy->bands[info->band];
|
|
|
|
len = min_t(u32, tx->skb->len + FCS_LEN,
|
|
tx->local->hw.wiphy->frag_threshold);
|
|
|
|
/* set up the tx rate control struct we give the RC algo */
|
|
txrc.hw = &tx->local->hw;
|
|
txrc.sband = sband;
|
|
txrc.bss_conf = &tx->sdata->vif.bss_conf;
|
|
txrc.skb = tx->skb;
|
|
txrc.reported_rate.idx = -1;
|
|
|
|
if (unlikely(info->control.flags & IEEE80211_TX_CTRL_DONT_USE_RATE_MASK)) {
|
|
txrc.rate_idx_mask = ~0;
|
|
} else {
|
|
txrc.rate_idx_mask = tx->sdata->rc_rateidx_mask[info->band];
|
|
|
|
if (tx->sdata->rc_has_mcs_mask[info->band])
|
|
txrc.rate_idx_mcs_mask =
|
|
tx->sdata->rc_rateidx_mcs_mask[info->band];
|
|
}
|
|
|
|
txrc.bss = (tx->sdata->vif.type == NL80211_IFTYPE_AP ||
|
|
tx->sdata->vif.type == NL80211_IFTYPE_MESH_POINT ||
|
|
tx->sdata->vif.type == NL80211_IFTYPE_ADHOC ||
|
|
tx->sdata->vif.type == NL80211_IFTYPE_OCB);
|
|
|
|
/* set up RTS protection if desired */
|
|
if (len > tx->local->hw.wiphy->rts_threshold) {
|
|
txrc.rts = true;
|
|
}
|
|
|
|
info->control.use_rts = txrc.rts;
|
|
info->control.use_cts_prot = tx->sdata->vif.bss_conf.use_cts_prot;
|
|
|
|
/*
|
|
* Use short preamble if the BSS can handle it, but not for
|
|
* management frames unless we know the receiver can handle
|
|
* that -- the management frame might be to a station that
|
|
* just wants a probe response.
|
|
*/
|
|
if (tx->sdata->vif.bss_conf.use_short_preamble &&
|
|
(ieee80211_is_tx_data(tx->skb) ||
|
|
(tx->sta && test_sta_flag(tx->sta, WLAN_STA_SHORT_PREAMBLE))))
|
|
txrc.short_preamble = true;
|
|
|
|
info->control.short_preamble = txrc.short_preamble;
|
|
|
|
/* don't ask rate control when rate already injected via radiotap */
|
|
if (info->control.flags & IEEE80211_TX_CTRL_RATE_INJECT)
|
|
return TX_CONTINUE;
|
|
|
|
if (tx->sta)
|
|
assoc = test_sta_flag(tx->sta, WLAN_STA_ASSOC);
|
|
|
|
/*
|
|
* Lets not bother rate control if we're associated and cannot
|
|
* talk to the sta. This should not happen.
|
|
*/
|
|
if (WARN(test_bit(SCAN_SW_SCANNING, &tx->local->scanning) && assoc &&
|
|
!rate_usable_index_exists(sband, &tx->sta->sta),
|
|
"%s: Dropped data frame as no usable bitrate found while "
|
|
"scanning and associated. Target station: "
|
|
"%pM on %d GHz band\n",
|
|
tx->sdata->name,
|
|
encap ? ((struct ethhdr *)hdr)->h_dest : hdr->addr1,
|
|
info->band ? 5 : 2))
|
|
return TX_DROP;
|
|
|
|
/*
|
|
* If we're associated with the sta at this point we know we can at
|
|
* least send the frame at the lowest bit rate.
|
|
*/
|
|
rate_control_get_rate(tx->sdata, tx->sta, &txrc);
|
|
|
|
if (tx->sta && !info->control.skip_table)
|
|
ratetbl = rcu_dereference(tx->sta->sta.rates);
|
|
|
|
if (unlikely(info->control.rates[0].idx < 0)) {
|
|
if (ratetbl) {
|
|
struct ieee80211_tx_rate rate = {
|
|
.idx = ratetbl->rate[0].idx,
|
|
.flags = ratetbl->rate[0].flags,
|
|
.count = ratetbl->rate[0].count
|
|
};
|
|
|
|
if (ratetbl->rate[0].idx < 0)
|
|
return TX_DROP;
|
|
|
|
tx->rate = rate;
|
|
} else {
|
|
return TX_DROP;
|
|
}
|
|
} else {
|
|
tx->rate = info->control.rates[0];
|
|
}
|
|
|
|
if (txrc.reported_rate.idx < 0) {
|
|
txrc.reported_rate = tx->rate;
|
|
if (tx->sta && ieee80211_is_tx_data(tx->skb))
|
|
tx->sta->deflink.tx_stats.last_rate = txrc.reported_rate;
|
|
} else if (tx->sta)
|
|
tx->sta->deflink.tx_stats.last_rate = txrc.reported_rate;
|
|
|
|
if (ratetbl)
|
|
return TX_CONTINUE;
|
|
|
|
if (unlikely(!info->control.rates[0].count))
|
|
info->control.rates[0].count = 1;
|
|
|
|
if (WARN_ON_ONCE((info->control.rates[0].count > 1) &&
|
|
(info->flags & IEEE80211_TX_CTL_NO_ACK)))
|
|
info->control.rates[0].count = 1;
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static __le16 ieee80211_tx_next_seq(struct sta_info *sta, int tid)
|
|
{
|
|
u16 *seq = &sta->tid_seq[tid];
|
|
__le16 ret = cpu_to_le16(*seq);
|
|
|
|
/* Increase the sequence number. */
|
|
*seq = (*seq + 0x10) & IEEE80211_SCTL_SEQ;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_sequence(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data;
|
|
int tid;
|
|
|
|
/*
|
|
* Packet injection may want to control the sequence
|
|
* number, if we have no matching interface then we
|
|
* neither assign one ourselves nor ask the driver to.
|
|
*/
|
|
if (unlikely(info->control.vif->type == NL80211_IFTYPE_MONITOR))
|
|
return TX_CONTINUE;
|
|
|
|
if (unlikely(ieee80211_is_ctl(hdr->frame_control)))
|
|
return TX_CONTINUE;
|
|
|
|
if (ieee80211_hdrlen(hdr->frame_control) < 24)
|
|
return TX_CONTINUE;
|
|
|
|
if (ieee80211_is_qos_nullfunc(hdr->frame_control))
|
|
return TX_CONTINUE;
|
|
|
|
if (info->control.flags & IEEE80211_TX_CTRL_NO_SEQNO)
|
|
return TX_CONTINUE;
|
|
|
|
/* SNS11 from 802.11be 10.3.2.14 */
|
|
if (unlikely(is_multicast_ether_addr(hdr->addr1) &&
|
|
ieee80211_vif_is_mld(info->control.vif) &&
|
|
info->control.vif->type == NL80211_IFTYPE_AP)) {
|
|
if (info->control.flags & IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX)
|
|
tx->sdata->mld_mcast_seq += 0x10;
|
|
hdr->seq_ctrl = cpu_to_le16(tx->sdata->mld_mcast_seq);
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* Anything but QoS data that has a sequence number field
|
|
* (is long enough) gets a sequence number from the global
|
|
* counter. QoS data frames with a multicast destination
|
|
* also use the global counter (802.11-2012 9.3.2.10).
|
|
*/
|
|
if (!ieee80211_is_data_qos(hdr->frame_control) ||
|
|
is_multicast_ether_addr(hdr->addr1)) {
|
|
/* driver should assign sequence number */
|
|
info->flags |= IEEE80211_TX_CTL_ASSIGN_SEQ;
|
|
/* for pure STA mode without beacons, we can do it */
|
|
hdr->seq_ctrl = cpu_to_le16(tx->sdata->sequence_number);
|
|
tx->sdata->sequence_number += 0x10;
|
|
if (tx->sta)
|
|
tx->sta->deflink.tx_stats.msdu[IEEE80211_NUM_TIDS]++;
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* This should be true for injected/management frames only, for
|
|
* management frames we have set the IEEE80211_TX_CTL_ASSIGN_SEQ
|
|
* above since they are not QoS-data frames.
|
|
*/
|
|
if (!tx->sta)
|
|
return TX_CONTINUE;
|
|
|
|
/* include per-STA, per-TID sequence counter */
|
|
tid = ieee80211_get_tid(hdr);
|
|
tx->sta->deflink.tx_stats.msdu[tid]++;
|
|
|
|
hdr->seq_ctrl = ieee80211_tx_next_seq(tx->sta, tid);
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static int ieee80211_fragment(struct ieee80211_tx_data *tx,
|
|
struct sk_buff *skb, int hdrlen,
|
|
int frag_threshold)
|
|
{
|
|
struct ieee80211_local *local = tx->local;
|
|
struct ieee80211_tx_info *info;
|
|
struct sk_buff *tmp;
|
|
int per_fragm = frag_threshold - hdrlen - FCS_LEN;
|
|
int pos = hdrlen + per_fragm;
|
|
int rem = skb->len - hdrlen - per_fragm;
|
|
|
|
if (WARN_ON(rem < 0))
|
|
return -EINVAL;
|
|
|
|
/* first fragment was already added to queue by caller */
|
|
|
|
while (rem) {
|
|
int fraglen = per_fragm;
|
|
|
|
if (fraglen > rem)
|
|
fraglen = rem;
|
|
rem -= fraglen;
|
|
tmp = dev_alloc_skb(local->tx_headroom +
|
|
frag_threshold +
|
|
IEEE80211_ENCRYPT_HEADROOM +
|
|
IEEE80211_ENCRYPT_TAILROOM);
|
|
if (!tmp)
|
|
return -ENOMEM;
|
|
|
|
__skb_queue_tail(&tx->skbs, tmp);
|
|
|
|
skb_reserve(tmp,
|
|
local->tx_headroom + IEEE80211_ENCRYPT_HEADROOM);
|
|
|
|
/* copy control information */
|
|
memcpy(tmp->cb, skb->cb, sizeof(tmp->cb));
|
|
|
|
info = IEEE80211_SKB_CB(tmp);
|
|
info->flags &= ~(IEEE80211_TX_CTL_CLEAR_PS_FILT |
|
|
IEEE80211_TX_CTL_FIRST_FRAGMENT);
|
|
|
|
if (rem)
|
|
info->flags |= IEEE80211_TX_CTL_MORE_FRAMES;
|
|
|
|
skb_copy_queue_mapping(tmp, skb);
|
|
tmp->priority = skb->priority;
|
|
tmp->dev = skb->dev;
|
|
|
|
/* copy header and data */
|
|
skb_put_data(tmp, skb->data, hdrlen);
|
|
skb_put_data(tmp, skb->data + pos, fraglen);
|
|
|
|
pos += fraglen;
|
|
}
|
|
|
|
/* adjust first fragment's length */
|
|
skb_trim(skb, hdrlen + per_fragm);
|
|
return 0;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_fragment(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct sk_buff *skb = tx->skb;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr = (void *)skb->data;
|
|
int frag_threshold = tx->local->hw.wiphy->frag_threshold;
|
|
int hdrlen;
|
|
int fragnum;
|
|
|
|
/* no matter what happens, tx->skb moves to tx->skbs */
|
|
__skb_queue_tail(&tx->skbs, skb);
|
|
tx->skb = NULL;
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_DONTFRAG)
|
|
return TX_CONTINUE;
|
|
|
|
if (ieee80211_hw_check(&tx->local->hw, SUPPORTS_TX_FRAG))
|
|
return TX_CONTINUE;
|
|
|
|
/*
|
|
* Warn when submitting a fragmented A-MPDU frame and drop it.
|
|
* This scenario is handled in ieee80211_tx_prepare but extra
|
|
* caution taken here as fragmented ampdu may cause Tx stop.
|
|
*/
|
|
if (WARN_ON(info->flags & IEEE80211_TX_CTL_AMPDU))
|
|
return TX_DROP;
|
|
|
|
hdrlen = ieee80211_hdrlen(hdr->frame_control);
|
|
|
|
/* internal error, why isn't DONTFRAG set? */
|
|
if (WARN_ON(skb->len + FCS_LEN <= frag_threshold))
|
|
return TX_DROP;
|
|
|
|
/*
|
|
* Now fragment the frame. This will allocate all the fragments and
|
|
* chain them (using skb as the first fragment) to skb->next.
|
|
* During transmission, we will remove the successfully transmitted
|
|
* fragments from this list. When the low-level driver rejects one
|
|
* of the fragments then we will simply pretend to accept the skb
|
|
* but store it away as pending.
|
|
*/
|
|
if (ieee80211_fragment(tx, skb, hdrlen, frag_threshold))
|
|
return TX_DROP;
|
|
|
|
/* update duration/seq/flags of fragments */
|
|
fragnum = 0;
|
|
|
|
skb_queue_walk(&tx->skbs, skb) {
|
|
const __le16 morefrags = cpu_to_le16(IEEE80211_FCTL_MOREFRAGS);
|
|
|
|
hdr = (void *)skb->data;
|
|
info = IEEE80211_SKB_CB(skb);
|
|
|
|
if (!skb_queue_is_last(&tx->skbs, skb)) {
|
|
hdr->frame_control |= morefrags;
|
|
/*
|
|
* No multi-rate retries for fragmented frames, that
|
|
* would completely throw off the NAV at other STAs.
|
|
*/
|
|
info->control.rates[1].idx = -1;
|
|
info->control.rates[2].idx = -1;
|
|
info->control.rates[3].idx = -1;
|
|
BUILD_BUG_ON(IEEE80211_TX_MAX_RATES != 4);
|
|
info->flags &= ~IEEE80211_TX_CTL_RATE_CTRL_PROBE;
|
|
} else {
|
|
hdr->frame_control &= ~morefrags;
|
|
}
|
|
hdr->seq_ctrl |= cpu_to_le16(fragnum & IEEE80211_SCTL_FRAG);
|
|
fragnum++;
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_stats(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct sk_buff *skb;
|
|
int ac = -1;
|
|
|
|
if (!tx->sta)
|
|
return TX_CONTINUE;
|
|
|
|
skb_queue_walk(&tx->skbs, skb) {
|
|
ac = skb_get_queue_mapping(skb);
|
|
tx->sta->deflink.tx_stats.bytes[ac] += skb->len;
|
|
}
|
|
if (ac >= 0)
|
|
tx->sta->deflink.tx_stats.packets[ac]++;
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_encrypt(struct ieee80211_tx_data *tx)
|
|
{
|
|
if (!tx->key)
|
|
return TX_CONTINUE;
|
|
|
|
switch (tx->key->conf.cipher) {
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
return ieee80211_crypto_wep_encrypt(tx);
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
return ieee80211_crypto_tkip_encrypt(tx);
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
return ieee80211_crypto_ccmp_encrypt(
|
|
tx, IEEE80211_CCMP_MIC_LEN);
|
|
case WLAN_CIPHER_SUITE_CCMP_256:
|
|
return ieee80211_crypto_ccmp_encrypt(
|
|
tx, IEEE80211_CCMP_256_MIC_LEN);
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
return ieee80211_crypto_aes_cmac_encrypt(tx);
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
return ieee80211_crypto_aes_cmac_256_encrypt(tx);
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
return ieee80211_crypto_aes_gmac_encrypt(tx);
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
return ieee80211_crypto_gcmp_encrypt(tx);
|
|
}
|
|
|
|
return TX_DROP;
|
|
}
|
|
|
|
static ieee80211_tx_result debug_noinline
|
|
ieee80211_tx_h_calculate_duration(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct sk_buff *skb;
|
|
struct ieee80211_hdr *hdr;
|
|
int next_len;
|
|
bool group_addr;
|
|
|
|
skb_queue_walk(&tx->skbs, skb) {
|
|
hdr = (void *) skb->data;
|
|
if (unlikely(ieee80211_is_pspoll(hdr->frame_control)))
|
|
break; /* must not overwrite AID */
|
|
if (!skb_queue_is_last(&tx->skbs, skb)) {
|
|
struct sk_buff *next = skb_queue_next(&tx->skbs, skb);
|
|
next_len = next->len;
|
|
} else
|
|
next_len = 0;
|
|
group_addr = is_multicast_ether_addr(hdr->addr1);
|
|
|
|
hdr->duration_id =
|
|
ieee80211_duration(tx, skb, group_addr, next_len);
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
/* actual transmit path */
|
|
|
|
static bool ieee80211_tx_prep_agg(struct ieee80211_tx_data *tx,
|
|
struct sk_buff *skb,
|
|
struct ieee80211_tx_info *info,
|
|
struct tid_ampdu_tx *tid_tx,
|
|
int tid)
|
|
{
|
|
bool queued = false;
|
|
bool reset_agg_timer = false;
|
|
struct sk_buff *purge_skb = NULL;
|
|
|
|
if (test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) {
|
|
reset_agg_timer = true;
|
|
} else if (test_bit(HT_AGG_STATE_WANT_START, &tid_tx->state)) {
|
|
/*
|
|
* nothing -- this aggregation session is being started
|
|
* but that might still fail with the driver
|
|
*/
|
|
} else if (!tx->sta->sta.txq[tid]) {
|
|
spin_lock(&tx->sta->lock);
|
|
/*
|
|
* Need to re-check now, because we may get here
|
|
*
|
|
* 1) in the window during which the setup is actually
|
|
* already done, but not marked yet because not all
|
|
* packets are spliced over to the driver pending
|
|
* queue yet -- if this happened we acquire the lock
|
|
* either before or after the splice happens, but
|
|
* need to recheck which of these cases happened.
|
|
*
|
|
* 2) during session teardown, if the OPERATIONAL bit
|
|
* was cleared due to the teardown but the pointer
|
|
* hasn't been assigned NULL yet (or we loaded it
|
|
* before it was assigned) -- in this case it may
|
|
* now be NULL which means we should just let the
|
|
* packet pass through because splicing the frames
|
|
* back is already done.
|
|
*/
|
|
tid_tx = rcu_dereference_protected_tid_tx(tx->sta, tid);
|
|
|
|
if (!tid_tx) {
|
|
/* do nothing, let packet pass through */
|
|
} else if (test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) {
|
|
reset_agg_timer = true;
|
|
} else {
|
|
queued = true;
|
|
if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) {
|
|
clear_sta_flag(tx->sta, WLAN_STA_SP);
|
|
ps_dbg(tx->sta->sdata,
|
|
"STA %pM aid %d: SP frame queued, close the SP w/o telling the peer\n",
|
|
tx->sta->sta.addr, tx->sta->sta.aid);
|
|
}
|
|
info->control.vif = &tx->sdata->vif;
|
|
info->control.flags |= IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
|
|
info->flags &= ~IEEE80211_TX_TEMPORARY_FLAGS;
|
|
__skb_queue_tail(&tid_tx->pending, skb);
|
|
if (skb_queue_len(&tid_tx->pending) > STA_MAX_TX_BUFFER)
|
|
purge_skb = __skb_dequeue(&tid_tx->pending);
|
|
}
|
|
spin_unlock(&tx->sta->lock);
|
|
|
|
if (purge_skb)
|
|
ieee80211_free_txskb(&tx->local->hw, purge_skb);
|
|
}
|
|
|
|
/* reset session timer */
|
|
if (reset_agg_timer)
|
|
tid_tx->last_tx = jiffies;
|
|
|
|
return queued;
|
|
}
|
|
|
|
void ieee80211_aggr_check(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct rate_control_ref *ref = sdata->local->rate_ctrl;
|
|
u16 tid;
|
|
|
|
if (!ref || !(ref->ops->capa & RATE_CTRL_CAPA_AMPDU_TRIGGER))
|
|
return;
|
|
|
|
if (!sta || !sta->sta.deflink.ht_cap.ht_supported ||
|
|
!sta->sta.wme || skb_get_queue_mapping(skb) == IEEE80211_AC_VO ||
|
|
skb->protocol == sdata->control_port_protocol)
|
|
return;
|
|
|
|
tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
|
|
if (likely(sta->ampdu_mlme.tid_tx[tid]))
|
|
return;
|
|
|
|
ieee80211_start_tx_ba_session(&sta->sta, tid, 0);
|
|
}
|
|
|
|
/*
|
|
* initialises @tx
|
|
* pass %NULL for the station if unknown, a valid pointer if known
|
|
* or an ERR_PTR() if the station is known not to exist
|
|
*/
|
|
static ieee80211_tx_result
|
|
ieee80211_tx_prepare(struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_tx_data *tx,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
bool aggr_check = false;
|
|
int tid;
|
|
|
|
memset(tx, 0, sizeof(*tx));
|
|
tx->skb = skb;
|
|
tx->local = local;
|
|
tx->sdata = sdata;
|
|
__skb_queue_head_init(&tx->skbs);
|
|
|
|
/*
|
|
* If this flag is set to true anywhere, and we get here,
|
|
* we are doing the needed processing, so remove the flag
|
|
* now.
|
|
*/
|
|
info->control.flags &= ~IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
|
|
|
|
hdr = (struct ieee80211_hdr *) skb->data;
|
|
|
|
if (likely(sta)) {
|
|
if (!IS_ERR(sta))
|
|
tx->sta = sta;
|
|
} else {
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN) {
|
|
tx->sta = rcu_dereference(sdata->u.vlan.sta);
|
|
if (!tx->sta && sdata->wdev.use_4addr)
|
|
return TX_DROP;
|
|
} else if (tx->sdata->control_port_protocol == tx->skb->protocol) {
|
|
tx->sta = sta_info_get_bss(sdata, hdr->addr1);
|
|
}
|
|
if (!tx->sta && !is_multicast_ether_addr(hdr->addr1)) {
|
|
tx->sta = sta_info_get(sdata, hdr->addr1);
|
|
aggr_check = true;
|
|
}
|
|
}
|
|
|
|
if (tx->sta && ieee80211_is_data_qos(hdr->frame_control) &&
|
|
!ieee80211_is_qos_nullfunc(hdr->frame_control) &&
|
|
ieee80211_hw_check(&local->hw, AMPDU_AGGREGATION) &&
|
|
!ieee80211_hw_check(&local->hw, TX_AMPDU_SETUP_IN_HW)) {
|
|
struct tid_ampdu_tx *tid_tx;
|
|
|
|
tid = ieee80211_get_tid(hdr);
|
|
tid_tx = rcu_dereference(tx->sta->ampdu_mlme.tid_tx[tid]);
|
|
if (!tid_tx && aggr_check) {
|
|
ieee80211_aggr_check(sdata, tx->sta, skb);
|
|
tid_tx = rcu_dereference(tx->sta->ampdu_mlme.tid_tx[tid]);
|
|
}
|
|
|
|
if (tid_tx) {
|
|
bool queued;
|
|
|
|
queued = ieee80211_tx_prep_agg(tx, skb, info,
|
|
tid_tx, tid);
|
|
|
|
if (unlikely(queued))
|
|
return TX_QUEUED;
|
|
}
|
|
}
|
|
|
|
if (is_multicast_ether_addr(hdr->addr1)) {
|
|
tx->flags &= ~IEEE80211_TX_UNICAST;
|
|
info->flags |= IEEE80211_TX_CTL_NO_ACK;
|
|
} else
|
|
tx->flags |= IEEE80211_TX_UNICAST;
|
|
|
|
if (!(info->flags & IEEE80211_TX_CTL_DONTFRAG)) {
|
|
if (!(tx->flags & IEEE80211_TX_UNICAST) ||
|
|
skb->len + FCS_LEN <= local->hw.wiphy->frag_threshold ||
|
|
info->flags & IEEE80211_TX_CTL_AMPDU)
|
|
info->flags |= IEEE80211_TX_CTL_DONTFRAG;
|
|
}
|
|
|
|
if (!tx->sta)
|
|
info->flags |= IEEE80211_TX_CTL_CLEAR_PS_FILT;
|
|
else if (test_and_clear_sta_flag(tx->sta, WLAN_STA_CLEAR_PS_FILT)) {
|
|
info->flags |= IEEE80211_TX_CTL_CLEAR_PS_FILT;
|
|
ieee80211_check_fast_xmit(tx->sta);
|
|
}
|
|
|
|
info->flags |= IEEE80211_TX_CTL_FIRST_FRAGMENT;
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static struct txq_info *ieee80211_get_txq(struct ieee80211_local *local,
|
|
struct ieee80211_vif *vif,
|
|
struct sta_info *sta,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_txq *txq = NULL;
|
|
|
|
if ((info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) ||
|
|
(info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE))
|
|
return NULL;
|
|
|
|
if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) &&
|
|
unlikely(!ieee80211_is_data_present(hdr->frame_control))) {
|
|
if ((!ieee80211_is_mgmt(hdr->frame_control) ||
|
|
ieee80211_is_bufferable_mmpdu(skb) ||
|
|
vif->type == NL80211_IFTYPE_STATION) &&
|
|
sta && sta->uploaded) {
|
|
/*
|
|
* This will be NULL if the driver didn't set the
|
|
* opt-in hardware flag.
|
|
*/
|
|
txq = sta->sta.txq[IEEE80211_NUM_TIDS];
|
|
}
|
|
} else if (sta) {
|
|
u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK;
|
|
|
|
if (!sta->uploaded)
|
|
return NULL;
|
|
|
|
txq = sta->sta.txq[tid];
|
|
} else {
|
|
txq = vif->txq;
|
|
}
|
|
|
|
if (!txq)
|
|
return NULL;
|
|
|
|
return to_txq_info(txq);
|
|
}
|
|
|
|
static void ieee80211_set_skb_enqueue_time(struct sk_buff *skb)
|
|
{
|
|
struct sk_buff *next;
|
|
codel_time_t now = codel_get_time();
|
|
|
|
skb_list_walk_safe(skb, skb, next)
|
|
IEEE80211_SKB_CB(skb)->control.enqueue_time = now;
|
|
}
|
|
|
|
static u32 codel_skb_len_func(const struct sk_buff *skb)
|
|
{
|
|
return skb->len;
|
|
}
|
|
|
|
static codel_time_t codel_skb_time_func(const struct sk_buff *skb)
|
|
{
|
|
const struct ieee80211_tx_info *info;
|
|
|
|
info = (const struct ieee80211_tx_info *)skb->cb;
|
|
return info->control.enqueue_time;
|
|
}
|
|
|
|
static struct sk_buff *codel_dequeue_func(struct codel_vars *cvars,
|
|
void *ctx)
|
|
{
|
|
struct ieee80211_local *local;
|
|
struct txq_info *txqi;
|
|
struct fq *fq;
|
|
struct fq_flow *flow;
|
|
|
|
txqi = ctx;
|
|
local = vif_to_sdata(txqi->txq.vif)->local;
|
|
fq = &local->fq;
|
|
|
|
if (cvars == &txqi->def_cvars)
|
|
flow = &txqi->tin.default_flow;
|
|
else
|
|
flow = &fq->flows[cvars - local->cvars];
|
|
|
|
return fq_flow_dequeue(fq, flow);
|
|
}
|
|
|
|
static void codel_drop_func(struct sk_buff *skb,
|
|
void *ctx)
|
|
{
|
|
struct ieee80211_local *local;
|
|
struct ieee80211_hw *hw;
|
|
struct txq_info *txqi;
|
|
|
|
txqi = ctx;
|
|
local = vif_to_sdata(txqi->txq.vif)->local;
|
|
hw = &local->hw;
|
|
|
|
ieee80211_free_txskb(hw, skb);
|
|
}
|
|
|
|
static struct sk_buff *fq_tin_dequeue_func(struct fq *fq,
|
|
struct fq_tin *tin,
|
|
struct fq_flow *flow)
|
|
{
|
|
struct ieee80211_local *local;
|
|
struct txq_info *txqi;
|
|
struct codel_vars *cvars;
|
|
struct codel_params *cparams;
|
|
struct codel_stats *cstats;
|
|
|
|
local = container_of(fq, struct ieee80211_local, fq);
|
|
txqi = container_of(tin, struct txq_info, tin);
|
|
cstats = &txqi->cstats;
|
|
|
|
if (txqi->txq.sta) {
|
|
struct sta_info *sta = container_of(txqi->txq.sta,
|
|
struct sta_info, sta);
|
|
cparams = &sta->cparams;
|
|
} else {
|
|
cparams = &local->cparams;
|
|
}
|
|
|
|
if (flow == &tin->default_flow)
|
|
cvars = &txqi->def_cvars;
|
|
else
|
|
cvars = &local->cvars[flow - fq->flows];
|
|
|
|
return codel_dequeue(txqi,
|
|
&flow->backlog,
|
|
cparams,
|
|
cvars,
|
|
cstats,
|
|
codel_skb_len_func,
|
|
codel_skb_time_func,
|
|
codel_drop_func,
|
|
codel_dequeue_func);
|
|
}
|
|
|
|
static void fq_skb_free_func(struct fq *fq,
|
|
struct fq_tin *tin,
|
|
struct fq_flow *flow,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_local *local;
|
|
|
|
local = container_of(fq, struct ieee80211_local, fq);
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
}
|
|
|
|
static void ieee80211_txq_enqueue(struct ieee80211_local *local,
|
|
struct txq_info *txqi,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct fq *fq = &local->fq;
|
|
struct fq_tin *tin = &txqi->tin;
|
|
u32 flow_idx = fq_flow_idx(fq, skb);
|
|
|
|
ieee80211_set_skb_enqueue_time(skb);
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
/*
|
|
* For management frames, don't really apply codel etc.,
|
|
* we don't want to apply any shaping or anything we just
|
|
* want to simplify the driver API by having them on the
|
|
* txqi.
|
|
*/
|
|
if (unlikely(txqi->txq.tid == IEEE80211_NUM_TIDS)) {
|
|
IEEE80211_SKB_CB(skb)->control.flags |=
|
|
IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
|
|
__skb_queue_tail(&txqi->frags, skb);
|
|
} else {
|
|
fq_tin_enqueue(fq, tin, flow_idx, skb,
|
|
fq_skb_free_func);
|
|
}
|
|
spin_unlock_bh(&fq->lock);
|
|
}
|
|
|
|
static bool fq_vlan_filter_func(struct fq *fq, struct fq_tin *tin,
|
|
struct fq_flow *flow, struct sk_buff *skb,
|
|
void *data)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
|
|
return info->control.vif == data;
|
|
}
|
|
|
|
void ieee80211_txq_remove_vlan(struct ieee80211_local *local,
|
|
struct ieee80211_sub_if_data *sdata)
|
|
{
|
|
struct fq *fq = &local->fq;
|
|
struct txq_info *txqi;
|
|
struct fq_tin *tin;
|
|
struct ieee80211_sub_if_data *ap;
|
|
|
|
if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP_VLAN))
|
|
return;
|
|
|
|
ap = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap);
|
|
|
|
if (!ap->vif.txq)
|
|
return;
|
|
|
|
txqi = to_txq_info(ap->vif.txq);
|
|
tin = &txqi->tin;
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
fq_tin_filter(fq, tin, fq_vlan_filter_func, &sdata->vif,
|
|
fq_skb_free_func);
|
|
spin_unlock_bh(&fq->lock);
|
|
}
|
|
|
|
void ieee80211_txq_init(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta,
|
|
struct txq_info *txqi, int tid)
|
|
{
|
|
fq_tin_init(&txqi->tin);
|
|
codel_vars_init(&txqi->def_cvars);
|
|
codel_stats_init(&txqi->cstats);
|
|
__skb_queue_head_init(&txqi->frags);
|
|
INIT_LIST_HEAD(&txqi->schedule_order);
|
|
|
|
txqi->txq.vif = &sdata->vif;
|
|
|
|
if (!sta) {
|
|
sdata->vif.txq = &txqi->txq;
|
|
txqi->txq.tid = 0;
|
|
txqi->txq.ac = IEEE80211_AC_BE;
|
|
|
|
return;
|
|
}
|
|
|
|
if (tid == IEEE80211_NUM_TIDS) {
|
|
if (sdata->vif.type == NL80211_IFTYPE_STATION) {
|
|
/* Drivers need to opt in to the management MPDU TXQ */
|
|
if (!ieee80211_hw_check(&sdata->local->hw,
|
|
STA_MMPDU_TXQ))
|
|
return;
|
|
} else if (!ieee80211_hw_check(&sdata->local->hw,
|
|
BUFF_MMPDU_TXQ)) {
|
|
/* Drivers need to opt in to the bufferable MMPDU TXQ */
|
|
return;
|
|
}
|
|
txqi->txq.ac = IEEE80211_AC_VO;
|
|
} else {
|
|
txqi->txq.ac = ieee80211_ac_from_tid(tid);
|
|
}
|
|
|
|
txqi->txq.sta = &sta->sta;
|
|
txqi->txq.tid = tid;
|
|
sta->sta.txq[tid] = &txqi->txq;
|
|
}
|
|
|
|
void ieee80211_txq_purge(struct ieee80211_local *local,
|
|
struct txq_info *txqi)
|
|
{
|
|
struct fq *fq = &local->fq;
|
|
struct fq_tin *tin = &txqi->tin;
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
fq_tin_reset(fq, tin, fq_skb_free_func);
|
|
ieee80211_purge_tx_queue(&local->hw, &txqi->frags);
|
|
spin_unlock_bh(&fq->lock);
|
|
|
|
spin_lock_bh(&local->active_txq_lock[txqi->txq.ac]);
|
|
list_del_init(&txqi->schedule_order);
|
|
spin_unlock_bh(&local->active_txq_lock[txqi->txq.ac]);
|
|
}
|
|
|
|
void ieee80211_txq_set_params(struct ieee80211_local *local)
|
|
{
|
|
if (local->hw.wiphy->txq_limit)
|
|
local->fq.limit = local->hw.wiphy->txq_limit;
|
|
else
|
|
local->hw.wiphy->txq_limit = local->fq.limit;
|
|
|
|
if (local->hw.wiphy->txq_memory_limit)
|
|
local->fq.memory_limit = local->hw.wiphy->txq_memory_limit;
|
|
else
|
|
local->hw.wiphy->txq_memory_limit = local->fq.memory_limit;
|
|
|
|
if (local->hw.wiphy->txq_quantum)
|
|
local->fq.quantum = local->hw.wiphy->txq_quantum;
|
|
else
|
|
local->hw.wiphy->txq_quantum = local->fq.quantum;
|
|
}
|
|
|
|
int ieee80211_txq_setup_flows(struct ieee80211_local *local)
|
|
{
|
|
struct fq *fq = &local->fq;
|
|
int ret;
|
|
int i;
|
|
bool supp_vht = false;
|
|
enum nl80211_band band;
|
|
|
|
ret = fq_init(fq, 4096);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* If the hardware doesn't support VHT, it is safe to limit the maximum
|
|
* queue size. 4 Mbytes is 64 max-size aggregates in 802.11n.
|
|
*/
|
|
for (band = 0; band < NUM_NL80211_BANDS; band++) {
|
|
struct ieee80211_supported_band *sband;
|
|
|
|
sband = local->hw.wiphy->bands[band];
|
|
if (!sband)
|
|
continue;
|
|
|
|
supp_vht = supp_vht || sband->vht_cap.vht_supported;
|
|
}
|
|
|
|
if (!supp_vht)
|
|
fq->memory_limit = 4 << 20; /* 4 Mbytes */
|
|
|
|
codel_params_init(&local->cparams);
|
|
local->cparams.interval = MS2TIME(100);
|
|
local->cparams.target = MS2TIME(20);
|
|
local->cparams.ecn = true;
|
|
|
|
local->cvars = kvcalloc(fq->flows_cnt, sizeof(local->cvars[0]),
|
|
GFP_KERNEL);
|
|
if (!local->cvars) {
|
|
spin_lock_bh(&fq->lock);
|
|
fq_reset(fq, fq_skb_free_func);
|
|
spin_unlock_bh(&fq->lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < fq->flows_cnt; i++)
|
|
codel_vars_init(&local->cvars[i]);
|
|
|
|
ieee80211_txq_set_params(local);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ieee80211_txq_teardown_flows(struct ieee80211_local *local)
|
|
{
|
|
struct fq *fq = &local->fq;
|
|
|
|
kvfree(local->cvars);
|
|
local->cvars = NULL;
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
fq_reset(fq, fq_skb_free_func);
|
|
spin_unlock_bh(&fq->lock);
|
|
}
|
|
|
|
static bool ieee80211_queue_skb(struct ieee80211_local *local,
|
|
struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_vif *vif;
|
|
struct txq_info *txqi;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_MONITOR)
|
|
return false;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
|
|
sdata = container_of(sdata->bss,
|
|
struct ieee80211_sub_if_data, u.ap);
|
|
|
|
vif = &sdata->vif;
|
|
txqi = ieee80211_get_txq(local, vif, sta, skb);
|
|
|
|
if (!txqi)
|
|
return false;
|
|
|
|
ieee80211_txq_enqueue(local, txqi, skb);
|
|
|
|
schedule_and_wake_txq(local, txqi);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ieee80211_tx_frags(struct ieee80211_local *local,
|
|
struct ieee80211_vif *vif,
|
|
struct sta_info *sta,
|
|
struct sk_buff_head *skbs,
|
|
bool txpending)
|
|
{
|
|
struct ieee80211_tx_control control = {};
|
|
struct sk_buff *skb, *tmp;
|
|
unsigned long flags;
|
|
|
|
skb_queue_walk_safe(skbs, skb, tmp) {
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
int q = info->hw_queue;
|
|
|
|
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
|
if (WARN_ON_ONCE(q >= local->hw.queues)) {
|
|
__skb_unlink(skb, skbs);
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
|
|
if (local->queue_stop_reasons[q] ||
|
|
(!txpending && !skb_queue_empty(&local->pending[q]))) {
|
|
if (unlikely(info->flags &
|
|
IEEE80211_TX_INTFL_OFFCHAN_TX_OK)) {
|
|
if (local->queue_stop_reasons[q] &
|
|
~BIT(IEEE80211_QUEUE_STOP_REASON_OFFCHANNEL)) {
|
|
/*
|
|
* Drop off-channel frames if queues
|
|
* are stopped for any reason other
|
|
* than off-channel operation. Never
|
|
* queue them.
|
|
*/
|
|
spin_unlock_irqrestore(
|
|
&local->queue_stop_reason_lock,
|
|
flags);
|
|
ieee80211_purge_tx_queue(&local->hw,
|
|
skbs);
|
|
return true;
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* Since queue is stopped, queue up frames for
|
|
* later transmission from the tx-pending
|
|
* tasklet when the queue is woken again.
|
|
*/
|
|
if (txpending)
|
|
skb_queue_splice_init(skbs,
|
|
&local->pending[q]);
|
|
else
|
|
skb_queue_splice_tail_init(skbs,
|
|
&local->pending[q]);
|
|
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock,
|
|
flags);
|
|
return false;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
|
|
|
|
info->control.vif = vif;
|
|
control.sta = sta ? &sta->sta : NULL;
|
|
|
|
__skb_unlink(skb, skbs);
|
|
drv_tx(local, &control, skb);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Returns false if the frame couldn't be transmitted but was queued instead.
|
|
*/
|
|
static bool __ieee80211_tx(struct ieee80211_local *local,
|
|
struct sk_buff_head *skbs, struct sta_info *sta,
|
|
bool txpending)
|
|
{
|
|
struct ieee80211_tx_info *info;
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct ieee80211_vif *vif;
|
|
struct sk_buff *skb;
|
|
bool result;
|
|
|
|
if (WARN_ON(skb_queue_empty(skbs)))
|
|
return true;
|
|
|
|
skb = skb_peek(skbs);
|
|
info = IEEE80211_SKB_CB(skb);
|
|
sdata = vif_to_sdata(info->control.vif);
|
|
if (sta && !sta->uploaded)
|
|
sta = NULL;
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_MONITOR:
|
|
if (sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
|
|
vif = &sdata->vif;
|
|
break;
|
|
}
|
|
sdata = rcu_dereference(local->monitor_sdata);
|
|
if (sdata && ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF)) {
|
|
vif = &sdata->vif;
|
|
info->hw_queue =
|
|
vif->hw_queue[skb_get_queue_mapping(skb)];
|
|
} else if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL)) {
|
|
ieee80211_purge_tx_queue(&local->hw, skbs);
|
|
return true;
|
|
} else
|
|
vif = NULL;
|
|
break;
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
sdata = container_of(sdata->bss,
|
|
struct ieee80211_sub_if_data, u.ap);
|
|
fallthrough;
|
|
default:
|
|
vif = &sdata->vif;
|
|
break;
|
|
}
|
|
|
|
result = ieee80211_tx_frags(local, vif, sta, skbs, txpending);
|
|
|
|
WARN_ON_ONCE(!skb_queue_empty(skbs));
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Invoke TX handlers, return 0 on success and non-zero if the
|
|
* frame was dropped or queued.
|
|
*
|
|
* The handlers are split into an early and late part. The latter is everything
|
|
* that can be sensitive to reordering, and will be deferred to after packets
|
|
* are dequeued from the intermediate queues (when they are enabled).
|
|
*/
|
|
static int invoke_tx_handlers_early(struct ieee80211_tx_data *tx)
|
|
{
|
|
ieee80211_tx_result res = TX_DROP;
|
|
|
|
#define CALL_TXH(txh) \
|
|
do { \
|
|
res = txh(tx); \
|
|
if (res != TX_CONTINUE) \
|
|
goto txh_done; \
|
|
} while (0)
|
|
|
|
CALL_TXH(ieee80211_tx_h_dynamic_ps);
|
|
CALL_TXH(ieee80211_tx_h_check_assoc);
|
|
CALL_TXH(ieee80211_tx_h_ps_buf);
|
|
CALL_TXH(ieee80211_tx_h_check_control_port_protocol);
|
|
CALL_TXH(ieee80211_tx_h_select_key);
|
|
|
|
txh_done:
|
|
if (unlikely(res == TX_DROP)) {
|
|
I802_DEBUG_INC(tx->local->tx_handlers_drop);
|
|
if (tx->skb)
|
|
ieee80211_free_txskb(&tx->local->hw, tx->skb);
|
|
else
|
|
ieee80211_purge_tx_queue(&tx->local->hw, &tx->skbs);
|
|
return -1;
|
|
} else if (unlikely(res == TX_QUEUED)) {
|
|
I802_DEBUG_INC(tx->local->tx_handlers_queued);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Late handlers can be called while the sta lock is held. Handlers that can
|
|
* cause packets to be generated will cause deadlock!
|
|
*/
|
|
static int invoke_tx_handlers_late(struct ieee80211_tx_data *tx)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
|
|
ieee80211_tx_result res = TX_CONTINUE;
|
|
|
|
if (!ieee80211_hw_check(&tx->local->hw, HAS_RATE_CONTROL))
|
|
CALL_TXH(ieee80211_tx_h_rate_ctrl);
|
|
|
|
if (unlikely(info->flags & IEEE80211_TX_INTFL_RETRANSMISSION)) {
|
|
__skb_queue_tail(&tx->skbs, tx->skb);
|
|
tx->skb = NULL;
|
|
goto txh_done;
|
|
}
|
|
|
|
CALL_TXH(ieee80211_tx_h_michael_mic_add);
|
|
CALL_TXH(ieee80211_tx_h_sequence);
|
|
CALL_TXH(ieee80211_tx_h_fragment);
|
|
/* handlers after fragment must be aware of tx info fragmentation! */
|
|
CALL_TXH(ieee80211_tx_h_stats);
|
|
CALL_TXH(ieee80211_tx_h_encrypt);
|
|
if (!ieee80211_hw_check(&tx->local->hw, HAS_RATE_CONTROL))
|
|
CALL_TXH(ieee80211_tx_h_calculate_duration);
|
|
#undef CALL_TXH
|
|
|
|
txh_done:
|
|
if (unlikely(res == TX_DROP)) {
|
|
I802_DEBUG_INC(tx->local->tx_handlers_drop);
|
|
if (tx->skb)
|
|
ieee80211_free_txskb(&tx->local->hw, tx->skb);
|
|
else
|
|
ieee80211_purge_tx_queue(&tx->local->hw, &tx->skbs);
|
|
return -1;
|
|
} else if (unlikely(res == TX_QUEUED)) {
|
|
I802_DEBUG_INC(tx->local->tx_handlers_queued);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int invoke_tx_handlers(struct ieee80211_tx_data *tx)
|
|
{
|
|
int r = invoke_tx_handlers_early(tx);
|
|
|
|
if (r)
|
|
return r;
|
|
return invoke_tx_handlers_late(tx);
|
|
}
|
|
|
|
bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif, struct sk_buff *skb,
|
|
int band, struct ieee80211_sta **sta)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_tx_data tx;
|
|
struct sk_buff *skb2;
|
|
|
|
if (ieee80211_tx_prepare(sdata, &tx, NULL, skb) == TX_DROP)
|
|
return false;
|
|
|
|
info->band = band;
|
|
info->control.vif = vif;
|
|
info->hw_queue = vif->hw_queue[skb_get_queue_mapping(skb)];
|
|
|
|
if (invoke_tx_handlers(&tx))
|
|
return false;
|
|
|
|
if (sta) {
|
|
if (tx.sta)
|
|
*sta = &tx.sta->sta;
|
|
else
|
|
*sta = NULL;
|
|
}
|
|
|
|
/* this function isn't suitable for fragmented data frames */
|
|
skb2 = __skb_dequeue(&tx.skbs);
|
|
if (WARN_ON(skb2 != skb || !skb_queue_empty(&tx.skbs))) {
|
|
ieee80211_free_txskb(hw, skb2);
|
|
ieee80211_purge_tx_queue(hw, &tx.skbs);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_tx_prepare_skb);
|
|
|
|
/*
|
|
* Returns false if the frame couldn't be transmitted but was queued instead.
|
|
*/
|
|
static bool ieee80211_tx(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb,
|
|
bool txpending)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_tx_data tx;
|
|
ieee80211_tx_result res_prepare;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
bool result = true;
|
|
|
|
if (unlikely(skb->len < 10)) {
|
|
dev_kfree_skb(skb);
|
|
return true;
|
|
}
|
|
|
|
/* initialises tx */
|
|
res_prepare = ieee80211_tx_prepare(sdata, &tx, sta, skb);
|
|
|
|
if (unlikely(res_prepare == TX_DROP)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
return true;
|
|
} else if (unlikely(res_prepare == TX_QUEUED)) {
|
|
return true;
|
|
}
|
|
|
|
/* set up hw_queue value early */
|
|
if (!(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN) ||
|
|
!ieee80211_hw_check(&local->hw, QUEUE_CONTROL))
|
|
info->hw_queue =
|
|
sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
|
|
|
|
if (invoke_tx_handlers_early(&tx))
|
|
return true;
|
|
|
|
if (ieee80211_queue_skb(local, sdata, tx.sta, tx.skb))
|
|
return true;
|
|
|
|
if (!invoke_tx_handlers_late(&tx))
|
|
result = __ieee80211_tx(local, &tx.skbs, tx.sta, txpending);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* device xmit handlers */
|
|
|
|
enum ieee80211_encrypt {
|
|
ENCRYPT_NO,
|
|
ENCRYPT_MGMT,
|
|
ENCRYPT_DATA,
|
|
};
|
|
|
|
static int ieee80211_skb_resize(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb,
|
|
int head_need,
|
|
enum ieee80211_encrypt encrypt)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
bool enc_tailroom;
|
|
int tail_need = 0;
|
|
|
|
enc_tailroom = encrypt == ENCRYPT_MGMT ||
|
|
(encrypt == ENCRYPT_DATA &&
|
|
sdata->crypto_tx_tailroom_needed_cnt);
|
|
|
|
if (enc_tailroom) {
|
|
tail_need = IEEE80211_ENCRYPT_TAILROOM;
|
|
tail_need -= skb_tailroom(skb);
|
|
tail_need = max_t(int, tail_need, 0);
|
|
}
|
|
|
|
if (skb_cloned(skb) &&
|
|
(!ieee80211_hw_check(&local->hw, SUPPORTS_CLONED_SKBS) ||
|
|
!skb_clone_writable(skb, ETH_HLEN) || enc_tailroom))
|
|
I802_DEBUG_INC(local->tx_expand_skb_head_cloned);
|
|
else if (head_need || tail_need)
|
|
I802_DEBUG_INC(local->tx_expand_skb_head);
|
|
else
|
|
return 0;
|
|
|
|
if (pskb_expand_head(skb, head_need, tail_need, GFP_ATOMIC)) {
|
|
wiphy_debug(local->hw.wiphy,
|
|
"failed to reallocate TX buffer\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ieee80211_xmit(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *) skb->data;
|
|
int headroom;
|
|
enum ieee80211_encrypt encrypt;
|
|
|
|
if (info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)
|
|
encrypt = ENCRYPT_NO;
|
|
else if (ieee80211_is_mgmt(hdr->frame_control))
|
|
encrypt = ENCRYPT_MGMT;
|
|
else
|
|
encrypt = ENCRYPT_DATA;
|
|
|
|
headroom = local->tx_headroom;
|
|
if (encrypt != ENCRYPT_NO)
|
|
headroom += IEEE80211_ENCRYPT_HEADROOM;
|
|
headroom -= skb_headroom(skb);
|
|
headroom = max_t(int, 0, headroom);
|
|
|
|
if (ieee80211_skb_resize(sdata, skb, headroom, encrypt)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
return;
|
|
}
|
|
|
|
/* reload after potential resize */
|
|
hdr = (struct ieee80211_hdr *) skb->data;
|
|
info->control.vif = &sdata->vif;
|
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
if (ieee80211_is_data(hdr->frame_control) &&
|
|
is_unicast_ether_addr(hdr->addr1)) {
|
|
if (mesh_nexthop_resolve(sdata, skb))
|
|
return; /* skb queued: don't free */
|
|
} else {
|
|
ieee80211_mps_set_frame_flags(sdata, NULL, hdr);
|
|
}
|
|
}
|
|
|
|
ieee80211_set_qos_hdr(sdata, skb);
|
|
ieee80211_tx(sdata, sta, skb, false);
|
|
}
|
|
|
|
static bool ieee80211_validate_radiotap_len(struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_radiotap_header *rthdr =
|
|
(struct ieee80211_radiotap_header *)skb->data;
|
|
|
|
/* check for not even having the fixed radiotap header part */
|
|
if (unlikely(skb->len < sizeof(struct ieee80211_radiotap_header)))
|
|
return false; /* too short to be possibly valid */
|
|
|
|
/* is it a header version we can trust to find length from? */
|
|
if (unlikely(rthdr->it_version))
|
|
return false; /* only version 0 is supported */
|
|
|
|
/* does the skb contain enough to deliver on the alleged length? */
|
|
if (unlikely(skb->len < ieee80211_get_radiotap_len(skb->data)))
|
|
return false; /* skb too short for claimed rt header extent */
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ieee80211_parse_tx_radiotap(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
|
struct ieee80211_radiotap_iterator iterator;
|
|
struct ieee80211_radiotap_header *rthdr =
|
|
(struct ieee80211_radiotap_header *) skb->data;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
int ret = ieee80211_radiotap_iterator_init(&iterator, rthdr, skb->len,
|
|
NULL);
|
|
u16 txflags;
|
|
u16 rate = 0;
|
|
bool rate_found = false;
|
|
u8 rate_retries = 0;
|
|
u16 rate_flags = 0;
|
|
u8 mcs_known, mcs_flags, mcs_bw;
|
|
u16 vht_known;
|
|
u8 vht_mcs = 0, vht_nss = 0;
|
|
int i;
|
|
|
|
if (!ieee80211_validate_radiotap_len(skb))
|
|
return false;
|
|
|
|
info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT |
|
|
IEEE80211_TX_CTL_DONTFRAG;
|
|
|
|
/*
|
|
* for every radiotap entry that is present
|
|
* (ieee80211_radiotap_iterator_next returns -ENOENT when no more
|
|
* entries present, or -EINVAL on error)
|
|
*/
|
|
|
|
while (!ret) {
|
|
ret = ieee80211_radiotap_iterator_next(&iterator);
|
|
|
|
if (ret)
|
|
continue;
|
|
|
|
/* see if this argument is something we can use */
|
|
switch (iterator.this_arg_index) {
|
|
/*
|
|
* You must take care when dereferencing iterator.this_arg
|
|
* for multibyte types... the pointer is not aligned. Use
|
|
* get_unaligned((type *)iterator.this_arg) to dereference
|
|
* iterator.this_arg for type "type" safely on all arches.
|
|
*/
|
|
case IEEE80211_RADIOTAP_FLAGS:
|
|
if (*iterator.this_arg & IEEE80211_RADIOTAP_F_FCS) {
|
|
/*
|
|
* this indicates that the skb we have been
|
|
* handed has the 32-bit FCS CRC at the end...
|
|
* we should react to that by snipping it off
|
|
* because it will be recomputed and added
|
|
* on transmission
|
|
*/
|
|
if (skb->len < (iterator._max_length + FCS_LEN))
|
|
return false;
|
|
|
|
skb_trim(skb, skb->len - FCS_LEN);
|
|
}
|
|
if (*iterator.this_arg & IEEE80211_RADIOTAP_F_WEP)
|
|
info->flags &= ~IEEE80211_TX_INTFL_DONT_ENCRYPT;
|
|
if (*iterator.this_arg & IEEE80211_RADIOTAP_F_FRAG)
|
|
info->flags &= ~IEEE80211_TX_CTL_DONTFRAG;
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_TX_FLAGS:
|
|
txflags = get_unaligned_le16(iterator.this_arg);
|
|
if (txflags & IEEE80211_RADIOTAP_F_TX_NOACK)
|
|
info->flags |= IEEE80211_TX_CTL_NO_ACK;
|
|
if (txflags & IEEE80211_RADIOTAP_F_TX_NOSEQNO)
|
|
info->control.flags |= IEEE80211_TX_CTRL_NO_SEQNO;
|
|
if (txflags & IEEE80211_RADIOTAP_F_TX_ORDER)
|
|
info->control.flags |=
|
|
IEEE80211_TX_CTRL_DONT_REORDER;
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_RATE:
|
|
rate = *iterator.this_arg;
|
|
rate_flags = 0;
|
|
rate_found = true;
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_ANTENNA:
|
|
/* this can appear multiple times, keep a bitmap */
|
|
info->control.antennas |= BIT(*iterator.this_arg);
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_DATA_RETRIES:
|
|
rate_retries = *iterator.this_arg;
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_MCS:
|
|
mcs_known = iterator.this_arg[0];
|
|
mcs_flags = iterator.this_arg[1];
|
|
if (!(mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_MCS))
|
|
break;
|
|
|
|
rate_found = true;
|
|
rate = iterator.this_arg[2];
|
|
rate_flags = IEEE80211_TX_RC_MCS;
|
|
|
|
if (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_GI &&
|
|
mcs_flags & IEEE80211_RADIOTAP_MCS_SGI)
|
|
rate_flags |= IEEE80211_TX_RC_SHORT_GI;
|
|
|
|
mcs_bw = mcs_flags & IEEE80211_RADIOTAP_MCS_BW_MASK;
|
|
if (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_BW &&
|
|
mcs_bw == IEEE80211_RADIOTAP_MCS_BW_40)
|
|
rate_flags |= IEEE80211_TX_RC_40_MHZ_WIDTH;
|
|
|
|
if (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_FEC &&
|
|
mcs_flags & IEEE80211_RADIOTAP_MCS_FEC_LDPC)
|
|
info->flags |= IEEE80211_TX_CTL_LDPC;
|
|
|
|
if (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_STBC) {
|
|
u8 stbc = u8_get_bits(mcs_flags,
|
|
IEEE80211_RADIOTAP_MCS_STBC_MASK);
|
|
|
|
info->flags |=
|
|
u32_encode_bits(stbc,
|
|
IEEE80211_TX_CTL_STBC);
|
|
}
|
|
break;
|
|
|
|
case IEEE80211_RADIOTAP_VHT:
|
|
vht_known = get_unaligned_le16(iterator.this_arg);
|
|
rate_found = true;
|
|
|
|
rate_flags = IEEE80211_TX_RC_VHT_MCS;
|
|
if ((vht_known & IEEE80211_RADIOTAP_VHT_KNOWN_GI) &&
|
|
(iterator.this_arg[2] &
|
|
IEEE80211_RADIOTAP_VHT_FLAG_SGI))
|
|
rate_flags |= IEEE80211_TX_RC_SHORT_GI;
|
|
if (vht_known &
|
|
IEEE80211_RADIOTAP_VHT_KNOWN_BANDWIDTH) {
|
|
if (iterator.this_arg[3] == 1)
|
|
rate_flags |=
|
|
IEEE80211_TX_RC_40_MHZ_WIDTH;
|
|
else if (iterator.this_arg[3] == 4)
|
|
rate_flags |=
|
|
IEEE80211_TX_RC_80_MHZ_WIDTH;
|
|
else if (iterator.this_arg[3] == 11)
|
|
rate_flags |=
|
|
IEEE80211_TX_RC_160_MHZ_WIDTH;
|
|
}
|
|
|
|
vht_mcs = iterator.this_arg[4] >> 4;
|
|
if (vht_mcs > 11)
|
|
vht_mcs = 0;
|
|
vht_nss = iterator.this_arg[4] & 0xF;
|
|
if (!vht_nss || vht_nss > 8)
|
|
vht_nss = 1;
|
|
break;
|
|
|
|
/*
|
|
* Please update the file
|
|
* Documentation/networking/mac80211-injection.rst
|
|
* when parsing new fields here.
|
|
*/
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != -ENOENT) /* ie, if we didn't simply run out of fields */
|
|
return false;
|
|
|
|
if (rate_found) {
|
|
struct ieee80211_supported_band *sband =
|
|
local->hw.wiphy->bands[info->band];
|
|
|
|
info->control.flags |= IEEE80211_TX_CTRL_RATE_INJECT;
|
|
|
|
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
|
|
info->control.rates[i].idx = -1;
|
|
info->control.rates[i].flags = 0;
|
|
info->control.rates[i].count = 0;
|
|
}
|
|
|
|
if (rate_flags & IEEE80211_TX_RC_MCS) {
|
|
/* reset antennas if not enough */
|
|
if (IEEE80211_HT_MCS_CHAINS(rate) >
|
|
hweight8(info->control.antennas))
|
|
info->control.antennas = 0;
|
|
|
|
info->control.rates[0].idx = rate;
|
|
} else if (rate_flags & IEEE80211_TX_RC_VHT_MCS) {
|
|
/* reset antennas if not enough */
|
|
if (vht_nss > hweight8(info->control.antennas))
|
|
info->control.antennas = 0;
|
|
|
|
ieee80211_rate_set_vht(info->control.rates, vht_mcs,
|
|
vht_nss);
|
|
} else if (sband) {
|
|
for (i = 0; i < sband->n_bitrates; i++) {
|
|
if (rate * 5 != sband->bitrates[i].bitrate)
|
|
continue;
|
|
|
|
info->control.rates[0].idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (info->control.rates[0].idx < 0)
|
|
info->control.flags &= ~IEEE80211_TX_CTRL_RATE_INJECT;
|
|
|
|
info->control.rates[0].flags = rate_flags;
|
|
info->control.rates[0].count = min_t(u8, rate_retries + 1,
|
|
local->hw.max_rate_tries);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_sub_if_data *tmp_sdata, *sdata;
|
|
struct cfg80211_chan_def *chandef;
|
|
u16 len_rthdr;
|
|
int hdrlen;
|
|
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
if (unlikely(!ieee80211_sdata_running(sdata)))
|
|
goto fail;
|
|
|
|
memset(info, 0, sizeof(*info));
|
|
info->flags = IEEE80211_TX_CTL_REQ_TX_STATUS |
|
|
IEEE80211_TX_CTL_INJECTED;
|
|
|
|
/* Sanity-check the length of the radiotap header */
|
|
if (!ieee80211_validate_radiotap_len(skb))
|
|
goto fail;
|
|
|
|
/* we now know there is a radiotap header with a length we can use */
|
|
len_rthdr = ieee80211_get_radiotap_len(skb->data);
|
|
|
|
/*
|
|
* fix up the pointers accounting for the radiotap
|
|
* header still being in there. We are being given
|
|
* a precooked IEEE80211 header so no need for
|
|
* normal processing
|
|
*/
|
|
skb_set_mac_header(skb, len_rthdr);
|
|
/*
|
|
* these are just fixed to the end of the rt area since we
|
|
* don't have any better information and at this point, nobody cares
|
|
*/
|
|
skb_set_network_header(skb, len_rthdr);
|
|
skb_set_transport_header(skb, len_rthdr);
|
|
|
|
if (skb->len < len_rthdr + 2)
|
|
goto fail;
|
|
|
|
hdr = (struct ieee80211_hdr *)(skb->data + len_rthdr);
|
|
hdrlen = ieee80211_hdrlen(hdr->frame_control);
|
|
|
|
if (skb->len < len_rthdr + hdrlen)
|
|
goto fail;
|
|
|
|
/*
|
|
* Initialize skb->protocol if the injected frame is a data frame
|
|
* carrying a rfc1042 header
|
|
*/
|
|
if (ieee80211_is_data(hdr->frame_control) &&
|
|
skb->len >= len_rthdr + hdrlen + sizeof(rfc1042_header) + 2) {
|
|
u8 *payload = (u8 *)hdr + hdrlen;
|
|
|
|
if (ether_addr_equal(payload, rfc1042_header))
|
|
skb->protocol = cpu_to_be16((payload[6] << 8) |
|
|
payload[7]);
|
|
}
|
|
|
|
rcu_read_lock();
|
|
|
|
/*
|
|
* We process outgoing injected frames that have a local address
|
|
* we handle as though they are non-injected frames.
|
|
* This code here isn't entirely correct, the local MAC address
|
|
* isn't always enough to find the interface to use; for proper
|
|
* VLAN support we have an nl80211-based mechanism.
|
|
*
|
|
* This is necessary, for example, for old hostapd versions that
|
|
* don't use nl80211-based management TX/RX.
|
|
*/
|
|
list_for_each_entry_rcu(tmp_sdata, &local->interfaces, list) {
|
|
if (!ieee80211_sdata_running(tmp_sdata))
|
|
continue;
|
|
if (tmp_sdata->vif.type == NL80211_IFTYPE_MONITOR ||
|
|
tmp_sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
|
|
continue;
|
|
if (ether_addr_equal(tmp_sdata->vif.addr, hdr->addr2)) {
|
|
sdata = tmp_sdata;
|
|
break;
|
|
}
|
|
}
|
|
|
|
chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
if (!chanctx_conf) {
|
|
tmp_sdata = rcu_dereference(local->monitor_sdata);
|
|
if (tmp_sdata)
|
|
chanctx_conf =
|
|
rcu_dereference(tmp_sdata->vif.bss_conf.chanctx_conf);
|
|
}
|
|
|
|
if (chanctx_conf)
|
|
chandef = &chanctx_conf->def;
|
|
else
|
|
goto fail_rcu;
|
|
|
|
/*
|
|
* If driver/HW supports IEEE80211_CHAN_CAN_MONITOR we still
|
|
* shouldn't transmit on disabled channels.
|
|
*/
|
|
if (!cfg80211_chandef_usable(local->hw.wiphy, chandef,
|
|
IEEE80211_CHAN_DISABLED))
|
|
goto fail_rcu;
|
|
|
|
/*
|
|
* Frame injection is not allowed if beaconing is not allowed
|
|
* or if we need radar detection. Beaconing is usually not allowed when
|
|
* the mode or operation (Adhoc, AP, Mesh) does not support DFS.
|
|
* Passive scan is also used in world regulatory domains where
|
|
* your country is not known and as such it should be treated as
|
|
* NO TX unless the channel is explicitly allowed in which case
|
|
* your current regulatory domain would not have the passive scan
|
|
* flag.
|
|
*
|
|
* Since AP mode uses monitor interfaces to inject/TX management
|
|
* frames we can make AP mode the exception to this rule once it
|
|
* supports radar detection as its implementation can deal with
|
|
* radar detection by itself. We can do that later by adding a
|
|
* monitor flag interfaces used for AP support.
|
|
*/
|
|
if (!cfg80211_reg_can_beacon(local->hw.wiphy, chandef,
|
|
sdata->vif.type))
|
|
goto fail_rcu;
|
|
|
|
info->band = chandef->chan->band;
|
|
|
|
/* Initialize skb->priority according to frame type and TID class,
|
|
* with respect to the sub interface that the frame will actually
|
|
* be transmitted on. If the DONT_REORDER flag is set, the original
|
|
* skb-priority is preserved to assure frames injected with this
|
|
* flag are not reordered relative to each other.
|
|
*/
|
|
ieee80211_select_queue_80211(sdata, skb, hdr);
|
|
skb_set_queue_mapping(skb, ieee80211_ac_from_tid(skb->priority));
|
|
|
|
/*
|
|
* Process the radiotap header. This will now take into account the
|
|
* selected chandef above to accurately set injection rates and
|
|
* retransmissions.
|
|
*/
|
|
if (!ieee80211_parse_tx_radiotap(skb, dev))
|
|
goto fail_rcu;
|
|
|
|
/* remove the injection radiotap header */
|
|
skb_pull(skb, len_rthdr);
|
|
|
|
ieee80211_xmit(sdata, NULL, skb);
|
|
rcu_read_unlock();
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
fail_rcu:
|
|
rcu_read_unlock();
|
|
fail:
|
|
dev_kfree_skb(skb);
|
|
return NETDEV_TX_OK; /* meaning, we dealt with the skb */
|
|
}
|
|
|
|
static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb)
|
|
{
|
|
u16 ethertype = (skb->data[12] << 8) | skb->data[13];
|
|
|
|
return ethertype == ETH_P_TDLS &&
|
|
skb->len > 14 &&
|
|
skb->data[14] == WLAN_TDLS_SNAP_RFTYPE;
|
|
}
|
|
|
|
int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb,
|
|
struct sta_info **sta_out)
|
|
{
|
|
struct sta_info *sta;
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
sta = rcu_dereference(sdata->u.vlan.sta);
|
|
if (sta) {
|
|
*sta_out = sta;
|
|
return 0;
|
|
} else if (sdata->wdev.use_4addr) {
|
|
return -ENOLINK;
|
|
}
|
|
fallthrough;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_OCB:
|
|
case NL80211_IFTYPE_ADHOC:
|
|
if (is_multicast_ether_addr(skb->data)) {
|
|
*sta_out = ERR_PTR(-ENOENT);
|
|
return 0;
|
|
}
|
|
sta = sta_info_get_bss(sdata, skb->data);
|
|
break;
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
/* determined much later */
|
|
*sta_out = NULL;
|
|
return 0;
|
|
#endif
|
|
case NL80211_IFTYPE_STATION:
|
|
if (sdata->wdev.wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS) {
|
|
sta = sta_info_get(sdata, skb->data);
|
|
if (sta && test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
|
|
if (test_sta_flag(sta,
|
|
WLAN_STA_TDLS_PEER_AUTH)) {
|
|
*sta_out = sta;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* TDLS link during setup - throw out frames to
|
|
* peer. Allow TDLS-setup frames to unauthorized
|
|
* peers for the special case of a link teardown
|
|
* after a TDLS sta is removed due to being
|
|
* unreachable.
|
|
*/
|
|
if (!ieee80211_is_tdls_setup(skb))
|
|
return -EINVAL;
|
|
}
|
|
|
|
}
|
|
|
|
sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
|
|
if (!sta)
|
|
return -ENOLINK;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
*sta_out = sta ?: ERR_PTR(-ENOENT);
|
|
return 0;
|
|
}
|
|
|
|
static u16 ieee80211_store_ack_skb(struct ieee80211_local *local,
|
|
struct sk_buff *skb,
|
|
u32 *info_flags,
|
|
u64 *cookie)
|
|
{
|
|
struct sk_buff *ack_skb;
|
|
u16 info_id = 0;
|
|
|
|
if (skb->sk)
|
|
ack_skb = skb_clone_sk(skb);
|
|
else
|
|
ack_skb = skb_clone(skb, GFP_ATOMIC);
|
|
|
|
if (ack_skb) {
|
|
unsigned long flags;
|
|
int id;
|
|
|
|
spin_lock_irqsave(&local->ack_status_lock, flags);
|
|
id = idr_alloc(&local->ack_status_frames, ack_skb,
|
|
1, 0x2000, GFP_ATOMIC);
|
|
spin_unlock_irqrestore(&local->ack_status_lock, flags);
|
|
|
|
if (id >= 0) {
|
|
info_id = id;
|
|
*info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
if (cookie) {
|
|
*cookie = ieee80211_mgmt_tx_cookie(local);
|
|
IEEE80211_SKB_CB(ack_skb)->ack.cookie = *cookie;
|
|
}
|
|
} else {
|
|
kfree_skb(ack_skb);
|
|
}
|
|
}
|
|
|
|
return info_id;
|
|
}
|
|
|
|
/**
|
|
* ieee80211_build_hdr - build 802.11 header in the given frame
|
|
* @sdata: virtual interface to build the header for
|
|
* @skb: the skb to build the header in
|
|
* @info_flags: skb flags to set
|
|
* @sta: the station pointer
|
|
* @ctrl_flags: info control flags to set
|
|
* @cookie: cookie pointer to fill (if not %NULL)
|
|
*
|
|
* This function takes the skb with 802.3 header and reformats the header to
|
|
* the appropriate IEEE 802.11 header based on which interface the packet is
|
|
* being transmitted on.
|
|
*
|
|
* Note that this function also takes care of the TX status request and
|
|
* potential unsharing of the SKB - this needs to be interleaved with the
|
|
* header building.
|
|
*
|
|
* The function requires the read-side RCU lock held
|
|
*
|
|
* Returns: the (possibly reallocated) skb or an ERR_PTR() code
|
|
*/
|
|
static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, u32 info_flags,
|
|
struct sta_info *sta, u32 ctrl_flags,
|
|
u64 *cookie)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_tx_info *info;
|
|
int head_need;
|
|
u16 ethertype, hdrlen, meshhdrlen = 0;
|
|
__le16 fc;
|
|
struct ieee80211_hdr hdr;
|
|
struct ieee80211s_hdr mesh_hdr __maybe_unused;
|
|
struct mesh_path __maybe_unused *mppath = NULL, *mpath = NULL;
|
|
const u8 *encaps_data;
|
|
int encaps_len, skip_header_bytes;
|
|
bool wme_sta = false, authorized = false;
|
|
bool tdls_peer;
|
|
bool multicast;
|
|
u16 info_id = 0;
|
|
struct ieee80211_chanctx_conf *chanctx_conf = NULL;
|
|
enum nl80211_band band;
|
|
int ret;
|
|
u8 link_id = u32_get_bits(ctrl_flags, IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
if (IS_ERR(sta))
|
|
sta = NULL;
|
|
|
|
#ifdef CONFIG_MAC80211_DEBUGFS
|
|
if (local->force_tx_status)
|
|
info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
#endif
|
|
|
|
/* convert Ethernet header to proper 802.11 header (based on
|
|
* operation mode) */
|
|
ethertype = (skb->data[12] << 8) | skb->data[13];
|
|
fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA);
|
|
|
|
if (!ieee80211_vif_is_mld(&sdata->vif))
|
|
chanctx_conf =
|
|
rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
if (sdata->wdev.use_4addr) {
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS);
|
|
/* RA TA DA SA */
|
|
memcpy(hdr.addr1, sta->sta.addr, ETH_ALEN);
|
|
memcpy(hdr.addr2, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(hdr.addr3, skb->data, ETH_ALEN);
|
|
memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
|
|
hdrlen = 30;
|
|
authorized = test_sta_flag(sta, WLAN_STA_AUTHORIZED);
|
|
wme_sta = sta->sta.wme;
|
|
}
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
struct ieee80211_sub_if_data *ap_sdata;
|
|
|
|
/* override chanctx_conf from AP (we don't have one) */
|
|
ap_sdata = container_of(sdata->bss,
|
|
struct ieee80211_sub_if_data,
|
|
u.ap);
|
|
chanctx_conf =
|
|
rcu_dereference(ap_sdata->vif.bss_conf.chanctx_conf);
|
|
}
|
|
if (sdata->wdev.use_4addr)
|
|
break;
|
|
fallthrough;
|
|
case NL80211_IFTYPE_AP:
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS);
|
|
/* DA BSSID SA */
|
|
memcpy(hdr.addr1, skb->data, ETH_ALEN);
|
|
|
|
if (ieee80211_vif_is_mld(&sdata->vif) && sta && !sta->sta.mlo) {
|
|
struct ieee80211_link_data *link;
|
|
|
|
link_id = sta->deflink.link_id;
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (WARN_ON(!link)) {
|
|
ret = -ENOLINK;
|
|
goto free;
|
|
}
|
|
memcpy(hdr.addr2, link->conf->addr, ETH_ALEN);
|
|
} else if (link_id == IEEE80211_LINK_UNSPECIFIED ||
|
|
(sta && sta->sta.mlo)) {
|
|
memcpy(hdr.addr2, sdata->vif.addr, ETH_ALEN);
|
|
} else {
|
|
struct ieee80211_bss_conf *conf;
|
|
|
|
conf = rcu_dereference(sdata->vif.link_conf[link_id]);
|
|
if (unlikely(!conf)) {
|
|
ret = -ENOLINK;
|
|
goto free;
|
|
}
|
|
|
|
memcpy(hdr.addr2, conf->addr, ETH_ALEN);
|
|
}
|
|
|
|
memcpy(hdr.addr3, skb->data + ETH_ALEN, ETH_ALEN);
|
|
hdrlen = 24;
|
|
break;
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
if (!is_multicast_ether_addr(skb->data)) {
|
|
struct sta_info *next_hop;
|
|
bool mpp_lookup = true;
|
|
|
|
mpath = mesh_path_lookup(sdata, skb->data);
|
|
if (mpath) {
|
|
mpp_lookup = false;
|
|
next_hop = rcu_dereference(mpath->next_hop);
|
|
if (!next_hop ||
|
|
!(mpath->flags & (MESH_PATH_ACTIVE |
|
|
MESH_PATH_RESOLVING)))
|
|
mpp_lookup = true;
|
|
}
|
|
|
|
if (mpp_lookup) {
|
|
mppath = mpp_path_lookup(sdata, skb->data);
|
|
if (mppath)
|
|
mppath->exp_time = jiffies;
|
|
}
|
|
|
|
if (mppath && mpath)
|
|
mesh_path_del(sdata, mpath->dst);
|
|
}
|
|
|
|
/*
|
|
* Use address extension if it is a packet from
|
|
* another interface or if we know the destination
|
|
* is being proxied by a portal (i.e. portal address
|
|
* differs from proxied address)
|
|
*/
|
|
if (ether_addr_equal(sdata->vif.addr, skb->data + ETH_ALEN) &&
|
|
!(mppath && !ether_addr_equal(mppath->mpp, skb->data))) {
|
|
hdrlen = ieee80211_fill_mesh_addresses(&hdr, &fc,
|
|
skb->data, skb->data + ETH_ALEN);
|
|
meshhdrlen = ieee80211_new_mesh_header(sdata, &mesh_hdr,
|
|
NULL, NULL);
|
|
} else {
|
|
/* DS -> MBSS (802.11-2012 13.11.3.3).
|
|
* For unicast with unknown forwarding information,
|
|
* destination might be in the MBSS or if that fails
|
|
* forwarded to another mesh gate. In either case
|
|
* resolution will be handled in ieee80211_xmit(), so
|
|
* leave the original DA. This also works for mcast */
|
|
const u8 *mesh_da = skb->data;
|
|
|
|
if (mppath)
|
|
mesh_da = mppath->mpp;
|
|
else if (mpath)
|
|
mesh_da = mpath->dst;
|
|
|
|
hdrlen = ieee80211_fill_mesh_addresses(&hdr, &fc,
|
|
mesh_da, sdata->vif.addr);
|
|
if (is_multicast_ether_addr(mesh_da))
|
|
/* DA TA mSA AE:SA */
|
|
meshhdrlen = ieee80211_new_mesh_header(
|
|
sdata, &mesh_hdr,
|
|
skb->data + ETH_ALEN, NULL);
|
|
else
|
|
/* RA TA mDA mSA AE:DA SA */
|
|
meshhdrlen = ieee80211_new_mesh_header(
|
|
sdata, &mesh_hdr, skb->data,
|
|
skb->data + ETH_ALEN);
|
|
|
|
}
|
|
|
|
/* For injected frames, fill RA right away as nexthop lookup
|
|
* will be skipped.
|
|
*/
|
|
if ((ctrl_flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP) &&
|
|
is_zero_ether_addr(hdr.addr1))
|
|
memcpy(hdr.addr1, skb->data, ETH_ALEN);
|
|
break;
|
|
#endif
|
|
case NL80211_IFTYPE_STATION:
|
|
/* we already did checks when looking up the RA STA */
|
|
tdls_peer = test_sta_flag(sta, WLAN_STA_TDLS_PEER);
|
|
|
|
if (tdls_peer) {
|
|
/* For TDLS only one link can be valid with peer STA */
|
|
int tdls_link_id = ieee80211_tdls_sta_link_id(sta);
|
|
struct ieee80211_link_data *link;
|
|
|
|
/* DA SA BSSID */
|
|
memcpy(hdr.addr1, skb->data, ETH_ALEN);
|
|
memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
|
|
link = rcu_dereference(sdata->link[tdls_link_id]);
|
|
if (WARN_ON_ONCE(!link)) {
|
|
ret = -EINVAL;
|
|
goto free;
|
|
}
|
|
memcpy(hdr.addr3, link->u.mgd.bssid, ETH_ALEN);
|
|
hdrlen = 24;
|
|
} else if (sdata->u.mgd.use_4addr &&
|
|
cpu_to_be16(ethertype) != sdata->control_port_protocol) {
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS |
|
|
IEEE80211_FCTL_TODS);
|
|
/* RA TA DA SA */
|
|
memcpy(hdr.addr1, sdata->deflink.u.mgd.bssid, ETH_ALEN);
|
|
memcpy(hdr.addr2, sdata->vif.addr, ETH_ALEN);
|
|
memcpy(hdr.addr3, skb->data, ETH_ALEN);
|
|
memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
|
|
hdrlen = 30;
|
|
} else {
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_TODS);
|
|
/* BSSID SA DA */
|
|
memcpy(hdr.addr1, sdata->vif.cfg.ap_addr, ETH_ALEN);
|
|
memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
|
|
memcpy(hdr.addr3, skb->data, ETH_ALEN);
|
|
hdrlen = 24;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_OCB:
|
|
/* DA SA BSSID */
|
|
memcpy(hdr.addr1, skb->data, ETH_ALEN);
|
|
memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
|
|
eth_broadcast_addr(hdr.addr3);
|
|
hdrlen = 24;
|
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
/* DA SA BSSID */
|
|
memcpy(hdr.addr1, skb->data, ETH_ALEN);
|
|
memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
|
|
memcpy(hdr.addr3, sdata->u.ibss.bssid, ETH_ALEN);
|
|
hdrlen = 24;
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
goto free;
|
|
}
|
|
|
|
if (!chanctx_conf) {
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
ret = -ENOTCONN;
|
|
goto free;
|
|
}
|
|
/* MLD transmissions must not rely on the band */
|
|
band = 0;
|
|
} else {
|
|
band = chanctx_conf->def.chan->band;
|
|
}
|
|
|
|
multicast = is_multicast_ether_addr(hdr.addr1);
|
|
|
|
/* sta is always NULL for mesh */
|
|
if (sta) {
|
|
authorized = test_sta_flag(sta, WLAN_STA_AUTHORIZED);
|
|
wme_sta = sta->sta.wme;
|
|
} else if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
/* For mesh, the use of the QoS header is mandatory */
|
|
wme_sta = true;
|
|
}
|
|
|
|
/* receiver does QoS (which also means we do) use it */
|
|
if (wme_sta) {
|
|
fc |= cpu_to_le16(IEEE80211_STYPE_QOS_DATA);
|
|
hdrlen += 2;
|
|
}
|
|
|
|
/*
|
|
* Drop unicast frames to unauthorised stations unless they are
|
|
* EAPOL frames from the local station.
|
|
*/
|
|
if (unlikely(!ieee80211_vif_is_mesh(&sdata->vif) &&
|
|
(sdata->vif.type != NL80211_IFTYPE_OCB) &&
|
|
!multicast && !authorized &&
|
|
(cpu_to_be16(ethertype) != sdata->control_port_protocol ||
|
|
!ieee80211_is_our_addr(sdata, skb->data + ETH_ALEN, NULL)))) {
|
|
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
|
net_info_ratelimited("%s: dropped frame to %pM (unauthorized port)\n",
|
|
sdata->name, hdr.addr1);
|
|
#endif
|
|
|
|
I802_DEBUG_INC(local->tx_handlers_drop_unauth_port);
|
|
|
|
ret = -EPERM;
|
|
goto free;
|
|
}
|
|
|
|
if (unlikely(!multicast &&
|
|
((skb->sk &&
|
|
skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS) ||
|
|
ctrl_flags & IEEE80211_TX_CTL_REQ_TX_STATUS)))
|
|
info_id = ieee80211_store_ack_skb(local, skb, &info_flags,
|
|
cookie);
|
|
|
|
/*
|
|
* If the skb is shared we need to obtain our own copy.
|
|
*/
|
|
skb = skb_share_check(skb, GFP_ATOMIC);
|
|
if (unlikely(!skb)) {
|
|
ret = -ENOMEM;
|
|
goto free;
|
|
}
|
|
|
|
hdr.frame_control = fc;
|
|
hdr.duration_id = 0;
|
|
hdr.seq_ctrl = 0;
|
|
|
|
skip_header_bytes = ETH_HLEN;
|
|
if (ethertype == ETH_P_AARP || ethertype == ETH_P_IPX) {
|
|
encaps_data = bridge_tunnel_header;
|
|
encaps_len = sizeof(bridge_tunnel_header);
|
|
skip_header_bytes -= 2;
|
|
} else if (ethertype >= ETH_P_802_3_MIN) {
|
|
encaps_data = rfc1042_header;
|
|
encaps_len = sizeof(rfc1042_header);
|
|
skip_header_bytes -= 2;
|
|
} else {
|
|
encaps_data = NULL;
|
|
encaps_len = 0;
|
|
}
|
|
|
|
skb_pull(skb, skip_header_bytes);
|
|
head_need = hdrlen + encaps_len + meshhdrlen - skb_headroom(skb);
|
|
|
|
/*
|
|
* So we need to modify the skb header and hence need a copy of
|
|
* that. The head_need variable above doesn't, so far, include
|
|
* the needed header space that we don't need right away. If we
|
|
* can, then we don't reallocate right now but only after the
|
|
* frame arrives at the master device (if it does...)
|
|
*
|
|
* If we cannot, however, then we will reallocate to include all
|
|
* the ever needed space. Also, if we need to reallocate it anyway,
|
|
* make it big enough for everything we may ever need.
|
|
*/
|
|
|
|
if (head_need > 0 || skb_cloned(skb)) {
|
|
head_need += IEEE80211_ENCRYPT_HEADROOM;
|
|
head_need += local->tx_headroom;
|
|
head_need = max_t(int, 0, head_need);
|
|
if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
skb = NULL;
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
}
|
|
|
|
if (encaps_data)
|
|
memcpy(skb_push(skb, encaps_len), encaps_data, encaps_len);
|
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
if (meshhdrlen > 0)
|
|
memcpy(skb_push(skb, meshhdrlen), &mesh_hdr, meshhdrlen);
|
|
#endif
|
|
|
|
if (ieee80211_is_data_qos(fc)) {
|
|
__le16 *qos_control;
|
|
|
|
qos_control = skb_push(skb, 2);
|
|
memcpy(skb_push(skb, hdrlen - 2), &hdr, hdrlen - 2);
|
|
/*
|
|
* Maybe we could actually set some fields here, for now just
|
|
* initialise to zero to indicate no special operation.
|
|
*/
|
|
*qos_control = 0;
|
|
} else
|
|
memcpy(skb_push(skb, hdrlen), &hdr, hdrlen);
|
|
|
|
skb_reset_mac_header(skb);
|
|
|
|
info = IEEE80211_SKB_CB(skb);
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->flags = info_flags;
|
|
if (info_id) {
|
|
info->status_data = info_id;
|
|
info->status_data_idr = 1;
|
|
}
|
|
info->band = band;
|
|
|
|
if (likely(!cookie)) {
|
|
ctrl_flags |= u32_encode_bits(link_id,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
} else {
|
|
unsigned int pre_conf_link_id;
|
|
|
|
/*
|
|
* ctrl_flags already have been set by
|
|
* ieee80211_tx_control_port(), here
|
|
* we just sanity check that
|
|
*/
|
|
|
|
pre_conf_link_id = u32_get_bits(ctrl_flags,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
if (pre_conf_link_id != link_id &&
|
|
link_id != IEEE80211_LINK_UNSPECIFIED) {
|
|
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
|
|
net_info_ratelimited("%s: dropped frame to %pM with bad link ID request (%d vs. %d)\n",
|
|
sdata->name, hdr.addr1,
|
|
pre_conf_link_id, link_id);
|
|
#endif
|
|
ret = -EINVAL;
|
|
goto free;
|
|
}
|
|
}
|
|
|
|
info->control.flags = ctrl_flags;
|
|
|
|
return skb;
|
|
free:
|
|
kfree_skb(skb);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/*
|
|
* fast-xmit overview
|
|
*
|
|
* The core idea of this fast-xmit is to remove per-packet checks by checking
|
|
* them out of band. ieee80211_check_fast_xmit() implements the out-of-band
|
|
* checks that are needed to get the sta->fast_tx pointer assigned, after which
|
|
* much less work can be done per packet. For example, fragmentation must be
|
|
* disabled or the fast_tx pointer will not be set. All the conditions are seen
|
|
* in the code here.
|
|
*
|
|
* Once assigned, the fast_tx data structure also caches the per-packet 802.11
|
|
* header and other data to aid packet processing in ieee80211_xmit_fast().
|
|
*
|
|
* The most difficult part of this is that when any of these assumptions
|
|
* change, an external trigger (i.e. a call to ieee80211_clear_fast_xmit(),
|
|
* ieee80211_check_fast_xmit() or friends) is required to reset the data,
|
|
* since the per-packet code no longer checks the conditions. This is reflected
|
|
* by the calls to these functions throughout the rest of the code, and must be
|
|
* maintained if any of the TX path checks change.
|
|
*/
|
|
|
|
void ieee80211_check_fast_xmit(struct sta_info *sta)
|
|
{
|
|
struct ieee80211_fast_tx build = {}, *fast_tx = NULL, *old;
|
|
struct ieee80211_local *local = sta->local;
|
|
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
|
struct ieee80211_hdr *hdr = (void *)build.hdr;
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
__le16 fc;
|
|
|
|
if (!ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT))
|
|
return;
|
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif))
|
|
mesh_fast_tx_flush_sta(sdata, sta);
|
|
|
|
/* Locking here protects both the pointer itself, and against concurrent
|
|
* invocations winning data access races to, e.g., the key pointer that
|
|
* is used.
|
|
* Without it, the invocation of this function right after the key
|
|
* pointer changes wouldn't be sufficient, as another CPU could access
|
|
* the pointer, then stall, and then do the cache update after the CPU
|
|
* that invalidated the key.
|
|
* With the locking, such scenarios cannot happen as the check for the
|
|
* key and the fast-tx assignment are done atomically, so the CPU that
|
|
* modifies the key will either wait or other one will see the key
|
|
* cleared/changed already.
|
|
*/
|
|
spin_lock_bh(&sta->lock);
|
|
if (ieee80211_hw_check(&local->hw, SUPPORTS_PS) &&
|
|
!ieee80211_hw_check(&local->hw, SUPPORTS_DYNAMIC_PS) &&
|
|
sdata->vif.type == NL80211_IFTYPE_STATION)
|
|
goto out;
|
|
|
|
if (!test_sta_flag(sta, WLAN_STA_AUTHORIZED) || !sta->uploaded)
|
|
goto out;
|
|
|
|
if (test_sta_flag(sta, WLAN_STA_PS_STA) ||
|
|
test_sta_flag(sta, WLAN_STA_PS_DRIVER) ||
|
|
test_sta_flag(sta, WLAN_STA_PS_DELIVER) ||
|
|
test_sta_flag(sta, WLAN_STA_CLEAR_PS_FILT))
|
|
goto out;
|
|
|
|
if (sdata->noack_map)
|
|
goto out;
|
|
|
|
/* fast-xmit doesn't handle fragmentation at all */
|
|
if (local->hw.wiphy->frag_threshold != (u32)-1 &&
|
|
!ieee80211_hw_check(&local->hw, SUPPORTS_TX_FRAG))
|
|
goto out;
|
|
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
rcu_read_lock();
|
|
chanctx_conf =
|
|
rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
if (!chanctx_conf) {
|
|
rcu_read_unlock();
|
|
goto out;
|
|
}
|
|
build.band = chanctx_conf->def.chan->band;
|
|
rcu_read_unlock();
|
|
} else {
|
|
/* MLD transmissions must not rely on the band */
|
|
build.band = 0;
|
|
}
|
|
|
|
fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA);
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_ADHOC:
|
|
/* DA SA BSSID */
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr1);
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr2);
|
|
memcpy(hdr->addr3, sdata->u.ibss.bssid, ETH_ALEN);
|
|
build.hdr_len = 24;
|
|
break;
|
|
case NL80211_IFTYPE_STATION:
|
|
if (test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
|
|
/* For TDLS only one link can be valid with peer STA */
|
|
int tdls_link_id = ieee80211_tdls_sta_link_id(sta);
|
|
struct ieee80211_link_data *link;
|
|
|
|
/* DA SA BSSID */
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr1);
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr2);
|
|
rcu_read_lock();
|
|
link = rcu_dereference(sdata->link[tdls_link_id]);
|
|
if (!WARN_ON_ONCE(!link))
|
|
memcpy(hdr->addr3, link->u.mgd.bssid, ETH_ALEN);
|
|
rcu_read_unlock();
|
|
build.hdr_len = 24;
|
|
break;
|
|
}
|
|
|
|
if (sdata->u.mgd.use_4addr) {
|
|
/* non-regular ethertype cannot use the fastpath */
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS |
|
|
IEEE80211_FCTL_TODS);
|
|
/* RA TA DA SA */
|
|
memcpy(hdr->addr1, sdata->deflink.u.mgd.bssid, ETH_ALEN);
|
|
memcpy(hdr->addr2, sdata->vif.addr, ETH_ALEN);
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr3);
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr4);
|
|
build.hdr_len = 30;
|
|
break;
|
|
}
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_TODS);
|
|
/* BSSID SA DA */
|
|
memcpy(hdr->addr1, sdata->vif.cfg.ap_addr, ETH_ALEN);
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr3);
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr2);
|
|
build.hdr_len = 24;
|
|
break;
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
if (sdata->wdev.use_4addr) {
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS |
|
|
IEEE80211_FCTL_TODS);
|
|
/* RA TA DA SA */
|
|
memcpy(hdr->addr1, sta->sta.addr, ETH_ALEN);
|
|
memcpy(hdr->addr2, sdata->vif.addr, ETH_ALEN);
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr3);
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr4);
|
|
build.hdr_len = 30;
|
|
break;
|
|
}
|
|
fallthrough;
|
|
case NL80211_IFTYPE_AP:
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS);
|
|
/* DA BSSID SA */
|
|
build.da_offs = offsetof(struct ieee80211_hdr, addr1);
|
|
if (sta->sta.mlo || !ieee80211_vif_is_mld(&sdata->vif)) {
|
|
memcpy(hdr->addr2, sdata->vif.addr, ETH_ALEN);
|
|
} else {
|
|
unsigned int link_id = sta->deflink.link_id;
|
|
struct ieee80211_link_data *link;
|
|
|
|
rcu_read_lock();
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (WARN_ON(!link)) {
|
|
rcu_read_unlock();
|
|
goto out;
|
|
}
|
|
memcpy(hdr->addr2, link->conf->addr, ETH_ALEN);
|
|
rcu_read_unlock();
|
|
}
|
|
build.sa_offs = offsetof(struct ieee80211_hdr, addr3);
|
|
build.hdr_len = 24;
|
|
break;
|
|
default:
|
|
/* not handled on fast-xmit */
|
|
goto out;
|
|
}
|
|
|
|
if (sta->sta.wme) {
|
|
build.hdr_len += 2;
|
|
fc |= cpu_to_le16(IEEE80211_STYPE_QOS_DATA);
|
|
}
|
|
|
|
/* We store the key here so there's no point in using rcu_dereference()
|
|
* but that's fine because the code that changes the pointers will call
|
|
* this function after doing so. For a single CPU that would be enough,
|
|
* for multiple see the comment above.
|
|
*/
|
|
build.key = rcu_access_pointer(sta->ptk[sta->ptk_idx]);
|
|
if (!build.key)
|
|
build.key = rcu_access_pointer(sdata->default_unicast_key);
|
|
if (build.key) {
|
|
bool gen_iv, iv_spc, mmic;
|
|
|
|
gen_iv = build.key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV;
|
|
iv_spc = build.key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE;
|
|
mmic = build.key->conf.flags &
|
|
(IEEE80211_KEY_FLAG_GENERATE_MMIC |
|
|
IEEE80211_KEY_FLAG_PUT_MIC_SPACE);
|
|
|
|
/* don't handle software crypto */
|
|
if (!(build.key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
|
|
goto out;
|
|
|
|
/* Key is being removed */
|
|
if (build.key->flags & KEY_FLAG_TAINTED)
|
|
goto out;
|
|
|
|
switch (build.key->conf.cipher) {
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_CCMP_256:
|
|
if (gen_iv)
|
|
build.pn_offs = build.hdr_len;
|
|
if (gen_iv || iv_spc)
|
|
build.hdr_len += IEEE80211_CCMP_HDR_LEN;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
if (gen_iv)
|
|
build.pn_offs = build.hdr_len;
|
|
if (gen_iv || iv_spc)
|
|
build.hdr_len += IEEE80211_GCMP_HDR_LEN;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_TKIP:
|
|
/* cannot handle MMIC or IV generation in xmit-fast */
|
|
if (mmic || gen_iv)
|
|
goto out;
|
|
if (iv_spc)
|
|
build.hdr_len += IEEE80211_TKIP_IV_LEN;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_WEP40:
|
|
case WLAN_CIPHER_SUITE_WEP104:
|
|
/* cannot handle IV generation in fast-xmit */
|
|
if (gen_iv)
|
|
goto out;
|
|
if (iv_spc)
|
|
build.hdr_len += IEEE80211_WEP_IV_LEN;
|
|
break;
|
|
case WLAN_CIPHER_SUITE_AES_CMAC:
|
|
case WLAN_CIPHER_SUITE_BIP_CMAC_256:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_128:
|
|
case WLAN_CIPHER_SUITE_BIP_GMAC_256:
|
|
WARN(1,
|
|
"management cipher suite 0x%x enabled for data\n",
|
|
build.key->conf.cipher);
|
|
goto out;
|
|
default:
|
|
/* we don't know how to generate IVs for this at all */
|
|
if (WARN_ON(gen_iv))
|
|
goto out;
|
|
}
|
|
|
|
fc |= cpu_to_le16(IEEE80211_FCTL_PROTECTED);
|
|
}
|
|
|
|
hdr->frame_control = fc;
|
|
|
|
memcpy(build.hdr + build.hdr_len,
|
|
rfc1042_header, sizeof(rfc1042_header));
|
|
build.hdr_len += sizeof(rfc1042_header);
|
|
|
|
fast_tx = kmemdup(&build, sizeof(build), GFP_ATOMIC);
|
|
/* if the kmemdup fails, continue w/o fast_tx */
|
|
|
|
out:
|
|
/* we might have raced against another call to this function */
|
|
old = rcu_dereference_protected(sta->fast_tx,
|
|
lockdep_is_held(&sta->lock));
|
|
rcu_assign_pointer(sta->fast_tx, fast_tx);
|
|
if (old)
|
|
kfree_rcu(old, rcu_head);
|
|
spin_unlock_bh(&sta->lock);
|
|
}
|
|
|
|
void ieee80211_check_fast_xmit_all(struct ieee80211_local *local)
|
|
{
|
|
struct sta_info *sta;
|
|
|
|
rcu_read_lock();
|
|
list_for_each_entry_rcu(sta, &local->sta_list, list)
|
|
ieee80211_check_fast_xmit(sta);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
void ieee80211_check_fast_xmit_iface(struct ieee80211_sub_if_data *sdata)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
|
|
rcu_read_lock();
|
|
|
|
list_for_each_entry_rcu(sta, &local->sta_list, list) {
|
|
if (sdata != sta->sdata &&
|
|
(!sta->sdata->bss || sta->sdata->bss != sdata->bss))
|
|
continue;
|
|
ieee80211_check_fast_xmit(sta);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
void ieee80211_clear_fast_xmit(struct sta_info *sta)
|
|
{
|
|
struct ieee80211_fast_tx *fast_tx;
|
|
|
|
spin_lock_bh(&sta->lock);
|
|
fast_tx = rcu_dereference_protected(sta->fast_tx,
|
|
lockdep_is_held(&sta->lock));
|
|
RCU_INIT_POINTER(sta->fast_tx, NULL);
|
|
spin_unlock_bh(&sta->lock);
|
|
|
|
if (fast_tx)
|
|
kfree_rcu(fast_tx, rcu_head);
|
|
}
|
|
|
|
static bool ieee80211_amsdu_realloc_pad(struct ieee80211_local *local,
|
|
struct sk_buff *skb, int headroom)
|
|
{
|
|
if (skb_headroom(skb) < headroom) {
|
|
I802_DEBUG_INC(local->tx_expand_skb_head);
|
|
|
|
if (pskb_expand_head(skb, headroom, 0, GFP_ATOMIC)) {
|
|
wiphy_debug(local->hw.wiphy,
|
|
"failed to reallocate TX buffer\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ieee80211_amsdu_prepare_head(struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_fast_tx *fast_tx,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr;
|
|
struct ethhdr *amsdu_hdr;
|
|
int hdr_len = fast_tx->hdr_len - sizeof(rfc1042_header);
|
|
int subframe_len = skb->len - hdr_len;
|
|
void *data;
|
|
u8 *qc, *h_80211_src, *h_80211_dst;
|
|
const u8 *bssid;
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)
|
|
return false;
|
|
|
|
if (info->control.flags & IEEE80211_TX_CTRL_AMSDU)
|
|
return true;
|
|
|
|
if (!ieee80211_amsdu_realloc_pad(local, skb,
|
|
sizeof(*amsdu_hdr) +
|
|
local->hw.extra_tx_headroom))
|
|
return false;
|
|
|
|
data = skb_push(skb, sizeof(*amsdu_hdr));
|
|
memmove(data, data + sizeof(*amsdu_hdr), hdr_len);
|
|
hdr = data;
|
|
amsdu_hdr = data + hdr_len;
|
|
/* h_80211_src/dst is addr* field within hdr */
|
|
h_80211_src = data + fast_tx->sa_offs;
|
|
h_80211_dst = data + fast_tx->da_offs;
|
|
|
|
amsdu_hdr->h_proto = cpu_to_be16(subframe_len);
|
|
ether_addr_copy(amsdu_hdr->h_source, h_80211_src);
|
|
ether_addr_copy(amsdu_hdr->h_dest, h_80211_dst);
|
|
|
|
/* according to IEEE 802.11-2012 8.3.2 table 8-19, the outer SA/DA
|
|
* fields needs to be changed to BSSID for A-MSDU frames depending
|
|
* on FromDS/ToDS values.
|
|
*/
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
bssid = sdata->vif.cfg.ap_addr;
|
|
break;
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
bssid = sdata->vif.addr;
|
|
break;
|
|
default:
|
|
bssid = NULL;
|
|
}
|
|
|
|
if (bssid && ieee80211_has_fromds(hdr->frame_control))
|
|
ether_addr_copy(h_80211_src, bssid);
|
|
|
|
if (bssid && ieee80211_has_tods(hdr->frame_control))
|
|
ether_addr_copy(h_80211_dst, bssid);
|
|
|
|
qc = ieee80211_get_qos_ctl(hdr);
|
|
*qc |= IEEE80211_QOS_CTL_A_MSDU_PRESENT;
|
|
|
|
info->control.flags |= IEEE80211_TX_CTRL_AMSDU;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ieee80211_amsdu_aggregate(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta,
|
|
struct ieee80211_fast_tx *fast_tx,
|
|
struct sk_buff *skb,
|
|
const u8 *da, const u8 *sa)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct fq *fq = &local->fq;
|
|
struct fq_tin *tin;
|
|
struct fq_flow *flow;
|
|
u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
|
|
struct ieee80211_txq *txq = sta->sta.txq[tid];
|
|
struct txq_info *txqi;
|
|
struct sk_buff **frag_tail, *head;
|
|
int subframe_len = skb->len - ETH_ALEN;
|
|
u8 max_subframes = sta->sta.max_amsdu_subframes;
|
|
int max_frags = local->hw.max_tx_fragments;
|
|
int max_amsdu_len = sta->sta.cur->max_amsdu_len;
|
|
int orig_truesize;
|
|
u32 flow_idx;
|
|
__be16 len;
|
|
void *data;
|
|
bool ret = false;
|
|
unsigned int orig_len;
|
|
int n = 2, nfrags, pad = 0;
|
|
u16 hdrlen;
|
|
|
|
if (!ieee80211_hw_check(&local->hw, TX_AMSDU))
|
|
return false;
|
|
|
|
if (sdata->vif.offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED)
|
|
return false;
|
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif))
|
|
return false;
|
|
|
|
if (skb_is_gso(skb))
|
|
return false;
|
|
|
|
if (!txq)
|
|
return false;
|
|
|
|
txqi = to_txq_info(txq);
|
|
if (test_bit(IEEE80211_TXQ_NO_AMSDU, &txqi->flags))
|
|
return false;
|
|
|
|
if (sta->sta.cur->max_rc_amsdu_len)
|
|
max_amsdu_len = min_t(int, max_amsdu_len,
|
|
sta->sta.cur->max_rc_amsdu_len);
|
|
|
|
if (sta->sta.cur->max_tid_amsdu_len[tid])
|
|
max_amsdu_len = min_t(int, max_amsdu_len,
|
|
sta->sta.cur->max_tid_amsdu_len[tid]);
|
|
|
|
flow_idx = fq_flow_idx(fq, skb);
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
|
|
/* TODO: Ideally aggregation should be done on dequeue to remain
|
|
* responsive to environment changes.
|
|
*/
|
|
|
|
tin = &txqi->tin;
|
|
flow = fq_flow_classify(fq, tin, flow_idx, skb);
|
|
head = skb_peek_tail(&flow->queue);
|
|
if (!head || skb_is_gso(head))
|
|
goto out;
|
|
|
|
orig_truesize = head->truesize;
|
|
orig_len = head->len;
|
|
|
|
if (skb->len + head->len > max_amsdu_len)
|
|
goto out;
|
|
|
|
nfrags = 1 + skb_shinfo(skb)->nr_frags;
|
|
nfrags += 1 + skb_shinfo(head)->nr_frags;
|
|
frag_tail = &skb_shinfo(head)->frag_list;
|
|
while (*frag_tail) {
|
|
nfrags += 1 + skb_shinfo(*frag_tail)->nr_frags;
|
|
frag_tail = &(*frag_tail)->next;
|
|
n++;
|
|
}
|
|
|
|
if (max_subframes && n > max_subframes)
|
|
goto out;
|
|
|
|
if (max_frags && nfrags > max_frags)
|
|
goto out;
|
|
|
|
if (!drv_can_aggregate_in_amsdu(local, head, skb))
|
|
goto out;
|
|
|
|
if (!ieee80211_amsdu_prepare_head(sdata, fast_tx, head))
|
|
goto out;
|
|
|
|
/* If n == 2, the "while (*frag_tail)" loop above didn't execute
|
|
* and frag_tail should be &skb_shinfo(head)->frag_list.
|
|
* However, ieee80211_amsdu_prepare_head() can reallocate it.
|
|
* Reload frag_tail to have it pointing to the correct place.
|
|
*/
|
|
if (n == 2)
|
|
frag_tail = &skb_shinfo(head)->frag_list;
|
|
|
|
/*
|
|
* Pad out the previous subframe to a multiple of 4 by adding the
|
|
* padding to the next one, that's being added. Note that head->len
|
|
* is the length of the full A-MSDU, but that works since each time
|
|
* we add a new subframe we pad out the previous one to a multiple
|
|
* of 4 and thus it no longer matters in the next round.
|
|
*/
|
|
hdrlen = fast_tx->hdr_len - sizeof(rfc1042_header);
|
|
if ((head->len - hdrlen) & 3)
|
|
pad = 4 - ((head->len - hdrlen) & 3);
|
|
|
|
if (!ieee80211_amsdu_realloc_pad(local, skb, sizeof(rfc1042_header) +
|
|
2 + pad))
|
|
goto out_recalc;
|
|
|
|
ret = true;
|
|
data = skb_push(skb, ETH_ALEN + 2);
|
|
ether_addr_copy(data, da);
|
|
ether_addr_copy(data + ETH_ALEN, sa);
|
|
|
|
data += 2 * ETH_ALEN;
|
|
len = cpu_to_be16(subframe_len);
|
|
memcpy(data, &len, 2);
|
|
memcpy(data + 2, rfc1042_header, sizeof(rfc1042_header));
|
|
|
|
memset(skb_push(skb, pad), 0, pad);
|
|
|
|
head->len += skb->len;
|
|
head->data_len += skb->len;
|
|
*frag_tail = skb;
|
|
|
|
out_recalc:
|
|
fq->memory_usage += head->truesize - orig_truesize;
|
|
if (head->len != orig_len) {
|
|
flow->backlog += head->len - orig_len;
|
|
tin->backlog_bytes += head->len - orig_len;
|
|
}
|
|
out:
|
|
spin_unlock_bh(&fq->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Can be called while the sta lock is held. Anything that can cause packets to
|
|
* be generated will cause deadlock!
|
|
*/
|
|
static ieee80211_tx_result
|
|
ieee80211_xmit_fast_finish(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta, u8 pn_offs,
|
|
struct ieee80211_key *key,
|
|
struct ieee80211_tx_data *tx)
|
|
{
|
|
struct sk_buff *skb = tx->skb;
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_hdr *hdr = (void *)skb->data;
|
|
u8 tid = IEEE80211_NUM_TIDS;
|
|
|
|
if (!ieee80211_hw_check(&tx->local->hw, HAS_RATE_CONTROL) &&
|
|
ieee80211_tx_h_rate_ctrl(tx) != TX_CONTINUE)
|
|
return TX_DROP;
|
|
|
|
if (key)
|
|
info->control.hw_key = &key->conf;
|
|
|
|
dev_sw_netstats_tx_add(skb->dev, 1, skb->len);
|
|
|
|
if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) {
|
|
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
|
|
hdr->seq_ctrl = ieee80211_tx_next_seq(sta, tid);
|
|
} else {
|
|
info->flags |= IEEE80211_TX_CTL_ASSIGN_SEQ;
|
|
hdr->seq_ctrl = cpu_to_le16(sdata->sequence_number);
|
|
sdata->sequence_number += 0x10;
|
|
}
|
|
|
|
if (skb_shinfo(skb)->gso_size)
|
|
sta->deflink.tx_stats.msdu[tid] +=
|
|
DIV_ROUND_UP(skb->len, skb_shinfo(skb)->gso_size);
|
|
else
|
|
sta->deflink.tx_stats.msdu[tid]++;
|
|
|
|
info->hw_queue = sdata->vif.hw_queue[skb_get_queue_mapping(skb)];
|
|
|
|
/* statistics normally done by ieee80211_tx_h_stats (but that
|
|
* has to consider fragmentation, so is more complex)
|
|
*/
|
|
sta->deflink.tx_stats.bytes[skb_get_queue_mapping(skb)] += skb->len;
|
|
sta->deflink.tx_stats.packets[skb_get_queue_mapping(skb)]++;
|
|
|
|
if (pn_offs) {
|
|
u64 pn;
|
|
u8 *crypto_hdr = skb->data + pn_offs;
|
|
|
|
switch (key->conf.cipher) {
|
|
case WLAN_CIPHER_SUITE_CCMP:
|
|
case WLAN_CIPHER_SUITE_CCMP_256:
|
|
case WLAN_CIPHER_SUITE_GCMP:
|
|
case WLAN_CIPHER_SUITE_GCMP_256:
|
|
pn = atomic64_inc_return(&key->conf.tx_pn);
|
|
crypto_hdr[0] = pn;
|
|
crypto_hdr[1] = pn >> 8;
|
|
crypto_hdr[3] = 0x20 | (key->conf.keyidx << 6);
|
|
crypto_hdr[4] = pn >> 16;
|
|
crypto_hdr[5] = pn >> 24;
|
|
crypto_hdr[6] = pn >> 32;
|
|
crypto_hdr[7] = pn >> 40;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return TX_CONTINUE;
|
|
}
|
|
|
|
static netdev_features_t
|
|
ieee80211_sdata_netdev_features(struct ieee80211_sub_if_data *sdata)
|
|
{
|
|
if (sdata->vif.type != NL80211_IFTYPE_AP_VLAN)
|
|
return sdata->vif.netdev_features;
|
|
|
|
if (!sdata->bss)
|
|
return 0;
|
|
|
|
sdata = container_of(sdata->bss, struct ieee80211_sub_if_data, u.ap);
|
|
return sdata->vif.netdev_features;
|
|
}
|
|
|
|
static struct sk_buff *
|
|
ieee80211_tx_skb_fixup(struct sk_buff *skb, netdev_features_t features)
|
|
{
|
|
if (skb_is_gso(skb)) {
|
|
struct sk_buff *segs;
|
|
|
|
segs = skb_gso_segment(skb, features);
|
|
if (!segs)
|
|
return skb;
|
|
if (IS_ERR(segs))
|
|
goto free;
|
|
|
|
consume_skb(skb);
|
|
return segs;
|
|
}
|
|
|
|
if (skb_needs_linearize(skb, features) && __skb_linearize(skb))
|
|
goto free;
|
|
|
|
if (skb->ip_summed == CHECKSUM_PARTIAL) {
|
|
int ofs = skb_checksum_start_offset(skb);
|
|
|
|
if (skb->encapsulation)
|
|
skb_set_inner_transport_header(skb, ofs);
|
|
else
|
|
skb_set_transport_header(skb, ofs);
|
|
|
|
if (skb_csum_hwoffload_help(skb, features))
|
|
goto free;
|
|
}
|
|
|
|
skb_mark_not_on_list(skb);
|
|
return skb;
|
|
|
|
free:
|
|
kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
void __ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta,
|
|
struct ieee80211_fast_tx *fast_tx,
|
|
struct sk_buff *skb, bool ampdu,
|
|
const u8 *da, const u8 *sa)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
|
|
struct ieee80211_tx_info *info;
|
|
struct ieee80211_tx_data tx;
|
|
ieee80211_tx_result r;
|
|
int hw_headroom = sdata->local->hw.extra_tx_headroom;
|
|
int extra_head = fast_tx->hdr_len - (ETH_HLEN - 2);
|
|
|
|
skb = skb_share_check(skb, GFP_ATOMIC);
|
|
if (unlikely(!skb))
|
|
return;
|
|
|
|
if ((hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) &&
|
|
ieee80211_amsdu_aggregate(sdata, sta, fast_tx, skb, da, sa))
|
|
return;
|
|
|
|
/* will not be crypto-handled beyond what we do here, so use false
|
|
* as the may-encrypt argument for the resize to not account for
|
|
* more room than we already have in 'extra_head'
|
|
*/
|
|
if (unlikely(ieee80211_skb_resize(sdata, skb,
|
|
max_t(int, extra_head + hw_headroom -
|
|
skb_headroom(skb), 0),
|
|
ENCRYPT_NO)))
|
|
goto free;
|
|
|
|
hdr = skb_push(skb, extra_head);
|
|
memcpy(skb->data, fast_tx->hdr, fast_tx->hdr_len);
|
|
memcpy(skb->data + fast_tx->da_offs, da, ETH_ALEN);
|
|
memcpy(skb->data + fast_tx->sa_offs, sa, ETH_ALEN);
|
|
|
|
info = IEEE80211_SKB_CB(skb);
|
|
memset(info, 0, sizeof(*info));
|
|
info->band = fast_tx->band;
|
|
info->control.vif = &sdata->vif;
|
|
info->flags = IEEE80211_TX_CTL_FIRST_FRAGMENT |
|
|
IEEE80211_TX_CTL_DONTFRAG;
|
|
info->control.flags = IEEE80211_TX_CTRL_FAST_XMIT |
|
|
u32_encode_bits(IEEE80211_LINK_UNSPECIFIED,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
#ifdef CONFIG_MAC80211_DEBUGFS
|
|
if (local->force_tx_status)
|
|
info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
#endif
|
|
|
|
if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) {
|
|
u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
|
|
|
|
*ieee80211_get_qos_ctl(hdr) = tid;
|
|
}
|
|
|
|
__skb_queue_head_init(&tx.skbs);
|
|
|
|
tx.flags = IEEE80211_TX_UNICAST;
|
|
tx.local = local;
|
|
tx.sdata = sdata;
|
|
tx.sta = sta;
|
|
tx.key = fast_tx->key;
|
|
|
|
if (ieee80211_queue_skb(local, sdata, sta, skb))
|
|
return;
|
|
|
|
tx.skb = skb;
|
|
r = ieee80211_xmit_fast_finish(sdata, sta, fast_tx->pn_offs,
|
|
fast_tx->key, &tx);
|
|
tx.skb = NULL;
|
|
if (r == TX_DROP)
|
|
goto free;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
|
|
sdata = container_of(sdata->bss,
|
|
struct ieee80211_sub_if_data, u.ap);
|
|
|
|
__skb_queue_tail(&tx.skbs, skb);
|
|
ieee80211_tx_frags(local, &sdata->vif, sta, &tx.skbs, false);
|
|
return;
|
|
|
|
free:
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata,
|
|
struct sta_info *sta,
|
|
struct ieee80211_fast_tx *fast_tx,
|
|
struct sk_buff *skb)
|
|
{
|
|
u16 ethertype = (skb->data[12] << 8) | skb->data[13];
|
|
struct ieee80211_hdr *hdr = (void *)fast_tx->hdr;
|
|
struct tid_ampdu_tx *tid_tx = NULL;
|
|
struct sk_buff *next;
|
|
struct ethhdr eth;
|
|
u8 tid = IEEE80211_NUM_TIDS;
|
|
|
|
/* control port protocol needs a lot of special handling */
|
|
if (cpu_to_be16(ethertype) == sdata->control_port_protocol)
|
|
return false;
|
|
|
|
/* only RFC 1042 SNAP */
|
|
if (ethertype < ETH_P_802_3_MIN)
|
|
return false;
|
|
|
|
/* don't handle TX status request here either */
|
|
if (skb->sk && skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)
|
|
return false;
|
|
|
|
if (hdr->frame_control & cpu_to_le16(IEEE80211_STYPE_QOS_DATA)) {
|
|
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
|
|
tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
|
|
if (tid_tx) {
|
|
if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state))
|
|
return false;
|
|
if (tid_tx->timeout)
|
|
tid_tx->last_tx = jiffies;
|
|
}
|
|
}
|
|
|
|
memcpy(ð, skb->data, ETH_HLEN - 2);
|
|
|
|
/* after this point (skb is modified) we cannot return false */
|
|
skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
|
|
if (!skb)
|
|
return true;
|
|
|
|
skb_list_walk_safe(skb, skb, next) {
|
|
skb_mark_not_on_list(skb);
|
|
__ieee80211_xmit_fast(sdata, sta, fast_tx, skb, tid_tx,
|
|
eth.h_dest, eth.h_source);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw,
|
|
struct ieee80211_txq *txq)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct txq_info *txqi = container_of(txq, struct txq_info, txq);
|
|
struct ieee80211_hdr *hdr;
|
|
struct sk_buff *skb = NULL;
|
|
struct fq *fq = &local->fq;
|
|
struct fq_tin *tin = &txqi->tin;
|
|
struct ieee80211_tx_info *info;
|
|
struct ieee80211_tx_data tx;
|
|
ieee80211_tx_result r;
|
|
struct ieee80211_vif *vif = txq->vif;
|
|
int q = vif->hw_queue[txq->ac];
|
|
unsigned long flags;
|
|
bool q_stopped;
|
|
|
|
WARN_ON_ONCE(softirq_count() == 0);
|
|
|
|
if (!ieee80211_txq_airtime_check(hw, txq))
|
|
return NULL;
|
|
|
|
begin:
|
|
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
|
|
q_stopped = local->queue_stop_reasons[q];
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
|
|
|
|
if (unlikely(q_stopped)) {
|
|
/* mark for waking later */
|
|
set_bit(IEEE80211_TXQ_DIRTY, &txqi->flags);
|
|
return NULL;
|
|
}
|
|
|
|
spin_lock_bh(&fq->lock);
|
|
|
|
/* Make sure fragments stay together. */
|
|
skb = __skb_dequeue(&txqi->frags);
|
|
if (unlikely(skb)) {
|
|
if (!(IEEE80211_SKB_CB(skb)->control.flags &
|
|
IEEE80211_TX_INTCFL_NEED_TXPROCESSING))
|
|
goto out;
|
|
IEEE80211_SKB_CB(skb)->control.flags &=
|
|
~IEEE80211_TX_INTCFL_NEED_TXPROCESSING;
|
|
} else {
|
|
if (unlikely(test_bit(IEEE80211_TXQ_STOP, &txqi->flags)))
|
|
goto out;
|
|
|
|
skb = fq_tin_dequeue(fq, tin, fq_tin_dequeue_func);
|
|
}
|
|
|
|
if (!skb)
|
|
goto out;
|
|
|
|
spin_unlock_bh(&fq->lock);
|
|
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
info = IEEE80211_SKB_CB(skb);
|
|
|
|
memset(&tx, 0, sizeof(tx));
|
|
__skb_queue_head_init(&tx.skbs);
|
|
tx.local = local;
|
|
tx.skb = skb;
|
|
tx.sdata = vif_to_sdata(info->control.vif);
|
|
|
|
if (txq->sta) {
|
|
tx.sta = container_of(txq->sta, struct sta_info, sta);
|
|
/*
|
|
* Drop unicast frames to unauthorised stations unless they are
|
|
* injected frames or EAPOL frames from the local station.
|
|
*/
|
|
if (unlikely(!(info->flags & IEEE80211_TX_CTL_INJECTED) &&
|
|
ieee80211_is_data(hdr->frame_control) &&
|
|
!ieee80211_vif_is_mesh(&tx.sdata->vif) &&
|
|
tx.sdata->vif.type != NL80211_IFTYPE_OCB &&
|
|
!is_multicast_ether_addr(hdr->addr1) &&
|
|
!test_sta_flag(tx.sta, WLAN_STA_AUTHORIZED) &&
|
|
(!(info->control.flags &
|
|
IEEE80211_TX_CTRL_PORT_CTRL_PROTO) ||
|
|
!ieee80211_is_our_addr(tx.sdata, hdr->addr2,
|
|
NULL)))) {
|
|
I802_DEBUG_INC(local->tx_handlers_drop_unauth_port);
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The key can be removed while the packet was queued, so need to call
|
|
* this here to get the current key.
|
|
*/
|
|
r = ieee80211_tx_h_select_key(&tx);
|
|
if (r != TX_CONTINUE) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
}
|
|
|
|
if (test_bit(IEEE80211_TXQ_AMPDU, &txqi->flags))
|
|
info->flags |= (IEEE80211_TX_CTL_AMPDU |
|
|
IEEE80211_TX_CTL_DONTFRAG);
|
|
|
|
if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) {
|
|
if (!ieee80211_hw_check(&local->hw, HAS_RATE_CONTROL)) {
|
|
r = ieee80211_tx_h_rate_ctrl(&tx);
|
|
if (r != TX_CONTINUE) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
}
|
|
}
|
|
goto encap_out;
|
|
}
|
|
|
|
if (info->control.flags & IEEE80211_TX_CTRL_FAST_XMIT) {
|
|
struct sta_info *sta = container_of(txq->sta, struct sta_info,
|
|
sta);
|
|
u8 pn_offs = 0;
|
|
|
|
if (tx.key &&
|
|
(tx.key->conf.flags & IEEE80211_KEY_FLAG_GENERATE_IV))
|
|
pn_offs = ieee80211_hdrlen(hdr->frame_control);
|
|
|
|
r = ieee80211_xmit_fast_finish(sta->sdata, sta, pn_offs,
|
|
tx.key, &tx);
|
|
if (r != TX_CONTINUE) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
}
|
|
} else {
|
|
if (invoke_tx_handlers_late(&tx))
|
|
goto begin;
|
|
|
|
skb = __skb_dequeue(&tx.skbs);
|
|
info = IEEE80211_SKB_CB(skb);
|
|
|
|
if (!skb_queue_empty(&tx.skbs)) {
|
|
spin_lock_bh(&fq->lock);
|
|
skb_queue_splice_tail(&tx.skbs, &txqi->frags);
|
|
spin_unlock_bh(&fq->lock);
|
|
}
|
|
}
|
|
|
|
if (skb_has_frag_list(skb) &&
|
|
!ieee80211_hw_check(&local->hw, TX_FRAG_LIST)) {
|
|
if (skb_linearize(skb)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
}
|
|
}
|
|
|
|
switch (tx.sdata->vif.type) {
|
|
case NL80211_IFTYPE_MONITOR:
|
|
if (tx.sdata->u.mntr.flags & MONITOR_FLAG_ACTIVE) {
|
|
vif = &tx.sdata->vif;
|
|
break;
|
|
}
|
|
tx.sdata = rcu_dereference(local->monitor_sdata);
|
|
if (tx.sdata &&
|
|
ieee80211_hw_check(&local->hw, WANT_MONITOR_VIF)) {
|
|
vif = &tx.sdata->vif;
|
|
info->hw_queue =
|
|
vif->hw_queue[skb_get_queue_mapping(skb)];
|
|
} else if (ieee80211_hw_check(&local->hw, QUEUE_CONTROL)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
goto begin;
|
|
} else {
|
|
info->control.vif = NULL;
|
|
return skb;
|
|
}
|
|
break;
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
tx.sdata = container_of(tx.sdata->bss,
|
|
struct ieee80211_sub_if_data, u.ap);
|
|
fallthrough;
|
|
default:
|
|
vif = &tx.sdata->vif;
|
|
break;
|
|
}
|
|
|
|
encap_out:
|
|
info->control.vif = vif;
|
|
|
|
if (tx.sta &&
|
|
wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL)) {
|
|
bool ampdu = txq->ac != IEEE80211_AC_VO;
|
|
u32 airtime;
|
|
|
|
airtime = ieee80211_calc_expected_tx_airtime(hw, vif, txq->sta,
|
|
skb->len, ampdu);
|
|
if (airtime) {
|
|
airtime = ieee80211_info_set_tx_time_est(info, airtime);
|
|
ieee80211_sta_update_pending_airtime(local, tx.sta,
|
|
txq->ac,
|
|
airtime,
|
|
false);
|
|
}
|
|
}
|
|
|
|
return skb;
|
|
|
|
out:
|
|
spin_unlock_bh(&fq->lock);
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_tx_dequeue);
|
|
|
|
static inline s32 ieee80211_sta_deficit(struct sta_info *sta, u8 ac)
|
|
{
|
|
struct airtime_info *air_info = &sta->airtime[ac];
|
|
|
|
return air_info->deficit - atomic_read(&air_info->aql_tx_pending);
|
|
}
|
|
|
|
static void
|
|
ieee80211_txq_set_active(struct txq_info *txqi)
|
|
{
|
|
struct sta_info *sta;
|
|
|
|
if (!txqi->txq.sta)
|
|
return;
|
|
|
|
sta = container_of(txqi->txq.sta, struct sta_info, sta);
|
|
sta->airtime[txqi->txq.ac].last_active = jiffies;
|
|
}
|
|
|
|
static bool
|
|
ieee80211_txq_keep_active(struct txq_info *txqi)
|
|
{
|
|
struct sta_info *sta;
|
|
|
|
if (!txqi->txq.sta)
|
|
return false;
|
|
|
|
sta = container_of(txqi->txq.sta, struct sta_info, sta);
|
|
if (ieee80211_sta_deficit(sta, txqi->txq.ac) >= 0)
|
|
return false;
|
|
|
|
return ieee80211_sta_keep_active(sta, txqi->txq.ac);
|
|
}
|
|
|
|
struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_txq *ret = NULL;
|
|
struct txq_info *txqi = NULL, *head = NULL;
|
|
bool found_eligible_txq = false;
|
|
|
|
spin_lock_bh(&local->active_txq_lock[ac]);
|
|
|
|
if (!local->schedule_round[ac])
|
|
goto out;
|
|
|
|
begin:
|
|
txqi = list_first_entry_or_null(&local->active_txqs[ac],
|
|
struct txq_info,
|
|
schedule_order);
|
|
if (!txqi)
|
|
goto out;
|
|
|
|
if (txqi == head) {
|
|
if (!found_eligible_txq)
|
|
goto out;
|
|
else
|
|
found_eligible_txq = false;
|
|
}
|
|
|
|
if (!head)
|
|
head = txqi;
|
|
|
|
if (txqi->txq.sta) {
|
|
struct sta_info *sta = container_of(txqi->txq.sta,
|
|
struct sta_info, sta);
|
|
bool aql_check = ieee80211_txq_airtime_check(hw, &txqi->txq);
|
|
s32 deficit = ieee80211_sta_deficit(sta, txqi->txq.ac);
|
|
|
|
if (aql_check)
|
|
found_eligible_txq = true;
|
|
|
|
if (deficit < 0)
|
|
sta->airtime[txqi->txq.ac].deficit +=
|
|
sta->airtime_weight;
|
|
|
|
if (deficit < 0 || !aql_check) {
|
|
list_move_tail(&txqi->schedule_order,
|
|
&local->active_txqs[txqi->txq.ac]);
|
|
goto begin;
|
|
}
|
|
}
|
|
|
|
if (txqi->schedule_round == local->schedule_round[ac])
|
|
goto out;
|
|
|
|
list_del_init(&txqi->schedule_order);
|
|
txqi->schedule_round = local->schedule_round[ac];
|
|
ret = &txqi->txq;
|
|
|
|
out:
|
|
spin_unlock_bh(&local->active_txq_lock[ac]);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_next_txq);
|
|
|
|
void __ieee80211_schedule_txq(struct ieee80211_hw *hw,
|
|
struct ieee80211_txq *txq,
|
|
bool force)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct txq_info *txqi = to_txq_info(txq);
|
|
bool has_queue;
|
|
|
|
spin_lock_bh(&local->active_txq_lock[txq->ac]);
|
|
|
|
has_queue = force || txq_has_queue(txq);
|
|
if (list_empty(&txqi->schedule_order) &&
|
|
(has_queue || ieee80211_txq_keep_active(txqi))) {
|
|
/* If airtime accounting is active, always enqueue STAs at the
|
|
* head of the list to ensure that they only get moved to the
|
|
* back by the airtime DRR scheduler once they have a negative
|
|
* deficit. A station that already has a negative deficit will
|
|
* get immediately moved to the back of the list on the next
|
|
* call to ieee80211_next_txq().
|
|
*/
|
|
if (txqi->txq.sta && local->airtime_flags && has_queue &&
|
|
wiphy_ext_feature_isset(local->hw.wiphy,
|
|
NL80211_EXT_FEATURE_AIRTIME_FAIRNESS))
|
|
list_add(&txqi->schedule_order,
|
|
&local->active_txqs[txq->ac]);
|
|
else
|
|
list_add_tail(&txqi->schedule_order,
|
|
&local->active_txqs[txq->ac]);
|
|
if (has_queue)
|
|
ieee80211_txq_set_active(txqi);
|
|
}
|
|
|
|
spin_unlock_bh(&local->active_txq_lock[txq->ac]);
|
|
}
|
|
EXPORT_SYMBOL(__ieee80211_schedule_txq);
|
|
|
|
DEFINE_STATIC_KEY_FALSE(aql_disable);
|
|
|
|
bool ieee80211_txq_airtime_check(struct ieee80211_hw *hw,
|
|
struct ieee80211_txq *txq)
|
|
{
|
|
struct sta_info *sta;
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
|
|
if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL))
|
|
return true;
|
|
|
|
if (static_branch_unlikely(&aql_disable))
|
|
return true;
|
|
|
|
if (!txq->sta)
|
|
return true;
|
|
|
|
if (unlikely(txq->tid == IEEE80211_NUM_TIDS))
|
|
return true;
|
|
|
|
sta = container_of(txq->sta, struct sta_info, sta);
|
|
if (atomic_read(&sta->airtime[txq->ac].aql_tx_pending) <
|
|
sta->airtime[txq->ac].aql_limit_low)
|
|
return true;
|
|
|
|
if (atomic_read(&local->aql_total_pending_airtime) <
|
|
local->aql_threshold &&
|
|
atomic_read(&sta->airtime[txq->ac].aql_tx_pending) <
|
|
sta->airtime[txq->ac].aql_limit_high)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_txq_airtime_check);
|
|
|
|
static bool
|
|
ieee80211_txq_schedule_airtime_check(struct ieee80211_local *local, u8 ac)
|
|
{
|
|
unsigned int num_txq = 0;
|
|
struct txq_info *txq;
|
|
u32 aql_limit;
|
|
|
|
if (!wiphy_ext_feature_isset(local->hw.wiphy, NL80211_EXT_FEATURE_AQL))
|
|
return true;
|
|
|
|
list_for_each_entry(txq, &local->active_txqs[ac], schedule_order)
|
|
num_txq++;
|
|
|
|
aql_limit = (num_txq - 1) * local->aql_txq_limit_low[ac] / 2 +
|
|
local->aql_txq_limit_high[ac];
|
|
|
|
return atomic_read(&local->aql_ac_pending_airtime[ac]) < aql_limit;
|
|
}
|
|
|
|
bool ieee80211_txq_may_transmit(struct ieee80211_hw *hw,
|
|
struct ieee80211_txq *txq)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct txq_info *iter, *tmp, *txqi = to_txq_info(txq);
|
|
struct sta_info *sta;
|
|
u8 ac = txq->ac;
|
|
|
|
spin_lock_bh(&local->active_txq_lock[ac]);
|
|
|
|
if (!txqi->txq.sta)
|
|
goto out;
|
|
|
|
if (list_empty(&txqi->schedule_order))
|
|
goto out;
|
|
|
|
if (!ieee80211_txq_schedule_airtime_check(local, ac))
|
|
goto out;
|
|
|
|
list_for_each_entry_safe(iter, tmp, &local->active_txqs[ac],
|
|
schedule_order) {
|
|
if (iter == txqi)
|
|
break;
|
|
|
|
if (!iter->txq.sta) {
|
|
list_move_tail(&iter->schedule_order,
|
|
&local->active_txqs[ac]);
|
|
continue;
|
|
}
|
|
sta = container_of(iter->txq.sta, struct sta_info, sta);
|
|
if (ieee80211_sta_deficit(sta, ac) < 0)
|
|
sta->airtime[ac].deficit += sta->airtime_weight;
|
|
list_move_tail(&iter->schedule_order, &local->active_txqs[ac]);
|
|
}
|
|
|
|
sta = container_of(txqi->txq.sta, struct sta_info, sta);
|
|
if (sta->airtime[ac].deficit >= 0)
|
|
goto out;
|
|
|
|
sta->airtime[ac].deficit += sta->airtime_weight;
|
|
list_move_tail(&txqi->schedule_order, &local->active_txqs[ac]);
|
|
spin_unlock_bh(&local->active_txq_lock[ac]);
|
|
|
|
return false;
|
|
out:
|
|
if (!list_empty(&txqi->schedule_order))
|
|
list_del_init(&txqi->schedule_order);
|
|
spin_unlock_bh(&local->active_txq_lock[ac]);
|
|
|
|
return true;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_txq_may_transmit);
|
|
|
|
void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
|
|
spin_lock_bh(&local->active_txq_lock[ac]);
|
|
|
|
if (ieee80211_txq_schedule_airtime_check(local, ac)) {
|
|
local->schedule_round[ac]++;
|
|
if (!local->schedule_round[ac])
|
|
local->schedule_round[ac]++;
|
|
} else {
|
|
local->schedule_round[ac] = 0;
|
|
}
|
|
|
|
spin_unlock_bh(&local->active_txq_lock[ac]);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_txq_schedule_start);
|
|
|
|
void __ieee80211_subif_start_xmit(struct sk_buff *skb,
|
|
struct net_device *dev,
|
|
u32 info_flags,
|
|
u32 ctrl_flags,
|
|
u64 *cookie)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
struct sk_buff *next;
|
|
int len = skb->len;
|
|
|
|
if (unlikely(!ieee80211_sdata_running(sdata) || skb->len < ETH_HLEN)) {
|
|
kfree_skb(skb);
|
|
return;
|
|
}
|
|
|
|
sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
|
|
|
|
rcu_read_lock();
|
|
|
|
if (ieee80211_vif_is_mesh(&sdata->vif) &&
|
|
ieee80211_hw_check(&local->hw, SUPPORT_FAST_XMIT) &&
|
|
ieee80211_mesh_xmit_fast(sdata, skb, ctrl_flags))
|
|
goto out;
|
|
|
|
if (ieee80211_lookup_ra_sta(sdata, skb, &sta))
|
|
goto out_free;
|
|
|
|
if (IS_ERR(sta))
|
|
sta = NULL;
|
|
|
|
skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb));
|
|
ieee80211_aggr_check(sdata, sta, skb);
|
|
|
|
if (sta) {
|
|
struct ieee80211_fast_tx *fast_tx;
|
|
|
|
fast_tx = rcu_dereference(sta->fast_tx);
|
|
|
|
if (fast_tx &&
|
|
ieee80211_xmit_fast(sdata, sta, fast_tx, skb))
|
|
goto out;
|
|
}
|
|
|
|
/* the frame could be fragmented, software-encrypted, and other
|
|
* things so we cannot really handle checksum or GSO offload.
|
|
* fix it up in software before we handle anything else.
|
|
*/
|
|
skb = ieee80211_tx_skb_fixup(skb, 0);
|
|
if (!skb) {
|
|
len = 0;
|
|
goto out;
|
|
}
|
|
|
|
skb_list_walk_safe(skb, skb, next) {
|
|
skb_mark_not_on_list(skb);
|
|
|
|
if (skb->protocol == sdata->control_port_protocol)
|
|
ctrl_flags |= IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP;
|
|
|
|
skb = ieee80211_build_hdr(sdata, skb, info_flags,
|
|
sta, ctrl_flags, cookie);
|
|
if (IS_ERR(skb)) {
|
|
kfree_skb_list(next);
|
|
goto out;
|
|
}
|
|
|
|
dev_sw_netstats_tx_add(dev, 1, skb->len);
|
|
|
|
ieee80211_xmit(sdata, sta, skb);
|
|
}
|
|
goto out;
|
|
out_free:
|
|
kfree_skb(skb);
|
|
len = 0;
|
|
out:
|
|
if (len)
|
|
ieee80211_tpt_led_trig_tx(local, len);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static int ieee80211_change_da(struct sk_buff *skb, struct sta_info *sta)
|
|
{
|
|
struct ethhdr *eth;
|
|
int err;
|
|
|
|
err = skb_ensure_writable(skb, ETH_HLEN);
|
|
if (unlikely(err))
|
|
return err;
|
|
|
|
eth = (void *)skb->data;
|
|
ether_addr_copy(eth->h_dest, sta->sta.addr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool ieee80211_multicast_to_unicast(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
const struct ethhdr *eth = (void *)skb->data;
|
|
const struct vlan_ethhdr *ethvlan = (void *)skb->data;
|
|
__be16 ethertype;
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
if (sdata->u.vlan.sta)
|
|
return false;
|
|
if (sdata->wdev.use_4addr)
|
|
return false;
|
|
fallthrough;
|
|
case NL80211_IFTYPE_AP:
|
|
/* check runtime toggle for this bss */
|
|
if (!sdata->bss->multicast_to_unicast)
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
/* multicast to unicast conversion only for some payload */
|
|
ethertype = eth->h_proto;
|
|
if (ethertype == htons(ETH_P_8021Q) && skb->len >= VLAN_ETH_HLEN)
|
|
ethertype = ethvlan->h_vlan_encapsulated_proto;
|
|
switch (ethertype) {
|
|
case htons(ETH_P_ARP):
|
|
case htons(ETH_P_IP):
|
|
case htons(ETH_P_IPV6):
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
ieee80211_convert_to_unicast(struct sk_buff *skb, struct net_device *dev,
|
|
struct sk_buff_head *queue)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
struct ieee80211_local *local = sdata->local;
|
|
const struct ethhdr *eth = (struct ethhdr *)skb->data;
|
|
struct sta_info *sta, *first = NULL;
|
|
struct sk_buff *cloned_skb;
|
|
|
|
rcu_read_lock();
|
|
|
|
list_for_each_entry_rcu(sta, &local->sta_list, list) {
|
|
if (sdata != sta->sdata)
|
|
/* AP-VLAN mismatch */
|
|
continue;
|
|
if (unlikely(ether_addr_equal(eth->h_source, sta->sta.addr)))
|
|
/* do not send back to source */
|
|
continue;
|
|
if (!first) {
|
|
first = sta;
|
|
continue;
|
|
}
|
|
cloned_skb = skb_clone(skb, GFP_ATOMIC);
|
|
if (!cloned_skb)
|
|
goto multicast;
|
|
if (unlikely(ieee80211_change_da(cloned_skb, sta))) {
|
|
dev_kfree_skb(cloned_skb);
|
|
goto multicast;
|
|
}
|
|
__skb_queue_tail(queue, cloned_skb);
|
|
}
|
|
|
|
if (likely(first)) {
|
|
if (unlikely(ieee80211_change_da(skb, first)))
|
|
goto multicast;
|
|
__skb_queue_tail(queue, skb);
|
|
} else {
|
|
/* no STA connected, drop */
|
|
kfree_skb(skb);
|
|
skb = NULL;
|
|
}
|
|
|
|
goto out;
|
|
multicast:
|
|
__skb_queue_purge(queue);
|
|
__skb_queue_tail(queue, skb);
|
|
out:
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, u32 ctrl_flags,
|
|
unsigned int link_id)
|
|
{
|
|
struct sk_buff *out;
|
|
|
|
out = skb_copy(skb, GFP_ATOMIC);
|
|
if (!out)
|
|
return;
|
|
|
|
ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK);
|
|
__ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, NULL);
|
|
}
|
|
|
|
static void ieee80211_mlo_multicast_tx(struct net_device *dev,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
unsigned long links = sdata->vif.active_links;
|
|
unsigned int link;
|
|
u32 ctrl_flags = IEEE80211_TX_CTRL_MCAST_MLO_FIRST_TX;
|
|
|
|
if (hweight16(links) == 1) {
|
|
ctrl_flags |= u32_encode_bits(__ffs(links),
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
__ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
for_each_set_bit(link, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
|
|
ieee80211_mlo_multicast_tx_one(sdata, skb, ctrl_flags, link);
|
|
ctrl_flags = 0;
|
|
}
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
/**
|
|
* ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs
|
|
* @skb: packet to be sent
|
|
* @dev: incoming interface
|
|
*
|
|
* On failure skb will be freed.
|
|
*
|
|
* Returns: the netdev TX status (but really only %NETDEV_TX_OK)
|
|
*/
|
|
netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
const struct ethhdr *eth = (void *)skb->data;
|
|
|
|
if (likely(!is_multicast_ether_addr(eth->h_dest)))
|
|
goto normal;
|
|
|
|
if (unlikely(!ieee80211_sdata_running(sdata))) {
|
|
kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
if (unlikely(ieee80211_multicast_to_unicast(skb, dev))) {
|
|
struct sk_buff_head queue;
|
|
|
|
__skb_queue_head_init(&queue);
|
|
ieee80211_convert_to_unicast(skb, dev, &queue);
|
|
while ((skb = __skb_dequeue(&queue)))
|
|
__ieee80211_subif_start_xmit(skb, dev, 0,
|
|
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
|
|
NULL);
|
|
} else if (ieee80211_vif_is_mld(&sdata->vif) &&
|
|
sdata->vif.type == NL80211_IFTYPE_AP &&
|
|
!ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) {
|
|
ieee80211_mlo_multicast_tx(dev, skb);
|
|
} else {
|
|
normal:
|
|
__ieee80211_subif_start_xmit(skb, dev, 0,
|
|
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
|
|
NULL);
|
|
}
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
|
|
|
|
static bool __ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, struct sta_info *sta,
|
|
bool txpending)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_tx_control control = {};
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_sta *pubsta = NULL;
|
|
unsigned long flags;
|
|
int q = info->hw_queue;
|
|
|
|
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
|
|
|
|
if (local->queue_stop_reasons[q] ||
|
|
(!txpending && !skb_queue_empty(&local->pending[q]))) {
|
|
if (txpending)
|
|
skb_queue_head(&local->pending[q], skb);
|
|
else
|
|
skb_queue_tail(&local->pending[q], skb);
|
|
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
|
|
|
|
return false;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
|
|
|
|
if (sta && sta->uploaded)
|
|
pubsta = &sta->sta;
|
|
|
|
control.sta = pubsta;
|
|
|
|
drv_tx(local, &control, skb);
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ieee80211_tx_8023(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, struct sta_info *sta,
|
|
bool txpending)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sk_buff *next;
|
|
bool ret = true;
|
|
|
|
if (ieee80211_queue_skb(local, sdata, sta, skb))
|
|
return true;
|
|
|
|
skb_list_walk_safe(skb, skb, next) {
|
|
skb_mark_not_on_list(skb);
|
|
if (!__ieee80211_tx_8023(sdata, skb, sta, txpending))
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata,
|
|
struct net_device *dev, struct sta_info *sta,
|
|
struct ieee80211_key *key, struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_tx_info *info;
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct tid_ampdu_tx *tid_tx;
|
|
struct sk_buff *seg, *next;
|
|
unsigned int skbs = 0, len = 0;
|
|
u16 queue;
|
|
u8 tid;
|
|
|
|
queue = ieee80211_select_queue(sdata, sta, skb);
|
|
skb_set_queue_mapping(skb, queue);
|
|
|
|
if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) &&
|
|
test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state))
|
|
goto out_free;
|
|
|
|
skb = skb_share_check(skb, GFP_ATOMIC);
|
|
if (unlikely(!skb))
|
|
return;
|
|
|
|
ieee80211_aggr_check(sdata, sta, skb);
|
|
|
|
tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
|
|
tid_tx = rcu_dereference(sta->ampdu_mlme.tid_tx[tid]);
|
|
if (tid_tx) {
|
|
if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) {
|
|
/* fall back to non-offload slow path */
|
|
__ieee80211_subif_start_xmit(skb, dev, 0,
|
|
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
if (tid_tx->timeout)
|
|
tid_tx->last_tx = jiffies;
|
|
}
|
|
|
|
skb = ieee80211_tx_skb_fixup(skb, ieee80211_sdata_netdev_features(sdata));
|
|
if (!skb)
|
|
return;
|
|
|
|
info = IEEE80211_SKB_CB(skb);
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
info->hw_queue = sdata->vif.hw_queue[queue];
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
|
|
sdata = container_of(sdata->bss,
|
|
struct ieee80211_sub_if_data, u.ap);
|
|
|
|
info->flags |= IEEE80211_TX_CTL_HW_80211_ENCAP;
|
|
info->control.vif = &sdata->vif;
|
|
|
|
if (key)
|
|
info->control.hw_key = &key->conf;
|
|
|
|
skb_list_walk_safe(skb, seg, next) {
|
|
skbs++;
|
|
len += seg->len;
|
|
if (seg != skb)
|
|
memcpy(IEEE80211_SKB_CB(seg), info, sizeof(*info));
|
|
}
|
|
|
|
if (unlikely(skb->sk &&
|
|
skb_shinfo(skb)->tx_flags & SKBTX_WIFI_STATUS)) {
|
|
info->status_data = ieee80211_store_ack_skb(local, skb,
|
|
&info->flags, NULL);
|
|
if (info->status_data)
|
|
info->status_data_idr = 1;
|
|
}
|
|
|
|
dev_sw_netstats_tx_add(dev, skbs, len);
|
|
sta->deflink.tx_stats.packets[queue] += skbs;
|
|
sta->deflink.tx_stats.bytes[queue] += len;
|
|
|
|
ieee80211_tpt_led_trig_tx(local, len);
|
|
|
|
ieee80211_tx_8023(sdata, skb, sta, false);
|
|
|
|
return;
|
|
|
|
out_free:
|
|
kfree_skb(skb);
|
|
}
|
|
|
|
netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb,
|
|
struct net_device *dev)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
struct ethhdr *ehdr = (struct ethhdr *)skb->data;
|
|
struct ieee80211_key *key;
|
|
struct sta_info *sta;
|
|
|
|
if (unlikely(!ieee80211_sdata_running(sdata) || skb->len < ETH_HLEN)) {
|
|
kfree_skb(skb);
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
rcu_read_lock();
|
|
|
|
if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
|
|
kfree_skb(skb);
|
|
goto out;
|
|
}
|
|
|
|
if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded ||
|
|
!test_sta_flag(sta, WLAN_STA_AUTHORIZED) ||
|
|
sdata->control_port_protocol == ehdr->h_proto))
|
|
goto skip_offload;
|
|
|
|
key = rcu_dereference(sta->ptk[sta->ptk_idx]);
|
|
if (!key)
|
|
key = rcu_dereference(sdata->default_unicast_key);
|
|
|
|
if (key && (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) ||
|
|
key->conf.cipher == WLAN_CIPHER_SUITE_TKIP))
|
|
goto skip_offload;
|
|
|
|
sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
|
|
ieee80211_8023_xmit(sdata, dev, sta, key, skb);
|
|
goto out;
|
|
|
|
skip_offload:
|
|
ieee80211_subif_start_xmit(skb, dev);
|
|
out:
|
|
rcu_read_unlock();
|
|
|
|
return NETDEV_TX_OK;
|
|
}
|
|
|
|
struct sk_buff *
|
|
ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, u32 info_flags)
|
|
{
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_tx_data tx = {
|
|
.local = sdata->local,
|
|
.sdata = sdata,
|
|
};
|
|
struct sta_info *sta;
|
|
|
|
rcu_read_lock();
|
|
|
|
if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
|
|
kfree_skb(skb);
|
|
skb = ERR_PTR(-EINVAL);
|
|
goto out;
|
|
}
|
|
|
|
skb = ieee80211_build_hdr(sdata, skb, info_flags, sta,
|
|
IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, NULL);
|
|
if (IS_ERR(skb))
|
|
goto out;
|
|
|
|
hdr = (void *)skb->data;
|
|
tx.sta = sta_info_get(sdata, hdr->addr1);
|
|
tx.skb = skb;
|
|
|
|
if (ieee80211_tx_h_select_key(&tx) != TX_CONTINUE) {
|
|
rcu_read_unlock();
|
|
kfree_skb(skb);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return skb;
|
|
}
|
|
|
|
/*
|
|
* ieee80211_clear_tx_pending may not be called in a context where
|
|
* it is possible that it packets could come in again.
|
|
*/
|
|
void ieee80211_clear_tx_pending(struct ieee80211_local *local)
|
|
{
|
|
struct sk_buff *skb;
|
|
int i;
|
|
|
|
for (i = 0; i < local->hw.queues; i++) {
|
|
while ((skb = skb_dequeue(&local->pending[i])) != NULL)
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Returns false if the frame couldn't be transmitted but was queued instead,
|
|
* which in this case means re-queued -- take as an indication to stop sending
|
|
* more pending frames.
|
|
*/
|
|
static bool ieee80211_tx_pending_skb(struct ieee80211_local *local,
|
|
struct sk_buff *skb)
|
|
{
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct sta_info *sta;
|
|
struct ieee80211_hdr *hdr;
|
|
bool result;
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
|
|
sdata = vif_to_sdata(info->control.vif);
|
|
|
|
if (info->control.flags & IEEE80211_TX_INTCFL_NEED_TXPROCESSING) {
|
|
/* update band only for non-MLD */
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
chanctx_conf =
|
|
rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
if (unlikely(!chanctx_conf)) {
|
|
dev_kfree_skb(skb);
|
|
return true;
|
|
}
|
|
info->band = chanctx_conf->def.chan->band;
|
|
}
|
|
result = ieee80211_tx(sdata, NULL, skb, true);
|
|
} else if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) {
|
|
if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) {
|
|
dev_kfree_skb(skb);
|
|
return true;
|
|
}
|
|
|
|
if (IS_ERR(sta) || (sta && !sta->uploaded))
|
|
sta = NULL;
|
|
|
|
result = ieee80211_tx_8023(sdata, skb, sta, true);
|
|
} else {
|
|
struct sk_buff_head skbs;
|
|
|
|
__skb_queue_head_init(&skbs);
|
|
__skb_queue_tail(&skbs, skb);
|
|
|
|
hdr = (struct ieee80211_hdr *)skb->data;
|
|
sta = sta_info_get(sdata, hdr->addr1);
|
|
|
|
result = __ieee80211_tx(local, &skbs, sta, true);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Transmit all pending packets. Called from tasklet.
|
|
*/
|
|
void ieee80211_tx_pending(struct tasklet_struct *t)
|
|
{
|
|
struct ieee80211_local *local = from_tasklet(local, t,
|
|
tx_pending_tasklet);
|
|
unsigned long flags;
|
|
int i;
|
|
bool txok;
|
|
|
|
rcu_read_lock();
|
|
|
|
spin_lock_irqsave(&local->queue_stop_reason_lock, flags);
|
|
for (i = 0; i < local->hw.queues; i++) {
|
|
/*
|
|
* If queue is stopped by something other than due to pending
|
|
* frames, or we have no pending frames, proceed to next queue.
|
|
*/
|
|
if (local->queue_stop_reasons[i] ||
|
|
skb_queue_empty(&local->pending[i]))
|
|
continue;
|
|
|
|
while (!skb_queue_empty(&local->pending[i])) {
|
|
struct sk_buff *skb = __skb_dequeue(&local->pending[i]);
|
|
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
|
|
|
|
if (WARN_ON(!info->control.vif)) {
|
|
ieee80211_free_txskb(&local->hw, skb);
|
|
continue;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock,
|
|
flags);
|
|
|
|
txok = ieee80211_tx_pending_skb(local, skb);
|
|
spin_lock_irqsave(&local->queue_stop_reason_lock,
|
|
flags);
|
|
if (!txok)
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&local->queue_stop_reason_lock, flags);
|
|
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
/* functions for drivers to get certain frames */
|
|
|
|
static void __ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_link_data *link,
|
|
struct ps_data *ps, struct sk_buff *skb,
|
|
bool is_template)
|
|
{
|
|
u8 *pos, *tim;
|
|
int aid0 = 0;
|
|
int i, have_bits = 0, n1, n2;
|
|
struct ieee80211_bss_conf *link_conf = link->conf;
|
|
|
|
/* Generate bitmap for TIM only if there are any STAs in power save
|
|
* mode. */
|
|
if (atomic_read(&ps->num_sta_ps) > 0)
|
|
/* in the hope that this is faster than
|
|
* checking byte-for-byte */
|
|
have_bits = !bitmap_empty((unsigned long *)ps->tim,
|
|
IEEE80211_MAX_AID+1);
|
|
if (!is_template) {
|
|
if (ps->dtim_count == 0)
|
|
ps->dtim_count = link_conf->dtim_period - 1;
|
|
else
|
|
ps->dtim_count--;
|
|
}
|
|
|
|
tim = pos = skb_put(skb, 5);
|
|
*pos++ = WLAN_EID_TIM;
|
|
*pos++ = 3;
|
|
*pos++ = ps->dtim_count;
|
|
*pos++ = link_conf->dtim_period;
|
|
|
|
if (ps->dtim_count == 0 && !skb_queue_empty(&ps->bc_buf))
|
|
aid0 = 1;
|
|
|
|
ps->dtim_bc_mc = aid0 == 1;
|
|
|
|
if (have_bits) {
|
|
/* Find largest even number N1 so that bits numbered 1 through
|
|
* (N1 x 8) - 1 in the bitmap are 0 and number N2 so that bits
|
|
* (N2 + 1) x 8 through 2007 are 0. */
|
|
n1 = 0;
|
|
for (i = 0; i < IEEE80211_MAX_TIM_LEN; i++) {
|
|
if (ps->tim[i]) {
|
|
n1 = i & 0xfe;
|
|
break;
|
|
}
|
|
}
|
|
n2 = n1;
|
|
for (i = IEEE80211_MAX_TIM_LEN - 1; i >= n1; i--) {
|
|
if (ps->tim[i]) {
|
|
n2 = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Bitmap control */
|
|
*pos++ = n1 | aid0;
|
|
/* Part Virt Bitmap */
|
|
skb_put_data(skb, ps->tim + n1, n2 - n1 + 1);
|
|
|
|
tim[1] = n2 - n1 + 4;
|
|
} else {
|
|
*pos++ = aid0; /* Bitmap control */
|
|
|
|
if (ieee80211_get_link_sband(link)->band != NL80211_BAND_S1GHZ) {
|
|
tim[1] = 4;
|
|
/* Part Virt Bitmap */
|
|
skb_put_u8(skb, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_link_data *link,
|
|
struct ps_data *ps, struct sk_buff *skb,
|
|
bool is_template)
|
|
{
|
|
struct ieee80211_local *local = sdata->local;
|
|
|
|
/*
|
|
* Not very nice, but we want to allow the driver to call
|
|
* ieee80211_beacon_get() as a response to the set_tim()
|
|
* callback. That, however, is already invoked under the
|
|
* sta_lock to guarantee consistent and race-free update
|
|
* of the tim bitmap in mac80211 and the driver.
|
|
*/
|
|
if (local->tim_in_locked_section) {
|
|
__ieee80211_beacon_add_tim(sdata, link, ps, skb, is_template);
|
|
} else {
|
|
spin_lock_bh(&local->tim_lock);
|
|
__ieee80211_beacon_add_tim(sdata, link, ps, skb, is_template);
|
|
spin_unlock_bh(&local->tim_lock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ieee80211_set_beacon_cntdwn(struct ieee80211_sub_if_data *sdata,
|
|
struct beacon_data *beacon,
|
|
struct ieee80211_link_data *link)
|
|
{
|
|
u8 *beacon_data, count, max_count = 1;
|
|
struct probe_resp *resp;
|
|
size_t beacon_data_len;
|
|
u16 *bcn_offsets;
|
|
int i;
|
|
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_AP:
|
|
beacon_data = beacon->tail;
|
|
beacon_data_len = beacon->tail_len;
|
|
break;
|
|
case NL80211_IFTYPE_ADHOC:
|
|
beacon_data = beacon->head;
|
|
beacon_data_len = beacon->head_len;
|
|
break;
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
beacon_data = beacon->head;
|
|
beacon_data_len = beacon->head_len;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
resp = rcu_dereference(link->u.ap.probe_resp);
|
|
|
|
bcn_offsets = beacon->cntdwn_counter_offsets;
|
|
count = beacon->cntdwn_current_counter;
|
|
if (link->conf->csa_active)
|
|
max_count = IEEE80211_MAX_CNTDWN_COUNTERS_NUM;
|
|
|
|
for (i = 0; i < max_count; ++i) {
|
|
if (bcn_offsets[i]) {
|
|
if (WARN_ON_ONCE(bcn_offsets[i] >= beacon_data_len))
|
|
return;
|
|
beacon_data[bcn_offsets[i]] = count;
|
|
}
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP && resp) {
|
|
u16 *resp_offsets = resp->cntdwn_counter_offsets;
|
|
|
|
resp->data[resp_offsets[i]] = count;
|
|
}
|
|
}
|
|
}
|
|
|
|
static u8 __ieee80211_beacon_update_cntdwn(struct beacon_data *beacon)
|
|
{
|
|
beacon->cntdwn_current_counter--;
|
|
|
|
/* the counter should never reach 0 */
|
|
WARN_ON_ONCE(!beacon->cntdwn_current_counter);
|
|
|
|
return beacon->cntdwn_current_counter;
|
|
}
|
|
|
|
u8 ieee80211_beacon_update_cntdwn(struct ieee80211_vif *vif, unsigned int link_id)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_link_data *link;
|
|
struct beacon_data *beacon = NULL;
|
|
u8 count = 0;
|
|
|
|
if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
|
|
return 0;
|
|
|
|
rcu_read_lock();
|
|
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (!link)
|
|
goto unlock;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
beacon = rcu_dereference(link->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)
|
|
goto unlock;
|
|
|
|
count = __ieee80211_beacon_update_cntdwn(beacon);
|
|
|
|
unlock:
|
|
rcu_read_unlock();
|
|
return count;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_update_cntdwn);
|
|
|
|
void ieee80211_beacon_set_cntdwn(struct ieee80211_vif *vif, u8 counter)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct beacon_data *beacon = NULL;
|
|
|
|
rcu_read_lock();
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
beacon = rcu_dereference(sdata->deflink.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)
|
|
goto unlock;
|
|
|
|
if (counter < beacon->cntdwn_current_counter)
|
|
beacon->cntdwn_current_counter = counter;
|
|
|
|
unlock:
|
|
rcu_read_unlock();
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_set_cntdwn);
|
|
|
|
bool ieee80211_beacon_cntdwn_is_complete(struct ieee80211_vif *vif,
|
|
unsigned int link_id)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_link_data *link;
|
|
struct beacon_data *beacon = NULL;
|
|
u8 *beacon_data;
|
|
size_t beacon_data_len;
|
|
int ret = false;
|
|
|
|
if (!ieee80211_sdata_running(sdata))
|
|
return false;
|
|
|
|
if (WARN_ON(link_id >= IEEE80211_MLD_MAX_NUM_LINKS))
|
|
return 0;
|
|
|
|
rcu_read_lock();
|
|
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (!link)
|
|
goto out;
|
|
|
|
if (vif->type == NL80211_IFTYPE_AP) {
|
|
beacon = rcu_dereference(link->u.ap.beacon);
|
|
if (WARN_ON(!beacon || !beacon->tail))
|
|
goto out;
|
|
beacon_data = beacon->tail;
|
|
beacon_data_len = beacon->tail_len;
|
|
} else if (vif->type == NL80211_IFTYPE_ADHOC) {
|
|
struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
|
|
|
|
beacon = rcu_dereference(ifibss->presp);
|
|
if (!beacon)
|
|
goto out;
|
|
|
|
beacon_data = beacon->head;
|
|
beacon_data_len = beacon->head_len;
|
|
} else if (vif->type == NL80211_IFTYPE_MESH_POINT) {
|
|
struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
|
|
|
beacon = rcu_dereference(ifmsh->beacon);
|
|
if (!beacon)
|
|
goto out;
|
|
|
|
beacon_data = beacon->head;
|
|
beacon_data_len = beacon->head_len;
|
|
} else {
|
|
WARN_ON(1);
|
|
goto out;
|
|
}
|
|
|
|
if (!beacon->cntdwn_counter_offsets[0])
|
|
goto out;
|
|
|
|
if (WARN_ON_ONCE(beacon->cntdwn_counter_offsets[0] > beacon_data_len))
|
|
goto out;
|
|
|
|
if (beacon_data[beacon->cntdwn_counter_offsets[0]] == 1)
|
|
ret = true;
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_cntdwn_is_complete);
|
|
|
|
static int ieee80211_beacon_protect(struct sk_buff *skb,
|
|
struct ieee80211_local *local,
|
|
struct ieee80211_sub_if_data *sdata,
|
|
struct ieee80211_link_data *link)
|
|
{
|
|
ieee80211_tx_result res;
|
|
struct ieee80211_tx_data tx;
|
|
struct sk_buff *check_skb;
|
|
|
|
memset(&tx, 0, sizeof(tx));
|
|
tx.key = rcu_dereference(link->default_beacon_key);
|
|
if (!tx.key)
|
|
return 0;
|
|
|
|
if (unlikely(tx.key->flags & KEY_FLAG_TAINTED)) {
|
|
tx.key = NULL;
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!(tx.key->conf.flags & IEEE80211_KEY_FLAG_SW_MGMT_TX) &&
|
|
tx.key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)
|
|
IEEE80211_SKB_CB(skb)->control.hw_key = &tx.key->conf;
|
|
|
|
tx.local = local;
|
|
tx.sdata = sdata;
|
|
__skb_queue_head_init(&tx.skbs);
|
|
__skb_queue_tail(&tx.skbs, skb);
|
|
res = ieee80211_tx_h_encrypt(&tx);
|
|
check_skb = __skb_dequeue(&tx.skbs);
|
|
/* we may crash after this, but it'd be a bug in crypto */
|
|
WARN_ON(check_skb != skb);
|
|
if (WARN_ON_ONCE(res != TX_CONTINUE))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
ieee80211_beacon_get_finish(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_link_data *link,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
struct beacon_data *beacon,
|
|
struct sk_buff *skb,
|
|
struct ieee80211_chanctx_conf *chanctx_conf,
|
|
u16 csa_off_base)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_tx_info *info;
|
|
enum nl80211_band band;
|
|
struct ieee80211_tx_rate_control txrc;
|
|
|
|
/* CSA offsets */
|
|
if (offs && beacon) {
|
|
u16 i;
|
|
|
|
for (i = 0; i < IEEE80211_MAX_CNTDWN_COUNTERS_NUM; i++) {
|
|
u16 csa_off = beacon->cntdwn_counter_offsets[i];
|
|
|
|
if (!csa_off)
|
|
continue;
|
|
|
|
offs->cntdwn_counter_offs[i] = csa_off_base + csa_off;
|
|
}
|
|
}
|
|
|
|
band = chanctx_conf->def.chan->band;
|
|
info = IEEE80211_SKB_CB(skb);
|
|
info->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
|
|
info->flags |= IEEE80211_TX_CTL_NO_ACK;
|
|
info->band = band;
|
|
|
|
memset(&txrc, 0, sizeof(txrc));
|
|
txrc.hw = hw;
|
|
txrc.sband = local->hw.wiphy->bands[band];
|
|
txrc.bss_conf = link->conf;
|
|
txrc.skb = skb;
|
|
txrc.reported_rate.idx = -1;
|
|
if (sdata->beacon_rate_set && sdata->beacon_rateidx_mask[band])
|
|
txrc.rate_idx_mask = sdata->beacon_rateidx_mask[band];
|
|
else
|
|
txrc.rate_idx_mask = sdata->rc_rateidx_mask[band];
|
|
txrc.bss = true;
|
|
rate_control_get_rate(sdata, NULL, &txrc);
|
|
|
|
info->control.vif = vif;
|
|
info->control.flags |= u32_encode_bits(link->link_id,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
info->flags |= IEEE80211_TX_CTL_CLEAR_PS_FILT |
|
|
IEEE80211_TX_CTL_ASSIGN_SEQ |
|
|
IEEE80211_TX_CTL_FIRST_FRAGMENT;
|
|
}
|
|
|
|
static void
|
|
ieee80211_beacon_add_mbssid(struct sk_buff *skb, struct beacon_data *beacon,
|
|
u8 i)
|
|
{
|
|
if (!beacon->mbssid_ies || !beacon->mbssid_ies->cnt ||
|
|
i > beacon->mbssid_ies->cnt)
|
|
return;
|
|
|
|
if (i < beacon->mbssid_ies->cnt) {
|
|
skb_put_data(skb, beacon->mbssid_ies->elem[i].data,
|
|
beacon->mbssid_ies->elem[i].len);
|
|
|
|
if (beacon->rnr_ies && beacon->rnr_ies->cnt) {
|
|
skb_put_data(skb, beacon->rnr_ies->elem[i].data,
|
|
beacon->rnr_ies->elem[i].len);
|
|
|
|
for (i = beacon->mbssid_ies->cnt; i < beacon->rnr_ies->cnt; i++)
|
|
skb_put_data(skb, beacon->rnr_ies->elem[i].data,
|
|
beacon->rnr_ies->elem[i].len);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/* i == beacon->mbssid_ies->cnt, include all MBSSID elements */
|
|
for (i = 0; i < beacon->mbssid_ies->cnt; i++)
|
|
skb_put_data(skb, beacon->mbssid_ies->elem[i].data,
|
|
beacon->mbssid_ies->elem[i].len);
|
|
}
|
|
|
|
static struct sk_buff *
|
|
ieee80211_beacon_get_ap(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_link_data *link,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
bool is_template,
|
|
struct beacon_data *beacon,
|
|
struct ieee80211_chanctx_conf *chanctx_conf,
|
|
u8 ema_index)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_if_ap *ap = &sdata->u.ap;
|
|
struct sk_buff *skb = NULL;
|
|
u16 csa_off_base = 0;
|
|
int mbssid_len;
|
|
|
|
if (beacon->cntdwn_counter_offsets[0]) {
|
|
if (!is_template)
|
|
ieee80211_beacon_update_cntdwn(vif, link->link_id);
|
|
|
|
ieee80211_set_beacon_cntdwn(sdata, beacon, link);
|
|
}
|
|
|
|
/* headroom, head length,
|
|
* tail length, maximum TIM length and multiple BSSID length
|
|
*/
|
|
mbssid_len = ieee80211_get_mbssid_beacon_len(beacon->mbssid_ies,
|
|
beacon->rnr_ies,
|
|
ema_index);
|
|
|
|
skb = dev_alloc_skb(local->tx_headroom + beacon->head_len +
|
|
beacon->tail_len + 256 +
|
|
local->hw.extra_beacon_tailroom + mbssid_len);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
skb_reserve(skb, local->tx_headroom);
|
|
skb_put_data(skb, beacon->head, beacon->head_len);
|
|
|
|
ieee80211_beacon_add_tim(sdata, link, &ap->ps, skb, is_template);
|
|
|
|
if (offs) {
|
|
offs->tim_offset = beacon->head_len;
|
|
offs->tim_length = skb->len - beacon->head_len;
|
|
offs->cntdwn_counter_offs[0] = beacon->cntdwn_counter_offsets[0];
|
|
|
|
if (mbssid_len) {
|
|
ieee80211_beacon_add_mbssid(skb, beacon, ema_index);
|
|
offs->mbssid_off = skb->len - mbssid_len;
|
|
}
|
|
|
|
/* for AP the csa offsets are from tail */
|
|
csa_off_base = skb->len;
|
|
}
|
|
|
|
if (beacon->tail)
|
|
skb_put_data(skb, beacon->tail, beacon->tail_len);
|
|
|
|
if (ieee80211_beacon_protect(skb, local, sdata, link) < 0) {
|
|
dev_kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
|
|
ieee80211_beacon_get_finish(hw, vif, link, offs, beacon, skb,
|
|
chanctx_conf, csa_off_base);
|
|
return skb;
|
|
}
|
|
|
|
static struct ieee80211_ema_beacons *
|
|
ieee80211_beacon_get_ap_ema_list(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_link_data *link,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
bool is_template, struct beacon_data *beacon,
|
|
struct ieee80211_chanctx_conf *chanctx_conf)
|
|
{
|
|
struct ieee80211_ema_beacons *ema = NULL;
|
|
|
|
if (!beacon->mbssid_ies || !beacon->mbssid_ies->cnt)
|
|
return NULL;
|
|
|
|
ema = kzalloc(struct_size(ema, bcn, beacon->mbssid_ies->cnt),
|
|
GFP_ATOMIC);
|
|
if (!ema)
|
|
return NULL;
|
|
|
|
for (ema->cnt = 0; ema->cnt < beacon->mbssid_ies->cnt; ema->cnt++) {
|
|
ema->bcn[ema->cnt].skb =
|
|
ieee80211_beacon_get_ap(hw, vif, link,
|
|
&ema->bcn[ema->cnt].offs,
|
|
is_template, beacon,
|
|
chanctx_conf, ema->cnt);
|
|
if (!ema->bcn[ema->cnt].skb)
|
|
break;
|
|
}
|
|
|
|
if (ema->cnt == beacon->mbssid_ies->cnt)
|
|
return ema;
|
|
|
|
ieee80211_beacon_free_ema_list(ema);
|
|
return NULL;
|
|
}
|
|
|
|
#define IEEE80211_INCLUDE_ALL_MBSSID_ELEMS -1
|
|
|
|
static struct sk_buff *
|
|
__ieee80211_beacon_get(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
bool is_template,
|
|
unsigned int link_id,
|
|
int ema_index,
|
|
struct ieee80211_ema_beacons **ema_beacons)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct beacon_data *beacon = NULL;
|
|
struct sk_buff *skb = NULL;
|
|
struct ieee80211_sub_if_data *sdata = NULL;
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
struct ieee80211_link_data *link;
|
|
|
|
rcu_read_lock();
|
|
|
|
sdata = vif_to_sdata(vif);
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (!link)
|
|
goto out;
|
|
chanctx_conf =
|
|
rcu_dereference(link->conf->chanctx_conf);
|
|
|
|
if (!ieee80211_sdata_running(sdata) || !chanctx_conf)
|
|
goto out;
|
|
|
|
if (offs)
|
|
memset(offs, 0, sizeof(*offs));
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP) {
|
|
beacon = rcu_dereference(link->u.ap.beacon);
|
|
if (!beacon)
|
|
goto out;
|
|
|
|
if (ema_beacons) {
|
|
*ema_beacons =
|
|
ieee80211_beacon_get_ap_ema_list(hw, vif, link,
|
|
offs,
|
|
is_template,
|
|
beacon,
|
|
chanctx_conf);
|
|
} else {
|
|
if (beacon->mbssid_ies && beacon->mbssid_ies->cnt) {
|
|
if (ema_index >= beacon->mbssid_ies->cnt)
|
|
goto out; /* End of MBSSID elements */
|
|
|
|
if (ema_index <= IEEE80211_INCLUDE_ALL_MBSSID_ELEMS)
|
|
ema_index = beacon->mbssid_ies->cnt;
|
|
} else {
|
|
ema_index = 0;
|
|
}
|
|
|
|
skb = ieee80211_beacon_get_ap(hw, vif, link, offs,
|
|
is_template, beacon,
|
|
chanctx_conf,
|
|
ema_index);
|
|
}
|
|
} else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) {
|
|
struct ieee80211_if_ibss *ifibss = &sdata->u.ibss;
|
|
struct ieee80211_hdr *hdr;
|
|
|
|
beacon = rcu_dereference(ifibss->presp);
|
|
if (!beacon)
|
|
goto out;
|
|
|
|
if (beacon->cntdwn_counter_offsets[0]) {
|
|
if (!is_template)
|
|
__ieee80211_beacon_update_cntdwn(beacon);
|
|
|
|
ieee80211_set_beacon_cntdwn(sdata, beacon, link);
|
|
}
|
|
|
|
skb = dev_alloc_skb(local->tx_headroom + beacon->head_len +
|
|
local->hw.extra_beacon_tailroom);
|
|
if (!skb)
|
|
goto out;
|
|
skb_reserve(skb, local->tx_headroom);
|
|
skb_put_data(skb, beacon->head, beacon->head_len);
|
|
|
|
hdr = (struct ieee80211_hdr *) skb->data;
|
|
hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_BEACON);
|
|
|
|
ieee80211_beacon_get_finish(hw, vif, link, offs, beacon, skb,
|
|
chanctx_conf, 0);
|
|
} else if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
|
|
|
|
beacon = rcu_dereference(ifmsh->beacon);
|
|
if (!beacon)
|
|
goto out;
|
|
|
|
if (beacon->cntdwn_counter_offsets[0]) {
|
|
if (!is_template)
|
|
/* TODO: For mesh csa_counter is in TU, so
|
|
* decrementing it by one isn't correct, but
|
|
* for now we leave it consistent with overall
|
|
* mac80211's behavior.
|
|
*/
|
|
__ieee80211_beacon_update_cntdwn(beacon);
|
|
|
|
ieee80211_set_beacon_cntdwn(sdata, beacon, link);
|
|
}
|
|
|
|
if (ifmsh->sync_ops)
|
|
ifmsh->sync_ops->adjust_tsf(sdata, beacon);
|
|
|
|
skb = dev_alloc_skb(local->tx_headroom +
|
|
beacon->head_len +
|
|
256 + /* TIM IE */
|
|
beacon->tail_len +
|
|
local->hw.extra_beacon_tailroom);
|
|
if (!skb)
|
|
goto out;
|
|
skb_reserve(skb, local->tx_headroom);
|
|
skb_put_data(skb, beacon->head, beacon->head_len);
|
|
ieee80211_beacon_add_tim(sdata, link, &ifmsh->ps, skb,
|
|
is_template);
|
|
|
|
if (offs) {
|
|
offs->tim_offset = beacon->head_len;
|
|
offs->tim_length = skb->len - beacon->head_len;
|
|
}
|
|
|
|
skb_put_data(skb, beacon->tail, beacon->tail_len);
|
|
ieee80211_beacon_get_finish(hw, vif, link, offs, beacon, skb,
|
|
chanctx_conf, 0);
|
|
} else {
|
|
WARN_ON(1);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return skb;
|
|
|
|
}
|
|
|
|
struct sk_buff *
|
|
ieee80211_beacon_get_template(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
unsigned int link_id)
|
|
{
|
|
return __ieee80211_beacon_get(hw, vif, offs, true, link_id,
|
|
IEEE80211_INCLUDE_ALL_MBSSID_ELEMS, NULL);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_get_template);
|
|
|
|
struct sk_buff *
|
|
ieee80211_beacon_get_template_ema_index(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
struct ieee80211_mutable_offsets *offs,
|
|
unsigned int link_id, u8 ema_index)
|
|
{
|
|
return __ieee80211_beacon_get(hw, vif, offs, true, link_id, ema_index,
|
|
NULL);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_get_template_ema_index);
|
|
|
|
void ieee80211_beacon_free_ema_list(struct ieee80211_ema_beacons *ema_beacons)
|
|
{
|
|
u8 i;
|
|
|
|
if (!ema_beacons)
|
|
return;
|
|
|
|
for (i = 0; i < ema_beacons->cnt; i++)
|
|
kfree_skb(ema_beacons->bcn[i].skb);
|
|
|
|
kfree(ema_beacons);
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_free_ema_list);
|
|
|
|
struct ieee80211_ema_beacons *
|
|
ieee80211_beacon_get_template_ema_list(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
unsigned int link_id)
|
|
{
|
|
struct ieee80211_ema_beacons *ema_beacons = NULL;
|
|
|
|
WARN_ON(__ieee80211_beacon_get(hw, vif, NULL, true, link_id, 0,
|
|
&ema_beacons));
|
|
|
|
return ema_beacons;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_get_template_ema_list);
|
|
|
|
struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
u16 *tim_offset, u16 *tim_length,
|
|
unsigned int link_id)
|
|
{
|
|
struct ieee80211_mutable_offsets offs = {};
|
|
struct sk_buff *bcn = __ieee80211_beacon_get(hw, vif, &offs, false,
|
|
link_id,
|
|
IEEE80211_INCLUDE_ALL_MBSSID_ELEMS,
|
|
NULL);
|
|
struct sk_buff *copy;
|
|
|
|
if (!bcn)
|
|
return bcn;
|
|
|
|
if (tim_offset)
|
|
*tim_offset = offs.tim_offset;
|
|
|
|
if (tim_length)
|
|
*tim_length = offs.tim_length;
|
|
|
|
if (ieee80211_hw_check(hw, BEACON_TX_STATUS) ||
|
|
!hw_to_local(hw)->monitors)
|
|
return bcn;
|
|
|
|
/* send a copy to monitor interfaces */
|
|
copy = skb_copy(bcn, GFP_ATOMIC);
|
|
if (!copy)
|
|
return bcn;
|
|
|
|
ieee80211_tx_monitor(hw_to_local(hw), copy, 1, false, NULL);
|
|
|
|
return bcn;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_beacon_get_tim);
|
|
|
|
struct sk_buff *ieee80211_proberesp_get(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct probe_resp *presp = NULL;
|
|
struct ieee80211_hdr *hdr;
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_AP)
|
|
return NULL;
|
|
|
|
rcu_read_lock();
|
|
presp = rcu_dereference(sdata->deflink.u.ap.probe_resp);
|
|
if (!presp)
|
|
goto out;
|
|
|
|
skb = dev_alloc_skb(presp->len);
|
|
if (!skb)
|
|
goto out;
|
|
|
|
skb_put_data(skb, presp->data, presp->len);
|
|
|
|
hdr = (struct ieee80211_hdr *) skb->data;
|
|
memset(hdr->addr1, 0, sizeof(hdr->addr1));
|
|
|
|
out:
|
|
rcu_read_unlock();
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_proberesp_get);
|
|
|
|
struct sk_buff *ieee80211_get_fils_discovery_tmpl(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct fils_discovery_data *tmpl = NULL;
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_AP)
|
|
return NULL;
|
|
|
|
rcu_read_lock();
|
|
tmpl = rcu_dereference(sdata->deflink.u.ap.fils_discovery);
|
|
if (!tmpl) {
|
|
rcu_read_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
skb = dev_alloc_skb(sdata->local->hw.extra_tx_headroom + tmpl->len);
|
|
if (skb) {
|
|
skb_reserve(skb, sdata->local->hw.extra_tx_headroom);
|
|
skb_put_data(skb, tmpl->data, tmpl->len);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_get_fils_discovery_tmpl);
|
|
|
|
struct sk_buff *
|
|
ieee80211_get_unsol_bcast_probe_resp_tmpl(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct sk_buff *skb = NULL;
|
|
struct unsol_bcast_probe_resp_data *tmpl = NULL;
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
|
|
if (sdata->vif.type != NL80211_IFTYPE_AP)
|
|
return NULL;
|
|
|
|
rcu_read_lock();
|
|
tmpl = rcu_dereference(sdata->deflink.u.ap.unsol_bcast_probe_resp);
|
|
if (!tmpl) {
|
|
rcu_read_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
skb = dev_alloc_skb(sdata->local->hw.extra_tx_headroom + tmpl->len);
|
|
if (skb) {
|
|
skb_reserve(skb, sdata->local->hw.extra_tx_headroom);
|
|
skb_put_data(skb, tmpl->data, tmpl->len);
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_get_unsol_bcast_probe_resp_tmpl);
|
|
|
|
struct sk_buff *ieee80211_pspoll_get(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct ieee80211_pspoll *pspoll;
|
|
struct ieee80211_local *local;
|
|
struct sk_buff *skb;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_STATION))
|
|
return NULL;
|
|
|
|
sdata = vif_to_sdata(vif);
|
|
local = sdata->local;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*pspoll));
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
|
|
pspoll = skb_put_zero(skb, sizeof(*pspoll));
|
|
pspoll->frame_control = cpu_to_le16(IEEE80211_FTYPE_CTL |
|
|
IEEE80211_STYPE_PSPOLL);
|
|
pspoll->aid = cpu_to_le16(sdata->vif.cfg.aid);
|
|
|
|
/* aid in PS-Poll has its two MSBs each set to 1 */
|
|
pspoll->aid |= cpu_to_le16(1 << 15 | 1 << 14);
|
|
|
|
memcpy(pspoll->bssid, sdata->deflink.u.mgd.bssid, ETH_ALEN);
|
|
memcpy(pspoll->ta, vif->addr, ETH_ALEN);
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_pspoll_get);
|
|
|
|
struct sk_buff *ieee80211_nullfunc_get(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif,
|
|
int link_id, bool qos_ok)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct ieee80211_link_data *link = NULL;
|
|
struct ieee80211_hdr_3addr *nullfunc;
|
|
struct sk_buff *skb;
|
|
bool qos = false;
|
|
|
|
if (WARN_ON(vif->type != NL80211_IFTYPE_STATION))
|
|
return NULL;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom +
|
|
sizeof(*nullfunc) + 2);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
rcu_read_lock();
|
|
if (qos_ok) {
|
|
struct sta_info *sta;
|
|
|
|
sta = sta_info_get(sdata, vif->cfg.ap_addr);
|
|
qos = sta && sta->sta.wme;
|
|
}
|
|
|
|
if (link_id >= 0) {
|
|
link = rcu_dereference(sdata->link[link_id]);
|
|
if (WARN_ON_ONCE(!link)) {
|
|
rcu_read_unlock();
|
|
kfree_skb(skb);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
|
|
nullfunc = skb_put_zero(skb, sizeof(*nullfunc));
|
|
nullfunc->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA |
|
|
IEEE80211_STYPE_NULLFUNC |
|
|
IEEE80211_FCTL_TODS);
|
|
if (qos) {
|
|
__le16 qoshdr = cpu_to_le16(7);
|
|
|
|
BUILD_BUG_ON((IEEE80211_STYPE_QOS_NULLFUNC |
|
|
IEEE80211_STYPE_NULLFUNC) !=
|
|
IEEE80211_STYPE_QOS_NULLFUNC);
|
|
nullfunc->frame_control |=
|
|
cpu_to_le16(IEEE80211_STYPE_QOS_NULLFUNC);
|
|
skb->priority = 7;
|
|
skb_set_queue_mapping(skb, IEEE80211_AC_VO);
|
|
skb_put_data(skb, &qoshdr, sizeof(qoshdr));
|
|
}
|
|
|
|
if (link) {
|
|
memcpy(nullfunc->addr1, link->conf->bssid, ETH_ALEN);
|
|
memcpy(nullfunc->addr2, link->conf->addr, ETH_ALEN);
|
|
memcpy(nullfunc->addr3, link->conf->bssid, ETH_ALEN);
|
|
} else {
|
|
memcpy(nullfunc->addr1, vif->cfg.ap_addr, ETH_ALEN);
|
|
memcpy(nullfunc->addr2, vif->addr, ETH_ALEN);
|
|
memcpy(nullfunc->addr3, vif->cfg.ap_addr, ETH_ALEN);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_nullfunc_get);
|
|
|
|
struct sk_buff *ieee80211_probereq_get(struct ieee80211_hw *hw,
|
|
const u8 *src_addr,
|
|
const u8 *ssid, size_t ssid_len,
|
|
size_t tailroom)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct ieee80211_hdr_3addr *hdr;
|
|
struct sk_buff *skb;
|
|
size_t ie_ssid_len;
|
|
u8 *pos;
|
|
|
|
ie_ssid_len = 2 + ssid_len;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + sizeof(*hdr) +
|
|
ie_ssid_len + tailroom);
|
|
if (!skb)
|
|
return NULL;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
|
|
hdr = skb_put_zero(skb, sizeof(*hdr));
|
|
hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
|
|
IEEE80211_STYPE_PROBE_REQ);
|
|
eth_broadcast_addr(hdr->addr1);
|
|
memcpy(hdr->addr2, src_addr, ETH_ALEN);
|
|
eth_broadcast_addr(hdr->addr3);
|
|
|
|
pos = skb_put(skb, ie_ssid_len);
|
|
*pos++ = WLAN_EID_SSID;
|
|
*pos++ = ssid_len;
|
|
if (ssid_len)
|
|
memcpy(pos, ssid, ssid_len);
|
|
pos += ssid_len;
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_probereq_get);
|
|
|
|
void ieee80211_rts_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
|
const void *frame, size_t frame_len,
|
|
const struct ieee80211_tx_info *frame_txctl,
|
|
struct ieee80211_rts *rts)
|
|
{
|
|
const struct ieee80211_hdr *hdr = frame;
|
|
|
|
rts->frame_control =
|
|
cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_RTS);
|
|
rts->duration = ieee80211_rts_duration(hw, vif, frame_len,
|
|
frame_txctl);
|
|
memcpy(rts->ra, hdr->addr1, sizeof(rts->ra));
|
|
memcpy(rts->ta, hdr->addr2, sizeof(rts->ta));
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_rts_get);
|
|
|
|
void ieee80211_ctstoself_get(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
|
|
const void *frame, size_t frame_len,
|
|
const struct ieee80211_tx_info *frame_txctl,
|
|
struct ieee80211_cts *cts)
|
|
{
|
|
const struct ieee80211_hdr *hdr = frame;
|
|
|
|
cts->frame_control =
|
|
cpu_to_le16(IEEE80211_FTYPE_CTL | IEEE80211_STYPE_CTS);
|
|
cts->duration = ieee80211_ctstoself_duration(hw, vif,
|
|
frame_len, frame_txctl);
|
|
memcpy(cts->ra, hdr->addr1, sizeof(cts->ra));
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_ctstoself_get);
|
|
|
|
struct sk_buff *
|
|
ieee80211_get_buffered_bc(struct ieee80211_hw *hw,
|
|
struct ieee80211_vif *vif)
|
|
{
|
|
struct ieee80211_local *local = hw_to_local(hw);
|
|
struct sk_buff *skb = NULL;
|
|
struct ieee80211_tx_data tx;
|
|
struct ieee80211_sub_if_data *sdata;
|
|
struct ps_data *ps;
|
|
struct ieee80211_tx_info *info;
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
|
|
sdata = vif_to_sdata(vif);
|
|
|
|
rcu_read_lock();
|
|
chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
|
|
if (!chanctx_conf)
|
|
goto out;
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP) {
|
|
struct beacon_data *beacon =
|
|
rcu_dereference(sdata->deflink.u.ap.beacon);
|
|
|
|
if (!beacon || !beacon->head)
|
|
goto out;
|
|
|
|
ps = &sdata->u.ap.ps;
|
|
} else if (ieee80211_vif_is_mesh(&sdata->vif)) {
|
|
ps = &sdata->u.mesh.ps;
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
if (ps->dtim_count != 0 || !ps->dtim_bc_mc)
|
|
goto out; /* send buffered bc/mc only after DTIM beacon */
|
|
|
|
while (1) {
|
|
skb = skb_dequeue(&ps->bc_buf);
|
|
if (!skb)
|
|
goto out;
|
|
local->total_ps_buffered--;
|
|
|
|
if (!skb_queue_empty(&ps->bc_buf) && skb->len >= 2) {
|
|
struct ieee80211_hdr *hdr =
|
|
(struct ieee80211_hdr *) skb->data;
|
|
/* more buffered multicast/broadcast frames ==> set
|
|
* MoreData flag in IEEE 802.11 header to inform PS
|
|
* STAs */
|
|
hdr->frame_control |=
|
|
cpu_to_le16(IEEE80211_FCTL_MOREDATA);
|
|
}
|
|
|
|
if (sdata->vif.type == NL80211_IFTYPE_AP)
|
|
sdata = IEEE80211_DEV_TO_SUB_IF(skb->dev);
|
|
if (!ieee80211_tx_prepare(sdata, &tx, NULL, skb))
|
|
break;
|
|
ieee80211_free_txskb(hw, skb);
|
|
}
|
|
|
|
info = IEEE80211_SKB_CB(skb);
|
|
|
|
tx.flags |= IEEE80211_TX_PS_BUFFERED;
|
|
info->band = chanctx_conf->def.chan->band;
|
|
|
|
if (invoke_tx_handlers(&tx))
|
|
skb = NULL;
|
|
out:
|
|
rcu_read_unlock();
|
|
|
|
return skb;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_get_buffered_bc);
|
|
|
|
int ieee80211_reserve_tid(struct ieee80211_sta *pubsta, u8 tid)
|
|
{
|
|
struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
|
|
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
|
struct ieee80211_local *local = sdata->local;
|
|
int ret;
|
|
u32 queues;
|
|
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
/* only some cases are supported right now */
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (WARN_ON(tid >= IEEE80211_NUM_UPS))
|
|
return -EINVAL;
|
|
|
|
if (sta->reserved_tid == tid) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
if (sta->reserved_tid != IEEE80211_TID_UNRESERVED) {
|
|
sdata_err(sdata, "TID reservation already active\n");
|
|
ret = -EALREADY;
|
|
goto out;
|
|
}
|
|
|
|
ieee80211_stop_vif_queues(sdata->local, sdata,
|
|
IEEE80211_QUEUE_STOP_REASON_RESERVE_TID);
|
|
|
|
synchronize_net();
|
|
|
|
/* Tear down BA sessions so we stop aggregating on this TID */
|
|
if (ieee80211_hw_check(&local->hw, AMPDU_AGGREGATION)) {
|
|
set_sta_flag(sta, WLAN_STA_BLOCK_BA);
|
|
__ieee80211_stop_tx_ba_session(sta, tid,
|
|
AGG_STOP_LOCAL_REQUEST);
|
|
}
|
|
|
|
queues = BIT(sdata->vif.hw_queue[ieee802_1d_to_ac[tid]]);
|
|
__ieee80211_flush_queues(local, sdata, queues, false);
|
|
|
|
sta->reserved_tid = tid;
|
|
|
|
ieee80211_wake_vif_queues(local, sdata,
|
|
IEEE80211_QUEUE_STOP_REASON_RESERVE_TID);
|
|
|
|
if (ieee80211_hw_check(&local->hw, AMPDU_AGGREGATION))
|
|
clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
|
|
|
|
ret = 0;
|
|
out:
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_reserve_tid);
|
|
|
|
void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid)
|
|
{
|
|
struct sta_info *sta = container_of(pubsta, struct sta_info, sta);
|
|
struct ieee80211_sub_if_data *sdata = sta->sdata;
|
|
|
|
lockdep_assert_wiphy(sdata->local->hw.wiphy);
|
|
|
|
/* only some cases are supported right now */
|
|
switch (sdata->vif.type) {
|
|
case NL80211_IFTYPE_STATION:
|
|
case NL80211_IFTYPE_AP:
|
|
case NL80211_IFTYPE_AP_VLAN:
|
|
break;
|
|
default:
|
|
WARN_ON(1);
|
|
return;
|
|
}
|
|
|
|
if (tid != sta->reserved_tid) {
|
|
sdata_err(sdata, "TID to unreserve (%d) isn't reserved\n", tid);
|
|
return;
|
|
}
|
|
|
|
sta->reserved_tid = IEEE80211_TID_UNRESERVED;
|
|
}
|
|
EXPORT_SYMBOL(ieee80211_unreserve_tid);
|
|
|
|
void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, int tid, int link_id,
|
|
enum nl80211_band band)
|
|
{
|
|
const struct ieee80211_hdr *hdr = (void *)skb->data;
|
|
int ac = ieee80211_ac_from_tid(tid);
|
|
unsigned int link;
|
|
|
|
skb_reset_mac_header(skb);
|
|
skb_set_queue_mapping(skb, ac);
|
|
skb->priority = tid;
|
|
|
|
skb->dev = sdata->dev;
|
|
|
|
BUILD_BUG_ON(IEEE80211_LINK_UNSPECIFIED < IEEE80211_MLD_MAX_NUM_LINKS);
|
|
BUILD_BUG_ON(!FIELD_FIT(IEEE80211_TX_CTRL_MLO_LINK,
|
|
IEEE80211_LINK_UNSPECIFIED));
|
|
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
link = 0;
|
|
} else if (link_id >= 0) {
|
|
link = link_id;
|
|
} else if (memcmp(sdata->vif.addr, hdr->addr2, ETH_ALEN) == 0) {
|
|
/* address from the MLD */
|
|
link = IEEE80211_LINK_UNSPECIFIED;
|
|
} else {
|
|
/* otherwise must be addressed from a link */
|
|
rcu_read_lock();
|
|
for (link = 0; link < ARRAY_SIZE(sdata->vif.link_conf); link++) {
|
|
struct ieee80211_bss_conf *link_conf;
|
|
|
|
link_conf = rcu_dereference(sdata->vif.link_conf[link]);
|
|
if (!link_conf)
|
|
continue;
|
|
if (memcmp(link_conf->addr, hdr->addr2, ETH_ALEN) == 0)
|
|
break;
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
if (WARN_ON_ONCE(link == ARRAY_SIZE(sdata->vif.link_conf)))
|
|
link = ffs(sdata->vif.active_links) - 1;
|
|
}
|
|
|
|
IEEE80211_SKB_CB(skb)->control.flags |=
|
|
u32_encode_bits(link, IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
/*
|
|
* The other path calling ieee80211_xmit is from the tasklet,
|
|
* and while we can handle concurrent transmissions locking
|
|
* requirements are that we do not come into tx with bhs on.
|
|
*/
|
|
local_bh_disable();
|
|
IEEE80211_SKB_CB(skb)->band = band;
|
|
ieee80211_xmit(sdata, NULL, skb);
|
|
local_bh_enable();
|
|
}
|
|
|
|
void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata,
|
|
struct sk_buff *skb, int tid, int link_id)
|
|
{
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
enum nl80211_band band;
|
|
|
|
rcu_read_lock();
|
|
if (!ieee80211_vif_is_mld(&sdata->vif)) {
|
|
WARN_ON(link_id >= 0);
|
|
chanctx_conf =
|
|
rcu_dereference(sdata->vif.bss_conf.chanctx_conf);
|
|
if (WARN_ON(!chanctx_conf)) {
|
|
rcu_read_unlock();
|
|
kfree_skb(skb);
|
|
return;
|
|
}
|
|
band = chanctx_conf->def.chan->band;
|
|
} else {
|
|
WARN_ON(link_id >= 0 &&
|
|
!(sdata->vif.active_links & BIT(link_id)));
|
|
/* MLD transmissions must not rely on the band */
|
|
band = 0;
|
|
}
|
|
|
|
__ieee80211_tx_skb_tid_band(sdata, skb, tid, link_id, band);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *buf, size_t len,
|
|
const u8 *dest, __be16 proto, bool unencrypted,
|
|
int link_id, u64 *cookie)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sta_info *sta;
|
|
struct sk_buff *skb;
|
|
struct ethhdr *ehdr;
|
|
u32 ctrl_flags = 0;
|
|
u32 flags = 0;
|
|
int err;
|
|
|
|
/* mutex lock is only needed for incrementing the cookie counter */
|
|
lockdep_assert_wiphy(local->hw.wiphy);
|
|
|
|
/* Only accept CONTROL_PORT_PROTOCOL configured in CONNECT/ASSOCIATE
|
|
* or Pre-Authentication
|
|
*/
|
|
if (proto != sdata->control_port_protocol &&
|
|
proto != cpu_to_be16(ETH_P_PREAUTH))
|
|
return -EINVAL;
|
|
|
|
if (proto == sdata->control_port_protocol)
|
|
ctrl_flags |= IEEE80211_TX_CTRL_PORT_CTRL_PROTO |
|
|
IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP;
|
|
|
|
if (unencrypted)
|
|
flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT;
|
|
|
|
if (cookie)
|
|
ctrl_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS;
|
|
|
|
flags |= IEEE80211_TX_INTFL_NL80211_FRAME_TX;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom +
|
|
sizeof(struct ethhdr) + len);
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom + sizeof(struct ethhdr));
|
|
|
|
skb_put_data(skb, buf, len);
|
|
|
|
ehdr = skb_push(skb, sizeof(struct ethhdr));
|
|
memcpy(ehdr->h_dest, dest, ETH_ALEN);
|
|
|
|
/* we may override the SA for MLO STA later */
|
|
if (link_id < 0) {
|
|
ctrl_flags |= u32_encode_bits(IEEE80211_LINK_UNSPECIFIED,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN);
|
|
} else {
|
|
struct ieee80211_bss_conf *link_conf;
|
|
|
|
ctrl_flags |= u32_encode_bits(link_id,
|
|
IEEE80211_TX_CTRL_MLO_LINK);
|
|
|
|
rcu_read_lock();
|
|
link_conf = rcu_dereference(sdata->vif.link_conf[link_id]);
|
|
if (!link_conf) {
|
|
dev_kfree_skb(skb);
|
|
rcu_read_unlock();
|
|
return -ENOLINK;
|
|
}
|
|
memcpy(ehdr->h_source, link_conf->addr, ETH_ALEN);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
ehdr->h_proto = proto;
|
|
|
|
skb->dev = dev;
|
|
skb->protocol = proto;
|
|
skb_reset_network_header(skb);
|
|
skb_reset_mac_header(skb);
|
|
|
|
if (local->hw.queues < IEEE80211_NUM_ACS)
|
|
goto start_xmit;
|
|
|
|
/* update QoS header to prioritize control port frames if possible,
|
|
* priorization also happens for control port frames send over
|
|
* AF_PACKET
|
|
*/
|
|
rcu_read_lock();
|
|
err = ieee80211_lookup_ra_sta(sdata, skb, &sta);
|
|
if (err) {
|
|
dev_kfree_skb(skb);
|
|
rcu_read_unlock();
|
|
return err;
|
|
}
|
|
|
|
if (!IS_ERR(sta)) {
|
|
u16 queue = ieee80211_select_queue(sdata, sta, skb);
|
|
|
|
skb_set_queue_mapping(skb, queue);
|
|
|
|
/*
|
|
* for MLO STA, the SA should be the AP MLD address, but
|
|
* the link ID has been selected already
|
|
*/
|
|
if (sta && sta->sta.mlo)
|
|
memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN);
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
start_xmit:
|
|
local_bh_disable();
|
|
__ieee80211_subif_start_xmit(skb, skb->dev, flags, ctrl_flags, cookie);
|
|
local_bh_enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev,
|
|
const u8 *buf, size_t len)
|
|
{
|
|
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
struct ieee80211_local *local = sdata->local;
|
|
struct sk_buff *skb;
|
|
|
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len +
|
|
30 + /* header size */
|
|
18); /* 11s header size */
|
|
if (!skb)
|
|
return -ENOMEM;
|
|
|
|
skb_reserve(skb, local->hw.extra_tx_headroom);
|
|
skb_put_data(skb, buf, len);
|
|
|
|
skb->dev = dev;
|
|
skb->protocol = htons(ETH_P_802_3);
|
|
skb_reset_network_header(skb);
|
|
skb_reset_mac_header(skb);
|
|
|
|
local_bh_disable();
|
|
__ieee80211_subif_start_xmit(skb, skb->dev, 0,
|
|
IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP,
|
|
NULL);
|
|
local_bh_enable();
|
|
|
|
return 0;
|
|
}
|