blob: 0cc99099a5355d32e60869777eb205d98f0f5769 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2010-2014, 2018-2023 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-dnt-cfg.h"
#include "iwl-dnt-dispatch.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;
}
/**
* iwl_tm_trace_end - Ends debug trace. Common for all op modes.
* @dev: testmode device struct
* Returns: an error code
*/
static int iwl_tm_trace_end(struct iwl_tm_gnl_dev *dev)
{
struct iwl_trans *trans = dev->trans;
struct iwl_test_trace *trace = &dev->tst.trace;
if (!trace->enabled)
return -EILSEQ;
if (trace->cpu_addr && trace->dma_addr)
dma_free_coherent(trans->dev, trace->size,
trace->cpu_addr, trace->dma_addr);
memset(trace, 0, sizeof(struct iwl_test_trace));
return 0;
}
/**
* iwl_tm_trace_begin() - Checks input data for trace request
* @dev: testmode device struct
* @data_in: Only size is relevant - Desired size of trace buffer.
* @data_out: Trace request data (address & size)
* Returns: an error code
*/
static int iwl_tm_trace_begin(struct iwl_tm_gnl_dev *dev,
struct iwl_tm_data *data_in,
struct iwl_tm_data *data_out)
{
struct iwl_tm_trace_request *req = data_in->data;
struct iwl_tm_trace_request *resp;
if (!data_in->data ||
data_in->len < sizeof(struct iwl_tm_trace_request))
return -EINVAL;
req = data_in->data;
/* size zero means use the default */
if (!req->size)
req->size = TRACE_BUFF_SIZE_DEF;
else if (req->size < TRACE_BUFF_SIZE_MIN ||
req->size > TRACE_BUFF_SIZE_MAX)
return -EINVAL;
else if (!dev->dnt->mon_buf_cpu_addr)
return -ENOMEM;
resp = kmalloc(sizeof(*resp), GFP_KERNEL);
if (!resp) {
return -ENOMEM;
}
resp->size = dev->dnt->mon_buf_size;
/* Casting to avoid compilation warnings when DMA address is 32bit */
resp->addr = (u64)dev->dnt->mon_base_addr;
data_out->data = resp;
data_out->len = sizeof(*resp);
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;
}
static int iwl_tm_get_device_status(struct iwl_tm_gnl_dev *dev,
struct iwl_tm_data *data_in,
struct iwl_tm_data *data_out)
{
__u32 *status;
status = kmalloc(sizeof(__u32), GFP_KERNEL);
if (!status)
return -ENOMEM;
*status = dev->dnt->iwl_dnt_status;
data_out->data = status;
data_out->len = sizeof(__u32);
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;
}
#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->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->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->hw_rf_id);
resp->flavor = CSR_HW_RFID_FLAVOR(trans->hw_rf_id);
resp->dash = CSR_HW_RFID_DASH(trans->hw_rf_id);
resp->step = CSR_HW_RFID_STEP(trans->hw_rf_id);
resp->type = CSR_HW_RFID_TYPE(trans->hw_rf_id);
resp->is_cdb = CSR_HW_RFID_IS_CDB(trans->hw_rf_id);
resp->is_jacket = CSR_HW_RFID_IS_JACKET(trans->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->hw_crf_id);
resp->dash = CSR_HW_RFID_DASH(trans->hw_crf_id);
resp->step = CSR_HW_RFID_STEP(trans->hw_crf_id);
resp->type = CSR_HW_RFID_TYPE(trans->hw_rf_id);
resp->is_cdb = CSR_HW_RFID_IS_CDB(trans->hw_rf_id);
resp->is_jacket = CSR_HW_RFID_IS_JACKET(trans->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 = iwl_tm_trace_begin(dev,
&cmd_data->data_in,
&cmd_data->data_out);
common_op = true;
break;
case IWL_TM_USER_CMD_END_TRACE:
ret = iwl_tm_trace_end(dev);
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 = iwl_tm_get_device_status(dev, &cmd_data->data_in,
&cmd_data->data_out);
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;
#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_trace_dump - Returns trace buffer data
* @dev: Device pointer
* @data_out: Dump data
* Returns: an error code
*/
static int iwl_tm_trace_dump(struct iwl_tm_gnl_dev *dev,
struct iwl_tm_data *data_out)
{
int ret;
u32 buf_size;
if (!(dev->dnt->iwl_dnt_status & IWL_DNT_STATUS_MON_CONFIGURED)) {
IWL_ERR(dev->trans, "Invalid monitor status\n");
return -EINVAL;
}
if (dev->dnt->mon_buf_size == 0) {
IWL_ERR(dev->trans, "No available monitor buffer\n");
return -ENOMEM;
}
buf_size = dev->dnt->mon_buf_size;
data_out->data = kmalloc(buf_size, GFP_KERNEL);
if (!data_out->data)
return -ENOMEM;
ret = iwl_dnt_dispatch_pull(dev->trans, data_out->data,
buf_size, MONITOR);
if (ret < 0) {
kfree(data_out->data);
return ret;
}
data_out->len = ret;
return 0;
}
/**
* 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 = iwl_tm_trace_dump(dev, &cmd_data->data_out);
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,
#if CFG80211_VERSION < KERNEL_VERSION(5,2,0)
.policy = iwl_tm_gnl_msg_policy,
#endif
.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,
#if CFG80211_VERSION < KERNEL_VERSION(5,2,0)
.policy = iwl_tm_gnl_msg_policy,
#endif
},
};
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,
#if CFG80211_VERSION >= KERNEL_VERSION(5,2,0)
.policy = iwl_tm_gnl_msg_policy,
#endif
.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);