Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  core.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright(c) 2019-2020  Realtek Corporation
 */

#include <linux/ip.h>
#include <linux/udp.h>

#include "cam.h"
#include "chan.h"
#include "coex.h"
#include "core.h"
#include "efuse.h"
#include "fw.h"
#include "mac.h"
#include "phy.h"
#include "ps.h"
#include "reg.h"
#include "sar.h"
#include "ser.h"
#include "txrx.h"
#include "util.h"
#include "wow.h"

static bool rtw89_disable_ps_mode;
module_param_named(disable_ps_mode, rtw89_disable_ps_mode, bool, 0644);
MODULE_PARM_DESC(disable_ps_mode, "Set Y to disable low power mode");

#define RTW89_DEF_CHAN(_freq, _hw_val, _flags, _band) \
 { .center_freq = _freq, .hw_value = _hw_val, .flags = _flags, .band = _band, }
#define RTW89_DEF_CHAN_2G(_freq, _hw_val) \
 RTW89_DEF_CHAN(_freq, _hw_val, 0, NL80211_BAND_2GHZ)
#define RTW89_DEF_CHAN_5G(_freq, _hw_val) \
 RTW89_DEF_CHAN(_freq, _hw_val, 0, NL80211_BAND_5GHZ)
#define RTW89_DEF_CHAN_5G_NO_HT40MINUS(_freq, _hw_val) \
 RTW89_DEF_CHAN(_freq, _hw_val, IEEE80211_CHAN_NO_HT40MINUS, NL80211_BAND_5GHZ)
#define RTW89_DEF_CHAN_6G(_freq, _hw_val) \
 RTW89_DEF_CHAN(_freq, _hw_val, 0, NL80211_BAND_6GHZ)

static struct ieee80211_channel rtw89_channels_2ghz[] = {
 RTW89_DEF_CHAN_2G(2412, 1),
 RTW89_DEF_CHAN_2G(2417, 2),
 RTW89_DEF_CHAN_2G(2422, 3),
 RTW89_DEF_CHAN_2G(2427, 4),
 RTW89_DEF_CHAN_2G(2432, 5),
 RTW89_DEF_CHAN_2G(2437, 6),
 RTW89_DEF_CHAN_2G(2442, 7),
 RTW89_DEF_CHAN_2G(2447, 8),
 RTW89_DEF_CHAN_2G(2452, 9),
 RTW89_DEF_CHAN_2G(2457, 10),
 RTW89_DEF_CHAN_2G(2462, 11),
 RTW89_DEF_CHAN_2G(2467, 12),
 RTW89_DEF_CHAN_2G(2472, 13),
 RTW89_DEF_CHAN_2G(2484, 14),
};

static struct ieee80211_channel rtw89_channels_5ghz[] = {
 RTW89_DEF_CHAN_5G(5180, 36),
 RTW89_DEF_CHAN_5G(5200, 40),
 RTW89_DEF_CHAN_5G(5220, 44),
 RTW89_DEF_CHAN_5G(5240, 48),
 RTW89_DEF_CHAN_5G(5260, 52),
 RTW89_DEF_CHAN_5G(5280, 56),
 RTW89_DEF_CHAN_5G(5300, 60),
 RTW89_DEF_CHAN_5G(5320, 64),
 RTW89_DEF_CHAN_5G(5500, 100),
 RTW89_DEF_CHAN_5G(5520, 104),
 RTW89_DEF_CHAN_5G(5540, 108),
 RTW89_DEF_CHAN_5G(5560, 112),
 RTW89_DEF_CHAN_5G(5580, 116),
 RTW89_DEF_CHAN_5G(5600, 120),
 RTW89_DEF_CHAN_5G(5620, 124),
 RTW89_DEF_CHAN_5G(5640, 128),
 RTW89_DEF_CHAN_5G(5660, 132),
 RTW89_DEF_CHAN_5G(5680, 136),
 RTW89_DEF_CHAN_5G(5700, 140),
 RTW89_DEF_CHAN_5G(5720, 144),
 RTW89_DEF_CHAN_5G(5745, 149),
 RTW89_DEF_CHAN_5G(5765, 153),
 RTW89_DEF_CHAN_5G(5785, 157),
 RTW89_DEF_CHAN_5G(5805, 161),
 RTW89_DEF_CHAN_5G_NO_HT40MINUS(5825, 165),
 RTW89_DEF_CHAN_5G(5845, 169),
 RTW89_DEF_CHAN_5G(5865, 173),
 RTW89_DEF_CHAN_5G(5885, 177),
};

static_assert(RTW89_5GHZ_UNII4_START_INDEX + RTW89_5GHZ_UNII4_CHANNEL_NUM ==
       ARRAY_SIZE(rtw89_channels_5ghz));

static struct ieee80211_channel rtw89_channels_6ghz[] = {
 RTW89_DEF_CHAN_6G(5955, 1),
 RTW89_DEF_CHAN_6G(5975, 5),
 RTW89_DEF_CHAN_6G(5995, 9),
 RTW89_DEF_CHAN_6G(6015, 13),
 RTW89_DEF_CHAN_6G(6035, 17),
 RTW89_DEF_CHAN_6G(6055, 21),
 RTW89_DEF_CHAN_6G(6075, 25),
 RTW89_DEF_CHAN_6G(6095, 29),
 RTW89_DEF_CHAN_6G(6115, 33),
 RTW89_DEF_CHAN_6G(6135, 37),
 RTW89_DEF_CHAN_6G(6155, 41),
 RTW89_DEF_CHAN_6G(6175, 45),
 RTW89_DEF_CHAN_6G(6195, 49),
 RTW89_DEF_CHAN_6G(6215, 53),
 RTW89_DEF_CHAN_6G(6235, 57),
 RTW89_DEF_CHAN_6G(6255, 61),
 RTW89_DEF_CHAN_6G(6275, 65),
 RTW89_DEF_CHAN_6G(6295, 69),
 RTW89_DEF_CHAN_6G(6315, 73),
 RTW89_DEF_CHAN_6G(6335, 77),
 RTW89_DEF_CHAN_6G(6355, 81),
 RTW89_DEF_CHAN_6G(6375, 85),
 RTW89_DEF_CHAN_6G(6395, 89),
 RTW89_DEF_CHAN_6G(6415, 93),
 RTW89_DEF_CHAN_6G(6435, 97),
 RTW89_DEF_CHAN_6G(6455, 101),
 RTW89_DEF_CHAN_6G(6475, 105),
 RTW89_DEF_CHAN_6G(6495, 109),
 RTW89_DEF_CHAN_6G(6515, 113),
 RTW89_DEF_CHAN_6G(6535, 117),
 RTW89_DEF_CHAN_6G(6555, 121),
 RTW89_DEF_CHAN_6G(6575, 125),
 RTW89_DEF_CHAN_6G(6595, 129),
 RTW89_DEF_CHAN_6G(6615, 133),
 RTW89_DEF_CHAN_6G(6635, 137),
 RTW89_DEF_CHAN_6G(6655, 141),
 RTW89_DEF_CHAN_6G(6675, 145),
 RTW89_DEF_CHAN_6G(6695, 149),
 RTW89_DEF_CHAN_6G(6715, 153),
 RTW89_DEF_CHAN_6G(6735, 157),
 RTW89_DEF_CHAN_6G(6755, 161),
 RTW89_DEF_CHAN_6G(6775, 165),
 RTW89_DEF_CHAN_6G(6795, 169),
 RTW89_DEF_CHAN_6G(6815, 173),
 RTW89_DEF_CHAN_6G(6835, 177),
 RTW89_DEF_CHAN_6G(6855, 181),
 RTW89_DEF_CHAN_6G(6875, 185),
 RTW89_DEF_CHAN_6G(6895, 189),
 RTW89_DEF_CHAN_6G(6915, 193),
 RTW89_DEF_CHAN_6G(6935, 197),
 RTW89_DEF_CHAN_6G(6955, 201),
 RTW89_DEF_CHAN_6G(6975, 205),
 RTW89_DEF_CHAN_6G(6995, 209),
 RTW89_DEF_CHAN_6G(7015, 213),
 RTW89_DEF_CHAN_6G(7035, 217),
 RTW89_DEF_CHAN_6G(7055, 221),
 RTW89_DEF_CHAN_6G(7075, 225),
 RTW89_DEF_CHAN_6G(7095, 229),
 RTW89_DEF_CHAN_6G(7115, 233),
};

static struct ieee80211_rate rtw89_bitrates[] = {
 { .bitrate = 10,  .hw_value = 0x00, },
 { .bitrate = 20,  .hw_value = 0x01, },
 { .bitrate = 55,  .hw_value = 0x02, },
 { .bitrate = 110, .hw_value = 0x03, },
 { .bitrate = 60,  .hw_value = 0x04, },
 { .bitrate = 90,  .hw_value = 0x05, },
 { .bitrate = 120, .hw_value = 0x06, },
 { .bitrate = 180, .hw_value = 0x07, },
 { .bitrate = 240, .hw_value = 0x08, },
 { .bitrate = 360, .hw_value = 0x09, },
 { .bitrate = 480, .hw_value = 0x0a, },
 { .bitrate = 540, .hw_value = 0x0b, },
};

static const struct ieee80211_iface_limit rtw89_iface_limits[] = {
 {
  .max = 1,
  .types = BIT(NL80211_IFTYPE_STATION),
 },
 {
  .max = 1,
  .types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
    BIT(NL80211_IFTYPE_P2P_GO) |
    BIT(NL80211_IFTYPE_AP),
 },
};

static const struct ieee80211_iface_limit rtw89_iface_limits_mcc[] = {
 {
  .max = 1,
  .types = BIT(NL80211_IFTYPE_STATION),
 },
 {
  .max = 1,
  .types = BIT(NL80211_IFTYPE_P2P_CLIENT) |
    BIT(NL80211_IFTYPE_P2P_GO),
 },
};

static const struct ieee80211_iface_combination rtw89_iface_combs[] = {
 {
  .limits = rtw89_iface_limits,
  .n_limits = ARRAY_SIZE(rtw89_iface_limits),
  .max_interfaces = RTW89_MAX_INTERFACE_NUM,
  .num_different_channels = 1,
 },
 {
  .limits = rtw89_iface_limits_mcc,
  .n_limits = ARRAY_SIZE(rtw89_iface_limits_mcc),
  .max_interfaces = RTW89_MAX_INTERFACE_NUM,
  .num_different_channels = 2,
 },
};

static const u8 rtw89_ext_capa_sta[] = {
 [0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING,
 [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT,
 [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF,
};

static const struct wiphy_iftype_ext_capab rtw89_iftypes_ext_capa[] = {
 {
  .iftype = NL80211_IFTYPE_STATION,
  .extended_capabilities = rtw89_ext_capa_sta,
  .extended_capabilities_mask = rtw89_ext_capa_sta,
  .extended_capabilities_len = sizeof(rtw89_ext_capa_sta),
  /* relevant only if EHT is supported */
  .eml_capabilities = 0,
  .mld_capa_and_ops = 0,
 },
};

#define RTW89_6GHZ_SPAN_HEAD 6145
#define RTW89_6GHZ_SPAN_IDX(center_freq) \
 ((((int)(center_freq) - RTW89_6GHZ_SPAN_HEAD) / 5) / 2)

#define RTW89_DECL_6GHZ_SPAN(center_freq, subband_l, subband_h) \
 [RTW89_6GHZ_SPAN_IDX(center_freq)] = { \
  .sar_subband_low = RTW89_SAR_6GHZ_ ## subband_l, \
  .sar_subband_high = RTW89_SAR_6GHZ_ ## subband_h, \
  .acpi_sar_subband_low = RTW89_ACPI_SAR_6GHZ_ ## subband_l, \
  .acpi_sar_subband_high = RTW89_ACPI_SAR_6GHZ_ ## subband_h, \
  .ant_gain_subband_low = RTW89_ANT_GAIN_6GHZ_ ## subband_l, \
  .ant_gain_subband_high = RTW89_ANT_GAIN_6GHZ_ ## subband_h, \
 }

/* Since 6GHz subbands are not edge aligned, some cases span two subbands.
 * In the following, we describe each of them with rtw89_6ghz_span.
 */

static const struct rtw89_6ghz_span rtw89_overlapping_6ghz[] = {
 RTW89_DECL_6GHZ_SPAN(6145, SUBBAND_5_L, SUBBAND_5_H),
 RTW89_DECL_6GHZ_SPAN(6165, SUBBAND_5_L, SUBBAND_5_H),
 RTW89_DECL_6GHZ_SPAN(6185, SUBBAND_5_L, SUBBAND_5_H),
 RTW89_DECL_6GHZ_SPAN(6505, SUBBAND_6, SUBBAND_7_L),
 RTW89_DECL_6GHZ_SPAN(6525, SUBBAND_6, SUBBAND_7_L),
 RTW89_DECL_6GHZ_SPAN(6545, SUBBAND_6, SUBBAND_7_L),
 RTW89_DECL_6GHZ_SPAN(6665, SUBBAND_7_L, SUBBAND_7_H),
 RTW89_DECL_6GHZ_SPAN(6705, SUBBAND_7_L, SUBBAND_7_H),
 RTW89_DECL_6GHZ_SPAN(6825, SUBBAND_7_H, SUBBAND_8),
 RTW89_DECL_6GHZ_SPAN(6865, SUBBAND_7_H, SUBBAND_8),
 RTW89_DECL_6GHZ_SPAN(6875, SUBBAND_7_H, SUBBAND_8),
 RTW89_DECL_6GHZ_SPAN(6885, SUBBAND_7_H, SUBBAND_8),
};

const struct rtw89_6ghz_span *
rtw89_get_6ghz_span(struct rtw89_dev *rtwdev, u32 center_freq)
{
 int idx;

 if (center_freq >= RTW89_6GHZ_SPAN_HEAD) {
  idx = RTW89_6GHZ_SPAN_IDX(center_freq);
  /* To decrease size of rtw89_overlapping_6ghz[],
 * RTW89_6GHZ_SPAN_IDX() truncates the leading NULLs
 * to make first span as index 0 of the table. So, if center
 * frequency is less than the first one, it will get netative.
 */

  if (idx >= 0 && idx < ARRAY_SIZE(rtw89_overlapping_6ghz))
   return &rtw89_overlapping_6ghz[idx];
 }

 return NULL;
}

bool rtw89_ra_report_to_bitrate(struct rtw89_dev *rtwdev, u8 rpt_rate, u16 *bitrate)
{
 struct ieee80211_rate rate;

 if (unlikely(rpt_rate >= ARRAY_SIZE(rtw89_bitrates))) {
  rtw89_debug(rtwdev, RTW89_DBG_UNEXP, "invalid rpt rate %d\n", rpt_rate);
  return false;
 }

 rate = rtw89_bitrates[rpt_rate];
 *bitrate = rate.bitrate;

 return true;
}

static const struct ieee80211_supported_band rtw89_sband_2ghz = {
 .band  = NL80211_BAND_2GHZ,
 .channels = rtw89_channels_2ghz,
 .n_channels = ARRAY_SIZE(rtw89_channels_2ghz),
 .bitrates = rtw89_bitrates,
 .n_bitrates = ARRAY_SIZE(rtw89_bitrates),
 .ht_cap  = {0},
 .vht_cap = {0},
};

static const struct ieee80211_supported_band rtw89_sband_5ghz = {
 .band  = NL80211_BAND_5GHZ,
 .channels = rtw89_channels_5ghz,
 .n_channels = ARRAY_SIZE(rtw89_channels_5ghz),

 /* 5G has no CCK rates, 1M/2M/5.5M/11M */
 .bitrates = rtw89_bitrates + 4,
 .n_bitrates = ARRAY_SIZE(rtw89_bitrates) - 4,
 .ht_cap  = {0},
 .vht_cap = {0},
};

static const struct ieee80211_supported_band rtw89_sband_6ghz = {
 .band  = NL80211_BAND_6GHZ,
 .channels = rtw89_channels_6ghz,
 .n_channels = ARRAY_SIZE(rtw89_channels_6ghz),

 /* 6G has no CCK rates, 1M/2M/5.5M/11M */
 .bitrates = rtw89_bitrates + 4,
 .n_bitrates = ARRAY_SIZE(rtw89_bitrates) - 4,
};

static void __rtw89_traffic_stats_accu(struct rtw89_traffic_stats *stats,
           struct sk_buff *skb, bool tx)
{
 if (tx) {
  stats->tx_cnt++;
  stats->tx_unicast += skb->len;
 } else {
  stats->rx_cnt++;
  stats->rx_unicast += skb->len;
 }
}

static void rtw89_traffic_stats_accu(struct rtw89_dev *rtwdev,
         struct rtw89_vif *rtwvif,
         struct sk_buff *skb,
         bool accu_dev, bool tx)
{
 struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;

 if (!ieee80211_is_data(hdr->frame_control))
  return;

 if (is_broadcast_ether_addr(hdr->addr1) ||
     is_multicast_ether_addr(hdr->addr1))
  return;

 if (accu_dev)
  __rtw89_traffic_stats_accu(&rtwdev->stats, skb, tx);

 if (rtwvif) {
  __rtw89_traffic_stats_accu(&rtwvif->stats, skb, tx);
  __rtw89_traffic_stats_accu(&rtwvif->stats_ps, skb, tx);
 }
}

void rtw89_get_default_chandef(struct cfg80211_chan_def *chandef)
{
 cfg80211_chandef_create(chandef, &rtw89_channels_2ghz[0],
    NL80211_CHAN_NO_HT);
}

void rtw89_get_channel_params(const struct cfg80211_chan_def *chandef,
         struct rtw89_chan *chan)
{
 struct ieee80211_channel *channel = chandef->chan;
 enum nl80211_chan_width width = chandef->width;
 u32 primary_freq, center_freq;
 u8 center_chan;
 u8 bandwidth = RTW89_CHANNEL_WIDTH_20;
 u32 offset;
 u8 band;

 center_chan = channel->hw_value;
 primary_freq = channel->center_freq;
 center_freq = chandef->center_freq1;

 switch (width) {
 case NL80211_CHAN_WIDTH_20_NOHT:
 case NL80211_CHAN_WIDTH_20:
  bandwidth = RTW89_CHANNEL_WIDTH_20;
  break;
 case NL80211_CHAN_WIDTH_40:
  bandwidth = RTW89_CHANNEL_WIDTH_40;
  if (primary_freq > center_freq) {
   center_chan -= 2;
  } else {
   center_chan += 2;
  }
  break;
 case NL80211_CHAN_WIDTH_80:
 case NL80211_CHAN_WIDTH_160:
  bandwidth = nl_to_rtw89_bandwidth(width);
  if (primary_freq > center_freq) {
   offset = (primary_freq - center_freq - 10) / 20;
   center_chan -= 2 + offset * 4;
  } else {
   offset = (center_freq - primary_freq - 10) / 20;
   center_chan += 2 + offset * 4;
  }
  break;
 default:
  center_chan = 0;
  break;
 }

 switch (channel->band) {
 default:
 case NL80211_BAND_2GHZ:
  band = RTW89_BAND_2G;
  break;
 case NL80211_BAND_5GHZ:
  band = RTW89_BAND_5G;
  break;
 case NL80211_BAND_6GHZ:
  band = RTW89_BAND_6G;
  break;
 }

 rtw89_chan_create(chan, center_chan, channel->hw_value, band, bandwidth);
}

static void __rtw89_core_set_chip_txpwr(struct rtw89_dev *rtwdev,
     const struct rtw89_chan *chan,
     enum rtw89_phy_idx phy_idx)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 bool entity_active;

 entity_active = rtw89_get_entity_state(rtwdev, phy_idx);
 if (!entity_active)
  return;

 chip->ops->set_txpwr(rtwdev, chan, phy_idx);
}

void rtw89_core_set_chip_txpwr(struct rtw89_dev *rtwdev)
{
 const struct rtw89_chan *chan;

 chan = rtw89_mgnt_chan_get(rtwdev, 0);
 __rtw89_core_set_chip_txpwr(rtwdev, chan, RTW89_PHY_0);

 if (!rtwdev->support_mlo)
  return;

 chan = rtw89_mgnt_chan_get(rtwdev, 1);
 __rtw89_core_set_chip_txpwr(rtwdev, chan, RTW89_PHY_1);
}

static void __rtw89_set_channel(struct rtw89_dev *rtwdev,
    const struct rtw89_chan *chan,
    enum rtw89_mac_idx mac_idx,
    enum rtw89_phy_idx phy_idx)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 const struct rtw89_chan_rcd *chan_rcd;
 struct rtw89_channel_help_params bak;
 bool entity_active;

 entity_active = rtw89_get_entity_state(rtwdev, phy_idx);

 chan_rcd = rtw89_chan_rcd_get_by_chan(chan);

 rtw89_chip_set_channel_prepare(rtwdev, &bak, chan, mac_idx, phy_idx);

 chip->ops->set_channel(rtwdev, chan, mac_idx, phy_idx);

 chip->ops->set_txpwr(rtwdev, chan, phy_idx);

 rtw89_chip_set_channel_done(rtwdev, &bak, chan, mac_idx, phy_idx);

 if (!entity_active || chan_rcd->band_changed) {
  rtw89_btc_ntfy_switch_band(rtwdev, phy_idx, chan->band_type);
  rtw89_chip_rfk_band_changed(rtwdev, phy_idx, chan);
 }

 rtw89_set_entity_state(rtwdev, phy_idx, true);
}

int rtw89_set_channel(struct rtw89_dev *rtwdev)
{
 const struct rtw89_chan *chan;
 enum rtw89_entity_mode mode;

 mode = rtw89_entity_recalc(rtwdev);
 if (mode < 0 || mode >= NUM_OF_RTW89_ENTITY_MODE) {
  WARN(1, "Invalid ent mode: %d\n", mode);
  return -EINVAL;
 }

 chan = rtw89_mgnt_chan_get(rtwdev, 0);
 __rtw89_set_channel(rtwdev, chan, RTW89_MAC_0, RTW89_PHY_0);

 if (!rtwdev->support_mlo)
  return 0;

 chan = rtw89_mgnt_chan_get(rtwdev, 1);
 __rtw89_set_channel(rtwdev, chan, RTW89_MAC_1, RTW89_PHY_1);

 return 0;
}

static enum rtw89_core_tx_type
rtw89_core_get_tx_type(struct rtw89_dev *rtwdev,
         struct sk_buff *skb)
{
 struct ieee80211_hdr *hdr = (void *)skb->data;
 __le16 fc = hdr->frame_control;

 if (ieee80211_is_mgmt(fc) || ieee80211_is_nullfunc(fc))
  return RTW89_CORE_TX_TYPE_MGMT;

 return RTW89_CORE_TX_TYPE_DATA;
}

static void
rtw89_core_tx_update_ampdu_info(struct rtw89_dev *rtwdev,
    struct rtw89_core_tx_request *tx_req,
    enum btc_pkt_type pkt_type)
{
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct ieee80211_link_sta *link_sta;
 struct sk_buff *skb = tx_req->skb;
 struct rtw89_sta *rtwsta;
 u8 ampdu_num;
 u8 tid;

 if (pkt_type == PACKET_EAPOL) {
  desc_info->bk = true;
  return;
 }

 if (!(IEEE80211_SKB_CB(skb)->flags & IEEE80211_TX_CTL_AMPDU))
  return;

 if (!rtwsta_link) {
  rtw89_warn(rtwdev, "cannot set ampdu info without sta\n");
  return;
 }

 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
 rtwsta = rtwsta_link->rtwsta;

 rcu_read_lock();

 link_sta = rtw89_sta_rcu_dereference_link(rtwsta_link, false);
 ampdu_num = (u8)((rtwsta->ampdu_params[tid].agg_num ?
     rtwsta->ampdu_params[tid].agg_num :
     4 << link_sta->ht_cap.ampdu_factor) - 1);

 desc_info->agg_en = true;
 desc_info->ampdu_density = link_sta->ht_cap.ampdu_density;
 desc_info->ampdu_num = ampdu_num;

 rcu_read_unlock();
}

static void
rtw89_core_tx_update_sec_key(struct rtw89_dev *rtwdev,
        struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_cam_info *cam_info = &rtwdev->cam_info;
 const struct rtw89_chip_info *chip = rtwdev->chip;
 const struct rtw89_sec_cam_entry *sec_cam;
 struct ieee80211_tx_info *info;
 struct ieee80211_key_conf *key;
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct sk_buff *skb = tx_req->skb;
 u8 sec_type = RTW89_SEC_KEY_TYPE_NONE;
 u8 sec_cam_idx;
 u64 pn64;

 info = IEEE80211_SKB_CB(skb);
 key = info->control.hw_key;
 sec_cam_idx = key->hw_key_idx;
 sec_cam = cam_info->sec_entries[sec_cam_idx];
 if (!sec_cam) {
  rtw89_warn(rtwdev, "sec cam entry is empty\n");
  return;
 }

 switch (key->cipher) {
 case WLAN_CIPHER_SUITE_WEP40:
  sec_type = RTW89_SEC_KEY_TYPE_WEP40;
  break;
 case WLAN_CIPHER_SUITE_WEP104:
  sec_type = RTW89_SEC_KEY_TYPE_WEP104;
  break;
 case WLAN_CIPHER_SUITE_TKIP:
  sec_type = RTW89_SEC_KEY_TYPE_TKIP;
  break;
 case WLAN_CIPHER_SUITE_CCMP:
  sec_type = RTW89_SEC_KEY_TYPE_CCMP128;
  break;
 case WLAN_CIPHER_SUITE_CCMP_256:
  sec_type = RTW89_SEC_KEY_TYPE_CCMP256;
  break;
 case WLAN_CIPHER_SUITE_GCMP:
  sec_type = RTW89_SEC_KEY_TYPE_GCMP128;
  break;
 case WLAN_CIPHER_SUITE_GCMP_256:
  sec_type = RTW89_SEC_KEY_TYPE_GCMP256;
  break;
 default:
  rtw89_warn(rtwdev, "key cipher not supported %d\n", key->cipher);
  return;
 }

 desc_info->sec_en = true;
 desc_info->sec_keyid = key->keyidx;
 desc_info->sec_type = sec_type;
 desc_info->sec_cam_idx = sec_cam->sec_cam_idx;

 if (!chip->hw_sec_hdr)
  return;

 pn64 = atomic64_inc_return(&key->tx_pn);
 desc_info->sec_seq[0] = pn64;
 desc_info->sec_seq[1] = pn64 >> 8;
 desc_info->sec_seq[2] = pn64 >> 16;
 desc_info->sec_seq[3] = pn64 >> 24;
 desc_info->sec_seq[4] = pn64 >> 32;
 desc_info->sec_seq[5] = pn64 >> 40;
 desc_info->wp_offset = 1; /* in unit of 8 bytes for security header */
}

static u16 rtw89_core_get_mgmt_rate(struct rtw89_dev *rtwdev,
        struct rtw89_core_tx_request *tx_req,
        const struct rtw89_chan *chan)
{
 struct sk_buff *skb = tx_req->skb;
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct ieee80211_tx_info *tx_info = IEEE80211_SKB_CB(skb);
 struct ieee80211_vif *vif = tx_info->control.vif;
 struct ieee80211_bss_conf *bss_conf;
 u16 lowest_rate;
 u16 rate;

 if (tx_info->flags & IEEE80211_TX_CTL_NO_CCK_RATE ||
     (vif && vif->p2p))
  lowest_rate = RTW89_HW_RATE_OFDM6;
 else if (chan->band_type == RTW89_BAND_2G)
  lowest_rate = RTW89_HW_RATE_CCK1;
 else
  lowest_rate = RTW89_HW_RATE_OFDM6;

 if (!rtwvif_link)
  return lowest_rate;

 rcu_read_lock();

 bss_conf = rtw89_vif_rcu_dereference_link(rtwvif_link, false);
 if (!bss_conf->basic_rates || !rtwsta_link) {
  rate = lowest_rate;
  goto out;
 }

 rate = __ffs(bss_conf->basic_rates) + lowest_rate;

out:
 rcu_read_unlock();

 return rate;
}

static u8 rtw89_core_tx_get_mac_id(struct rtw89_dev *rtwdev,
       struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;

 if (desc_info->mlo && !desc_info->sw_mld) {
  if (rtwsta_link)
   return rtw89_sta_get_main_macid(rtwsta_link->rtwsta);
  else
   return rtw89_vif_get_main_macid(rtwvif_link->rtwvif);
 }

 if (!rtwsta_link)
  return rtwvif_link->mac_id;

 return rtwsta_link->mac_id;
}

static void rtw89_core_tx_update_llc_hdr(struct rtw89_dev *rtwdev,
      struct rtw89_tx_desc_info *desc_info,
      struct sk_buff *skb)
{
 struct ieee80211_hdr *hdr = (void *)skb->data;
 __le16 fc = hdr->frame_control;

 desc_info->hdr_llc_len = ieee80211_hdrlen(fc);
 desc_info->hdr_llc_len >>= 1; /* in unit of 2 bytes */
}

static void
rtw89_core_tx_update_mgmt_info(struct rtw89_dev *rtwdev,
          struct rtw89_core_tx_request *tx_req)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 const struct rtw89_chan *chan = rtw89_chan_get(rtwdev,
             rtwvif_link->chanctx_idx);
 struct sk_buff *skb = tx_req->skb;
 u8 qsel, ch_dma;

 qsel = rtw89_core_get_qsel_mgmt(rtwdev, tx_req);
 ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel);

 desc_info->qsel = qsel;
 desc_info->ch_dma = ch_dma;
 desc_info->port = desc_info->hiq ? rtwvif_link->port : 0;
 desc_info->mac_id = rtw89_core_tx_get_mac_id(rtwdev, tx_req);
 desc_info->hw_ssn_sel = RTW89_MGMT_HW_SSN_SEL;
 desc_info->hw_seq_mode = RTW89_MGMT_HW_SEQ_MODE;

 /* fixed data rate for mgmt frames */
 desc_info->en_wd_info = true;
 desc_info->use_rate = true;
 desc_info->dis_data_fb = true;
 desc_info->data_rate = rtw89_core_get_mgmt_rate(rtwdev, tx_req, chan);

 if (chip->hw_mgmt_tx_encrypt && IEEE80211_SKB_CB(skb)->control.hw_key) {
  rtw89_core_tx_update_sec_key(rtwdev, tx_req);
  rtw89_core_tx_update_llc_hdr(rtwdev, desc_info, skb);
 }

 rtw89_debug(rtwdev, RTW89_DBG_TXRX,
      "tx mgmt frame with rate 0x%x on channel %d (band %d, bw %d)\n",
      desc_info->data_rate, chan->channel, chan->band_type,
      chan->band_width);
}

static void
rtw89_core_tx_update_h2c_info(struct rtw89_dev *rtwdev,
         struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;

 desc_info->is_bmc = false;
 desc_info->wd_page = false;
 desc_info->ch_dma = RTW89_DMA_H2C;
}

static void rtw89_core_get_no_ul_ofdma_htc(struct rtw89_dev *rtwdev, __le32 *htc,
        const struct rtw89_chan *chan)
{
 static const u8 rtw89_bandwidth_to_om[] = {
  [RTW89_CHANNEL_WIDTH_20] = HTC_OM_CHANNEL_WIDTH_20,
  [RTW89_CHANNEL_WIDTH_40] = HTC_OM_CHANNEL_WIDTH_40,
  [RTW89_CHANNEL_WIDTH_80] = HTC_OM_CHANNEL_WIDTH_80,
  [RTW89_CHANNEL_WIDTH_160] = HTC_OM_CHANNEL_WIDTH_160_OR_80_80,
  [RTW89_CHANNEL_WIDTH_80_80] = HTC_OM_CHANNEL_WIDTH_160_OR_80_80,
 };
 const struct rtw89_chip_info *chip = rtwdev->chip;
 struct rtw89_hal *hal = &rtwdev->hal;
 u8 om_bandwidth;

 if (!chip->dis_2g_40m_ul_ofdma ||
     chan->band_type != RTW89_BAND_2G ||
     chan->band_width != RTW89_CHANNEL_WIDTH_40)
  return;

 om_bandwidth = chan->band_width < ARRAY_SIZE(rtw89_bandwidth_to_om) ?
         rtw89_bandwidth_to_om[chan->band_width] : 0;
 *htc = le32_encode_bits(RTW89_HTC_VARIANT_HE, RTW89_HTC_MASK_VARIANT) |
        le32_encode_bits(RTW89_HTC_VARIANT_HE_CID_OM, RTW89_HTC_MASK_CTL_ID) |
        le32_encode_bits(hal->rx_nss - 1, RTW89_HTC_MASK_HTC_OM_RX_NSS) |
        le32_encode_bits(om_bandwidth, RTW89_HTC_MASK_HTC_OM_CH_WIDTH) |
        le32_encode_bits(1, RTW89_HTC_MASK_HTC_OM_UL_MU_DIS) |
        le32_encode_bits(hal->tx_nss - 1, RTW89_HTC_MASK_HTC_OM_TX_NSTS) |
        le32_encode_bits(0, RTW89_HTC_MASK_HTC_OM_ER_SU_DIS) |
        le32_encode_bits(0, RTW89_HTC_MASK_HTC_OM_DL_MU_MIMO_RR) |
        le32_encode_bits(0, RTW89_HTC_MASK_HTC_OM_UL_MU_DATA_DIS);
}

static bool
__rtw89_core_tx_check_he_qos_htc(struct rtw89_dev *rtwdev,
     struct rtw89_core_tx_request *tx_req,
     enum btc_pkt_type pkt_type)
{
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct sk_buff *skb = tx_req->skb;
 struct ieee80211_hdr *hdr = (void *)skb->data;
 struct ieee80211_link_sta *link_sta;
 __le16 fc = hdr->frame_control;

 /* AP IOT issue with EAPoL, ARP and DHCP */
 if (pkt_type < PACKET_MAX)
  return false;

 if (!rtwsta_link)
  return false;

 rcu_read_lock();

 link_sta = rtw89_sta_rcu_dereference_link(rtwsta_link, false);
 if (!link_sta->he_cap.has_he) {
  rcu_read_unlock();
  return false;
 }

 rcu_read_unlock();

 if (!ieee80211_is_data_qos(fc))
  return false;

 if (skb_headroom(skb) < IEEE80211_HT_CTL_LEN)
  return false;

 if (rtwsta_link && rtwsta_link->ra_report.might_fallback_legacy)
  return false;

 return true;
}

static void
__rtw89_core_tx_adjust_he_qos_htc(struct rtw89_dev *rtwdev,
      struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct sk_buff *skb = tx_req->skb;
 struct ieee80211_hdr *hdr = (void *)skb->data;
 __le16 fc = hdr->frame_control;
 void *data;
 __le32 *htc;
 u8 *qc;
 int hdr_len;

 hdr_len = ieee80211_has_a4(fc) ? 32 : 26;
 data = skb_push(skb, IEEE80211_HT_CTL_LEN);
 memmove(data, data + IEEE80211_HT_CTL_LEN, hdr_len);

 hdr = data;
 htc = data + hdr_len;
 hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_ORDER);
 *htc = rtwsta_link->htc_template ? rtwsta_link->htc_template :
        le32_encode_bits(RTW89_HTC_VARIANT_HE, RTW89_HTC_MASK_VARIANT) |
        le32_encode_bits(RTW89_HTC_VARIANT_HE_CID_CAS, RTW89_HTC_MASK_CTL_ID);

 qc = data + hdr_len - IEEE80211_QOS_CTL_LEN;
 qc[0] |= IEEE80211_QOS_CTL_EOSP;
}

static void
rtw89_core_tx_update_he_qos_htc(struct rtw89_dev *rtwdev,
    struct rtw89_core_tx_request *tx_req,
    enum btc_pkt_type pkt_type)
{
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;

 if (!__rtw89_core_tx_check_he_qos_htc(rtwdev, tx_req, pkt_type))
  goto desc_bk;

 __rtw89_core_tx_adjust_he_qos_htc(rtwdev, tx_req);

 desc_info->pkt_size += IEEE80211_HT_CTL_LEN;
 desc_info->a_ctrl_bsr = true;

desc_bk:
 if (!rtwvif_link || rtwvif_link->last_a_ctrl == desc_info->a_ctrl_bsr)
  return;

 rtwvif_link->last_a_ctrl = desc_info->a_ctrl_bsr;
 desc_info->bk = true;
}

static u16 rtw89_core_get_data_rate(struct rtw89_dev *rtwdev,
        struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link);
 struct rtw89_phy_rate_pattern *rate_pattern = &rtwvif_link->rate_pattern;
 enum rtw89_chanctx_idx idx = rtwvif_link->chanctx_idx;
 const struct rtw89_chan *chan = rtw89_chan_get(rtwdev, idx);
 struct ieee80211_link_sta *link_sta;
 u16 lowest_rate;
 u16 rate;

 if (rate_pattern->enable)
  return rate_pattern->rate;

 if (vif->p2p)
  lowest_rate = RTW89_HW_RATE_OFDM6;
 else if (chan->band_type == RTW89_BAND_2G)
  lowest_rate = RTW89_HW_RATE_CCK1;
 else
  lowest_rate = RTW89_HW_RATE_OFDM6;

 if (!rtwsta_link)
  return lowest_rate;

 rcu_read_lock();

 link_sta = rtw89_sta_rcu_dereference_link(rtwsta_link, false);
 if (!link_sta->supp_rates[chan->band_type]) {
  rate = lowest_rate;
  goto out;
 }

 rate = __ffs(link_sta->supp_rates[chan->band_type]) + lowest_rate;

out:
 rcu_read_unlock();

 return rate;
}

static void
rtw89_core_tx_update_data_info(struct rtw89_dev *rtwdev,
          struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_vif_link *rtwvif_link = tx_req->rtwvif_link;
 struct rtw89_sta_link *rtwsta_link = tx_req->rtwsta_link;
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct sk_buff *skb = tx_req->skb;
 u8 tid, tid_indicate;
 u8 qsel, ch_dma;

 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK;
 tid_indicate = rtw89_core_get_tid_indicate(rtwdev, tid);
 qsel = desc_info->hiq ? RTW89_TX_QSEL_B0_HI : rtw89_core_get_qsel(rtwdev, tid);
 ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel);

 desc_info->ch_dma = ch_dma;
 desc_info->tid_indicate = tid_indicate;
 desc_info->qsel = qsel;
 desc_info->mac_id = rtw89_core_tx_get_mac_id(rtwdev, tx_req);
 desc_info->port = desc_info->hiq ? rtwvif_link->port : 0;
 desc_info->er_cap = rtwsta_link ? rtwsta_link->er_cap : false;
 desc_info->stbc = rtwsta_link ? rtwsta_link->ra.stbc_cap : false;
 desc_info->ldpc = rtwsta_link ? rtwsta_link->ra.ldpc_cap : false;

 /* enable wd_info for AMPDU */
 desc_info->en_wd_info = true;

 if (IEEE80211_SKB_CB(skb)->control.hw_key)
  rtw89_core_tx_update_sec_key(rtwdev, tx_req);

 desc_info->data_retry_lowest_rate = rtw89_core_get_data_rate(rtwdev, tx_req);
}

static enum btc_pkt_type
rtw89_core_tx_btc_spec_pkt_notify(struct rtw89_dev *rtwdev,
      struct rtw89_core_tx_request *tx_req)
{
 struct wiphy *wiphy = rtwdev->hw->wiphy;
 struct sk_buff *skb = tx_req->skb;
 struct udphdr *udphdr;

 if (IEEE80211_SKB_CB(skb)->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO) {
  wiphy_work_queue(wiphy, &rtwdev->btc.eapol_notify_work);
  return PACKET_EAPOL;
 }

 if (skb->protocol == htons(ETH_P_ARP)) {
  wiphy_work_queue(wiphy, &rtwdev->btc.arp_notify_work);
  return PACKET_ARP;
 }

 if (skb->protocol == htons(ETH_P_IP) &&
     ip_hdr(skb)->protocol == IPPROTO_UDP) {
  udphdr = udp_hdr(skb);
  if (((udphdr->source == htons(67) && udphdr->dest == htons(68)) ||
       (udphdr->source == htons(68) && udphdr->dest == htons(67))) &&
      skb->len > 282) {
   wiphy_work_queue(wiphy, &rtwdev->btc.dhcp_notify_work);
   return PACKET_DHCP;
  }
 }

 if (skb->protocol == htons(ETH_P_IP) &&
     ip_hdr(skb)->protocol == IPPROTO_ICMP) {
  wiphy_work_queue(wiphy, &rtwdev->btc.icmp_notify_work);
  return PACKET_ICMP;
 }

 return PACKET_MAX;
}

static void
rtw89_core_tx_wake(struct rtw89_dev *rtwdev,
     struct rtw89_core_tx_request *tx_req)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;

 if (!RTW89_CHK_FW_FEATURE(TX_WAKE, &rtwdev->fw))
  return;

 switch (chip->chip_id) {
 case RTL8852BT:
  if (test_bit(RTW89_FLAG_LEISURE_PS, rtwdev->flags))
   goto notify;
  break;
 case RTL8852C:
  if (test_bit(RTW89_FLAG_LOW_POWER_MODE, rtwdev->flags))
   goto notify;
  break;
 default:
  if (test_bit(RTW89_FLAG_LOW_POWER_MODE, rtwdev->flags) &&
      tx_req->tx_type == RTW89_CORE_TX_TYPE_MGMT)
   goto notify;
  break;
 }

 return;

notify:
 rtw89_mac_notify_wake(rtwdev);
}

static void
rtw89_core_tx_update_desc_info(struct rtw89_dev *rtwdev,
          struct rtw89_core_tx_request *tx_req)
{
 struct rtw89_tx_desc_info *desc_info = &tx_req->desc_info;
 struct sk_buff *skb = tx_req->skb;
 struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
 struct ieee80211_hdr *hdr = (void *)skb->data;
 struct rtw89_addr_cam_entry *addr_cam;
 enum rtw89_core_tx_type tx_type;
 enum btc_pkt_type pkt_type;
 bool upd_wlan_hdr = false;
 bool is_bmc;
 u16 seq;

 if (tx_req->sta)
  desc_info->mlo = tx_req->sta->mlo;
 else if (tx_req->vif)
  desc_info->mlo = ieee80211_vif_is_mld(tx_req->vif);

 seq = (le16_to_cpu(hdr->seq_ctrl) & IEEE80211_SCTL_SEQ) >> 4;
 if (tx_req->tx_type != RTW89_CORE_TX_TYPE_FWCMD) {
  tx_type = rtw89_core_get_tx_type(rtwdev, skb);
  tx_req->tx_type = tx_type;

  addr_cam = rtw89_get_addr_cam_of(tx_req->rtwvif_link,
       tx_req->rtwsta_link);
  if (addr_cam->valid && desc_info->mlo)
   upd_wlan_hdr = true;
 }
 is_bmc = (is_broadcast_ether_addr(hdr->addr1) ||
    is_multicast_ether_addr(hdr->addr1));

 desc_info->seq = seq;
 desc_info->pkt_size = skb->len;
 desc_info->is_bmc = is_bmc;
 desc_info->wd_page = true;
 desc_info->hiq = info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM;
 desc_info->upd_wlan_hdr = upd_wlan_hdr;

 switch (tx_req->tx_type) {
 case RTW89_CORE_TX_TYPE_MGMT:
  rtw89_core_tx_update_mgmt_info(rtwdev, tx_req);
  break;
 case RTW89_CORE_TX_TYPE_DATA:
  rtw89_core_tx_update_data_info(rtwdev, tx_req);
  pkt_type = rtw89_core_tx_btc_spec_pkt_notify(rtwdev, tx_req);
  rtw89_core_tx_update_he_qos_htc(rtwdev, tx_req, pkt_type);
  rtw89_core_tx_update_ampdu_info(rtwdev, tx_req, pkt_type);
  rtw89_core_tx_update_llc_hdr(rtwdev, desc_info, skb);
  break;
 case RTW89_CORE_TX_TYPE_FWCMD:
  rtw89_core_tx_update_h2c_info(rtwdev, tx_req);
  break;
 }
}

static void rtw89_tx_wait_work(struct wiphy *wiphy, struct wiphy_work *work)
{
 struct rtw89_dev *rtwdev = container_of(work, struct rtw89_dev,
      tx_wait_work.work);

 rtw89_tx_wait_list_clear(rtwdev);
}

void rtw89_core_tx_kick_off(struct rtw89_dev *rtwdev, u8 qsel)
{
 u8 ch_dma;

 ch_dma = rtw89_core_get_ch_dma(rtwdev, qsel);

 rtw89_hci_tx_kick_off(rtwdev, ch_dma);
}

int rtw89_core_tx_kick_off_and_wait(struct rtw89_dev *rtwdev, struct sk_buff *skb,
        struct rtw89_tx_wait_info *wait, int qsel,
        unsigned int timeout)
{
 unsigned long time_left;
 int ret = 0;

 lockdep_assert_wiphy(rtwdev->hw->wiphy);

 rtw89_core_tx_kick_off(rtwdev, qsel);
 time_left = wait_for_completion_timeout(&wait->completion,
      msecs_to_jiffies(timeout));

 if (time_left == 0) {
  ret = -ETIMEDOUT;
  list_add_tail(&wait->list, &rtwdev->tx_waits);
  wiphy_delayed_work_queue(rtwdev->hw->wiphy, &rtwdev->tx_wait_work,
      RTW89_TX_WAIT_WORK_TIMEOUT);
 } else {
  if (!wait->tx_done)
   ret = -EAGAIN;
  rtw89_tx_wait_release(wait);
 }

 return ret;
}

int rtw89_h2c_tx(struct rtw89_dev *rtwdev,
   struct sk_buff *skb, bool fwdl)
{
 struct rtw89_core_tx_request tx_req = {0};
 u32 cnt;
 int ret;

 if (!test_bit(RTW89_FLAG_POWERON, rtwdev->flags)) {
  rtw89_debug(rtwdev, RTW89_DBG_FW,
       "ignore h2c due to power is off with firmware state=%d\n",
       test_bit(RTW89_FLAG_FW_RDY, rtwdev->flags));
  dev_kfree_skb(skb);
  return 0;
 }

 tx_req.skb = skb;
 tx_req.tx_type = RTW89_CORE_TX_TYPE_FWCMD;
 if (fwdl)
  tx_req.desc_info.fw_dl = true;

 rtw89_core_tx_update_desc_info(rtwdev, &tx_req);

 if (!fwdl)
  rtw89_hex_dump(rtwdev, RTW89_DBG_FW, "H2C: ", skb->data, skb->len);

 cnt = rtw89_hci_check_and_reclaim_tx_resource(rtwdev, RTW89_TXCH_CH12);
 if (cnt == 0) {
  rtw89_err(rtwdev, "no tx fwcmd resource\n");
  return -ENOSPC;
 }

 ret = rtw89_hci_tx_write(rtwdev, &tx_req);
 if (ret) {
  rtw89_err(rtwdev, "failed to transmit skb to HCI\n");
  return ret;
 }
 rtw89_hci_tx_kick_off(rtwdev, RTW89_TXCH_CH12);

 return 0;
}

static int rtw89_core_tx_write_link(struct rtw89_dev *rtwdev,
        struct rtw89_vif_link *rtwvif_link,
        struct rtw89_sta_link *rtwsta_link,
        struct sk_buff *skb, int *qsel, bool sw_mld,
        struct rtw89_tx_wait_info *wait)
{
 struct ieee80211_sta *sta = rtwsta_link_to_sta_safe(rtwsta_link);
 struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link);
 struct rtw89_tx_skb_data *skb_data = RTW89_TX_SKB_CB(skb);
 struct rtw89_vif *rtwvif = rtwvif_link->rtwvif;
 struct rtw89_core_tx_request tx_req = {};
 int ret;

 tx_req.skb = skb;
 tx_req.vif = vif;
 tx_req.sta = sta;
 tx_req.rtwvif_link = rtwvif_link;
 tx_req.rtwsta_link = rtwsta_link;
 tx_req.desc_info.sw_mld = sw_mld;

 rtw89_traffic_stats_accu(rtwdev, rtwvif, skb, truetrue);
 rtw89_wow_parse_akm(rtwdev, skb);
 rtw89_core_tx_update_desc_info(rtwdev, &tx_req);
 rtw89_core_tx_wake(rtwdev, &tx_req);

 rcu_assign_pointer(skb_data->wait, wait);

 ret = rtw89_hci_tx_write(rtwdev, &tx_req);
 if (ret) {
  rtw89_err(rtwdev, "failed to transmit skb to HCI\n");
  return ret;
 }

 if (qsel)
  *qsel = tx_req.desc_info.qsel;

 return 0;
}

int rtw89_core_tx_write(struct rtw89_dev *rtwdev, struct ieee80211_vif *vif,
   struct ieee80211_sta *sta, struct sk_buff *skb, int *qsel)
{
 struct rtw89_sta *rtwsta = sta_to_rtwsta_safe(sta);
 struct rtw89_vif *rtwvif = vif_to_rtwvif(vif);
 struct rtw89_sta_link *rtwsta_link = NULL;
 struct rtw89_vif_link *rtwvif_link;

 if (rtwsta) {
  rtwsta_link = rtw89_get_designated_link(rtwsta);
  if (unlikely(!rtwsta_link)) {
   rtw89_err(rtwdev, "tx: find no sta designated link\n");
   return -ENOLINK;
  }

  rtwvif_link = rtwsta_link->rtwvif_link;
 } else {
  rtwvif_link = rtw89_get_designated_link(rtwvif);
  if (unlikely(!rtwvif_link)) {
   rtw89_err(rtwdev, "tx: find no vif designated link\n");
   return -ENOLINK;
  }
 }

 return rtw89_core_tx_write_link(rtwdev, rtwvif_link, rtwsta_link, skb, qsel, false,
     NULL);
}

static __le32 rtw89_build_txwd_body0(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY0_WP_OFFSET, desc_info->wp_offset) |
      FIELD_PREP(RTW89_TXWD_BODY0_WD_INFO_EN, desc_info->en_wd_info) |
      FIELD_PREP(RTW89_TXWD_BODY0_CHANNEL_DMA, desc_info->ch_dma) |
      FIELD_PREP(RTW89_TXWD_BODY0_HDR_LLC_LEN, desc_info->hdr_llc_len) |
      FIELD_PREP(RTW89_TXWD_BODY0_WD_PAGE, desc_info->wd_page) |
      FIELD_PREP(RTW89_TXWD_BODY0_FW_DL, desc_info->fw_dl) |
      FIELD_PREP(RTW89_TXWD_BODY0_HW_SSN_SEL, desc_info->hw_ssn_sel) |
      FIELD_PREP(RTW89_TXWD_BODY0_HW_SSN_MODE, desc_info->hw_seq_mode);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body0_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY0_WP_OFFSET_V1, desc_info->wp_offset) |
      FIELD_PREP(RTW89_TXWD_BODY0_WD_INFO_EN, desc_info->en_wd_info) |
      FIELD_PREP(RTW89_TXWD_BODY0_CHANNEL_DMA, desc_info->ch_dma) |
      FIELD_PREP(RTW89_TXWD_BODY0_HDR_LLC_LEN, desc_info->hdr_llc_len) |
      FIELD_PREP(RTW89_TXWD_BODY0_WD_PAGE, desc_info->wd_page) |
      FIELD_PREP(RTW89_TXWD_BODY0_FW_DL, desc_info->fw_dl);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body1_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY1_ADDR_INFO_NUM, desc_info->addr_info_nr) |
      FIELD_PREP(RTW89_TXWD_BODY1_SEC_KEYID, desc_info->sec_keyid) |
      FIELD_PREP(RTW89_TXWD_BODY1_SEC_TYPE, desc_info->sec_type);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY2_TID_INDICATE, desc_info->tid_indicate) |
      FIELD_PREP(RTW89_TXWD_BODY2_QSEL, desc_info->qsel) |
      FIELD_PREP(RTW89_TXWD_BODY2_TXPKT_SIZE, desc_info->pkt_size) |
      FIELD_PREP(RTW89_TXWD_BODY2_MACID, desc_info->mac_id);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body3(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY3_SW_SEQ, desc_info->seq) |
      FIELD_PREP(RTW89_TXWD_BODY3_AGG_EN, desc_info->agg_en) |
      FIELD_PREP(RTW89_TXWD_BODY3_BK, desc_info->bk);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body4(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY4_SEC_IV_L0, desc_info->sec_seq[0]) |
      FIELD_PREP(RTW89_TXWD_BODY4_SEC_IV_L1, desc_info->sec_seq[1]);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body5(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY5_SEC_IV_H2, desc_info->sec_seq[2]) |
      FIELD_PREP(RTW89_TXWD_BODY5_SEC_IV_H3, desc_info->sec_seq[3]) |
      FIELD_PREP(RTW89_TXWD_BODY5_SEC_IV_H4, desc_info->sec_seq[4]) |
      FIELD_PREP(RTW89_TXWD_BODY5_SEC_IV_H5, desc_info->sec_seq[5]);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body7_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_BODY7_USE_RATE_V1, desc_info->use_rate) |
      FIELD_PREP(RTW89_TXWD_BODY7_DATA_RATE, desc_info->data_rate);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info0(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO0_USE_RATE, desc_info->use_rate) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_RATE, desc_info->data_rate) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_STBC, desc_info->stbc) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_LDPC, desc_info->ldpc) |
      FIELD_PREP(RTW89_TXWD_INFO0_DISDATAFB, desc_info->dis_data_fb) |
      FIELD_PREP(RTW89_TXWD_INFO0_MULTIPORT_ID, desc_info->port);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info0_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO0_DATA_STBC, desc_info->stbc) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_LDPC, desc_info->ldpc) |
      FIELD_PREP(RTW89_TXWD_INFO0_DISDATAFB, desc_info->dis_data_fb) |
      FIELD_PREP(RTW89_TXWD_INFO0_MULTIPORT_ID, desc_info->port) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_ER, desc_info->er_cap) |
      FIELD_PREP(RTW89_TXWD_INFO0_DATA_BW_ER, 0);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO1_MAX_AGGNUM, desc_info->ampdu_num) |
      FIELD_PREP(RTW89_TXWD_INFO1_A_CTRL_BSR, desc_info->a_ctrl_bsr) |
      FIELD_PREP(RTW89_TXWD_INFO1_DATA_RTY_LOWEST_RATE,
          desc_info->data_retry_lowest_rate);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO2_AMPDU_DENSITY, desc_info->ampdu_density) |
      FIELD_PREP(RTW89_TXWD_INFO2_SEC_TYPE, desc_info->sec_type) |
      FIELD_PREP(RTW89_TXWD_INFO2_SEC_HW_ENC, desc_info->sec_en) |
      FIELD_PREP(RTW89_TXWD_INFO2_SEC_CAM_IDX, desc_info->sec_cam_idx);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info2_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO2_AMPDU_DENSITY, desc_info->ampdu_density) |
      FIELD_PREP(RTW89_TXWD_INFO2_FORCE_KEY_EN, desc_info->sec_en) |
      FIELD_PREP(RTW89_TXWD_INFO2_SEC_CAM_IDX, desc_info->sec_cam_idx);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info4(struct rtw89_tx_desc_info *desc_info)
{
 bool rts_en = !desc_info->is_bmc;
 u32 dword = FIELD_PREP(RTW89_TXWD_INFO4_RTS_EN, rts_en) |
      FIELD_PREP(RTW89_TXWD_INFO4_HW_RTS_EN, 1);

 return cpu_to_le32(dword);
}

void rtw89_core_fill_txdesc(struct rtw89_dev *rtwdev,
       struct rtw89_tx_desc_info *desc_info,
       void *txdesc)
{
 struct rtw89_txwd_body *txwd_body = (struct rtw89_txwd_body *)txdesc;
 struct rtw89_txwd_info *txwd_info;

 txwd_body->dword0 = rtw89_build_txwd_body0(desc_info);
 txwd_body->dword2 = rtw89_build_txwd_body2(desc_info);
 txwd_body->dword3 = rtw89_build_txwd_body3(desc_info);

 if (!desc_info->en_wd_info)
  return;

 txwd_info = (struct rtw89_txwd_info *)(txwd_body + 1);
 txwd_info->dword0 = rtw89_build_txwd_info0(desc_info);
 txwd_info->dword1 = rtw89_build_txwd_info1(desc_info);
 txwd_info->dword2 = rtw89_build_txwd_info2(desc_info);
 txwd_info->dword4 = rtw89_build_txwd_info4(desc_info);

}
EXPORT_SYMBOL(rtw89_core_fill_txdesc);

void rtw89_core_fill_txdesc_v1(struct rtw89_dev *rtwdev,
          struct rtw89_tx_desc_info *desc_info,
          void *txdesc)
{
 struct rtw89_txwd_body_v1 *txwd_body = (struct rtw89_txwd_body_v1 *)txdesc;
 struct rtw89_txwd_info *txwd_info;

 txwd_body->dword0 = rtw89_build_txwd_body0_v1(desc_info);
 txwd_body->dword1 = rtw89_build_txwd_body1_v1(desc_info);
 txwd_body->dword2 = rtw89_build_txwd_body2(desc_info);
 txwd_body->dword3 = rtw89_build_txwd_body3(desc_info);
 if (desc_info->sec_en) {
  txwd_body->dword4 = rtw89_build_txwd_body4(desc_info);
  txwd_body->dword5 = rtw89_build_txwd_body5(desc_info);
 }
 txwd_body->dword7 = rtw89_build_txwd_body7_v1(desc_info);

 if (!desc_info->en_wd_info)
  return;

 txwd_info = (struct rtw89_txwd_info *)(txwd_body + 1);
 txwd_info->dword0 = rtw89_build_txwd_info0_v1(desc_info);
 txwd_info->dword1 = rtw89_build_txwd_info1(desc_info);
 txwd_info->dword2 = rtw89_build_txwd_info2_v1(desc_info);
 txwd_info->dword4 = rtw89_build_txwd_info4(desc_info);
}
EXPORT_SYMBOL(rtw89_core_fill_txdesc_v1);

static __le32 rtw89_build_txwd_body0_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY0_WP_OFFSET_V1, desc_info->wp_offset) |
      FIELD_PREP(BE_TXD_BODY0_WDINFO_EN, desc_info->en_wd_info) |
      FIELD_PREP(BE_TXD_BODY0_CH_DMA, desc_info->ch_dma) |
      FIELD_PREP(BE_TXD_BODY0_HDR_LLC_LEN, desc_info->hdr_llc_len) |
      FIELD_PREP(BE_TXD_BODY0_WD_PAGE, desc_info->wd_page);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body1_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY1_ADDR_INFO_NUM, desc_info->addr_info_nr) |
      FIELD_PREP(BE_TXD_BODY1_SEC_KEYID, desc_info->sec_keyid) |
      FIELD_PREP(BE_TXD_BODY1_SEC_TYPE, desc_info->sec_type);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body2_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY2_TID_IND, desc_info->tid_indicate) |
      FIELD_PREP(BE_TXD_BODY2_QSEL, desc_info->qsel) |
      FIELD_PREP(BE_TXD_BODY2_TXPKTSIZE, desc_info->pkt_size) |
      FIELD_PREP(BE_TXD_BODY2_AGG_EN, desc_info->agg_en) |
      FIELD_PREP(BE_TXD_BODY2_BK, desc_info->bk) |
      FIELD_PREP(BE_TXD_BODY2_MACID, desc_info->mac_id);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body3_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY3_WIFI_SEQ, desc_info->seq) |
      FIELD_PREP(BE_TXD_BODY3_MLO_FLAG, desc_info->mlo) |
      FIELD_PREP(BE_TXD_BODY3_IS_MLD_SW_EN, desc_info->sw_mld);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body4_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY4_SEC_IV_L0, desc_info->sec_seq[0]) |
      FIELD_PREP(BE_TXD_BODY4_SEC_IV_L1, desc_info->sec_seq[1]);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body5_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY5_SEC_IV_H2, desc_info->sec_seq[2]) |
      FIELD_PREP(BE_TXD_BODY5_SEC_IV_H3, desc_info->sec_seq[3]) |
      FIELD_PREP(BE_TXD_BODY5_SEC_IV_H4, desc_info->sec_seq[4]) |
      FIELD_PREP(BE_TXD_BODY5_SEC_IV_H5, desc_info->sec_seq[5]);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body6_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY6_UPD_WLAN_HDR, desc_info->upd_wlan_hdr);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_body7_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_BODY7_USERATE_SEL, desc_info->use_rate) |
      FIELD_PREP(BE_TXD_BODY7_DATA_ER, desc_info->er_cap) |
      FIELD_PREP(BE_TXD_BODY7_DATA_BW_ER, 0) |
      FIELD_PREP(BE_TXD_BODY7_DATARATE, desc_info->data_rate);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info0_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_INFO0_DATA_STBC, desc_info->stbc) |
      FIELD_PREP(BE_TXD_INFO0_DATA_LDPC, desc_info->ldpc) |
      FIELD_PREP(BE_TXD_INFO0_DISDATAFB, desc_info->dis_data_fb) |
      FIELD_PREP(BE_TXD_INFO0_MULTIPORT_ID, desc_info->port);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info1_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_INFO1_MAX_AGG_NUM, desc_info->ampdu_num) |
      FIELD_PREP(BE_TXD_INFO1_A_CTRL_BSR, desc_info->a_ctrl_bsr) |
      FIELD_PREP(BE_TXD_INFO1_DATA_RTY_LOWEST_RATE,
          desc_info->data_retry_lowest_rate);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info2_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_TXD_INFO2_AMPDU_DENSITY, desc_info->ampdu_density) |
      FIELD_PREP(BE_TXD_INFO2_FORCE_KEY_EN, desc_info->sec_en) |
      FIELD_PREP(BE_TXD_INFO2_SEC_CAM_IDX, desc_info->sec_cam_idx);

 return cpu_to_le32(dword);
}

static __le32 rtw89_build_txwd_info4_v2(struct rtw89_tx_desc_info *desc_info)
{
 bool rts_en = !desc_info->is_bmc;
 u32 dword = FIELD_PREP(BE_TXD_INFO4_RTS_EN, rts_en) |
      FIELD_PREP(BE_TXD_INFO4_HW_RTS_EN, 1);

 return cpu_to_le32(dword);
}

void rtw89_core_fill_txdesc_v2(struct rtw89_dev *rtwdev,
          struct rtw89_tx_desc_info *desc_info,
          void *txdesc)
{
 struct rtw89_txwd_body_v2 *txwd_body = txdesc;
 struct rtw89_txwd_info_v2 *txwd_info;

 txwd_body->dword0 = rtw89_build_txwd_body0_v2(desc_info);
 txwd_body->dword1 = rtw89_build_txwd_body1_v2(desc_info);
 txwd_body->dword2 = rtw89_build_txwd_body2_v2(desc_info);
 txwd_body->dword3 = rtw89_build_txwd_body3_v2(desc_info);
 if (desc_info->sec_en) {
  txwd_body->dword4 = rtw89_build_txwd_body4_v2(desc_info);
  txwd_body->dword5 = rtw89_build_txwd_body5_v2(desc_info);
 }
 txwd_body->dword6 = rtw89_build_txwd_body6_v2(desc_info);
 txwd_body->dword7 = rtw89_build_txwd_body7_v2(desc_info);

 if (!desc_info->en_wd_info)
  return;

 txwd_info = (struct rtw89_txwd_info_v2 *)(txwd_body + 1);
 txwd_info->dword0 = rtw89_build_txwd_info0_v2(desc_info);
 txwd_info->dword1 = rtw89_build_txwd_info1_v2(desc_info);
 txwd_info->dword2 = rtw89_build_txwd_info2_v2(desc_info);
 txwd_info->dword4 = rtw89_build_txwd_info4_v2(desc_info);
}
EXPORT_SYMBOL(rtw89_core_fill_txdesc_v2);

static __le32 rtw89_build_txwd_fwcmd0_v1(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(AX_RXD_RPKT_LEN_MASK, desc_info->pkt_size) |
      FIELD_PREP(AX_RXD_RPKT_TYPE_MASK, desc_info->fw_dl ?
            RTW89_CORE_RX_TYPE_FWDL :
            RTW89_CORE_RX_TYPE_H2C);

 return cpu_to_le32(dword);
}

void rtw89_core_fill_txdesc_fwcmd_v1(struct rtw89_dev *rtwdev,
         struct rtw89_tx_desc_info *desc_info,
         void *txdesc)
{
 struct rtw89_rxdesc_short *txwd_v1 = (struct rtw89_rxdesc_short *)txdesc;

 txwd_v1->dword0 = rtw89_build_txwd_fwcmd0_v1(desc_info);
}
EXPORT_SYMBOL(rtw89_core_fill_txdesc_fwcmd_v1);

static __le32 rtw89_build_txwd_fwcmd0_v2(struct rtw89_tx_desc_info *desc_info)
{
 u32 dword = FIELD_PREP(BE_RXD_RPKT_LEN_MASK, desc_info->pkt_size) |
      FIELD_PREP(BE_RXD_RPKT_TYPE_MASK, desc_info->fw_dl ?
            RTW89_CORE_RX_TYPE_FWDL :
            RTW89_CORE_RX_TYPE_H2C);

 return cpu_to_le32(dword);
}

void rtw89_core_fill_txdesc_fwcmd_v2(struct rtw89_dev *rtwdev,
         struct rtw89_tx_desc_info *desc_info,
         void *txdesc)
{
 struct rtw89_rxdesc_short_v2 *txwd_v2 = (struct rtw89_rxdesc_short_v2 *)txdesc;

 txwd_v2->dword0 = rtw89_build_txwd_fwcmd0_v2(desc_info);
}
EXPORT_SYMBOL(rtw89_core_fill_txdesc_fwcmd_v2);

static int rtw89_core_rx_process_mac_ppdu(struct rtw89_dev *rtwdev,
       struct sk_buff *skb,
       struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_chip_info *chip = rtwdev->chip;
 const struct rtw89_rxinfo *rxinfo = (const struct rtw89_rxinfo *)skb->data;
 const struct rtw89_rxinfo_user *user;
 enum rtw89_chip_gen chip_gen = rtwdev->chip->chip_gen;
 int rx_cnt_size = RTW89_PPDU_MAC_RX_CNT_SIZE;
 bool rx_cnt_valid = false;
 bool invalid = false;
 u8 plcp_size = 0;
 u8 *phy_sts;
 u8 usr_num;
 int i;

 if (chip_gen == RTW89_CHIP_BE) {
  invalid = le32_get_bits(rxinfo->w0, RTW89_RXINFO_W0_INVALID_V1);
  rx_cnt_size = RTW89_PPDU_MAC_RX_CNT_SIZE_V1;
 }

 if (invalid)
  return -EINVAL;

 rx_cnt_valid = le32_get_bits(rxinfo->w0, RTW89_RXINFO_W0_RX_CNT_VLD);
 if (chip_gen == RTW89_CHIP_BE) {
  plcp_size = le32_get_bits(rxinfo->w0, RTW89_RXINFO_W0_PLCP_LEN_V1) << 3;
  usr_num = le32_get_bits(rxinfo->w0, RTW89_RXINFO_W0_USR_NUM_V1);
 } else {
  plcp_size = le32_get_bits(rxinfo->w1, RTW89_RXINFO_W1_PLCP_LEN) << 3;
  usr_num = le32_get_bits(rxinfo->w0, RTW89_RXINFO_W0_USR_NUM);
 }
 if (usr_num > chip->ppdu_max_usr) {
  rtw89_warn(rtwdev, "Invalid user number (%d) in mac info\n",
      usr_num);
  return -EINVAL;
 }

 for (i = 0; i < usr_num; i++) {
  user = &rxinfo->user[i];
  if (!le32_get_bits(user->w0, RTW89_RXINFO_USER_MAC_ID_VALID))
   continue;
  /* For WiFi 7 chips, RXWD.mac_id of PPDU status is not set
 * by hardware, so update mac_id by rxinfo_user[].mac_id.
 */

  if (chip_gen == RTW89_CHIP_BE)
   phy_ppdu->mac_id =
    le32_get_bits(user->w0, RTW89_RXINFO_USER_MACID);
  phy_ppdu->has_data =
   le32_get_bits(user->w0, RTW89_RXINFO_USER_DATA);
  phy_ppdu->has_bcn =
   le32_get_bits(user->w0, RTW89_RXINFO_USER_BCN);
  break;
 }

 phy_sts = skb->data + RTW89_PPDU_MAC_INFO_SIZE;
 phy_sts += usr_num * RTW89_PPDU_MAC_INFO_USR_SIZE;
 /* 8-byte alignment */
 if (usr_num & BIT(0))
  phy_sts += RTW89_PPDU_MAC_INFO_USR_SIZE;
 if (rx_cnt_valid)
  phy_sts += rx_cnt_size;
 phy_sts += plcp_size;

 if (phy_sts > skb->data + skb->len)
  return -EINVAL;

 phy_ppdu->buf = phy_sts;
 phy_ppdu->len = skb->data + skb->len - phy_sts;

 return 0;
}

static u8 rtw89_get_data_rate_nss(struct rtw89_dev *rtwdev, u16 data_rate)
{
 u8 data_rate_mode;

 data_rate_mode = rtw89_get_data_rate_mode(rtwdev, data_rate);
 switch (data_rate_mode) {
 case DATA_RATE_MODE_NON_HT:
  return 1;
 case DATA_RATE_MODE_HT:
  return rtw89_get_data_ht_nss(rtwdev, data_rate) + 1;
 case DATA_RATE_MODE_VHT:
 case DATA_RATE_MODE_HE:
 case DATA_RATE_MODE_EHT:
  return rtw89_get_data_nss(rtwdev, data_rate) + 1;
 default:
  rtw89_warn(rtwdev, "invalid RX rate mode %d\n", data_rate_mode);
  return 0;
 }
}

static void rtw89_core_rx_process_phy_ppdu_iter(void *data,
      struct ieee80211_sta *sta)
{
 struct rtw89_rx_phy_ppdu *phy_ppdu = (struct rtw89_rx_phy_ppdu *)data;
 struct rtw89_sta *rtwsta = sta_to_rtwsta(sta);
 struct rtw89_dev *rtwdev = rtwsta->rtwdev;
 struct rtw89_hal *hal = &rtwdev->hal;
 struct rtw89_sta_link *rtwsta_link;
 u8 ant_num = hal->ant_diversity ? 2 : rtwdev->chip->rf_path_num;
 u8 ant_pos = U8_MAX;
 u8 evm_pos = 0;
 int i;

 rtwsta_link = rtw89_sta_get_link_inst(rtwsta, phy_ppdu->phy_idx);
 if (unlikely(!rtwsta_link))
  return;

 if (rtwsta_link->mac_id != phy_ppdu->mac_id || !phy_ppdu->to_self)
  return;

 if (hal->ant_diversity && hal->antenna_rx) {
  ant_pos = __ffs(hal->antenna_rx);
  evm_pos = ant_pos;
 }

 ewma_rssi_add(&rtwsta_link->avg_rssi, phy_ppdu->rssi_avg);

 if (ant_pos < ant_num) {
  ewma_rssi_add(&rtwsta_link->rssi[ant_pos], phy_ppdu->rssi[0]);
 } else {
  for (i = 0; i < rtwdev->chip->rf_path_num; i++)
   ewma_rssi_add(&rtwsta_link->rssi[i], phy_ppdu->rssi[i]);
 }

 if (phy_ppdu->ofdm.has && (phy_ppdu->has_data || phy_ppdu->has_bcn)) {
  ewma_snr_add(&rtwsta_link->avg_snr, phy_ppdu->ofdm.avg_snr);
  if (rtw89_get_data_rate_nss(rtwdev, phy_ppdu->rate) == 1) {
   ewma_evm_add(&rtwsta_link->evm_1ss, phy_ppdu->ofdm.evm_min);
  } else {
   ewma_evm_add(&rtwsta_link->evm_min[evm_pos],
         phy_ppdu->ofdm.evm_min);
   ewma_evm_add(&rtwsta_link->evm_max[evm_pos],
         phy_ppdu->ofdm.evm_max);
  }
 }
}

#define VAR_LEN 0xff
#define VAR_LEN_UNIT 8
static u16 rtw89_core_get_phy_status_ie_len(struct rtw89_dev *rtwdev,
         const struct rtw89_phy_sts_iehdr *iehdr)
{
 static const u8 physts_ie_len_tabs[RTW89_CHIP_GEN_NUM][32] = {
  [RTW89_CHIP_AX] = {
   16, 32, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
   VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 16, 24, VAR_LEN,
   VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32
  },
  [RTW89_CHIP_BE] = {
   32, 40, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
   VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 88, 56, VAR_LEN,
   VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32
  },
 };
 const u8 *physts_ie_len_tab;
 u16 ie_len;
 u8 ie;

 physts_ie_len_tab = physts_ie_len_tabs[rtwdev->chip->chip_gen];

 ie = le32_get_bits(iehdr->w0, RTW89_PHY_STS_IEHDR_TYPE);
 if (physts_ie_len_tab[ie] != VAR_LEN)
  ie_len = physts_ie_len_tab[ie];
 else
  ie_len = le32_get_bits(iehdr->w0, RTW89_PHY_STS_IEHDR_LEN) * VAR_LEN_UNIT;

 return ie_len;
}

static void rtw89_core_parse_phy_status_ie01_v2(struct rtw89_dev *rtwdev,
      const struct rtw89_phy_sts_iehdr *iehdr,
      struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_ie01_v2 *ie;
 u8 *rpl_fd = phy_ppdu->rpl_fd;

 ie = (const struct rtw89_phy_sts_ie01_v2 *)iehdr;
 rpl_fd[RF_PATH_A] = le32_get_bits(ie->w8, RTW89_PHY_STS_IE01_V2_W8_RPL_FD_A);
 rpl_fd[RF_PATH_B] = le32_get_bits(ie->w8, RTW89_PHY_STS_IE01_V2_W8_RPL_FD_B);
 rpl_fd[RF_PATH_C] = le32_get_bits(ie->w9, RTW89_PHY_STS_IE01_V2_W9_RPL_FD_C);
 rpl_fd[RF_PATH_D] = le32_get_bits(ie->w9, RTW89_PHY_STS_IE01_V2_W9_RPL_FD_D);

 phy_ppdu->bw_idx = le32_get_bits(ie->w5, RTW89_PHY_STS_IE01_V2_W5_BW_IDX);
}

static void rtw89_core_parse_phy_status_ie01(struct rtw89_dev *rtwdev,
          const struct rtw89_phy_sts_iehdr *iehdr,
          struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_ie01 *ie = (const struct rtw89_phy_sts_ie01 *)iehdr;
 s16 cfo;
 u32 t;

 phy_ppdu->chan_idx = le32_get_bits(ie->w0, RTW89_PHY_STS_IE01_W0_CH_IDX);

 if (rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR) {
  phy_ppdu->ldpc = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_LDPC);
  phy_ppdu->stbc = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_STBC);
 }

 if (!phy_ppdu->hdr_2_en)
  phy_ppdu->rx_path_en =
   le32_get_bits(ie->w0, RTW89_PHY_STS_IE01_W0_RX_PATH_EN);

 if (phy_ppdu->rate < RTW89_HW_RATE_OFDM6)
  return;

 if (!phy_ppdu->to_self)
  return;

 phy_ppdu->rpl_avg = le32_get_bits(ie->w0, RTW89_PHY_STS_IE01_W0_RSSI_AVG_FD);
 phy_ppdu->ofdm.avg_snr = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_AVG_SNR);
 phy_ppdu->ofdm.evm_max = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_EVM_MAX);
 phy_ppdu->ofdm.evm_min = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_EVM_MIN);
 phy_ppdu->ofdm.has = true;

 /* sign conversion for S(12,2) */
 if (rtwdev->chip->cfo_src_fd) {
  t = le32_get_bits(ie->w1, RTW89_PHY_STS_IE01_W1_FD_CFO);
  cfo = sign_extend32(t, 11);
 } else {
  t = le32_get_bits(ie->w1, RTW89_PHY_STS_IE01_W1_PREMB_CFO);
  cfo = sign_extend32(t, 11);
 }

 rtw89_phy_cfo_parse(rtwdev, cfo, phy_ppdu);

 if (rtwdev->chip->chip_gen == RTW89_CHIP_BE)
  rtw89_core_parse_phy_status_ie01_v2(rtwdev, iehdr, phy_ppdu);
}

static void rtw89_core_parse_phy_status_ie00(struct rtw89_dev *rtwdev,
          const struct rtw89_phy_sts_iehdr *iehdr,
          struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_ie00 *ie = (const struct rtw89_phy_sts_ie00 *)iehdr;
 u16 tmp_rpl;

 tmp_rpl = le32_get_bits(ie->w0, RTW89_PHY_STS_IE00_W0_RPL);
 phy_ppdu->rpl_avg = tmp_rpl >> 1;

 if (!phy_ppdu->hdr_2_en)
  phy_ppdu->rx_path_en =
   le32_get_bits(ie->w3, RTW89_PHY_STS_IE00_W3_RX_PATH_EN);
}

static void rtw89_core_parse_phy_status_ie00_v2(struct rtw89_dev *rtwdev,
      const struct rtw89_phy_sts_iehdr *iehdr,
      struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_ie00_v2 *ie;
 u8 *rpl_path = phy_ppdu->rpl_path;
 u16 tmp_rpl[RF_PATH_MAX];
 u8 i;

 ie = (const struct rtw89_phy_sts_ie00_v2 *)iehdr;
 tmp_rpl[RF_PATH_A] = le32_get_bits(ie->w4, RTW89_PHY_STS_IE00_V2_W4_RPL_TD_A);
 tmp_rpl[RF_PATH_B] = le32_get_bits(ie->w4, RTW89_PHY_STS_IE00_V2_W4_RPL_TD_B);
 tmp_rpl[RF_PATH_C] = le32_get_bits(ie->w4, RTW89_PHY_STS_IE00_V2_W4_RPL_TD_C);
 tmp_rpl[RF_PATH_D] = le32_get_bits(ie->w5, RTW89_PHY_STS_IE00_V2_W5_RPL_TD_D);

 for (i = 0; i < RF_PATH_MAX; i++)
  rpl_path[i] = tmp_rpl[i] >> 1;
}

static int rtw89_core_process_phy_status_ie(struct rtw89_dev *rtwdev,
         const struct rtw89_phy_sts_iehdr *iehdr,
         struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 u8 ie;

 ie = le32_get_bits(iehdr->w0, RTW89_PHY_STS_IEHDR_TYPE);

 switch (ie) {
 case RTW89_PHYSTS_IE00_CMN_CCK:
  rtw89_core_parse_phy_status_ie00(rtwdev, iehdr, phy_ppdu);
  if (rtwdev->chip->chip_gen == RTW89_CHIP_BE)
   rtw89_core_parse_phy_status_ie00_v2(rtwdev, iehdr, phy_ppdu);
  break;
 case RTW89_PHYSTS_IE01_CMN_OFDM:
  rtw89_core_parse_phy_status_ie01(rtwdev, iehdr, phy_ppdu);
  break;
 default:
  break;
 }

 return 0;
}

static void rtw89_core_update_phy_ppdu_hdr_v2(struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_hdr_v2 *hdr = phy_ppdu->buf + PHY_STS_HDR_LEN;

 phy_ppdu->rx_path_en = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_V2_W0_PATH_EN);
}

static void rtw89_core_update_phy_ppdu(struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_hdr *hdr = phy_ppdu->buf;
 u8 *rssi = phy_ppdu->rssi;

 phy_ppdu->ie = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_W0_IE_MAP);
 phy_ppdu->rssi_avg = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_W0_RSSI_AVG);
 rssi[RF_PATH_A] = le32_get_bits(hdr->w1, RTW89_PHY_STS_HDR_W1_RSSI_A);
 rssi[RF_PATH_B] = le32_get_bits(hdr->w1, RTW89_PHY_STS_HDR_W1_RSSI_B);
 rssi[RF_PATH_C] = le32_get_bits(hdr->w1, RTW89_PHY_STS_HDR_W1_RSSI_C);
 rssi[RF_PATH_D] = le32_get_bits(hdr->w1, RTW89_PHY_STS_HDR_W1_RSSI_D);

 phy_ppdu->hdr_2_en = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_W0_HDR_2_EN);
 if (phy_ppdu->hdr_2_en)
  rtw89_core_update_phy_ppdu_hdr_v2(phy_ppdu);
}

static int rtw89_core_rx_process_phy_ppdu(struct rtw89_dev *rtwdev,
       struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 const struct rtw89_phy_sts_hdr *hdr = phy_ppdu->buf;
 u32 len_from_header;
 bool physts_valid;

 physts_valid = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_W0_VALID);
 if (!physts_valid)
  return -EINVAL;

 len_from_header = le32_get_bits(hdr->w0, RTW89_PHY_STS_HDR_W0_LEN) << 3;

 if (rtwdev->chip->chip_gen == RTW89_CHIP_BE)
  len_from_header += PHY_STS_HDR_LEN;

 if (len_from_header != phy_ppdu->len) {
  rtw89_debug(rtwdev, RTW89_DBG_UNEXP, "phy ppdu len mismatch\n");
  return -EINVAL;
 }
 rtw89_core_update_phy_ppdu(phy_ppdu);

 return 0;
}

static int rtw89_core_rx_parse_phy_sts(struct rtw89_dev *rtwdev,
           struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 u16 ie_len;
 void *pos, *end;

 /* mark invalid reports and bypass them */
 if (phy_ppdu->ie < RTW89_CCK_PKT)
  return -EINVAL;

 pos = phy_ppdu->buf + PHY_STS_HDR_LEN;
 if (phy_ppdu->hdr_2_en)
  pos += PHY_STS_HDR_LEN;
 end = phy_ppdu->buf + phy_ppdu->len;
 while (pos < end) {
  const struct rtw89_phy_sts_iehdr *iehdr = pos;

  ie_len = rtw89_core_get_phy_status_ie_len(rtwdev, iehdr);
  rtw89_core_process_phy_status_ie(rtwdev, iehdr, phy_ppdu);
  pos += ie_len;
  if (pos > end || ie_len == 0) {
   rtw89_debug(rtwdev, RTW89_DBG_TXRX,
        "phy status parse failed\n");
   return -EINVAL;
  }
 }

 rtw89_chip_convert_rpl_to_rssi(rtwdev, phy_ppdu);
 rtw89_phy_antdiv_parse(rtwdev, phy_ppdu);

 return 0;
}

static void rtw89_core_rx_process_phy_sts(struct rtw89_dev *rtwdev,
       struct rtw89_rx_phy_ppdu *phy_ppdu)
{
 int ret;

 ret = rtw89_core_rx_parse_phy_sts(rtwdev, phy_ppdu);
 if (ret)
  rtw89_debug(rtwdev, RTW89_DBG_TXRX, "parse phy sts failed\n");
 else
  phy_ppdu->valid = true;

 ieee80211_iterate_stations_atomic(rtwdev->hw,
       rtw89_core_rx_process_phy_ppdu_iter,
       phy_ppdu);
}

static u8 rtw89_rxdesc_to_nl_he_gi(struct rtw89_dev *rtwdev,
       u8 desc_info_gi,
       bool rx_status)
{
 switch (desc_info_gi) {
 case RTW89_GILTF_SGI_4XHE08:
 case RTW89_GILTF_2XHE08:
 case RTW89_GILTF_1XHE08:
  return NL80211_RATE_INFO_HE_GI_0_8;
 case RTW89_GILTF_2XHE16:
 case RTW89_GILTF_1XHE16:
  return NL80211_RATE_INFO_HE_GI_1_6;
 case RTW89_GILTF_LGI_4XHE32:
  return NL80211_RATE_INFO_HE_GI_3_2;
 default:
  rtw89_warn(rtwdev, "invalid gi_ltf=%d", desc_info_gi);
  if (rx_status)
   return NL80211_RATE_INFO_HE_GI_3_2;
  return U8_MAX;
 }
}

static u8 rtw89_rxdesc_to_nl_eht_gi(struct rtw89_dev *rtwdev,
        u8 desc_info_gi,
        bool rx_status)
{
 switch (desc_info_gi) {
 case RTW89_GILTF_SGI_4XHE08:
 case RTW89_GILTF_2XHE08:
 case RTW89_GILTF_1XHE08:
  return NL80211_RATE_INFO_EHT_GI_0_8;
 case RTW89_GILTF_2XHE16:
 case RTW89_GILTF_1XHE16:
  return NL80211_RATE_INFO_EHT_GI_1_6;
 case RTW89_GILTF_LGI_4XHE32:
  return NL80211_RATE_INFO_EHT_GI_3_2;
 default:
  rtw89_warn(rtwdev, "invalid gi_ltf=%d", desc_info_gi);
  if (rx_status)
   return NL80211_RATE_INFO_EHT_GI_3_2;
  return U8_MAX;
 }
}

static u8 rtw89_rxdesc_to_nl_he_eht_gi(struct rtw89_dev *rtwdev,
           u8 desc_info_gi,
           bool rx_status, bool eht)
{
 return eht ? rtw89_rxdesc_to_nl_eht_gi(rtwdev, desc_info_gi, rx_status) :
       rtw89_rxdesc_to_nl_he_gi(rtwdev, desc_info_gi, rx_status);
}

static
bool rtw89_check_rx_statu_gi_match(struct ieee80211_rx_status *status, u8 gi_ltf,
       bool eht)
{
 if (eht)
  return status->eht.gi == gi_ltf;

 return status->he_gi == gi_ltf;
}

static bool rtw89_core_rx_ppdu_match(struct rtw89_dev *rtwdev,
         struct rtw89_rx_desc_info *desc_info,
         struct ieee80211_rx_status *status)
{
 u8 band = desc_info->bb_sel ? RTW89_PHY_1 : RTW89_PHY_0;
 u8 data_rate_mode, bw, rate_idx = MASKBYTE0, gi_ltf;
 bool eht = false;
 u16 data_rate;
 bool ret;

 data_rate = desc_info->data_rate;
 data_rate_mode = rtw89_get_data_rate_mode(rtwdev, data_rate);
 if (data_rate_mode == DATA_RATE_MODE_NON_HT) {
  rate_idx = rtw89_get_data_not_ht_idx(rtwdev, data_rate);
  /* rate_idx is still hardware value here */
 } else if (data_rate_mode == DATA_RATE_MODE_HT) {
  rate_idx = rtw89_get_data_ht_mcs(rtwdev, data_rate);
 } else if (data_rate_mode == DATA_RATE_MODE_VHT ||
     data_rate_mode == DATA_RATE_MODE_HE ||
     data_rate_mode == DATA_RATE_MODE_EHT) {
  rate_idx = rtw89_get_data_mcs(rtwdev, data_rate);
 } else {
  rtw89_warn(rtwdev, "invalid RX rate mode %d\n", data_rate_mode);
 }

 eht = data_rate_mode == DATA_RATE_MODE_EHT;
 bw = rtw89_hw_to_rate_info_bw(desc_info->bw);
 gi_ltf = rtw89_rxdesc_to_nl_he_eht_gi(rtwdev, desc_info->gi_ltf, false, eht);
 ret = rtwdev->ppdu_sts.curr_rx_ppdu_cnt[band] == desc_info->ppdu_cnt &&
       status->rate_idx == rate_idx &&
       rtw89_check_rx_statu_gi_match(status, gi_ltf, eht) &&
       status->bw == bw;

 return ret;
}

struct rtw89_vif_rx_stats_iter_data {
 struct rtw89_dev *rtwdev;
 struct rtw89_rx_phy_ppdu *phy_ppdu;
 struct rtw89_rx_desc_info *desc_info;
 struct sk_buff *skb;
 const u8 *bssid;
};

static void rtw89_stats_trigger_frame(struct rtw89_dev *rtwdev,
          struct rtw89_vif_link *rtwvif_link,
          struct ieee80211_bss_conf *bss_conf,
          struct sk_buff *skb)
{
 struct ieee80211_trigger *tf = (struct ieee80211_trigger *)skb->data;
 struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link);
 struct rtw89_vif *rtwvif = rtwvif_link->rtwvif;
 u8 *pos, *end, type, tf_bw;
 u16 aid, tf_rua;

 if (!ether_addr_equal(bss_conf->bssid, tf->ta) ||
     rtwvif_link->wifi_role != RTW89_WIFI_ROLE_STATION ||
     rtwvif_link->net_type == RTW89_NET_TYPE_NO_LINK)
  return;

 type = le64_get_bits(tf->common_info, IEEE80211_TRIGGER_TYPE_MASK);
 if (type != IEEE80211_TRIGGER_TYPE_BASIC && type != IEEE80211_TRIGGER_TYPE_MU_BAR)
  return;

 end = (u8 *)tf + skb->len;
 pos = tf->variable;

 while (end - pos >= RTW89_TF_BASIC_USER_INFO_SZ) {
  aid = RTW89_GET_TF_USER_INFO_AID12(pos);
  tf_rua = RTW89_GET_TF_USER_INFO_RUA(pos);
  tf_bw = le64_get_bits(tf->common_info, IEEE80211_TRIGGER_ULBW_MASK);
  rtw89_debug(rtwdev, RTW89_DBG_TXRX,
       "[TF] aid: %d, ul_mcs: %d, rua: %d, bw: %d\n",
       aid, RTW89_GET_TF_USER_INFO_UL_MCS(pos),
       tf_rua, tf_bw);

  if (aid == RTW89_TF_PAD)
   break;

  if (aid == vif->cfg.aid) {
   enum nl80211_he_ru_alloc rua;

   rtwvif->stats.rx_tf_acc++;
   rtwdev->stats.rx_tf_acc++;

   /* The following only required for HE trigger frame, but we
 * cannot use UL HE-SIG-A2 reserved subfield to identify it
 * since some 11ax APs will fill it with all 0s, which will
 * be misunderstood as EHT trigger frame.
 */

   if (bss_conf->eht_support)
    break;

   rua = rtw89_he_rua_to_ru_alloc(tf_rua >> 1);

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=100 H=98 G=98

¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge