| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright (c) 2017-2026 Morse Micro | |
| */ | |
| #include "core.h" | |
| #include <linux/slab.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/crc32.h> | |
| #include <net/mac80211.h> | |
| #include <asm/div64.h> | |
| #include <linux/kernel.h> | |
| #include "hif.h" | |
| #include "mac.h" | |
| #include "bus.h" | |
| #include "ps.h" | |
| #include "rc.h" | |
| /* | |
| * Arbitrary size limit for the filter command address list, to ensure that | |
| * the command does not exceed page/MTU size. This will be far greater than | |
| * the number of filters supported by the firmware. | |
| */ | |
| #define MCAST_FILTER_COUNT_MAX (1024 / sizeof(filter->addr_list[0])) | |
| /* Calculate average RSSI for Rx status */ | |
| #define CALC_AVG_RSSI(_avg, _sample) ((((_avg) * 9 + (_sample)) / 10)) | |
| /* | |
| * When automatically trying MCS0 before MCS10, this is how many | |
| * MCS0 attempts to make | |
| */ | |
| #define MCS0_BEFORE_MCS10_COUNT (1) | |
| /* Maximum TX power (default) */ | |
| #define MAX_TX_POWER_MBM (2200) | |
| /* | |
| * Since S1G runs at 1/10th the clockrate of VHT, the worst-case | |
| * transmission time is significantly longer then that of non-S1G | |
| * PHYs. | |
| */ | |
| #define MM81X_FLUSH_TIMEOUT (16 * HZ) | |
| /* Default queue count */ | |
| #define MM81X_HW_QUEUE_COUNT (4) | |
| /* Max rates per skb */ | |
| #define MM81X_HW_MAX_RATES (4) | |
| /* Max reported rates */ | |
| #define MM81X_HW_MAX_REPORT_RATES (4) | |
| /* Max rate attempts */ | |
| #define MM81X_HW_MAX_RATE_TRIES (1) | |
| /* Max sk pacing shift */ | |
| #define MM81X_HW_TX_SK_PACING_SHIFT (3) | |
| /* NSS/MCS map values */ | |
| #define MM81X_NSS_MCS_BYTE_0 0xfe /* 1SS */ | |
| #define MM81X_NSS_MCS_BYTE_1 0x00 | |
| #define MM81X_NSS_MCS_BYTE_2 0xfc /* 1SS */ | |
| #define MM81X_NSS_MCS_BYTE_3 0x01 | |
| #define MM81X_NSS_MCS_BYTE_4 0x00 | |
| /* HW restart delay time before terminating hardware IF work items */ | |
| #define MM81X_HW_RESTART_DELAY_MS 20 | |
| /* clang-format off */ | |
| /* mm81x chips do not support 16MHz */ | |
| #define CHANS1G(channel, frequency, offset, chan_flags) \ | |
| { \ | |
| .band = NL80211_BAND_S1GHZ, \ | |
| .center_freq = (frequency), \ | |
| .freq_offset = (offset), \ | |
| .hw_value = (channel), \ | |
| .flags = ((chan_flags) | IEEE80211_CHAN_NO_16MHZ), \ | |
| .max_antenna_gain = 0, \ | |
| .max_power = 30, \ | |
| } | |
| static struct ieee80211_channel mors_s1ghz_channels[] = { | |
| CHANS1G(1, 902, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), | |
| CHANS1G(3, 903, 500, 0), | |
| CHANS1G(5, 904, 500, 0), | |
| CHANS1G(7, 905, 500, 0), | |
| CHANS1G(9, 906, 500, 0), | |
| CHANS1G(11, 907, 500, 0), | |
| CHANS1G(13, 908, 500, 0), | |
| CHANS1G(15, 909, 500, 0), | |
| CHANS1G(17, 910, 500, 0), | |
| CHANS1G(19, 911, 500, 0), | |
| CHANS1G(21, 912, 500, 0), | |
| CHANS1G(23, 913, 500, 0), | |
| CHANS1G(25, 914, 500, 0), | |
| CHANS1G(27, 915, 500, 0), | |
| CHANS1G(29, 916, 500, 0), | |
| CHANS1G(31, 917, 500, 0), | |
| CHANS1G(33, 918, 500, 0), | |
| CHANS1G(35, 919, 500, 0), | |
| CHANS1G(37, 920, 500, 0), | |
| CHANS1G(39, 921, 500, 0), | |
| CHANS1G(41, 922, 500, 0), | |
| CHANS1G(43, 923, 500, 0), | |
| CHANS1G(45, 924, 500, 0), | |
| CHANS1G(47, 925, 500, 0), | |
| CHANS1G(49, 926, 500, 0), | |
| CHANS1G(51, 927, 500, IEEE80211_CHAN_S1G_NO_PRIMARY), | |
| }; | |
| /* clang-format on */ | |
| static struct ieee80211_supported_band mors_band_s1ghz = { | |
| .band = NL80211_BAND_S1GHZ, | |
| .s1g_cap.s1g = true, | |
| .channels = mors_s1ghz_channels, | |
| .n_channels = ARRAY_SIZE(mors_s1ghz_channels), | |
| .bitrates = NULL, | |
| .n_bitrates = 0, | |
| .s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */ | |
| }; | |
| static struct ieee80211_iface_limit mors_if_limits[] = { | |
| { | |
| .max = MM81X_MAX_IF, | |
| .types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), | |
| }, | |
| }; | |
| static struct ieee80211_iface_combination mors_if_combs[] = { | |
| { | |
| .limits = mors_if_limits, | |
| .n_limits = ARRAY_SIZE(mors_if_limits), | |
| .max_interfaces = MM81X_MAX_IF, | |
| .num_different_channels = 1, | |
| }, | |
| }; | |
| /* Convert from a time in time units (1024us) to us */ | |
| #define MM81X_TU_TO_US(x) ((x) * 1024UL) | |
| /* Convert from a time in time units (1024us) to ms */ | |
| #define MM81X_TU_TO_MS(x) (MM81X_TU_TO_US(x) / 1000UL) | |
| /* Default time to dwell on a scan channel */ | |
| #define MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS (30) | |
| /* Default time to dwell on a scan channel for passive scan */ | |
| #define MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS (110) | |
| /* Default time to dwell on home channel, in between scan channels */ | |
| #define MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS (200) | |
| /* Typical time it takes to send the probe */ | |
| #define MM81X_HWSCAN_PROBE_DELAY_MS (30) | |
| /* A margin to account for event/command processing */ | |
| #define MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS (2000) | |
| /* Scan channel frequency mask */ | |
| #define HW_SCAN_CH_LIST_FREQ_KHZ GENMASK(19, 0) | |
| /* | |
| * Scan channel bandwidth mask. | |
| * Encoded as: 0 = 1MHz, 1 = 2MHz, 2 = 4MHz, 3 = 8MHz | |
| */ | |
| #define HW_SCAN_CH_LIST_OP_BW GENMASK(21, 20) | |
| /* | |
| * Scan channel primary channel width. | |
| * Encoded as: 0 = 1MHz, 1 = 2MHz | |
| */ | |
| #define HW_SCAN_CH_LIST_PRIM_CH_WIDTH BIT(22) | |
| /* Index into power_list for tx power of channel */ | |
| #define HW_SCAN_CH_LIST_PWR_LIST_IDX GENMASK(31, 26) | |
| struct hw_scan_tlv_hdr { | |
| __le16 tag; | |
| __le16 len; | |
| } __packed; | |
| struct hw_scan_tlv_channel_list { | |
| struct hw_scan_tlv_hdr hdr; | |
| __le32 channels[]; | |
| } __packed; | |
| struct hw_scan_tlv_power_list { | |
| struct hw_scan_tlv_hdr hdr; | |
| s32 tx_power_qdbm[]; | |
| } __packed; | |
| struct hw_scan_tlv_probe_req { | |
| struct hw_scan_tlv_hdr hdr; | |
| /* Probe request frame template (including SSIDs) */ | |
| u8 buf[]; | |
| } __packed; | |
| struct hw_scan_tlv_dwell_on_home { | |
| struct hw_scan_tlv_hdr hdr; | |
| /* Time to dwell on home between scan channels */ | |
| __le32 home_dwell_time_ms; | |
| } __packed; | |
| #define DOT11AH_BA_MAX_MPDU_PER_AMPDU (32) | |
| /* wiphy scan params */ | |
| #define MM81X_MAX_SCAN_IE_LEN 512 | |
| #define MM81X_MAX_SCAN_SSIDS 1 | |
| #define MM81X_MAX_REMAIN_ON_CHAN_DURATION 10000 | |
| static bool mm81x_reg_h_cc_equal(const char *cc1, const char *cc2) | |
| { | |
| return (cc1[0] == cc2[0]) && (cc1[1] == cc2[1]); | |
| } | |
| static bool mm81x_tx_h_pkt_over_rts_threshold(struct mm81x *mors, | |
| struct ieee80211_tx_info *info, | |
| struct sk_buff *skb) | |
| { | |
| u8 ccmp_len; | |
| if (!info->control.hw_key) | |
| return ((skb->len + FCS_LEN) > mors->rts_threshold); | |
| if (info->control.hw_key->keylen == 32) | |
| ccmp_len = | |
| IEEE80211_CCMP_256_HDR_LEN + IEEE80211_CCMP_256_MIC_LEN; | |
| else if (info->control.hw_key->keylen == 16) | |
| ccmp_len = IEEE80211_CCMP_HDR_LEN + IEEE80211_CCMP_MIC_LEN; | |
| else | |
| ccmp_len = 0; | |
| return ((skb->len + FCS_LEN + ccmp_len) > mors->rts_threshold); | |
| } | |
| static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mors, | |
| struct sk_buff *skb, | |
| struct ieee80211_sta *sta) | |
| { | |
| struct mm81x_sta *mors_sta; | |
| struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | |
| if (!sta) | |
| return false; | |
| mors_sta = (struct mm81x_sta *)sta->drv_priv; | |
| if (!mors_sta->tx_ps_filter_en) | |
| return false; | |
| dev_dbg(mors->dev, "Frame for sta[%pM] PS filtered", mors_sta->addr); | |
| info->flags |= IEEE80211_TX_STAT_TX_FILTERED; | |
| info->flags &= ~IEEE80211_TX_CTL_AMPDU; | |
| ieee80211_tx_status_skb(mors->hw, skb); | |
| return true; | |
| } | |
| static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) | |
| { | |
| int ret = 0; | |
| u32 i; | |
| struct mm81x *mors = hw->priv; | |
| struct host_cmd_resp_get_disabled_channels *resp; | |
| u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) * | |
| ARRAY_SIZE(mors_s1ghz_channels) + | |
| sizeof(*resp); | |
| resp = kzalloc(resp_len, GFP_KERNEL); | |
| if (!resp) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| ret = mm81x_cmd_get_disabled_channels(mors, resp, resp_len); | |
| if (ret) | |
| goto out; | |
| for (i = 0; i < ARRAY_SIZE(mors_s1ghz_channels); i++) { | |
| struct ieee80211_channel *ch = &mors_s1ghz_channels[i]; | |
| if (ch->flags & IEEE80211_CHAN_DISABLED) | |
| continue; | |
| ch->flags &= ~IEEE80211_CHAN_S1G_NO_PRIMARY; | |
| } | |
| for (i = 0; i < le32_to_cpu(resp->n_channels); i++) { | |
| struct ieee80211_channel *ch; | |
| struct host_cmd_disabled_channel_entry *entry = | |
| &resp->channels[i]; | |
| if (entry->bw_mhz != 1) | |
| continue; | |
| ch = ieee80211_get_channel_khz( | |
| hw->wiphy, | |
| KHZ100_TO_KHZ(le16_to_cpu(entry->freq_100khz))); | |
| if (!ch) | |
| continue; | |
| ch->flags |= IEEE80211_CHAN_S1G_NO_PRIMARY; | |
| dev_dbg(mors->dev, "set NO_PRIMARY on %u KHz", | |
| ieee80211_channel_to_khz(ch)); | |
| } | |
| out: | |
| if (ret) | |
| dev_err(mors->dev, "failed to set disabled primary channels"); | |
| kfree(resp); | |
| } | |
| static int mm81x_mac_ops_start(struct ieee80211_hw *hw) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| mors->started = true; | |
| return 0; | |
| } | |
| static int mm81x_tx_h_get_max_bw(struct mm81x *mors) | |
| { | |
| return MM81X_FW_SUPP(&mors->fw_caps, 8MHZ) ? 8 : | |
| MM81X_FW_SUPP(&mors->fw_caps, 4MHZ) ? 4 : | |
| MM81X_FW_SUPP(&mors->fw_caps, 2MHZ) ? 2 : | |
| 1; | |
| } | |
| static void mm81x_mac_caps_init(struct mm81x *mors) | |
| { | |
| struct mm81x_fw_caps *fw_caps = &mors->fw_caps; | |
| struct ieee80211_sta_s1g_cap *s1g = &mors_band_s1ghz.s1g_cap; | |
| #define __FW_CAP_N(_n, _cap, _bit) \ | |
| do { \ | |
| if (MM81X_FW_SUPP(fw_caps, _cap)) \ | |
| s1g->cap[_n] |= (_bit); \ | |
| } while (0) | |
| #define FW_CAP0(_cap, _bit) __FW_CAP_N(0, _cap, _bit) | |
| #define FW_CAP3(_cap, _bit) __FW_CAP_N(3, _cap, _bit) | |
| #define FW_CAP5(_cap, _bit) __FW_CAP_N(5, _cap, _bit) | |
| #define FW_CAP6(_cap, _bit) __FW_CAP_N(6, _cap, _bit) | |
| #define FW_CAP7(_cap, _bit) __FW_CAP_N(7, _cap, _bit) | |
| #define FW_CAP8(_cap, _bit) __FW_CAP_N(8, _cap, _bit) | |
| #define FW_CAP9(_cap, _bit) __FW_CAP_N(9, _cap, _bit) | |
| FW_CAP0(S1G_LONG, S1G_CAP0_S1G_LONG); | |
| s1g->cap[0] |= S1G_CAP0_SGI_1MHZ; | |
| if (MM81X_FW_SUPP(fw_caps, SGI)) { | |
| FW_CAP0(2MHZ, S1G_CAP0_SGI_2MHZ); | |
| FW_CAP0(4MHZ, S1G_CAP0_SGI_4MHZ); | |
| FW_CAP0(8MHZ, S1G_CAP0_SGI_8MHZ); | |
| } | |
| if (MM81X_FW_SUPP(fw_caps, 8MHZ)) | |
| s1g->cap[0] |= S1G_SUPP_CH_WIDTH_8; | |
| else if (MM81X_FW_SUPP(fw_caps, 4MHZ)) | |
| s1g->cap[0] |= S1G_SUPP_CH_WIDTH_4; | |
| else if (MM81X_FW_SUPP(fw_caps, 2MHZ)) | |
| s1g->cap[0] |= S1G_SUPP_CH_WIDTH_2; | |
| FW_CAP3(RD_RESPONDER, S1G_CAP3_RD_RESPONDER); | |
| FW_CAP3(LONG_MPDU, S1G_CAP3_MAX_MPDU_LEN); | |
| FW_CAP5(AMSDU, S1G_CAP5_AMSDU); | |
| FW_CAP5(AMPDU, S1G_CAP5_AMPDU); | |
| FW_CAP5(ASYMMETRIC_BA_SUPPORT, S1G_CAP5_ASYMMETRIC_BA); | |
| FW_CAP5(FLOW_CONTROL, S1G_CAP5_FLOW_CONTROL); | |
| FW_CAP6(OBSS_MITIGATION, S1G_CAP6_OBSS_MITIGATION); | |
| FW_CAP6(FRAGMENT_BA, S1G_CAP6_FRAGMENT_BA); | |
| FW_CAP6(NDP_PSPOLL, S1G_CAP6_NDP_PS_POLL); | |
| FW_CAP6(TXOP_SHARING_IMPLICIT_ACK, S1G_CAP6_TXOP_SHARING_IMP_ACK); | |
| FW_CAP6(HTC_VHT_MFB, S1G_CAP6_VHT_LINK_ADAPT); | |
| FW_CAP7(TACK_AS_PSPOLL, S1G_CAP7_TACK_AS_PS_POLL); | |
| FW_CAP7(DUPLICATE_1MHZ, S1G_CAP7_DUP_1MHZ); | |
| FW_CAP7(MCS_NEGOTIATION, S1G_CAP7_MCS_NEGOTIATION); | |
| FW_CAP7(1MHZ_CONTROL_RESPONSE_PREAMBLE, | |
| S1G_CAP7_1MHZ_CTL_RESPONSE_PREAMBLE); | |
| FW_CAP7(SECTOR_TRAINING, S1G_CAP7_SECTOR_TRAINING_OPERATION); | |
| FW_CAP7(TMP_PS_MODE_SWITCH, S1G_CAP7_TEMP_PS_MODE_SWITCH); | |
| FW_CAP8(BDT, S1G_CAP8_BDT); | |
| FW_CAP9(LINK_ADAPTATION_WO_NDP_CMAC, | |
| S1G_CAP9_LINK_ADAPT_PER_CONTROL_RESPONSE); | |
| /* 1SS MCS 9 for Rx / Tx map */ | |
| s1g->nss_mcs[0] = MM81X_NSS_MCS_BYTE_0; | |
| s1g->nss_mcs[1] = MM81X_NSS_MCS_BYTE_1; | |
| s1g->nss_mcs[2] = MM81X_NSS_MCS_BYTE_2; | |
| s1g->nss_mcs[3] = MM81X_NSS_MCS_BYTE_3; | |
| s1g->nss_mcs[4] = MM81X_NSS_MCS_BYTE_4; | |
| #undef FW_CAP0 | |
| #undef FW_CAP3 | |
| #undef FW_CAP5 | |
| #undef FW_CAP6 | |
| #undef FW_CAP7 | |
| #undef FW_CAP8 | |
| #undef FW_CAP9 | |
| #undef __FW_CAP_N | |
| } | |
| static void mm81x_mac_beacon_irq_enable(struct mm81x_vif *mors_vif, bool enable) | |
| { | |
| struct mm81x *mors = mm81x_vif_to_mors(mors_vif); | |
| u8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mors_vif->id; | |
| enable ? set_bit(beacon_irq_num, &mors->beacon_irqs_enabled) : | |
| clear_bit(beacon_irq_num, &mors->beacon_irqs_enabled); | |
| mm81x_hw_irq_enable(mors, beacon_irq_num, enable); | |
| } | |
| static void mm81x_beacon_h_fill_tx_info(struct mm81x *mors, | |
| struct mm81x_skb_tx_info *tx_info, | |
| struct mm81x_vif *mors_vif, | |
| int tx_bw_mhz) | |
| { | |
| enum dot11_bandwidth bw_idx = | |
| mm81x_ratecode_bw_mhz_to_bw_index(tx_bw_mhz); | |
| enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT; | |
| tx_info->flags |= | |
| cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); | |
| if (bw_idx == DOT11_BANDWIDTH_1MHZ) | |
| pream = MM81X_RATE_PREAMBLE_S1G_1M; | |
| tx_info->rates[0].count = 1; | |
| tx_info->rates[1].count = 0; | |
| tx_info->rates[0].mm81x_ratecode = | |
| mm81x_ratecode_init(bw_idx, 0, 0, pream); | |
| if (mors->fw_flags & MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION) | |
| tx_info->flags |= | |
| cpu_to_le32(MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); | |
| } | |
| static void mm81x_mac_beacon_work(struct work_struct *work) | |
| { | |
| struct mm81x_vif *mors_vif = | |
| from_work(mors_vif, work, u.ap.beacon_work); | |
| struct mm81x *mors = mm81x_vif_to_mors(mors_vif); | |
| struct mm81x_skbq *mq; | |
| struct sk_buff *beacon; | |
| struct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mors_vif); | |
| struct mm81x_skb_tx_info tx_info = { 0 }; | |
| int num_bcn_vifs = atomic_read(&mors->num_bcn_vifs); | |
| mq = mm81x_hif_get_tx_beacon_queue(mors); | |
| if (!mq) { | |
| dev_err(mors->dev, "no matching beacon Q found"); | |
| return; | |
| } | |
| if (mm81x_skbq_count(mq) >= num_bcn_vifs) { | |
| dev_err(mors->dev, | |
| "previous beacon not consumed, dropping req [id:%d]", | |
| mors_vif->id); | |
| return; | |
| } | |
| beacon = ieee80211_beacon_get(mors->hw, vif, false); | |
| if (!beacon) | |
| return; | |
| mm81x_beacon_h_fill_tx_info(mors, &tx_info, mors_vif, | |
| cfg80211_chandef_s1g_pri_width(&mors->chandef)); | |
| mm81x_skbq_skb_tx(mq, &beacon, &tx_info, MM81X_SKB_CHAN_BEACON); | |
| } | |
| void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status) | |
| { | |
| int vif_id; | |
| unsigned long masked_status = (status & mors->beacon_irqs_enabled) >> | |
| MM81X_INT_BEACON_BASE_NUM; | |
| guard(rcu)(); | |
| for_each_set_bit(vif_id, &masked_status, MM81X_MAX_IF) { | |
| struct mm81x_vif *mors_vif; | |
| struct ieee80211_vif *vif; | |
| vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); | |
| if (vif) { | |
| mors_vif = ieee80211_vif_to_mors_vif(vif); | |
| queue_work(system_bh_wq, &mors_vif->u.ap.beacon_work); | |
| } | |
| } | |
| } | |
| static void mm81x_mac_beacon_init(struct mm81x_vif *mors_vif) | |
| { | |
| struct mm81x *mors = mm81x_vif_to_mors(mors_vif); | |
| INIT_WORK(&mors_vif->u.ap.beacon_work, mm81x_mac_beacon_work); | |
| mm81x_mac_beacon_irq_enable(mors_vif, true); | |
| atomic_inc(&mors->num_bcn_vifs); | |
| } | |
| static struct hw_scan_tlv_hdr mm81x_hw_scan_h_pack_tlv_hdr(u16 tag, u16 len) | |
| { | |
| struct hw_scan_tlv_hdr hdr = { .tag = cpu_to_le16(tag), | |
| .len = cpu_to_le16(len) }; | |
| return hdr; | |
| } | |
| static __le32 mm81x_hw_scan_h_pack_channel(struct ieee80211_channel *chan, | |
| u8 pwr_idx) | |
| { | |
| __le32 packed = 0; | |
| u32 freq_khz = ieee80211_channel_to_khz(chan); | |
| packed |= le32_encode_bits(freq_khz, HW_SCAN_CH_LIST_FREQ_KHZ); | |
| packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), | |
| HW_SCAN_CH_LIST_OP_BW); | |
| packed |= le32_encode_bits(mm81x_ratecode_bw_mhz_to_bw_index(1), | |
| HW_SCAN_CH_LIST_PRIM_CH_WIDTH); | |
| packed |= le32_encode_bits(pwr_idx, HW_SCAN_CH_LIST_PWR_LIST_IDX); | |
| return packed; | |
| } | |
| static u8 * | |
| mm81x_hw_scan_h_add_channel_list_tlv(u8 *buf, | |
| struct mm81x_hw_scan_params *params) | |
| { | |
| int i; | |
| struct hw_scan_tlv_channel_list *ch_list = | |
| (struct hw_scan_tlv_channel_list *)buf; | |
| ch_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( | |
| HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST, | |
| params->num_chans * sizeof(ch_list->channels[0])); | |
| for (i = 0; i < params->num_chans; i++) { | |
| struct ieee80211_channel *chan = params->channels[i].channel; | |
| ch_list->channels[i] = mm81x_hw_scan_h_pack_channel( | |
| chan, params->channels[i].power_idx); | |
| } | |
| return (u8 *)&ch_list->channels[i]; | |
| } | |
| static u8 * | |
| mm81x_hw_scan_h_add_power_list_tlv(u8 *buf, struct mm81x_hw_scan_params *params) | |
| { | |
| int i; | |
| struct hw_scan_tlv_power_list *pwr_list = | |
| (struct hw_scan_tlv_power_list *)buf; | |
| size_t size = sizeof(pwr_list->tx_power_qdbm[0]) * params->n_powers; | |
| pwr_list->hdr = mm81x_hw_scan_h_pack_tlv_hdr( | |
| HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST, size); | |
| for (i = 0; i < params->n_powers; i++) | |
| pwr_list->tx_power_qdbm[i] = params->powers_qdbm[i]; | |
| return (u8 *)&pwr_list->tx_power_qdbm[i]; | |
| } | |
| static u8 * | |
| mm81x_hw_scan_h_add_probe_req_tlv(u8 *buf, struct mm81x_hw_scan_params *params) | |
| { | |
| struct sk_buff *skb = params->probe_req; | |
| struct hw_scan_tlv_probe_req *probe_req = | |
| (struct hw_scan_tlv_probe_req *)buf; | |
| probe_req->hdr = mm81x_hw_scan_h_pack_tlv_hdr( | |
| HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ, skb->len); | |
| memcpy(probe_req->buf, skb->data, skb->len); | |
| return buf + sizeof(*probe_req) + skb->len; | |
| } | |
| static u8 * | |
| mm81x_hw_scan_h_insert_dwell_time_tlv(u8 *buf, | |
| struct mm81x_hw_scan_params *params) | |
| { | |
| struct hw_scan_tlv_dwell_on_home *dwell = | |
| (struct hw_scan_tlv_dwell_on_home *)buf; | |
| dwell->hdr = mm81x_hw_scan_h_pack_tlv_hdr( | |
| HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME, | |
| sizeof(*dwell) - sizeof(dwell->hdr)); | |
| dwell->home_dwell_time_ms = cpu_to_le32(params->dwell_on_home_ms); | |
| return buf + sizeof(*dwell); | |
| } | |
| static int __mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, | |
| u8 *ssid, u8 ssid_len, | |
| struct ieee80211_scan_ies *ies) | |
| { | |
| u8 *pos; | |
| struct sk_buff *probe_req; | |
| struct ieee80211_tx_info *info; | |
| u16 ies_len = ies->len[NL80211_BAND_S1GHZ] + ies->common_ie_len; | |
| probe_req = ieee80211_probereq_get(params->hw, params->vif->addr, ssid, | |
| ssid_len, ies_len); | |
| if (!probe_req) | |
| return -ENOMEM; | |
| pos = skb_put(probe_req, ies_len); | |
| memcpy(pos, ies->common_ies, ies->common_ie_len); | |
| pos += ies->common_ie_len; | |
| memcpy(pos, ies->ies[NL80211_BAND_S1GHZ], ies->len[NL80211_BAND_S1GHZ]); | |
| info = IEEE80211_SKB_CB(probe_req); | |
| info->control.vif = params->vif; | |
| params->probe_req = probe_req; | |
| return 0; | |
| } | |
| static void mm81x_hw_scan_h_init_ssid(struct mm81x *mors, | |
| struct cfg80211_ssid *ssids, int n_ssids, | |
| u8 **out_ssid, u8 *out_ssid_len) | |
| { | |
| *out_ssid = NULL; | |
| *out_ssid_len = 0; | |
| if (n_ssids > 0) { | |
| if (n_ssids > 1) { | |
| dev_warn( | |
| mors->dev, | |
| "Multiple SSIDs found when only one supported. Using the first only."); | |
| } | |
| *out_ssid_len = ssids[0].ssid_len; | |
| *out_ssid = ssids[0].ssid; | |
| } | |
| } | |
| static int | |
| mm81x_hw_scan_h_init_probe_req(struct mm81x_hw_scan_params *params, | |
| struct ieee80211_scan_request *scan_req) | |
| { | |
| struct mm81x *mors = params->hw->priv; | |
| struct cfg80211_scan_request *req = &scan_req->req; | |
| struct ieee80211_scan_ies *ies = &scan_req->ies; | |
| u8 ssid_len = 0; | |
| u8 *ssid = NULL; | |
| mm81x_hw_scan_h_init_ssid(mors, req->ssids, req->n_ssids, &ssid, | |
| &ssid_len); | |
| return __mm81x_hw_scan_h_init_probe_req(params, ssid, ssid_len, ies); | |
| } | |
| static bool | |
| mm81x_hw_scan_h_is_chan_present(const struct mm81x_hw_scan_params *params, | |
| const struct ieee80211_channel *chan) | |
| { | |
| int channel; | |
| for (channel = 0; channel < params->num_chans; channel++) { | |
| if (params->channels[channel].channel == chan) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static int mm81x_hw_scan_h_insert_chan(struct mm81x_hw_scan_params *params, | |
| struct ieee80211_channel *chan) | |
| { | |
| if (!params->channels) | |
| return -EFAULT; | |
| if (!chan) | |
| return -EFAULT; | |
| if (params->num_chans >= params->allocated_chans) | |
| return -ENOMEM; | |
| if (mm81x_hw_scan_h_is_chan_present(params, chan)) | |
| return 0; | |
| params->channels[params->num_chans].channel = chan; | |
| params->num_chans++; | |
| return 0; | |
| } | |
| static int mm81x_hw_scan_h_init_chan_list(struct mm81x_hw_scan_params *params, | |
| struct ieee80211_channel **chans, | |
| u32 n_channels) | |
| { | |
| int i, j; | |
| int num_pwrs_coarse = 0; | |
| int last_pwr = INT_MIN; | |
| int chans_to_allocate = 0; | |
| for (i = 0; i < n_channels; i++) | |
| if (chans[i]) | |
| chans_to_allocate++; | |
| params->num_chans = 0; | |
| params->allocated_chans = 0; | |
| params->channels = kzalloc_objs(*params->channels, chans_to_allocate); | |
| if (!params->channels) | |
| return -ENOMEM; | |
| params->allocated_chans = chans_to_allocate; | |
| for (i = 0; i < n_channels; i++) | |
| if (chans[i]) | |
| mm81x_hw_scan_h_insert_chan(params, chans[i]); | |
| /* | |
| * Calculate a rough estimate of number of different channel | |
| * powers required | |
| */ | |
| for (i = 0; i < params->num_chans; i++) { | |
| if (chans[i]->max_reg_power != last_pwr) { | |
| last_pwr = chans[i]->max_reg_power; | |
| num_pwrs_coarse++; | |
| } | |
| } | |
| params->powers_qdbm = kmalloc_objs(*params->powers_qdbm, | |
| num_pwrs_coarse); | |
| if (!params->powers_qdbm) | |
| return -ENOMEM; | |
| params->n_powers = 0; | |
| for (i = 0; i < params->num_chans; i++) { | |
| s32 power_qdbm = | |
| MBM_TO_QDBM(DBM_TO_MBM(chans[i]->max_reg_power)); | |
| /* Try and find the power in the list */ | |
| for (j = 0; j < params->n_powers; j++) | |
| if (params->powers_qdbm[j] == power_qdbm) | |
| break; | |
| /* Reached the end of the list - add the new power option */ | |
| if (j == params->n_powers) { | |
| params->powers_qdbm[j] = power_qdbm; | |
| params->n_powers++; | |
| if (params->n_powers > num_pwrs_coarse) { | |
| WARN_ON(1); | |
| return -EFAULT; | |
| } | |
| } | |
| /* Give the index of the power level to the channel */ | |
| params->channels[i].power_idx = j; | |
| } | |
| return 0; | |
| } | |
| static void mm81x_hw_scan_h_clean_params(struct mm81x_hw_scan_params *params) | |
| { | |
| if (params->probe_req) | |
| dev_kfree_skb_any(params->probe_req); | |
| kfree(params->channels); | |
| kfree(params->powers_qdbm); | |
| params->num_chans = 0; | |
| params->allocated_chans = 0; | |
| } | |
| size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params) | |
| { | |
| struct hw_scan_tlv_channel_list *ch_list; | |
| struct hw_scan_tlv_power_list *pwr_list; | |
| struct hw_scan_tlv_probe_req *probe_req; | |
| struct hw_scan_tlv_dwell_on_home *dwell; | |
| struct host_cmd_req_hw_scan *req; | |
| size_t cmd_size = sizeof(*req); | |
| /* No TLVs if simple abort command */ | |
| if (params->operation != MM81X_HW_SCAN_OP_START) | |
| return cmd_size; | |
| cmd_size += struct_size(ch_list, channels, params->num_chans); | |
| cmd_size += struct_size(pwr_list, tx_power_qdbm, params->n_powers); | |
| if (params->probe_req) | |
| cmd_size += struct_size(probe_req, buf, params->probe_req->len); | |
| if (params->dwell_on_home_ms) | |
| cmd_size += sizeof(*dwell); | |
| return cmd_size; | |
| } | |
| u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf) | |
| { | |
| buf = mm81x_hw_scan_h_add_channel_list_tlv(buf, params); | |
| buf = mm81x_hw_scan_h_add_power_list_tlv(buf, params); | |
| if (params->dwell_on_home_ms) | |
| buf = mm81x_hw_scan_h_insert_dwell_time_tlv(buf, params); | |
| if (params->probe_req) | |
| buf = mm81x_hw_scan_h_add_probe_req_tlv(buf, params); | |
| return buf; | |
| } | |
| static u32 mm81x_hw_scan_h_get_dwell_on_home(struct mm81x *mors, | |
| struct ieee80211_vif *vif) | |
| { | |
| if (vif->type == NL80211_IFTYPE_STATION && vif->cfg.assoc) | |
| return mors->hw_scan.home_dwell_ms; | |
| return 0; | |
| } | |
| static struct mm81x_hw_scan_params * | |
| __mm81x_hw_scan_h_init_params(struct mm81x *mors) | |
| { | |
| struct mm81x_hw_scan_params *params = mors->hw_scan.params; | |
| if (!params) { | |
| params = kzalloc_obj(*params); | |
| if (params) | |
| mors->hw_scan.params = params; | |
| } else { | |
| mm81x_hw_scan_h_clean_params(params); | |
| memset(params, 0, sizeof(*params)); | |
| } | |
| return params; | |
| } | |
| static int mm81x_hw_scan_h_init_params(struct mm81x *mors, | |
| struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct cfg80211_scan_request *req) | |
| { | |
| struct mm81x_hw_scan_params *params = mors->hw_scan.params; | |
| params = __mm81x_hw_scan_h_init_params(mors); | |
| if (!params) { | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| return -ENOMEM; | |
| } | |
| params->hw = hw; | |
| params->vif = vif; | |
| params->has_directed_ssid = (req->ssids && req->ssids[0].ssid_len > 0); | |
| params->operation = MM81X_HW_SCAN_OP_START; | |
| params->dwell_on_home_ms = mm81x_hw_scan_h_get_dwell_on_home(mors, vif); | |
| if (req->duration) | |
| params->dwell_time_ms = MM81X_TU_TO_MS(req->duration); | |
| else if (req->n_ssids == 0) | |
| params->dwell_time_ms = | |
| MM81X_HWSCAN_DEFAULT_PASSIVE_DWELL_TIME_MS; | |
| else | |
| params->dwell_time_ms = MM81X_HWSCAN_DEFAULT_DWELL_TIME_MS; | |
| return 0; | |
| } | |
| static u32 mm81x_hw_scan_h_calc_timeout(struct mm81x_hw_scan_params *params) | |
| { | |
| u32 ret = 0; | |
| ret = params->dwell_time_ms + params->dwell_on_home_ms; | |
| if (params->probe_req) | |
| ret += MM81X_HWSCAN_PROBE_DELAY_MS; | |
| ret *= params->num_chans; | |
| ret += MM81X_HWSCAN_TIMEOUT_OVERHEAD_MS; | |
| return ret; | |
| } | |
| static int mm81x_mac_ops_hw_scan(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_scan_request *hw_req) | |
| { | |
| int ret = 0; | |
| struct mm81x *mors = hw->priv; | |
| struct cfg80211_scan_request *req = &hw_req->req; | |
| struct mm81x_hw_scan_params *params; | |
| struct ieee80211_channel **chans = hw_req->req.channels; | |
| dev_dbg(mors->dev, "state %d", mors->hw_scan.state); | |
| if (!mors->started) { | |
| dev_warn(mors->dev, "device not ready"); | |
| ret = -ENODEV; | |
| goto exit; | |
| } | |
| switch (mors->hw_scan.state) { | |
| case HW_SCAN_STATE_IDLE: | |
| mors->hw_scan.state = HW_SCAN_STATE_RUNNING; | |
| reinit_completion(&mors->hw_scan.scan_done); | |
| break; | |
| case HW_SCAN_STATE_RUNNING: | |
| case HW_SCAN_STATE_ABORTING: | |
| ret = -EBUSY; | |
| goto exit; | |
| } | |
| ret = mm81x_hw_scan_h_init_params(mors, hw, vif, req); | |
| if (ret) | |
| goto exit; | |
| params = mors->hw_scan.params; | |
| ret = mm81x_hw_scan_h_init_chan_list(params, chans, | |
| hw_req->req.n_channels); | |
| if (ret) | |
| goto exit; | |
| /* Only init the probe request template if this is an active scan */ | |
| if (req->n_ssids > 0) { | |
| ret = mm81x_hw_scan_h_init_probe_req(params, hw_req); | |
| if (ret) { | |
| dev_err(mors->dev, "Failed to init probe req %d", ret); | |
| goto exit; | |
| } | |
| } | |
| ret = mm81x_cmd_hw_scan(mors, params, false); | |
| if (ret) { | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| goto exit; | |
| } | |
| ieee80211_queue_delayed_work( | |
| mors->hw, &mors->hw_scan.timeout, | |
| msecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params))); | |
| exit: | |
| return ret; | |
| } | |
| static void mm81x_hw_scan_abort(struct mm81x *mors) | |
| { | |
| int ret; | |
| struct mm81x_hw_scan_params params = { 0 }; | |
| switch (mors->hw_scan.state) { | |
| case HW_SCAN_STATE_IDLE: | |
| case HW_SCAN_STATE_ABORTING: | |
| /* scan not running */ | |
| return; | |
| case HW_SCAN_STATE_RUNNING: | |
| mors->hw_scan.state = HW_SCAN_STATE_ABORTING; | |
| break; | |
| } | |
| params.operation = MM81X_HW_SCAN_OP_STOP; | |
| ret = mm81x_cmd_hw_scan(mors, ¶ms, false); | |
| if (ret || !mors->started || | |
| !wait_for_completion_timeout(&mors->hw_scan.scan_done, 1 * HZ)) { | |
| /* | |
| * We may have lost the event on the bus, the chip could be | |
| * wedged, or the cmd failed for another reason. Nevertheless, | |
| * we should call the done event so mac80211 knows to unblock | |
| * itself. | |
| */ | |
| struct cfg80211_scan_info info = { .aborted = true }; | |
| ieee80211_scan_completed(mors->hw, &info); | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| } | |
| } | |
| static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| cancel_delayed_work_sync(&mors->hw_scan.timeout); | |
| mm81x_hw_scan_abort(mors); | |
| } | |
| static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| struct cfg80211_scan_info info = { 0 }; | |
| dev_dbg(mors->dev, "completing hw scan"); | |
| switch (mors->hw_scan.state) { | |
| case HW_SCAN_STATE_IDLE: | |
| /* Scan has already been stopped. Just continue */ | |
| goto exit; | |
| case HW_SCAN_STATE_RUNNING: | |
| case HW_SCAN_STATE_ABORTING: | |
| info.aborted = (mors->hw_scan.state == HW_SCAN_STATE_ABORTING); | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| } | |
| ieee80211_scan_completed(mors->hw, &info); | |
| exit: | |
| complete(&mors->hw_scan.scan_done); | |
| cancel_delayed_work_sync(&mors->hw_scan.timeout); | |
| } | |
| static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work) | |
| { | |
| struct mm81x *mors = | |
| container_of(work, struct mm81x, hw_scan.timeout.work); | |
| dev_err(mors->dev, "hw scan timed out, aborting"); | |
| mm81x_hw_scan_abort(mors); | |
| } | |
| static void mm81x_mac_hw_scan_init(struct mm81x *mors) | |
| { | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| mors->hw_scan.params = NULL; | |
| mors->hw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS; | |
| init_completion(&mors->hw_scan.scan_done); | |
| INIT_DELAYED_WORK(&mors->hw_scan.timeout, | |
| mm81x_mac_hw_scan_timeout_work); | |
| } | |
| static void mm81x_mac_hw_scan_destroy(struct mm81x *mors) | |
| { | |
| cancel_delayed_work_sync(&mors->hw_scan.timeout); | |
| if (mors->hw_scan.params) | |
| mm81x_hw_scan_h_clean_params(mors->hw_scan.params); | |
| kfree(mors->hw_scan.params); | |
| mors->hw_scan.params = NULL; | |
| } | |
| static void mm81x_mac_hw_scan_finish(struct mm81x *mors) | |
| { | |
| struct cfg80211_scan_info info = { | |
| .aborted = true, | |
| }; | |
| if (mors->hw_scan.state == HW_SCAN_STATE_IDLE) | |
| return; | |
| ieee80211_scan_completed(mors->hw, &info); | |
| complete(&mors->hw_scan.scan_done); | |
| mors->hw_scan.state = HW_SCAN_STATE_IDLE; | |
| cancel_delayed_work_sync(&mors->hw_scan.timeout); | |
| } | |
| int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb) | |
| { | |
| struct host_cmd_event *event = (struct host_cmd_event *)(skb->data); | |
| u16 event_id = le16_to_cpu(event->hdr.message_id); | |
| u16 event_iid = le16_to_cpu(event->hdr.host_id); | |
| u16 vif_id = le16_to_cpu(event->hdr.vif_id); | |
| struct ieee80211_vif *vif; | |
| if (!HOST_CMD_IS_EVT(event) || event_iid != 0) | |
| return -EINVAL; | |
| switch (event_id) { | |
| case HOST_CMD_ID_EVT_HW_SCAN_DONE: | |
| dev_dbg(mors->dev, | |
| "Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received."); | |
| mm81x_mac_hw_scan_done_event(mors->hw); | |
| break; | |
| case HOST_CMD_ID_EVT_BEACON_LOSS: | |
| dev_dbg(mors->dev, | |
| "Event: HOST_CMD_ID_EVT_BEACON_LOSS Received"); | |
| scoped_guard(rcu) { | |
| vif = mm81x_rcu_dereference_vif_id(mors, vif_id, true); | |
| if (vif) | |
| ieee80211_beacon_loss(vif); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static void mm81x_tx_h_apply_mcs10(struct mm81x *mors, | |
| struct mm81x_skb_tx_info *tx_info) | |
| { | |
| u8 i; | |
| u8 j; | |
| int mcs0_first_idx = -1; | |
| int mcs0_last_idx = -1; | |
| /* Find out where our first and last MCS0 entries are. */ | |
| for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { | |
| enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_index_get( | |
| tx_info->rates[i].mm81x_ratecode); | |
| if (bw_idx == DOT11_BANDWIDTH_1MHZ) { | |
| mcs0_last_idx = i; | |
| if (mcs0_first_idx == -1) | |
| mcs0_first_idx = i; | |
| } | |
| /* | |
| * If the count is 0 then we are at the end of the table. | |
| * Break to allow us to reuse i indicating the end of the | |
| * table. | |
| */ | |
| if (tx_info->rates[i].count == 0) | |
| break; | |
| } | |
| /* If there aren't any MCS0 (at 1MHz) entries we are done. */ | |
| if (mcs0_first_idx < 0) | |
| return; | |
| /* | |
| * If we are in MCS10_MODE_AUTO add MCS10 counts to the table if they | |
| * will fit. There should be three cases: | |
| * | |
| * - There is one MSC0 entry and the table is full -> do nothing | |
| * - There is one MSC0 entry and the table has space -> adjust MSC0 | |
| * down and add MCS 10 | |
| * - There are multiple MCS0 entries -> replace entries after the first | |
| * with MCS 10 | |
| */ | |
| /* Case 3 - replace additional entries. */ | |
| if (mcs0_last_idx > mcs0_first_idx) { | |
| for (j = mcs0_first_idx + 1; j < i; j++) { | |
| enum dot11_bandwidth bw_idx = | |
| mm81x_ratecode_bw_index_get( | |
| tx_info->rates[j].mm81x_ratecode); | |
| u8 mcs_index = mm81x_ratecode_mcs_index_get( | |
| tx_info->rates[j].mm81x_ratecode); | |
| if (mcs_index == 0 && bw_idx == DOT11_BANDWIDTH_1MHZ) { | |
| mm81x_ratecode_mcs_index_set( | |
| &tx_info->rates[j].mm81x_ratecode, 10); | |
| } | |
| } | |
| /* Case 2 - add additional MCS10 entry. */ | |
| } else if (mcs0_last_idx == mcs0_first_idx && | |
| i < (IEEE80211_TX_MAX_RATES)) { | |
| int pre_mcs10_mcs0_count = | |
| min_t(u8, tx_info->rates[mcs0_last_idx].count, | |
| MCS0_BEFORE_MCS10_COUNT); | |
| int mcs10_count = tx_info->rates[mcs0_last_idx].count - | |
| pre_mcs10_mcs0_count; | |
| /* | |
| * If there were less retries than our desired minimum MCS0 we | |
| * don't add MCS10 retries. | |
| */ | |
| if (mcs10_count > 0) { | |
| /* Use the same flags for MCS10 as MCS0. */ | |
| tx_info->rates[i].mm81x_ratecode = | |
| tx_info->rates[mcs0_last_idx].mm81x_ratecode; | |
| mm81x_ratecode_mcs_index_set( | |
| &tx_info->rates[i].mm81x_ratecode, 10); | |
| tx_info->rates[mcs0_last_idx].count = | |
| pre_mcs10_mcs0_count; | |
| tx_info->rates[i].count = mcs10_count; | |
| } | |
| } | |
| } | |
| void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb) | |
| { | |
| struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; | |
| struct mm81x_sta *mors_sta = (struct mm81x_sta *)pubsta->drv_priv; | |
| u8 tid = ieee80211_get_tid(hdr); | |
| /* we are already aggregating */ | |
| if (mors_sta->tid_tx[tid] || mors_sta->tid_start_tx[tid]) | |
| return; | |
| if (mors_sta->state < IEEE80211_STA_AUTHORIZED) | |
| return; | |
| if (skb_get_queue_mapping(skb) == IEEE80211_AC_VO) | |
| return; | |
| if (unlikely(!ieee80211_is_data_qos(hdr->frame_control))) | |
| return; | |
| if (unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE))) | |
| return; | |
| mors_sta->tid_start_tx[tid] = true; | |
| ieee80211_start_tx_ba_session(pubsta, tid, 0); | |
| } | |
| int mm81x_tx_h_get_attempts(struct mm81x *mors, | |
| struct mm81x_skb_tx_status *tx_sts) | |
| { | |
| int attempts = 0; | |
| int i; | |
| int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES); | |
| for (i = 0; i < count; i++) { | |
| if (tx_sts->rates[i].count > 0) | |
| attempts += tx_sts->rates[i].count; | |
| else | |
| break; | |
| } | |
| return attempts; | |
| } | |
| static void mm81x_tx_h_fill_info(struct mm81x *mors, | |
| struct mm81x_skb_tx_info *tx_info, | |
| struct sk_buff *skb, struct ieee80211_vif *vif, | |
| int tx_bw_mhz, struct ieee80211_sta *sta) | |
| { | |
| int i; | |
| struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | |
| struct mm81x_vif *mors_vif = ieee80211_vif_to_mors_vif(vif); | |
| struct mm81x_sta *mors_sta = NULL; | |
| struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; | |
| int op_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); | |
| u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; | |
| bool rts_allowed = op_bw_mhz < 8; | |
| if (sta) | |
| mors_sta = (struct mm81x_sta *)sta->drv_priv; | |
| rts_allowed &= mm81x_tx_h_pkt_over_rts_threshold(mors, info, skb); | |
| mm81x_rc_sta_fill_tx_rates(mors, tx_info, skb, sta, tx_bw_mhz, | |
| rts_allowed); | |
| for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { | |
| if (rts_allowed) | |
| mm81x_ratecode_enable_rts( | |
| &tx_info->rates[i].mm81x_ratecode); | |
| if (info->control.rates[i].flags & IEEE80211_TX_RC_SHORT_GI) | |
| mm81x_ratecode_enable_sgi( | |
| &tx_info->rates[i].mm81x_ratecode); | |
| } | |
| /* Apply change of MCS0 to MCS10 if required. */ | |
| mm81x_tx_h_apply_mcs10(mors, tx_info); | |
| tx_info->flags |= | |
| cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mors_vif->id)); | |
| if (info->flags & IEEE80211_TX_CTL_AMPDU) | |
| tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_CTL_AMPDU); | |
| if (info->flags & IEEE80211_TX_CTL_SEND_AFTER_DTIM) | |
| tx_info->flags |= | |
| cpu_to_le32(MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM); | |
| if (info->flags & IEEE80211_TX_CTL_NO_PS_BUFFER) { | |
| tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); | |
| if (info->flags & IEEE80211_TX_STATUS_EOSP) | |
| tx_info->flags |= cpu_to_le32( | |
| MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT); | |
| } else if (ieee80211_is_mgmt(hdr->frame_control) && | |
| !ieee80211_is_bufferable_mmpdu(skb)) { | |
| tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_NO_PS_BUFFER); | |
| } | |
| if (info->control.hw_key) { | |
| tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_HW_ENCRYPT); | |
| tx_info->flags |= cpu_to_le32(MM81X_TX_CONF_FLAGS_KEY_IDX_SET( | |
| info->control.hw_key->hw_key_idx)); | |
| } | |
| tx_info->tid = tid; | |
| if (mors_sta) { | |
| tx_info->tid_params = mors_sta->tid_params[tid]; | |
| if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) { | |
| if (mors_sta->tx_ps_filter_en) | |
| dev_dbg(mors->dev, | |
| "TX ps filter cleared sta[%pM]", | |
| mors_sta->addr); | |
| mors_sta->tx_ps_filter_en = false; | |
| } | |
| } | |
| } | |
| static void mm81x_mac_ops_tx(struct ieee80211_hw *hw, | |
| struct ieee80211_tx_control *control, | |
| struct sk_buff *skb) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_skbq *mq = NULL; | |
| struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); | |
| struct ieee80211_vif *vif = info->control.vif; | |
| struct mm81x_skb_tx_info tx_info = { 0 }; | |
| struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; | |
| bool is_mgmt = ieee80211_is_mgmt(hdr->frame_control); | |
| int tx_bw_mhz = cfg80211_chandef_get_width(&mors->chandef); | |
| struct ieee80211_sta *sta = control->sta; | |
| int max_tx_bw = 0, sta_max_bw_mhz = 0; | |
| if (sta) { | |
| struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; | |
| sta_max_bw_mhz = mors_sta->max_bw_mhz; | |
| } | |
| max_tx_bw = mm81x_tx_h_get_max_bw(mors); | |
| tx_bw_mhz = min(max_tx_bw, tx_bw_mhz); | |
| if (is_mgmt) | |
| tx_bw_mhz = cfg80211_chandef_s1g_pri_width(&mors->chandef); | |
| if (sta_max_bw_mhz) | |
| tx_bw_mhz = min(tx_bw_mhz, sta_max_bw_mhz); | |
| if (ieee80211_is_probe_resp(hdr->frame_control)) | |
| tx_bw_mhz = 1; | |
| mm81x_tx_h_fill_info(mors, &tx_info, skb, vif, tx_bw_mhz, sta); | |
| if (mm81x_tx_h_ps_filtered_for_sta(mors, skb, sta)) | |
| return; | |
| if (is_mgmt) | |
| mq = mm81x_hif_get_tx_mgmt_queue(mors); | |
| else | |
| mq = mm81x_hif_get_tx_data_queue(mors, | |
| dot11_tid_to_ac(tx_info.tid)); | |
| mm81x_skbq_skb_tx(mq, &skb, &tx_info, | |
| (is_mgmt) ? MM81X_SKB_CHAN_MGMT : | |
| MM81X_SKB_CHAN_DATA); | |
| } | |
| static void mm81x_mac_ops_stop(struct ieee80211_hw *hw, bool suspend) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| mors->started = false; | |
| } | |
| static void mm81x_mac_beacon_finish(struct mm81x_vif *mors_vif) | |
| { | |
| struct mm81x *mors = mm81x_vif_to_mors(mors_vif); | |
| mm81x_mac_beacon_irq_enable(mors_vif, false); | |
| cancel_work_sync(&mors_vif->u.ap.beacon_work); | |
| /* | |
| * Side effect of the restarting required when | |
| * reacting to regdom changes... | |
| */ | |
| atomic_add_unless(&mors->num_bcn_vifs, -1, 0); | |
| } | |
| static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif) | |
| { | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; | |
| ret = mm81x_cmd_rm_if(mors, mors_vif->id); | |
| if (ret) | |
| dev_err(mors->dev, "mm81x_cmd_rm_if failed %d", ret); | |
| RCU_INIT_POINTER(mors->vifs[mors_vif->id], NULL); | |
| } | |
| static s32 mm81x_mac_get_max_txpower(struct mm81x *mors) | |
| { | |
| int ret; | |
| s32 power_mbm; | |
| /* Retrieve maximum TX power the chip can transmit */ | |
| ret = mm81x_cmd_get_max_txpower(mors, &power_mbm); | |
| if (ret) { | |
| dev_err(mors->dev, "using default tx max power %d mBm", | |
| MAX_TX_POWER_MBM); | |
| return MAX_TX_POWER_MBM; | |
| } | |
| dev_dbg(mors->dev, "Max tx power detected %d mBm", power_mbm); | |
| return power_mbm; | |
| } | |
| static s32 mm81x_mac_set_txpower(struct mm81x *mors, s32 power_mbm) | |
| { | |
| int ret; | |
| s32 out_power_mbm; | |
| if (mors->tx_max_power_mbm == INT_MAX) | |
| mors->tx_max_power_mbm = mm81x_mac_get_max_txpower(mors); | |
| power_mbm = min(power_mbm, mors->tx_max_power_mbm); | |
| if (power_mbm == mors->tx_power_mbm) | |
| return mors->tx_power_mbm; | |
| ret = mm81x_cmd_set_txpower(mors, &out_power_mbm, power_mbm); | |
| if (ret) { | |
| dev_err(mors->dev, "failed, power %d mBm ret %d", power_mbm, | |
| ret); | |
| return mors->tx_power_mbm; | |
| } | |
| if (out_power_mbm != mors->tx_power_mbm) { | |
| dev_dbg(mors->dev, "%d -> %d mBm", mors->tx_power_mbm, | |
| out_power_mbm); | |
| mors->tx_power_mbm = out_power_mbm; | |
| } | |
| return mors->tx_power_mbm; | |
| } | |
| static int mm81x_mac_set_channel(struct mm81x *mors, u32 op_chan_freq_hz, | |
| u8 pri_1mhz_chan_idx, u8 op_bw_mhz, | |
| u8 pri_bw_mhz) | |
| { | |
| int ret; | |
| ret = mm81x_cmd_set_channel(mors, op_chan_freq_hz, pri_1mhz_chan_idx, | |
| op_bw_mhz, pri_bw_mhz, &mors->tx_power_mbm); | |
| if (ret) { | |
| dev_err(mors->dev, "mm81x_cmd_set_channel() failed, ret %d", | |
| ret); | |
| return ret; | |
| } | |
| mm81x_mac_set_txpower(mors, mors->tx_power_mbm); | |
| return 0; | |
| } | |
| static u8 mm81x_mac_pri_chan_to_index(const struct cfg80211_chan_def *chandef) | |
| { | |
| u32 bw_mhz = cfg80211_chandef_get_width(chandef); | |
| u32 op_center_khz = ieee80211_chandef_to_khz(chandef); | |
| u32 first_1mhz_center_khz = op_center_khz - (bw_mhz * 500) + 500; | |
| u32 pri_1mhz_khz = ieee80211_channel_to_khz(chandef->chan); | |
| return (pri_1mhz_khz - first_1mhz_center_khz) / 1000; | |
| } | |
| static int mm81x_mac_ops_change_channel(struct ieee80211_hw *hw, | |
| struct cfg80211_chan_def *chandef) | |
| { | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| u64 freq_hz = KHZ_TO_HZ(ieee80211_chandef_to_khz(chandef)); | |
| u8 op_bw_mhz = cfg80211_chandef_get_width(chandef); | |
| u8 pri_1mhz_idx = mm81x_mac_pri_chan_to_index(chandef); | |
| int pri_chan_width_mhz = cfg80211_chandef_s1g_pri_width(chandef); | |
| dev_dbg(mors->dev, "ch: freq=%llu Hz bw=%u pri_idx=%d pri_bw=%d", | |
| freq_hz, op_bw_mhz, pri_1mhz_idx, pri_chan_width_mhz); | |
| ret = mm81x_mac_set_channel(mors, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz, | |
| pri_chan_width_mhz); | |
| if (ret) | |
| return ret; | |
| memcpy(&mors->chandef, chandef, sizeof(mors->chandef)); | |
| return 0; | |
| } | |
| static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx, | |
| u32 changed) | |
| { | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| struct ieee80211_conf *conf = &hw->conf; | |
| struct ieee80211_channel *channel = conf->chandef.chan; | |
| if (!mors->started) | |
| return 0; | |
| if (changed & IEEE80211_CONF_CHANGE_CHANNEL) { | |
| ret = mm81x_mac_ops_change_channel(hw, &conf->chandef); | |
| if (ret < 0) | |
| return ret; | |
| } | |
| if ((changed & IEEE80211_CONF_CHANGE_POWER) && | |
| !(changed & IEEE80211_CONF_CHANGE_CHANNEL) && | |
| !(conf->flags & IEEE80211_CONF_MONITOR)) { | |
| s32 power_mbm = DBM_TO_MBM(conf->power_level); | |
| power_mbm = min(DBM_TO_MBM(channel->max_reg_power), power_mbm); | |
| power_mbm = mm81x_mac_set_txpower(mors, power_mbm); | |
| conf->power_level = MBM_TO_DBM(power_mbm); | |
| } | |
| return 0; | |
| } | |
| static int mm81x_mac_ops_get_txpower(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| unsigned int link_id, int *dbm) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| struct ieee80211_chanctx_conf *chanctx_conf; | |
| struct cfg80211_chan_def *chandef = &vif->bss_conf.chanreq.oper; | |
| scoped_guard(rcu) { | |
| chanctx_conf = rcu_access_pointer(vif->bss_conf.chanctx_conf); | |
| if (!chanctx_conf || | |
| !cfg80211_chandef_identical(chandef, &chanctx_conf->def)) | |
| return -ENODATA; | |
| } | |
| *dbm = MBM_TO_DBM(mors->tx_power_mbm); | |
| return 0; | |
| } | |
| static void mm81x_mac_config_ps(struct mm81x *mors, struct ieee80211_vif *vif) | |
| { | |
| bool en_ps = vif->cfg.ps; | |
| if (vif->type == NL80211_IFTYPE_AP || !mors->ps.enable) | |
| return; | |
| if (mors->config_ps == en_ps) | |
| return; | |
| dev_dbg(mors->dev, "change powersave mode: %d (current %d)", en_ps, | |
| mors->config_ps); | |
| mors->config_ps = en_ps; | |
| if (en_ps) { | |
| mm81x_cmd_set_ps(mors, true); | |
| mm81x_ps_enable(mors); | |
| } else { | |
| mm81x_ps_disable(mors); | |
| mm81x_cmd_set_ps(mors, false); | |
| } | |
| } | |
| static void mm81x_mac_ops_bss_info_changed(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_bss_conf *info, | |
| u64 changed) | |
| { | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; | |
| if (changed & BSS_CHANGED_PS) | |
| mm81x_mac_config_ps(mors, vif); | |
| if (changed & BSS_CHANGED_BEACON_ENABLED) { | |
| mm81x_cmd_config_beacon_timer(mors, mors_vif, | |
| info->enable_beacon); | |
| if (!info->enable_beacon) | |
| mm81x_mac_beacon_finish(mors_vif); | |
| } | |
| if (changed & BSS_CHANGED_BEACON_INT || changed & BSS_CHANGED_SSID) { | |
| ret = mm81x_cmd_cfg_bss(mors, mors_vif->id, info->beacon_int, | |
| info->dtim_period, | |
| mm81x_vif_generate_cssid(vif)); | |
| if (ret) | |
| dev_err(mors->dev, "mm81x_cmd_cfg_bss failed %d", ret); | |
| } | |
| } | |
| static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, | |
| struct netdev_hw_addr_list *mc_list) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| struct mcast_filter *filter; | |
| struct netdev_hw_addr *addr; | |
| u16 addr_count = netdev_hw_addr_list_count(mc_list); | |
| u16 len = sizeof(*filter) + addr_count * sizeof(filter->addr_list[0]); | |
| filter = kzalloc(len, GFP_ATOMIC); | |
| if (!filter) | |
| return 0; | |
| if (addr_count > MCAST_FILTER_COUNT_MAX) { | |
| dev_warn( | |
| mors->dev, | |
| "Multicast filtering disabled - too many groups (%d) > %u", | |
| addr_count, (u16)MCAST_FILTER_COUNT_MAX); | |
| filter->count = 0; | |
| } else { | |
| netdev_hw_addr_list_for_each(addr, mc_list) { | |
| dev_dbg(mors->dev, "mcast whitelist (%d): %pM", | |
| filter->count, addr->addr); | |
| filter->addr_list[filter->count++] = | |
| mac2le32(addr->addr); | |
| } | |
| } | |
| return (u64)(unsigned long)filter; | |
| } | |
| static void mm81x_mac_ops_configure_filter(struct ieee80211_hw *hw, | |
| unsigned int changed_flags, | |
| unsigned int *total_flags, | |
| u64 multicast) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| struct mcast_filter *cmd = (void *)(unsigned long)multicast; | |
| struct mm81x_vif *mors_vif = NULL; | |
| struct ieee80211_vif *vif = NULL; | |
| int vif_id = 0; | |
| int ret = 0; | |
| if (!cmd) | |
| goto out; | |
| kfree(mors->mcast_filter); | |
| mors->mcast_filter = cmd; | |
| for (vif_id = 0; vif_id < ARRAY_SIZE(mors->vifs); vif_id++) { | |
| vif = mm81x_rcu_dereference_vif_id(mors, vif_id, false); | |
| if (!vif) | |
| continue; | |
| mors_vif = ieee80211_vif_to_mors_vif(vif); | |
| ret = mm81x_cmd_cfg_multicast_filter(mors, mors_vif); | |
| if (!ret) | |
| continue; | |
| dev_err(mors->dev, "Multicast filtering failed - rc=%d", ret); | |
| mors->mcast_filter = NULL; | |
| kfree(cmd); | |
| break; | |
| } | |
| out: | |
| *total_flags &= 0; | |
| } | |
| static int mm81x_mac_ops_conf_tx(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| unsigned int link_id, u16 ac, | |
| const struct ieee80211_tx_queue_params *params) | |
| { | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_queue_params mqp; | |
| mqp.aci = map_mac80211q_2_mm81x_aci(ac); | |
| mqp.aifs = params->aifs; | |
| mqp.cw_max = params->cw_max; | |
| mqp.cw_min = params->cw_min; | |
| mqp.uapsd = params->uapsd; | |
| mqp.txop = params->txop << 5; | |
| dev_dbg(mors->dev, "queue:%d txop:%d cw_min:%d cw_max:%d aifs:%d", | |
| mqp.aci, mqp.txop, mqp.cw_min, mqp.cw_max, mqp.aifs); | |
| ret = mm81x_cmd_cfg_qos(mors, &mqp); | |
| if (ret) | |
| dev_dbg(mors->dev, "mm81x_cmd_cfg_qos failed %d", ret); | |
| return ret; | |
| } | |
| static int mm81x_mac_ops_sta_state(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_sta *sta, | |
| enum ieee80211_sta_state old_state, | |
| enum ieee80211_sta_state new_state) | |
| { | |
| u16 aid; | |
| int ret; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; | |
| struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; | |
| /* Ignore both NOTEXIST to NONE and NONE to NOTEXIST */ | |
| if ((old_state == IEEE80211_STA_NOTEXIST && | |
| new_state == IEEE80211_STA_NONE) || | |
| (old_state == IEEE80211_STA_NONE && | |
| new_state == IEEE80211_STA_NOTEXIST)) | |
| return 0; | |
| if (vif->type == NL80211_IFTYPE_STATION) | |
| aid = vif->cfg.aid; | |
| else | |
| aid = sta->aid; | |
| ret = mm81x_cmd_sta_state(mors, mors_vif, aid, sta, new_state); | |
| if (ret < 0) | |
| goto exit; | |
| ether_addr_copy(mors_sta->addr, sta->addr); | |
| mors_sta->state = new_state; | |
| if (new_state > old_state && new_state == IEEE80211_STA_ASSOC) { | |
| if (vif->type == NL80211_IFTYPE_AP) | |
| mors_vif->u.ap.num_stas++; | |
| else if (vif->type == NL80211_IFTYPE_STATION) | |
| mors_vif->u.sta.is_assoc = true; | |
| } | |
| if (new_state < old_state && new_state == IEEE80211_STA_NONE) { | |
| if (vif->type == NL80211_IFTYPE_AP) | |
| mors_vif->u.ap.num_stas--; | |
| else if (vif->type == NL80211_IFTYPE_STATION) | |
| mors_vif->u.sta.is_assoc = false; | |
| } | |
| exit: | |
| /* | |
| * Always update our mmrc sta state even on failure to ensure | |
| * we don't hold a dangling sta on error | |
| */ | |
| mm81x_rc_sta_state_check(mors, vif, sta, old_state, new_state); | |
| return new_state < old_state ? 0 : ret; | |
| } | |
| static int mm81x_mac_ops_ampdu_action(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_ampdu_params *params) | |
| { | |
| u16 tid = params->tid; | |
| struct mm81x *mors = hw->priv; | |
| struct ieee80211_sta *sta = params->sta; | |
| struct mm81x_sta *mors_sta = (struct mm81x_sta *)sta->drv_priv; | |
| u16 buf_size = | |
| min_t(u16, params->buf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU); | |
| switch (params->action) { | |
| case IEEE80211_AMPDU_TX_START: | |
| dev_dbg(mors->dev, "%pM.%d A-MPDU TX start", mors_sta->addr, | |
| tid); | |
| ieee80211_start_tx_ba_cb_irqsafe(vif, sta->addr, tid); | |
| break; | |
| case IEEE80211_AMPDU_TX_STOP_CONT: | |
| case IEEE80211_AMPDU_TX_STOP_FLUSH: | |
| case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: | |
| dev_dbg(mors->dev, "%pM.%d A-MPDU TX flush", mors_sta->addr, | |
| tid); | |
| mors_sta->tid_start_tx[tid] = false; | |
| mors_sta->tid_tx[tid] = false; | |
| mors_sta->tid_params[tid] = 0; | |
| ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); | |
| break; | |
| case IEEE80211_AMPDU_TX_OPERATIONAL: | |
| dev_dbg(mors->dev, "%pM.%d A-MPDU TX oper", mors_sta->addr, | |
| tid); | |
| mors_sta->tid_tx[tid] = true; | |
| if (!buf_size) { | |
| dev_err(mors->dev, "%pM.%d A-MPDU Invalid buf size", | |
| mors_sta->addr, tid); | |
| break; | |
| } | |
| mors_sta->tid_params[tid] = | |
| u8_encode_bits(buf_size - 1, | |
| TX_INFO_TID_PARAMS_MAX_REORDER_BUF) | | |
| u8_encode_bits(1, TX_INFO_TID_PARAMS_AMPDU_ENABLED) | | |
| u8_encode_bits(params->amsdu, | |
| TX_INFO_TID_PARAMS_AMSDU_SUPPORTED); | |
| break; | |
| default: | |
| break; | |
| } | |
| return 0; | |
| } | |
| static int mm81x_mac_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_sta *sta, | |
| struct ieee80211_key_conf *key) | |
| { | |
| u16 aid; | |
| int ret = -EOPNOTSUPP; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; | |
| enum host_cmd_key_cipher cipher; | |
| enum host_cmd_aes_key_len length; | |
| if (vif->type == NL80211_IFTYPE_STATION) { | |
| aid = vif->cfg.aid; | |
| } else if (sta) { | |
| aid = sta->aid; | |
| } else { | |
| /* Is a group key - AID is unused */ | |
| WARN_ON(key->flags & IEEE80211_KEY_FLAG_PAIRWISE); | |
| aid = 0; | |
| } | |
| switch (cmd) { | |
| case SET_KEY: { | |
| switch (key->cipher) { | |
| case WLAN_CIPHER_SUITE_CCMP: | |
| case WLAN_CIPHER_SUITE_CCMP_256: | |
| cipher = HOST_CMD_KEY_CIPHER_AES_CCM; | |
| break; | |
| case WLAN_CIPHER_SUITE_GCMP: | |
| case WLAN_CIPHER_SUITE_GCMP_256: | |
| cipher = HOST_CMD_KEY_CIPHER_AES_GCM; | |
| break; | |
| default: | |
| /* Cipher suite currently not supported */ | |
| ret = -EOPNOTSUPP; | |
| goto exit; | |
| } | |
| switch (key->keylen) { | |
| case 16: | |
| length = HOST_CMD_AES_KEY_LEN_LENGTH_128; | |
| break; | |
| case 32: | |
| length = HOST_CMD_AES_KEY_LEN_LENGTH_256; | |
| break; | |
| default: | |
| /* Key length not supported */ | |
| ret = -EOPNOTSUPP; | |
| goto exit; | |
| } | |
| ret = mm81x_cmd_install_key(mors, mors_vif, aid, key, cipher, | |
| length); | |
| break; | |
| } | |
| case DISABLE_KEY: | |
| ret = mm81x_cmd_disable_key(mors, mors_vif, aid, key); | |
| if (ret) { | |
| /* Must return 0 */ | |
| dev_warn(mors->dev, "Failed to remove key"); | |
| ret = 0; | |
| } | |
| break; | |
| default: | |
| WARN_ON(1); | |
| } | |
| if (ret) { | |
| dev_dbg(mors->dev, "Falling back to software crypto"); | |
| ret = 1; | |
| } | |
| exit: | |
| return ret; | |
| } | |
| static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx, | |
| u32 value) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| return mm81x_cmd_set_frag_threshold(mors, value); | |
| } | |
| static u8 mm81x_rx_h_rc_bw_to_rx_bw(__le32 ratecode) | |
| { | |
| enum dot11_bandwidth bw = mm81x_ratecode_bw_index_get(ratecode); | |
| switch (bw) { | |
| case DOT11_BANDWIDTH_1MHZ: | |
| return RATE_INFO_BW_1; | |
| case DOT11_BANDWIDTH_2MHZ: | |
| return RATE_INFO_BW_2; | |
| case DOT11_BANDWIDTH_4MHZ: | |
| return RATE_INFO_BW_4; | |
| case DOT11_BANDWIDTH_8MHZ: | |
| return RATE_INFO_BW_8; | |
| default: | |
| return RATE_INFO_BW_1; | |
| } | |
| } | |
| static void mm81x_rx_h_fill_status(struct mm81x *mors, | |
| struct mm81x_skb_rx_status *hdr_rx_status, | |
| struct ieee80211_rx_status *rx_status, | |
| struct sk_buff *skb) | |
| { | |
| u32 flags = le32_to_cpu(hdr_rx_status->flags); | |
| u16 freq_100khz = le16_to_cpu(hdr_rx_status->freq_100khz); | |
| __le32 ratecode = hdr_rx_status->mm81x_ratecode; | |
| rx_status->signal = le16_to_cpu(hdr_rx_status->rssi); | |
| rx_status->encoding = RX_ENC_S1G; | |
| rx_status->band = NL80211_BAND_S1GHZ; | |
| rx_status->freq = KHZ100_TO_MHZ(freq_100khz); | |
| rx_status->freq_offset = (freq_100khz % 10) ? 1 : 0; | |
| rx_status->nss = NSS_IDX_TO_NSS(mm81x_ratecode_nss_index_get(ratecode)); | |
| if (flags & MM81X_RX_STATUS_FLAGS_DECRYPTED) | |
| rx_status->flag |= RX_FLAG_DECRYPTED; | |
| rx_status->rate_idx = mm81x_ratecode_mcs_index_get(ratecode); | |
| rx_status->bw = mm81x_rx_h_rc_bw_to_rx_bw(ratecode); | |
| if (mm81x_ratecode_sgi_get(ratecode)) | |
| rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; | |
| } | |
| static void mm81x_rx_h_update_sta(struct ieee80211_vif *vif, | |
| struct ieee80211_hdr *hdr, | |
| struct ieee80211_rx_status *rx_status) | |
| { | |
| struct ieee80211_sta *sta; | |
| struct mm81x_sta *msta; | |
| u8 *lookup = ieee80211_is_s1g_beacon(hdr->frame_control) ? hdr->addr1 : | |
| hdr->addr2; | |
| lockdep_assert_in_rcu_read_lock(); | |
| sta = ieee80211_find_sta(vif, lookup); | |
| if (!sta) | |
| return; | |
| msta = (void *)sta->drv_priv; | |
| if (msta->avg_rssi) { | |
| msta->avg_rssi = | |
| CALC_AVG_RSSI(msta->avg_rssi, rx_status->signal); | |
| } else { | |
| msta->avg_rssi = rx_status->signal; | |
| } | |
| } | |
| static struct ieee80211_vif * | |
| mm81x_rx_h_skb_get_vif(struct mm81x *mors, struct sk_buff *skb, | |
| struct mm81x_skb_rx_status *hdr_rx_status) | |
| { | |
| u8 vif_id = u32_get_bits(le32_to_cpu(hdr_rx_status->flags), | |
| MM81X_RX_STATUS_FLAGS_VIF_ID); | |
| lockdep_assert_in_rcu_read_lock(); | |
| if (vif_id == INVALID_VIF_INDEX) | |
| return NULL; | |
| return mm81x_rcu_dereference_vif_id(mors, vif_id, true); | |
| } | |
| void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb, | |
| struct mm81x_skb_rx_status *hdr_rx_status) | |
| { | |
| struct ieee80211_vif *vif; | |
| struct ieee80211_hw *hw = mors->hw; | |
| struct ieee80211_rx_status rx_status; | |
| struct ieee80211_hdr *hdr = (void *)skb->data; | |
| memset(&rx_status, 0, sizeof(rx_status)); | |
| if (!mors->started || !skb->data || !skb->len) { | |
| dev_kfree_skb_any(skb); | |
| return; | |
| } | |
| mm81x_rx_h_fill_status(mors, hdr_rx_status, &rx_status, skb); | |
| scoped_guard(rcu) { | |
| vif = mm81x_rx_h_skb_get_vif(mors, skb, hdr_rx_status); | |
| if (!vif) | |
| goto rx; | |
| mm81x_rx_h_update_sta(vif, hdr, &rx_status); | |
| } | |
| rx: | |
| memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); | |
| ieee80211_rx_ni(hw, skb); | |
| } | |
| static void mm81x_mac_flush_queues(struct mm81x *mors) | |
| { | |
| /* | |
| * No need to call mm81x_skbq_stop_tx_queues as mac80211 | |
| * has already cancelled each queue prior to calling .flush() | |
| */ | |
| mm81x_skbq_data_traffic_pause(mors); | |
| flush_work(&mors->hif_work); | |
| flush_work(&mors->tx_stale_work); | |
| mm81x_hif_clear_events(mors); | |
| mm81x_hif_flush_tx_data(mors); | |
| mm81x_hif_flush_cmds(mors); | |
| /* Re-enable data, not that there will be any */ | |
| mm81x_skbq_data_traffic_resume(mors); | |
| } | |
| static bool mm81x_mac_has_tx_pending(struct mm81x *mors) | |
| { | |
| struct mm81x_skbq *mgmt_q = mm81x_hif_get_tx_mgmt_queue(mors); | |
| struct mm81x_skbq *tx_qs; | |
| int num_qs, i; | |
| mm81x_hif_skbq_get_tx_qs(mors, &tx_qs, &num_qs); | |
| for (i = 0; i < num_qs; i++) | |
| if (mm81x_skbq_count(&tx_qs[i]) || | |
| mm81x_skbq_pending_count(&tx_qs[i])) | |
| return true; | |
| if (mm81x_skbq_count(mgmt_q) || mm81x_skbq_pending_count(mgmt_q)) | |
| return true; | |
| return false; | |
| } | |
| static void mm81x_mac_wait_queues(struct mm81x *mors) | |
| { | |
| if (!wait_event_timeout(mors->tx_empty_waitq, | |
| !mm81x_mac_has_tx_pending(mors), | |
| MM81X_FLUSH_TIMEOUT)) | |
| dev_warn(mors->dev, "Unable to empty queues before timeout"); | |
| } | |
| static void mm81x_mac_ops_flush(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, u32 queues, | |
| bool drop) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| /* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */ | |
| if (drop) | |
| mm81x_mac_flush_queues(mors); | |
| else | |
| mm81x_mac_wait_queues(mors); | |
| } | |
| static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw, | |
| int radio_idx, u32 value) | |
| { | |
| struct mm81x *mors = hw->priv; | |
| mors->rts_threshold = value; | |
| return 0; | |
| } | |
| static void mm81x_mac_ops_sta_statistics(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif, | |
| struct ieee80211_sta *sta, | |
| struct station_info *sinfo) | |
| { | |
| struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; | |
| struct mm81x *mors = hw->priv; | |
| const struct mmrc_table *tb = msta->rc.tb; | |
| struct mmrc_rate rate; | |
| if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) { | |
| sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_TX_BITRATE); | |
| return; | |
| } | |
| rate = tb->best_tp; | |
| sinfo->txrate.mcs = rate.rate; | |
| sinfo->txrate.nss = NSS_IDX_TO_NSS(rate.ss); | |
| sinfo->txrate.flags = RATE_INFO_FLAGS_S1G_MCS; | |
| switch (rate.bw) { | |
| case MMRC_BW_1MHZ: | |
| sinfo->txrate.bw = RATE_INFO_BW_1; | |
| break; | |
| case MMRC_BW_2MHZ: | |
| sinfo->txrate.bw = RATE_INFO_BW_2; | |
| break; | |
| case MMRC_BW_4MHZ: | |
| sinfo->txrate.bw = RATE_INFO_BW_4; | |
| break; | |
| case MMRC_BW_8MHZ: | |
| sinfo->txrate.bw = RATE_INFO_BW_8; | |
| break; | |
| default: | |
| break; | |
| } | |
| if (rate.guard == MMRC_GUARD_SHORT) | |
| sinfo->txrate.flags |= (RATE_INFO_FLAGS_SHORT_GI); | |
| dev_dbg(mors->dev, "mcs: %d, bw: %d, flag: 0x%x", rate.rate, rate.bw, | |
| sinfo->txrate.flags); | |
| sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BITRATE); | |
| } | |
| static u32 mm81x_get_expected_throughput(struct ieee80211_hw *hw, | |
| struct ieee80211_sta *sta) | |
| { | |
| struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv; | |
| struct mm81x *mors = hw->priv; | |
| const struct mmrc_table *tb = msta->rc.tb; | |
| struct mmrc_rate rate; | |
| u32 tput; | |
| if (!tb || tb->best_tp.rate == MMRC_MCS_UNUSED) | |
| return 0; | |
| rate = tb->best_tp; | |
| tput = BPS_TO_KBPS(mmrc_calculate_theoretical_throughput(rate)); | |
| dev_dbg(mors->dev, "Throughput: MCS: %d, BW: %d, GI: %d -> %u", | |
| rate.rate, 1 << rate.bw, rate.guard, tput); | |
| return tput; | |
| } | |
| static void mm81x_mac_restart_cleanup_iter(void *data, u8 *mac, | |
| struct ieee80211_vif *vif) | |
| { | |
| if (vif->type == NL80211_IFTYPE_AP) | |
| mm81x_mac_beacon_finish((struct mm81x_vif *)vif->drv_priv); | |
| } | |
| static void mm81x_mac_restart_cleanup(struct mm81x *mors) | |
| { | |
| ieee80211_iterate_active_interfaces(mors->hw, | |
| IEEE80211_IFACE_ITER_NORMAL, | |
| mm81x_mac_restart_cleanup_iter, | |
| NULL); | |
| mm81x_mac_hw_scan_finish(mors); | |
| } | |
| static int mm81x_mac_restart(struct mm81x *mors) | |
| { | |
| int ret; | |
| u32 chip_id; | |
| mors->started = false; | |
| mm81x_ps_disable(mors); | |
| mm81x_bus_set_irq(mors, false); | |
| mm81x_hw_irq_clear(mors); | |
| ieee80211_stop_queues(mors->hw); | |
| set_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); | |
| set_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); | |
| /* Allow time for in-transit tx/rx packets to settle */ | |
| mdelay(MM81X_HW_RESTART_DELAY_MS); | |
| flush_work(&mors->hif_work); | |
| flush_work(&mors->tx_stale_work); | |
| mm81x_hif_clear_events(mors); | |
| mm81x_hif_flush_tx_data(mors); | |
| mm81x_hif_flush_cmds(mors); | |
| mm81x_claim_bus(mors); | |
| ret = mm81x_reg32_read(mors, MM81X_REG_CHIP_ID(mors), &chip_id); | |
| mm81x_release_bus(mors); | |
| if (ret < 0) { | |
| dev_err(mors->dev, "Failed to access HW: %d", ret); | |
| goto exit; | |
| } | |
| mm81x_mac_restart_cleanup(mors); | |
| ret = mm81x_fw_init(mors, true); | |
| if (ret < 0) { | |
| dev_err(mors->dev, "Failed to init firmware: %d", ret); | |
| goto exit; | |
| } | |
| mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true); | |
| ret = mm81x_fw_parse_ext_host_tbl(mors); | |
| if (ret) { | |
| dev_err(mors->dev, "failed to parse extended host table: %d", | |
| ret); | |
| goto exit; | |
| } | |
| mm81x_mac_caps_init(mors); | |
| mm81x_bus_set_irq(mors, true); | |
| clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags); | |
| clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mors->state_flags); | |
| clear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags); | |
| clear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags); | |
| mm81x_mac_check_fw_disabled_chans(mors->hw); | |
| ieee80211_restart_hw(mors->hw); | |
| exit: | |
| mm81x_ps_enable(mors); | |
| return ret; | |
| } | |
| static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, | |
| struct ieee80211_vif *vif) | |
| { | |
| int ret = 0; | |
| struct mm81x *mors = hw->priv; | |
| struct mm81x_vif *mors_vif = (struct mm81x_vif *)vif->drv_priv; | |
| if (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags)) { | |
| dev_info(mors->dev, "Restarting chip with regdom: %s", | |
| mors->country); | |
| ret = mm81x_mac_restart(mors); | |
| if (ret) { | |
| dev_err(mors->dev, "Failed to restart chip"); | |
| return ret; | |
| } | |
| /* | |
| * mac_restart will trigger ieee80211_hw_restart and | |
| * add_interface will re-enter. just exit here instead. | |
| */ | |
| return 0; | |
| } | |
| vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; | |
| mors_vif->mors = mors; | |
| ret = mm81x_cmd_add_if(mors, &mors_vif->id, vif->addr, vif->type); | |
| if (ret) { | |
| dev_err(mors->dev, "mm81x_cmd_add_if failed %d", ret); | |
| return ret; | |
| } | |
| if (mors_vif->id >= ARRAY_SIZE(mors->vifs)) { | |
| dev_err(mors->dev, "vif_id is too large %u", mors_vif->id); | |
| ret = -EOPNOTSUPP; | |
| return ret; | |
| } | |
| if (mors_vif->id != (mors_vif->id & MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) { | |
| dev_err(mors->dev, "invalid vif_id %u", mors_vif->id); | |
| ret = -EOPNOTSUPP; | |
| return ret; | |
| } | |
| rcu_assign_pointer(mors->vifs[mors_vif->id], vif); | |
| if (vif->type == NL80211_IFTYPE_AP) | |
| mm81x_mac_beacon_init(mors_vif); | |
| ret = mm81x_cmd_get_capabilities(mors, mors_vif->id, &mors->fw_caps); | |
| if (ret) { | |
| dev_err(mors->dev, | |
| "mm81x_cmd_get_capabilities failed for vif %d", | |
| mors_vif->id); | |
| return ret; | |
| } | |
| ieee80211_wake_queues(mors->hw); | |
| return ret; | |
| } | |
| static const struct ieee80211_ops mm81x_ops = { | |
| .start = mm81x_mac_ops_start, | |
| .stop = mm81x_mac_ops_stop, | |
| .config = mm81x_mac_ops_config, | |
| .wake_tx_queue = ieee80211_handle_wake_tx_queue, | |
| .tx = mm81x_mac_ops_tx, | |
| .add_interface = mm81x_mac_ops_add_interface, | |
| .remove_interface = mm81x_mac_ops_remove_interface, | |
| .configure_filter = mm81x_mac_ops_configure_filter, | |
| .sta_state = mm81x_mac_ops_sta_state, | |
| .flush = mm81x_mac_ops_flush, | |
| .set_frag_threshold = mm81x_mac_set_frag_threshold, | |
| .set_rts_threshold = mm81x_mac_ops_set_rts_threshold, | |
| .sta_statistics = mm81x_mac_ops_sta_statistics, | |
| .get_expected_throughput = mm81x_get_expected_throughput, | |
| .hw_scan = mm81x_mac_ops_hw_scan, | |
| .cancel_hw_scan = mm81x_mac_ops_cancel_hw_scan, | |
| .get_txpower = mm81x_mac_ops_get_txpower, | |
| .bss_info_changed = mm81x_mac_ops_bss_info_changed, | |
| .prepare_multicast = mm81x_mac_ops_prepare_multicast, | |
| .conf_tx = mm81x_mac_ops_conf_tx, | |
| .ampdu_action = mm81x_mac_ops_ampdu_action, | |
| .set_key = mm81x_mac_ops_set_key, | |
| .add_chanctx = ieee80211_emulate_add_chanctx, | |
| .remove_chanctx = ieee80211_emulate_remove_chanctx, | |
| .change_chanctx = ieee80211_emulate_change_chanctx, | |
| .switch_vif_chanctx = ieee80211_emulate_switch_vif_chanctx, | |
| }; | |
| static void mm81x_reg_notifier(struct wiphy *wiphy, | |
| struct regulatory_request *request) | |
| { | |
| int ret; | |
| struct mm81x *mors = wiphy_to_ieee80211_hw(wiphy)->priv; | |
| if (mm81x_reg_h_cc_equal(request->alpha2, "00") || | |
| mm81x_reg_h_cc_equal(request->alpha2, mors->country)) | |
| return; | |
| memcpy(mors->country, request->alpha2, sizeof(mors->country)); | |
| ret = mm81x_mac_restart(mors); | |
| if (ret) | |
| dev_err(mors->dev, "Failed to restart chip: %d", ret); | |
| } | |
| static void mm81x_mac_config_hw(struct mm81x *mors) | |
| { | |
| int i; | |
| struct ieee80211_hw *hw = mors->hw; | |
| struct wiphy *wiphy; | |
| for (i = 0; i < NUM_NL80211_BANDS; i++) | |
| hw->wiphy->bands[i] = NULL; | |
| hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mors_band_s1ghz; | |
| hw->wiphy->interface_modes = BIT(NL80211_IFTYPE_AP) | | |
| BIT(NL80211_IFTYPE_STATION); | |
| hw->wiphy->reg_notifier = mm81x_reg_notifier; | |
| hw->queues = MM81X_HW_QUEUE_COUNT; | |
| hw->max_rates = MM81X_HW_MAX_RATES; | |
| hw->max_report_rates = MM81X_HW_MAX_REPORT_RATES; | |
| hw->max_rate_tries = MM81X_HW_MAX_RATE_TRIES; | |
| hw->tx_sk_pacing_shift = MM81X_HW_TX_SK_PACING_SHIFT; | |
| hw->vif_data_size = sizeof(struct mm81x_vif); | |
| hw->sta_data_size = sizeof(struct mm81x_sta); | |
| hw->extra_tx_headroom = | |
| sizeof(struct mm81x_skb_hdr) + mm81x_bus_get_alignment(mors); | |
| mors->wiphy = hw->wiphy; | |
| ieee80211_hw_set(hw, SIGNAL_DBM); | |
| ieee80211_hw_set(hw, MFP_CAPABLE); | |
| ieee80211_hw_set(hw, REPORTS_TX_ACK_STATUS); | |
| ieee80211_hw_set(hw, AMPDU_AGGREGATION); | |
| ieee80211_hw_set(hw, HOST_BROADCAST_PS_BUFFERING); | |
| ieee80211_hw_set(hw, HAS_RATE_CONTROL); | |
| ieee80211_hw_set(hw, SUPPORTS_PS); | |
| ieee80211_hw_set(hw, NEED_DTIM_BEFORE_ASSOC); | |
| ieee80211_hw_set(hw, PS_NULLFUNC_STACK); | |
| ieee80211_hw_set(hw, SUPPORTS_TX_FRAG); | |
| ieee80211_hw_set(hw, SUPPORTS_NDP_BLOCKACK); | |
| SET_IEEE80211_PERM_ADDR(hw, mors->macaddr); | |
| wiphy = mors->wiphy; | |
| wiphy->flags |= WIPHY_FLAG_AP_UAPSD; | |
| wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; | |
| if (!mors->ps.enable) | |
| wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; | |
| wiphy->features |= NL80211_FEATURE_AP_MODE_CHAN_WIDTH_CHANGE | | |
| NL80211_FEATURE_TX_POWER_INSERTION; | |
| wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS); | |
| wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SET_SCAN_DWELL); | |
| wiphy->iface_combinations = mors_if_combs; | |
| wiphy->n_iface_combinations = ARRAY_SIZE(mors_if_combs); | |
| wiphy->max_scan_ie_len = MM81X_MAX_SCAN_IE_LEN; | |
| wiphy->max_scan_ssids = MM81X_MAX_SCAN_SSIDS; | |
| wiphy->signal_type = CFG80211_SIGNAL_TYPE_MBM; | |
| wiphy->max_remain_on_channel_duration = | |
| MM81X_MAX_REMAIN_ON_CHAN_DURATION; | |
| } | |
| static void mm81x_stale_tx_status_timer(struct timer_list *t) | |
| { | |
| struct mm81x *mors = timer_container_of(mors, t, stale_status.timer); | |
| spin_lock_bh(&mors->stale_status.lock); | |
| if (mm81x_hif_get_tx_status_pending_count(mors)) | |
| queue_work(mors->net_wq, &mors->tx_stale_work); | |
| spin_unlock_bh(&mors->stale_status.lock); | |
| } | |
| static void mm81x_stale_tx_status_timer_finish(struct mm81x *mors) | |
| { | |
| timer_shutdown_sync(&mors->stale_status.timer); | |
| } | |
| static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mors) | |
| { | |
| spin_lock_init(&mors->stale_status.lock); | |
| timer_setup(&mors->stale_status.timer, mm81x_stale_tx_status_timer, 0); | |
| } | |
| int mm81x_mac_register(struct mm81x *mors) | |
| { | |
| int ret; | |
| struct ieee80211_hw *hw = mors->hw; | |
| mors->tx_power_mbm = INT_MAX; | |
| mors->tx_max_power_mbm = INT_MAX; | |
| mors->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; | |
| ret = mm81x_ps_init(mors); | |
| if (ret) | |
| return ret; | |
| mm81x_mac_config_hw(mors); | |
| mm81x_mac_hw_scan_init(mors); | |
| mm81x_mac_stale_tx_status_timer_init(mors); | |
| ret = ieee80211_register_hw(hw); | |
| if (ret) { | |
| dev_err(mors->dev, "ieee80211_register_hw failed %d", ret); | |
| mm81x_mac_unregister(mors); | |
| return ret; | |
| } | |
| mm81x_rc_init(mors); | |
| /* | |
| * At this stage, we know bus and pager system interrupts are enabled. | |
| * Trigger the receive workqueue to drain any incoming chip-to-host | |
| * pending packets been pushed in the period between the firmware | |
| * initialization and interrupts being enabled. | |
| */ | |
| set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags); | |
| queue_work(mors->chip_wq, &mors->hif_work); | |
| return ret; | |
| } | |
| void mm81x_mac_unregister(struct mm81x *mors) | |
| { | |
| mm81x_ps_disable(mors); | |
| mm81x_rc_deinit(mors); | |
| mm81x_mac_hw_scan_destroy(mors); | |
| ieee80211_stop_queues(mors->hw); | |
| ieee80211_unregister_hw(mors->hw); | |
| mm81x_hif_flush_tx_data(mors); | |
| mm81x_hif_flush_cmds(mors); | |
| mm81x_stale_tx_status_timer_finish(mors); | |
| mm81x_ps_finish(mors); | |
| kfree(mors->mcast_filter); | |
| } | |
| struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev) | |
| { | |
| struct ieee80211_hw *hw; | |
| struct mm81x *mors; | |
| hw = ieee80211_alloc_hw(sizeof(*mors) + priv_size, &mm81x_ops); | |
| if (!hw) { | |
| dev_err(dev, "ieee80211_alloc_hw failed\r\n"); | |
| return NULL; | |
| } | |
| SET_IEEE80211_DEV(hw, dev); | |
| memset(hw->priv, 0, sizeof(*mors)); | |
| mors = hw->priv; | |
| mors->hw = hw; | |
| mors->dev = dev; | |
| mutex_init(&mors->cmd_lock); | |
| mutex_init(&mors->cmd_wait); | |
| init_waitqueue_head(&mors->tx_empty_waitq); | |
| return mors; | |
| } | |
| void mm81x_mac_free(struct mm81x *mors) | |
| { | |
| ieee80211_free_hw(mors->hw); | |
| } |