blob: 4430fa09e2506f1b02631e08660ef0b80aca07ec [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2010 - 2014 Intel Corporation. All rights reserved.
* Copyright(c) 2013 - 2014 Intel Mobile Communications GmbH
* Copyright(c) 2016 - 2017 Intel Deutschland GmbH
* Copyright(c) 2018 Intel Corporation
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*****************************************************************************/
#include "iwl-tm-gnl.h"
#include <net/genetlink.h>
#include <linux/export.h>
#include "iwl-csr.h"
#include "iwl-dnt-cfg.h"
#include "iwl-dnt-dispatch.h"
#include "iwl-drv.h"
#include "iwl-fh.h"
#include "iwl-io.h"
#include "iwl-op-mode.h"
#include "iwl-prph.h"
#include "iwl-tm-infc.h"
#include "iwl-trans.h"
/**
* iwl_tm_validate_fw_cmd() - Validates FW host command input data
* @data_in: Input to be validated
*
*/
static int iwl_tm_validate_fw_cmd(struct iwl_tm_data* data_in) {
struct iwl_tm_cmd_request* cmd_req;
uint32_t 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.
*/
static int iwl_tm_validate_reg_ops(struct iwl_tm_data* data_in) {
struct iwl_tm_regs_request* request;
uint32_t request_size;
uint32_t 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
*/
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)
*/
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 = (uint64_t)dev->dnt->mon_base_addr;
data_out->data = resp;
data_out->len = sizeof(*resp);
return 0;
}
static bool iwl_tm_gnl_valid_hw_addr(uint32_t 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
*/
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;
uint32_t 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;
}
if ((cmd_in->offset < IWL_ABS_PRPH_START) && (cmd_in->offset >= IWL_ABS_PRPH_START + PRPH_END)) {
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
*/
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;
}
if ((cmd_in->offset < IWL_ABS_PRPH_START) && (cmd_in->offset >= IWL_ABS_PRPH_START + PRPH_END)) {
return 0;
}
return -EINVAL;
}
/**
* iwl_tm_notifications_en() - Checks input for enable test notifications
* @tst: Device's test data pointer
* @data_in: uint32_t notification (flag)
*/
static int iwl_tm_notifications_en(struct iwl_test* tst, struct iwl_tm_data* data_in) {
uint32_t notification_en;
if (!data_in->data || (data_in->len != sizeof(uint32_t))) {
return -EINVAL;
}
notification_en = *(uint32_t*)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
*
*/
static int iwl_tm_validate_tx_cmd(struct iwl_tm_data* data_in) {
struct iwl_tm_mod_tx_request* cmd_req;
uint32_t 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
*
*/
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
*
*/
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 = CSR_HW_REV_STEP(trans->hw_rev);
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));
#ifdef BACKPORTS_BRANCH_TSTAMP
strncpy(resp->branch_time, BACKPORTS_BRANCH_TSTAMP, sizeof(resp->branch_time));
#endif
strncpy(resp->build_time, BACKPORTS_BUILD_TSTAMP, sizeof(resp->build_time));
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);
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;
uint32_t 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 __genl_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)
*/
static struct iwl_tm_gnl_dev* iwl_tm_gnl_get_dev(const char* dev_name) {
struct iwl_tm_gnl_dev *dev_itr, *dev = NULL;
iwl_assert_lock_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_message() - 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
*/
static struct sk_buff* iwl_tm_gnl_create_msg(uint32_t pid, uint32_t 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
*
* Initiate a message sending to user space (as apposed
* to replying to a message that was initiated by user
* space). Uses multicast broadcasting method.
*/
int iwl_tm_gnl_send_msg(struct iwl_trans* trans, uint32_t cmd, bool check_notify, void* data_out,
uint32_t data_len, gfp_t flags) {
struct iwl_tm_gnl_dev* dev;
struct iwl_tm_gnl_cmd cmd_data;
struct sk_buff* skb;
uint32_t 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
*/
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(genl_info_snd_portid(info), 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
*/
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;
}
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
*/
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
* @tst: Device's test data
* @data_out: Dump data
*/
static int iwl_tm_trace_dump(struct iwl_tm_gnl_dev* dev, struct iwl_tm_data* data_out) {
int ret;
uint32_t 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.
*/
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
*/
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
*/
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
* 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
*/
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];
void* 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 = (uint8_t*)cb->args[1];
dump_size = cb->args[2];
dump_offset = cb->args[3];
nlmsg_head = genlmsg_put(skb, NETLINK_CB_PORTID(cb->skb), 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 = genl_info_snd_portid(info);
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 __genl_const struct genl_ops iwl_tm_gnl_ops[] = {
{
.cmd = IWL_TM_GNL_CMD_EXECUTE,
.policy = iwl_tm_gnl_msg_policy,
.doit = iwl_tm_gnl_cmd_do,
.dumpit = iwl_tm_gnl_dump,
.done = iwl_tm_gnl_done,
},
{
.cmd = IWL_TM_GNL_CMD_SUBSCRIBE_EVENTS,
.policy = iwl_tm_gnl_msg_policy,
.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,
.module = THIS_MODULE,
.ops = iwl_tm_gnl_ops,
.n_ops = ARRAY_SIZE(iwl_tm_gnl_ops),
.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 == netlink_notify_portid(notify)) {
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
*
* 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
*/
int iwl_tm_gnl_exit(void) {
netlink_unregister_notifier(&iwl_tm_gnl_netlink_notifier);
return genl_unregister_family(&iwl_tm_gnl_family);
}
/**
* iwl_tm_fw_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);