/**
 * @file mlan_meas.c
 *
 *  @brief Implementation of measurement interface code with the app/firmware
 *
 *  Driver implementation for sending and retrieving measurement requests
 *    and responses.
 *
 *  Current use is limited to 802.11h.
 *
 *  Requires use of the following preprocessor define:
 *    - ENABLE_MEAS
 *
 *
 *  Copyright 2008-2020 NXP
 *
 *  This software file (the File) is distributed by NXP
 *  under the terms of the GNU General Public License Version 2, June 1991
 *  (the License).  You may use, redistribute and/or modify the File in
 *  accordance with the terms and conditions of the License, a copy of which
 *  is available by writing to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the
 *  worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
 *
 *  THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE
 *  ARE EXPRESSLY DISCLAIMED.  The License provides additional details about
 *  this warranty disclaimer.
 *
 */

/*************************************************************
Change Log:
    03/24/2009: initial version
************************************************************/

#include "mlan.h"
#include "mlan_join.h"
#include "mlan_util.h"
#include "mlan_fw.h"
#include "mlan_main.h"
#include "mlan_ioctl.h"
#include "mlan_meas.h"

#ifdef DEBUG_LEVEL2
/** String descriptions of the different measurement enums.  Debug display */
static const char *meas_type_str[WLAN_MEAS_NUM_TYPES] = {
	"basic",
};

/********************************************************
			Local Functions
********************************************************/

/**
 *  @brief Retrieve the measurement string representation of a meas_type enum
 *  Used for debug display only
 *
 *  @param meas_type Measurement type enumeration input for string lookup
 *
 *  @return         Constant string representing measurement type
 */
static const char *wlan_meas_get_meas_type_str(MeasType_t meas_type)
{
	if (meas_type <= WLAN_MEAS_11H_MAX_TYPE)
		return meas_type_str[meas_type];

	return "Invld";
}
#endif

/**
 *  @brief Debug print display of the input measurement request
 *
 *  @param pmeas_req  Pointer to the measurement request to display
 *
 *  @return          N/A
 */
static void
wlan_meas_dump_meas_req(const HostCmd_DS_MEASUREMENT_REQUEST *pmeas_req)
{
	ENTER();

	PRINTM(MINFO, "Meas: Req: ------------------------------\n");

	PRINTM(MINFO, "Meas: Req: mac_addr: " MACSTR "\n",
	       MAC2STR(pmeas_req->mac_addr));

	PRINTM(MINFO, "Meas: Req:  dlgTkn: %d\n", pmeas_req->dialog_token);
	PRINTM(MINFO, "Meas: Req:    mode: dm[%c] rpt[%c] req[%c]\n",
	       pmeas_req->req_mode.duration_mandatory ? 'X' : ' ',
	       pmeas_req->req_mode.report ? 'X' : ' ',
	       pmeas_req->req_mode.request ? 'X' : ' ');
	PRINTM(MINFO, "Meas: Req:        : en[%c] par[%c]\n",
	       pmeas_req->req_mode.enable ? 'X' : ' ',
	       pmeas_req->req_mode.parallel ? 'X' : ' ');
#ifdef DEBUG_LEVEL2
	PRINTM(MINFO, "Meas: Req: measTyp: %s\n",
	       wlan_meas_get_meas_type_str(pmeas_req->meas_type));
#endif

	switch (pmeas_req->meas_type) {
	case WLAN_MEAS_BASIC:
		/* Lazy cheat, fields of bas, cca, rpi union match on the
		 * request */
		PRINTM(MINFO, "Meas: Req: chan: %u\n",
		       pmeas_req->req.basic.channel);
		PRINTM(MINFO, "Meas: Req: strt: %llu\n",
		       wlan_le64_to_cpu(pmeas_req->req.basic.start_time));
		PRINTM(MINFO, "Meas: Req:  dur: %u\n",
		       wlan_le16_to_cpu(pmeas_req->req.basic.duration));
		break;
	default:
		PRINTM(MINFO, "Meas: Req: <unhandled>\n");
		break;
	}

	PRINTM(MINFO, "Meas: Req: ------------------------------\n");
	LEAVE();
}

/**
 *  @brief Debug print display of the input measurement report
 *
 *  @param pmeas_rpt  Pointer to measurement report to display
 *
 *  @return          N/A
 */
static void
wlan_meas_dump_meas_rpt(const HostCmd_DS_MEASUREMENT_REPORT *pmeas_rpt)
{
	MeasType_t type;
	ENTER();

	PRINTM(MINFO, "Meas: Rpt: ------------------------------\n");
	PRINTM(MINFO, "Meas: Rpt: mac_addr: " MACSTR "\n",
	       MAC2STR(pmeas_rpt->mac_addr));

	PRINTM(MINFO, "Meas: Rpt:  dlgTkn: %d\n", pmeas_rpt->dialog_token);

	PRINTM(MINFO, "Meas: Rpt: rptMode: (%x): Rfs[%c] ICp[%c] Lt[%c]\n",
	       *(t_u8 *)&pmeas_rpt->rpt_mode,
	       pmeas_rpt->rpt_mode.refused ? 'X' : ' ',
	       pmeas_rpt->rpt_mode.incapable ? 'X' : ' ',
	       pmeas_rpt->rpt_mode.late ? 'X' : ' ');
#ifdef DEBUG_LEVEL2
	PRINTM(MINFO, "Meas: Rpt: measTyp: %s\n",
	       wlan_meas_get_meas_type_str(pmeas_rpt->meas_type));
#endif

	type = wlan_le32_to_cpu(pmeas_rpt->meas_type);
	switch (type) {
	case WLAN_MEAS_BASIC:
		PRINTM(MINFO, "Meas: Rpt: chan: %u\n",
		       pmeas_rpt->rpt.basic.channel);
		PRINTM(MINFO, "Meas: Rpt: strt: %llu\n",
		       wlan_le64_to_cpu(pmeas_rpt->rpt.basic.start_time));
		PRINTM(MINFO, "Meas: Rpt:  dur: %u\n",
		       wlan_le16_to_cpu(pmeas_rpt->rpt.basic.duration));
		PRINTM(MINFO, "Meas: Rpt:  bas: (%x): unmsd[%c], radar[%c]\n",
		       *(t_u8 *)&(pmeas_rpt->rpt.basic.map),
		       pmeas_rpt->rpt.basic.map.unmeasured ? 'X' : ' ',
		       pmeas_rpt->rpt.basic.map.radar ? 'X' : ' ');
		PRINTM(MINFO, "Meas: Rpt:  bas: unidSig[%c] ofdm[%c] bss[%c]\n",
		       pmeas_rpt->rpt.basic.map.unidentified_sig ? 'X' : ' ',
		       pmeas_rpt->rpt.basic.map.ofdm_preamble ? 'X' : ' ',
		       pmeas_rpt->rpt.basic.map.bss ? 'X' : ' ');
		break;
	default:
		PRINTM(MINFO, "Meas: Rpt: <unhandled>\n");
		break;
	}

	PRINTM(MINFO, "Meas: Rpt: ------------------------------\n");
	LEAVE();
}

/**
 *  @brief Retrieve a measurement report from the firmware
 *
 *  Callback from command processing when a measurement report is received
 *    from the firmware.  Perform the following when a report is received:
 *
 *   -# Debug displays the report if compiled with the appropriate flags
 *   -# If we are pending on a specific measurement report token, and it
 *      matches the received report's token, store the report and wake up
 *      any pending threads
 *
 *  @param pmpriv Private driver information structure
 *  @param resp HostCmd_DS_COMMAND struct returned from the firmware command
 *              passing a HostCmd_DS_MEASUREMENT_REPORT structure.
 *
 *  @return     MLAN_STATUS_SUCCESS
 */
static int wlan_meas_cmdresp_get_report(mlan_private *pmpriv,
					const HostCmd_DS_COMMAND *resp)
{
	mlan_adapter *pmadapter = pmpriv->adapter;
	const HostCmd_DS_MEASUREMENT_REPORT *pmeas_rpt = &resp->params.meas_rpt;

	ENTER();

	PRINTM(MINFO, "Meas: Rpt: %#x-%u, Seq=%u, Ret=%u\n", resp->command,
	       resp->size, resp->seq_num, resp->result);

	/* Debug displays the measurement report */
	wlan_meas_dump_meas_rpt(pmeas_rpt);

	/*
	 * Check if we are pending on a measurement report and it matches
	 *  the dialog token of the received report:
	 */
	if (pmadapter->state_meas.meas_rpt_pend_on &&
	    pmadapter->state_meas.meas_rpt_pend_on == pmeas_rpt->dialog_token) {
		PRINTM(MINFO, "Meas: Rpt: RCV'd Pend on meas #%d\n",
		       pmadapter->state_meas.meas_rpt_pend_on);

		/* Clear the pending report indicator */
		pmadapter->state_meas.meas_rpt_pend_on = 0;

		/* Copy the received report into the measurement state for
		 * retrieval */
		memcpy_ext(pmadapter, &pmadapter->state_meas.meas_rpt_returned,
			   pmeas_rpt,
			   sizeof(pmadapter->state_meas.meas_rpt_returned),
			   sizeof(pmadapter->state_meas.meas_rpt_returned));

		/*
		 * Wake up any threads pending on the wait queue
		 */
		wlan_recv_event(pmpriv, MLAN_EVENT_ID_DRV_MEAS_REPORT, MNULL);
	}

	LEAVE();

	return MLAN_STATUS_SUCCESS;
}

/**
 *  @brief Prepare CMD_MEASURMENT_REPORT firmware command
 *
 *  @param pmpriv     Private driver information structure
 *  @param pcmd_ptr   Output parameter: Pointer to the command being prepared
 *                    for the firmware
 *  @param pinfo_buf  HostCmd_DS_MEASUREMENT_REQUEST passed as void data block
 *
 *  @return          MLAN_STATUS_SUCCESS
 */
static int wlan_meas_cmd_request(mlan_private *pmpriv,
				 HostCmd_DS_COMMAND *pcmd_ptr,
				 const void *pinfo_buf)
{
	const HostCmd_DS_MEASUREMENT_REQUEST *pmeas_req =
		(HostCmd_DS_MEASUREMENT_REQUEST *)pinfo_buf;

	ENTER();

	pcmd_ptr->command = HostCmd_CMD_MEASUREMENT_REQUEST;
	pcmd_ptr->size = sizeof(HostCmd_DS_MEASUREMENT_REQUEST) + S_DS_GEN;

	memcpy_ext(pmpriv->adapter, &pcmd_ptr->params.meas_req, pmeas_req,
		   sizeof(pcmd_ptr->params.meas_req),
		   sizeof(pcmd_ptr->params.meas_req));

	PRINTM(MINFO, "Meas: Req: %#x-%u, Seq=%u, Ret=%u\n", pcmd_ptr->command,
	       pcmd_ptr->size, pcmd_ptr->seq_num, pcmd_ptr->result);

	wlan_meas_dump_meas_req(pmeas_req);

	LEAVE();

	return MLAN_STATUS_SUCCESS;
}

/**
 *  @brief  Retrieve a measurement report from the firmware
 *
 *  The firmware will send a EVENT_MEAS_REPORT_RDY event when it
 *    completes or receives a measurement report.  The event response
 *    handler will then start a HostCmd_CMD_MEASUREMENT_REPORT firmware command
 *    which gets completed for transmission to the firmware in this routine.
 *
 *  @param pmpriv    Private driver information structure
 *  @param pcmd_ptr  Output parameter: Pointer to the command being prepared
 *                   for the firmware
 *
 *  @return        MLAN_STATUS_SUCCESS
 */
static int wlan_meas_cmd_get_report(mlan_private *pmpriv,
				    HostCmd_DS_COMMAND *pcmd_ptr)
{
	ENTER();

	pcmd_ptr->command = HostCmd_CMD_MEASUREMENT_REPORT;
	pcmd_ptr->size = sizeof(HostCmd_DS_MEASUREMENT_REPORT) + S_DS_GEN;

	memset(pmpriv->adapter, &pcmd_ptr->params.meas_rpt, 0x00,
	       sizeof(pcmd_ptr->params.meas_rpt));

	/*
	 * Set the meas_rpt.mac_addr to our mac address to get a meas report,
	 *   setting the mac to another STA address instructs the firmware
	 *   to transmit this measurement report frame instead
	 */
	memcpy_ext(pmpriv->adapter, pcmd_ptr->params.meas_rpt.mac_addr,
		   pmpriv->curr_addr,
		   sizeof(pcmd_ptr->params.meas_rpt.mac_addr),
		   sizeof(pcmd_ptr->params.meas_rpt.mac_addr));

	LEAVE();

	return MLAN_STATUS_SUCCESS;
}

/********************************************************
			Global functions
********************************************************/

/**
 *  @brief Send the input measurement request to the firmware.
 *
 *  If the dialog token in the measurement request is set to 0, the function
 *    will use an local static auto-incremented token in the measurement
 *    request.  This ensures the dialog token is always set.
 *
 *  If wait_for_resp_timeout is set, the function will block its return on
 *     a timeout or returned measurement report that matches the requests
 *     dialog token.
 *
 *  @param pmpriv                  Private driver information structure
 *  @param pmeas_req               Pointer to the measurement request to send
 *  @param wait_for_resp_timeout   Timeout value of the measurement request
 *                                 in ms.
 *  @param pioctl_req              Pointer to IOCTL request buffer
 *  @param pmeas_rpt               Output parameter: Pointer for the resulting
 *                                 measurement report
 *
 *  @return
 *    - 0 for success
 *    - -ETIMEDOUT if the measurement report does not return before
 *      the timeout expires
 *    - Error return from wlan_prepare_cmd routine otherwise
 */
int wlan_meas_util_send_req(mlan_private *pmpriv,
			    HostCmd_DS_MEASUREMENT_REQUEST *pmeas_req,
			    t_u32 wait_for_resp_timeout,
			    pmlan_ioctl_req pioctl_req,
			    HostCmd_DS_MEASUREMENT_REPORT *pmeas_rpt)
{
	static t_u8 auto_dialog_tok;
	wlan_meas_state_t *pmeas_state = &pmpriv->adapter->state_meas;
	int ret;

	ENTER();

	/* If dialogTok was set to 0 or not provided, autoset */
	pmeas_req->dialog_token =
		(pmeas_req->dialog_token ? pmeas_req->dialog_token :
					   ++auto_dialog_tok);

	/* Check for rollover of the dialog token.  Avoid using 0 as a token */
	pmeas_req->dialog_token =
		(pmeas_req->dialog_token ? pmeas_req->dialog_token : 1);

	/*
	 * If the request is to pend waiting for the result, set the dialog
	 * token of this measurement request in the state structure.  The
	 * measurement report handling routines can then check the incoming
	 * measurement reports for a match with this dialog token.
	 */
	if (wait_for_resp_timeout) {
		pmeas_state->meas_rpt_pend_on = pmeas_req->dialog_token;
		PRINTM(MINFO, "Meas: Req: START Pend on meas #%d\n",
		       pmeas_req->dialog_token);
	}

	/* Send the measurement request to the firmware */
	ret = wlan_prepare_cmd(pmpriv, HostCmd_CMD_MEASUREMENT_REQUEST,
			       HostCmd_ACT_GEN_SET, 0, (t_void *)pioctl_req,
			       (void *)pmeas_req);

	LEAVE();
	return ret;
}

/**
 *  @brief  Prepare the HostCmd_DS_Command structure for a measurement command.
 *
 *  Use the Command field to determine if the command being set up is for
 *     11h and call one of the local command handlers accordingly for:
 *
 *        - HostCmd_CMD_MEASUREMENT_REQUEST
 *        - HostCmd_CMD_MEASUREMENT_REPORT
 *
 *  @param pmpriv     Private driver information structure
 *  @param pcmd_ptr   Output parameter: Pointer to the command being prepared
 *                    for the firmware
 *  @param pinfo_buf  Void buffer passthrough with data necessary for a
 *                    specific command type
 *
 *  @return         MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 *
 */
int wlan_meas_cmd_process(mlan_private *pmpriv, HostCmd_DS_COMMAND *pcmd_ptr,
			  const void *pinfo_buf)
{
	int ret = MLAN_STATUS_SUCCESS;

	ENTER();
	switch (pcmd_ptr->command) {
	case HostCmd_CMD_MEASUREMENT_REQUEST:
		ret = wlan_meas_cmd_request(pmpriv, pcmd_ptr, pinfo_buf);
		break;
	case HostCmd_CMD_MEASUREMENT_REPORT:
		ret = wlan_meas_cmd_get_report(pmpriv, pcmd_ptr);
		break;
	default:
		ret = MLAN_STATUS_FAILURE;
	}

	pcmd_ptr->command = wlan_cpu_to_le16(pcmd_ptr->command);
	pcmd_ptr->size = wlan_cpu_to_le16(pcmd_ptr->size);
	LEAVE();
	return ret;
}

/**
 *  @brief Handle the command response from the firmware for a measurement
 *         command
 *
 *  Use the Command field to determine if the command response being
 *    is for meas.  Call the local command response handler accordingly for:
 *
 *        - HostCmd_CMD_802_MEASUREMENT_REQUEST
 *        - HostCmd_CMD_802_MEASUREMENT_REPORT
 *
 *  @param pmpriv Private driver information structure
 *  @param resp   HostCmd_DS_COMMAND struct returned from the firmware command
 *
 *  @return     MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
int wlan_meas_cmdresp_process(mlan_private *pmpriv,
			      const HostCmd_DS_COMMAND *resp)
{
	int ret = MLAN_STATUS_SUCCESS;

	ENTER();
	switch (resp->command) {
	case HostCmd_CMD_MEASUREMENT_REQUEST:
		PRINTM(MINFO, "Meas: Req Resp: Sz=%u, Seq=%u, Ret=%u\n",
		       resp->size, resp->seq_num, resp->result);
		break;
	case HostCmd_CMD_MEASUREMENT_REPORT:
		ret = wlan_meas_cmdresp_get_report(pmpriv, resp);
		break;
	default:
		ret = MLAN_STATUS_FAILURE;
	}

	LEAVE();
	return ret;
}