/******************************************************************************
 *
 * 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
 * Copyright(c) 2018        Intel Corporation
 *
 * 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.
 *
 * 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
 * Copyright(c) 2018        Intel Corporation
 * 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 "mvm.h"
#include "fw/api/nan.h"

#define NAN_WARMUP_TIMEOUT_USEC  (120000000ULL)
#define NAN_CHANNEL_24           (6)
#define NAN_CHANNEL_52           (149)

enum srf_type {
	SRF_BF_TYPE             = BIT(0),
	SRF_INCLUDE             = BIT(1),
	SRF_BLOOM_FILTER_IDX    = BIT(2) | BIT(3),
};

static bool iwl_mvm_can_beacon(struct ieee80211_vif *vif,
			       enum nl80211_band band, u8 channel)
{
	struct wiphy *wiphy = ieee80211_vif_to_wdev(vif)->wiphy;
	int freq = ieee80211_channel_to_frequency(channel, band);
	struct ieee80211_channel *chan = ieee80211_get_channel(wiphy,
							       freq);
	struct cfg80211_chan_def def;

	if (!chan)
		return false;

	cfg80211_chandef_create(&def, chan, NL80211_CHAN_NO_HT);
	return cfg80211_reg_can_beacon(wiphy, &def, vif->type);
}

static inline bool iwl_mvm_nan_is_ver2(struct ieee80211_hw *hw)
{
	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);

	return fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_NAN2_VER2);
}

static inline size_t iwl_mvm_nan_cfg_cmd_len(struct ieee80211_hw *hw)
{
	return iwl_mvm_nan_is_ver2(hw) ? sizeof(struct iwl_nan_cfg_cmd_v2) :
					 sizeof(struct iwl_nan_cfg_cmd);
}

static inline struct iwl_nan_umac_cfg
*iwl_mvm_nan_get_umac_cfg(struct ieee80211_hw *hw, void *nan_cfg_cmd)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       &((struct iwl_nan_cfg_cmd_v2 *)nan_cfg_cmd)->umac_cfg :
	       &((struct iwl_nan_cfg_cmd *)nan_cfg_cmd)->umac_cfg;
}

static inline struct iwl_nan_testbed_cfg
*iwl_mvm_nan_get_tb_cfg(struct ieee80211_hw *hw, void *nan_cfg_cmd)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       &((struct iwl_nan_cfg_cmd_v2 *)nan_cfg_cmd)->tb_cfg :
	       &((struct iwl_nan_cfg_cmd *)nan_cfg_cmd)->tb_cfg;
}

static inline struct iwl_nan_nan2_cfg
*iwl_mvm_nan_get_nan2_cfg(struct ieee80211_hw *hw, void *nan_cfg_cmd)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       &((struct iwl_nan_cfg_cmd_v2 *)nan_cfg_cmd)->nan2_cfg :
	       &((struct iwl_nan_cfg_cmd *)nan_cfg_cmd)->nan2_cfg;
}

int iwl_mvm_start_nan(struct ieee80211_hw *hw,
		      struct ieee80211_vif *vif,
		      struct cfg80211_nan_conf *conf)
{
	struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif);
	void *cmd;
	struct iwl_nan_umac_cfg *umac_cfg;
	struct iwl_nan_testbed_cfg *tb_cfg;
	struct iwl_nan_nan2_cfg *nan2_cfg;
	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
	int ret = 0;
	u16 cdw = 0;

	IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), "Start NAN\n");

	/* 2GHz is mandatory and nl80211 should make sure it is set.
	 * Warn and add 2GHz if this happens anyway.
	 */
	if (WARN_ON(conf->bands && !(conf->bands & BIT(NL80211_BAND_2GHZ))))
		return -EINVAL;

	conf->bands |= BIT(NL80211_BAND_2GHZ);
	cmd = kzalloc(iwl_mvm_nan_cfg_cmd_len(hw), GFP_KERNEL);
	if (!cmd)
		return -ENOMEM;

	umac_cfg = iwl_mvm_nan_get_umac_cfg(hw, cmd);
	tb_cfg = iwl_mvm_nan_get_tb_cfg(hw, cmd);
	nan2_cfg = iwl_mvm_nan_get_nan2_cfg(hw, cmd);

	mutex_lock(&mvm->mutex);

	umac_cfg->action = cpu_to_le32(FW_CTXT_ACTION_ADD);
	umac_cfg->tsf_id = cpu_to_le32(mvmvif->tsf_id);
	umac_cfg->beacon_template_id = cpu_to_le32(mvmvif->id);

	ether_addr_copy(umac_cfg->node_addr, vif->addr);
	umac_cfg->sta_id = cpu_to_le32(mvm->aux_sta.sta_id);
	umac_cfg->master_pref = conf->master_pref;

	if (conf->bands & BIT(NL80211_BAND_2GHZ)) {
		if (!iwl_mvm_can_beacon(vif, NL80211_BAND_2GHZ,
					NAN_CHANNEL_24)) {
			IWL_ERR(mvm, "Can't beacon on %d\n", NAN_CHANNEL_24);
			ret = -EINVAL;
			goto out;
		}

		tb_cfg->chan24 = NAN_CHANNEL_24;
		cdw |= conf->cdw_2g;
	}

	if (conf->bands & BIT(NL80211_BAND_5GHZ)) {
		if (!iwl_mvm_can_beacon(vif, NL80211_BAND_5GHZ,
					NAN_CHANNEL_52)) {
			IWL_ERR(mvm, "Can't beacon on %d\n", NAN_CHANNEL_52);
			ret = -EINVAL;
			goto out;
		}

		tb_cfg->chan52 = NAN_CHANNEL_52;
		cdw |= conf->cdw_5g << 3;
	}

	tb_cfg->warmup_timer = cpu_to_le32(NAN_WARMUP_TIMEOUT_USEC);
	tb_cfg->op_bands = 3;
	nan2_cfg->cdw = cpu_to_le16(cdw);

	if ((conf->bands & BIT(NL80211_BAND_2GHZ)) &&
	    (conf->bands & BIT(NL80211_BAND_5GHZ)))
		umac_cfg->dual_band = cpu_to_le32(1);

	ret = iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(NAN_CONFIG_CMD,
						   NAN_GROUP, 0),
				   0, iwl_mvm_nan_cfg_cmd_len(hw), cmd);

	if (!ret)
		mvm->nan_vif = vif;

out:
	mutex_unlock(&mvm->mutex);
	kfree(cmd);

	return ret;
}

int iwl_mvm_stop_nan(struct ieee80211_hw *hw,
		     struct ieee80211_vif *vif)
{
	void *cmd;
	struct iwl_nan_umac_cfg *umac_cfg;
	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
	int ret = 0;

	IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), "Stop NAN\n");

	cmd = kzalloc(iwl_mvm_nan_cfg_cmd_len(hw), GFP_KERNEL);
	if (!cmd)
		return -ENOMEM;

	umac_cfg = iwl_mvm_nan_get_umac_cfg(hw, cmd);

	mutex_lock(&mvm->mutex);
	umac_cfg->action = cpu_to_le32(FW_CTXT_ACTION_REMOVE);

	ret = iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(NAN_CONFIG_CMD,
						   NAN_GROUP, 0),
				   0, iwl_mvm_nan_cfg_cmd_len(hw), cmd);

	if (!ret)
		mvm->nan_vif = NULL;
	mutex_unlock(&mvm->mutex);
	kfree(cmd);

	return ret;
}

static enum iwl_fw_nan_func_type
iwl_fw_nan_func_type(enum nl80211_nan_function_type type)
{
	switch (type) {
	case NL80211_NAN_FUNC_PUBLISH:
		return IWL_NAN_DE_FUNC_PUBLISH;
	case NL80211_NAN_FUNC_SUBSCRIBE:
		return IWL_NAN_DE_FUNC_SUBSCRIBE;
	case NL80211_NAN_FUNC_FOLLOW_UP:
		return IWL_NAN_DE_FUNC_FOLLOW_UP;
	default:
		return IWL_NAN_DE_FUNC_NOT_VALID;
	}
}

static u8
iwl_mvm_get_match_filter_len(struct cfg80211_nan_func_filter *filters,
			     u8 num_filters)
{
	int i;
	unsigned int len = 0;

	len += num_filters;
	for (i = 0; i < num_filters; i++)
		len += filters[i].len;

	if (WARN_ON_ONCE(len > U8_MAX))
		return 0;

	return len;
}

static void iwl_mvm_copy_filters(struct cfg80211_nan_func_filter *filters,
				 u8 num_filters, u8 *cmd_data)
{
	int i;
	u8 offset = 0;

	for (i = 0; i < num_filters; i++) {
		memcpy(cmd_data + offset, &filters[i].len,
		       sizeof(u8));
		offset++;
		if (filters[i].len > 0)
			memcpy(cmd_data + offset, filters[i].filter,
			       filters[i].len);

		offset += filters[i].len;
	}
}

static inline size_t iwl_mvm_nan_add_func_cmd_len(struct ieee80211_hw *hw)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       sizeof(struct iwl_nan_add_func_cmd_v2) :
	       sizeof(struct iwl_nan_add_func_cmd);
}

static inline struct iwl_nan_add_func_common
*iwl_mvm_nan_get_add_func_common(struct ieee80211_hw *hw,
				 void *nan_add_func_cmd)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       &((struct iwl_nan_add_func_cmd_v2 *)nan_add_func_cmd)->cmn :
	       &((struct iwl_nan_add_func_cmd *)nan_add_func_cmd)->cmn;
}

static inline u8 *iwl_mvm_nan_get_add_func_data(struct ieee80211_hw *hw,
						void *nan_add_func_cmd)
{
	return iwl_mvm_nan_is_ver2(hw) ?
	       ((struct iwl_nan_add_func_cmd_v2 *)nan_add_func_cmd)->data :
	       ((struct iwl_nan_add_func_cmd *)nan_add_func_cmd)->data;
}

int iwl_mvm_add_nan_func(struct ieee80211_hw *hw,
			 struct ieee80211_vif *vif,
			 const struct cfg80211_nan_func *nan_func)
{
	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
	void *cmd;
	struct iwl_nan_add_func_common *cmn;
	struct iwl_host_cmd hcmd = {
		.id = iwl_cmd_id(NAN_DISCOVERY_FUNC_CMD, NAN_GROUP, 0),
		.flags = CMD_WANT_SKB,
	};
	struct iwl_nan_add_func_res *resp;
	struct iwl_rx_packet *pkt;
	u8 *cmd_data;
	u16 flags = 0;
	u8 tx_filt_len, rx_filt_len;
	size_t cmd_len;
	int ret = 0;

	IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), "Add NAN func\n");

	mutex_lock(&mvm->mutex);

	/* We assume here that mac80211 properly validated the nan_func */
	cmd_len = iwl_mvm_nan_add_func_cmd_len(hw) +
		  ALIGN(nan_func->serv_spec_info_len, 4);
	if (nan_func->srf_bf_len)
		cmd_len += ALIGN(nan_func->srf_bf_len + 1, 4);
	else if (nan_func->srf_num_macs)
		cmd_len += ALIGN(nan_func->srf_num_macs * ETH_ALEN + 1, 4);

	rx_filt_len = iwl_mvm_get_match_filter_len(nan_func->rx_filters,
						   nan_func->num_rx_filters);

	tx_filt_len = iwl_mvm_get_match_filter_len(nan_func->tx_filters,
						   nan_func->num_tx_filters);

	cmd_len += ALIGN(rx_filt_len, 4);
	cmd_len += ALIGN(tx_filt_len, 4);

	cmd = kzalloc(cmd_len, GFP_KERNEL);

	if (!cmd) {
		ret = -ENOBUFS;
		goto unlock;
	}

	hcmd.len[0] = cmd_len;
	hcmd.data[0] = cmd;

	cmn = iwl_mvm_nan_get_add_func_common(hw, cmd);

	cmd_data = iwl_mvm_nan_get_add_func_data(hw, cmd);
	cmn->action = cpu_to_le32(FW_CTXT_ACTION_ADD);
	cmn->type = iwl_fw_nan_func_type(nan_func->type);
	cmn->instance_id = nan_func->instance_id;
	cmn->dw_interval = 1;

	memcpy(&cmn->service_id, nan_func->service_id, sizeof(cmn->service_id));

	/*
	 * TODO: Currently we want all the events, however we might need to be
	 * able to unset this flag for solicited publish to disable "Replied"
	 * events.
	 */
	flags |= IWL_NAN_DE_FUNC_FLAG_RAISE_EVENTS;
	if (nan_func->subscribe_active ||
	    nan_func->publish_type == NL80211_NAN_UNSOLICITED_PUBLISH)
		flags |= IWL_NAN_DE_FUNC_FLAG_UNSOLICITED_OR_ACTIVE;

	if (nan_func->close_range)
		flags |= IWL_NAN_DE_FUNC_FLAG_CLOSE_RANGE;

	if (nan_func->type == NL80211_NAN_FUNC_FOLLOW_UP ||
	    (nan_func->type == NL80211_NAN_FUNC_PUBLISH &&
	     !nan_func->publish_bcast))
		flags |= IWL_NAN_DE_FUNC_FLAG_UNICAST;

	if (nan_func->publish_type == NL80211_NAN_SOLICITED_PUBLISH)
		flags |= IWL_NAN_DE_FUNC_FLAG_SOLICITED;

	cmn->flags = cpu_to_le16(flags);
	cmn->ttl = cpu_to_le32(nan_func->ttl);
	cmn->serv_info_len = nan_func->serv_spec_info_len;
	if (nan_func->serv_spec_info_len)
		memcpy(cmd_data, nan_func->serv_spec_info,
		       nan_func->serv_spec_info_len);

	if (nan_func->type == NL80211_NAN_FUNC_FOLLOW_UP) {
		cmn->flw_up_id = nan_func->followup_id;
		cmn->flw_up_req_id = nan_func->followup_reqid;
		memcpy(cmn->flw_up_addr, nan_func->followup_dest.addr,
		       ETH_ALEN);
		cmn->ttl = cpu_to_le32(1);
	}

	cmd_data += ALIGN(cmn->serv_info_len, 4);
	if (nan_func->srf_bf_len) {
		u8 srf_ctl = 0;

		srf_ctl |= SRF_BF_TYPE;
		srf_ctl |= (nan_func->srf_bf_idx << 2) & SRF_BLOOM_FILTER_IDX;
		if (nan_func->srf_include)
			srf_ctl |= SRF_INCLUDE;

		cmn->srf_len = nan_func->srf_bf_len + 1;
		memcpy(cmd_data, &srf_ctl, sizeof(srf_ctl));
		memcpy(cmd_data + 1, nan_func->srf_bf, nan_func->srf_bf_len);
	} else if (nan_func->srf_num_macs) {
		u8 srf_ctl = 0;
		int i;

		if (nan_func->srf_include)
			srf_ctl |= SRF_INCLUDE;

		cmn->srf_len = nan_func->srf_num_macs * ETH_ALEN + 1;
		memcpy(cmd_data, &srf_ctl, sizeof(srf_ctl));

		for (i = 0; i < nan_func->srf_num_macs; i++) {
			memcpy(cmd_data + 1 + i * ETH_ALEN,
			       nan_func->srf_macs[i].addr, ETH_ALEN);
		}
	}

	cmd_data += ALIGN(cmn->srf_len, 4);

	if (rx_filt_len > 0)
		iwl_mvm_copy_filters(nan_func->rx_filters,
				     nan_func->num_rx_filters, cmd_data);

	cmn->rx_filter_len = rx_filt_len;
	cmd_data += ALIGN(cmn->rx_filter_len, 4);

	if (tx_filt_len > 0)
		iwl_mvm_copy_filters(nan_func->tx_filters,
				     nan_func->num_tx_filters, cmd_data);

	cmn->tx_filter_len = tx_filt_len;

	ret = iwl_mvm_send_cmd(mvm, &hcmd);

	if (ret) {
		IWL_ERR(mvm, "Couldn't send NAN_DISCOVERY_FUNC_CMD: %d\n", ret);
		goto out_free;
	}

	pkt = hcmd.resp_pkt;

	if (WARN_ON(iwl_rx_packet_payload_len(pkt) != sizeof(*resp))) {
		ret = -EIO;
		goto out_free_resp;
	}

	resp = (void *)pkt->data;

	IWL_DEBUG_MAC80211(mvm,
			   "Add NAN func response status: %d, instance_id: %d\n",
			   resp->status, resp->instance_id);

	if (resp->status == IWL_NAN_DE_FUNC_STATUS_INSUFFICIENT_ENTRIES ||
	    resp->status == IWL_NAN_DE_FUNC_STATUS_INSUFFICIENT_MEMORY) {
		ret = -ENOBUFS;
		goto out_free_resp;
	}

	if (resp->status != IWL_NAN_DE_FUNC_STATUS_SUCCESSFUL) {
		ret = -EIO;
		goto out_free_resp;
	}

	if (cmn->instance_id &&
	    WARN_ON(resp->instance_id != cmn->instance_id)) {
		ret = -EIO;
		goto out_free_resp;
	}

	ret = 0;
out_free_resp:
	iwl_free_resp(&hcmd);
out_free:
	kfree(cmd);
unlock:
	mutex_unlock(&mvm->mutex);
	return ret;
}

void iwl_mvm_del_nan_func(struct ieee80211_hw *hw,
			  struct ieee80211_vif *vif,
			  u8 instance_id)
{
	struct iwl_mvm *mvm = IWL_MAC80211_GET_MVM(hw);
	void *cmd;
	struct iwl_nan_add_func_common *cmn;
	int ret;

	IWL_DEBUG_MAC80211(IWL_MAC80211_GET_MVM(hw), "Remove NAN func\n");

	cmd = kzalloc(iwl_mvm_nan_add_func_cmd_len(hw), GFP_KERNEL);
	if (!cmd) {
		IWL_ERR(mvm,
			"Failed to allocate command to remove NAN func instance_id: %d\n",
			instance_id);
		return;
	}

	cmn = iwl_mvm_nan_get_add_func_common(hw, cmd);

	mutex_lock(&mvm->mutex);
	cmn->action = cpu_to_le32(FW_CTXT_ACTION_REMOVE);
	cmn->instance_id = instance_id;

	ret = iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(NAN_DISCOVERY_FUNC_CMD,
						   NAN_GROUP, 0),
				   0, iwl_mvm_nan_add_func_cmd_len(hw), cmd);
	if (ret)
		IWL_ERR(mvm, "Failed to remove NAN func instance_id: %d\n",
			instance_id);

	mutex_unlock(&mvm->mutex);
	kfree(cmd);
}

static u8 iwl_cfg_nan_func_type(u8 fw_type)
{
	switch (fw_type) {
	case IWL_NAN_DE_FUNC_PUBLISH:
		return NL80211_NAN_FUNC_PUBLISH;
	case IWL_NAN_DE_FUNC_SUBSCRIBE:
		return NL80211_NAN_FUNC_SUBSCRIBE;
	case IWL_NAN_DE_FUNC_FOLLOW_UP:
		return NL80211_NAN_FUNC_FOLLOW_UP;
	default:
		return NL80211_NAN_FUNC_MAX_TYPE + 1;
	}
}

static void iwl_mvm_nan_match_v1(struct iwl_mvm *mvm,
				 struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_nan_disc_evt_notify_v1 *ev = (void *)pkt->data;
	struct cfg80211_nan_match_params match = {0};
	int len = iwl_rx_packet_payload_len(pkt);

	if (WARN_ON_ONCE(!mvm->nan_vif)) {
		IWL_ERR(mvm, "NAN vif is NULL\n");
		return;
	}

	if (WARN_ON_ONCE(len < sizeof(*ev))) {
		IWL_ERR(mvm, "Invalid NAN match event length: %d\n",
			len);
		return;
	}

	if (WARN_ON_ONCE(len < sizeof(*ev) + ev->service_info_len)) {
		IWL_ERR(mvm,
			"Invalid NAN match event length: %d, info_len: %d\n",
			len, ev->service_info_len);
		return;
	}

	match.type = iwl_cfg_nan_func_type(ev->type);

	if (WARN_ON_ONCE(match.type > NL80211_NAN_FUNC_MAX_TYPE)) {
		IWL_ERR(mvm, "Invalid func type\n");
		return;
	}

	match.inst_id = ev->instance_id;
	match.peer_inst_id = ev->peer_instance;
	match.addr = ev->peer_mac_addr;
	match.info = ev->buf;
	match.info_len = ev->service_info_len;
	ieee80211_nan_func_match(mvm->nan_vif, &match,
				 GFP_ATOMIC);
}

static void iwl_mvm_nan_match_v2(struct iwl_mvm *mvm,
				 struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_nan_disc_evt_notify_v2 *ev = (void *)pkt->data;
	u32 len = iwl_rx_packet_payload_len(pkt);
	u32 i = 0;

	if (WARN_ONCE(!mvm->nan_vif, "NAN vif is NULL"))
		return;

	if (WARN_ONCE(len < sizeof(*ev), "Invalid NAN match event length=%u",
		      len))
		return;

	if (WARN_ONCE(len < sizeof(*ev) + le32_to_cpu(ev->match_len) +
		      le32_to_cpu(ev->avail_attrs_len),
		      "Bad NAN match event: len=%u, match=%u, attrs=%u\n",
		      len, ev->match_len, ev->avail_attrs_len))
		return;

	i = 0;
	while (i < le32_to_cpu(ev->match_len)) {
		struct cfg80211_nan_match_params match = {0};
		struct iwl_nan_disc_info *disc_info =
			(struct iwl_nan_disc_info *)(((u8 *)(ev + 1)) + i);

		match.type = iwl_cfg_nan_func_type(disc_info->type);
		match.inst_id = disc_info->instance_id;
		match.peer_inst_id = disc_info->peer_instance;
		match.addr = ev->peer_mac_addr;
		match.info = disc_info->buf;
		match.info_len = disc_info->service_info_len;
		ieee80211_nan_func_match(mvm->nan_vif, &match,
					 GFP_ATOMIC);

		i += ALIGN(sizeof(*disc_info) +
			   disc_info->service_info_len +
			   le16_to_cpu(disc_info->sdea_service_info_len) +
			   le16_to_cpu(disc_info->sec_ctxt_len), 4);
	}
}

void iwl_mvm_nan_match(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb)
{
	if (fw_has_api(&mvm->fw->ucode_capa, IWL_UCODE_TLV_API_NAN_NOTIF_V2))
		iwl_mvm_nan_match_v2(mvm, rxb);
	else
		iwl_mvm_nan_match_v1(mvm, rxb);
}

void iwl_mvm_nan_de_term_notif(struct iwl_mvm *mvm,
			       struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	struct iwl_nan_de_term *ev = (void *)pkt->data;
	int len = iwl_rx_packet_payload_len(pkt);
	enum nl80211_nan_func_term_reason nl_reason;

	if (WARN_ON_ONCE(!mvm->nan_vif)) {
		IWL_ERR(mvm, "NAN vif is NULL\n");
		return;
	}

	if (WARN_ON_ONCE(len != sizeof(*ev))) {
		IWL_ERR(mvm, "NAN DE termination event bad length: %d\n",
			len);
		return;
	}

	switch (ev->reason) {
	case IWL_NAN_DE_TERM_TTL_REACHED:
		nl_reason = NL80211_NAN_FUNC_TERM_REASON_TTL_EXPIRED;
		break;
	case IWL_NAN_DE_TERM_USER_REQUEST:
		nl_reason = NL80211_NAN_FUNC_TERM_REASON_USER_REQUEST;
		break;
	case IWL_NAN_DE_TERM_FAILURE:
		nl_reason = NL80211_NAN_FUNC_TERM_REASON_ERROR;
		break;
	default:
		WARN_ON_ONCE(1);
		return;
	}

	ieee80211_nan_func_terminated(mvm->nan_vif, ev->instance_id, nl_reason,
				      GFP_ATOMIC);
}

int iwl_mvm_nan_config_nan_faw_cmd(struct iwl_mvm *mvm,
				   struct cfg80211_chan_def *chandef, u8 slots)
{
	struct iwl_nan_faw_config cmd = {};
	struct iwl_mvm_vif *mvmvif;
	int ret;

	if (WARN_ON(!mvm->nan_vif))
		return -EINVAL;

	mutex_lock(&mvm->mutex);

	mvmvif = iwl_mvm_vif_from_mac80211(mvm->nan_vif);

	/* Set the channel info data */
	cmd.faw_ci.band = (chandef->chan->band == NL80211_BAND_2GHZ ?
	      PHY_BAND_24 : PHY_BAND_5);

	cmd.faw_ci.channel = chandef->chan->hw_value;
	cmd.faw_ci.width = iwl_mvm_get_channel_width(chandef);
	cmd.faw_ci.ctrl_pos = iwl_mvm_get_ctrl_pos(chandef);
	ieee80211_chandef_to_operating_class(chandef, &cmd.op_class);
	cmd.slots = slots;
	cmd.type = IWL_NAN_POST_NAN_ATTR_FURTHER_NAN;
	cmd.id_n_color = cpu_to_le32(FW_CMD_ID_AND_COLOR(mvmvif->id,
							 mvmvif->color));

	ret = iwl_mvm_send_cmd_pdu(mvm, iwl_cmd_id(NAN_FAW_CONFIG_CMD,
						   NAN_GROUP, 0),
				   0, sizeof(cmd), &cmd);

	mutex_unlock(&mvm->mutex);

	return ret;
}
