/*
 * Copyright (c) 2019-2021 The Linux Foundation. All rights reserved.
 * Copyright (c) 2021-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_sta_info.c
 *
 * Store and manage station info structure.
 *
 */

#include <wlan_hdd_includes.h>
#include "wlan_hdd_sta_info.h"

#define HDD_MAX_PEERS 32

char *sta_info_string_from_dbgid(wlan_sta_info_dbgid id)
{
	static const char *strings[] = {
				"STA_INFO_ID_RESERVED",
				"STA_INFO_CFG80211_GET_LINK_PROPERTIES",
				"STA_INFO_SOFTAP_INSPECT_TX_EAP_PKT",
				"STA_INFO_SOFTAP_CHECK_WAIT_FOR_TX_EAP_PKT",
				"STA_INFO_SOFTAP_INSPECT_DHCP_PACKET",
				"STA_INFO_SOFTAP_HARD_START_XMIT",
				"STA_INFO_SOFTAP_INIT_TX_RX_STA",
				"STA_INFO_SOFTAP_RX_PACKET_CBK",
				"STA_INFO_SOFTAP_REGISTER_STA",
				"STA_INFO_GET_CACHED_STATION_REMOTE",
				"STA_INFO_HDD_GET_STATION_REMOTE",
				"STA_INFO_WLAN_HDD_CFG80211_GET_STATION",
				"STA_INFO_SOFTAP_DEAUTH_CURRENT_STA",
				"STA_INFO_SOFTAP_DEAUTH_ALL_STA",
				"STA_INFO_CFG80211_DEL_STATION",
				"STA_INFO_HDD_CLEAR_ALL_STA",
				"STA_INFO_FILL_STATION_INFO",
				"STA_INFO_HOSTAPD_SAP_EVENT_CB",
				"STA_INFO_SAP_INDICATE_DISCONNECT_FOR_STA",
				"STA_INFO_IS_PEER_ASSOCIATED",
				"STA_INFO_SAP_SET_TWO_INTS_GETNONE",
				"STA_INFO_SAP_GETASSOC_STAMACADDR",
				"STA_INFO_SOFTAP_GET_STA_INFO",
				"STA_INFO_GET_SOFTAP_LINKSPEED",
				"STA_INFO_CONNECTION_IN_PROGRESS_ITERATOR",
				"STA_INFO_SOFTAP_STOP_BSS",
				"STA_INFO_SOFTAP_CHANGE_STA_STATE",
				"STA_INFO_CLEAR_CACHED_STA_INFO",
				"STA_INFO_ATTACH_DETACH",
				"STA_INFO_SHOW",
				"STA_INFO_SOFTAP_IPA_RX_PKT_CALLBACK",
				"STA_INFO_WLAN_HDD_CFG80211_DUMP_STATION",
				"STA_INFO_SON_GET_DATRATE_INFO",
				"STA_INFO_ID_MAX"};
	int32_t num_dbg_strings = QDF_ARRAY_SIZE(strings);

	if (id >= num_dbg_strings) {
		char *ret = "";

		hdd_err("Debug string not found for debug id %d", id);
		return ret;
	}

	return (char *)strings[id];
}

QDF_STATUS hdd_sta_info_init(struct hdd_sta_info_obj *sta_info_container)
{
	if (!sta_info_container) {
		hdd_err("Parameter null");
		return QDF_STATUS_E_INVAL;
	}

	qdf_spinlock_create(&sta_info_container->sta_obj_lock);
	qdf_list_create(&sta_info_container->sta_obj, HDD_MAX_PEERS);

	return QDF_STATUS_SUCCESS;
}

void hdd_sta_info_deinit(struct hdd_sta_info_obj *sta_info_container)
{
	if (!sta_info_container) {
		hdd_err("Parameter null");
		return;
	}

	qdf_list_destroy(&sta_info_container->sta_obj);
	qdf_spinlock_destroy(&sta_info_container->sta_obj_lock);
}

QDF_STATUS hdd_sta_info_attach(struct hdd_sta_info_obj *sta_info_container,
			       struct hdd_station_info *sta_info)
{
	if (!sta_info_container || !sta_info) {
		hdd_err("Parameter(s) null");
		return QDF_STATUS_E_INVAL;
	}

	qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	hdd_take_sta_info_ref(sta_info_container, sta_info, false,
			      STA_INFO_ATTACH_DETACH);
	qdf_list_insert_front(&sta_info_container->sta_obj,
			      &sta_info->sta_node);
	sta_info->is_attached = true;

	qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);

	return QDF_STATUS_SUCCESS;
}

void hdd_sta_info_detach(struct hdd_sta_info_obj *sta_info_container,
			 struct hdd_station_info **sta_info)
{
	struct hdd_station_info *info;

	if (!sta_info_container || !sta_info) {
		hdd_err("Parameter(s) null");
		return;
	}

	info = *sta_info;

	if (!info)
		return;

	qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	if (info->is_attached) {
		info->is_attached = false;
		hdd_put_sta_info_ref(sta_info_container, sta_info, false,
				     STA_INFO_ATTACH_DETACH);
	} else {
		hdd_info("Stainfo is already detached");
	}

	qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);
}

struct hdd_station_info *hdd_get_sta_info_by_id(
				struct hdd_sta_info_obj *sta_info_container,
				const int idx,
				wlan_sta_info_dbgid sta_info_dbgid)
{
	struct hdd_station_info *sta_info = NULL;
	int i = 0;

	if (!sta_info_container) {
		hdd_err("Parameter(s) null");
		return NULL;
	}

	qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	qdf_list_for_each(&sta_info_container->sta_obj, sta_info, sta_node) {
		if (qdf_is_macaddr_broadcast(&sta_info->sta_mac))
			continue;
		if (i == idx) {
			hdd_take_sta_info_ref(sta_info_container,
					      sta_info, false, sta_info_dbgid);
			qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);
			return sta_info;
		}
		i++;
	}

	qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);

	return NULL;
}

struct hdd_station_info *hdd_get_sta_info_by_mac(
				struct hdd_sta_info_obj *sta_info_container,
				const uint8_t *mac_addr,
				wlan_sta_info_dbgid sta_info_dbgid)
{
	struct hdd_station_info *sta_info = NULL;

	if (!mac_addr || !sta_info_container ||
	    qdf_is_macaddr_zero((struct qdf_mac_addr *)mac_addr)) {
		hdd_err("Parameter(s) null");
		return NULL;
	}

	qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	qdf_list_for_each(&sta_info_container->sta_obj, sta_info, sta_node) {
		if (qdf_is_macaddr_equal(&sta_info->sta_mac,
					 (struct qdf_mac_addr *)mac_addr) ||
		    qdf_is_macaddr_equal(&sta_info->mld_addr,
					 (struct qdf_mac_addr *)mac_addr)) {
			hdd_take_sta_info_ref(sta_info_container,
					      sta_info, false, sta_info_dbgid);
			qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);
			return sta_info;
		}
	}

	qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);

	return NULL;
}

void hdd_take_sta_info_ref(struct hdd_sta_info_obj *sta_info_container,
			   struct hdd_station_info *sta_info,
			   bool lock_required,
			   wlan_sta_info_dbgid sta_info_dbgid)
{
	if (!sta_info_container || !sta_info) {
		hdd_err("Parameter(s) null");
		return;
	}

	if (sta_info_dbgid >= STA_INFO_ID_MAX) {
		hdd_err("Invalid sta_info debug id %d", sta_info_dbgid);
		return;
	}

	if (lock_required)
		qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	qdf_atomic_inc(&sta_info->ref_cnt);
	qdf_atomic_inc(&sta_info->ref_cnt_dbgid[sta_info_dbgid]);

	if (lock_required)
		qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);
}

void
hdd_put_sta_info_ref(struct hdd_sta_info_obj *sta_info_container,
		     struct hdd_station_info **sta_info, bool lock_required,
		     wlan_sta_info_dbgid sta_info_dbgid)
{
	struct hdd_station_info *info;
	struct qdf_mac_addr addr;

	if (!sta_info_container || !sta_info) {
		hdd_err("Parameter(s) null");
		return;
	}

	info = *sta_info;

	if (!info) {
		hdd_err("station info NULL");
		return;
	}

	if (sta_info_dbgid >= STA_INFO_ID_MAX) {
		hdd_err("Invalid sta_info debug id %d", sta_info_dbgid);
		return;
	}

	if (lock_required)
		qdf_spin_lock_bh(&sta_info_container->sta_obj_lock);

	/*
	 * In case the put_ref is called more than twice for a single take_ref,
	 * this will result in either a BUG or page fault. In both the cases,
	 * the root cause would be known and the buggy put_ref can be taken
	 * care of.
	 */
	if (!qdf_atomic_read(&info->ref_cnt_dbgid[sta_info_dbgid])) {
		hdd_err("Sta_info ref count put is detected without get for debug id %s",
			sta_info_string_from_dbgid(sta_info_dbgid));

		QDF_BUG(0);
	}

	qdf_atomic_dec(&info->ref_cnt);
	qdf_atomic_dec(&info->ref_cnt_dbgid[sta_info_dbgid]);

	if (qdf_atomic_read(&info->ref_cnt)) {
		if (lock_required)
			qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);
		return;
	}

	qdf_copy_macaddr(&addr, &info->sta_mac);
	if (info->assoc_req_ies.len) {
		qdf_mem_free(info->assoc_req_ies.ptr);
		info->assoc_req_ies.ptr = NULL;
		info->assoc_req_ies.len = 0;
	}

	qdf_list_remove_node(&sta_info_container->sta_obj, &info->sta_node);
	qdf_mem_free(info);
	*sta_info = NULL;

	if (lock_required)
		qdf_spin_unlock_bh(&sta_info_container->sta_obj_lock);

	hdd_nofl_debug("STA_INFO: " QDF_MAC_ADDR_FMT " freed",
		       QDF_MAC_ADDR_REF(addr.bytes));
}

void hdd_clear_cached_sta_info(struct hdd_adapter *adapter)
{
	struct hdd_station_info *sta_info = NULL, *tmp = NULL;

	if (!adapter) {
		hdd_err("Parameter null");
		return;
	}

	hdd_for_each_sta_ref_safe(adapter->cache_sta_info_list, sta_info, tmp,
				  STA_INFO_CLEAR_CACHED_STA_INFO) {
		hdd_sta_info_detach(&adapter->cache_sta_info_list, &sta_info);
		hdd_put_sta_info_ref(&adapter->cache_sta_info_list, &sta_info,
				     true, STA_INFO_CLEAR_CACHED_STA_INFO);
	}
}

QDF_STATUS
hdd_get_front_sta_info_no_lock(struct hdd_sta_info_obj *sta_info_container,
			       struct hdd_station_info **out_sta_info)
{
	QDF_STATUS status;
	qdf_list_node_t *node;

	*out_sta_info = NULL;

	status = qdf_list_peek_front(&sta_info_container->sta_obj, &node);

	if (QDF_IS_STATUS_ERROR(status))
		return status;

	*out_sta_info =
		qdf_container_of(node, struct hdd_station_info, sta_node);

	return QDF_STATUS_SUCCESS;
}

QDF_STATUS
hdd_get_next_sta_info_no_lock(struct hdd_sta_info_obj *sta_info_container,
			      struct hdd_station_info *current_sta_info,
			      struct hdd_station_info **out_sta_info)
{
	QDF_STATUS status;
	qdf_list_node_t *node;

	if (!current_sta_info)
		return QDF_STATUS_E_INVAL;

	*out_sta_info = NULL;

	status = qdf_list_peek_next(&sta_info_container->sta_obj,
				    &current_sta_info->sta_node,
				    &node);

	if (QDF_IS_STATUS_ERROR(status))
		return status;

	*out_sta_info =
		qdf_container_of(node, struct hdd_station_info, sta_node);

	return QDF_STATUS_SUCCESS;
}

