// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2017-2026 Morse Micro */ #include "core.h" #include #include #include #include #include #include #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 = kcalloc(chans_to_allocate, sizeof(*params->channels), GFP_KERNEL); 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_array( num_pwrs_coarse, sizeof(*params->powers_qdbm), GFP_KERNEL); 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, GFP_KERNEL); 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); }