/******************************************************************************
 *
 * This file is provided under a dual BSD/GPLv2 license.  When using or
 * redistributing this file, you may do so under either license.
 *
 * GPL LICENSE SUMMARY
 *
 * Copyright(c) 2015 - 2017 Intel Deutschland GmbH
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
 * USA
 *
 * The full GNU General Public License is included in this distribution
 * in the file called COPYING.
 *
 * Contact Information:
 * Intel Linux Wireless <linuxwifi@intel.com>
 * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
 *
 * BSD LICENSE
 *
 * Copyright(c) 2015 - 2017 Intel Deutschland GmbH
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/
#include <net/cfg80211.h>
#include <linux/etherdevice.h>
#include <linux/math64.h>
#include "mvm.h"
#include "iwl-io.h"
#include "iwl-prph.h"
#include "fw/api/tof.h"

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
static u32 tof_tsf_addr_hash(const void *key, u32 length, u32 seed)
{
	return jhash(key, ETH_ALEN, seed);
}

static const struct rhashtable_params tsf_rht_params = {
	.automatic_shrinking = true,
	.head_offset = offsetof(struct iwl_mvm_tof_tsf_entry, hash_node),
	.key_offset = offsetof(struct iwl_mvm_tof_tsf_entry, bssid),
	.key_len = ETH_ALEN,
	.hashfn = tof_tsf_addr_hash,
};
#endif

void iwl_mvm_tof_init(struct iwl_mvm *mvm)
{
	struct iwl_mvm_tof_data *tof_data = &mvm->tof_data;

	if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT))
		return;

	memset(tof_data, 0, sizeof(*tof_data));

#ifdef CPTCFG_IWLWIFI_DEBUGFS
	if (IWL_MVM_TOF_IS_RESPONDER) {
		tof_data->responder_cfg.sta_id = IWL_MVM_INVALID_STA;
	}
#endif

	tof_data->range_req.req_timeout = 1;
	tof_data->range_req.initiator = 1;
	tof_data->range_req.report_policy = IWL_MVM_TOF_RESPONSE_COMPLETE;

	tof_data->range_req_ext.tsf_timer_offset_msec =
		cpu_to_le16(IWL_MVM_FTM_REQ_EXT_TSF_TIMER_OFFSET_MSEC_DFLT);
	tof_data->range_req_ext.min_delta_ftm =
		IWL_MVM_FTM_REQ_EXT_MIN_DELTA_FTM_DFLT;
	tof_data->range_req_ext.ftm_format_and_bw20M =
		IWL_MVM_FTM_REQ_EXT_FORMAT_AND_BW20M_DFLT;
	tof_data->range_req_ext.ftm_format_and_bw40M =
		IWL_MVM_FTM_REQ_EXT_FORMAT_AND_BW40M_DFLT;
	tof_data->range_req_ext.ftm_format_and_bw80M =
		IWL_MVM_FTM_REQ_EXT_FORMAT_AND_BW80M_DFLT;

	mvm->tof_data.active_request_id = IWL_MVM_TOF_RANGE_REQ_MAX_ID;
	mvm->tof_data.active_cookie = 0;
	mvm->tof_data.enable_dyn_ack = 1;

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
	{
		if (rhashtable_init(&tof_data->tsf_hash, &tsf_rht_params))
			IWL_ERR(mvm, "TSF hashtable init failed\n");
		else
			tof_data->tsf_hash_valid = true;
	}
#endif

	INIT_LIST_HEAD(&tof_data->lci_civic_info);
	mvm->init_status |= IWL_MVM_INIT_STATUS_TOF_INIT_COMPLETE;
}

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
void iwl_mvm_tof_update_tsf(struct iwl_mvm *mvm, struct iwl_rx_packet *pkt)
{
	u32 delta, ts;
	u8 delta_sign;
	struct iwl_mvm_tof_tsf_entry *tsf_entry;
	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(pkt->data +
					sizeof(struct iwl_rx_mpdu_res_start));
	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)hdr;

	if (!mvm->tof_data.tsf_hash_valid)
		return;

	ts = (u32)le64_to_cpu(mgmt->u.beacon.timestamp);
	if (ts > le32_to_cpu(mvm->last_phy_info.system_timestamp)) {
		delta = ts - le32_to_cpu(mvm->last_phy_info.system_timestamp);
		delta_sign = 0;
	} else {
		delta = le32_to_cpu(mvm->last_phy_info.system_timestamp) - ts;
		delta_sign = 1;
	}

	/* try to find this bss in the hash table */
	tsf_entry = rhashtable_lookup_fast(&mvm->tof_data.tsf_hash,
					   hdr->addr3, tsf_rht_params);
	if (tsf_entry) {
		tsf_entry->delta = delta;
		tsf_entry->delta_sign = delta_sign;
		return;
	}

	/* the bss is not found in the hash table */
	tsf_entry = kmalloc(sizeof(*tsf_entry), GFP_ATOMIC);
	if (!tsf_entry)
		return;

	tsf_entry->delta = delta;
	tsf_entry->delta_sign = delta_sign;
	ether_addr_copy(tsf_entry->bssid, hdr->addr3);

	rhashtable_insert_fast(&mvm->tof_data.tsf_hash, &tsf_entry->hash_node,
			       tsf_rht_params);
}

static void iwl_mvm_tof_range_req_fill_tsf(struct iwl_mvm *mvm)
{
	int i;
	struct iwl_tof_range_req_cmd *cmd = &mvm->tof_data.range_req;
	struct iwl_mvm_tof_tsf_entry *tsf_entry;

	if (!mvm->tof_data.tsf_hash_valid)
		return;

	for (i = 0; i < cmd->num_of_ap; i++) {
		tsf_entry = rhashtable_lookup_fast(&mvm->tof_data.tsf_hash,
						   cmd->ap[i].bssid,
						   tsf_rht_params);
		if (tsf_entry) {
			cmd->ap[i].tsf_delta = cpu_to_le32(tsf_entry->delta);
			cmd->ap[i].tsf_delta_direction = tsf_entry->delta_sign;
		} else {
			IWL_INFO(mvm, "Cannot find BSSID %pM\n",
				 cmd->ap[i].bssid);
			cmd->ap[i].tsf_delta = 0;
			cmd->ap[i].tsf_delta_direction = 0;
		}
	}
}

static void iwl_mvm_tsf_hash_free_elem(void *ptr, void *arg)
{
	kfree(ptr);
}
#endif

static void iwl_mvm_tof_clean_lci_civic(struct iwl_mvm_tof_data *data)
{
	struct lci_civic_entry *cur, *prev;

	list_for_each_entry_safe(cur, prev, &data->lci_civic_info, list) {
		list_del(&cur->list);
		kfree(cur);
	}
}

static void iwl_mvm_tof_reset_active(struct iwl_mvm *mvm)
{
	mvm->tof_data.active_request_id = IWL_MVM_TOF_RANGE_REQ_MAX_ID;
	mvm->tof_data.active_cookie = 0;
	kfree(mvm->tof_data.active_request.targets);
	mvm->tof_data.active_request.targets = NULL;
	memset(&mvm->tof_data.active_bssid_for_tsf, 0, ETH_ALEN);
	iwl_mvm_tof_clean_lci_civic(&mvm->tof_data);
}

void iwl_mvm_tof_clean(struct iwl_mvm *mvm)
{
	struct iwl_mvm_tof_data *tof_data = &mvm->tof_data;

	if (!fw_has_capa(&mvm->fw->ucode_capa,
			 IWL_UCODE_TLV_CAPA_TOF_SUPPORT) ||
	    !(mvm->init_status & IWL_MVM_INIT_STATUS_TOF_INIT_COMPLETE))
		return;

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
	if (tof_data->tsf_hash_valid)
		rhashtable_free_and_destroy(&tof_data->tsf_hash,
					    iwl_mvm_tsf_hash_free_elem, NULL);
#endif

	kfree(tof_data->active_request.targets);
	iwl_mvm_tof_clean_lci_civic(tof_data);
	memset(tof_data, 0, sizeof(*tof_data));
	mvm->tof_data.active_request_id = IWL_MVM_TOF_RANGE_REQ_MAX_ID;
	mvm->init_status &= ~IWL_MVM_INIT_STATUS_TOF_INIT_COMPLETE;
}

static void iwl_tof_iterator(void *_data, u8 *mac,
			     struct ieee80211_vif *vif)
{
	bool *enabled = _data;

	/* non bss vif exists */
	if (ieee80211_vif_type_p2p(vif) !=  NL80211_IFTYPE_STATION)
		*enabled = false;
}

int iwl_mvm_tof_config_cmd(struct iwl_mvm *mvm)
{
	struct iwl_tof_config_cmd *cmd = &mvm->tof_data.tof_cfg;
	bool enabled;

	lockdep_assert_held(&mvm->mutex);

	if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT))
		return -EINVAL;

	ieee80211_iterate_active_interfaces_atomic(mvm->hw,
						   IEEE80211_IFACE_ITER_NORMAL,
						   iwl_tof_iterator, &enabled);
	if (!enabled) {
		IWL_DEBUG_INFO(mvm, "ToF is not supported (non bss vif)\n");
		return -EINVAL;
	}

	mvm->tof_data.active_request_id = IWL_MVM_TOF_RANGE_REQ_MAX_ID;
	mvm->tof_data.active_cookie = 0;
	return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_CONFIG_CMD,
						    TOF_GROUP, 0),
				    0, sizeof(*cmd), cmd);
}

int iwl_mvm_tof_range_abort_cmd(struct iwl_mvm *mvm, u8 id)
{
	struct iwl_tof_range_abort_cmd cmd = {
		.request_id = id,
	};

	lockdep_assert_held(&mvm->mutex);

	IWL_DEBUG_INFO(mvm, "Sending ToF abort command\n");

	if (!fw_has_capa(&mvm->fw->ucode_capa,
			 IWL_UCODE_TLV_CAPA_TOF_SUPPORT)) {
		IWL_ERR(mvm, "%s: ToF is not supported!\n", __func__);
		return -EINVAL;
	}

	if (id != mvm->tof_data.active_request_id) {
		IWL_ERR(mvm, "Invalid range request id %d (active %d)\n",
			id, mvm->tof_data.active_request_id);
		return -EINVAL;
	}

	/* after abort is sent there's no active request anymore */
	iwl_mvm_tof_reset_active(mvm);

	return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_RANGE_ABORT_CMD,
						    TOF_GROUP, 0),
				    0, sizeof(cmd), &cmd);
}

/* Initializes responder_cfg command. (TOF_RESPONDER_CONFIG_CMD_API in FW) */
static void
iwl_mvm_tof_set_responder(struct iwl_mvm *mvm,
			  struct ieee80211_vif *vif,
			  struct cfg80211_ftm_responder_params *params,
			  struct cfg80211_chan_def *def)
{
	struct iwl_tof_responder_config_cmd *cmd = &mvm->tof_data.responder_cfg;

	memset(cmd, 0, sizeof(*cmd));

	cmd->channel_num = def->chan->hw_value;

	switch (def->width) {
	case NL80211_CHAN_WIDTH_20_NOHT:
		cmd->bandwidth = IWL_TOF_BW_20_LEGACY;
		break;
	case NL80211_CHAN_WIDTH_20:
		cmd->bandwidth = IWL_TOF_BW_20_HT;
		break;
	case NL80211_CHAN_WIDTH_40:
		cmd->bandwidth = IWL_TOF_BW_40;
		if (def->center_freq1 > def->chan->center_freq)
			cmd->ctrl_ch_position = 1;
		break;
	case NL80211_CHAN_WIDTH_80:
		cmd->bandwidth = IWL_TOF_BW_80;
		if (def->center_freq1 > def->chan->center_freq)
			cmd->ctrl_ch_position = 2;
		if (abs((int)def->center_freq1 -
			 (int)def->chan->center_freq) > 20)
			cmd->ctrl_ch_position += 1;
		break;
	default:
		WARN_ON(1);
	}

	cmd->cmd_valid_fields =
		cpu_to_le32(IWL_TOF_RESPONDER_CMD_VALID_CHAN_INFO |
			   /* ftm_resp_asap == true means asap ONLY mode,
			    * meaning non-ASAP not supported.
			    */
			   (iwlmvm_mod_params.ftm_resp_asap ?
			    0 : IWL_TOF_RESPONDER_CMD_VALID_NON_ASAP_SUPPORT));

	cmd->responder_cfg_flags =
		cpu_to_le32(iwlmvm_mod_params.ftm_resp_asap ?
			    0 : IWL_TOF_RESPONDER_FLAGS_NON_ASAP_SUPPORT);
}

int iwl_mvm_tof_responder_cmd(struct iwl_mvm *mvm,
			      struct ieee80211_vif *vif)
{
	struct iwl_tof_responder_config_cmd *cmd = &mvm->tof_data.responder_cfg;
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);

	lockdep_assert_held(&mvm->mutex);

	if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT))
		return -EINVAL;

	if (vif->p2p || vif->type != NL80211_IFTYPE_AP ||
	    !mvmvif->ap_ibss_active) {
		IWL_ERR(mvm, "Cannot start responder, not in AP mode\n");
		return -EIO;
	}

	/* sta_id and mac address are always present in the responder
	 * configuration cmd
	 */
	cmd->sta_id = mvmvif->bcast_sta.sta_id;
	memcpy(cmd->bssid, vif->addr, ETH_ALEN);
	cmd->cmd_valid_fields |= cpu_to_le32(
					IWL_TOF_RESPONDER_CMD_VALID_BSSID |
					IWL_TOF_RESPONDER_CMD_VALID_STA_ID);
	return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_RESPONDER_CONFIG_CMD,
						    TOF_GROUP, 0),
				    0, sizeof(*cmd), cmd);
}

static void
iwl_mvm_tof_set_responder_dyn(struct iwl_mvm *mvm,
			      struct ieee80211_vif *vif,
			      struct cfg80211_ftm_responder_params *params)
{
	struct iwl_tof_responder_dyn_config_cmd *cmd =
					&mvm->tof_data.responder_dyn_cfg;
	int aligned = ALIGN(params->lci_len + 2, 4);

	if (aligned + 2 + params->civic_len > IWL_TOF_LCI_CIVIC_BUF_SIZE)
		return;

	memset(cmd, 0, sizeof(*cmd));

	cmd->lci_len = cpu_to_le32(params->lci_len + 2);
	cmd->civic_len = cpu_to_le32(params->civic_len + 2);

	cmd->lci_civic[0] = WLAN_EID_MEASURE_REPORT;
	cmd->lci_civic[1] = params->lci_len;
	memcpy(cmd->lci_civic + 2, params->lci, params->lci_len);

	cmd->lci_civic[aligned] = WLAN_EID_MEASURE_REPORT;
	cmd->lci_civic[aligned + 1] = params->civic_len;
	memcpy(cmd->lci_civic + aligned + 2, params->civic, params->civic_len);
}

int iwl_mvm_tof_responder_dyn_cfg_cmd(struct iwl_mvm *mvm,
				struct ieee80211_vif *vif)
{
	struct iwl_tof_responder_dyn_config_cmd *cmd =
		&mvm->tof_data.responder_dyn_cfg;
	u32 actual_lci_len =
		ALIGN(le32_to_cpu(cmd->lci_len), 4);
	u32 actual_civic_len =
		ALIGN(le32_to_cpu(cmd->civic_len), 4);

	lockdep_assert_held(&mvm->mutex);

	if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT))
		return -EINVAL;

	if (vif->p2p || vif->type != NL80211_IFTYPE_AP) {
		IWL_ERR(mvm, "Cannot start responder, not in AP mode\n");
		return -EIO;
	}

	return iwl_mvm_send_cmd_pdu(mvm,
				    iwl_cmd_id(TOF_RESPONDER_DYN_CONFIG_CMD,
					       TOF_GROUP, 0),
				    0, sizeof(*cmd) + actual_lci_len +
				    actual_civic_len, cmd);
}

int iwl_mvm_tof_start_responder(struct iwl_mvm *mvm,
				struct ieee80211_vif *vif,
				struct cfg80211_ftm_responder_params *params)
{
	struct ieee80211_chanctx_conf ctx, *pctx;
	u16 *phy_ctxt_id;
	struct iwl_mvm_phy_ctxt *phy_ctxt;
	int ret;

	lockdep_assert_held(&mvm->mutex);

	rcu_read_lock();
	pctx = rcu_dereference(vif->chanctx_conf);
	/* Copy the ctx to unlock the rcu and send the phy ctxt. We don't care
	 * about changes in the ctx after releasing the lock because the driver
	 * is still protected by the mutex. */
	ctx = *pctx;
	phy_ctxt_id  = (u16 *)pctx->drv_priv;
	rcu_read_unlock();

	phy_ctxt = &mvm->phy_ctxts[*phy_ctxt_id];
	ret = iwl_mvm_phy_ctxt_changed(mvm, phy_ctxt, &ctx.def,
				       ctx.rx_chains_static,
				       ctx.rx_chains_dynamic);
	if (ret)
		return ret;

	iwl_mvm_tof_set_responder(mvm, vif, params, &ctx.def);
	ret = iwl_mvm_tof_responder_cmd(mvm, vif);
	if (ret)
		return ret;

	if (params->lci_len || params->civic_len) {
		iwl_mvm_tof_set_responder_dyn(mvm, vif, params);
		ret = iwl_mvm_tof_responder_dyn_cfg_cmd(mvm, vif);
	}

	return ret;
}

void iwl_mvm_tof_restart_responder(struct iwl_mvm *mvm,
				   struct ieee80211_vif *vif)
{
	iwl_mvm_tof_responder_cmd(mvm, vif);
	if (mvm->tof_data.responder_dyn_cfg.lci_len ||
	    mvm->tof_data.responder_dyn_cfg.civic_len)
		iwl_mvm_tof_responder_dyn_cfg_cmd(mvm, vif);
}

int iwl_mvm_tof_perform_ftm(struct iwl_mvm *mvm, u64 cookie,
			    struct ieee80211_vif *vif,
			    struct cfg80211_ftm_request *req)
{
	struct iwl_tof_range_req_cmd *cmd = &mvm->tof_data.range_req;
	int i;
	int ret = 0;

	lockdep_assert_held(&mvm->mutex);

	/* nesting of range requests is not supported in FW */
	if (mvm->tof_data.active_request_id != IWL_MVM_TOF_RANGE_REQ_MAX_ID) {
		IWL_DEBUG_INFO(mvm,
			       "Cannot send range req, already active req %d\n",
			       mvm->tof_data.active_request_id);
		return -EBUSY;
	}

	/* FW requires sending the ext command prior to each range request */
	ret = iwl_mvm_tof_range_request_ext_cmd(mvm);
	if (ret)
		goto err;

	cmd->request_id++;
	if (cmd->request_id == 0)
		cmd->request_id++;
	cmd->one_sided_los_disable = 0;
	cmd->req_timeout = req->timeout;
	cmd->report_policy = IWL_MVM_TOF_RESPONSE_COMPLETE;
	cmd->num_of_ap = req->num_of_targets;
	cmd->macaddr_random = 1;
	memcpy(cmd->macaddr_template, req->macaddr_template, ETH_ALEN);
	for (i = 0; i < ETH_ALEN; i++)
		cmd->macaddr_mask[i] = ~req->macaddr_mask[i];

	memset(cmd->ap, 0, sizeof(cmd->ap));

	for (i = 0; i < cmd->num_of_ap; i++) {
		struct cfg80211_ftm_target *req_target = &req->targets[i];
		struct iwl_tof_range_req_ap_entry *cmd_target = &cmd->ap[i];

		cmd_target->channel_num = ieee80211_frequency_to_channel(
				req_target->chan_def.chan->center_freq);
		switch (req_target->chan_def.width) {
		case NL80211_CHAN_WIDTH_20_NOHT:
			cmd_target->bandwidth = IWL_TOF_BW_20_LEGACY;
			break;
		case NL80211_CHAN_WIDTH_20:
			cmd_target->bandwidth = IWL_TOF_BW_20_HT;
			break;
		case NL80211_CHAN_WIDTH_40:
			cmd_target->bandwidth = IWL_TOF_BW_40;
			break;
		case NL80211_CHAN_WIDTH_80:
			cmd_target->bandwidth = IWL_TOF_BW_80;
			break;
		default:
			IWL_ERR(mvm, "Unsupported BW in FTM request (%d)\n",
				req_target->chan_def.width);
			ret = -EINVAL;
			goto err;
		}
		cmd_target->ctrl_ch_position =
			(req_target->chan_def.width > NL80211_CHAN_WIDTH_20) ?
			iwl_mvm_get_ctrl_pos(&req_target->chan_def) : 0;

		cmd_target->tsf_delta_direction = 0;
		cmd_target->tsf_delta = 0;

		memcpy(cmd_target->bssid, req_target->bssid, ETH_ALEN);
		cmd_target->measure_type = req_target->one_sided;
		cmd_target->num_of_bursts = req_target->num_of_bursts_exp;
		cmd_target->burst_period =
			cpu_to_le16(req_target->burst_period);
		cmd_target->samples_per_burst = req_target->samples_per_burst;
		cmd_target->retries_per_sample = req_target->retries;
		cmd_target->asap_mode = req_target->asap;
		cmd_target->enable_dyn_ack = mvm->tof_data.enable_dyn_ack;
		cmd_target->rssi = 0;

		if (req_target->lci)
			cmd_target->location_req |= IWL_TOF_LOC_LCI;
		if (req_target->civic)
			cmd_target->location_req |= IWL_TOF_LOC_CIVIC;

		/* By default it's 0 - IWL_TOF_ALGO_TYPE_MAX_LIKE */
		cmd_target->algo_type = mvm->tof_data.tof_algo_type;
		cmd_target->notify_mcsi = IWL_TOF_MCSI_ENABLED;
	}

	mvm->tof_data.active_cookie = cookie;
	memcpy(&mvm->tof_data.active_request, req,
	       sizeof(struct cfg80211_ftm_request));
	mvm->tof_data.active_request.targets =
		kmemdup(req->targets, sizeof(struct cfg80211_ftm_target) *
			req->num_of_targets, GFP_KERNEL);
	if (!mvm->tof_data.active_request.targets) {
		ret = -ENOMEM;
		goto err;
	}
	if (vif->bss_conf.assoc && req->report_tsf)
		memcpy(mvm->tof_data.active_bssid_for_tsf, vif->bss_conf.bssid,
		       ETH_ALEN);

	if (vif->bss_conf.assoc)
		memcpy(cmd->range_req_bssid, vif->bss_conf.bssid, ETH_ALEN);
	else
		eth_broadcast_addr(cmd->range_req_bssid);

	return iwl_mvm_tof_range_request_cmd(mvm);

err:
	iwl_mvm_tof_reset_active(mvm);
	return ret;
}

int iwl_mvm_tof_abort_ftm(struct iwl_mvm *mvm, u64 cookie)
{
	lockdep_assert_held(&mvm->mutex);

	if (cookie != mvm->tof_data.active_cookie)
		return -EINVAL;

	return iwl_mvm_tof_range_abort_cmd(mvm,
					   mvm->tof_data.active_request_id);
}

static void iwl_mvm_debug_range_req(struct iwl_mvm *mvm)
{
	struct iwl_tof_range_req_cmd *req = &mvm->tof_data.range_req;
	int i;

	IWL_DEBUG_INFO(mvm, "Sending FTM request, params:\n");
	IWL_DEBUG_INFO(mvm, "\trequest id: %hhu\n", req->request_id);
	IWL_DEBUG_INFO(mvm, "\tinitiator: %hhu\n", req->initiator);
	IWL_DEBUG_INFO(mvm, "\tOSLD: %hhu\n", req->one_sided_los_disable);
	IWL_DEBUG_INFO(mvm, "\tTO: %hhu\n", req->req_timeout);
	IWL_DEBUG_INFO(mvm, "\treport policy: %hhu\n", req->report_policy);
	IWL_DEBUG_INFO(mvm, "\tnum of aps: %hhu\n", req->num_of_ap);
	IWL_DEBUG_INFO(mvm, "\tmac rand: %hhu\n", req->macaddr_random);
	IWL_DEBUG_INFO(mvm, "\tmac temp: %pM\n", req->macaddr_template);
	IWL_DEBUG_INFO(mvm, "\tmac mask: %pM\n", req->macaddr_mask);

	for (i = 0; i < req->num_of_ap; i++) {
		struct iwl_tof_range_req_ap_entry ap = req->ap[i];

		IWL_DEBUG_INFO(mvm, "ap[%d]:\n", i);
		IWL_DEBUG_INFO(mvm, "\tchannel: %hhu\n", ap.channel_num);
		IWL_DEBUG_INFO(mvm, "\tbw: %hhu\n", ap.bandwidth);
		IWL_DEBUG_INFO(mvm, "\ttsf delta direction: %hhu\n",
			       ap.tsf_delta);
		IWL_DEBUG_INFO(mvm, "\tctrl channel: %hhu\n",
			       ap.ctrl_ch_position);
		IWL_DEBUG_INFO(mvm, "\tbssid: %pM\n", ap.bssid);
		IWL_DEBUG_INFO(mvm, "\tone sided: %hhu\n", ap.measure_type);
		IWL_DEBUG_INFO(mvm, "\tnum of bursts: %hhu\n",
			       ap.num_of_bursts);
		IWL_DEBUG_INFO(mvm, "\tburst period: %hu\n",
			       le16_to_cpu(ap.burst_period));
		IWL_DEBUG_INFO(mvm, "\tsamples/burst: %hhu\n",
			       ap.samples_per_burst);
		IWL_DEBUG_INFO(mvm, "\tretries/sample: %hhu\n",
			       ap.retries_per_sample);
		IWL_DEBUG_INFO(mvm, "\ttsf delta: %u\n",
			       le32_to_cpu(ap.tsf_delta));
		IWL_DEBUG_INFO(mvm, "\tlocation: %hhu\n", ap.location_req);
		IWL_DEBUG_INFO(mvm, "\tasap: %hhu\n", ap.asap_mode);
		IWL_DEBUG_INFO(mvm, "\tdyn ack: %hhu\n", ap.enable_dyn_ack);
		IWL_DEBUG_INFO(mvm, "\trssi: %hhd\n", ap.rssi);
		IWL_DEBUG_INFO(mvm, "\tnotify MCSI: %hhu\n", ap.notify_mcsi);
	}
}

static int
iwl_tof_range_request_status_to_err(enum iwl_tof_range_request_status s)
{
	switch (s) {
	case IWL_TOF_RANGE_REQUEST_STATUS_SUCCESS:
		return 0;
	case IWL_TOF_RANGE_REQUEST_STATUS_BUSY:
		return -EBUSY;
	default:
		WARN_ON_ONCE(1);
		return -EIO;
	}
}

int iwl_mvm_tof_range_request_cmd(struct iwl_mvm *mvm)
{
	int err;
	u32 status;
	struct iwl_host_cmd cmd = {
		.id = iwl_cmd_id(TOF_RANGE_REQ_CMD, TOF_GROUP, 0),
		.len = { sizeof(mvm->tof_data.range_req), },
		/* no copy because of the command size */
		.dataflags = { IWL_HCMD_DFL_NOCOPY, },
	};

	lockdep_assert_held(&mvm->mutex);

	if (!fw_has_capa(&mvm->fw->ucode_capa, IWL_UCODE_TLV_CAPA_TOF_SUPPORT))
		return -EINVAL;

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
	iwl_mvm_tof_range_req_fill_tsf(mvm);
#endif
	mvm->tof_data.active_request_id = mvm->tof_data.range_req.request_id;

	cmd.data[0] = &mvm->tof_data.range_req;

	iwl_mvm_debug_range_req(mvm);

	status = 0;
	err = iwl_mvm_send_cmd_status(mvm, &cmd, &status);
	if (err) {
		IWL_ERR(mvm, "Failed to send ToF cmd! err: %d\n", err);
	} else if (status) {
		IWL_ERR(mvm, "ToF cmd failure! status: %u\n", status);
		err = iwl_tof_range_request_status_to_err(status);
	}
	if (err)
		iwl_mvm_tof_reset_active(mvm);

	return err;
}

int iwl_mvm_tof_range_request_ext_cmd(struct iwl_mvm *mvm)
{
	lockdep_assert_held(&mvm->mutex);

	if (!fw_has_capa(&mvm->fw->ucode_capa,
			 IWL_UCODE_TLV_CAPA_TOF_SUPPORT)) {
		IWL_ERR(mvm, "%s: ToF is not supported!\n", __func__);
		return -EINVAL;
	}

	return iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(TOF_RANGE_REQ_EXT_CMD,
						    TOF_GROUP, 0),
				    0, sizeof(mvm->tof_data.range_req_ext),
				    &mvm->tof_data.range_req_ext);
}

static struct cfg80211_ftm_target *
iwl_mvm_tof_find_target_in_request(struct iwl_mvm *mvm, const u8 *bssid)
{
	int i;

	for (i = 0; i < mvm->tof_data.active_request.num_of_targets; i++)
		if (ether_addr_equal_unaligned(
		    mvm->tof_data.active_request.targets[i].bssid, bssid))
			return &mvm->tof_data.active_request.targets[i];

	return NULL;
}

#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
static u64 iwl_mvm_tof_get_tsf(struct iwl_mvm *mvm, u32 gp2_ts)
{
	struct iwl_mvm_tof_tsf_entry *tsf_entry;
	u8 *bssid = mvm->tof_data.active_bssid_for_tsf;

	tsf_entry = rhashtable_lookup_fast(&mvm->tof_data.tsf_hash, bssid,
					   tsf_rht_params);
	if (!tsf_entry)
		return 0;

	if (tsf_entry->delta_sign)
		return (u64)gp2_ts - tsf_entry->delta;
	else
		return (u64)gp2_ts + tsf_entry->delta;
}
#endif

static u64 iwl_mvm_tof_get_host_time(struct iwl_mvm *mvm, u32 msrment_gp2_ts)
{
	u32 curr_gp2, diff;
	u64 now_from_boot_ns;

	iwl_mvm_get_sync_time(mvm, &curr_gp2, &now_from_boot_ns);

	if (curr_gp2 >= msrment_gp2_ts)
		diff = curr_gp2 - msrment_gp2_ts;
	else
		diff = curr_gp2 + (U32_MAX - msrment_gp2_ts + 1);

	return now_from_boot_ns - (u64)diff * 1000;
}

static inline enum rate_info_bw iwl_mvm_tof_fw_bw_to_rate_info_bw(u8 fw_bw)
{
	switch (fw_bw) {
	case 0:
		return RATE_INFO_BW_20;
	case 1:
		return RATE_INFO_BW_40;
	case 2:
		return RATE_INFO_BW_80;
	default:
		break;
	}

	return -1;
}

static inline int iwl_mvm_tof_is_ht(struct iwl_mvm *mvm, u8 fw_bw)
{
	switch (iwl_mvm_tof_fw_bw_to_rate_info_bw(fw_bw)) {
	case RATE_INFO_BW_20:
		return mvm->tof_data.range_req_ext.ftm_format_and_bw20M ==
			IEEE80211_FTM_FORMAT_BW_HT_20;
	case RATE_INFO_BW_40:
		return mvm->tof_data.range_req_ext.ftm_format_and_bw40M ==
			IEEE80211_FTM_FORMAT_BW_HT_40;
	default:
		break;
	}

	return 0;
}

static inline int iwl_mvm_tof_is_vht(struct iwl_mvm *mvm, u8 fw_bw)
{
	switch (iwl_mvm_tof_fw_bw_to_rate_info_bw(fw_bw)) {
	case RATE_INFO_BW_20:
		return mvm->tof_data.range_req_ext.ftm_format_and_bw20M ==
			IEEE80211_FTM_FORMAT_BW_VHT_20;
	case RATE_INFO_BW_40:
		return mvm->tof_data.range_req_ext.ftm_format_and_bw40M ==
			IEEE80211_FTM_FORMAT_BW_VHT_40;
	case RATE_INFO_BW_80:
		return mvm->tof_data.range_req_ext.ftm_format_and_bw80M ==
			IEEE80211_FTM_FORMAT_BW_VHT_80;
	default:
		break;
	}

	return 0;
}

static void iwl_mvm_debug_range_resp(struct iwl_mvm *mvm,
				     struct cfg80211_msrment_response *resp)
{
	u8 num_of_entries = resp->u.ftm.num_of_entries;
	int i;

	IWL_DEBUG_INFO(mvm, "Range response received\n");
	IWL_DEBUG_INFO(mvm, "status: %d, cookie: %lld, num of entries: %hhx\n",
		       resp->status, resp->cookie, num_of_entries);

	for (i = 0; i < num_of_entries; i++) {
		struct cfg80211_ftm_result *res = &resp->u.ftm.entries[i];

		IWL_DEBUG_INFO(mvm, "entry %d\n", i);
		IWL_DEBUG_INFO(mvm, "\tstatus: %d\n", res->status);
		IWL_DEBUG_INFO(mvm, "\tcomplete: %s\n",
			       res->complete ? "true" : "false");
		IWL_DEBUG_INFO(mvm, "\tBSSID: %pM\n", res->target->bssid);
		IWL_DEBUG_INFO(mvm, "\thost time: %llu\n", res->host_time);
		IWL_DEBUG_INFO(mvm, "\ttsf: %llu\n", res->tsf);
		IWL_DEBUG_INFO(mvm, "\tburst index: %hhu\n", res->burst_index);
		IWL_DEBUG_INFO(mvm, "\tmeasurement num: %u\n",
			       res->measurement_num);
		IWL_DEBUG_INFO(mvm, "\tsuccess num: %u\n", res->success_num);
		IWL_DEBUG_INFO(mvm, "\tnum per burst: %hhu\n",
			       res->num_per_burst);
		IWL_DEBUG_INFO(mvm, "\tretry after duration: %u\n",
			       res->retry_after_duration);
		IWL_DEBUG_INFO(mvm, "\tburst duration: %u\n",
			       res->burst_duration);
		IWL_DEBUG_INFO(mvm, "\tnegotiated burst: %u\n",
			       res->negotiated_burst_num);
		IWL_DEBUG_INFO(mvm, "\trssi: %hhd\n", res->rssi);
		IWL_DEBUG_INFO(mvm, "\trssi spread: %hhu\n", res->rssi_spread);
		IWL_DEBUG_INFO(mvm, "\trtt: %lld\n", res->rtt);
		IWL_DEBUG_INFO(mvm, "\trtt var: %llu\n", res->rtt_variance);
		IWL_DEBUG_INFO(mvm, "\trtt spread: %llu\n", res->rtt_spread);
		IWL_DEBUG_INFO(mvm, "\tdistance: %lld\n", res->distance);
		IWL_DEBUG_INFO(mvm, "\tdistance variance: %llu\n",
			       res->distance_variance);
		IWL_DEBUG_INFO(mvm, "\tdistance spread: %llu\n",
			       res->distance_spread);
		IWL_DEBUG_INFO(mvm, "\tfilled: %x\n", res->filled);
	}
}

static enum nl80211_msrment_status
iwl_mvm_get_msrment_status(enum iwl_tof_response_status status)
{
	switch (status) {
	case IWL_TOF_RESPONSE_SUCCESS:
		return NL80211_MSRMENT_STATUS_SUCCESS;
	case IWL_TOF_RESPONSE_TIMEOUT:
		return NL80211_MSRMENT_STATUS_TIMEOUT;
	case IWL_TOF_RESPONSE_ABORTED:
	default:
		return NL80211_MSRMENT_STATUS_FAIL;
	}
}

static enum nl80211_ftm_response_status
iwl_mvm_get_target_status(enum iwl_tof_entry_status status)
{
	switch (status) {
	case IWL_TOF_ENTRY_SUCCESS:
		return NL80211_FTM_RESP_SUCCESS;
	case IWL_TOF_ENTRY_TIMING_MEASURE_TIMEOUT:
		return NL80211_FTM_RESP_NOT_MEASURED;
	case IWL_TOF_ENTRY_NO_RESPONSE:
		return NL80211_FTM_RESP_TARGET_UNAVAILABLE;
	default:
		return NL80211_FTM_RESP_FAIL;
	}
}

static void iwl_mvm_get_lci_civic(struct iwl_mvm_tof_data *data,
				  struct cfg80211_ftm_result *res,
				  struct cfg80211_ftm_target *target)
{
	struct lci_civic_entry *entry;

	if (!target->lci && !target->civic)
		return;

	list_for_each_entry(entry, &data->lci_civic_info, list) {
		if (!ether_addr_equal_unaligned(target->bssid, entry->bssid))
			continue;

		if (entry->lci_len && target->lci) {
			res->lci_len = entry->lci_len;
			res->lci = entry->buf;
			res->filled |= BIT(NL80211_FTM_RESP_ENTRY_ATTR_LCI);
		}

		if (entry->civic_len && target->civic) {
			res->civic_len = entry->civic_len;
			res->civic = entry->buf + entry->lci_len;
			res->filled |= BIT(NL80211_FTM_RESP_ENTRY_ATTR_CIVIC);
		}

		break;
	}
}

/* Speed of light in cm/nanosec. Though RTT is in picosec units, calculations
 * are done using nanosec, in order to avoid floating point usage.
 */
#define SOL_CM_NSEC 30

void iwl_mvm_tof_range_resp(struct iwl_mvm *mvm,
			    struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_tof_range_rsp_ntfy *fw_resp = (void *)pkt->data;
	struct cfg80211_msrment_response user_resp = {0};
	int i;

	lockdep_assert_held(&mvm->mutex);

	if (fw_resp->request_id != mvm->tof_data.active_request_id) {
		IWL_ERR(mvm, "Request id mismatch, got %d, active %d\n",
			fw_resp->request_id,
			mvm->tof_data.active_request_id);
		return;
	}

	if (fw_resp->num_of_aps > mvm->tof_data.active_request.num_of_targets) {
		IWL_ERR(mvm, "FTM range response invalid\n");
		return;
	}

	user_resp.cookie = mvm->tof_data.active_cookie;
	user_resp.type = NL80211_MSRMENT_TYPE_FTM;
	user_resp.status = iwl_mvm_get_msrment_status(fw_resp->request_status);
	user_resp.u.ftm.num_of_entries = fw_resp->num_of_aps;
	user_resp.u.ftm.entries = kzalloc(sizeof(*user_resp.u.ftm.entries) *
					  fw_resp->num_of_aps, GFP_KERNEL);
	if (!user_resp.u.ftm.entries) {
		iwl_mvm_tof_reset_active(mvm);
		return;
	}

	for (i = 0; i < fw_resp->num_of_aps && i < IWL_MVM_TOF_MAX_APS; i++) {
		struct cfg80211_ftm_result *result =
			&user_resp.u.ftm.entries[i];
		struct iwl_tof_range_rsp_ap_entry_ntfy *fw_ap = &fw_resp->ap[i];
		struct cfg80211_ftm_target *target;
		u32 timestamp;

		target = iwl_mvm_tof_find_target_in_request(mvm, fw_ap->bssid);
		if (!target) {
			IWL_WARN(mvm,
				 "Unknown bssid (target #%d) in FTM response\n",
				 i);
			continue;
		}

		result->status =
			iwl_mvm_get_target_status(fw_ap->measure_status);
		result->target = target;
		timestamp = le32_to_cpu(fw_ap->timestamp);
		result->host_time =
			iwl_mvm_tof_get_host_time(mvm, timestamp);
		result->rssi = fw_ap->rssi;
		result->rssi_spread = fw_ap->rssi_spread;
		if (iwl_mvm_tof_is_ht(mvm, fw_ap->measure_bw))
			result->tx_rate_info.flags |= RATE_INFO_FLAGS_MCS;
		if (iwl_mvm_tof_is_vht(mvm, fw_ap->measure_bw))
			result->tx_rate_info.flags |= RATE_INFO_FLAGS_VHT_MCS;
		result->tx_rate_info.bw =
			iwl_mvm_tof_fw_bw_to_rate_info_bw(fw_ap->measure_bw);
		result->rtt = (s32)le32_to_cpu(fw_ap->rtt);
		result->rtt_variance = le32_to_cpu(fw_ap->rtt_variance);
		result->rtt_spread = le32_to_cpu(fw_ap->rtt_spread);
		result->distance = div_s64(div_s64(result->rtt, 2) *
					   SOL_CM_NSEC, 1000);
		result->distance_variance = div_u64((result->rtt_variance >>
						     2) *
						    (SOL_CM_NSEC * SOL_CM_NSEC),
						     1000000);
		result->distance_spread = div_u64((result->rtt_spread >> 1) *
						  SOL_CM_NSEC, 1000);
		iwl_mvm_get_lci_civic(&mvm->tof_data, result, target);

#define FTM_RESP_BIT(attr) BIT(NL80211_FTM_RESP_ENTRY_ATTR_##attr)
#ifdef CPTCFG_IWLMVM_TOF_TSF_WA
		if (mvm->tof_data.active_request.report_tsf) {
			result->tsf = iwl_mvm_tof_get_tsf(mvm, timestamp);
			result->filled |= FTM_RESP_BIT(TSF);
		}
#endif

		/* Mark only optional fields */
		result->filled |= FTM_RESP_BIT(HOST_TIME) |
				  FTM_RESP_BIT(RSSI) |
				  FTM_RESP_BIT(RSSI_SPREAD) |
				  FTM_RESP_BIT(TX_RATE_INFO) |
				  FTM_RESP_BIT(RTT_VAR) |
				  FTM_RESP_BIT(RTT_SPREAD) |
				  FTM_RESP_BIT(DISTANCE) |
				  FTM_RESP_BIT(DISTANCE_VAR) |
				  FTM_RESP_BIT(DISTANCE_SPREAD);
#undef FTM_RESP_BIT
	}

	iwl_mvm_debug_range_resp(mvm, &user_resp);

	cfg80211_measurement_response(mvm->hw->wiphy, &user_resp, GFP_KERNEL);
	kfree(user_resp.u.ftm.entries);

	/* for debugfs retrieving */
	memcpy(&mvm->tof_data.range_resp, fw_resp,
	       sizeof(struct iwl_tof_range_rsp_ntfy));

	if (fw_resp->last_in_batch)
		iwl_mvm_tof_reset_active(mvm);

	return;
}

void iwl_mvm_tof_mcsi_notif(struct iwl_mvm *mvm,
			    struct iwl_rx_cmd_buffer *rxb)

{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_tof_mcsi_notif *resp = (void *)pkt->data;

	IWL_DEBUG_INFO(mvm, "MCSI notification, token %d\n", resp->token);
	return;
}

void iwl_mvm_tof_responder_stats(struct iwl_mvm *mvm,
				 struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_tof_responder_stats *resp = (void *)pkt->data;
	struct cfg80211_ftm_responder_stats *stats = &mvm->tof_data.resp_stats;
	unsigned flags = le32_to_cpu(resp->flags);

	IWL_DEBUG_INFO(mvm, "Responder statistics info\n");

	if (resp->success_ftm == resp->ftm_per_burst)
		stats->success_num++;
	else if (resp->success_ftm >= 2)
		stats->partial_num++;
	else
		stats->failed_num++;

	if (flags & FTM_RESP_STAT_ASAP_REQ &&
	    flags & FTM_RESP_STAT_ASAP_RESP)
		stats->asap_num++;

	if (flags & FTM_RESP_STAT_NON_ASAP_RESP)
		stats->non_asap_num++;

	stats->total_duration_ms += le32_to_cpu(resp->duration) / USEC_PER_MSEC;

	if (flags & FTM_RESP_STAT_TRIGGER_UNKNOWN)
		stats->unknown_triggers_num++;

	if (flags & FTM_RESP_STAT_DUP)
		stats->reschedule_requests_num++;

	if (flags & FTM_RESP_STAT_NON_ASAP_OUT_WIN)
		stats->out_of_window_triggers_num++;

	return;
}

void iwl_mvm_tof_lc_notif(struct iwl_mvm *mvm,
			  struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	const struct ieee80211_mgmt *mgmt = (void *)pkt->data;
	size_t len = iwl_rx_packet_payload_len(pkt);
	struct lci_civic_entry *lci_civic;
	const u8 *ies, *lci, *civic, *msr_ie;
	size_t ies_len, lci_len = 0, civic_len = 0;
	size_t baselen = IEEE80211_MIN_ACTION_SIZE +
			 sizeof(mgmt->u.action.u.ftm);
	static const u8 rprt_type_lci = IEEE80211_SPCT_MSR_RPRT_TYPE_LCI;
	static const u8 rprt_type_civic = IEEE80211_SPCT_MSR_RPRT_TYPE_CIVIC;

	if (len <= baselen)
		return;

	ies = mgmt->u.action.u.ftm.variable;
	ies_len = len - baselen;

	msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len,
					&rprt_type_lci, 1, 4);
	if (msr_ie) {
		lci = msr_ie + 2;
		lci_len = msr_ie[1];
	}

	msr_ie = cfg80211_find_ie_match(WLAN_EID_MEASURE_REPORT, ies, ies_len,
					&rprt_type_civic, 1, 4);
	if (msr_ie) {
		civic = msr_ie + 2;
		civic_len = msr_ie[1];
	}

	lci_civic = kmalloc(sizeof(*lci_civic) + lci_len + civic_len,
			    GFP_KERNEL);
	if (!lci_civic)
		return;

	memcpy(lci_civic->bssid, mgmt->bssid, ETH_ALEN);

	lci_civic->lci_len = lci_len;
	if (lci_len)
		memcpy(lci_civic->buf, lci, lci_len);

	lci_civic->civic_len = civic_len;
	if (civic_len)
		memcpy(lci_civic->buf + lci_len, civic, civic_len);

	list_add_tail(&lci_civic->list, &mvm->tof_data.lci_civic_info);
}
