/******************************************************************************
 *
 * Copyright(c) 2019 - 2023 Realtek 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.
 *
 *****************************************************************************/
#include <drv_types.h>

#if defined(CONFIG_CMD_GENERAL) || defined(CONFIG_STA_CMD_DISPR)
#ifdef CONFIG_CMD_GENERAL/*for warkaround*/
static void
phl_run_core_cmd(void *drv_priv, u8 *cmd, u32 cmd_len, enum rtw_phl_status status)
{
	struct dvobj_priv *dvobj = (struct dvobj_priv *)drv_priv;
	struct cmd_obj *pcmd = (struct cmd_obj *)cmd;
	_adapter *padapter = pcmd->padapter;
	struct cmd_priv *pcmdpriv = &dvobj->cmdpriv;

	if (status == RTW_PHL_STATUS_CANNOT_IO ||
		status == RTW_PHL_STATUS_CMD_ERROR ||
		RTW_CANNOT_RUN(dvobj)) {
		RTW_INFO(FUNC_ADPT_FMT "%s FALSE -bDriverStopped(%s) bSurpriseRemoved(%s)\n"
			, FUNC_ADPT_ARG(padapter)
			, rtw_cmd_name(pcmd)
			, dev_is_drv_stopped(dvobj) ? "True" : "False"
			, dev_is_surprise_removed(dvobj) ? "True" : "False");

		if (pcmd->cmdcode == CMD_SET_DRV_EXTRA) {
			struct drvextra_cmd_parm *extra_parm =
				(struct drvextra_cmd_parm *)pcmd->parmbuf;

			if (extra_parm->pbuf && (extra_parm->size > 0))
				rtw_mfree(extra_parm->pbuf, extra_parm->size);
		}

		_rtw_mutex_lock(&pcmdpriv->sctx_mutex);
		if (pcmd->sctx) {
			if (0)
				RTW_PRINT(FUNC_ADPT_FMT" pcmd->sctx\n", FUNC_ADPT_ARG(pcmd->padapter));
			rtw_sctx_done_err(&pcmd->sctx, RTW_SCTX_DONE_CMD_DROP);
		}
		_rtw_mutex_unlock(&pcmdpriv->sctx_mutex);
		rtw_free_cmd_obj(pcmd);
		return;
	}

	if (status == RTW_PHL_STATUS_CMD_TIMEOUT)
		RTW_ERR("%s %s cmd timeout\n", __func__, rtw_cmd_name(pcmd));
	else if (status == RTW_PHL_STATUS_FAILURE) /*PHL fail due to 1. CMD_DROP:cmd abort or cancel 2.CMD_FAIL*/
		RTW_ERR("%s %s cmd failure\n", __func__, rtw_cmd_name(pcmd));

	rtw_run_cmd(padapter, pcmd, false);
}
#endif /* CONFIG_CMD_GENERAL */

#ifdef CONFIG_STA_CMD_DISPR
static u32 _evt_joinbss_hdl(struct _ADAPTER *a, struct wlan_network *network)
{
	struct dvobj_priv *d;
	u8 *mac;
	struct sta_info *sta;
	enum rtw_phl_status status;
	u32 res = _FAIL;


	d = adapter_to_dvobj(a);
	mac = network->network.MacAddress;
	sta = rtw_get_stainfo(&a->stapriv, mac);
	if (!sta) {
		RTW_ERR(FUNC_ADPT_FMT ": sta(" MAC_FMT ") not found!\n",
			FUNC_ADPT_ARG(a), MAC_ARG(mac));
		goto disconnect;
	}

	if (network->join_res < 0)
		goto disconnect;

	status = rtw_phl_connect_linked(d->phl, a->connect_bidx, a->phl_role, sta->phl_sta, mac);
	if (status == RTW_PHL_STATUS_SUCCESS)
		return _SUCCESS;

	RTW_ERR(FUNC_ADPT_FMT ": rtw_phl_connect_linked FAIL(%u)!\n",
		FUNC_ADPT_ARG(a), status);

disconnect:
	RTW_INFO(FUNC_ADPT_FMT ": something may go wrong, run disconnect! "
		 "join_res=%d for " MAC_FMT "\n",
		 FUNC_ADPT_ARG(a), network->join_res, MAC_ARG(mac));

	status = rtw_connect_disconnect_prepare(a);
	if (status != RTW_PHL_STATUS_SUCCESS) {
		RTW_ERR(FUNC_ADPT_FMT ": rtw_connect_disconnect_prepare FAIL(%u)!\n",
			FUNC_ADPT_ARG(a), status);
		goto exit;
	}
	res = _SUCCESS;

exit:
	return res;
}
#endif /* CONFIG_STA_CMD_DISPR */


#ifdef CONFIG_PCIE_TRX_MIT
static void rtw_pcie_trx_mit_cb(void *drv_priv, u8 *cmd, u32 cmd_len, enum rtw_phl_status status)
{
	/* cmd point to mit_info. */
	struct rtw_pcie_trx_mit_info_t *mit_info = (struct rtw_pcie_trx_mit_info_t *)cmd;

	RTW_DBG("%s: mit_info pointer=%p\n", __func__, mit_info);
	rtw_mfree(mit_info, sizeof(struct rtw_pcie_trx_mit_info_t));
}

u8 rtw_pcie_trx_mit_cmd(_adapter *padapter, u32 tx_timer, u8 tx_counter,
			u32 rx_timer, u8 rx_counter, u8 fixed_mit)
{
	struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
	struct rtw_pcie_trx_mit_info_t *mit_info;
	enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE;
	u8 res = _FAIL;
	/* ToDo CONFIG_RTW_MLD: [currently primary link only] */
	struct _ADAPTER_LINK *padapter_link = GET_PRIMARY_LINK(padapter);

	mit_info = (struct rtw_pcie_trx_mit_info_t *)rtw_zmalloc(sizeof(struct rtw_pcie_trx_mit_info_t));
	if (mit_info == NULL)
		goto exit;

	mit_info->tx_timer = tx_timer;
	mit_info->tx_counter = tx_counter;
	mit_info->rx_timer = rx_timer;
	mit_info->rx_counter = rx_counter;
	mit_info->fixed_mitigation = fixed_mit;

	RTW_DBG("%s: mit_info pointer=%p\n", __func__, mit_info);

	psts = rtw_phl_cmd_enqueue(dvobj->phl,
				padapter_link->wrlink->hw_band,
				MSG_EVT_PCIE_TRX_MIT,
				(u8 *)mit_info, sizeof(mit_info),
				rtw_pcie_trx_mit_cb,
				PHL_CMD_NO_WAIT, 0);

	/* Send cmd fail */
	if (psts != RTW_PHL_STATUS_SUCCESS) {
		RTW_ERR("%s: cmd failed!\n", __func__);
		rtw_mfree(mit_info, sizeof(struct rtw_pcie_trx_mit_info_t));
		goto exit;
	}

	res = _SUCCESS;

exit:
	return res;
}
#endif /* CONFIG_PCIE_TRX_MIT */

#ifdef CONFIG_POST_CORE_KEEP_ALIVE
u8 rtw_set_post_keep_alive_param(_adapter *padapter, u8 if_sta_chk_rx_bmp,
			   u8 if_sta_chk_tx_bmp, u32 *if_ap_chk_sta_bmp)
{
	struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
	struct rtw_keep_alive_param klive_param = {0};
	enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE;
	u8 res = _FAIL;
	bool need_post_chk = _FALSE;
	u8 i;

#ifdef CONFIG_AP_MODE
	for (i = 0; i < dvobj->iface_nums; i++) {
		if (if_ap_chk_sta_bmp[i] != 0) {
			need_post_chk = _TRUE;
			break;
		}
	}
#endif
	/* skip post keep alive chk */
	if (!need_post_chk && if_sta_chk_rx_bmp == 0 && if_sta_chk_tx_bmp == 0) {
		res = _SUCCESS;
		goto exit;
	}

	klive_param.sta_chk_rx_bmp = if_sta_chk_rx_bmp;
	klive_param.sta_chk_tx_bmp = if_sta_chk_tx_bmp;
	_rtw_memcpy(klive_param.ap_chk_sta_bmp, if_ap_chk_sta_bmp, sizeof(klive_param.ap_chk_sta_bmp));

	psts = rtw_phl_set_wdog_state_keep_alive(dvobj->phl, true, &klive_param);
	if (psts == RTW_PHL_STATUS_SUCCESS)
		res = _SUCCESS;

exit:
	return res;
}
#endif

#ifdef CONFIG_TDLS
enum rtw_phl_status rtw_send_tdls_sync_msg(struct _ADAPTER *a)
{
	struct dvobj_priv *d = adapter_to_dvobj(a);
	struct phl_msg msg = {0};
	struct phl_msg_attribute attr = {0};
	struct rtw_wifi_role_t *role = a->phl_role;
	enum rtw_phl_status status;
	/* ToDo CONFIG_RTW_MLD: [currently primary link only] */
	struct _ADAPTER_LINK *alink = GET_PRIMARY_LINK(a);

	RTW_DBG(FUNC_ADPT_FMT ": +\n", FUNC_ADPT_ARG(a));

	SET_MSG_MDL_ID_FIELD(msg.msg_id, PHL_MDL_MRC);
	SET_MSG_EVT_ID_FIELD(msg.msg_id, MSG_EVT_TDLS_SYNC);
	msg.inbuf = (u8 *)role;
	msg.rsvd[0].ptr = (u8 *)alink->wrlink;
	msg.band_idx = alink->wrlink->hw_band;

	status = rtw_phl_send_msg_to_dispr(GET_PHL_INFO(d),
					   &msg, &attr, NULL);
	if (status != RTW_PHL_STATUS_SUCCESS) {
		RTW_ERR(FUNC_ADPT_FMT ": Send tdls sync event fail(0x%02x)!\n",
			FUNC_ADPT_ARG(a), status);
	}

	return status;
}
#endif /* CONFIG_TDLS */

u32 rtw_enqueue_phl_cmd(struct cmd_obj *pcmd)
{
	u32 res = _FAIL;
	_adapter *padapter = pcmd->padapter;
	enum phl_band_idx band_idx = pcmd->band_idx;
	struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
	enum rtw_phl_status psts = RTW_PHL_STATUS_FAILURE;

	if (band_idx >= HW_BAND_MAX) {
		/* ToDo CONFIG_RTW_MLD: [currently primary link only] */
		struct _ADAPTER_LINK *padapter_link = GET_PRIMARY_LINK(padapter);

		if (rtw_is_adapter_up(padapter) == _FALSE) {
			RTW_ERR("%s Drop CMD: %s\n", __func__, rtw_cmd_name(pcmd));
			goto free_cmd;
		}

		band_idx = ALINK_GET_HWBAND(padapter_link);
	}

#if defined(CONFIG_STA_CMD_DISPR) || defined(CONFIG_CMD_AP_DISPR)
	switch (pcmd->cmdcode) {
#ifdef CONFIG_STA_CMD_DISPR
	case CMD_JOINBSS:
		/*
		* Call rtw_connect_cmd() in rtw_join_cmd_hdl()
		*/
		break;

	case CMD_DISCONNECT:
		/*
		 * Disconnect for STA wouldn't come to here,
		 * because it would be handled in rtw_disassoc_cmd() directly.
		 */
		break;

	case CMD_SET_MLME_EVT:
		{
		struct rtw_evt_header *hdr;
		struct wlan_network *network;

		hdr = (struct rtw_evt_header*)pcmd->parmbuf;
		switch (hdr->id) {
		case EVT_JOINBSS:
			network = (struct wlan_network *)(pcmd->parmbuf + sizeof(*hdr));
			res = _evt_joinbss_hdl(padapter, network);
			goto free_cmd;

		case EVT_DEL_STA:
			if (MLME_IS_STA(padapter)) {
				psts = rtw_disconnect_cmd(padapter, pcmd);
				if (psts != RTW_PHL_STATUS_SUCCESS) {
					if (psts == RTW_PHL_STATUS_RESOURCE)
						res = _SUCCESS;
					goto free_cmd;
				}
				/* pcmd & pcmd->parmbuf would be freed in framework */
				res = _SUCCESS;
				goto exit;
			}
			break;

		default:
			break;
		}
		/* not handled event would be processed later */
		break;
		}
#endif /* CONFIG_STA_CMD_DISPR */
#ifdef CONFIG_AP_CMD_DISPR
	case CMD_CREATE_BSS:
		{
		psts = rtw_ap_start_cmd(pcmd);
		if (psts != RTW_PHL_STATUS_SUCCESS)
			goto free_cmd;
		/* pcmd & pcmd->parmbuf should be freed here */
		res = _SUCCESS;
		goto exit;
		}
#endif /* CONFIG_AP_CMD_DISPR */


#ifdef CONFIG_AP_CMD_DISPR
	case CMD_SET_DRV_EXTRA:
		{
		struct drvextra_cmd_parm *parm;

		parm = (struct drvextra_cmd_parm *)pcmd->parmbuf;
		if (parm->ec_id == STOP_AP_WK_CID) {
			psts = rtw_ap_stop_cmd(pcmd);
			if (psts != RTW_PHL_STATUS_SUCCESS)
				goto free_cmd;
			res = _SUCCESS;
			goto exit;
		}
		break;
		}
#endif /* CONFIG_AP_CMD_DISPR */

	default:
		break;
	}
#endif /* CONFIG_STA_CMD_DISPR || CONFIG_CMD_AP_DISPR */

#ifdef CONFIG_CMD_GENERAL
	psts = rtw_phl_cmd_enqueue(dvobj->phl,
			band_idx,
			MSG_EVT_LINUX_CMD_WRK,
			(u8 *)pcmd, sizeof(struct cmd_obj),
			phl_run_core_cmd,
			PHL_CMD_NO_WAIT, 0);

	/* Send cmd fail */
	if (psts != RTW_PHL_STATUS_SUCCESS)
		goto free_cmd; /* keep res == _FAIL */
#endif /* CONFIG_CMD_GENERAL */

	res = _SUCCESS;
	goto exit;

free_cmd:
	if (pcmd->cmdcode == CMD_SET_DRV_EXTRA) {
		struct drvextra_cmd_parm *extra_parm =
			(struct drvextra_cmd_parm *)pcmd->parmbuf;

		if (extra_parm->pbuf && (extra_parm->size > 0))
			rtw_mfree(extra_parm->pbuf, extra_parm->size);
	}
#ifdef CONFIG_80211BE_EHT
	else if (pcmd->cmdcode == CMD_SET_MLME_EVT) {
		struct rtw_evt_header *hdr =
			(struct rtw_evt_header*)pcmd->parmbuf;

		hdr = (struct rtw_evt_header*)pcmd->parmbuf;
		if (hdr && hdr->id == EVT_JOINBSS) {
			struct wlan_network *network =
				(struct wlan_network *)(pcmd->parmbuf + sizeof(*hdr));
			if (network && network->network.is_mld) {
				rtw_free_cloned_mld_network(network->network.mld_network);
				pcmd->parmbuf = NULL;
			}
		}
	}
#endif
	rtw_free_cmd_obj(pcmd);

exit:
	return res;
}
#endif

#ifdef CONFIG_FSM
static void phl_run_core_cmd(void *priv, void *parm, bool discard)
{
	_adapter *padapter = (_adapter *)priv;
	struct cmd_obj *pcmd = (struct cmd_obj *)parm;

	rtw_run_cmd(padapter, pcmd, discard);
}

#define PHL_RES2RES(a) (a == RTW_PHL_STATUS_SUCCESS) ? _SUCCESS : _FAIL
u32 rtw_enqueue_phl_cmd(struct cmd_obj *pcmd)
{
	u32 res = RTW_PHL_STATUS_FAILURE;
	_adapter *padapter = pcmd->padapter;
	struct dvobj_priv *dvobj = adapter_to_dvobj(padapter);
	struct phl_cmd_job job;
	void *msg;

	_rtw_memset(&job, 0, sizeof(job));

	switch (pcmd->cmdcode) {
	case CMD_SITE_SURVEY:
	{
		res = rtw_site_survey_fsm(padapter, pcmd);
	}
	break;
	case CMD_SET_DRV_EXTRA:
	{
		struct drvextra_cmd_parm *parm;

		parm = (struct drvextra_cmd_parm *)pcmd->parmbuf;

		if ((parm->ec_id ==  MGNT_TX_WK_CID) &&
			(rtw_cfg80211_get_is_roch(padapter))) {

			rtw_phl_job_fill_fptr(dvobj->phl, &job,
				phl_run_core_cmd, padapter,
				pcmd, rtw_cmd_name(pcmd),
				(pcmd->no_io) ? PWR_NO_IO : PWR_BASIC_IO);

			res = rtw_phl_scan_off_ch_tx(
				dvobj->phl, &job, sizeof(job));

			if (res != RTW_PHL_STATUS_SUCCESS)
				goto free_cmd;

			return PHL_RES2RES(res);
		}
	}
		fallthrough;
	default:
		rtw_phl_job_fill_fptr(dvobj->phl, &job,
			phl_run_core_cmd, padapter,
			pcmd, rtw_cmd_name(pcmd),
			(pcmd->no_io) ? PWR_NO_IO : PWR_BASIC_IO);

		res = phl_cmd_complete_job(dvobj->phl, &job);
		if (res != RTW_PHL_STATUS_SUCCESS)
			goto free_cmd;

		return PHL_RES2RES(res);
	}

free_cmd:

	if (pcmd->cmdcode == CMD_SET_DRV_EXTRA) {
		struct drvextra_cmd_parm *extra_parm =
			(struct drvextra_cmd_parm *)pcmd->parmbuf;

		if (extra_parm->pbuf && extra_parm->size > 0)
			rtw_mfree(extra_parm->pbuf, extra_parm->size);
	}
	rtw_free_cmd_obj(pcmd);

	return PHL_RES2RES(res);
}
#endif /*CONFIG_FSM*/

