// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
 * Copyright (C) 2010-2014, 2018-2023, 2025 Intel Corporation
 * Copyright (C) 2013-2014 Intel Mobile Communications GmbH
 * Copyright (C) 2016-2017 Intel Deutschland GmbH
 */
#include <linux/export.h>
#include <net/genetlink.h>
#include "iwl-drv.h"
#include "iwl-io.h"
#include "iwl-fh.h"
#include "iwl-prph.h"
#include "iwl-trans.h"
#include "iwl-op-mode.h"
#include "iwl-tm-gnl.h"
#include "iwl-tm-infc.h"
#include "iwl-csr.h"

/**
 * iwl_tm_validate_fw_cmd() - Validates FW host command input data
 * @data_in:	Input to be validated
 * Returns: an error code
 */
static int iwl_tm_validate_fw_cmd(struct iwl_tm_data *data_in)
{
	struct iwl_tm_cmd_request *cmd_req;
	u32 data_buf_size;

	if (!data_in->data ||
	    (data_in->len < sizeof(struct iwl_tm_cmd_request)))
		return -EINVAL;

	cmd_req = (struct iwl_tm_cmd_request *)data_in->data;

	data_buf_size = data_in->len - sizeof(struct iwl_tm_cmd_request);
	if (data_buf_size < cmd_req->len)
		return -EINVAL;

	return 0;
}

/**
 * iwl_tm_validate_reg_ops() - Checks the input data for registers operations
 * @data_in:	data is casted to iwl_tm_regs_request len in
 *		the size of the request struct in bytes.
 * Returns: an error code
 */
static int iwl_tm_validate_reg_ops(struct iwl_tm_data *data_in)
{
	struct iwl_tm_regs_request *request;
	u32 request_size;
	u32 idx;

	if (!data_in->data ||
	    (data_in->len < sizeof(struct iwl_tm_regs_request)))
		return -EINVAL;

	request = (struct iwl_tm_regs_request *)(data_in->data);
	request_size = sizeof(struct iwl_tm_regs_request) +
		       request->num * sizeof(struct iwl_tm_reg_op);
	if (data_in->len < request_size)
		return -EINVAL;

	/*
	 * Calculate result size - result is returned only for read ops
	 * Also, verifying inputs
	 */
	for (idx = 0;  idx < request->num; idx++) {
		if (request->reg_ops[idx].op_type >= IWL_TM_REG_OP_MAX)
			return -EINVAL;

		/*
		* Allow access only to FH/CSR/HBUS in direct mode.
		* Since we don't have the upper bounds for the CSR
		* and HBUS segments, we will use only the upper
		* bound of FH for sanity check.
		*/
		if (!IS_AL_ADDR(request->reg_ops[idx].address)) {
			if (request->reg_ops[idx].address >=
			    FH_MEM_UPPER_BOUND)
				return -EINVAL;
		}
	}

	return 0;
}

static bool iwl_tm_gnl_valid_hw_addr(u32 addr)
{
	/* TODO need to implement */
	return true;
}

/**
 * iwl_tm_validate_sram_write_req() - Checks input data of SRAM write request
 * @dev:	testmode device struct
 * @data_in:	SRAM access request
 * Returns: an error code
 */
static int iwl_tm_validate_sram_write_req(struct iwl_tm_gnl_dev *dev,
					  struct iwl_tm_data *data_in)
{
	struct iwl_tm_sram_write_request *cmd_in;
	u32 data_buf_size;

	if (!dev->trans->op_mode) {
		IWL_ERR(dev->trans, "No op_mode!\n");
		return -ENODEV;
	}

	if (!data_in->data ||
	    data_in->len < sizeof(struct iwl_tm_sram_write_request))
		return -EINVAL;

	cmd_in = data_in->data;

	data_buf_size =
		data_in->len - sizeof(struct iwl_tm_sram_write_request);
	if (data_buf_size < cmd_in->len)
		return -EINVAL;

	if (iwl_tm_gnl_valid_hw_addr(cmd_in->offset))
		return 0;

	return -EINVAL;
}

/**
 * iwl_tm_validate_sram_read_req() - Checks input data of SRAM read request
 * @dev:	testmode device struct
 * @data_in:	SRAM access request
 * Returns: an error code
 */
static int iwl_tm_validate_sram_read_req(struct iwl_tm_gnl_dev *dev,
					 struct iwl_tm_data *data_in)
{
	struct iwl_tm_sram_read_request *cmd_in;

	if (!dev->trans->op_mode) {
		IWL_ERR(dev->trans, "No op_mode!\n");
		return -ENODEV;
	}

	if (!data_in->data ||
	    data_in->len < sizeof(struct iwl_tm_sram_read_request))
		return -EINVAL;

	cmd_in = data_in->data;

	if (iwl_tm_gnl_valid_hw_addr(cmd_in->offset))
		return 0;

	return -EINVAL;
}

/**
 * iwl_tm_notifications_en() - Checks input for enable test notifications
 * @tst:	Device's test data pointer
 * @data_in:	u32 notification (flag)
 * Returns: an error code
 */
static int iwl_tm_notifications_en(struct iwl_test *tst,
				   struct iwl_tm_data *data_in)
{
	u32 notification_en;

	if (!data_in->data || (data_in->len != sizeof(u32)))
		return -EINVAL;

	notification_en = *(u32 *)data_in->data;
	if ((notification_en != NOTIFICATIONS_ENABLE) &&
	    (notification_en != NOTIFICATIONS_DISABLE))
		return -EINVAL;

	tst->notify = notification_en == NOTIFICATIONS_ENABLE;

	return 0;
}

/**
 * iwl_tm_validate_tx_cmd() - Validates FW host command input data
 * @data_in:	Input to be validated
 * Returns: an error code
 */
static int iwl_tm_validate_tx_cmd(struct iwl_tm_data *data_in)
{
	struct iwl_tm_mod_tx_request *cmd_req;
	u32 data_buf_size;

	if (!data_in->data ||
	    (data_in->len < sizeof(struct iwl_tm_mod_tx_request)))
		return -EINVAL;

	cmd_req = (struct iwl_tm_mod_tx_request *)data_in->data;

	data_buf_size = data_in->len -
			sizeof(struct iwl_tm_mod_tx_request);
	if (data_buf_size < cmd_req->len)
		return -EINVAL;

	if (cmd_req->sta_id >= IWL_TM_STATION_COUNT)
		return -EINVAL;

	return 0;
}

/**
 * iwl_tm_validate_rx_hdrs_mode_req() - Validates RX headers mode request
 * @data_in:	Input to be validated
 * Returns: an error code
 */
static int iwl_tm_validate_rx_hdrs_mode_req(struct iwl_tm_data *data_in)
{
	if (!data_in->data ||
	    (data_in->len < sizeof(struct iwl_xvt_rx_hdrs_mode_request)))
		return -EINVAL;

	return 0;
}

static int iwl_tm_validate_get_chip_id(struct iwl_trans *trans)
{
	return 0;
}

/**
 * iwl_tm_validate_apmg_pd_mode_req() - Validates apmg rx mode request
 * @data_in:	Input to be validated
 * Returns: an error code
 */
static int iwl_tm_validate_apmg_pd_mode_req(struct iwl_tm_data *data_in)
{
	if (!data_in->data ||
	    (data_in->len != sizeof(struct iwl_xvt_apmg_pd_mode_request)))
		return -EINVAL;

	return 0;
}

#if IS_ENABLED(CPTCFG_IWLXVT)
static int iwl_tm_switch_op_mode(struct iwl_tm_gnl_dev *dev,
				 struct iwl_tm_data *data_in)
{
	struct iwl_switch_op_mode *switch_cmd = data_in->data;
	struct iwl_drv *drv;
	int ret = 0;

	if (data_in->len < sizeof(*switch_cmd))
		return -EINVAL;

	drv = iwl_drv_get_dev_container(dev->trans->dev);
	if (!drv) {
		IWL_ERR(dev->trans, "Couldn't retrieve device information\n");
		return -ENODEV;
	}

	/* Executing switch command */
	ret = iwl_drv_switch_op_mode(drv, switch_cmd->new_op_mode);

	if (ret < 0)
		IWL_ERR(dev->trans, "Failed to switch op mode to %s (err:%d)\n",
			switch_cmd->new_op_mode, ret);

	return ret;
}

static int iwl_tm_select_op_mode(struct iwl_tm_gnl_dev *dev,
				 struct iwl_tm_data *data_in)
{
	struct iwl_select_op_mode *select_cmd = data_in->data;
	struct iwl_drv *drv;
	bool xvt;
	int ret;

	if (data_in->len < sizeof(*select_cmd))
		return -EINVAL;

	drv = iwl_drv_get_dev_container(dev->trans->dev);
	if (!drv) {
		IWL_ERR(dev->trans, "Couldn't retrieve device information\n");
		return -ENODEV;
	}

	xvt = select_cmd->op_mode == IWL_SELECT_XVT_OP_MODE;

	/* Executing select command */
	ret = iwl_drv_select_op_mode(drv, xvt);

	if (ret < 0)
		IWL_ERR(dev->trans, "Failed to select op mode to %d (err:%d)\n",
			select_cmd->op_mode, ret);

	return ret;
}
#endif

static int iwl_tm_gnl_get_sil_step(struct iwl_trans *trans,
				   struct iwl_tm_data *data_out)
{
	struct iwl_sil_step *resp;
	data_out->data =  kmalloc(sizeof(struct iwl_sil_step), GFP_KERNEL);
	if (!data_out->data)
		return -ENOMEM;
	data_out->len = sizeof(struct iwl_sil_step);
	resp = (struct iwl_sil_step *)data_out->data;
	resp->silicon_step = trans->info.hw_rev_step;
	return 0;
}

static int iwl_tm_gnl_get_build_info(struct iwl_trans *trans,
				     struct iwl_tm_data *data_out)
{
	struct iwl_tm_build_info *resp;

	data_out->data =  kmalloc(sizeof(*resp), GFP_KERNEL);
	if (!data_out->data)
		return -ENOMEM;
	data_out->len = sizeof(struct iwl_tm_build_info);
	resp = (struct iwl_tm_build_info *)data_out->data;

	memset(resp, 0 , sizeof(*resp));
	strncpy(resp->driver_version, BACKPORTS_GIT_TRACKED,
		sizeof(resp->driver_version));

	return 0;
}

static int iwl_tm_gnl_get_sil_type(struct iwl_trans * trans,struct iwl_tm_data * data_out)
{
	struct iwl_tm_sil_type *resp;

	resp = kzalloc(sizeof(*resp), GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	resp->silicon_type = CSR_HW_REV_TYPE(trans->info.hw_rev);

	data_out->data = resp;
	data_out->len = sizeof(*resp);

	return 0;
}

static int iwl_tm_gnl_get_rfid(struct iwl_trans *trans,
			       struct iwl_tm_data *data_out)
{
	struct iwl_tm_rfid *resp;

	resp = kzalloc(sizeof(*resp), GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	IWL_DEBUG_INFO(trans, "HW RFID=0x08%X\n", trans->info.hw_rf_id);

	resp->flavor = CSR_HW_RFID_FLAVOR(trans->info.hw_rf_id);
	resp->dash   = CSR_HW_RFID_DASH(trans->info.hw_rf_id);
	resp->step   = CSR_HW_RFID_STEP(trans->info.hw_rf_id);
	resp->type   = CSR_HW_RFID_TYPE(trans->info.hw_rf_id);
	resp->is_cdb = CSR_HW_RFID_IS_CDB(trans->info.hw_rf_id);
	resp->is_jacket = CSR_HW_RFID_IS_JACKET(trans->info.hw_rf_id);

	data_out->data = resp;
	data_out->len = sizeof(*resp);

	return 0;
}

static int iwl_tm_gnl_get_rfid_v2(struct iwl_trans *trans,
				  struct iwl_tm_data *data_out)
{
	struct iwl_tm_rfid *resp;

	resp = kzalloc(sizeof(*resp), GFP_KERNEL);
	if (!resp)
		return -ENOMEM;

	resp->flavor = CSR_HW_RFID_FLAVOR(trans->info.hw_crf_id);
	resp->dash   = CSR_HW_RFID_DASH(trans->info.hw_crf_id);
	resp->step   = CSR_HW_RFID_STEP(trans->info.hw_crf_id);
	resp->type   = CSR_HW_RFID_TYPE(trans->info.hw_rf_id);
	resp->is_cdb = CSR_HW_RFID_IS_CDB(trans->info.hw_rf_id);
	resp->is_jacket = CSR_HW_RFID_IS_JACKET(trans->info.hw_rf_id);

	data_out->data = resp;
	data_out->len = sizeof(*resp);

	return 0;
}

/*
 * Testmode GNL family types (This NL family
 * will eventually replace nl80211 support in
 * iwl xVM modules)
 */

#define IWL_TM_GNL_FAMILY_NAME	"iwl_tm_gnl"
#define IWL_TM_GNL_MC_GRP_NAME	"iwl_tm_mcgrp"
#define IWL_TM_GNL_VERSION_NR	1

#define IWL_TM_GNL_DEVNAME_LEN	256


/**
 * struct iwl_tm_gnl_cmd - Required data for command execution.
 * @dev_name:	Target device
 * @cmd:	Command index
 * @data_in:	Input data
 * @data_out:	Output data, to be sent when
 *		command is done.
 */
struct iwl_tm_gnl_cmd {
	const char *dev_name;
	u32 cmd;
	struct iwl_tm_data data_in;
	struct iwl_tm_data data_out;
};

static struct list_head dev_list; /* protected by mutex or RCU */
static struct mutex dev_list_mtx;

/* Testmode GNL family command attributes  */
enum iwl_tm_gnl_cmd_attr_t {
	IWL_TM_GNL_MSG_ATTR_INVALID = 0,
	IWL_TM_GNL_MSG_ATTR_DEVNAME,
	IWL_TM_GNL_MSG_ATTR_CMD,
	IWL_TM_GNL_MSG_ATTR_DATA,
	IWL_TM_GNL_MSG_ATTR_MAX
};

/* TM GNL family definition */
static struct genl_family iwl_tm_gnl_family;
static const struct genl_multicast_group iwl_tm_gnl_mcgrps[] = {
	{ .name = IWL_TM_GNL_MC_GRP_NAME, },
};

/* TM GNL bus policy */
static const struct nla_policy
iwl_tm_gnl_msg_policy[IWL_TM_GNL_MSG_ATTR_MAX] = {
	[IWL_TM_GNL_MSG_ATTR_DEVNAME] =	{
			.type = NLA_NUL_STRING,
			.len = IWL_TM_GNL_DEVNAME_LEN-1 },
	[IWL_TM_GNL_MSG_ATTR_CMD]  = { .type = NLA_U32, },
	[IWL_TM_GNL_MSG_ATTR_DATA] = { .type = NLA_BINARY, },
};

/**
 * iwl_tm_gnl_get_dev() - Retrieve device information
 * @dev_name:	Device to be found
 *
 * Finds the device information according to device name,
 * must be protected by list mutex when used (mutex is not
 * locked inside the function to allow code flexibility)
 *
 * Returns: an error code
 */
static struct iwl_tm_gnl_dev *iwl_tm_gnl_get_dev(const char *dev_name)
{
	struct iwl_tm_gnl_dev *dev_itr, *dev = NULL;

	lockdep_assert_held(&dev_list_mtx);

	list_for_each_entry(dev_itr, &dev_list, list) {
		if (!strcmp(dev_itr->dev_name, dev_name)) {
			dev = dev_itr;
			break;
		}
	}

	return dev;
}

/**
 * iwl_tm_gnl_create_msg - Creates a genl message
 * @pid:	Netlink PID that the message is addressed to
 * @seq:	sequence number (usually the one of the sender)
 * @cmd_data:	Message's data
 * @flags:	Resources allocation flags
 *
 * Returns: an error code
 */
static struct sk_buff *iwl_tm_gnl_create_msg(u32 pid, u32 seq,
					     struct iwl_tm_gnl_cmd cmd_data,
					     gfp_t flags)
{
	void *nlmsg_head;
	struct sk_buff *skb;
	int ret;

	skb = genlmsg_new(NLMSG_GOODSIZE, flags);
	if (!skb)
		goto send_msg_err;

	nlmsg_head = genlmsg_put(skb, pid, seq,
				 &iwl_tm_gnl_family, 0,
				 IWL_TM_GNL_CMD_EXECUTE);
	if (!nlmsg_head)
		goto send_msg_err;

	ret = nla_put_string(skb, IWL_TM_GNL_MSG_ATTR_DEVNAME,
			     cmd_data.dev_name);
	if (ret)
		goto send_msg_err;

	ret = nla_put_u32(skb, IWL_TM_GNL_MSG_ATTR_CMD,
			  cmd_data.cmd);
	if (ret)
		goto send_msg_err;

	if (cmd_data.data_out.len && cmd_data.data_out.data) {
		ret = nla_put(skb, IWL_TM_GNL_MSG_ATTR_DATA,
			cmd_data.data_out.len, cmd_data.data_out.data);
		if (ret)
			goto send_msg_err;
	}

	genlmsg_end(skb, nlmsg_head);

	return skb;

send_msg_err:
	if (skb)
		kfree_skb(skb);

	return NULL;
}

/**
 * iwl_tm_gnl_send_msg - Sends a message to mcast or userspace listener
 * @trans:	transport
 * @cmd:	Command index
 * @check_notify: only send when notify is set
 * @data_out:	Data to be sent
 * @data_len: data length
 * @flags: allocation flags
 *
 * Initiate a message sending to user space (as apposed
 * to replying to a message that was initiated by user
 * space). Uses multicast broadcasting method.
 *
 * Returns: an error code
 */
int iwl_tm_gnl_send_msg(struct iwl_trans *trans, u32 cmd, bool check_notify,
			void *data_out, u32 data_len, gfp_t flags)
{
	struct iwl_tm_gnl_dev *dev;
	struct iwl_tm_gnl_cmd cmd_data;
	struct sk_buff *skb;
	u32 nlportid;

	if (WARN_ON_ONCE(!trans))
		return -EINVAL;

	if (!trans->tmdev)
		return 0;
	dev = trans->tmdev;

	nlportid = READ_ONCE(dev->nl_events_portid);

	if (check_notify && !dev->tst.notify)
		return 0;

	memset(&cmd_data, 0 , sizeof(struct iwl_tm_gnl_cmd));
	cmd_data.dev_name = dev_name(trans->dev);
	cmd_data.cmd = cmd;
	cmd_data.data_out.data = data_out;
	cmd_data.data_out.len = data_len;

	skb = iwl_tm_gnl_create_msg(nlportid, 0, cmd_data, flags);
	if (!skb)
		return -EINVAL;

	if (nlportid)
		return genlmsg_unicast(&init_net, skb, nlportid);
	return genlmsg_multicast(&iwl_tm_gnl_family, skb, 0, 0, flags);
}
IWL_EXPORT_SYMBOL(iwl_tm_gnl_send_msg);

/**
 * iwl_tm_gnl_reply() - Sends command's results back to user space
 * @info:	info struct received in .doit callback
 * @cmd_data:	Data of command to be responded
 * Returns: an error code
 */
static int iwl_tm_gnl_reply(struct genl_info *info,
			    struct iwl_tm_gnl_cmd cmd_data)
{
	struct sk_buff *skb;

	skb = iwl_tm_gnl_create_msg(info->snd_portid, info->snd_seq,
				    cmd_data, GFP_KERNEL);
	if (!skb)
		return -EINVAL;

	return genlmsg_reply(skb, info);
}

/**
 * iwl_tm_gnl_cmd_execute() - Execute IWL testmode GNL command
 * @cmd_data:	Pointer to the data of command to be executed
 * Returns: an error code
 */
static int iwl_tm_gnl_cmd_execute(struct iwl_tm_gnl_cmd *cmd_data)
{
	struct iwl_tm_gnl_dev *dev;
	bool common_op = false;
	int ret = 0;
	mutex_lock(&dev_list_mtx);
	dev = iwl_tm_gnl_get_dev(cmd_data->dev_name);
	mutex_unlock(&dev_list_mtx);
	if (!dev)
		return -ENODEV;

	IWL_DEBUG_INFO(dev->trans, "%s cmd=0x%X\n", __func__, cmd_data->cmd);
	switch (cmd_data->cmd) {

	case IWL_TM_USER_CMD_HCMD:
		ret = iwl_tm_validate_fw_cmd(&cmd_data->data_in);
		break;

	case IWL_TM_USER_CMD_REG_ACCESS:
		ret = iwl_tm_validate_reg_ops(&cmd_data->data_in);
		break;

	case IWL_TM_USER_CMD_SRAM_WRITE:
		ret = iwl_tm_validate_sram_write_req(dev, &cmd_data->data_in);
		break;

	case IWL_TM_USER_CMD_BEGIN_TRACE:
		ret = -EOPNOTSUPP;
		common_op = true;
		break;

	case IWL_TM_USER_CMD_END_TRACE:
		ret = -EOPNOTSUPP;
		common_op = true;
		break;

	case IWL_XVT_CMD_MOD_TX:
		ret = iwl_tm_validate_tx_cmd(&cmd_data->data_in);
		break;

	case IWL_XVT_CMD_RX_HDRS_MODE:
		ret =  iwl_tm_validate_rx_hdrs_mode_req(&cmd_data->data_in);
		break;

	case IWL_XVT_CMD_APMG_PD_MODE:
		ret =  iwl_tm_validate_apmg_pd_mode_req(&cmd_data->data_in);
		break;

	case IWL_TM_USER_CMD_NOTIFICATIONS:
		ret = iwl_tm_notifications_en(&dev->tst, &cmd_data->data_in);
		common_op = true;
		break;

	case IWL_TM_USER_CMD_GET_DEVICE_STATUS:
		ret = -EOPNOTSUPP;
		common_op = true;
		break;
#if IS_ENABLED(CPTCFG_IWLXVT)
	case IWL_TM_USER_CMD_SWITCH_OP_MODE:
		ret = iwl_tm_switch_op_mode(dev, &cmd_data->data_in);
		common_op = true;
		break;
	case IWL_TM_USER_CMD_SELECT_OP_MODE:
		ret = iwl_tm_select_op_mode(dev, &cmd_data->data_in);
		common_op = true;
		break;
#endif
	case IWL_XVT_CMD_GET_CHIP_ID:
		ret = iwl_tm_validate_get_chip_id(dev->trans);
		break;

	case IWL_TM_USER_CMD_GET_SIL_STEP:
		ret = iwl_tm_gnl_get_sil_step(dev->trans, &cmd_data->data_out);
		common_op = true;
		break;

	case IWL_TM_USER_CMD_GET_DRIVER_BUILD_INFO:
		ret = iwl_tm_gnl_get_build_info(dev->trans,
						&cmd_data->data_out);
		common_op = true;
		break;

	case IWL_TM_USER_CMD_GET_SIL_TYPE:
		ret = iwl_tm_gnl_get_sil_type(dev->trans, &cmd_data->data_out);
		common_op = true;
		break;

	case IWL_TM_USER_CMD_GET_RFID:
		ret = iwl_tm_gnl_get_rfid(dev->trans, &cmd_data->data_out);
		common_op = true;
		break;

	case IWL_TM_USER_CMD_GET_RFID_V2:
		ret = iwl_tm_gnl_get_rfid_v2(dev->trans, &cmd_data->data_out);
		common_op = true;
		break;
	}

	if (ret) {
		IWL_ERR(dev->trans, "%s Error=%d\n", __func__, ret);
		return ret;
	}

	if (!common_op)
		ret = iwl_tm_execute_cmd(&dev->trans->testmode, cmd_data->cmd,
					 &cmd_data->data_in,
					 &cmd_data->data_out);

	if (ret)
		IWL_ERR(dev->trans, "%s ret=%d\n", __func__, ret);
	else
		IWL_DEBUG_INFO(dev->trans, "%s ended Ok\n", __func__);
	return ret;
}

/**
 * iwl_tm_mem_dump() - Returns memory buffer data
 * @dev:	testmode device struct
 * @data_in:	input data
 * @data_out:	Dump data
 * Returns: an error code
 */
static int iwl_tm_mem_dump(struct iwl_tm_gnl_dev *dev,
			   struct iwl_tm_data *data_in,
			   struct iwl_tm_data *data_out)
{
	int ret;

	ret = iwl_tm_validate_sram_read_req(dev, data_in);
	if (ret)
		return ret;

	return iwl_tm_execute_cmd(&dev->trans->testmode,
				  IWL_TM_USER_CMD_SRAM_READ,
				  data_in, data_out);
}

/**
 * iwl_tm_gnl_command_dump() - Returns dump buffer data
 * @cmd_data:	Pointer to the data of dump command.
 *		Only device name and command index are the relevant input.
 *		Data out is the start address of the buffer, and it's size.
 * Returns: an error code
 */
static int iwl_tm_gnl_command_dump(struct iwl_tm_gnl_cmd *cmd_data)
{
	struct iwl_tm_gnl_dev *dev;
	int ret = 0;

	mutex_lock(&dev_list_mtx);
	dev = iwl_tm_gnl_get_dev(cmd_data->dev_name);
	mutex_unlock(&dev_list_mtx);
	if (!dev)
		return -ENODEV;

	switch (cmd_data->cmd) {

	case IWL_TM_USER_CMD_TRACE_DUMP:
		ret = -EOPNOTSUPP;
		break;

	case IWL_TM_USER_CMD_SRAM_READ:
		ret = iwl_tm_mem_dump(dev,
				      &cmd_data->data_in,
				      &cmd_data->data_out);
		break;

	default:
		return -EOPNOTSUPP;
	}

	return ret;
}

/**
 * iwl_tm_gnl_parse_msg - Extract input cmd data out of netlink attributes
 * @attrs:	Input netlink attributes
 * @cmd_data:	Command
 * Returns: an error code
 */
static int iwl_tm_gnl_parse_msg(struct nlattr **attrs,
				struct iwl_tm_gnl_cmd *cmd_data)
{
	memset(cmd_data, 0 , sizeof(struct iwl_tm_gnl_cmd));

	if (!attrs[IWL_TM_GNL_MSG_ATTR_DEVNAME] ||
	    !attrs[IWL_TM_GNL_MSG_ATTR_CMD])
		return -EINVAL;

	cmd_data->dev_name = nla_data(attrs[IWL_TM_GNL_MSG_ATTR_DEVNAME]);
	cmd_data->cmd = nla_get_u32(attrs[IWL_TM_GNL_MSG_ATTR_CMD]);

	if (attrs[IWL_TM_GNL_MSG_ATTR_DATA]) {
		cmd_data->data_in.data =
			nla_data(attrs[IWL_TM_GNL_MSG_ATTR_DATA]);
		cmd_data->data_in.len =
			nla_len(attrs[IWL_TM_GNL_MSG_ATTR_DATA]);
	}

	return 0;
}

/**
 * iwl_tm_gnl_cmd_do() - Executes IWL testmode GNL command
 * @skb: SKB with the command data
 * @info: generic netlink info
 * Returns: an error code
 */
static int iwl_tm_gnl_cmd_do(struct sk_buff *skb, struct genl_info *info)
{
	struct iwl_tm_gnl_cmd cmd_data;
	int ret;

	ret = iwl_tm_gnl_parse_msg(info->attrs, &cmd_data);
	if (ret)
		return ret;

	ret = iwl_tm_gnl_cmd_execute(&cmd_data);
	if (!ret && cmd_data.data_out.len) {
		ret = iwl_tm_gnl_reply(info, cmd_data);
		/*
		 * In this case, data out should be allocated in
		 * iwl_tm_gnl_cmd_execute so it should be freed
		 * here
		 */
		kfree(cmd_data.data_out.data);
	}

	return ret;
}

/**
 * iwl_tm_gnl_dump() - Executes IWL testmode GNL command
 * @skb: SKB to fill
 * @cb: netlink callback data
 *
 * cb->args[0]:	Command number, incremented by one (as it may be zero)
 *		We're keeping the command so we can tell if is it the
 *		first dump call or not.
 * cb->args[1]:	Buffer data to dump
 * cb->args[2]:	Buffer size
 * cb->args[3]: Buffer offset from where to dump in the next round
 * Returns: an error code
 */
static int iwl_tm_gnl_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
	struct iwl_tm_gnl_cmd cmd_data;
	void *nlmsg_head = NULL;
	struct nlattr *attrs[IWL_TM_GNL_MSG_ATTR_MAX];
	u8 *dump_addr;
	unsigned long dump_offset;
	int dump_size, chunk_size, ret;

	if (!cb->args[0]) {
		/*
		 * This is the first part of the dump - Parse dump data
		 * out of the data in the netlink header and set up the
		 * dump in cb->args[].
		 */
		ret = nlmsg_parse(cb->nlh, GENL_HDRLEN, attrs,
				  IWL_TM_GNL_MSG_ATTR_MAX - 1,
				  iwl_tm_gnl_msg_policy, NULL);
		if (ret)
			return ret;

		ret = iwl_tm_gnl_parse_msg(attrs, &cmd_data);
		if (ret)
			return ret;

		ret = iwl_tm_gnl_command_dump(&cmd_data);
		if (ret)
			return ret;

		/* Incrementing command since command number may be zero */
		cb->args[0] = cmd_data.cmd + 1;
		cb->args[1] = (unsigned long)cmd_data.data_out.data;
		cb->args[2] = cmd_data.data_out.len;
		cb->args[3] = 0;

		if (!cb->args[2])
			return -ENODATA;
	}

	dump_addr = (u8 *)cb->args[1];
	dump_size = cb->args[2];
	dump_offset = cb->args[3];

	nlmsg_head = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
				 cb->nlh->nlmsg_seq,
				 &iwl_tm_gnl_family, NLM_F_MULTI,
				 IWL_TM_GNL_CMD_EXECUTE);

	/*
	 * Reserve some room for NL attribute header,
	 * 16 bytes should be enough.
	 */
	chunk_size = skb_tailroom(skb) - 16;
	if (chunk_size <= 0) {
		ret = -ENOMEM;
		goto dump_err;
	}

	if (chunk_size > dump_size - dump_offset)
		chunk_size = dump_size - dump_offset;

	if (chunk_size) {
		ret = nla_put(skb, IWL_TM_GNL_MSG_ATTR_DATA,
			      chunk_size, dump_addr + dump_offset);
		if (ret)
			goto dump_err;
	}

	genlmsg_end(skb, nlmsg_head);

	/* move offset */
	cb->args[3] += chunk_size;

	return cb->args[2] - cb->args[3];

dump_err:
	genlmsg_cancel(skb, nlmsg_head);
	return ret;
}

static int iwl_tm_gnl_done(struct netlink_callback *cb)
{
	switch (cb->args[0] - 1) {
	case IWL_TM_USER_CMD_SRAM_READ:
	case IWL_TM_USER_CMD_TRACE_DUMP:
		kfree((void *)cb->args[1]);
		return 0;
	}

	return -EOPNOTSUPP;
}

static int iwl_tm_gnl_cmd_subscribe(struct sk_buff *skb, struct genl_info *info)
{
	struct iwl_tm_gnl_dev *dev;
	const char *dev_name;
	int ret;

	if (!info->attrs[IWL_TM_GNL_MSG_ATTR_DEVNAME])
		return -EINVAL;

	dev_name = nla_data(info->attrs[IWL_TM_GNL_MSG_ATTR_DEVNAME]);

	mutex_lock(&dev_list_mtx);
	dev = iwl_tm_gnl_get_dev(dev_name);
	if (!dev) {
		ret = -ENODEV;
		goto unlock;
	}

	if (dev->nl_events_portid) {
		ret = -EBUSY;
		goto unlock;
	}

	dev->nl_events_portid = info->snd_portid;
	ret = 0;

 unlock:
	mutex_unlock(&dev_list_mtx);
	return ret;
}

/*
 * iwl_tm_gnl_ops - GNL Family commands.
 * There is only one NL command, and only one callback,
 * which handles all NL messages.
 */
static const struct genl_ops iwl_tm_gnl_ops[] = {
	{
	  .cmd = IWL_TM_GNL_CMD_EXECUTE,
#if LINUX_VERSION_IS_GEQ(5,2,0)
	  .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
#endif

	  .doit = iwl_tm_gnl_cmd_do,
	  .dumpit = iwl_tm_gnl_dump,
	  .done = iwl_tm_gnl_done,
	},
	{
		.cmd = IWL_TM_GNL_CMD_SUBSCRIBE_EVENTS,
#if LINUX_VERSION_IS_GEQ(5,2,0)
		.validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
#endif

		.doit = iwl_tm_gnl_cmd_subscribe,
	},
};

static struct genl_family iwl_tm_gnl_family __genl_ro_after_init = {
	.hdrsize	= 0,
	.name		= IWL_TM_GNL_FAMILY_NAME,
	.version	= IWL_TM_GNL_VERSION_NR,
	.maxattr	= IWL_TM_GNL_MSG_ATTR_MAX,
	.policy		= iwl_tm_gnl_msg_policy,
	.module		= THIS_MODULE,
	.ops		= iwl_tm_gnl_ops,
	.n_ops		= ARRAY_SIZE(iwl_tm_gnl_ops),
#if LINUX_VERSION_IS_GEQ(6,1,0)
	.resv_start_op  = IWL_TM_GNL_CMD_SUBSCRIBE_EVENTS + 1,
#endif
	.mcgrps		= iwl_tm_gnl_mcgrps,
	.n_mcgrps	= ARRAY_SIZE(iwl_tm_gnl_mcgrps),
};

/**
 * iwl_tm_gnl_add() - Registers a devices/op-mode in the iwl-tm-gnl list
 * @trans:	transport struct for the device to register for
 */
void iwl_tm_gnl_add(struct iwl_trans *trans)
{
	struct iwl_tm_gnl_dev *dev;

	if (!trans)
		return;

	if (trans->tmdev)
		return;

	mutex_lock(&dev_list_mtx);

	if (iwl_tm_gnl_get_dev(dev_name(trans->dev)))
		goto unlock;

	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
	if (!dev)
		goto unlock;

	dev->dev_name = dev_name(trans->dev);
	trans->tmdev = dev;
	dev->trans = trans;
	list_add_tail_rcu(&dev->list, &dev_list);

unlock:
	mutex_unlock(&dev_list_mtx);
}

/**
 * iwl_tm_gnl_remove() - Unregisters a devices/op-mode in the iwl-tm-gnl list
 * @trans:	transport struct for the device
 */
void iwl_tm_gnl_remove(struct iwl_trans *trans)
{
	struct iwl_tm_gnl_dev *dev_itr, *tmp;

	if (WARN_ON_ONCE(!trans))
		return;

	/* Searching for operation mode in list */
	mutex_lock(&dev_list_mtx);
	list_for_each_entry_safe(dev_itr, tmp, &dev_list, list) {
		if (dev_itr->trans == trans) {
			/*
			 * Device found. Removing it from list
			 * and releasing it's resources
			 */
			list_del_rcu(&dev_itr->list);
			synchronize_rcu();
			kfree(dev_itr);
			break;
		}
	}

	trans->tmdev = NULL;
	mutex_unlock(&dev_list_mtx);
}

static int iwl_tm_gnl_netlink_notify(struct notifier_block *nb,
				     unsigned long state,
				     void *_notify)
{
	struct netlink_notify *notify = _notify;
	struct iwl_tm_gnl_dev *dev;

	rcu_read_lock();
	list_for_each_entry_rcu(dev, &dev_list, list) {
		if (dev->nl_events_portid == notify->portid)
			dev->nl_events_portid = 0;
	}
	rcu_read_unlock();

	return NOTIFY_OK;
}

static struct notifier_block iwl_tm_gnl_netlink_notifier = {
	.notifier_call = iwl_tm_gnl_netlink_notify,
};


/**
 * iwl_tm_gnl_init() - Registers tm-gnl module
 * Returns: an error code
 *
 * Registers Testmode GNL family and initializes
 * TM GNL global variables
 */
int iwl_tm_gnl_init(void)
{
	int ret;

	INIT_LIST_HEAD(&dev_list);
	mutex_init(&dev_list_mtx);

	ret = genl_register_family(&iwl_tm_gnl_family);
	if (ret)
		return ret;
	ret = netlink_register_notifier(&iwl_tm_gnl_netlink_notifier);
	if (ret)
		genl_unregister_family(&iwl_tm_gnl_family);
	return ret;
}

/**
 * iwl_tm_gnl_exit() - Unregisters Testmode GNL family
 * Returns: an error code
 */
int iwl_tm_gnl_exit(void)
{
	netlink_unregister_notifier(&iwl_tm_gnl_netlink_notifier);
	return genl_unregister_family(&iwl_tm_gnl_family);
}

/**
 * iwl_tm_gnl_send_rx - Send a spontaneous rx message to user
 * @trans:	Pointer to the transport layer
 * @rxb:	Contains rx packet to be sent
 */
void iwl_tm_gnl_send_rx(struct iwl_trans *trans, struct iwl_rx_cmd_buffer *rxb)
{
	struct iwl_rx_packet *pkt = rxb_addr(rxb);
	int length = iwl_rx_packet_len(pkt);

	/* the length doesn't include len_n_flags field, so add it manually */
	length += sizeof(__le32);

	iwl_tm_gnl_send_msg(trans, IWL_TM_USER_CMD_NOTIF_UCODE_RX_PKT, true,
			    (void *)pkt, length, GFP_ATOMIC);
}
IWL_EXPORT_SYMBOL(iwl_tm_gnl_send_rx);
