// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2024 Intel Corporation
 */
#include "session-protect.h"
#include "fw/api/time-event.h"
#include "fw/api/context.h"
#include "iface.h"
#include <net/mac80211.h>

void iwl_mld_handle_session_prot_notif(struct iwl_mld *mld,
				       struct iwl_rx_packet *pkt)
{
	struct iwl_session_prot_notif *notif = (void *)pkt->data;
	int fw_link_id = le32_to_cpu(notif->mac_link_id);
	struct ieee80211_bss_conf *link_conf =
		iwl_mld_fw_id_to_link_conf(mld, fw_link_id);
	struct ieee80211_vif *vif;
	struct iwl_mld_vif *mld_vif;
	struct iwl_mld_session_protect *session_protect;

	if (WARN_ON(!link_conf))
		return;

	vif = link_conf->vif;
	mld_vif = iwl_mld_vif_from_mac80211(vif);
	session_protect = &mld_vif->session_protect;

	if (!le32_to_cpu(notif->status)) {
		memset(session_protect, 0, sizeof(*session_protect));
	} else if (le32_to_cpu(notif->start)) {
		/* End_jiffies indicates an active session */
		session_protect->session_requested = false;
		session_protect->end_jiffies =
			TU_TO_EXP_TIME(session_protect->duration);
		/* !session_protect->end_jiffies means inactive session */
		if (!session_protect->end_jiffies)
			session_protect->end_jiffies = 1;
	} else {
		memset(session_protect, 0, sizeof(*session_protect));
	}
}

static int _iwl_mld_schedule_session_protection(struct iwl_mld *mld,
						struct ieee80211_vif *vif,
						u32 duration, u32 min_duration,
						int link_id)
{
	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
	struct iwl_mld_link *link =
		iwl_mld_link_dereference_check(mld_vif, link_id);
	struct iwl_mld_session_protect *session_protect =
		&mld_vif->session_protect;
	struct iwl_session_prot_cmd cmd = {
		.id_and_color = cpu_to_le32(link->fw_id),
		.action = cpu_to_le32(FW_CTXT_ACTION_ADD),
		.conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC),
		.duration_tu = cpu_to_le32(MSEC_TO_TU(duration)),
	};
	int ret;

	lockdep_assert_wiphy(mld->wiphy);

	WARN(hweight16(vif->active_links) > 1,
	     "Session protection isn't allowed with more than one active link");

	if (session_protect->end_jiffies &&
	    time_after(session_protect->end_jiffies,
		       TU_TO_EXP_TIME(min_duration))) {
		IWL_DEBUG_TE(mld, "We have ample in the current session: %u\n",
			     jiffies_to_msecs(session_protect->end_jiffies -
					      jiffies));
		return -EALREADY;
	}

	IWL_DEBUG_TE(mld, "Add a new session protection, duration %d TU\n",
		     le32_to_cpu(cmd.duration_tu));

	ret = iwl_mld_send_cmd_pdu(mld, WIDE_ID(MAC_CONF_GROUP,
						SESSION_PROTECTION_CMD), &cmd);

	if (ret)
		return ret;

	/* end_jiffies will be updated when handling session_prot_notif */
	session_protect->end_jiffies = 0;
	session_protect->duration = duration;
	session_protect->session_requested = true;

	return 0;
}

void iwl_mld_schedule_session_protection(struct iwl_mld *mld,
					 struct ieee80211_vif *vif,
					 u32 duration, u32 min_duration,
					 int link_id)
{
	int ret;

	ret = _iwl_mld_schedule_session_protection(mld, vif, duration,
						   min_duration, link_id);
	if (ret && ret != -EALREADY)
		IWL_ERR(mld,
			"Couldn't send the SESSION_PROTECTION_CMD (%d)\n",
			ret);
}

struct iwl_mld_session_start_data {
	struct iwl_mld *mld;
	struct ieee80211_bss_conf *link_conf;
	bool success;
};

static bool iwl_mld_session_start_fn(struct iwl_notif_wait_data *notif_wait,
				     struct iwl_rx_packet *pkt, void *_data)
{
	struct iwl_session_prot_notif *notif = (void *)pkt->data;
	unsigned int pkt_len = iwl_rx_packet_payload_len(pkt);
	struct iwl_mld_session_start_data *data = _data;
	struct ieee80211_bss_conf *link_conf;
	struct iwl_mld *mld = data->mld;
	int fw_link_id;

	if (IWL_FW_CHECK(mld, pkt_len < sizeof(*notif),
			 "short session prot notif (%d)\n",
			 pkt_len))
		return false;

	fw_link_id = le32_to_cpu(notif->mac_link_id);
	link_conf = iwl_mld_fw_id_to_link_conf(mld, fw_link_id);

	if (link_conf != data->link_conf)
		return false;

	if (!le32_to_cpu(notif->status))
		return true;

	if (notif->start) {
		data->success = true;
		return true;
	}

	return false;
}

int iwl_mld_start_session_protection(struct iwl_mld *mld,
				     struct ieee80211_vif *vif,
				     u32 duration, u32 min_duration,
				     int link_id, unsigned long timeout)
{
	static const u16 start_notif[] = { SESSION_PROTECTION_NOTIF };
	struct iwl_notification_wait start_wait;
	struct iwl_mld_session_start_data data = {
		.mld = mld,
		.link_conf = wiphy_dereference(mld->wiphy,
					       vif->link_conf[link_id]),
	};
	int ret;

	if (WARN_ON(!data.link_conf))
		return -EINVAL;

	iwl_init_notification_wait(&mld->notif_wait, &start_wait,
				   start_notif, ARRAY_SIZE(start_notif),
				   iwl_mld_session_start_fn, &data);

	ret = _iwl_mld_schedule_session_protection(mld, vif, duration,
						   min_duration, link_id);

	if (ret) {
		iwl_remove_notification(&mld->notif_wait, &start_wait);
		return ret == -EALREADY ? 0 : ret;
	}

	ret = iwl_wait_notification(&mld->notif_wait, &start_wait, timeout);
	if (ret)
		return ret;
	return data.success ? 0 : -EIO;
}

int iwl_mld_cancel_session_protection(struct iwl_mld *mld,
				      struct ieee80211_vif *vif,
				      int link_id)
{
	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
	struct iwl_mld_link *link =
		iwl_mld_link_dereference_check(mld_vif, link_id);
	struct iwl_mld_session_protect *session_protect =
		&mld_vif->session_protect;
	struct iwl_session_prot_cmd cmd = {
		.action = cpu_to_le32(FW_CTXT_ACTION_REMOVE),
		.conf_id = cpu_to_le32(SESSION_PROTECT_CONF_ASSOC),
	};
	int ret;

	lockdep_assert_wiphy(mld->wiphy);

	/* If there isn't an active session or a requested one for this
	 * link do nothing
	 */
	if (!session_protect->session_requested &&
	    !session_protect->end_jiffies)
		return 0;

	if (WARN_ON(!link))
		return -EINVAL;

	cmd.id_and_color = cpu_to_le32(link->fw_id);

	ret = iwl_mld_send_cmd_pdu(mld,
				   WIDE_ID(MAC_CONF_GROUP,
					   SESSION_PROTECTION_CMD), &cmd);
	if (ret) {
		IWL_ERR(mld,
			"Couldn't send the SESSION_PROTECTION_CMD\n");
		return ret;
	}

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

	return 0;
}