// SPDX-License-Identifier: ISC /* * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
switch (key->cipher) { case WLAN_CIPHER_SUITE_CCMP:
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_CCM];
key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV_MGMT; break; case WLAN_CIPHER_SUITE_TKIP:
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_TKIP];
arg.key_txmic_len = 8;
arg.key_rxmic_len = 8; break; case WLAN_CIPHER_SUITE_WEP40: case WLAN_CIPHER_SUITE_WEP104:
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_WEP]; break; case WLAN_CIPHER_SUITE_CCMP_256:
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_CCM]; break; case WLAN_CIPHER_SUITE_GCMP: case WLAN_CIPHER_SUITE_GCMP_256:
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_AES_GCM];
key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV_MGMT; break; case WLAN_CIPHER_SUITE_BIP_GMAC_128: case WLAN_CIPHER_SUITE_BIP_GMAC_256: case WLAN_CIPHER_SUITE_BIP_CMAC_256: case WLAN_CIPHER_SUITE_AES_CMAC:
WARN_ON(1); return -EINVAL; default:
ath10k_warn(ar, "cipher %d is not supported\n", key->cipher); return -EOPNOTSUPP;
}
if (test_bit(ATH10K_FLAG_RAW_MODE, &ar->dev_flags))
key->flags |= IEEE80211_KEY_FLAG_GENERATE_IV;
if (cmd == DISABLE_KEY) { if (flags & WMI_KEY_GROUP) { /* Not all hardware handles group-key deletion operation * correctly. Replace the key with a junk value to invalidate it.
*/
get_random_bytes(key->key, key->keylen);
} else {
arg.key_cipher = ar->wmi_key_cipher[WMI_CIPHER_NONE];
arg.key_data = NULL;
}
}
/* In some cases (notably with static WEP IBSS with multiple keys) * multicast Tx becomes broken. Both pairwise and groupwise keys are * installed already. Using WMI_KEY_TX_USAGE in different combinations * didn't seem help. Using def_keyid vdev parameter seems to be * effective so use that. * * FIXME: Revisit. Perhaps this can be done in a less hacky way.
*/ if (arvif->vif->type != NL80211_IFTYPE_ADHOC) return 0;
if (arvif->def_wep_key_idx == -1) return 0;
ret = ath10k_wmi_vdev_set_param(arvif->ar,
arvif->vdev_id,
arvif->ar->wmi.vdev_param->def_keyid,
arvif->def_wep_key_idx); if (ret) {
ath10k_warn(ar, "failed to re-set def wpa key idxon vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
return 0;
}
staticint ath10k_clear_peer_keys(struct ath10k_vif *arvif, const u8 *addr)
{ struct ath10k *ar = arvif->ar; struct ath10k_peer *peer; int first_errno = 0; int ret; int i;
u32 flags = 0;
for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { if (peer->keys[i] == NULL) continue;
/* key flags are not required to delete the key */
ret = ath10k_install_key(arvif, peer->keys[i],
DISABLE_KEY, addr, flags); if (ret < 0 && first_errno == 0)
first_errno = ret;
if (ret < 0)
ath10k_warn(ar, "failed to remove peer wep key %d: %d\n",
i, ret);
/* We don't know which vdev this peer belongs to, * since WMI doesn't give us that information. * * FIXME: multi-bss needs to be handled.
*/
peer = ath10k_peer_find(ar, 0, addr); if (!peer) returnfalse;
for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { if (peer->keys[i] && peer->keys[i]->keyidx == keyidx) returntrue;
}
returnfalse;
}
staticint ath10k_clear_vdev_key(struct ath10k_vif *arvif, struct ieee80211_key_conf *key)
{ struct ath10k *ar = arvif->ar; struct ath10k_peer *peer;
u8 addr[ETH_ALEN]; int first_errno = 0; int ret; int i;
u32 flags = 0;
lockdep_assert_held(&ar->conf_mutex);
for (;;) { /* since ath10k_install_key we can't hold data_lock all the * time, so we try to remove the keys incrementally
*/
spin_lock_bh(&ar->data_lock);
i = 0;
list_for_each_entry(peer, &ar->peers, list) { for (i = 0; i < ARRAY_SIZE(peer->keys); i++) { if (peer->keys[i] == key) {
ether_addr_copy(addr, peer->addr);
peer->keys[i] = NULL; break;
}
}
if (i < ARRAY_SIZE(peer->keys)) break;
}
spin_unlock_bh(&ar->data_lock);
if (i == ARRAY_SIZE(peer->keys)) break; /* key flags are not required to delete the key */
ret = ath10k_install_key(arvif, key, DISABLE_KEY, addr, flags); if (ret < 0 && first_errno == 0)
first_errno = ret;
if (ret)
ath10k_warn(ar, "failed to remove key for %pM: %d\n",
addr, ret);
}
static u8 ath10k_parse_mpdudensity(u8 mpdudensity)
{ /* * 802.11n D2.0 defined values for "Minimum MPDU Start Spacing": * 0 for no restriction * 1 for 1/4 us * 2 for 1/2 us * 3 for 1 us * 4 for 2 us * 5 for 4 us * 6 for 8 us * 7 for 16 us
*/ switch (mpdudensity) { case 0: return 0; case 1: case 2: case 3: /* Our lower layer calculations limit our precision to * 1 microsecond
*/ return 1; case 4: return 2; case 5: return 4; case 6: return 8; case 7: return 16; default: return 0;
}
}
if (test_bit(WMI_SERVICE_SYNC_DELETE_CMDS, ar->wmi.svc_map)) {
ret = ath10k_wait_for_peer_deleted(ar, vdev_id, addr); if (ret) {
ath10k_warn(ar, "failed wait for peer deleted"); return;
}
time_left = wait_for_completion_timeout(&ar->peer_delete_done,
5 * HZ); if (!time_left)
ath10k_warn(ar, "Timeout in receiving peer delete response\n");
}
}
/* Each vdev consumes a peer entry as well. */ if (ar->num_peers + list_count_nodes(&ar->arvifs) >= ar->max_num_peers) return -ENOBUFS;
ret = ath10k_wmi_peer_create(ar, vdev_id, addr, peer_type); if (ret) {
ath10k_warn(ar, "failed to create wmi peer %pM on vdev %i: %i\n",
addr, vdev_id, ret); return ret;
}
ret = ath10k_wait_for_peer_created(ar, vdev_id, addr); if (ret) {
ath10k_warn(ar, "failed to wait for created wmi peer %pM on vdev %i: %i\n",
addr, vdev_id, ret); return ret;
}
spin_lock_bh(&ar->data_lock);
peer = ath10k_peer_find(ar, vdev_id, addr); if (!peer) {
spin_unlock_bh(&ar->data_lock);
ath10k_warn(ar, "failed to find peer %pM on vdev %i after creation\n",
addr, vdev_id);
ath10k_wait_for_peer_delete_done(ar, vdev_id, addr); return -ENOENT;
}
param = ar->wmi.pdev_param->sta_kickout_th;
ret = ath10k_wmi_pdev_set_param(ar, param,
ATH10K_KICKOUT_THRESHOLD); if (ret) {
ath10k_warn(ar, "failed to set kickout threshold on vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
param = ar->wmi.vdev_param->ap_keepalive_min_idle_inactive_time_secs;
ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param,
ATH10K_KEEPALIVE_MIN_IDLE); if (ret) {
ath10k_warn(ar, "failed to set keepalive minimum idle time on vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
param = ar->wmi.vdev_param->ap_keepalive_max_idle_inactive_time_secs;
ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param,
ATH10K_KEEPALIVE_MAX_IDLE); if (ret) {
ath10k_warn(ar, "failed to set keepalive maximum idle time on vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
param = ar->wmi.vdev_param->ap_keepalive_max_unresponsive_time_secs;
ret = ath10k_wmi_vdev_set_param(ar, arvif->vdev_id, param,
ATH10K_KEEPALIVE_MAX_UNRESPONSIVE); if (ret) {
ath10k_warn(ar, "failed to set keepalive maximum unresponsive time on vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
/* Double check that peer is properly un-referenced from * the peer_map
*/ for (i = 0; i < ARRAY_SIZE(ar->peer_map); i++) { if (ar->peer_map[i] == peer) {
ath10k_warn(ar, "removing stale peer_map entry for %pM (ptr %p idx %d)\n",
peer->addr, peer, i);
ar->peer_map[i] = NULL;
}
}
/* TODO setup this dynamically, what in case we * don't have any vifs?
*/
arg.channel.mode = chan_to_phymode(chandef);
arg.channel.chan_radar =
!!(channel->flags & IEEE80211_CHAN_RADAR);
ret = ath10k_wmi_vdev_start(ar, &arg); if (ret) {
ath10k_warn(ar, "failed to request monitor vdev %i start: %d\n",
vdev_id, ret); return ret;
}
ret = ath10k_vdev_setup_sync(ar); if (ret) {
ath10k_warn(ar, "failed to synchronize setup for monitor vdev %i start: %d\n",
vdev_id, ret); return ret;
}
ret = ath10k_wmi_vdev_up(ar, vdev_id, 0, ar->mac_addr); if (ret) {
ath10k_warn(ar, "failed to put up monitor vdev %i: %d\n",
vdev_id, ret); goto vdev_stop;
}
vdev_stop:
ret = ath10k_wmi_vdev_stop(ar, ar->monitor_vdev_id); if (ret)
ath10k_warn(ar, "failed to stop monitor vdev %i after start failure: %d\n",
ar->monitor_vdev_id, ret);
return ret;
}
staticint ath10k_monitor_vdev_stop(struct ath10k *ar)
{ int ret = 0;
lockdep_assert_held(&ar->conf_mutex);
ret = ath10k_wmi_vdev_down(ar, ar->monitor_vdev_id); if (ret)
ath10k_warn(ar, "failed to put down monitor vdev %i: %d\n",
ar->monitor_vdev_id, ret);
staticbool ath10k_mac_monitor_vdev_is_needed(struct ath10k *ar)
{ int num_ctx;
/* At least one chanctx is required to derive a channel to start * monitor vdev on.
*/
num_ctx = ath10k_mac_num_chanctxs(ar); if (num_ctx == 0) returnfalse;
/* If there's already an existing special monitor interface then don't * bother creating another monitor vdev.
*/ if (ar->monitor_arvif) returnfalse;
staticbool ath10k_mac_monitor_vdev_is_allowed(struct ath10k *ar)
{ int num_ctx;
num_ctx = ath10k_mac_num_chanctxs(ar);
/* FIXME: Current interface combinations and cfg80211/mac80211 code * shouldn't allow this but make sure to prevent handling the following * case anyway since multi-channel DFS hasn't been tested at all.
*/ if (test_bit(ATH10K_CAC_RUNNING, &ar->dev_flags) && num_ctx > 1) returnfalse;
staticvoid ath10k_recalc_radar_detection(struct ath10k *ar)
{ int ret;
lockdep_assert_held(&ar->conf_mutex);
ath10k_stop_cac(ar);
if (!ath10k_mac_has_radar_enabled(ar)) return;
if (ar->num_started_vdevs > 0) return;
ret = ath10k_start_cac(ar); if (ret) { /* * Not possible to start CAC on current channel so starting * radiation is not allowed, make this channel DFS_UNAVAILABLE * by indicating that radar was detected.
*/
ath10k_warn(ar, "failed to start CAC: %d\n", ret);
ieee80211_radar_detected(ar->hw, NULL);
}
}
ret = ath10k_wmi_p2p_go_bcn_ie(ar, arvif->vdev_id, p2p_ie); if (ret) {
ath10k_warn(ar, "failed to submit p2p go bcn ie for vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
if (!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map)) return 0;
if (arvif->vdev_type != WMI_VDEV_TYPE_AP &&
arvif->vdev_type != WMI_VDEV_TYPE_IBSS) return 0;
bcn = ieee80211_beacon_get_template(hw, vif, &offs, 0); if (!bcn) {
ath10k_warn(ar, "failed to get beacon template from mac80211\n"); return -EPERM;
}
ret = ath10k_mac_setup_bcn_p2p_ie(arvif, bcn); if (ret) {
ath10k_warn(ar, "failed to setup p2p go bcn ie: %d\n", ret);
kfree_skb(bcn); return ret;
}
/* P2P IE is inserted by firmware automatically (as configured above) * so remove it from the base beacon template to avoid duplicate P2P * IEs in beacon frames.
*/
ath10k_mac_remove_vendor_ie(bcn, WLAN_OUI_WFA, WLAN_OUI_TYPE_WFA_P2P,
offsetof(struct ieee80211_mgmt,
u.beacon.variable));
/* When originally vdev is started during assign_vif_chanctx() some * information is missing, notably SSID. Firmware revisions with beacon * offloading require the SSID to be provided during vdev (re)start to * handle hidden SSID properly. * * Vdev restart must be done after vdev has been both started and * upped. Otherwise some firmware revisions (at least 10.2) fail to * deliver vdev restart response event causing timeouts during vdev * syncing in ath10k. * * Note: The vdev down/up and template reinstallation could be skipped * since only wmi-tlv firmware are known to have beacon offload and * wmi-tlv doesn't seem to misbehave like 10.2 wrt vdev restart * response delivery. It's probably more robust to keep it as is.
*/ if (!test_bit(WMI_SERVICE_BEACON_OFFLOAD, ar->wmi.svc_map)) return 0;
if (WARN_ON(!arvif->is_started)) return -EINVAL;
if (WARN_ON(!arvif->is_up)) return -EINVAL;
if (WARN_ON(ath10k_mac_vif_chan(arvif->vif, &def))) return -EINVAL;
ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); if (ret) {
ath10k_warn(ar, "failed to bring down ap vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
/* Vdev down reset beacon & presp templates. Reinstall them. Otherwise * firmware will crash upon vdev up.
*/
ret = ath10k_mac_setup_bcn_tmpl(arvif); if (ret) {
ath10k_warn(ar, "failed to update beacon template: %d\n", ret); return ret;
}
ret = ath10k_mac_setup_prb_tmpl(arvif); if (ret) {
ath10k_warn(ar, "failed to update presp template: %d\n", ret); return ret;
}
ret = ath10k_vdev_restart(arvif, &def); if (ret) {
ath10k_warn(ar, "failed to restart ap vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
ret = ath10k_wmi_vdev_up(arvif->ar, arvif->vdev_id, arvif->aid,
arvif->bssid); if (ret) {
ath10k_warn(ar, "failed to bring up ap vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
return 0;
}
staticvoid ath10k_control_beaconing(struct ath10k_vif *arvif, struct ieee80211_bss_conf *info)
{ struct ath10k *ar = arvif->ar; int ret = 0;
lockdep_assert_held(&arvif->ar->conf_mutex);
if (!info->enable_beacon) {
ret = ath10k_wmi_vdev_down(ar, arvif->vdev_id); if (ret)
ath10k_warn(ar, "failed to down vdev_id %i: %d\n",
arvif->vdev_id, ret);
staticvoid ath10k_control_ibss(struct ath10k_vif *arvif, struct ieee80211_vif *vif)
{ struct ath10k *ar = arvif->ar;
u32 vdev_param; int ret = 0;
lockdep_assert_held(&arvif->ar->conf_mutex);
if (!vif->cfg.ibss_joined) { if (is_zero_ether_addr(arvif->bssid)) return;
eth_zero_addr(arvif->bssid);
return;
}
vdev_param = arvif->ar->wmi.vdev_param->atim_window;
ret = ath10k_wmi_vdev_set_param(arvif->ar, arvif->vdev_id, vdev_param,
ATH10K_DEFAULT_ATIM); if (ret)
ath10k_warn(ar, "failed to set IBSS ATIM for vdev %d: %d\n",
arvif->vdev_id, ret);
}
if (arvif->vif->type != NL80211_IFTYPE_STATION) return 0;
enable_ps = arvif->ps;
if (enable_ps && ath10k_mac_num_vifs_started(ar) > 1 &&
!test_bit(ATH10K_FW_FEATURE_MULTI_VIF_PS_SUPPORT,
ar->running_fw->fw_file.fw_features)) {
ath10k_warn(ar, "refusing to enable ps on vdev %i: not supported by fw\n",
arvif->vdev_id);
enable_ps = false;
}
if (!arvif->is_started) { /* mac80211 can update vif powersave state while disconnected. * Firmware doesn't behave nicely and consumes more power than * necessary if PS is disabled on a non-started vdev. Hence * force-enable PS for non-running vdevs.
*/
psmode = WMI_STA_PS_MODE_ENABLED;
} elseif (enable_ps) {
psmode = WMI_STA_PS_MODE_ENABLED;
param = WMI_STA_PS_PARAM_INACTIVITY_TIME;
ret = ath10k_wmi_set_psmode(ar, arvif->vdev_id, psmode); if (ret) {
ath10k_warn(ar, "failed to set PS Mode %d for vdev %d: %d\n",
psmode, arvif->vdev_id, ret); return ret;
}
if (arvif->vdev_type != WMI_VDEV_TYPE_STA) return 0;
if (!test_bit(WMI_SERVICE_STA_KEEP_ALIVE, ar->wmi.svc_map)) return 0;
/* Some firmware revisions have a bug and ignore the `enabled` field. * Instead use the interval to disable the keepalive.
*/
arg.vdev_id = arvif->vdev_id;
arg.enabled = 1;
arg.method = WMI_STA_KEEPALIVE_METHOD_NULL_FRAME;
arg.interval = WMI_STA_KEEPALIVE_INTERVAL_DISABLE;
ret = ath10k_wmi_sta_keepalive(ar, &arg); if (ret) {
ath10k_warn(ar, "failed to submit keepalive on vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
/* Firmware doesn't report beacon loss events repeatedly. If AP probe * (done by mac80211) succeeds but beacons do not resume then it * doesn't make sense to continue operation. Queue connection loss work * which can be cancelled when beacon is received.
*/
ieee80211_queue_delayed_work(hw, &arvif->connection_loss_work,
ATH10K_CONNECTION_LOSS_HZ);
}
/**********************/ /* Station management */ /**********************/
static u32 ath10k_peer_assoc_h_listen_intval(struct ath10k *ar, struct ieee80211_vif *vif)
{ /* Some firmware revisions have unstable STA powersave when listen * interval is set too high (e.g. 5). The symptoms are firmware doesn't * generate NullFunc frames properly even if buffered frames have been * indicated in Beacon TIM. Firmware would seldom wake up to pull * buffered frames. Often pinging the device from AP would simply fail. * * As a workaround set it to 1.
*/ if (vif->type == NL80211_IFTYPE_STATION) return 1;
for (i = 0, n = 0, max_nss = 0; i < IEEE80211_HT_MCS_MASK_LEN * 8; i++) if ((ht_cap->mcs.rx_mask[i / 8] & BIT(i % 8)) &&
(ht_mcs_mask[i / 8] & BIT(i % 8))) {
max_nss = (i / 8) + 1;
arg->peer_ht_rates.rates[n++] = i;
}
/* * This is a workaround for HT-enabled STAs which break the spec * and have no HT capabilities RX mask (no HT RX MCS map). * * As per spec, in section 20.3.5 Modulation and coding scheme (MCS), * MCS 0 through 7 are mandatory in 20MHz with 800 ns GI at all STAs. * * Firmware asserts if such situation occurs.
*/ if (n == 0) {
arg->peer_ht_rates.num_rates = 8; for (i = 0; i < arg->peer_ht_rates.num_rates; i++)
arg->peer_ht_rates.rates[i] = i;
} else {
arg->peer_ht_rates.num_rates = n;
arg->peer_num_spatial_streams = min(sta->deflink.rx_nss,
max_nss);
}
if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VO)
uapsd |= WMI_AP_PS_UAPSD_AC3_DELIVERY_EN |
WMI_AP_PS_UAPSD_AC3_TRIGGER_EN; if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_VI)
uapsd |= WMI_AP_PS_UAPSD_AC2_DELIVERY_EN |
WMI_AP_PS_UAPSD_AC2_TRIGGER_EN; if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BK)
uapsd |= WMI_AP_PS_UAPSD_AC1_DELIVERY_EN |
WMI_AP_PS_UAPSD_AC1_TRIGGER_EN; if (sta->uapsd_queues & IEEE80211_WMM_IE_STA_QOSINFO_AC_BE)
uapsd |= WMI_AP_PS_UAPSD_AC0_DELIVERY_EN |
WMI_AP_PS_UAPSD_AC0_TRIGGER_EN;
if (sta->max_sp < MAX_WMI_AP_PS_PEER_PARAM_MAX_SP)
max_sp = sta->max_sp;
ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id,
sta->addr,
WMI_AP_PS_PEER_PARAM_UAPSD,
uapsd); if (ret) {
ath10k_warn(ar, "failed to set ap ps peer param uapsd for vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id,
sta->addr,
WMI_AP_PS_PEER_PARAM_MAX_SP,
max_sp); if (ret) {
ath10k_warn(ar, "failed to set ap ps peer param max sp for vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
/* TODO setup this based on STA listen interval and * beacon interval. Currently we don't know * sta->listen_interval - mac80211 patch required. * Currently use 10 seconds
*/
ret = ath10k_wmi_set_ap_ps_param(ar, arvif->vdev_id, sta->addr,
WMI_AP_PS_PEER_PARAM_AGEOUT_TIME,
10); if (ret) {
ath10k_warn(ar, "failed to set ap ps peer param ageout time for vdev %i: %d\n",
arvif->vdev_id, ret); return ret;
}
}
return 0;
}
static u16
ath10k_peer_assoc_h_vht_limit(u16 tx_mcs_set, const u16 vht_mcs_limit[NL80211_VHT_NSS_MAX])
{ int idx_limit; int nss;
u16 mcs_map;
u16 mcs;
switch (idx_limit) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: default: /* see ath10k_mac_can_set_bitrate_mask() */
WARN_ON(1);
fallthrough; case -1:
mcs = IEEE80211_VHT_MCS_NOT_SUPPORTED; break; case 7:
mcs = IEEE80211_VHT_MCS_SUPPORT_0_7; break; case 8:
mcs = IEEE80211_VHT_MCS_SUPPORT_0_8; break; case 9:
mcs = IEEE80211_VHT_MCS_SUPPORT_0_9; break;
}
/* Workaround: Some Netgear/Linksys 11ac APs set Rx A-MPDU factor to * zero in VHT IE. Using it would result in degraded throughput. * arg->peer_max_mpdu at this point contains HT max_mpdu so keep * it if VHT max_mpdu is smaller.
*/
arg->peer_max_mpdu = max(arg->peer_max_mpdu,
(1U << (IEEE80211_HT_MAX_AMPDU_FACTOR +
ampdu_factor)) - 1);
if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_80)
arg->peer_flags |= ar->wmi.peer_flags->bw80;
if (sta->deflink.bandwidth == IEEE80211_STA_RX_BW_160)
arg->peer_flags |= ar->wmi.peer_flags->bw160;
/* Calculate peer NSS capability from VHT capabilities if STA * supports VHT.
*/ for (i = 0, max_nss = 0, vht_mcs = 0; i < NL80211_VHT_NSS_MAX; i++) {
vht_mcs = __le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map) >>
(2 * i) & 3;
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.