(Patches split per file for review, see cover letter for more information) Signed-off-by: Lachlan Hodges --- drivers/net/wireless/morsemicro/mm81x/mac.c | 2642 +++++++++++++++++++ 1 file changed, 2642 insertions(+) create mode 100644 drivers/net/wireless/morsemicro/mm81x/mac.c diff --git a/drivers/net/wireless/morsemicro/mm81x/mac.c b/drivers/net/wireless/morsemicro/mm81x/mac.c new file mode 100644 index 000000000000..2465a99c8048 --- /dev/null +++ b/drivers/net/wireless/morsemicro/mm81x/mac.c @@ -0,0 +1,2642 @@ +// 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 "debug.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) + +/* 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) \ +{ \ + .band = NL80211_BAND_S1GHZ, \ + .center_freq = (frequency), \ + .freq_offset = (offset), \ + .hw_value = (channel), \ + .flags = IEEE80211_CHAN_NO_16MHZ, \ + .max_antenna_gain = 0, \ + .max_power = 30, \ +} + +static struct ieee80211_channel mm_s1ghz_channels[] = { + CHANS1G(1, 902, 500), + CHANS1G(3, 903, 500), + CHANS1G(5, 904, 500), + CHANS1G(7, 905, 500), + CHANS1G(9, 906, 500), + CHANS1G(11, 907, 500), + CHANS1G(13, 908, 500), + CHANS1G(15, 909, 500), + CHANS1G(17, 910, 500), + CHANS1G(19, 911, 500), + CHANS1G(21, 912, 500), + CHANS1G(23, 913, 500), + CHANS1G(25, 914, 500), + CHANS1G(27, 915, 500), + CHANS1G(29, 916, 500), + CHANS1G(31, 917, 500), + CHANS1G(33, 918, 500), + CHANS1G(35, 919, 500), + CHANS1G(37, 920, 500), + CHANS1G(39, 921, 500), + CHANS1G(41, 922, 500), + CHANS1G(43, 923, 500), + CHANS1G(45, 924, 500), + CHANS1G(47, 925, 500), + CHANS1G(49, 926, 500), + CHANS1G(51, 927, 500), +}; + +/* clang-format on */ + +static struct ieee80211_supported_band mm_band_s1ghz = { + .band = NL80211_BAND_S1GHZ, + .s1g_cap.s1g = true, + .channels = mm_s1ghz_channels, + .n_channels = ARRAY_SIZE(mm_s1ghz_channels), + .bitrates = NULL, + .n_bitrates = 0, + .s1g_cap.cap[4] = 0x80 /* STA type sensor only for AP & STA */ +}; + +static struct ieee80211_iface_limit mm_if_limits[] = { + { + .max = MM81X_MAX_IF, + .types = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP), + }, +}; + +static struct ieee80211_iface_combination mm_if_combs[] = { + { + .limits = mm_if_limits, + .n_limits = ARRAY_SIZE(mm_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 int mm81x_tx_h_get_prim_bw(struct cfg80211_chan_def *chandef) +{ + return chandef->s1g_primary_2mhz ? 2 : 1; +} + +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 *mm, + struct ieee80211_tx_info *info, + struct sk_buff *skb) +{ + u8 ccmp_len; + + if (!info->control.hw_key) + return ((skb->len + FCS_LEN) > mm->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) > mm->rts_threshold); +} + +static bool mm81x_tx_h_ps_filtered_for_sta(struct mm81x *mm, + struct sk_buff *skb, + struct ieee80211_sta *sta) +{ + struct mm81x_sta *mm_sta; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + + if (!sta) + return false; + + mm_sta = (struct mm81x_sta *)sta->drv_priv; + + if (!mm_sta->tx_ps_filter_en) + return false; + + mm81x_dbg(mm, MM81X_DBG_ANY, "Frame for sta[%pM] PS filtered", + mm_sta->addr); + + info->flags |= IEEE80211_TX_STAT_TX_FILTERED; + info->flags &= ~IEEE80211_TX_CTL_AMPDU; + + ieee80211_tx_status_skb(mm->hw, skb); + return true; +} + +static void mm81x_mac_check_fw_disabled_chans(struct ieee80211_hw *hw) +{ + int ret = 0; + u32 i; + struct mm81x *mm = hw->priv; + struct host_cmd_resp_get_disabled_channels *resp; + u32 resp_len = sizeof(struct host_cmd_disabled_channel_entry) * + ARRAY_SIZE(mm_s1ghz_channels) + + sizeof(*resp); + + lockdep_assert_held(&mm->lock); + + resp = kzalloc(resp_len, GFP_KERNEL); + if (!resp) { + ret = -ENOMEM; + goto out; + } + + ret = mm81x_cmd_get_disabled_channels(mm, resp, resp_len); + if (ret) + goto out; + + for (i = 0; i < ARRAY_SIZE(mm_s1ghz_channels); i++) { + struct ieee80211_channel *ch = &mm_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; + mm81x_dbg(mm, MM81X_DBG_MAC, "set NO_PRIMARY on %u KHz", + ieee80211_channel_to_khz(ch)); + } + +out: + if (ret) + mm81x_err(mm, "failed to set disabled primary channels"); + + kfree(resp); +} + +static int mm81x_mac_ops_start(struct ieee80211_hw *hw) +{ + struct mm81x *mm = hw->priv; + + mm->started = true; + return 0; +} + +static int mm81x_tx_h_get_max_bw(struct mm81x *mm) +{ + return MM81X_FW_SUPP(&mm->fw_caps, 8MHZ) ? 8 : + MM81X_FW_SUPP(&mm->fw_caps, 4MHZ) ? 4 : + MM81X_FW_SUPP(&mm->fw_caps, 2MHZ) ? 2 : + 1; +} + +static void mm81x_mac_caps_init(struct mm81x *mm) +{ + struct mm81x_fw_caps *fw_caps = &mm->fw_caps; + struct ieee80211_sta_s1g_cap *s1g = &mm_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 *mm_vif, bool enable) +{ + struct mm81x *mm = mm81x_vif_to_mm(mm_vif); + u8 beacon_irq_num = MM81X_INT_BEACON_BASE_NUM + mm_vif->id; + + enable ? set_bit(beacon_irq_num, &mm->beacon_irqs_enabled) : + clear_bit(beacon_irq_num, &mm->beacon_irqs_enabled); + + mm81x_hw_irq_enable(mm, beacon_irq_num, enable); +} + +static void mm81x_beacon_h_fill_tx_info(struct mm81x *mm, + struct mm81x_skb_tx_info *tx_info, + struct mm81x_vif *mm_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(mm_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 (mm->firmware_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_tasklet(unsigned long data) +{ + struct mm81x_vif *mm_vif = (struct mm81x_vif *)data; + struct mm81x *mm = mm81x_vif_to_mm(mm_vif); + struct mm81x_skbq *mq; + struct sk_buff *beacon; + struct ieee80211_vif *vif = mm81x_vif_to_ieee80211_vif(mm_vif); + struct mm81x_skb_tx_info tx_info = { 0 }; + int num_bcn_vifs = atomic_read(&mm->num_bcn_vifs); + + mq = mm81x_hif_get_tx_beacon_queue(mm); + if (!mq) { + mm81x_err(mm, "no matching beacon Q found"); + return; + } + + if (mm81x_skbq_count(mq) >= num_bcn_vifs) { + mm81x_err(mm, + "previous beacon not consumed, dropping req [id:%d]", + mm_vif->id); + return; + } + + beacon = ieee80211_beacon_get(mm->hw, vif, false); + if (!beacon) { + mm81x_err(mm, "failed to retrieve beacon"); + return; + } + + mm81x_beacon_h_fill_tx_info(mm, &tx_info, mm_vif, + mm81x_tx_h_get_prim_bw(&mm->chandef)); + mm81x_skbq_skb_tx(mq, &beacon, &tx_info, MM81X_SKB_CHAN_BEACON); +} + +void mm81x_mac_beacon_irq_handle(struct mm81x *mm, u32 status) +{ + int vif_id; + unsigned long masked_status = (status & mm->beacon_irqs_enabled) >> + MM81X_INT_BEACON_BASE_NUM; + + guard(rcu)(); + for_each_set_bit(vif_id, &masked_status, MM81X_MAX_IF) { + struct mm81x_vif *mm_vif; + struct ieee80211_vif *vif; + + vif = mm81x_rcu_dereference_vif_id(mm, vif_id, true); + if (!vif) + continue; + + mm_vif = ieee80211_vif_to_mm_vif(vif); + tasklet_schedule(&mm_vif->u.ap.beacon_tasklet); + } +} + +static void mm81x_mac_beacon_init(struct mm81x_vif *mm_vif) +{ + struct mm81x *mm = mm81x_vif_to_mm(mm_vif); + + tasklet_init(&mm_vif->u.ap.beacon_tasklet, mm81x_mac_beacon_tasklet, + (unsigned long)mm_vif); + + mm81x_mac_beacon_irq_enable(mm_vif, true); + atomic_inc(&mm->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 *mm, + 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) { + mm81x_warn( + mm, + "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 *mm = 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(mm, 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 *mm, + struct ieee80211_vif *vif) +{ + if (vif->type == NL80211_IFTYPE_STATION && + mm81x_mac_is_sta_vif_associated(vif)) + return mm->hw_scan.home_dwell_ms; + return 0; +} + +static struct mm81x_hw_scan_params * +__mm81x_hw_scan_h_init_params(struct mm81x *mm) +{ + struct mm81x_hw_scan_params *params = mm->hw_scan.params; + + if (!params) { + params = kzalloc_obj(*params, GFP_KERNEL); + if (params) + mm->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 *mm, + struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct cfg80211_scan_request *req) +{ + struct mm81x_hw_scan_params *params = mm->hw_scan.params; + + lockdep_assert_held(&mm->lock); + + params = __mm81x_hw_scan_h_init_params(mm); + if (!params) { + mm->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(mm, vif); + params->use_1mhz_probes = true; + + 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 *mm = hw->priv; + struct cfg80211_scan_request *req = &hw_req->req; + struct mm81x_hw_scan_params *params; + struct ieee80211_channel **chans = hw_req->req.channels; + + mutex_lock(&mm->lock); + + mm81x_dbg(mm, MM81X_DBG_ANY, "state %d", mm->hw_scan.state); + + if (!mm->started) { + mm81x_warn(mm, "device not ready"); + ret = -ENODEV; + goto exit; + } + + switch (mm->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + mm->hw_scan.state = HW_SCAN_STATE_RUNNING; + reinit_completion(&mm->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(mm, hw, vif, req); + if (ret) + goto exit; + + params = mm->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) { + mm81x_err(mm, "Failed to init probe req %d", ret); + goto exit; + } + } + + ret = mm81x_cmd_hw_scan(mm, params, false); + if (ret) { + mm->hw_scan.state = HW_SCAN_STATE_IDLE; + goto exit; + } + + ieee80211_queue_delayed_work( + mm->hw, &mm->hw_scan.timeout, + msecs_to_jiffies(mm81x_hw_scan_h_calc_timeout(params))); +exit: + mutex_unlock(&mm->lock); + return ret; +} + +static void mm81x_hw_scan_h_cancel(struct mm81x *mm) +{ + int ret; + struct mm81x_hw_scan_params params = { 0 }; + + mutex_lock(&mm->lock); + cancel_delayed_work_sync(&mm->hw_scan.timeout); + + switch (mm->hw_scan.state) { + case HW_SCAN_STATE_IDLE: + case HW_SCAN_STATE_ABORTING: + /* scan not running */ + mutex_unlock(&mm->lock); + return; + case HW_SCAN_STATE_RUNNING: + mm->hw_scan.state = HW_SCAN_STATE_ABORTING; + break; + } + + params.operation = MM81X_HW_SCAN_OP_STOP; + + ret = mm81x_cmd_hw_scan(mm, ¶ms, false); + + mutex_unlock(&mm->lock); + + if (ret || !mm->started || + !wait_for_completion_timeout(&mm->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 }; + + mutex_lock(&mm->lock); + ieee80211_scan_completed(mm->hw, &info); + mm->hw_scan.state = HW_SCAN_STATE_IDLE; + + mutex_unlock(&mm->lock); + } +} + +static void mm81x_mac_ops_cancel_hw_scan(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + struct mm81x *mm = hw->priv; + + cancel_delayed_work_sync(&mm->hw_scan.timeout); + mm81x_hw_scan_h_cancel(mm); +} + +static void mm81x_mac_hw_scan_done_event(struct ieee80211_hw *hw) +{ + struct mm81x *mm = hw->priv; + struct cfg80211_scan_info info = { 0 }; + + mm81x_dbg(mm, MM81X_DBG_ANY, "completing hw scan"); + + mutex_lock(&mm->lock); + + switch (mm->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: + mm->hw_scan.state = HW_SCAN_STATE_IDLE; + info.aborted = (mm->hw_scan.state == HW_SCAN_STATE_ABORTING); + } + + ieee80211_scan_completed(mm->hw, &info); +exit: + complete(&mm->hw_scan.scan_done); + mutex_unlock(&mm->lock); + cancel_delayed_work_sync(&mm->hw_scan.timeout); +} + +static void mm81x_mac_hw_scan_timeout_work(struct work_struct *work) +{ + struct mm81x *mm = + container_of(work, struct mm81x, hw_scan.timeout.work); + + mm81x_err(mm, "hw scan timed out, aborting"); + mm81x_hw_scan_h_cancel(mm); +} + +static void mm81x_mac_hw_scan_init(struct mm81x *mm) +{ + mm->hw_scan.state = HW_SCAN_STATE_IDLE; + mm->hw_scan.params = NULL; + mm->hw_scan.home_dwell_ms = MM81X_HWSCAN_DEFAULT_DWELL_ON_HOME_MS; + + init_completion(&mm->hw_scan.scan_done); + INIT_DELAYED_WORK(&mm->hw_scan.timeout, mm81x_mac_hw_scan_timeout_work); +} + +static void mm81x_mac_hw_scan_destroy(struct mm81x *mm) +{ + cancel_delayed_work_sync(&mm->hw_scan.timeout); + if (mm->hw_scan.params) + mm81x_hw_scan_h_clean_params(mm->hw_scan.params); + kfree(mm->hw_scan.params); + mm->hw_scan.params = NULL; +} + +static void mm81x_mac_hw_scan_finish(struct mm81x *mm) +{ + struct cfg80211_scan_info info = { + .aborted = true, + }; + lockdep_assert_held(&mm->lock); + + if (mm->hw_scan.state == HW_SCAN_STATE_IDLE) + return; + + ieee80211_scan_completed(mm->hw, &info); + complete(&mm->hw_scan.scan_done); + mm->hw_scan.state = HW_SCAN_STATE_IDLE; + cancel_delayed_work_sync(&mm->hw_scan.timeout); +} + +int mm81x_mac_event_recv(struct mm81x *mm, struct sk_buff *skb) +{ + int ret; + 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); + + if (!HOST_CMD_IS_EVT(event) || event_iid != 0) { + ret = -EINVAL; + goto exit; + } + + switch (event_id) { + case HOST_CMD_ID_EVT_HW_SCAN_DONE: { + mm81x_dbg(mm, MM81X_DBG_ANY, + "Event: HOST_CMD_ID_EVT_HW_SCAN_DONE Received."); + mm81x_mac_hw_scan_done_event(mm->hw); + ret = 0; + break; + } + default: + ret = 0; + break; + } + +exit: + return ret; +} + +static void mm81x_tx_h_apply_mcs10(struct mm81x *mm, + 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 *mm_sta = (struct mm81x_sta *)pubsta->drv_priv; + u8 tid = ieee80211_get_tid(hdr); + + /* we are already aggregating */ + if (mm_sta->tid_tx[tid] || mm_sta->tid_start_tx[tid]) + return; + + if (mm_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; + + mm_sta->tid_start_tx[tid] = true; + ieee80211_start_tx_ba_session(pubsta, tid, 0); +} + +int mm81x_tx_h_get_attempts(struct mm81x *mm, + 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 *mm, + 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 *mm_vif = ieee80211_vif_to_mm_vif(vif); + struct mm81x_sta *mm_sta = NULL; + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + int op_bw_mhz = cfg80211_chandef_get_width(&mm->chandef); + u8 tid = skb->priority & IEEE80211_QOS_CTL_TAG1D_MASK; + bool rts_allowed = op_bw_mhz < 8; + + if (sta) + mm_sta = (struct mm81x_sta *)sta->drv_priv; + + rts_allowed &= mm81x_tx_h_pkt_over_rts_threshold(mm, info, skb); + + mm81x_rc_sta_fill_tx_rates(mm, 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(mm, tx_info); + + tx_info->flags |= + cpu_to_le32(MM81X_TX_CONF_FLAGS_VIF_ID_SET(mm_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 (mm_sta) { + tx_info->tid_params = mm_sta->tid_params[tid]; + + if (info->flags & IEEE80211_TX_CTL_CLEAR_PS_FILT) { + if (mm_sta->tx_ps_filter_en) + mm81x_dbg(mm, MM81X_DBG_ANY, + "TX ps filter cleared sta[%pM]", + mm_sta->addr); + mm_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 *mm = 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(&mm->chandef); + struct ieee80211_sta *sta = control->sta; + int max_tx_bw = 0, sta_max_bw_mhz = 0; + + if (sta) { + struct mm81x_sta *mm_sta = (struct mm81x_sta *)sta->drv_priv; + + sta_max_bw_mhz = mm_sta->max_bw_mhz; + } + + max_tx_bw = mm81x_tx_h_get_max_bw(mm); + tx_bw_mhz = min(max_tx_bw, tx_bw_mhz); + + if (is_mgmt) + tx_bw_mhz = mm81x_tx_h_get_prim_bw(&mm->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(mm, &tx_info, skb, vif, tx_bw_mhz, sta); + + if (mm81x_tx_h_ps_filtered_for_sta(mm, skb, sta)) + return; + + if (is_mgmt) + mq = mm81x_hif_get_tx_mgmt_queue(mm); + else + mq = mm81x_hif_get_tx_data_queue(mm, + 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 *mm = hw->priv; + + mutex_lock(&mm->lock); + mm->started = false; + mutex_unlock(&mm->lock); +} + +static void mm81x_mac_beacon_finish(struct mm81x_vif *mm_vif) +{ + struct mm81x *mm = mm81x_vif_to_mm(mm_vif); + + mm81x_mac_beacon_irq_enable(mm_vif, false); + tasklet_kill(&mm_vif->u.ap.beacon_tasklet); + /* + * Side effect of the restarting required when + * reacting to regdom changes... + */ + atomic_add_unless(&mm->num_bcn_vifs, -1, 0); +} + +static void mm81x_mac_ops_remove_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret; + struct mm81x *mm = hw->priv; + struct mm81x_vif *mm_vif = (struct mm81x_vif *)vif->drv_priv; + + mutex_lock(&mm->lock); + + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_finish(mm_vif); + + ret = mm81x_cmd_rm_if(mm, mm_vif->id); + if (ret) + mm81x_err(mm, "mm81x_cmd_rm_if failed %d", ret); + + RCU_INIT_POINTER(mm->vifs[mm_vif->id], NULL); + mutex_unlock(&mm->lock); +} + +static s32 mm81x_mac_get_max_txpower(struct mm81x *mm) +{ + int ret; + s32 power_mbm; + + /* Retrieve maximum TX power the chip can transmit */ + ret = mm81x_cmd_get_max_txpower(mm, &power_mbm); + if (ret) { + mm81x_err(mm, "using default tx max power %d mBm", + MAX_TX_POWER_MBM); + return MAX_TX_POWER_MBM; + } + + mm81x_dbg(mm, MM81X_DBG_MAC, "Max tx power detected %d mBm", power_mbm); + return power_mbm; +} + +static s32 mm81x_mac_set_txpower(struct mm81x *mm, s32 power_mbm) +{ + int ret; + s32 out_power_mbm; + + if (mm->tx_max_power_mbm == INT_MAX) + mm->tx_max_power_mbm = mm81x_mac_get_max_txpower(mm); + + power_mbm = min(power_mbm, mm->tx_max_power_mbm); + if (power_mbm == mm->tx_power_mbm) + return mm->tx_power_mbm; + + ret = mm81x_cmd_set_txpower(mm, &out_power_mbm, power_mbm); + if (ret) { + mm81x_err(mm, "failed, power %d mBm ret %d", power_mbm, ret); + return mm->tx_power_mbm; + } + + if (out_power_mbm != mm->tx_power_mbm) { + mm81x_dbg(mm, MM81X_DBG_MAC, "%d -> %d mBm", mm->tx_power_mbm, + out_power_mbm); + mm->tx_power_mbm = out_power_mbm; + } + + return mm->tx_power_mbm; +} + +static int mm81x_mac_set_channel(struct mm81x *mm, 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(mm, op_chan_freq_hz, pri_1mhz_chan_idx, + op_bw_mhz, pri_bw_mhz, &mm->tx_power_mbm); + if (ret) { + mm81x_err(mm, "mm81x_cmd_set_channel() failed, ret %d", ret); + return ret; + } + + mm81x_mac_set_txpower(mm, mm->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 ieee80211_chanctx_conf *ctx) +{ + int ret; + struct mm81x *mm = hw->priv; + struct cfg80211_chan_def *chandef = &ctx->def; + 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 = mm81x_tx_h_get_prim_bw(chandef); + + mm81x_dbg(mm, MM81X_DBG_MAC, + "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(mm, freq_hz, (u8)pri_1mhz_idx, op_bw_mhz, + pri_chan_width_mhz); + if (ret) + return ret; + + memcpy(&mm->chandef, chandef, sizeof(mm->chandef)); + return 0; +} + +static int mm81x_mac_ops_add_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + int err = 0; + struct mm81x *mm = hw->priv; + + mutex_lock(&mm->lock); + err = mm81x_mac_ops_change_channel(hw, ctx); + mutex_unlock(&mm->lock); + return err; +} + +static void mm81x_mac_ops_remove_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx) +{ + /* mm81x only supports a single chanctx */ + UNUSED(hw); + UNUSED(ctx); +} + +static void mm81x_mac_ops_change_chanctx(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *ctx, + u32 changed) +{ + struct mm81x *mm = hw->priv; + + UNUSED(ctx); + + if (!mm->started) + return; + + /* + * mm81x only support changing/setting the channel + * when we create an interface. + */ + if (WARN_ON(changed & IEEE80211_CHANCTX_CHANGE_CHANNEL)) + mm81x_err(mm, "Changing channel via chanctx not supported"); +} + +static int +mm81x_mac_ops_assign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_chanctx_conf *ctx) +{ + /* mm81x only supports a single chanctx */ + UNUSED(hw); + UNUSED(vif); + UNUSED(link_conf); + UNUSED(ctx); + + return 0; +} + +static void +mm81x_mac_ops_unassign_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_chanctx_conf *ctx) +{ + /* mm81x only supports a single chanctx */ + UNUSED(hw); + UNUSED(vif); + UNUSED(link_conf); + UNUSED(ctx); +} + +static int mm81x_mac_ops_config(struct ieee80211_hw *hw, int radio_idx, + u32 changed) +{ + struct mm81x *mm = hw->priv; + struct ieee80211_conf *conf = &hw->conf; + struct ieee80211_channel *channel = conf->chandef.chan; + + if (!mm->started) + return 0; + + mutex_lock(&mm->lock); + + if ((changed & IEEE80211_CONF_CHANGE_POWER) && + !(conf->flags & IEEE80211_CONF_MONITOR)) { + s32 power_mbm = DBM_TO_MBM(conf->power_level); + + power_mbm = min(channel->max_reg_power, power_mbm); + power_mbm = mm81x_mac_set_txpower(mm, power_mbm); + conf->power_level = MBM_TO_DBM(power_mbm); + } + + mutex_unlock(&mm->lock); + 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 *mm = 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; + } + + mutex_lock(&mm->lock); + *dbm = MBM_TO_DBM(mm->tx_power_mbm); + mutex_unlock(&mm->lock); + return 0; +} + +static void mm81x_mac_config_ps(struct mm81x *mm, struct ieee80211_vif *vif) +{ + bool en_ps = vif->cfg.ps; + + if (vif->type == NL80211_IFTYPE_AP) + return; + + if (mm->config_ps == en_ps) + return; + + mm81x_dbg(mm, MM81X_DBG_MAC, "change powersave mode: %d (current %d)", + en_ps, mm->config_ps); + + mm->config_ps = en_ps; + + /* + * If we have GPIO pins wired. Let's control host-to-chip PS + * mechanism. Otherwise, ignore the command altogether. + */ + if (en_ps) { + mm81x_cmd_set_ps(mm, true); + mm81x_ps_enable(mm); + } else { + mm81x_ps_disable(mm); + mm81x_cmd_set_ps(mm, 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 *mm = hw->priv; + struct mm81x_vif *mm_vif = (struct mm81x_vif *)vif->drv_priv; + + mutex_lock(&mm->lock); + + if (changed & BSS_CHANGED_PS) + mm81x_mac_config_ps(mm, vif); + + if (changed & BSS_CHANGED_BEACON_ENABLED) { + /* start command is sent, only if it was previously stopped */ + if ((mm_vif->u.ap.beaconing_enabled && info->enable_beacon) || + !info->enable_beacon) + mm81x_cmd_config_beacon_timer(mm, mm_vif, + info->enable_beacon); + + mm_vif->u.ap.beaconing_enabled = true; + } + + if (changed & BSS_CHANGED_BEACON_INT || changed & BSS_CHANGED_SSID) { + ret = mm81x_cmd_cfg_bss(mm, mm_vif->id, info->beacon_int, + info->dtim_period, + mm81x_vif_generate_cssid(vif)); + if (ret) + mm81x_err(mm, "mm81x_cmd_cfg_bss failed %d", ret); + } + + mutex_unlock(&mm->lock); +} + +static u64 mm81x_mac_ops_prepare_multicast(struct ieee80211_hw *hw, + struct netdev_hw_addr_list *mc_list) +{ + struct mm81x *mm = 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) { + mm81x_warn( + mm, + "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) { + mm81x_dbg(mm, MM81X_DBG_ANY, + "mcast whitelist (%d): %pM", filter->count, + addr->addr); + filter->addr_list[filter->count++] = + mac2leuint32(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 *mm = hw->priv; + struct mcast_filter *cmd = (void *)(unsigned long)multicast; + struct mm81x_vif *mm_vif = NULL; + struct ieee80211_vif *vif = NULL; + int vif_id = 0; + int ret = 0; + + if (!cmd) + goto out; + + mutex_lock(&mm->lock); + kfree(mm->mcast_filter); + mm->mcast_filter = cmd; + + for (vif_id = 0; vif_id < ARRAY_SIZE(mm->vifs); vif_id++) { + vif = mm81x_rcu_dereference_vif_id(mm, vif_id, false); + if (!vif) + continue; + + mm_vif = ieee80211_vif_to_mm_vif(vif); + + ret = mm81x_cmd_cfg_multicast_filter(mm, mm_vif); + if (!ret) + continue; + + mm81x_err(mm, "Multicast filtering failed - rc=%d", ret); + mm->mcast_filter = NULL; + kfree(cmd); + break; + } + +out: + mutex_unlock(&mm->lock); + *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 *mm = hw->priv; + struct mm81x_queue_params mqp; + + mutex_lock(&mm->lock); + 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; + + mm81x_dbg(mm, MM81X_DBG_ANY, + "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(mm, &mqp); + if (ret) + mm81x_dbg(mm, MM81X_DBG_ANY, "mm81x_cmd_cfg_qos failed %d", + ret); + + mutex_unlock(&mm->lock); + 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 = 0; + struct mm81x *mm = hw->priv; + struct mm81x_vif *mm_vif = (struct mm81x_vif *)vif->drv_priv; + struct mm81x_sta *mm_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 = mm81x_mac_sta_aid(vif); + else + aid = sta->aid; + + mutex_lock(&mm->lock); + ret = mm81x_cmd_sta_state(mm, mm_vif, aid, sta, new_state); + if (ret < 0) + goto exit; + + ether_addr_copy(mm_sta->addr, sta->addr); + mm_sta->state = new_state; + + if (new_state > old_state && new_state == IEEE80211_STA_ASSOC) { + if (vif->type == NL80211_IFTYPE_AP) + mm_vif->u.ap.num_stas++; + else if (vif->type == NL80211_IFTYPE_STATION) + mm_vif->u.sta.is_assoc = true; + } + + if (new_state < old_state && new_state == IEEE80211_STA_NONE) { + if (vif->type == NL80211_IFTYPE_AP) + mm_vif->u.ap.num_stas--; + else if (vif->type == NL80211_IFTYPE_STATION) + mm_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(mm, vif, sta, old_state, new_state); + mutex_unlock(&mm->lock); + 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 *mm = hw->priv; + struct ieee80211_sta *sta = params->sta; + struct mm81x_sta *mm_sta = (struct mm81x_sta *)sta->drv_priv; + u16 buf_size = + min_t(u16, params->buf_size, DOT11AH_BA_MAX_MPDU_PER_AMPDU); + + mutex_lock(&mm->lock); + switch (params->action) { + case IEEE80211_AMPDU_TX_START: + mm81x_dbg(mm, MM81X_DBG_ANY, "%pM.%d A-MPDU TX start", + mm_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: + mm81x_dbg(mm, MM81X_DBG_ANY, "%pM.%d A-MPDU TX flush", + mm_sta->addr, tid); + mm_sta->tid_start_tx[tid] = false; + mm_sta->tid_tx[tid] = false; + mm_sta->tid_params[tid] = 0; + ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); + break; + case IEEE80211_AMPDU_TX_OPERATIONAL: + mm81x_dbg(mm, MM81X_DBG_ANY, "%pM.%d A-MPDU TX oper", + mm_sta->addr, tid); + mm_sta->tid_tx[tid] = true; + if (!buf_size) { + mm81x_err(mm, "%pM.%d A-MPDU Invalid buf size", + mm_sta->addr, tid); + break; + } + mm_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; + } + + mutex_unlock(&mm->lock); + 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 *mm = hw->priv; + struct mm81x_vif *mm_vif = (struct mm81x_vif *)vif->drv_priv; + enum host_cmd_key_cipher cipher; + enum host_cmd_aes_key_len length; + + mutex_lock(&mm->lock); + + if (vif->type == NL80211_IFTYPE_STATION) { + aid = mm81x_mac_sta_aid(vif); + } 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(mm, mm_vif, aid, key, cipher, + length); + break; + } + case DISABLE_KEY: + ret = mm81x_cmd_disable_key(mm, mm_vif, aid, key); + if (ret) { + /* Must return 0 */ + mm81x_warn(mm, "Failed to remove key"); + ret = 0; + } + break; + default: + WARN_ON(1); + } + + if (ret) { + mm81x_dbg(mm, MM81X_DBG_ANY, "Falling back to software crypto"); + ret = 1; + } + +exit: + mutex_unlock(&mm->lock); + return ret; +} + +static int mm81x_mac_set_frag_threshold(struct ieee80211_hw *hw, int radio_idx, + u32 value) +{ + int ret = -EINVAL; + struct mm81x *mm = hw->priv; + + mutex_lock(&mm->lock); + ret = mm81x_cmd_set_frag_threshold(mm, value); + mutex_unlock(&mm->lock); + return ret; +} + +static void mm81x_rx_h_fill_status(struct mm81x *mm, + struct mm81x_skb_rx_status *hdr_rx_status, + struct ieee80211_rx_status *rx_status, + struct sk_buff *skb) +{ + u8 mcs_index; + 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_VHT; + 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; + + mcs_index = mm81x_ratecode_mcs_index_get(ratecode); + rx_status->rate_idx = (mcs_index == 10) ? 0 : mcs_index; + + if (mm81x_ratecode_sgi_get(ratecode)) + rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI; +} + +/* + * The firmware passes up NULL vifs for broadcast management frames. Find + * the first interface that best fits the frame we are rx'ing. This + * has the clear downside if we have two vifs with the same interface type + * the 2nd vif will never be targeted. For now, this will have to do. + */ +static struct ieee80211_vif *mm81x_rx_h_find_bcast_vif(struct mm81x *mm, + struct sk_buff *skb) +{ + int vif_id; + struct ieee80211_vif *vif; + const struct ieee80211_hdr *hdr = (void *)skb->data; + + lockdep_assert_in_rcu_read_lock(); + + for (vif_id = 0; vif_id < ARRAY_SIZE(mm->vifs); vif_id++) { + vif = mm81x_rcu_dereference_vif_id(mm, vif_id, true); + if (!vif) + continue; + + if (!ieee80211_is_mgmt(hdr->frame_control)) + return vif; + + switch (le16_to_cpu(hdr->frame_control) & + IEEE80211_FCTL_STYPE) { + case IEEE80211_STYPE_BEACON: + if (vif->type == NL80211_IFTYPE_STATION) + return vif; + break; + case IEEE80211_STYPE_PROBE_RESP: + if (vif->type == NL80211_IFTYPE_STATION) + return vif; + break; + case IEEE80211_STYPE_PROBE_REQ: + if (vif->type == NL80211_IFTYPE_AP) + return vif; + break; + default: + return vif; + } + } + + return NULL; +} + +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 *mm, 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(); + + /* + * The firmware passes up broadcast mgmt frames such as beacons with a + * NULL VIF. Assign the correct VIF. If no matching VIF was found, the + * VIF is not yet up. + */ + if (vif_id == INVALID_VIF_INDEX) + return mm81x_rx_h_find_bcast_vif(mm, skb); + + return mm81x_rcu_dereference_vif_id(mm, vif_id, true); +} + +void mm81x_mac_rx_skb(struct mm81x *mm, struct sk_buff *skb, + struct mm81x_skb_rx_status *hdr_rx_status) +{ + struct ieee80211_vif *vif; + struct ieee80211_hw *hw = mm->hw; + struct ieee80211_rx_status rx_status; + struct ieee80211_hdr *hdr = (void *)skb->data; + + memset(&rx_status, 0, sizeof(rx_status)); + + if (!mm->started || !skb->data || !skb->len) { + dev_kfree_skb_any(skb); + return; + } + + mm81x_rx_h_fill_status(mm, hdr_rx_status, &rx_status, skb); + + scoped_guard(rcu) { + vif = mm81x_rx_h_skb_get_vif(mm, skb, hdr_rx_status); + if (!vif) { + dev_kfree_skb_any(skb); + return; + } + + mm81x_rx_h_update_sta(vif, hdr, &rx_status); + } + + memcpy(IEEE80211_SKB_RXCB(skb), &rx_status, sizeof(rx_status)); + + ieee80211_rx_irqsafe(hw, skb); +} + +static void mm81x_mac_ops_flush(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, u32 queues, + bool drop) +{ + struct mm81x *mm = hw->priv; + + /* We don't support IEEE80211_HW_QUEUE_CONTROL so flush all queues */ + if (drop) { + /* + * 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(mm); + + flush_work(&mm->hif_work); + flush_work(&mm->tx_stale_work); + + mm81x_hif_clear_events(mm); + mm81x_hif_flush_tx_data(mm); + mm81x_hif_flush_cmds(mm); + + /* Reenable data, not that there will be any */ + mm81x_skbq_data_traffic_resume(mm); + } +} + +static int mm81x_mac_ops_set_rts_threshold(struct ieee80211_hw *hw, + int radio_idx, u32 value) +{ + struct mm81x *mm = hw->priv; + + mm->rts_threshold = value; + return 0; +} + +static void mm81x_mac_ops_sta_rc_update(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_link_sta *link_sta, + u32 changed) +{ + struct mm81x *mm = hw->priv; + struct ieee80211_sta *sta = link_sta->sta; + enum ieee80211_sta_state old_state; + enum ieee80211_sta_state new_state; + + mm81x_dbg(mm, MM81X_DBG_ANY, + "Rate control config updated (changed %u, peer address %pM)", + changed, sta->addr); + + if (!(changed & IEEE80211_RC_BW_CHANGED)) + return; + + /* + * Simulate the disconnection and connection to reinitialize the sta + * in mmrc with new BW + */ + old_state = IEEE80211_STA_ASSOC; + new_state = IEEE80211_STA_NOTEXIST; + + mm81x_dbg( + mm, MM81X_DBG_ANY, + "Remove sta, old_state=%d, new_state=%d, changed=0x%x, bw_changed=%d", + old_state, new_state, changed, + (changed & IEEE80211_RC_BW_CHANGED)); + mutex_lock(&mm->lock); + + mm81x_rc_sta_state_check(mm, vif, sta, old_state, new_state); + + old_state = IEEE80211_STA_NOTEXIST; + new_state = IEEE80211_STA_ASSOC; + + mm81x_dbg(mm, MM81X_DBG_ANY, "Add sta, old_state=%d, new_state=%d", + old_state, new_state); + + mm81x_rc_sta_state_check(mm, vif, sta, old_state, new_state); + + mutex_unlock(&mm->lock); +} + +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 *mm = 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); + + mm81x_dbg(mm, MM81X_DBG_ANY, "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 *mm = 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)); + mm81x_dbg(mm, MM81X_DBG_ANY, + "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 *mm) +{ + ieee80211_iterate_active_interfaces(mm->hw, IEEE80211_IFACE_ITER_NORMAL, + mm81x_mac_restart_cleanup_iter, + NULL); + mm81x_mac_hw_scan_finish(mm); +} + +static int mm81x_mac_restart(struct mm81x *mm) +{ + int ret; + u32 chip_id; + + lockdep_assert_held(&mm->lock); + + mm->started = false; + mm81x_ps_disable(mm); + mm81x_bus_set_irq(mm, false); + mm81x_hw_irq_clear(mm); + ieee80211_stop_queues(mm->hw); + + set_bit(MM81X_STATE_DATA_TX_STOPPED, &mm->state_flags); + set_bit(MM81X_STATE_DATA_QS_STOPPED, &mm->state_flags); + + /* Allow time for in-transit tx/rx packets to settle */ + mdelay(MM81X_HW_RESTART_DELAY_MS); + flush_work(&mm->hif_work); + flush_work(&mm->tx_stale_work); + mm81x_hif_clear_events(mm); + mm81x_hif_flush_tx_data(mm); + mm81x_hif_flush_cmds(mm); + + mm81x_claim_bus(mm); + ret = mm81x_reg32_read(mm, MM81X_REG_CHIP_ID(mm), &chip_id); + mm81x_release_bus(mm); + + if (ret < 0) { + mm81x_err(mm, "Failed to access HW: %d", ret); + goto exit; + } + + mm81x_mac_restart_cleanup(mm); + + ret = mm81x_fw_init(mm, true); + if (ret < 0) { + mm81x_err(mm, "Failed to init firmware: %d", ret); + goto exit; + } + + mm81x_hw_irq_enable(mm, MM81X_INT_HW_STOP_NOTIFICATION_NUM, true); + + ret = mm81x_fw_parse_ext_host_tbl(mm); + if (ret) { + mm81x_err(mm, "failed to parse extended host table: %d", ret); + goto exit; + } + + mm81x_mac_caps_init(mm); + + mm81x_bus_set_irq(mm, true); + clear_bit(MM81X_STATE_DATA_TX_STOPPED, &mm->state_flags); + clear_bit(MM81X_STATE_DATA_QS_STOPPED, &mm->state_flags); + clear_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mm->state_flags); + clear_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mm->state_flags); + mm81x_mac_check_fw_disabled_chans(mm->hw); + ieee80211_restart_hw(mm->hw); + +exit: + mm81x_ps_enable(mm); + return ret; +} + +static int mm81x_mac_ops_add_interface(struct ieee80211_hw *hw, + struct ieee80211_vif *vif) +{ + int ret = 0; + struct mm81x *mm = hw->priv; + struct mm81x_vif *mm_vif = (struct mm81x_vif *)vif->drv_priv; + + if (test_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mm->state_flags)) { + mm81x_info(mm, "Restarting chip with regdom: %s", mm->country); + mutex_lock(&mm->lock); + + ret = mm81x_mac_restart(mm); + if (ret) { + mm81x_err(mm, "Failed to restart chip"); + goto exit; + } + + mutex_unlock(&mm->lock); + + /* + * mac_restart will trigger ieee80211_hw_restart and + * add_interface will re-enter. just exit here instead. + */ + return 0; + } + + mutex_lock(&mm->lock); + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + mm_vif->u.ap.beaconing_enabled = false; + mm_vif->mm = mm; + + ret = mm81x_cmd_add_if(mm, &mm_vif->id, vif->addr, vif->type); + if (ret) { + mm81x_err(mm, "mm81x_cmd_add_if failed %d", ret); + goto exit; + } + + if (mm_vif->id >= ARRAY_SIZE(mm->vifs)) { + mm81x_err(mm, "vif_id is too large %u", mm_vif->id); + ret = -EOPNOTSUPP; + goto exit; + } + + if (mm_vif->id != (mm_vif->id & MM81X_TX_CONF_FLAGS_VIF_ID_MASK)) { + mm81x_err(mm, "invalid vif_id %u", mm_vif->id); + ret = -EOPNOTSUPP; + goto exit; + } + + rcu_assign_pointer(mm->vifs[mm_vif->id], vif); + + if (vif->type == NL80211_IFTYPE_AP) + mm81x_mac_beacon_init(mm_vif); + + ret = mm81x_cmd_get_capabilities(mm, mm_vif->id, &mm->fw_caps); + if (ret) { + mm81x_err(mm, "mm81x_cmd_get_capabilities failed for vif %d", + mm_vif->id); + goto exit; + } + + ieee80211_wake_queues(mm->hw); +exit: + mutex_unlock(&mm->lock); + 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, + .link_sta_rc_update = mm81x_mac_ops_sta_rc_update, + .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 = mm81x_mac_ops_add_chanctx, + .remove_chanctx = mm81x_mac_ops_remove_chanctx, + .change_chanctx = mm81x_mac_ops_change_chanctx, + .assign_vif_chanctx = mm81x_mac_ops_assign_vif_chanctx, + .unassign_vif_chanctx = mm81x_mac_ops_unassign_vif_chanctx, +}; + +struct mm81x *mm81x_mac_create(size_t priv_size, struct device *dev) +{ + struct ieee80211_hw *hw; + struct mm81x *mm; + + hw = ieee80211_alloc_hw(sizeof(*mm) + 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(*mm)); + + mm = hw->priv; + mm->hw = hw; + mm->dev = dev; + mutex_init(&mm->lock); + mutex_init(&mm->cmd_lock); + mutex_init(&mm->cmd_wait); + + return mm; +} + +static void mm81x_reg_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + int ret; + struct mm81x *mm = wiphy_to_ieee80211_hw(wiphy)->priv; + + if (mm81x_reg_h_cc_equal(request->alpha2, mm->country)) + return; + + memcpy(mm->country, request->alpha2, sizeof(mm->country)); + + mutex_lock(&mm->lock); + + ret = mm81x_mac_restart(mm); + if (ret) { + mm81x_err(mm, "Failed to restart chip: %d", ret); + dump_stack(); + } + + mutex_unlock(&mm->lock); +} + +static void mm81x_mac_config_hw(struct mm81x *mm) +{ + int i; + struct ieee80211_hw *hw = mm->hw; + struct wiphy *wiphy; + + for (i = 0; i < NUM_NL80211_BANDS; i++) + hw->wiphy->bands[i] = NULL; + + hw->wiphy->bands[NL80211_BAND_S1GHZ] = &mm_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(mm); + + mm->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); + + SET_IEEE80211_PERM_ADDR(hw, mm->macaddr); + + wiphy = mm->wiphy; + + wiphy->flags |= WIPHY_FLAG_AP_UAPSD; + wiphy->flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT; + + if (!mm->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 = mm_if_combs; + wiphy->n_iface_combinations = ARRAY_SIZE(mm_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 *mm = timer_container_of(mm, t, stale_status.timer); + + spin_lock_bh(&mm->stale_status.lock); + if (mm81x_hif_get_tx_status_pending_count(mm)) + queue_work(mm->net_wq, &mm->tx_stale_work); + spin_unlock_bh(&mm->stale_status.lock); +} + +static void mm81x_stale_tx_status_timer_finish(struct mm81x *mm) +{ + timer_delete_sync_try(&mm->stale_status.timer); +} + +static void mm81x_mac_stale_tx_status_timer_init(struct mm81x *mm) +{ + spin_lock_init(&mm->stale_status.lock); + timer_setup(&mm->stale_status.timer, mm81x_stale_tx_status_timer, 0); +} + +static int mm81x_mac_init(struct mm81x *mm) +{ + int ret; + + mm->tx_power_mbm = INT_MAX; + mm->tx_max_power_mbm = INT_MAX; + mm->rts_threshold = IEEE80211_MAX_RTS_THRESHOLD; + + ret = mm81x_ps_init(mm); + if (ret < 0) + return ret; + + mm81x_mac_config_hw(mm); + mm81x_mac_hw_scan_init(mm); + mm81x_mac_stale_tx_status_timer_init(mm); + + return 0; +} + +int mm81x_mac_register(struct mm81x *mm) +{ + int ret; + struct ieee80211_hw *hw = mm->hw; + + ret = mm81x_mac_init(mm); + if (ret) { + mm81x_err(mm, "mm81x_mac_init failed %d", ret); + return ret; + } + + ret = ieee80211_register_hw(hw); + if (ret) { + mm81x_err(mm, "ieee80211_register_hw failed %d", ret); + mm81x_mac_unregister(mm); + return ret; + } + + mm81x_rc_init(mm); + + /* + * 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, &mm->hif.event_flags); + queue_work(mm->chip_wq, &mm->hif_work); + + return ret; +} + +static void mm81x_ieee80211_deinit(struct mm81x *mm) +{ + ieee80211_stop_queues(mm->hw); + ieee80211_unregister_hw(mm->hw); +} + +static void mm81x_mac_deinit(struct mm81x *mm) +{ + mm81x_ieee80211_deinit(mm); + mm81x_hif_flush_tx_data(mm); + mm81x_hif_flush_cmds(mm); +} + +void mm81x_mac_unregister(struct mm81x *mm) +{ + mm81x_ps_disable(mm); + mm81x_rc_deinit(mm); + mm81x_mac_hw_scan_destroy(mm); + mm81x_mac_deinit(mm); + mm81x_stale_tx_status_timer_finish(mm); + mm81x_ps_finish(mm); +} + +void mm81x_mac_destroy(struct mm81x *mm) +{ + ieee80211_free_hw(mm->hw); +} -- 2.43.0