/*
 * Copyright (c) 2021, The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * DOC: wlan_hdd_eht.c
 *
 * WLAN Host Device Driver file for 802.11be (Extremely High Throughput)
 * support.
 *
 */

#include "wlan_hdd_main.h"
#include "wlan_hdd_eht.h"
#include "osif_sync.h"
#include "wlan_utility.h"
#include "wlan_mlme_ucfg_api.h"
#include "qc_sap_ioctl.h"
#include "wma_api.h"
#include "wlan_osif_features.h"
#include "wlan_psoc_mlme_ucfg_api.h"

#if defined(WLAN_FEATURE_11BE) && defined(CFG80211_11BE_BASIC)
#define CHAN_WIDTH_SET_40MHZ_IN_2G \
	IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G
#define CHAN_WIDTH_SET_40MHZ_80MHZ_IN_5G \
	IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G
#define CHAN_WIDTH_SET_160MHZ_IN_5G \
	IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G
#define CHAN_WIDTH_SET_80PLUS80_MHZ_IN_5G \
	IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G

void hdd_update_tgt_eht_cap(struct hdd_context *hdd_ctx,
			    struct wma_tgt_cfg *cfg)
{
	tDot11fIEeht_cap eht_cap_ini = {0};

	ucfg_mlme_update_tgt_eht_cap(hdd_ctx->psoc, cfg);
	sme_update_tgt_eht_cap(hdd_ctx->mac_handle, cfg, &eht_cap_ini);
}

/*
 * Typical 802.11 Multi-Link element
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * | Elem ID | Elem Len |Elem ID Extn | MLink Ctrl | Common Info | Link Info |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *      1          1           1           2        Variable Len  Variable Len
 */
void wlan_hdd_get_mlo_link_id(struct hdd_beacon_data *beacon,
			      uint8_t *link_id, uint8_t *num_link)
{
	const uint8_t *mlie, *cmn_info_ie, *link_info_ie;
	uint8_t total_len, cmn_info_len, link_info_len;
	uint8_t link_len;

	mlie = wlan_get_ext_ie_ptr_from_ext_id(MLO_IE_OUI_TYPE, MLO_IE_OUI_SIZE,
					       beacon->tail, beacon->tail_len);
	if (mlie) {
		hdd_debug("ML IE found in beacon data");
		*num_link = 1;

		mlie++; /* WLAN_MAC_EID_EXT */
		total_len = *mlie++; /* length */

		cmn_info_ie = mlie + 3;
		cmn_info_len = *cmn_info_ie;

		/* 802.11 Common info sub-element in Multi-link element
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 * |Cmn info Len |MLD MAC| Link ID | .....
		 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		 *        1          6        0/1
		 */

		*link_id = *(cmn_info_ie + 1 + QDF_MAC_ADDR_SIZE);

		/* Length of link info equal total length minus below:
		 * 1-Byte Extn Ele ID
		 * 2-Byte Multi link control
		 * Length of Common info sub-element
		 */

		link_info_ie = cmn_info_ie + cmn_info_len;
		link_info_len = total_len - cmn_info_len - 3;
		while (link_info_len > 0) {
			link_info_ie++;
			link_info_len--;
			/* length of sub element ID */
			link_len = *link_info_ie++;
			link_info_len--;
			link_info_ie += link_len;
			link_info_len -= link_len;
			(*num_link)++;
		}
	} else {
		*num_link = 0;
		hdd_debug("ML IE not found in beacon data");
	}
}

void wlan_hdd_check_11be_support(struct hdd_beacon_data *beacon,
				 struct sap_config *config)
{
	const uint8_t *ie;

	ie = wlan_get_ext_ie_ptr_from_ext_id(EHT_CAP_OUI_TYPE, EHT_CAP_OUI_SIZE,
					     beacon->tail, beacon->tail_len);
	if (ie)
		config->SapHw_mode = eCSR_DOT11_MODE_11be;
}

static void
hdd_update_wiphy_eht_caps_6ghz(struct hdd_context *hdd_ctx,
			       tDot11fIEeht_cap eht_cap)
{
	struct ieee80211_supported_band *band_6g =
		   hdd_ctx->wiphy->bands[HDD_NL80211_BAND_6GHZ];
	uint8_t *phy_info =
		    hdd_ctx->iftype_data_6g->eht_cap.eht_cap_elem.phy_cap_info;
	struct ieee80211_sband_iftype_data *iftype_sta;
	struct ieee80211_sband_iftype_data *iftype_ap;

	if (!band_6g || !phy_info) {
		hdd_debug("6ghz not supported in wiphy");
		return;
	}

	hdd_ctx->iftype_data_6g->types_mask =
		(BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP));
	band_6g->n_iftype_data = EHT_OPMODE_SUPPORTED;
	band_6g->iftype_data = hdd_ctx->iftype_data_6g;
	iftype_sta = hdd_ctx->iftype_data_6g;
	iftype_ap = hdd_ctx->iftype_data_6g + 1;


	hdd_ctx->iftype_data_6g->eht_cap.has_eht = eht_cap.present;
	if (hdd_ctx->iftype_data_6g->eht_cap.has_eht &&
	    !hdd_ctx->iftype_data_6g->he_cap.has_he) {
		hdd_debug("6 GHz HE caps not present");
		hdd_ctx->iftype_data_6g->eht_cap.has_eht = false;
		band_6g->n_iftype_data = 1;
		return;
	}

	if (eht_cap.support_320mhz_6ghz)
		phy_info[0] |= IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;

	if (eht_cap.su_beamformer)
		phy_info[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER;

	if (eht_cap.su_beamformee)
		phy_info[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE;

	qdf_mem_copy(iftype_ap, hdd_ctx->iftype_data_6g,
		     sizeof(struct ieee80211_supported_band));

	iftype_sta->types_mask = BIT(NL80211_IFTYPE_STATION);
	iftype_ap->types_mask = BIT(NL80211_IFTYPE_AP);
}

#ifdef CFG80211_RU_PUNCT_SUPPORT
static void hdd_update_wiphy_punct_support(struct hdd_context *hdd_ctx)
{
	/*
	 * ru_punct_supp_bw is the minimum BW of puncture.
	 * If it is set to 80, then 160 and 320 puncture bandwidth will also be
	 * supported in this case.
	 * If it is set to 320, then only 320 puncture bandwidth is supported.
	 */
	hdd_ctx->wiphy->ru_punct_supp_bw = NL80211_RU_PUNCT_SUPP_BW_80;
	hdd_debug("ru_punct_supp_bw: %d", hdd_ctx->wiphy->ru_punct_supp_bw);
}
#else
static void hdd_update_wiphy_punct_support(struct hdd_context *hdd_ctx)
{
}
#endif

void hdd_update_wiphy_eht_cap(struct hdd_context *hdd_ctx)
{
	tDot11fIEeht_cap eht_cap_cfg;
	struct ieee80211_supported_band *band_2g =
			hdd_ctx->wiphy->bands[HDD_NL80211_BAND_2GHZ];
	struct ieee80211_supported_band *band_5g =
			hdd_ctx->wiphy->bands[HDD_NL80211_BAND_5GHZ];
	QDF_STATUS status;
	uint8_t *phy_info_5g =
		    hdd_ctx->iftype_data_5g->eht_cap.eht_cap_elem.phy_cap_info;
	uint8_t *phy_info_2g =
		    hdd_ctx->iftype_data_2g->eht_cap.eht_cap_elem.phy_cap_info;
	bool eht_capab;
	struct ieee80211_sband_iftype_data *iftype_sta;
	struct ieee80211_sband_iftype_data *iftype_ap;

	hdd_enter();

	ucfg_psoc_mlme_get_11be_capab(hdd_ctx->psoc, &eht_capab);
	if (!eht_capab)
		return;

	status = ucfg_mlme_cfg_get_eht_caps(hdd_ctx->psoc, &eht_cap_cfg);
	if (QDF_IS_STATUS_ERROR(status))
		return;

	if (eht_cap_cfg.present)
		hdd_update_wiphy_punct_support(hdd_ctx);

	if (band_2g) {
		iftype_sta = hdd_ctx->iftype_data_2g;
		iftype_ap = hdd_ctx->iftype_data_2g + 1;
		hdd_ctx->iftype_data_2g->types_mask =
			(BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP));
		band_2g->n_iftype_data = EHT_OPMODE_SUPPORTED;
		band_2g->iftype_data = hdd_ctx->iftype_data_2g;

		hdd_ctx->iftype_data_2g->eht_cap.has_eht = eht_cap_cfg.present;
		if (hdd_ctx->iftype_data_2g->eht_cap.has_eht &&
		    !hdd_ctx->iftype_data_2g->he_cap.has_he) {
			hdd_debug("2.4 GHz HE caps not present");
			hdd_ctx->iftype_data_2g->eht_cap.has_eht = false;
			band_2g->n_iftype_data = 1;
			goto band_5ghz;
		}

		if (eht_cap_cfg.su_beamformer)
			phy_info_2g[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER;

		if (eht_cap_cfg.su_beamformee)
			phy_info_2g[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE;

		qdf_mem_copy(iftype_ap, hdd_ctx->iftype_data_2g,
			     sizeof(struct ieee80211_supported_band));

		iftype_sta->types_mask = BIT(NL80211_IFTYPE_STATION);
		iftype_ap->types_mask = BIT(NL80211_IFTYPE_AP);
	}

band_5ghz:
	if (band_5g) {
		iftype_sta = hdd_ctx->iftype_data_5g;
		iftype_ap = hdd_ctx->iftype_data_5g + 1;
		hdd_ctx->iftype_data_5g->types_mask =
			(BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP));
		band_5g->n_iftype_data = EHT_OPMODE_SUPPORTED;
		band_5g->iftype_data = hdd_ctx->iftype_data_5g;

		hdd_ctx->iftype_data_5g->eht_cap.has_eht = eht_cap_cfg.present;
		if (hdd_ctx->iftype_data_5g->eht_cap.has_eht &&
		    !hdd_ctx->iftype_data_5g->he_cap.has_he) {
			hdd_debug("5 GHz HE caps not present");
			hdd_ctx->iftype_data_5g->eht_cap.has_eht = false;
			band_5g->n_iftype_data = 1;
			goto band_6ghz;
		}

		if (eht_cap_cfg.su_beamformer)
			phy_info_5g[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER;

		if (eht_cap_cfg.su_beamformee)
			phy_info_5g[0] |= IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE;

		qdf_mem_copy(iftype_ap, hdd_ctx->iftype_data_5g,
			     sizeof(struct ieee80211_supported_band));

		iftype_sta->types_mask = BIT(NL80211_IFTYPE_STATION);
		iftype_ap->types_mask = BIT(NL80211_IFTYPE_AP);
	}

band_6ghz:
	hdd_update_wiphy_eht_caps_6ghz(hdd_ctx, eht_cap_cfg);

	hdd_exit();
}

int hdd_set_11be_rate_code(struct hdd_adapter *adapter, uint16_t rate_code)
{
	uint8_t preamble = 0, nss = 0, rix = 0;
	int ret;
	struct sap_config *sap_config = NULL;

	if (adapter->device_mode == QDF_SAP_MODE)
		sap_config = &adapter->deflink->session.ap.sap_config;

	if (!sap_config) {
		if (!sme_is_feature_supported_by_fw(DOT11BE)) {
			hdd_err_rl("Target does not support 11be");
			return -EIO;
		}
	} else if (sap_config->SapHw_mode != eCSR_DOT11_MODE_11be &&
		   sap_config->SapHw_mode != eCSR_DOT11_MODE_11be_ONLY) {
		hdd_err_rl("Invalid hw mode, SAP hw_mode= 0x%x, ch_freq = %d",
			   sap_config->SapHw_mode, sap_config->chan_freq);
		return -EIO;
	}

	if ((rate_code >> 8) != WMI_RATE_PREAMBLE_EHT) {
		hdd_err_rl("Invalid input: %x", rate_code);
		return -EIO;
	}

	rix = RC_2_RATE_IDX_11BE(rate_code);
	preamble = rate_code >> 8;
	nss = HT_RC_2_STREAMS_11BE(rate_code) + 1;

	hdd_debug("SET_11BE_RATE rate_code %d rix %d preamble %x nss %d",
		  rate_code, rix, preamble, nss);

	ret = wma_cli_set_command(adapter->deflink->vdev_id,
				  wmi_vdev_param_fixed_rate,
				  rate_code, VDEV_CMD);

	return ret;
}

/**
 * hdd_map_eht_gi_to_os() - map txrate_gi to os guard interval
 * @guard_interval: guard interval get from fw rate
 *
 * Return: os guard interval value
 */
static inline uint8_t hdd_map_eht_gi_to_os(enum txrate_gi guard_interval)
{
	switch (guard_interval) {
	case TXRATE_GI_0_8_US:
		return NL80211_RATE_INFO_EHT_GI_0_8;
	case TXRATE_GI_1_6_US:
		return NL80211_RATE_INFO_EHT_GI_1_6;
	case TXRATE_GI_3_2_US:
		return NL80211_RATE_INFO_EHT_GI_3_2;
	default:
		return NL80211_RATE_INFO_EHT_GI_0_8;
	}
}

/**
 * wlan_hdd_fill_os_eht_rateflags() - Fill EHT related rate_info
 * @os_rate: rate info for os
 * @rate_flags: rate flags
 * @dcm: dcm from rate
 * @guard_interval: guard interval from rate
 *
 * Return: none
 */
void wlan_hdd_fill_os_eht_rateflags(struct rate_info *os_rate,
				    enum tx_rate_info rate_flags,
				    uint8_t dcm,
				    enum txrate_gi guard_interval)
{
	/* as fw not yet report ofdma to host, so don't
	 * fill RATE_INFO_BW_EHT_RU.
	 */
	if (rate_flags & (TX_RATE_EHT80 | TX_RATE_EHT40 |
	    TX_RATE_EHT20 | TX_RATE_EHT160 | TX_RATE_EHT320)) {
		if (rate_flags & TX_RATE_EHT320)
			hdd_set_rate_bw(os_rate, HDD_RATE_BW_320);
		else if (rate_flags & TX_RATE_EHT160)
			hdd_set_rate_bw(os_rate, HDD_RATE_BW_160);
		else if (rate_flags & TX_RATE_EHT80)
			hdd_set_rate_bw(os_rate, HDD_RATE_BW_80);
		else if (rate_flags & TX_RATE_EHT40)
			hdd_set_rate_bw(os_rate, HDD_RATE_BW_40);

		os_rate->flags |= RATE_INFO_FLAGS_EHT_MCS;
	}
}

#ifdef FEATURE_RX_LINKSPEED_ROAM_TRIGGER
void
wlan_hdd_refill_os_eht_rateflags(struct rate_info *os_rate, uint8_t preamble)
{
	if (preamble == DOT11_BE)
		os_rate->flags |= RATE_INFO_FLAGS_EHT_MCS;
}

void
wlan_hdd_refill_os_eht_bw(struct rate_info *os_rate, enum rx_tlv_bw bw)
{
	if (bw == RX_TLV_BW_320MHZ)
		os_rate->bw = RATE_INFO_BW_320;
	else
		os_rate->bw = RATE_INFO_BW_20; /* Invalid bw: set 20M */
}
#endif
#endif
