1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00
linux/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
Sriram R 046d2e7c50 mac80211: prepare sta handling for MLO support
Currently in mac80211 each STA object is represented
using sta_info datastructure with the associated
STA specific information and drivers access ieee80211_sta
part of it.

With MLO (Multi Link Operation) support being added
in 802.11be standard, though the association is logically
with a single Multi Link capable STA, at the physical level
communication can happen via different advertised
links (uniquely identified by Channel, operating class,
BSSID) and hence the need to handle multiple link
STA parameters within a composite sta_info object
called the MLD STA. The different link STA part of
MLD STA are identified using the link address which can
be same or different as the MLD STA address and unique
link id based on the link vif.

To support extension of such a model, the sta_info
datastructure is modified to hold multiple link STA
objects with link specific params currently within
sta_info moved to this new structure. Similarly this is
done for ieee80211_sta as well which will be accessed
within mac80211 as well as by drivers, hence trivial
driver changes are expected to support this.

For current non MLO supported drivers, only one link STA
is present and link information is accessed via 'deflink'
member.

For MLO drivers, we still need to define the APIs etc. to
get the correct link ID and access the correct part of
the station info.

Currently in mac80211, all link STA info are accessed directly
via deflink. These will be updated to access via link pointers
indexed by link id with MLO support patches, with link id
being 0 for non MLO supported cases.

Except for couple of macro related changes, below spatch takes
care of updating mac80211 and driver code to access to the
link STA info via deflink.

  @ieee80211_sta@
  struct ieee80211_sta *s;
  struct sta_info *si;
  identifier var = {supp_rates, ht_cap, vht_cap, he_cap, he_6ghz_capa, eht_cap, rx_nss, bandwidth, txpwr};
  @@

  (
    s->
  -    var
  +    deflink.var
  |
   si->sta.
  -    var
  +    deflink.var
  )

  @sta_info@
  struct sta_info *si;
  identifier var = {gtk, pcpu_rx_stats, rx_stats, rx_stats_avg, status_stats, tx_stats, cur_max_bandwidth};
  @@

  (
    si->
  -    var
  +    deflink.var
  )

Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
Link: https://lore.kernel.org/r/1649086883-13246-1-git-send-email-quic_srirrama@quicinc.com
[remove MLO-drivers notes from commit message, not clear yet; run spatch]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2022-04-11 16:42:03 +02:00

1231 lines
31 KiB
C

// SPDX-License-Identifier: ISC
/*
* Copyright (C) 2016 Felix Fietkau <nbd@nbd.name>
* Copyright (C) 2018 Stanislaw Gruszka <stf_xl@wp.pl>
*/
#include "mt76x02.h"
#include "mt76x02_trace.h"
#include "trace.h"
void mt76x02_mac_reset_counters(struct mt76x02_dev *dev)
{
int i;
mt76_rr(dev, MT_RX_STAT_0);
mt76_rr(dev, MT_RX_STAT_1);
mt76_rr(dev, MT_RX_STAT_2);
mt76_rr(dev, MT_TX_STA_0);
mt76_rr(dev, MT_TX_STA_1);
mt76_rr(dev, MT_TX_STA_2);
for (i = 0; i < 16; i++)
mt76_rr(dev, MT_TX_AGG_CNT(i));
for (i = 0; i < 16; i++)
mt76_rr(dev, MT_TX_STAT_FIFO);
memset(dev->mt76.aggr_stats, 0, sizeof(dev->mt76.aggr_stats));
}
EXPORT_SYMBOL_GPL(mt76x02_mac_reset_counters);
static enum mt76x02_cipher_type
mt76x02_mac_get_key_info(struct ieee80211_key_conf *key, u8 *key_data)
{
memset(key_data, 0, 32);
if (!key)
return MT76X02_CIPHER_NONE;
if (key->keylen > 32)
return MT76X02_CIPHER_NONE;
memcpy(key_data, key->key, key->keylen);
switch (key->cipher) {
case WLAN_CIPHER_SUITE_WEP40:
return MT76X02_CIPHER_WEP40;
case WLAN_CIPHER_SUITE_WEP104:
return MT76X02_CIPHER_WEP104;
case WLAN_CIPHER_SUITE_TKIP:
return MT76X02_CIPHER_TKIP;
case WLAN_CIPHER_SUITE_CCMP:
return MT76X02_CIPHER_AES_CCMP;
default:
return MT76X02_CIPHER_NONE;
}
}
int mt76x02_mac_shared_key_setup(struct mt76x02_dev *dev, u8 vif_idx,
u8 key_idx, struct ieee80211_key_conf *key)
{
enum mt76x02_cipher_type cipher;
u8 key_data[32];
u32 val;
cipher = mt76x02_mac_get_key_info(key, key_data);
if (cipher == MT76X02_CIPHER_NONE && key)
return -EOPNOTSUPP;
val = mt76_rr(dev, MT_SKEY_MODE(vif_idx));
val &= ~(MT_SKEY_MODE_MASK << MT_SKEY_MODE_SHIFT(vif_idx, key_idx));
val |= cipher << MT_SKEY_MODE_SHIFT(vif_idx, key_idx);
mt76_wr(dev, MT_SKEY_MODE(vif_idx), val);
mt76_wr_copy(dev, MT_SKEY(vif_idx, key_idx), key_data,
sizeof(key_data));
return 0;
}
EXPORT_SYMBOL_GPL(mt76x02_mac_shared_key_setup);
void mt76x02_mac_wcid_sync_pn(struct mt76x02_dev *dev, u8 idx,
struct ieee80211_key_conf *key)
{
enum mt76x02_cipher_type cipher;
u8 key_data[32];
u32 iv, eiv;
u64 pn;
cipher = mt76x02_mac_get_key_info(key, key_data);
iv = mt76_rr(dev, MT_WCID_IV(idx));
eiv = mt76_rr(dev, MT_WCID_IV(idx) + 4);
pn = (u64)eiv << 16;
if (cipher == MT76X02_CIPHER_TKIP) {
pn |= (iv >> 16) & 0xff;
pn |= (iv & 0xff) << 8;
} else if (cipher >= MT76X02_CIPHER_AES_CCMP) {
pn |= iv & 0xffff;
} else {
return;
}
atomic64_set(&key->tx_pn, pn);
}
int mt76x02_mac_wcid_set_key(struct mt76x02_dev *dev, u8 idx,
struct ieee80211_key_conf *key)
{
enum mt76x02_cipher_type cipher;
u8 key_data[32];
u8 iv_data[8];
u64 pn;
cipher = mt76x02_mac_get_key_info(key, key_data);
if (cipher == MT76X02_CIPHER_NONE && key)
return -EOPNOTSUPP;
mt76_wr_copy(dev, MT_WCID_KEY(idx), key_data, sizeof(key_data));
mt76_rmw_field(dev, MT_WCID_ATTR(idx), MT_WCID_ATTR_PKEY_MODE, cipher);
memset(iv_data, 0, sizeof(iv_data));
if (key) {
mt76_rmw_field(dev, MT_WCID_ATTR(idx), MT_WCID_ATTR_PAIRWISE,
!!(key->flags & IEEE80211_KEY_FLAG_PAIRWISE));
pn = atomic64_read(&key->tx_pn);
iv_data[3] = key->keyidx << 6;
if (cipher >= MT76X02_CIPHER_TKIP) {
iv_data[3] |= 0x20;
put_unaligned_le32(pn >> 16, &iv_data[4]);
}
if (cipher == MT76X02_CIPHER_TKIP) {
iv_data[0] = (pn >> 8) & 0xff;
iv_data[1] = (iv_data[0] | 0x20) & 0x7f;
iv_data[2] = pn & 0xff;
} else if (cipher >= MT76X02_CIPHER_AES_CCMP) {
put_unaligned_le16((pn & 0xffff), &iv_data[0]);
}
}
mt76_wr_copy(dev, MT_WCID_IV(idx), iv_data, sizeof(iv_data));
return 0;
}
void mt76x02_mac_wcid_setup(struct mt76x02_dev *dev, u8 idx,
u8 vif_idx, u8 *mac)
{
struct mt76_wcid_addr addr = {};
u32 attr;
attr = FIELD_PREP(MT_WCID_ATTR_BSS_IDX, vif_idx & 7) |
FIELD_PREP(MT_WCID_ATTR_BSS_IDX_EXT, !!(vif_idx & 8));
mt76_wr(dev, MT_WCID_ATTR(idx), attr);
if (idx >= 128)
return;
if (mac)
memcpy(addr.macaddr, mac, ETH_ALEN);
mt76_wr_copy(dev, MT_WCID_ADDR(idx), &addr, sizeof(addr));
}
EXPORT_SYMBOL_GPL(mt76x02_mac_wcid_setup);
void mt76x02_mac_wcid_set_drop(struct mt76x02_dev *dev, u8 idx, bool drop)
{
u32 val = mt76_rr(dev, MT_WCID_DROP(idx));
u32 bit = MT_WCID_DROP_MASK(idx);
/* prevent unnecessary writes */
if ((val & bit) != (bit * drop))
mt76_wr(dev, MT_WCID_DROP(idx), (val & ~bit) | (bit * drop));
}
static u16
mt76x02_mac_tx_rate_val(struct mt76x02_dev *dev,
const struct ieee80211_tx_rate *rate, u8 *nss_val)
{
u8 phy, rate_idx, nss, bw = 0;
u16 rateval;
if (rate->flags & IEEE80211_TX_RC_VHT_MCS) {
rate_idx = rate->idx;
nss = 1 + (rate->idx >> 4);
phy = MT_PHY_TYPE_VHT;
if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
bw = 2;
else if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
bw = 1;
} else if (rate->flags & IEEE80211_TX_RC_MCS) {
rate_idx = rate->idx;
nss = 1 + (rate->idx >> 3);
phy = MT_PHY_TYPE_HT;
if (rate->flags & IEEE80211_TX_RC_GREEN_FIELD)
phy = MT_PHY_TYPE_HT_GF;
if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
bw = 1;
} else {
const struct ieee80211_rate *r;
int band = dev->mphy.chandef.chan->band;
u16 val;
r = &dev->mt76.hw->wiphy->bands[band]->bitrates[rate->idx];
if (rate->flags & IEEE80211_TX_RC_USE_SHORT_PREAMBLE)
val = r->hw_value_short;
else
val = r->hw_value;
phy = val >> 8;
rate_idx = val & 0xff;
nss = 1;
}
rateval = FIELD_PREP(MT_RXWI_RATE_INDEX, rate_idx);
rateval |= FIELD_PREP(MT_RXWI_RATE_PHY, phy);
rateval |= FIELD_PREP(MT_RXWI_RATE_BW, bw);
if (rate->flags & IEEE80211_TX_RC_SHORT_GI)
rateval |= MT_RXWI_RATE_SGI;
*nss_val = nss;
return rateval;
}
void mt76x02_mac_wcid_set_rate(struct mt76x02_dev *dev, struct mt76_wcid *wcid,
const struct ieee80211_tx_rate *rate)
{
s8 max_txpwr_adj = mt76x02_tx_get_max_txpwr_adj(dev, rate);
u16 rateval;
u32 tx_info;
s8 nss;
rateval = mt76x02_mac_tx_rate_val(dev, rate, &nss);
tx_info = FIELD_PREP(MT_WCID_TX_INFO_RATE, rateval) |
FIELD_PREP(MT_WCID_TX_INFO_NSS, nss) |
FIELD_PREP(MT_WCID_TX_INFO_TXPWR_ADJ, max_txpwr_adj) |
MT_WCID_TX_INFO_SET;
wcid->tx_info = tx_info;
}
void mt76x02_mac_set_short_preamble(struct mt76x02_dev *dev, bool enable)
{
if (enable)
mt76_set(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_PREAMB_SHORT);
else
mt76_clear(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_PREAMB_SHORT);
}
bool mt76x02_mac_load_tx_status(struct mt76x02_dev *dev,
struct mt76x02_tx_status *stat)
{
u32 stat1, stat2;
stat2 = mt76_rr(dev, MT_TX_STAT_FIFO_EXT);
stat1 = mt76_rr(dev, MT_TX_STAT_FIFO);
stat->valid = !!(stat1 & MT_TX_STAT_FIFO_VALID);
if (!stat->valid)
return false;
stat->success = !!(stat1 & MT_TX_STAT_FIFO_SUCCESS);
stat->aggr = !!(stat1 & MT_TX_STAT_FIFO_AGGR);
stat->ack_req = !!(stat1 & MT_TX_STAT_FIFO_ACKREQ);
stat->wcid = FIELD_GET(MT_TX_STAT_FIFO_WCID, stat1);
stat->rate = FIELD_GET(MT_TX_STAT_FIFO_RATE, stat1);
stat->retry = FIELD_GET(MT_TX_STAT_FIFO_EXT_RETRY, stat2);
stat->pktid = FIELD_GET(MT_TX_STAT_FIFO_EXT_PKTID, stat2);
trace_mac_txstat_fetch(dev, stat);
return true;
}
static int
mt76x02_mac_process_tx_rate(struct ieee80211_tx_rate *txrate, u16 rate,
enum nl80211_band band)
{
u8 idx = FIELD_GET(MT_RXWI_RATE_INDEX, rate);
txrate->idx = 0;
txrate->flags = 0;
txrate->count = 1;
switch (FIELD_GET(MT_RXWI_RATE_PHY, rate)) {
case MT_PHY_TYPE_OFDM:
if (band == NL80211_BAND_2GHZ)
idx += 4;
txrate->idx = idx;
return 0;
case MT_PHY_TYPE_CCK:
if (idx >= 8)
idx -= 8;
txrate->idx = idx;
return 0;
case MT_PHY_TYPE_HT_GF:
txrate->flags |= IEEE80211_TX_RC_GREEN_FIELD;
fallthrough;
case MT_PHY_TYPE_HT:
txrate->flags |= IEEE80211_TX_RC_MCS;
txrate->idx = idx;
break;
case MT_PHY_TYPE_VHT:
txrate->flags |= IEEE80211_TX_RC_VHT_MCS;
txrate->idx = idx;
break;
default:
return -EINVAL;
}
switch (FIELD_GET(MT_RXWI_RATE_BW, rate)) {
case MT_PHY_BW_20:
break;
case MT_PHY_BW_40:
txrate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH;
break;
case MT_PHY_BW_80:
txrate->flags |= IEEE80211_TX_RC_80_MHZ_WIDTH;
break;
default:
return -EINVAL;
}
if (rate & MT_RXWI_RATE_SGI)
txrate->flags |= IEEE80211_TX_RC_SHORT_GI;
return 0;
}
void mt76x02_mac_write_txwi(struct mt76x02_dev *dev, struct mt76x02_txwi *txwi,
struct sk_buff *skb, struct mt76_wcid *wcid,
struct ieee80211_sta *sta, int len)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
struct ieee80211_tx_rate *rate = &info->control.rates[0];
struct ieee80211_key_conf *key = info->control.hw_key;
u32 wcid_tx_info;
u16 rate_ht_mask = FIELD_PREP(MT_RXWI_RATE_PHY, BIT(1) | BIT(2));
u16 txwi_flags = 0, rateval;
u8 nss;
s8 txpwr_adj, max_txpwr_adj;
u8 ccmp_pn[8], nstreams = dev->mphy.chainmask & 0xf;
memset(txwi, 0, sizeof(*txwi));
mt76_tx_check_agg_ssn(sta, skb);
if (!info->control.hw_key && wcid && wcid->hw_key_idx != 0xff &&
ieee80211_has_protected(hdr->frame_control)) {
wcid = NULL;
ieee80211_get_tx_rates(info->control.vif, sta, skb,
info->control.rates, 1);
}
if (wcid)
txwi->wcid = wcid->idx;
else
txwi->wcid = 0xff;
if (wcid && wcid->sw_iv && key) {
u64 pn = atomic64_inc_return(&key->tx_pn);
ccmp_pn[0] = pn;
ccmp_pn[1] = pn >> 8;
ccmp_pn[2] = 0;
ccmp_pn[3] = 0x20 | (key->keyidx << 6);
ccmp_pn[4] = pn >> 16;
ccmp_pn[5] = pn >> 24;
ccmp_pn[6] = pn >> 32;
ccmp_pn[7] = pn >> 40;
txwi->iv = *((__le32 *)&ccmp_pn[0]);
txwi->eiv = *((__le32 *)&ccmp_pn[4]);
}
if (wcid && (rate->idx < 0 || !rate->count)) {
wcid_tx_info = wcid->tx_info;
rateval = FIELD_GET(MT_WCID_TX_INFO_RATE, wcid_tx_info);
max_txpwr_adj = FIELD_GET(MT_WCID_TX_INFO_TXPWR_ADJ,
wcid_tx_info);
nss = FIELD_GET(MT_WCID_TX_INFO_NSS, wcid_tx_info);
} else {
rateval = mt76x02_mac_tx_rate_val(dev, rate, &nss);
max_txpwr_adj = mt76x02_tx_get_max_txpwr_adj(dev, rate);
}
txwi->rate = cpu_to_le16(rateval);
txpwr_adj = mt76x02_tx_get_txpwr_adj(dev, dev->txpower_conf,
max_txpwr_adj);
txwi->ctl2 = FIELD_PREP(MT_TX_PWR_ADJ, txpwr_adj);
if (nstreams > 1 && mt76_rev(&dev->mt76) >= MT76XX_REV_E4)
txwi->txstream = 0x13;
else if (nstreams > 1 && mt76_rev(&dev->mt76) >= MT76XX_REV_E3 &&
!(txwi->rate & cpu_to_le16(rate_ht_mask)))
txwi->txstream = 0x93;
if (is_mt76x2(dev) && (info->flags & IEEE80211_TX_CTL_LDPC))
txwi->rate |= cpu_to_le16(MT_RXWI_RATE_LDPC);
if ((info->flags & IEEE80211_TX_CTL_STBC) && nss == 1)
txwi->rate |= cpu_to_le16(MT_RXWI_RATE_STBC);
if (nss > 1 && sta && sta->smps_mode == IEEE80211_SMPS_DYNAMIC)
txwi_flags |= MT_TXWI_FLAGS_MMPS;
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
txwi->ack_ctl |= MT_TXWI_ACK_CTL_REQ;
if (info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ)
txwi->ack_ctl |= MT_TXWI_ACK_CTL_NSEQ;
if ((info->flags & IEEE80211_TX_CTL_AMPDU) && sta) {
u8 ba_size = IEEE80211_MIN_AMPDU_BUF;
u8 ampdu_density = sta->deflink.ht_cap.ampdu_density;
ba_size <<= sta->deflink.ht_cap.ampdu_factor;
ba_size = min_t(int, 63, ba_size - 1);
if (info->flags & IEEE80211_TX_CTL_RATE_CTRL_PROBE)
ba_size = 0;
txwi->ack_ctl |= FIELD_PREP(MT_TXWI_ACK_CTL_BA_WINDOW, ba_size);
if (ampdu_density < IEEE80211_HT_MPDU_DENSITY_4)
ampdu_density = IEEE80211_HT_MPDU_DENSITY_4;
txwi_flags |= MT_TXWI_FLAGS_AMPDU |
FIELD_PREP(MT_TXWI_FLAGS_MPDU_DENSITY, ampdu_density);
}
if (ieee80211_is_probe_resp(hdr->frame_control) ||
ieee80211_is_beacon(hdr->frame_control))
txwi_flags |= MT_TXWI_FLAGS_TS;
txwi->flags |= cpu_to_le16(txwi_flags);
txwi->len_ctl = cpu_to_le16(len);
}
EXPORT_SYMBOL_GPL(mt76x02_mac_write_txwi);
static void
mt76x02_tx_rate_fallback(struct ieee80211_tx_rate *rates, int idx, int phy)
{
u8 mcs, nss;
if (!idx)
return;
rates += idx - 1;
rates[1] = rates[0];
switch (phy) {
case MT_PHY_TYPE_VHT:
mcs = ieee80211_rate_get_vht_mcs(rates);
nss = ieee80211_rate_get_vht_nss(rates);
if (mcs == 0)
nss = max_t(int, nss - 1, 1);
else
mcs--;
ieee80211_rate_set_vht(rates + 1, mcs, nss);
break;
case MT_PHY_TYPE_HT_GF:
case MT_PHY_TYPE_HT:
/* MCS 8 falls back to MCS 0 */
if (rates[0].idx == 8) {
rates[1].idx = 0;
break;
}
fallthrough;
default:
rates[1].idx = max_t(int, rates[0].idx - 1, 0);
break;
}
}
static void
mt76x02_mac_fill_tx_status(struct mt76x02_dev *dev, struct mt76x02_sta *msta,
struct ieee80211_tx_info *info,
struct mt76x02_tx_status *st, int n_frames)
{
struct ieee80211_tx_rate *rate = info->status.rates;
struct ieee80211_tx_rate last_rate;
u16 first_rate;
int retry = st->retry;
int phy;
int i;
if (!n_frames)
return;
phy = FIELD_GET(MT_RXWI_RATE_PHY, st->rate);
if (st->pktid & MT_PACKET_ID_HAS_RATE) {
first_rate = st->rate & ~MT_PKTID_RATE;
first_rate |= st->pktid & MT_PKTID_RATE;
mt76x02_mac_process_tx_rate(&rate[0], first_rate,
dev->mphy.chandef.chan->band);
} else if (rate[0].idx < 0) {
if (!msta)
return;
mt76x02_mac_process_tx_rate(&rate[0], msta->wcid.tx_info,
dev->mphy.chandef.chan->band);
}
mt76x02_mac_process_tx_rate(&last_rate, st->rate,
dev->mphy.chandef.chan->band);
for (i = 0; i < ARRAY_SIZE(info->status.rates); i++) {
retry--;
if (i + 1 == ARRAY_SIZE(info->status.rates)) {
info->status.rates[i] = last_rate;
info->status.rates[i].count = max_t(int, retry, 1);
break;
}
mt76x02_tx_rate_fallback(info->status.rates, i, phy);
if (info->status.rates[i].idx == last_rate.idx)
break;
}
if (i + 1 < ARRAY_SIZE(info->status.rates)) {
info->status.rates[i + 1].idx = -1;
info->status.rates[i + 1].count = 0;
}
info->status.ampdu_len = n_frames;
info->status.ampdu_ack_len = st->success ? n_frames : 0;
if (st->aggr)
info->flags |= IEEE80211_TX_CTL_AMPDU |
IEEE80211_TX_STAT_AMPDU;
if (!st->ack_req)
info->flags |= IEEE80211_TX_CTL_NO_ACK;
else if (st->success)
info->flags |= IEEE80211_TX_STAT_ACK;
}
void mt76x02_send_tx_status(struct mt76x02_dev *dev,
struct mt76x02_tx_status *stat, u8 *update)
{
struct ieee80211_tx_info info = {};
struct ieee80211_tx_status status = {
.info = &info
};
static const u8 ac_to_tid[4] = {
[IEEE80211_AC_BE] = 0,
[IEEE80211_AC_BK] = 1,
[IEEE80211_AC_VI] = 4,
[IEEE80211_AC_VO] = 6
};
struct mt76_wcid *wcid = NULL;
struct mt76x02_sta *msta = NULL;
struct mt76_dev *mdev = &dev->mt76;
struct sk_buff_head list;
u32 duration = 0;
u8 cur_pktid;
u32 ac = 0;
int len = 0;
if (stat->pktid == MT_PACKET_ID_NO_ACK)
return;
rcu_read_lock();
if (stat->wcid < MT76x02_N_WCIDS)
wcid = rcu_dereference(dev->mt76.wcid[stat->wcid]);
if (wcid && wcid->sta) {
void *priv;
priv = msta = container_of(wcid, struct mt76x02_sta, wcid);
status.sta = container_of(priv, struct ieee80211_sta,
drv_priv);
}
mt76_tx_status_lock(mdev, &list);
if (wcid) {
if (mt76_is_skb_pktid(stat->pktid))
status.skb = mt76_tx_status_skb_get(mdev, wcid,
stat->pktid, &list);
if (status.skb)
status.info = IEEE80211_SKB_CB(status.skb);
}
if (!status.skb && !(stat->pktid & MT_PACKET_ID_HAS_RATE)) {
mt76_tx_status_unlock(mdev, &list);
goto out;
}
if (msta && stat->aggr && !status.skb) {
u32 stat_val, stat_cache;
stat_val = stat->rate;
stat_val |= ((u32)stat->retry) << 16;
stat_cache = msta->status.rate;
stat_cache |= ((u32)msta->status.retry) << 16;
if (*update == 0 && stat_val == stat_cache &&
stat->wcid == msta->status.wcid && msta->n_frames < 32) {
msta->n_frames++;
mt76_tx_status_unlock(mdev, &list);
goto out;
}
cur_pktid = msta->status.pktid;
mt76x02_mac_fill_tx_status(dev, msta, status.info,
&msta->status, msta->n_frames);
msta->status = *stat;
msta->n_frames = 1;
*update = 0;
} else {
cur_pktid = stat->pktid;
mt76x02_mac_fill_tx_status(dev, msta, status.info, stat, 1);
*update = 1;
}
if (status.skb) {
info = *status.info;
len = status.skb->len;
ac = skb_get_queue_mapping(status.skb);
mt76_tx_status_skb_done(mdev, status.skb, &list);
} else if (msta) {
len = status.info->status.ampdu_len * ewma_pktlen_read(&msta->pktlen);
ac = FIELD_GET(MT_PKTID_AC, cur_pktid);
}
mt76_tx_status_unlock(mdev, &list);
if (!status.skb)
ieee80211_tx_status_ext(mt76_hw(dev), &status);
if (!len)
goto out;
duration = ieee80211_calc_tx_airtime(mt76_hw(dev), &info, len);
spin_lock_bh(&dev->mt76.cc_lock);
dev->tx_airtime += duration;
spin_unlock_bh(&dev->mt76.cc_lock);
if (msta)
ieee80211_sta_register_airtime(status.sta, ac_to_tid[ac], duration, 0);
out:
rcu_read_unlock();
}
static int
mt76x02_mac_process_rate(struct mt76x02_dev *dev,
struct mt76_rx_status *status,
u16 rate)
{
u8 idx = FIELD_GET(MT_RXWI_RATE_INDEX, rate);
switch (FIELD_GET(MT_RXWI_RATE_PHY, rate)) {
case MT_PHY_TYPE_OFDM:
if (idx >= 8)
idx = 0;
if (status->band == NL80211_BAND_2GHZ)
idx += 4;
status->rate_idx = idx;
return 0;
case MT_PHY_TYPE_CCK:
if (idx >= 8) {
idx -= 8;
status->enc_flags |= RX_ENC_FLAG_SHORTPRE;
}
if (idx >= 4)
idx = 0;
status->rate_idx = idx;
return 0;
case MT_PHY_TYPE_HT_GF:
status->enc_flags |= RX_ENC_FLAG_HT_GF;
fallthrough;
case MT_PHY_TYPE_HT:
status->encoding = RX_ENC_HT;
status->rate_idx = idx;
break;
case MT_PHY_TYPE_VHT: {
u8 n_rxstream = dev->mphy.chainmask & 0xf;
status->encoding = RX_ENC_VHT;
status->rate_idx = FIELD_GET(MT_RATE_INDEX_VHT_IDX, idx);
status->nss = min_t(u8, n_rxstream,
FIELD_GET(MT_RATE_INDEX_VHT_NSS, idx) + 1);
break;
}
default:
return -EINVAL;
}
if (rate & MT_RXWI_RATE_LDPC)
status->enc_flags |= RX_ENC_FLAG_LDPC;
if (rate & MT_RXWI_RATE_SGI)
status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
if (rate & MT_RXWI_RATE_STBC)
status->enc_flags |= 1 << RX_ENC_FLAG_STBC_SHIFT;
switch (FIELD_GET(MT_RXWI_RATE_BW, rate)) {
case MT_PHY_BW_20:
break;
case MT_PHY_BW_40:
status->bw = RATE_INFO_BW_40;
break;
case MT_PHY_BW_80:
status->bw = RATE_INFO_BW_80;
break;
default:
break;
}
return 0;
}
void mt76x02_mac_setaddr(struct mt76x02_dev *dev, const u8 *addr)
{
static const u8 null_addr[ETH_ALEN] = {};
int i;
ether_addr_copy(dev->mphy.macaddr, addr);
if (!is_valid_ether_addr(dev->mphy.macaddr)) {
eth_random_addr(dev->mphy.macaddr);
dev_info(dev->mt76.dev,
"Invalid MAC address, using random address %pM\n",
dev->mphy.macaddr);
}
mt76_wr(dev, MT_MAC_ADDR_DW0, get_unaligned_le32(dev->mphy.macaddr));
mt76_wr(dev, MT_MAC_ADDR_DW1,
get_unaligned_le16(dev->mphy.macaddr + 4) |
FIELD_PREP(MT_MAC_ADDR_DW1_U2ME_MASK, 0xff));
mt76_wr(dev, MT_MAC_BSSID_DW0,
get_unaligned_le32(dev->mphy.macaddr));
mt76_wr(dev, MT_MAC_BSSID_DW1,
get_unaligned_le16(dev->mphy.macaddr + 4) |
FIELD_PREP(MT_MAC_BSSID_DW1_MBSS_MODE, 3) | /* 8 APs + 8 STAs */
MT_MAC_BSSID_DW1_MBSS_LOCAL_BIT);
/* enable 7 additional beacon slots and control them with bypass mask */
mt76_rmw_field(dev, MT_MAC_BSSID_DW1, MT_MAC_BSSID_DW1_MBEACON_N, 7);
for (i = 0; i < 16; i++)
mt76x02_mac_set_bssid(dev, i, null_addr);
}
EXPORT_SYMBOL_GPL(mt76x02_mac_setaddr);
static int
mt76x02_mac_get_rssi(struct mt76x02_dev *dev, s8 rssi, int chain)
{
struct mt76x02_rx_freq_cal *cal = &dev->cal.rx;
rssi += cal->rssi_offset[chain];
rssi -= cal->lna_gain;
return rssi;
}
int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
void *rxi)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ieee80211_hdr *hdr;
struct mt76x02_rxwi *rxwi = rxi;
struct mt76x02_sta *sta;
u32 rxinfo = le32_to_cpu(rxwi->rxinfo);
u32 ctl = le32_to_cpu(rxwi->ctl);
u16 rate = le16_to_cpu(rxwi->rate);
u16 tid_sn = le16_to_cpu(rxwi->tid_sn);
bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST);
int pad_len = 0, nstreams = dev->mphy.chainmask & 0xf;
s8 signal;
u8 pn_len;
u8 wcid;
int len;
if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
return -EINVAL;
if (rxinfo & MT_RXINFO_L2PAD)
pad_len += 2;
if (rxinfo & MT_RXINFO_DECRYPT) {
status->flag |= RX_FLAG_DECRYPTED;
status->flag |= RX_FLAG_MMIC_STRIPPED;
status->flag |= RX_FLAG_MIC_STRIPPED;
status->flag |= RX_FLAG_IV_STRIPPED;
}
wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
sta = mt76x02_rx_get_sta(&dev->mt76, wcid);
status->wcid = mt76x02_rx_get_sta_wcid(sta, unicast);
len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl);
pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo);
if (pn_len) {
int offset = ieee80211_get_hdrlen_from_skb(skb) + pad_len;
u8 *data = skb->data + offset;
status->iv[0] = data[7];
status->iv[1] = data[6];
status->iv[2] = data[5];
status->iv[3] = data[4];
status->iv[4] = data[1];
status->iv[5] = data[0];
/*
* Driver CCMP validation can't deal with fragments.
* Let mac80211 take care of it.
*/
if (rxinfo & MT_RXINFO_FRAG) {
status->flag &= ~RX_FLAG_IV_STRIPPED;
} else {
pad_len += pn_len << 2;
len -= pn_len << 2;
}
}
mt76x02_remove_hdr_pad(skb, pad_len);
if ((rxinfo & MT_RXINFO_BA) && !(rxinfo & MT_RXINFO_NULL))
status->aggr = true;
if (rxinfo & MT_RXINFO_AMPDU) {
status->flag |= RX_FLAG_AMPDU_DETAILS;
status->ampdu_ref = dev->ampdu_ref;
/*
* When receiving an A-MPDU subframe and RSSI info is not valid,
* we can assume that more subframes belonging to the same A-MPDU
* are coming. The last one will have valid RSSI info
*/
if (rxinfo & MT_RXINFO_RSSI) {
if (!++dev->ampdu_ref)
dev->ampdu_ref++;
}
}
if (WARN_ON_ONCE(len > skb->len))
return -EINVAL;
pskb_trim(skb, len);
status->chains = BIT(0);
signal = mt76x02_mac_get_rssi(dev, rxwi->rssi[0], 0);
status->chain_signal[0] = signal;
if (nstreams > 1) {
status->chains |= BIT(1);
status->chain_signal[1] = mt76x02_mac_get_rssi(dev,
rxwi->rssi[1],
1);
}
status->freq = dev->mphy.chandef.chan->center_freq;
status->band = dev->mphy.chandef.chan->band;
hdr = (struct ieee80211_hdr *)skb->data;
status->qos_ctl = *ieee80211_get_qos_ctl(hdr);
status->seqno = FIELD_GET(MT_RXWI_SN, tid_sn);
return mt76x02_mac_process_rate(dev, status, rate);
}
void mt76x02_mac_poll_tx_status(struct mt76x02_dev *dev, bool irq)
{
struct mt76x02_tx_status stat = {};
u8 update = 1;
bool ret;
if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
return;
trace_mac_txstat_poll(dev);
while (!irq || !kfifo_is_full(&dev->txstatus_fifo)) {
if (!spin_trylock(&dev->txstatus_fifo_lock))
break;
ret = mt76x02_mac_load_tx_status(dev, &stat);
spin_unlock(&dev->txstatus_fifo_lock);
if (!ret)
break;
if (!irq) {
mt76x02_send_tx_status(dev, &stat, &update);
continue;
}
kfifo_put(&dev->txstatus_fifo, stat);
}
}
void mt76x02_tx_complete_skb(struct mt76_dev *mdev, struct mt76_queue_entry *e)
{
struct mt76x02_dev *dev = container_of(mdev, struct mt76x02_dev, mt76);
struct mt76x02_txwi *txwi;
u8 *txwi_ptr;
if (!e->txwi) {
dev_kfree_skb_any(e->skb);
return;
}
mt76x02_mac_poll_tx_status(dev, false);
txwi_ptr = mt76_get_txwi_ptr(mdev, e->txwi);
txwi = (struct mt76x02_txwi *)txwi_ptr;
trace_mac_txdone(mdev, txwi->wcid, txwi->pktid);
mt76_tx_complete_skb(mdev, e->wcid, e->skb);
}
EXPORT_SYMBOL_GPL(mt76x02_tx_complete_skb);
void mt76x02_mac_set_rts_thresh(struct mt76x02_dev *dev, u32 val)
{
u32 data = 0;
if (val != ~0)
data = FIELD_PREP(MT_PROT_CFG_CTRL, 1) |
MT_PROT_CFG_RTS_THRESH;
mt76_rmw_field(dev, MT_TX_RTS_CFG, MT_TX_RTS_CFG_THRESH, val);
mt76_rmw(dev, MT_CCK_PROT_CFG,
MT_PROT_CFG_CTRL | MT_PROT_CFG_RTS_THRESH, data);
mt76_rmw(dev, MT_OFDM_PROT_CFG,
MT_PROT_CFG_CTRL | MT_PROT_CFG_RTS_THRESH, data);
}
void mt76x02_mac_set_tx_protection(struct mt76x02_dev *dev, bool legacy_prot,
int ht_mode)
{
int mode = ht_mode & IEEE80211_HT_OP_MODE_PROTECTION;
bool non_gf = !!(ht_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT);
u32 prot[6];
u32 vht_prot[3];
int i;
u16 rts_thr;
for (i = 0; i < ARRAY_SIZE(prot); i++) {
prot[i] = mt76_rr(dev, MT_CCK_PROT_CFG + i * 4);
prot[i] &= ~MT_PROT_CFG_CTRL;
if (i >= 2)
prot[i] &= ~MT_PROT_CFG_RATE;
}
for (i = 0; i < ARRAY_SIZE(vht_prot); i++) {
vht_prot[i] = mt76_rr(dev, MT_TX_PROT_CFG6 + i * 4);
vht_prot[i] &= ~(MT_PROT_CFG_CTRL | MT_PROT_CFG_RATE);
}
rts_thr = mt76_get_field(dev, MT_TX_RTS_CFG, MT_TX_RTS_CFG_THRESH);
if (rts_thr != 0xffff)
prot[0] |= MT_PROT_CTRL_RTS_CTS;
if (legacy_prot) {
prot[1] |= MT_PROT_CTRL_CTS2SELF;
prot[2] |= MT_PROT_RATE_CCK_11;
prot[3] |= MT_PROT_RATE_CCK_11;
prot[4] |= MT_PROT_RATE_CCK_11;
prot[5] |= MT_PROT_RATE_CCK_11;
vht_prot[0] |= MT_PROT_RATE_CCK_11;
vht_prot[1] |= MT_PROT_RATE_CCK_11;
vht_prot[2] |= MT_PROT_RATE_CCK_11;
} else {
if (rts_thr != 0xffff)
prot[1] |= MT_PROT_CTRL_RTS_CTS;
prot[2] |= MT_PROT_RATE_OFDM_24;
prot[3] |= MT_PROT_RATE_DUP_OFDM_24;
prot[4] |= MT_PROT_RATE_OFDM_24;
prot[5] |= MT_PROT_RATE_DUP_OFDM_24;
vht_prot[0] |= MT_PROT_RATE_OFDM_24;
vht_prot[1] |= MT_PROT_RATE_DUP_OFDM_24;
vht_prot[2] |= MT_PROT_RATE_SGI_OFDM_24;
}
switch (mode) {
case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER:
case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED:
prot[2] |= MT_PROT_CTRL_RTS_CTS;
prot[3] |= MT_PROT_CTRL_RTS_CTS;
prot[4] |= MT_PROT_CTRL_RTS_CTS;
prot[5] |= MT_PROT_CTRL_RTS_CTS;
vht_prot[0] |= MT_PROT_CTRL_RTS_CTS;
vht_prot[1] |= MT_PROT_CTRL_RTS_CTS;
vht_prot[2] |= MT_PROT_CTRL_RTS_CTS;
break;
case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ:
prot[3] |= MT_PROT_CTRL_RTS_CTS;
prot[5] |= MT_PROT_CTRL_RTS_CTS;
vht_prot[1] |= MT_PROT_CTRL_RTS_CTS;
vht_prot[2] |= MT_PROT_CTRL_RTS_CTS;
break;
}
if (non_gf) {
prot[4] |= MT_PROT_CTRL_RTS_CTS;
prot[5] |= MT_PROT_CTRL_RTS_CTS;
}
for (i = 0; i < ARRAY_SIZE(prot); i++)
mt76_wr(dev, MT_CCK_PROT_CFG + i * 4, prot[i]);
for (i = 0; i < ARRAY_SIZE(vht_prot); i++)
mt76_wr(dev, MT_TX_PROT_CFG6 + i * 4, vht_prot[i]);
}
void mt76x02_update_channel(struct mt76_phy *mphy)
{
struct mt76x02_dev *dev = container_of(mphy->dev, struct mt76x02_dev, mt76);
struct mt76_channel_state *state;
state = mphy->chan_state;
state->cc_busy += mt76_rr(dev, MT_CH_BUSY);
spin_lock_bh(&dev->mt76.cc_lock);
state->cc_tx += dev->tx_airtime;
dev->tx_airtime = 0;
spin_unlock_bh(&dev->mt76.cc_lock);
}
EXPORT_SYMBOL_GPL(mt76x02_update_channel);
static void mt76x02_check_mac_err(struct mt76x02_dev *dev)
{
if (dev->mt76.beacon_mask) {
if (mt76_rr(dev, MT_TX_STA_0) & MT_TX_STA_0_BEACONS) {
dev->beacon_hang_check = 0;
return;
}
if (++dev->beacon_hang_check < 10)
return;
dev->beacon_hang_check = 0;
} else {
u32 val = mt76_rr(dev, 0x10f4);
if (!(val & BIT(29)) || !(val & (BIT(7) | BIT(5))))
return;
}
dev_err(dev->mt76.dev, "MAC error detected\n");
mt76_wr(dev, MT_MAC_SYS_CTRL, 0);
mt76x02_wait_for_txrx_idle(&dev->mt76);
mt76_set(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_RESET_CSR);
udelay(10);
mt76_wr(dev, MT_MAC_SYS_CTRL,
MT_MAC_SYS_CTRL_ENABLE_TX | MT_MAC_SYS_CTRL_ENABLE_RX);
}
static void
mt76x02_edcca_tx_enable(struct mt76x02_dev *dev, bool enable)
{
if (enable) {
u32 data;
mt76_set(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_TX);
mt76_set(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_EN);
/* enable pa-lna */
data = mt76_rr(dev, MT_TX_PIN_CFG);
data |= MT_TX_PIN_CFG_TXANT |
MT_TX_PIN_CFG_RXANT |
MT_TX_PIN_RFTR_EN |
MT_TX_PIN_TRSW_EN;
mt76_wr(dev, MT_TX_PIN_CFG, data);
} else {
mt76_clear(dev, MT_MAC_SYS_CTRL, MT_MAC_SYS_CTRL_ENABLE_TX);
mt76_clear(dev, MT_AUTO_RSP_CFG, MT_AUTO_RSP_EN);
/* disable pa-lna */
mt76_clear(dev, MT_TX_PIN_CFG, MT_TX_PIN_CFG_TXANT);
mt76_clear(dev, MT_TX_PIN_CFG, MT_TX_PIN_CFG_RXANT);
}
dev->ed_tx_blocked = !enable;
}
void mt76x02_edcca_init(struct mt76x02_dev *dev)
{
dev->ed_trigger = 0;
dev->ed_silent = 0;
if (dev->ed_monitor) {
struct ieee80211_channel *chan = dev->mphy.chandef.chan;
u8 ed_th = chan->band == NL80211_BAND_5GHZ ? 0x0e : 0x20;
mt76_clear(dev, MT_TX_LINK_CFG, MT_TX_CFACK_EN);
mt76_set(dev, MT_TXOP_CTRL_CFG, MT_TXOP_ED_CCA_EN);
mt76_rmw(dev, MT_BBP(AGC, 2), GENMASK(15, 0),
ed_th << 8 | ed_th);
mt76_set(dev, MT_TXOP_HLDR_ET, MT_TXOP_HLDR_TX40M_BLK_EN);
} else {
mt76_set(dev, MT_TX_LINK_CFG, MT_TX_CFACK_EN);
mt76_clear(dev, MT_TXOP_CTRL_CFG, MT_TXOP_ED_CCA_EN);
if (is_mt76x2(dev)) {
mt76_wr(dev, MT_BBP(AGC, 2), 0x00007070);
mt76_set(dev, MT_TXOP_HLDR_ET,
MT_TXOP_HLDR_TX40M_BLK_EN);
} else {
mt76_wr(dev, MT_BBP(AGC, 2), 0x003a6464);
mt76_clear(dev, MT_TXOP_HLDR_ET,
MT_TXOP_HLDR_TX40M_BLK_EN);
}
}
mt76x02_edcca_tx_enable(dev, true);
dev->ed_monitor_learning = true;
/* clear previous CCA timer value */
mt76_rr(dev, MT_ED_CCA_TIMER);
dev->ed_time = ktime_get_boottime();
}
EXPORT_SYMBOL_GPL(mt76x02_edcca_init);
#define MT_EDCCA_TH 92
#define MT_EDCCA_BLOCK_TH 2
#define MT_EDCCA_LEARN_TH 50
#define MT_EDCCA_LEARN_CCA 180
#define MT_EDCCA_LEARN_TIMEOUT (20 * HZ)
static void mt76x02_edcca_check(struct mt76x02_dev *dev)
{
ktime_t cur_time;
u32 active, val, busy;
cur_time = ktime_get_boottime();
val = mt76_rr(dev, MT_ED_CCA_TIMER);
active = ktime_to_us(ktime_sub(cur_time, dev->ed_time));
dev->ed_time = cur_time;
busy = (val * 100) / active;
busy = min_t(u32, busy, 100);
if (busy > MT_EDCCA_TH) {
dev->ed_trigger++;
dev->ed_silent = 0;
} else {
dev->ed_silent++;
dev->ed_trigger = 0;
}
if (dev->cal.agc_lowest_gain &&
dev->cal.false_cca > MT_EDCCA_LEARN_CCA &&
dev->ed_trigger > MT_EDCCA_LEARN_TH) {
dev->ed_monitor_learning = false;
dev->ed_trigger_timeout = jiffies + 20 * HZ;
} else if (!dev->ed_monitor_learning &&
time_is_after_jiffies(dev->ed_trigger_timeout)) {
dev->ed_monitor_learning = true;
mt76x02_edcca_tx_enable(dev, true);
}
if (dev->ed_monitor_learning)
return;
if (dev->ed_trigger > MT_EDCCA_BLOCK_TH && !dev->ed_tx_blocked)
mt76x02_edcca_tx_enable(dev, false);
else if (dev->ed_silent > MT_EDCCA_BLOCK_TH && dev->ed_tx_blocked)
mt76x02_edcca_tx_enable(dev, true);
}
void mt76x02_mac_work(struct work_struct *work)
{
struct mt76x02_dev *dev = container_of(work, struct mt76x02_dev,
mphy.mac_work.work);
int i, idx;
mutex_lock(&dev->mt76.mutex);
mt76_update_survey(&dev->mphy);
for (i = 0, idx = 0; i < 16; i++) {
u32 val = mt76_rr(dev, MT_TX_AGG_CNT(i));
dev->mt76.aggr_stats[idx++] += val & 0xffff;
dev->mt76.aggr_stats[idx++] += val >> 16;
}
mt76x02_check_mac_err(dev);
if (dev->ed_monitor)
mt76x02_edcca_check(dev);
mutex_unlock(&dev->mt76.mutex);
mt76_tx_status_check(&dev->mt76, false);
ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mphy.mac_work,
MT_MAC_WORK_INTERVAL);
}
void mt76x02_mac_cc_reset(struct mt76x02_dev *dev)
{
dev->mphy.survey_time = ktime_get_boottime();
mt76_wr(dev, MT_CH_TIME_CFG,
MT_CH_TIME_CFG_TIMER_EN |
MT_CH_TIME_CFG_TX_AS_BUSY |
MT_CH_TIME_CFG_RX_AS_BUSY |
MT_CH_TIME_CFG_NAV_AS_BUSY |
MT_CH_TIME_CFG_EIFS_AS_BUSY |
MT_CH_CCA_RC_EN |
FIELD_PREP(MT_CH_TIME_CFG_CH_TIMER_CLR, 1));
/* channel cycle counters read-and-clear */
mt76_rr(dev, MT_CH_BUSY);
mt76_rr(dev, MT_CH_IDLE);
}
EXPORT_SYMBOL_GPL(mt76x02_mac_cc_reset);
void mt76x02_mac_set_bssid(struct mt76x02_dev *dev, u8 idx, const u8 *addr)
{
idx &= 7;
mt76_wr(dev, MT_MAC_APC_BSSID_L(idx), get_unaligned_le32(addr));
mt76_rmw_field(dev, MT_MAC_APC_BSSID_H(idx), MT_MAC_APC_BSSID_H_ADDR,
get_unaligned_le16(addr + 4));
}