| /****************************************************************************** |
| * |
| * 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); |