blob: 24690229f01d3a49712f4dbe628a4e01a53e1b21 [file] [log] [blame]
/******************************************************************************
*
* Copyright(c) 2005 - 2014 Intel Corporation. All rights reserved.
* Copyright(c) 2013 - 2015 Intel Mobile Communications GmbH
* Copyright (C) 2015 - 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 "user-infc.h"
#include <linux/dma-mapping.h>
#include <linux/etherdevice.h>
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/pci_ids.h>
#include "iwl-csr.h"
#include "iwl-dnt-cfg.h"
#include "iwl-dnt-dispatch.h"
#include "iwl-drv.h"
#include "iwl-io.h"
#include "iwl-op-mode.h"
#include "iwl-phy-db.h"
#include "iwl-prph.h"
#include "iwl-trans.h"
#include "xvt.h"
#define XVT_UCODE_CALIB_TIMEOUT (CPTCFG_IWL_TIMEOUT_FACTOR * HZ)
#define XVT_SCU_BASE (0xe6a00000)
#define XVT_SCU_SNUM1 (XVT_SCU_BASE + 0x300)
#define XVT_SCU_SNUM2 (XVT_SCU_SNUM1 + 0x4)
#define XVT_SCU_SNUM3 (XVT_SCU_SNUM2 + 0x4)
#define XVT_MAX_TX_COUNT (ULLONG_MAX)
#define XVT_LMAC_0_STA_ID (0) /* must be aligned with station id added in USC */
#define XVT_LMAC_1_STA_ID (3) /* must be aligned with station id added in USC */
#define XVT_STOP_TX (IEEE80211_SCTL_FRAG + 1)
void iwl_xvt_send_user_rx_notif(struct iwl_xvt* xvt, struct iwl_rx_cmd_buffer* rxb) {
struct iwl_rx_packet* pkt = rxb_addr(rxb);
void* data = pkt->data;
uint32_t size = iwl_rx_packet_payload_len(pkt);
IWL_DEBUG_INFO(xvt, "rx notification: group=0x%x, id=0x%x\n", pkt->hdr.group_id, pkt->hdr.cmd);
switch (WIDE_ID(pkt->hdr.group_id, pkt->hdr.cmd)) {
case WIDE_ID(LONG_GROUP, GET_SET_PHY_DB_CMD):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_PHY_DB, data, size, GFP_ATOMIC);
break;
case DTS_MEASUREMENT_NOTIFICATION:
case WIDE_ID(PHY_OPS_GROUP, DTS_MEASUREMENT_NOTIF_WIDE):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_DTS_MEASUREMENTS, data, size, GFP_ATOMIC);
break;
case REPLY_RX_DSP_EXT_INFO:
if (!xvt->rx_hdr_enabled) {
break;
}
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_RX_HDR, data, size, GFP_ATOMIC);
break;
case APMG_PD_SV_CMD:
if (!xvt->apmg_pd_en) {
break;
}
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_APMG_PD, data, size, GFP_ATOMIC);
break;
case REPLY_RX_MPDU_CMD:
if (!xvt->send_rx_mpdu) {
break;
}
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_UCODE_RX_PKT, data, size, GFP_ATOMIC);
break;
case NVM_COMMIT_COMPLETE_NOTIFICATION:
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_COMMIT_STATISTICS, data, size, GFP_ATOMIC);
break;
case REPLY_HD_PARAMS_CMD:
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_BFE, data, size, GFP_ATOMIC);
break;
case DEBUG_LOG_MSG:
iwl_dnt_dispatch_collect_ucode_message(xvt->trans, rxb);
break;
case WIDE_ID(TOF_GROUP, TOF_MCSI_DEBUG_NOTIF):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_LOC_MCSI, data, size, GFP_ATOMIC);
break;
case WIDE_ID(TOF_GROUP, TOF_RANGE_RESPONSE_NOTIF):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_LOC_RANGE, data, size, GFP_ATOMIC);
break;
case WIDE_ID(XVT_GROUP, IQ_CALIB_CONFIG_NOTIF):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_IQ_CALIB, data, size, GFP_ATOMIC);
break;
case WIDE_ID(PHY_OPS_GROUP, CT_KILL_NOTIFICATION):
iwl_xvt_user_send_notif(xvt, IWL_TM_USER_CMD_NOTIF_CT_KILL, data, size, GFP_ATOMIC);
break;
case REPLY_RX_PHY_CMD:
IWL_DEBUG_INFO(xvt, "REPLY_RX_PHY_CMD received but not handled\n");
break;
case INIT_COMPLETE_NOTIF:
IWL_DEBUG_INFO(xvt, "received INIT_COMPLETE_NOTIF\n");
break;
case TX_CMD:
if (xvt->send_tx_resp) {
iwl_xvt_user_send_notif(xvt, IWL_XVT_CMD_TX_CMD_RESP, data, size, GFP_ATOMIC);
}
break;
default:
IWL_DEBUG_INFO(xvt, "xVT mode RX command 0x%x not handled\n", pkt->hdr.cmd);
}
}
static void iwl_xvt_led_enable(struct iwl_xvt* xvt) {
iwl_write32(xvt->trans, CSR_LED_REG, CSR_LED_REG_TURN_ON);
}
static void iwl_xvt_led_disable(struct iwl_xvt* xvt) {
iwl_write32(xvt->trans, CSR_LED_REG, CSR_LED_REG_TURN_OFF);
}
static int iwl_xvt_sdio_io_toggle(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_tm_sdio_io_toggle* sdio_io_toggle = data_in->data;
return iwl_trans_test_mode_cmd(xvt->trans, sdio_io_toggle->enable);
}
/**
* iwl_xvt_read_sv_drop - read SV drop version
* @xvt: xvt data
* Return: the SV drop (>= 0) or a negative error number
*/
static int iwl_xvt_read_sv_drop(struct iwl_xvt* xvt) {
struct xvt_debug_cmd debug_cmd = {
.opcode = cpu_to_le32(XVT_DBG_GET_SVDROP_VER_OP),
.dw_num = 0,
};
struct xvt_debug_res* debug_res;
struct iwl_rx_packet* pkt;
struct iwl_host_cmd host_cmd = {
.id = REPLY_DEBUG_XVT_CMD,
.data[0] = &debug_cmd,
.len[0] = sizeof(debug_cmd),
.dataflags[0] = IWL_HCMD_DFL_NOCOPY,
.flags = CMD_WANT_SKB,
};
int ret;
if (xvt->state != IWL_XVT_STATE_OPERATIONAL) {
return 0;
}
ret = iwl_xvt_send_cmd(xvt, &host_cmd);
if (ret) {
return ret;
}
/* Retrieve response packet */
pkt = host_cmd.resp_pkt;
/* Get response data */
debug_res = (struct xvt_debug_res*)pkt->data;
if (le32_to_cpu(debug_res->dw_num) < 1) {
ret = -ENODATA;
goto out;
}
ret = le32_to_cpu(debug_res->data[0]) & 0xFF;
out:
iwl_free_resp(&host_cmd);
return ret;
}
static int iwl_xvt_get_dev_info(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_tm_dev_info_req* dev_info_req;
struct iwl_tm_dev_info* dev_info;
const uint8_t driver_ver[] = BACKPORTS_GIT_TRACKED;
int sv_step = 0x00;
int dev_info_size;
bool read_sv_drop = true;
if (data_in) {
dev_info_req = (struct iwl_tm_dev_info_req*)data_in->data;
read_sv_drop = dev_info_req->read_sv ? true : false;
}
if (xvt->fwrt.cur_fw_img == IWL_UCODE_REGULAR && read_sv_drop) {
sv_step = iwl_xvt_read_sv_drop(xvt);
if (sv_step < 0) {
return sv_step;
}
}
dev_info_size = sizeof(struct iwl_tm_dev_info) + (strlen(driver_ver) + 1) * sizeof(uint8_t);
dev_info = kzalloc(dev_info_size, GFP_KERNEL);
if (!dev_info) {
return -ENOMEM;
}
dev_info->dev_id = xvt->trans->hw_id;
dev_info->fw_ver = xvt->fw->ucode_ver;
dev_info->vendor_id = PCI_VENDOR_ID_INTEL;
dev_info->build_ver = sv_step;
/*
* TODO: Silicon step is retrieved by reading
* radio register 0x00. Simplifying implementation
* by reading it in user space.
*/
dev_info->silicon_step = 0x00;
strcpy(dev_info->driver_ver, driver_ver);
data_out->data = dev_info;
data_out->len = dev_info_size;
return 0;
}
static int iwl_xvt_set_sw_config(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
struct iwl_xvt_sw_cfg_request* sw_cfg = (struct iwl_xvt_sw_cfg_request*)data_in->data;
struct iwl_phy_cfg_cmd* fw_calib_cmd_cfg = xvt->sw_stack_cfg.fw_calib_cmd_cfg;
__le32 cfg_mask = cpu_to_le32(sw_cfg->cfg_mask), fw_calib_event, fw_calib_flow, event_override,
flow_override;
int usr_idx, iwl_idx;
if (data_in->len < sizeof(struct iwl_xvt_sw_cfg_request)) {
return -EINVAL;
}
xvt->sw_stack_cfg.fw_dbg_flags = sw_cfg->dbg_flags;
xvt->sw_stack_cfg.load_mask = sw_cfg->load_mask;
xvt->sw_stack_cfg.calib_override_mask = sw_cfg->cfg_mask;
for (usr_idx = 0; usr_idx < IWL_USER_FW_IMAGE_IDX_TYPE_MAX; usr_idx++) {
switch (usr_idx) {
case IWL_USER_FW_IMAGE_IDX_INIT:
iwl_idx = IWL_UCODE_INIT;
break;
case IWL_USER_FW_IMAGE_IDX_REGULAR:
iwl_idx = IWL_UCODE_REGULAR;
break;
case IWL_USER_FW_IMAGE_IDX_WOWLAN:
iwl_idx = IWL_UCODE_WOWLAN;
break;
}
/* TODO: Calculate PHY config according to device values */
fw_calib_cmd_cfg[iwl_idx].phy_cfg = cpu_to_le32(xvt->fw->phy_config);
/*
* If a cfg_mask bit is unset, take the default value
* from the FW. Otherwise, take the value from sw_cfg.
*/
fw_calib_event = xvt->fw->default_calib[iwl_idx].event_trigger;
event_override = cpu_to_le32(sw_cfg->calib_ctrl[usr_idx].event_trigger);
fw_calib_cmd_cfg[iwl_idx].calib_control.event_trigger =
(~cfg_mask & fw_calib_event) | (cfg_mask & event_override);
fw_calib_flow = xvt->fw->default_calib[iwl_idx].flow_trigger;
flow_override = cpu_to_le32(sw_cfg->calib_ctrl[usr_idx].flow_trigger);
fw_calib_cmd_cfg[iwl_idx].calib_control.flow_trigger =
(~cfg_mask & fw_calib_flow) | (cfg_mask & flow_override);
}
return 0;
}
static int iwl_xvt_get_sw_config(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_xvt_sw_cfg_request* get_cfg_req;
struct iwl_xvt_sw_cfg_request* sw_cfg;
struct iwl_phy_cfg_cmd* fw_calib_cmd_cfg = xvt->sw_stack_cfg.fw_calib_cmd_cfg;
__le32 event_trigger, flow_trigger;
int i, u;
if (data_in->len < sizeof(struct iwl_xvt_sw_cfg_request)) {
return -EINVAL;
}
get_cfg_req = data_in->data;
sw_cfg = kzalloc(sizeof(*sw_cfg), GFP_KERNEL);
if (!sw_cfg) {
return -ENOMEM;
}
sw_cfg->load_mask = xvt->sw_stack_cfg.load_mask;
sw_cfg->phy_config = xvt->fw->phy_config;
sw_cfg->cfg_mask = xvt->sw_stack_cfg.calib_override_mask;
sw_cfg->dbg_flags = xvt->sw_stack_cfg.fw_dbg_flags;
for (i = 0; i < IWL_UCODE_TYPE_MAX; i++) {
switch (i) {
case IWL_UCODE_INIT:
u = IWL_USER_FW_IMAGE_IDX_INIT;
break;
case IWL_UCODE_REGULAR:
u = IWL_USER_FW_IMAGE_IDX_REGULAR;
break;
case IWL_UCODE_WOWLAN:
u = IWL_USER_FW_IMAGE_IDX_WOWLAN;
break;
case IWL_UCODE_REGULAR_USNIFFER:
continue;
}
if (get_cfg_req->get_calib_type == IWL_XVT_GET_CALIB_TYPE_DEF) {
event_trigger = xvt->fw->default_calib[i].event_trigger;
flow_trigger = xvt->fw->default_calib[i].flow_trigger;
} else {
event_trigger = fw_calib_cmd_cfg[i].calib_control.event_trigger;
flow_trigger = fw_calib_cmd_cfg[i].calib_control.flow_trigger;
}
sw_cfg->calib_ctrl[u].event_trigger = le32_to_cpu(event_trigger);
sw_cfg->calib_ctrl[u].flow_trigger = le32_to_cpu(flow_trigger);
}
data_out->data = sw_cfg;
data_out->len = sizeof(*sw_cfg);
return 0;
}
static int iwl_xvt_send_phy_cfg_cmd(struct iwl_xvt* xvt, uint32_t ucode_type) {
struct iwl_phy_cfg_cmd* calib_cmd_cfg = &xvt->sw_stack_cfg.fw_calib_cmd_cfg[ucode_type];
int err;
IWL_DEBUG_INFO(xvt, "Sending Phy CFG command: 0x%x\n", calib_cmd_cfg->phy_cfg);
/* ESL workaround - calibration is not allowed */
if (CPTCFG_IWL_TIMEOUT_FACTOR > 20) {
calib_cmd_cfg->calib_control.event_trigger = 0;
calib_cmd_cfg->calib_control.flow_trigger = 0;
}
/* Sending calibration configuration control data */
err = iwl_xvt_send_cmd_pdu(xvt, PHY_CONFIGURATION_CMD, 0, sizeof(*calib_cmd_cfg), calib_cmd_cfg);
if (err) {
IWL_ERR(xvt, "Error (%d) running INIT calibrations control\n", err);
}
return err;
}
static int iwl_xvt_continue_init_unified(struct iwl_xvt* xvt) {
struct iwl_nvm_access_complete_cmd nvm_complete = {};
struct iwl_notification_wait init_complete_wait;
static const uint16_t init_complete[] = {INIT_COMPLETE_NOTIF};
int err;
err = iwl_xvt_send_cmd_pdu(xvt, WIDE_ID(REGULATORY_AND_NVM_GROUP, NVM_ACCESS_COMPLETE), 0,
sizeof(nvm_complete), &nvm_complete);
if (err) {
goto init_error;
}
xvt->state = IWL_XVT_STATE_OPERATIONAL;
iwl_init_notification_wait(&xvt->notif_wait, &init_complete_wait, init_complete,
sizeof(init_complete), NULL, NULL);
err = iwl_xvt_send_phy_cfg_cmd(xvt, IWL_UCODE_REGULAR);
if (err) {
iwl_remove_notification(&xvt->notif_wait, &init_complete_wait);
goto init_error;
}
err = iwl_wait_notification(&xvt->notif_wait, &init_complete_wait, XVT_UCODE_CALIB_TIMEOUT);
if (err) {
goto init_error;
}
return 0;
init_error:
xvt->state = IWL_XVT_STATE_UNINITIALIZED;
iwl_trans_stop_device(xvt->trans);
return err;
}
static int iwl_xvt_run_runtime_fw(struct iwl_xvt* xvt, bool cont_run) {
int err;
err = iwl_xvt_run_fw(xvt, IWL_UCODE_REGULAR, cont_run);
if (err) {
goto fw_error;
}
xvt->state = IWL_XVT_STATE_OPERATIONAL;
if (iwl_xvt_is_unified_fw(xvt)) {
err = iwl_xvt_nvm_init(xvt);
if (err) {
IWL_ERR(xvt, "Failed to read NVM: %d\n", err);
return err;
}
return iwl_xvt_continue_init_unified(xvt);
}
/* Send phy db control command and then phy db calibration*/
err = iwl_send_phy_db_data(xvt->phy_db);
if (err) {
goto phy_error;
}
err = iwl_xvt_send_phy_cfg_cmd(xvt, IWL_UCODE_REGULAR);
if (err) {
goto phy_error;
}
return 0;
phy_error:
iwl_trans_stop_device(xvt->trans);
fw_error:
xvt->state = IWL_XVT_STATE_UNINITIALIZED;
return err;
}
static bool iwl_xvt_wait_phy_db_entry(struct iwl_notif_wait_data* notif_wait,
struct iwl_rx_packet* pkt, void* data) {
struct iwl_phy_db* phy_db = data;
if (pkt->hdr.cmd != CALIB_RES_NOTIF_PHY_DB) {
WARN_ON(pkt->hdr.cmd != INIT_COMPLETE_NOTIF);
return true;
}
WARN_ON(iwl_phy_db_set_section(phy_db, pkt));
return false;
}
/*
* iwl_xvt_start_op_mode starts FW according to load mask,
* waits for alive notification from device, and sends it
* to user.
*/
static int iwl_xvt_start_op_mode(struct iwl_xvt* xvt) {
int err = 0;
uint32_t ucode_type = IWL_UCODE_INIT;
/*
* If init FW and runtime FW are both enabled,
* Runtime FW will be executed after "continue
* initialization" is done.
* If init FW is disabled and runtime FW is
* enabled, run Runtime FW. If runtime fw is
* disabled, do nothing.
*/
if (!(xvt->sw_stack_cfg.load_mask & IWL_XVT_LOAD_MASK_INIT)) {
if (xvt->sw_stack_cfg.load_mask & IWL_XVT_LOAD_MASK_RUNTIME) {
err = iwl_xvt_run_runtime_fw(xvt, false);
} else {
if (xvt->state != IWL_XVT_STATE_UNINITIALIZED) {
xvt->fw_running = false;
iwl_trans_stop_device(xvt->trans);
}
err = iwl_trans_start_hw(xvt->trans);
if (err) {
IWL_ERR(xvt, "Failed to start HW\n");
} else {
iwl_write32(xvt->trans, CSR_RESET, 0);
xvt->state = IWL_XVT_STATE_NO_FW;
}
}
return err;
}
/* when fw image is unified, only regular ucode is loaded. */
if (iwl_xvt_is_unified_fw(xvt)) {
ucode_type = IWL_UCODE_REGULAR;
}
err = iwl_xvt_run_fw(xvt, ucode_type, false);
if (err) {
return err;
}
xvt->state = IWL_XVT_STATE_INIT_STARTED;
err = iwl_xvt_nvm_init(xvt);
if (err) {
IWL_ERR(xvt, "Failed to read NVM: %d\n", err);
}
/*
* The initialization flow is not yet complete.
* User need to execute "Continue initialization"
* flow in order to complete it.
*
* NOT sending ALIVE notification to user. User
* knows that FW is alive when "start op mode"
* returns without errors.
*/
return err;
}
static void iwl_xvt_stop_op_mode(struct iwl_xvt* xvt) {
if (xvt->state == IWL_XVT_STATE_UNINITIALIZED) {
return;
}
if (xvt->fw_running) {
iwl_xvt_txq_disable(xvt);
xvt->fw_running = false;
}
iwl_trans_stop_device(xvt->trans);
iwl_free_fw_paging(&xvt->fwrt);
xvt->state = IWL_XVT_STATE_UNINITIALIZED;
}
/*
* iwl_xvt_continue_init get phy calibrations data from
* device and stores them. It also runs runtime FW if it
* is marked in the load mask.
*/
static int iwl_xvt_continue_init(struct iwl_xvt* xvt) {
struct iwl_notification_wait calib_wait;
static const uint16_t init_complete[] = {INIT_COMPLETE_NOTIF, CALIB_RES_NOTIF_PHY_DB};
int err;
if (xvt->state != IWL_XVT_STATE_INIT_STARTED) {
return -EINVAL;
}
if (iwl_xvt_is_unified_fw(xvt)) {
return iwl_xvt_continue_init_unified(xvt);
}
iwl_init_notification_wait(&xvt->notif_wait, &calib_wait, init_complete,
ARRAY_SIZE(init_complete), iwl_xvt_wait_phy_db_entry, xvt->phy_db);
err = iwl_xvt_send_phy_cfg_cmd(xvt, IWL_UCODE_INIT);
if (err) {
iwl_remove_notification(&xvt->notif_wait, &calib_wait);
goto error;
}
/*
* Waiting for the calibration complete notification
* iwl_xvt_wait_phy_db_entry will store the calibrations
*/
err = iwl_wait_notification(&xvt->notif_wait, &calib_wait, XVT_UCODE_CALIB_TIMEOUT);
if (err) {
goto error;
}
xvt->state = IWL_XVT_STATE_OPERATIONAL;
if (xvt->sw_stack_cfg.load_mask & IWL_XVT_LOAD_MASK_RUNTIME)
/* Run runtime FW stops the device by itself if error occurs */
{
err = iwl_xvt_run_runtime_fw(xvt, true);
}
goto cont_init_end;
error:
xvt->state = IWL_XVT_STATE_UNINITIALIZED;
iwl_xvt_txq_disable(xvt);
iwl_trans_stop_device(xvt->trans);
cont_init_end:
return err;
}
static int iwl_xvt_get_phy_db(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_xvt_phy_db_request* phy_db_req = (struct iwl_xvt_phy_db_request*)data_in->data;
struct iwl_xvt_phy_db_request* phy_db_resp;
uint8_t* phy_data;
uint16_t phy_size;
uint32_t resp_size;
int err;
if ((data_in->len < sizeof(struct iwl_xvt_phy_db_request)) || (phy_db_req->size != 0)) {
return -EINVAL;
}
err = iwl_phy_db_get_section_data(xvt->phy_db, phy_db_req->type, &phy_data, &phy_size,
phy_db_req->chg_id);
if (err) {
return err;
}
resp_size = sizeof(*phy_db_resp) + phy_size;
phy_db_resp = kzalloc(resp_size, GFP_KERNEL);
if (!phy_db_resp) {
return -ENOMEM;
}
phy_db_resp->chg_id = phy_db_req->chg_id;
phy_db_resp->type = phy_db_req->type;
phy_db_resp->size = phy_size;
memcpy(phy_db_resp->data, phy_data, phy_size);
data_out->data = phy_db_resp;
data_out->len = resp_size;
return 0;
}
static struct iwl_device_cmd* iwl_xvt_init_tx_dev_cmd(struct iwl_xvt* xvt) {
struct iwl_device_cmd* dev_cmd;
dev_cmd = iwl_trans_alloc_tx_cmd(xvt->trans);
if (unlikely(!dev_cmd)) {
return NULL;
}
memset(dev_cmd, 0, sizeof(*dev_cmd));
dev_cmd->hdr.cmd = TX_CMD;
return dev_cmd;
}
static uint16_t iwl_xvt_get_offload_assist(struct ieee80211_hdr* hdr) {
int hdrlen = ieee80211_hdrlen(hdr->frame_control);
uint16_t offload_assist = 0;
bool amsdu;
amsdu = ieee80211_is_data_qos(hdr->frame_control) &&
(*ieee80211_get_qos_ctl(hdr) & IEEE80211_QOS_CTL_A_MSDU_PRESENT);
if (amsdu) {
offload_assist |= BIT(TX_CMD_OFFLD_AMSDU);
}
/*
* padding is inserted later in transport.
* do not align A-MSDUs to dword, as the subframe header
* aligns the SNAP header.
*/
if (hdrlen % 4 && !amsdu) {
offload_assist |= BIT(TX_CMD_OFFLD_PAD);
}
return offload_assist;
}
static struct iwl_device_cmd* iwl_xvt_set_tx_params_gen3(struct iwl_xvt* xvt, struct sk_buff* skb,
uint32_t rate_flags, uint32_t tx_flags)
{
struct iwl_device_cmd* dev_cmd;
struct iwl_tx_cmd_gen3* cmd;
struct ieee80211_hdr* hdr = (struct ieee80211_hdr*)skb->data;
struct iwl_xvt_skb_info* skb_info = (void*)skb->cb;
uint32_t header_length = ieee80211_hdrlen(hdr->frame_control);
dev_cmd = iwl_xvt_init_tx_dev_cmd(xvt);
if (unlikely(!dev_cmd)) {
return NULL;
}
cmd = (struct iwl_tx_cmd_gen3*)dev_cmd->payload;
cmd->offload_assist |= cpu_to_le32(iwl_xvt_get_offload_assist(hdr));
cmd->len = cpu_to_le16((uint16_t)skb->len);
cmd->flags = cpu_to_le16(tx_flags);
if (ieee80211_has_morefrags(hdr->frame_control))
/* though this flag is not supported for gen3, it is used
* here for silicon feedback tests. */
{
cmd->flags |= cpu_to_le16(TX_CMD_FLG_MORE_FRAG);
}
cmd->rate_n_flags = cpu_to_le32(rate_flags);
/* Copy MAC header from skb into command buffer */
memcpy(cmd->hdr, hdr, header_length);
/* Saving device command address itself in the control buffer, to be
* used when reclaiming the command.
*/
skb_info->dev_cmd = dev_cmd;
return dev_cmd;
}
static struct iwl_device_cmd* iwl_xvt_set_tx_params_gen2(struct iwl_xvt* xvt, struct sk_buff* skb,
uint32_t rate_flags, uint32_t flags) {
struct iwl_device_cmd* dev_cmd;
struct iwl_xvt_skb_info* skb_info = (void*)skb->cb;
struct iwl_tx_cmd_gen2* tx_cmd;
struct ieee80211_hdr* hdr = (struct ieee80211_hdr*)skb->data;
uint32_t header_length = ieee80211_hdrlen(hdr->frame_control);
dev_cmd = iwl_xvt_init_tx_dev_cmd(xvt);
if (unlikely(!dev_cmd)) {
return NULL;
}
tx_cmd = (struct iwl_tx_cmd_gen2*)dev_cmd->payload;
tx_cmd->len = cpu_to_le16((uint16_t)skb->len);
tx_cmd->offload_assist |= cpu_to_le16(iwl_xvt_get_offload_assist(hdr));
tx_cmd->flags = cpu_to_le32(flags);
if (ieee80211_has_morefrags(hdr->frame_control))
/* though this flag is not supported for gen2, it is used
* for silicon feedback tests. */
{
tx_cmd->flags |= cpu_to_le32(TX_CMD_FLG_MORE_FRAG);
}
tx_cmd->rate_n_flags = cpu_to_le32(rate_flags);
/* Copy MAC header from skb into command buffer */
memcpy(tx_cmd->hdr, hdr, header_length);
/* Saving device command address itself in the
* control buffer, to be used when reclaiming
* the command. */
skb_info->dev_cmd = dev_cmd;
return dev_cmd;
}
/*
* Allocates and sets the Tx cmd the driver data pointers in the skb
*/
static struct iwl_device_cmd* iwl_xvt_set_mod_tx_params(struct iwl_xvt* xvt, struct sk_buff* skb,
uint8_t sta_id, uint32_t rate_flags,
uint32_t flags) {
struct iwl_device_cmd* dev_cmd;
struct iwl_xvt_skb_info* skb_info = (void*)skb->cb;
struct iwl_tx_cmd* tx_cmd;
dev_cmd = iwl_xvt_init_tx_dev_cmd(xvt);
if (unlikely(!dev_cmd)) {
return NULL;
}
tx_cmd = (struct iwl_tx_cmd*)dev_cmd->payload;
tx_cmd->len = cpu_to_le16((uint16_t)skb->len);
tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE);
tx_cmd->sta_id = sta_id;
tx_cmd->rate_n_flags = cpu_to_le32(rate_flags);
tx_cmd->tx_flags = cpu_to_le32(flags);
/* the skb should already hold the data */
memcpy(tx_cmd->hdr, skb->data, sizeof(struct ieee80211_hdr));
/*
* Saving device command address itself in the
* control buffer, to be used when reclaiming
* the command.
*/
skb_info->dev_cmd = dev_cmd;
return dev_cmd;
}
static void iwl_xvt_set_seq_number(struct iwl_xvt* xvt, struct tx_meta_data* meta_tx,
struct sk_buff* skb, uint8_t frag_num) {
struct ieee80211_hdr* hdr = (struct ieee80211_hdr*)skb->data;
uint8_t *qc, tid;
if (!ieee80211_is_data_qos(hdr->frame_control) || is_multicast_ether_addr(hdr->addr1)) {
return;
}
qc = ieee80211_get_qos_ctl(hdr);
tid = *qc & IEEE80211_QOS_CTL_TID_MASK;
if (WARN_ON(tid >= IWL_MAX_TID_COUNT)) {
tid = IWL_MAX_TID_COUNT - 1;
}
/* frag_num is expected to be zero in case of no fragmentation */
hdr->seq_ctrl = cpu_to_le16(meta_tx->seq_num[tid] | (frag_num & IEEE80211_SCTL_FRAG));
if (!ieee80211_has_morefrags(hdr->frame_control)) {
meta_tx->seq_num[tid] += 0x10;
}
}
static int iwl_xvt_send_packet(struct iwl_xvt* xvt, struct iwl_tm_mod_tx_request* tx_req,
uint32_t* status, struct tx_meta_data* meta_tx) {
struct sk_buff* skb;
struct iwl_device_cmd* dev_cmd;
int time_remain, err = 0;
uint32_t flags = 0;
uint32_t rate_flags = tx_req->rate_flags;
if (xvt->fw_error) {
IWL_ERR(xvt, "FW Error while sending Tx\n");
*status = XVT_TX_DRIVER_ABORTED;
return -ENODEV;
}
skb = alloc_skb(tx_req->len, GFP_KERNEL);
if (!skb) {
*status = XVT_TX_DRIVER_ABORTED;
return -ENOMEM;
}
memcpy(skb_put(skb, tx_req->len), tx_req->data, tx_req->len);
iwl_xvt_set_seq_number(xvt, meta_tx, skb, 0);
flags = tx_req->no_ack ? 0 : TX_CMD_FLG_ACK;
if (iwl_xvt_is_unified_fw(xvt)) {
flags |= IWL_TX_FLAGS_CMD_RATE;
if (xvt->trans->cfg->device_family >= IWL_DEVICE_FAMILY_22560) {
dev_cmd = iwl_xvt_set_tx_params_gen3(xvt, skb, rate_flags, flags);
} else {
dev_cmd = iwl_xvt_set_tx_params_gen2(xvt, skb, rate_flags, flags);
}
} else {
dev_cmd = iwl_xvt_set_mod_tx_params(xvt, skb, tx_req->sta_id, tx_req->rate_flags, flags);
}
if (!dev_cmd) {
kfree_skb(skb);
*status = XVT_TX_DRIVER_ABORTED;
return -ENOMEM;
}
if (tx_req->trigger_led) {
iwl_xvt_led_enable(xvt);
}
/* wait until the tx queue isn't full */
time_remain = wait_event_interruptible_timeout(meta_tx->mod_tx_wq, !meta_tx->txq_full, HZ);
if (time_remain <= 0) {
/* This should really not happen */
WARN_ON_ONCE(meta_tx->txq_full);
IWL_ERR(xvt, "Error while sending Tx\n");
*status = XVT_TX_DRIVER_QUEUE_FULL;
err = -EIO;
goto err;
}
if (xvt->fw_error) {
WARN_ON_ONCE(meta_tx->txq_full);
IWL_ERR(xvt, "FW Error while sending Tx\n");
*status = XVT_TX_DRIVER_ABORTED;
err = -ENODEV;
goto err;
}
/* Assume we have one Txing thread only: the queue is not full
* any more - nobody could fill it up in the meantime since we
* were blocked.
*/
local_bh_disable();
err = iwl_trans_tx(xvt->trans, skb, dev_cmd, meta_tx->queue);
local_bh_enable();
if (err) {
IWL_ERR(xvt, "Tx command failed (error %d)\n", err);
*status = XVT_TX_DRIVER_ABORTED;
goto err;
}
if (tx_req->trigger_led) {
iwl_xvt_led_disable(xvt);
}
return err;
err:
iwl_trans_free_tx_cmd(xvt->trans, dev_cmd);
kfree_skb(skb);
return err;
}
static struct iwl_device_cmd* iwl_xvt_set_tx_params(struct iwl_xvt* xvt, struct sk_buff* skb,
struct iwl_xvt_tx_start* tx_start,
uint8_t packet_index) {
struct iwl_device_cmd* dev_cmd;
struct iwl_xvt_skb_info* skb_info = (void*)skb->cb;
struct iwl_tx_cmd* tx_cmd;
/* the skb should already hold the data */
struct ieee80211_hdr* hdr = (struct ieee80211_hdr*)skb->data;
uint32_t header_length = ieee80211_hdrlen(hdr->frame_control);
dev_cmd = iwl_xvt_init_tx_dev_cmd(xvt);
if (unlikely(!dev_cmd)) {
return NULL;
}
tx_cmd = (struct iwl_tx_cmd*)dev_cmd->payload;
/* let the fw manage the seq number for non-qos/multicast */
if (!ieee80211_is_data_qos(hdr->frame_control) || is_multicast_ether_addr(hdr->addr1)) {
tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_SEQ_CTL);
}
tx_cmd->len = cpu_to_le16((uint16_t)skb->len);
tx_cmd->offload_assist |= cpu_to_le16(iwl_xvt_get_offload_assist(hdr));
tx_cmd->tx_flags |= cpu_to_le32(tx_start->tx_data.tx_flags);
if (ieee80211_has_morefrags(hdr->frame_control)) {
tx_cmd->tx_flags |= cpu_to_le32(TX_CMD_FLG_MORE_FRAG);
}
tx_cmd->rate_n_flags = cpu_to_le32(tx_start->tx_data.rate_flags);
tx_cmd->sta_id = tx_start->frames_data[packet_index].sta_id;
tx_cmd->sec_ctl = tx_start->frames_data[packet_index].sec_ctl;
tx_cmd->initial_rate_index = tx_start->tx_data.initial_rate_index;
tx_cmd->life_time = cpu_to_le32(TX_CMD_LIFE_TIME_INFINITE);
tx_cmd->rts_retry_limit = tx_start->tx_data.rts_retry_limit;
tx_cmd->data_retry_limit = tx_start->tx_data.data_retry_limit;
tx_cmd->tid_tspec = tx_start->frames_data[packet_index].tid_tspec;
memcpy(tx_cmd->key, tx_start->frames_data[packet_index].key, sizeof(tx_cmd->key));
memcpy(tx_cmd->hdr, hdr, header_length);
/*
* Saving device command address itself in the control buffer,
* to be used when reclaiming the command.
*/
skb_info->dev_cmd = dev_cmd;
return dev_cmd;
}
static struct sk_buff* iwl_xvt_set_skb(struct iwl_xvt* xvt, struct ieee80211_hdr* hdr,
struct tx_payload* payload) {
struct sk_buff* skb;
uint32_t header_size = ieee80211_hdrlen(hdr->frame_control);
uint32_t payload_length = payload->length;
uint32_t packet_length = payload_length + header_size;
skb = alloc_skb(packet_length, GFP_KERNEL);
if (!skb) {
return NULL;
}
/* copy MAC header into skb */
memcpy(skb_put(skb, header_size), hdr, header_size);
/* copy frame payload into skb */
memcpy(skb_put(skb, payload_length), payload, payload_length);
return skb;
}
static struct sk_buff* iwl_xvt_create_fragment_skb(struct iwl_xvt* xvt, struct ieee80211_hdr* hdr,
struct tx_payload* payload,
uint32_t fragment_size, uint8_t frag_num) {
struct sk_buff* skb;
const __le16 morefrags = cpu_to_le16(IEEE80211_FCTL_MOREFRAGS);
uint32_t header_size = ieee80211_hdrlen(hdr->frame_control);
uint32_t skb_size, offset, payload_remain, payload_chunck_size;
if (WARN(fragment_size <= header_size || !ieee80211_is_data_qos(hdr->frame_control),
"can't fragment, fragment_size small big or not qos data")) {
return NULL;
}
payload_chunck_size = fragment_size - header_size;
offset = payload_chunck_size * frag_num;
if (WARN(offset >= payload->length, "invalid fragment number %d\n", frag_num)) {
return NULL;
}
payload_remain = payload->length - offset;
if (fragment_size < payload_remain + header_size) {
skb_size = fragment_size;
hdr->frame_control |= morefrags;
} else {
skb_size = payload_remain + header_size;
hdr->frame_control &= ~morefrags;
payload_chunck_size = payload_remain;
}
skb = alloc_skb(skb_size, GFP_KERNEL);
if (!skb) {
return NULL;
}
/* copy MAC header into skb */
memcpy(skb_put(skb, header_size), hdr, header_size);
/* copy frame payload into skb */
memcpy(skb_put(skb, payload_chunck_size), &payload->payload[offset], payload_chunck_size);
return skb;
}
static struct sk_buff* iwl_xvt_get_skb(struct iwl_xvt* xvt, struct ieee80211_hdr* hdr,
struct tx_payload* payload, uint32_t fragment_size,
uint8_t frag_num) {
if (fragment_size == 0) { /* no framgmentation */
return iwl_xvt_set_skb(xvt, hdr, payload);
}
return iwl_xvt_create_fragment_skb(xvt, hdr, payload, fragment_size, frag_num);
}
static int iwl_xvt_transmit_packet(struct iwl_xvt* xvt, struct sk_buff* skb,
struct iwl_xvt_tx_start* tx_start, uint8_t packet_index,
uint8_t frag_num, uint32_t* status) {
struct iwl_device_cmd* dev_cmd;
int time_remain, err = 0;
uint8_t queue = tx_start->frames_data[packet_index].queue;
struct tx_queue_data* queue_data = &xvt->queue_data[queue];
uint32_t rate_flags = tx_start->tx_data.rate_flags;
uint32_t tx_flags = tx_start->tx_data.tx_flags;
/* set tx number */
iwl_xvt_set_seq_number(xvt, &xvt->tx_meta_data[XVT_LMAC_0_ID], skb, frag_num);
if (iwl_xvt_is_unified_fw(xvt)) {
if (xvt->trans->cfg->device_family >= IWL_DEVICE_FAMILY_22560) {
dev_cmd = iwl_xvt_set_tx_params_gen3(xvt, skb, rate_flags, tx_flags);
} else {
dev_cmd = iwl_xvt_set_tx_params_gen2(xvt, skb, rate_flags, tx_flags);
}
} else {
dev_cmd = iwl_xvt_set_tx_params(xvt, skb, tx_start, packet_index);
}
if (!dev_cmd) {
kfree_skb(skb);
*status = XVT_TX_DRIVER_ABORTED;
return -ENOMEM;
}
/* wait until the tx queue isn't full */
time_remain = wait_event_interruptible_timeout(queue_data->tx_wq, !queue_data->txq_full, HZ);
if (time_remain <= 0) {
/* This should really not happen */
WARN_ON_ONCE(queue_data->txq_full);
IWL_ERR(xvt, "Error while sending Tx\n");
*status = XVT_TX_DRIVER_QUEUE_FULL;
err = -EIO;
goto on_err;
}
if (xvt->fw_error) {
WARN_ON_ONCE(queue_data->txq_full);
IWL_ERR(xvt, "FW Error while sending packet\n");
*status = XVT_TX_DRIVER_ABORTED;
err = -ENODEV;
goto on_err;
}
/* Assume we have one Txing thread only: the queue is not full
* any more - nobody could fill it up in the meantime since we
* were blocked.
*/
local_bh_disable();
err = iwl_trans_tx(xvt->trans, skb, dev_cmd, queue);
local_bh_enable();
if (err) {
IWL_ERR(xvt, "Tx command failed (error %d)\n", err);
*status = XVT_TX_DRIVER_ABORTED;
goto on_err;
}
return 0;
on_err:
iwl_trans_free_tx_cmd(xvt->trans, dev_cmd);
kfree_skb(skb);
return err;
}
static int iwl_xvt_send_tx_done_notif(struct iwl_xvt* xvt, uint32_t status) {
struct iwl_xvt_tx_done* done_notif;
uint32_t i, j, done_notif_size, num_of_queues = 0;
int err;
for (i = 1; i < IWL_MAX_HW_QUEUES; i++) {
if (xvt->queue_data[i].allocated_queue) {
num_of_queues++;
}
}
done_notif_size = sizeof(*done_notif) + num_of_queues * sizeof(struct iwl_xvt_post_tx_data);
done_notif = kzalloc(done_notif_size, GFP_KERNEL);
if (!done_notif) {
return -ENOMEM;
}
done_notif->status = status;
done_notif->num_of_queues = num_of_queues;
for (i = 1, j = 0; i <= num_of_queues; i++) {
if (!xvt->queue_data[i].allocated_queue) {
continue;
}
done_notif->tx_data[j].num_of_packets = xvt->queue_data[i].tx_counter;
done_notif->tx_data[j].queue = i;
j++;
}
err = iwl_xvt_user_send_notif(xvt, IWL_XVT_CMD_ENHANCED_TX_DONE, (void*)done_notif,
done_notif_size, GFP_ATOMIC);
if (err) {
IWL_ERR(xvt, "Error %d sending tx_done notification\n", err);
kfree(done_notif);
return err;
}
return 0;
}
static int iwl_xvt_start_tx_handler(void* data) {
struct iwl_xvt_enhanced_tx_data* task_data = data;
struct iwl_xvt_tx_start* tx_start = &task_data->tx_start_data;
struct iwl_xvt* xvt = task_data->xvt;
uint8_t num_of_frames;
uint32_t status, packets_in_cycle = 0;
int time_remain, err = 0, sent_packets = 0;
uint32_t num_of_cycles = tx_start->num_of_cycles;
uint64_t i, num_of_iterations;
/* reset tx parameters */
xvt->num_of_tx_resp = 0;
xvt->send_tx_resp = tx_start->send_tx_resp;
status = 0;
for (i = 0; i < IWL_MAX_HW_QUEUES; i++) {
xvt->queue_data[i].tx_counter = 0;
}
num_of_frames = tx_start->num_of_different_frames;
for (i = 0; i < num_of_frames; i++) {
packets_in_cycle += tx_start->frames_data[i].times;
}
if (WARN(packets_in_cycle == 0, "invalid packets amount to send")) {
return -EINVAL;
}
if (num_of_cycles == IWL_XVT_TX_MODULATED_INFINITE) {
num_of_cycles = XVT_MAX_TX_COUNT / packets_in_cycle;
}
xvt->expected_tx_amount = packets_in_cycle * num_of_cycles;
num_of_iterations = num_of_cycles * num_of_frames;
for (i = 0; (i < num_of_iterations) && !kthread_should_stop(); i++) {
uint16_t j, times;
uint8_t frame_index, payload_idx, frag_idx, frag_num;
struct ieee80211_hdr* hdr;
struct sk_buff* skb;
uint8_t frag_size = tx_start->tx_data.fragment_size;
struct tx_payload* payload;
uint8_t frag_array_size = ARRAY_SIZE(tx_start->tx_data.frag_num);
frame_index = i % num_of_frames;
payload_idx = tx_start->frames_data[frame_index].payload_index;
payload = xvt->payloads[payload_idx];
hdr = (struct ieee80211_hdr*)tx_start->frames_data[frame_index].header;
times = tx_start->frames_data[frame_index].times;
for (j = 0; j < times; j++) {
if (xvt->fw_error) {
IWL_ERR(xvt, "FW Error during TX\n");
status = XVT_TX_DRIVER_ABORTED;
err = -ENODEV;
goto on_exit;
}
frag_idx = 0;
while (frag_idx < frag_array_size) {
frag_num = tx_start->tx_data.frag_num[frag_idx];
if (frag_num == XVT_STOP_TX || (frag_size == 0 && frag_idx > 0)) {
break;
}
skb = iwl_xvt_get_skb(xvt, hdr, payload, frag_size, frag_num);
if (!skb) {
IWL_ERR(xvt, "skb is NULL\n");
status = XVT_TX_DRIVER_ABORTED;
err = -ENOMEM;
goto on_exit;
}
err = iwl_xvt_transmit_packet(xvt, skb, tx_start, frame_index, frag_num, &status);
sent_packets++;
if (err) {
IWL_ERR(xvt, "stop due to err %d\n", err);
goto on_exit;
}
++frag_idx;
}
}
}
time_remain = wait_event_interruptible_timeout(
xvt->tx_done_wq, xvt->num_of_tx_resp == sent_packets, 5 * HZ * CPTCFG_IWL_TIMEOUT_FACTOR);
if (time_remain <= 0) {
IWL_ERR(xvt, "Not all Tx messages were sent\n");
status = XVT_TX_DRIVER_TIMEOUT;
}
on_exit:
err = iwl_xvt_send_tx_done_notif(xvt, status);
xvt->is_enhanced_tx = false;
kfree(data);
for (i = 0; i < IWL_XVT_MAX_PAYLOADS_AMOUNT; i++) {
kfree(xvt->payloads[i]);
xvt->payloads[i] = NULL;
}
do_exit(err);
}
static int iwl_xvt_modulated_tx_handler(void* data) {
uint64_t tx_count, max_tx;
int time_remain, num_of_packets, err = 0;
struct iwl_xvt* xvt;
struct iwl_xvt_tx_mod_done* done_notif;
uint32_t status = XVT_TX_DRIVER_SUCCESSFUL;
struct iwl_xvt_tx_mod_task_data* task_data = (struct iwl_xvt_tx_mod_task_data*)data;
struct tx_meta_data* xvt_tx;
xvt = task_data->xvt;
xvt_tx = &xvt->tx_meta_data[task_data->lmac_id];
xvt_tx->tx_task_operating = true;
num_of_packets = task_data->tx_req.times;
max_tx = (num_of_packets == IWL_XVT_TX_MODULATED_INFINITE) ? XVT_MAX_TX_COUNT : num_of_packets;
xvt_tx->tot_tx = num_of_packets;
xvt_tx->tx_counter = 0;
for (tx_count = 0; (tx_count < max_tx) && (!kthread_should_stop()); tx_count++) {
err = iwl_xvt_send_packet(xvt, &task_data->tx_req, &status, xvt_tx);
if (err) {
IWL_ERR(xvt, "stop send packets due to err %d\n", err);
break;
}
}
if (!err) {
time_remain = wait_event_interruptible_timeout(xvt_tx->mod_tx_done_wq,
xvt_tx->tx_counter == tx_count, 5 * HZ);
if (time_remain <= 0) {
IWL_ERR(xvt, "Not all Tx messages were sent\n");
xvt_tx->tx_task_operating = false;
status = XVT_TX_DRIVER_TIMEOUT;
}
}
done_notif = kmalloc(sizeof(*done_notif), GFP_KERNEL);
if (!done_notif) {
xvt_tx->tx_task_operating = false;
kfree(data);
return -ENOMEM;
}
done_notif->num_of_packets = xvt_tx->tx_counter;
done_notif->status = status;
done_notif->lmac_id = task_data->lmac_id;
err = iwl_xvt_user_send_notif(xvt, IWL_XVT_CMD_SEND_MOD_TX_DONE, (void*)done_notif,
sizeof(*done_notif), GFP_ATOMIC);
if (err) {
IWL_ERR(xvt, "Error %d sending tx_done notification\n", err);
kfree(done_notif);
}
xvt_tx->tx_task_operating = false;
kfree(data);
do_exit(err);
}
static int iwl_xvt_modulated_tx_infinite_stop(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
int err = 0;
uint32_t lmac_id = ((struct iwl_xvt_tx_mod_stop*)data_in->data)->lmac_id;
struct tx_meta_data* xvt_tx = &xvt->tx_meta_data[lmac_id];
if (xvt_tx->tx_mod_thread && xvt_tx->tx_task_operating) {
err = kthread_stop(xvt_tx->tx_mod_thread);
xvt_tx->tx_mod_thread = NULL;
}
return err;
}
static inline int map_sta_to_lmac(struct iwl_xvt* xvt, uint8_t sta_id) {
switch (sta_id) {
case XVT_LMAC_0_STA_ID:
return XVT_LMAC_0_ID;
case XVT_LMAC_1_STA_ID:
return XVT_LMAC_1_ID;
default:
IWL_ERR(xvt, "wrong sta id, can't match queue\n");
return -EINVAL;
}
}
static int iwl_xvt_tx_queue_cfg(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
struct iwl_xvt_tx_queue_cfg* input = (struct iwl_xvt_tx_queue_cfg*)data_in->data;
uint8_t sta_id = input->sta_id;
int lmac_id = map_sta_to_lmac(xvt, sta_id);
if (lmac_id < 0) {
return lmac_id;
}
switch (input->operation) {
case TX_QUEUE_CFG_ADD:
return iwl_xvt_allocate_tx_queue(xvt, sta_id, lmac_id);
case TX_QUEUE_CFG_REMOVE:
iwl_xvt_free_tx_queue(xvt, lmac_id);
break;
default:
IWL_ERR(xvt, "failed in tx config - wrong operation\n");
return -EINVAL;
}
return 0;
}
static int iwl_xvt_start_tx(struct iwl_xvt* xvt, struct iwl_xvt_driver_command_req* req) {
struct iwl_xvt_enhanced_tx_data* task_data;
if (WARN(xvt->is_enhanced_tx || xvt->tx_meta_data[XVT_LMAC_0_ID].tx_task_operating ||
xvt->tx_meta_data[XVT_LMAC_1_ID].tx_task_operating,
"TX is already in progress\n")) {
return -EINVAL;
}
xvt->is_enhanced_tx = true;
task_data = kzalloc(sizeof(*task_data), GFP_KERNEL);
if (!task_data) {
xvt->is_enhanced_tx = false;
return -ENOMEM;
}
task_data->xvt = xvt;
memcpy(&task_data->tx_start_data, req->input_data, sizeof(struct iwl_xvt_tx_start));
xvt->tx_task = kthread_run(iwl_xvt_start_tx_handler, task_data, "start enhanced tx command");
if (!xvt->tx_task) {
xvt->is_enhanced_tx = true;
kfree(task_data);
return -ENOMEM;
}
return 0;
}
static int iwl_xvt_stop_tx(struct iwl_xvt* xvt) {
int err = 0;
if (xvt->tx_task && xvt->is_enhanced_tx) {
err = kthread_stop(xvt->tx_task);
xvt->tx_task = NULL;
}
return err;
}
static int iwl_xvt_set_tx_payload(struct iwl_xvt* xvt, struct iwl_xvt_driver_command_req* req) {
struct iwl_xvt_set_tx_payload* input = (struct iwl_xvt_set_tx_payload*)req->input_data;
uint32_t size = sizeof(struct tx_payload) + input->length;
struct tx_payload* payload_struct;
if (WARN(input->index >= IWL_XVT_MAX_PAYLOADS_AMOUNT, "invalid payload index\n")) {
return -EINVAL;
}
/* First free payload in case index is already in use */
kfree(xvt->payloads[input->index]);
/* Allocate payload in xvt buffer */
xvt->payloads[input->index] = kzalloc(size, GFP_KERNEL);
if (!xvt->payloads[input->index]) {
return -ENOMEM;
}
payload_struct = xvt->payloads[input->index];
payload_struct->length = input->length;
memcpy(payload_struct->payload, input->payload, input->length);
return 0;
}
static int iwl_xvt_modulated_tx(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
uint32_t pkt_length = ((struct iwl_tm_mod_tx_request*)data_in->data)->len;
uint32_t req_length = sizeof(struct iwl_tm_mod_tx_request) + pkt_length;
uint32_t task_data_length = sizeof(struct iwl_xvt_tx_mod_task_data) + pkt_length;
struct tx_meta_data* xvt_tx = &xvt->tx_meta_data[XVT_LMAC_0_ID];
uint8_t sta_id;
int lmac_id;
struct iwl_xvt_tx_mod_task_data* task_data;
/* Verify this command was not called while tx is operating */
if (WARN_ON(xvt->is_enhanced_tx)) {
return -EINVAL;
}
task_data = kzalloc(task_data_length, GFP_KERNEL);
if (!task_data) {
return -ENOMEM;
}
/*
* no need to check whether tx already operating on lmac, since check
* is already done in the USC
*/
task_data->xvt = xvt;
memcpy(&task_data->tx_req, data_in->data, req_length);
if (iwl_xvt_is_unified_fw(xvt)) {
sta_id = task_data->tx_req.sta_id;
lmac_id = map_sta_to_lmac(xvt, sta_id);
if (lmac_id < 0) {
return lmac_id;
}
task_data->lmac_id = lmac_id;
xvt_tx = &xvt->tx_meta_data[lmac_id];
/* check if tx queue is allocated. if not - return */
if (xvt_tx->queue < 0) {
IWL_ERR(xvt, "failed in tx - queue is not allocated\n");
return -EIO;
}
}
xvt_tx->tx_mod_thread = kthread_run(iwl_xvt_modulated_tx_handler, task_data, "tx mod infinite");
if (!xvt_tx->tx_mod_thread) {
xvt_tx->tx_task_operating = false;
kfree(task_data);
return -ENOMEM;
}
return 0;
}
static int iwl_xvt_rx_hdrs_mode(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
struct iwl_xvt_rx_hdrs_mode_request* rx_hdr = data_in->data;
if (data_in->len < sizeof(struct iwl_xvt_rx_hdrs_mode_request)) {
return -EINVAL;
}
if (rx_hdr->mode) {
xvt->rx_hdr_enabled = true;
} else {
xvt->rx_hdr_enabled = false;
}
return 0;
}
static int iwl_xvt_apmg_pd_mode(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
struct iwl_xvt_apmg_pd_mode_request* apmg_pd = data_in->data;
if (apmg_pd->mode) {
xvt->apmg_pd_en = true;
} else {
xvt->apmg_pd_en = false;
}
return 0;
}
static int iwl_xvt_allocate_dma(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_xvt_alloc_dma* dma_req = data_in->data;
struct iwl_xvt_alloc_dma* dma_res;
if (data_in->len < sizeof(struct iwl_xvt_alloc_dma)) {
return -EINVAL;
}
if (xvt->dma_cpu_addr) {
IWL_ERR(xvt, "XVT DMA already allocated\n");
return -EBUSY;
}
xvt->dma_cpu_addr =
dma_alloc_coherent(xvt->trans->dev, dma_req->size, &(xvt->dma_addr), GFP_KERNEL);
if (!xvt->dma_cpu_addr) {
return false;
}
dma_res = kmalloc(sizeof(*dma_res), GFP_KERNEL);
if (!dma_res) {
dma_free_coherent(xvt->trans->dev, dma_req->size, xvt->dma_cpu_addr, xvt->dma_addr);
xvt->dma_cpu_addr = NULL;
xvt->dma_addr = 0;
return -ENOMEM;
}
dma_res->size = dma_req->size;
/* Casting to avoid compilation warnings when DMA address is 32bit */
dma_res->addr = (uint64_t)xvt->dma_addr;
data_out->data = dma_res;
data_out->len = sizeof(struct iwl_xvt_alloc_dma);
xvt->dma_buffer_size = dma_req->size;
return 0;
}
static int iwl_xvt_get_dma(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_xvt_get_dma* get_dma_resp;
uint32_t resp_size;
if (!xvt->dma_cpu_addr) {
return -ENOMEM;
}
resp_size = sizeof(*get_dma_resp) + xvt->dma_buffer_size;
get_dma_resp = kmalloc(resp_size, GFP_KERNEL);
if (!get_dma_resp) {
return -ENOMEM;
}
get_dma_resp->size = xvt->dma_buffer_size;
memcpy(get_dma_resp->data, xvt->dma_cpu_addr, xvt->dma_buffer_size);
data_out->data = get_dma_resp;
data_out->len = resp_size;
return 0;
}
static int iwl_xvt_free_dma(struct iwl_xvt* xvt, struct iwl_tm_data* data_in) {
if (!xvt->dma_cpu_addr) {
IWL_ERR(xvt, "XVT DMA was not allocated\n");
return 0;
}
dma_free_coherent(xvt->trans->dev, xvt->dma_buffer_size, xvt->dma_cpu_addr, xvt->dma_addr);
xvt->dma_cpu_addr = NULL;
xvt->dma_addr = 0;
xvt->dma_buffer_size = 0;
return 0;
}
static int iwl_xvt_get_chip_id(struct iwl_xvt* xvt, struct iwl_tm_data* data_out) {
struct iwl_xvt_chip_id* chip_id;
chip_id = kmalloc(sizeof(struct iwl_xvt_chip_id), GFP_KERNEL);
if (!chip_id) {
return -ENOMEM;
}
chip_id->registers[0] = ioread32((void __force __iomem*)XVT_SCU_SNUM1);
chip_id->registers[1] = ioread32((void __force __iomem*)XVT_SCU_SNUM2);
chip_id->registers[2] = ioread32((void __force __iomem*)XVT_SCU_SNUM3);
data_out->data = chip_id;
data_out->len = sizeof(struct iwl_xvt_chip_id);
return 0;
}
static int iwl_xvt_get_mac_addr_info(struct iwl_xvt* xvt, struct iwl_tm_data* data_out) {
struct iwl_xvt_mac_addr_info* mac_addr_info;
uint32_t mac_addr0, mac_addr1;
__u8 temp_mac_addr[ETH_ALEN];
const uint8_t* hw_addr;
mac_addr_info = kzalloc(sizeof(*mac_addr_info), GFP_KERNEL);
if (!mac_addr_info) {
return -ENOMEM;
}
if (xvt->cfg->nvm_type != IWL_NVM_EXT) {
memcpy(mac_addr_info->mac_addr, xvt->nvm_hw_addr, sizeof(mac_addr_info->mac_addr));
} else {
/* MAC address in family 8000 */
if (xvt->is_nvm_mac_override) {
memcpy(mac_addr_info->mac_addr, xvt->nvm_mac_addr, sizeof(mac_addr_info->mac_addr));
} else {
/* read the mac address from WFMP registers */
mac_addr0 = iwl_trans_read_prph(xvt->trans, WFMP_MAC_ADDR_0);
mac_addr1 = iwl_trans_read_prph(xvt->trans, WFMP_MAC_ADDR_1);
hw_addr = (const uint8_t*)&mac_addr0;
temp_mac_addr[0] = hw_addr[3];
temp_mac_addr[1] = hw_addr[2];
temp_mac_addr[2] = hw_addr[1];
temp_mac_addr[3] = hw_addr[0];
hw_addr = (const uint8_t*)&mac_addr1;
temp_mac_addr[4] = hw_addr[1];
temp_mac_addr[5] = hw_addr[0];
memcpy(mac_addr_info->mac_addr, temp_mac_addr, sizeof(mac_addr_info->mac_addr));
}
}
data_out->data = mac_addr_info;
data_out->len = sizeof(*mac_addr_info);
return 0;
}
static int iwl_xvt_add_txq(struct iwl_xvt* xvt, struct iwl_scd_txq_cfg_cmd* cmd, uint16_t ssn,
uint16_t flags, int size) {
int queue_id = cmd->scd_queue, ret;
if (iwl_xvt_is_unified_fw(xvt)) {
/*TODO: add support for second lmac*/
queue_id = iwl_trans_txq_alloc(xvt->trans, cpu_to_le16(flags), cmd->sta_id, cmd->tid,
SCD_QUEUE_CFG, size, 0);
if (queue_id < 0) {
return queue_id;
}
} else {
iwl_trans_txq_enable_cfg(xvt->trans, queue_id, ssn, NULL, 0);
ret = iwl_xvt_send_cmd_pdu(xvt, SCD_QUEUE_CFG, 0, sizeof(*cmd), cmd);
if (ret) {
IWL_ERR(xvt, "Failed to config queue %d on FIFO %d\n", cmd->scd_queue, cmd->tx_fifo);
return ret;
}
}
xvt->queue_data[queue_id].allocated_queue = true;
init_waitqueue_head(&xvt->queue_data[queue_id].tx_wq);
return queue_id;
}
static int iwl_xvt_remove_txq(struct iwl_xvt* xvt, struct iwl_scd_txq_cfg_cmd* cmd) {
int ret = 0;
if (iwl_xvt_is_unified_fw(xvt)) {
iwl_trans_txq_free(xvt->trans, cmd->scd_queue);
} else {
iwl_trans_txq_disable(xvt->trans, cmd->scd_queue, false);
ret = iwl_xvt_send_cmd_pdu(xvt, SCD_QUEUE_CFG, 0, sizeof(*cmd), cmd);
}
if (WARN(ret, "failed to send SCD_QUEUE_CFG")) {
return ret;
}
xvt->queue_data[cmd->scd_queue].allocated_queue = false;
return 0;
}
static int iwl_xvt_config_txq(struct iwl_xvt* xvt, struct iwl_xvt_driver_command_req* req,
struct iwl_xvt_driver_command_resp* resp) {
struct iwl_xvt_txq_config* conf = (struct iwl_xvt_txq_config*)req->input_data;
struct iwl_xvt_txq_config_resp txq_resp;
int queue_id = conf->scd_queue, error;
struct iwl_scd_txq_cfg_cmd cmd = {
.sta_id = conf->sta_id,
.tid = conf->tid,
.scd_queue = conf->scd_queue,
.action = conf->action,
.aggregate = conf->aggregate,
.tx_fifo = conf->tx_fifo,
.window = conf->window,
.ssn = cpu_to_le16(conf->ssn),
};
if (req->max_out_length < sizeof(txq_resp)) {
return -ENOBUFS;
}
if (conf->action == TX_QUEUE_CFG_REMOVE) {
error = iwl_xvt_remove_txq(xvt, &cmd);
if (WARN(error, "failed to remove queue")) {
return error;
}
} else {
queue_id = iwl_xvt_add_txq(xvt, &cmd, conf->ssn, conf->flags, conf->queue_size);
if (queue_id < 0) {
return queue_id;
}
}
txq_resp.scd_queue = queue_id;
txq_resp.sta_id = conf->sta_id;
txq_resp.tid = conf->tid;
memcpy(resp->resp_data, &txq_resp, sizeof(txq_resp));
resp->length = sizeof(txq_resp);
return 0;
}
static int iwl_xvt_get_rx_agg_stats_cmd(struct iwl_xvt* xvt, struct iwl_xvt_driver_command_req* req,
struct iwl_xvt_driver_command_resp* resp) {
struct iwl_xvt_get_rx_agg_stats* params = (void*)req->input_data;
struct iwl_xvt_get_rx_agg_stats_resp* stats_resp = (void*)resp->resp_data;
struct iwl_xvt_reorder_buffer* buffer;
int i;
IWL_DEBUG_INFO(xvt, "get rx agg stats: sta_id=%d, tid=%d\n", params->sta_id, params->tid);
if (req->max_out_length < sizeof(stats_resp)) {
return -ENOBUFS;
}
for (i = 0; i < ARRAY_SIZE(xvt->reorder_bufs); i++) {
buffer = &xvt->reorder_bufs[i];
if (buffer->sta_id != params->sta_id || buffer->tid != params->tid) {
continue;
}
spin_lock_bh(&buffer->lock);
stats_resp->dropped = buffer->stats.dropped;
stats_resp->released = buffer->stats.released;
stats_resp->skipped = buffer->stats.skipped;
stats_resp->reordered = buffer->stats.reordered;
/* clear statistics */
memset(&buffer->stats, 0, sizeof(buffer->stats));
spin_unlock_bh(&buffer->lock);
break;
}
if (i == ARRAY_SIZE(xvt->reorder_bufs)) {
return -ENOENT;
}
resp->length = sizeof(*stats_resp);
return 0;
}
static void iwl_xvt_config_rx_mpdu(struct iwl_xvt* xvt, struct iwl_xvt_driver_command_req* req)
{
xvt->send_rx_mpdu = ((struct iwl_xvt_config_rx_mpdu_req*)req->input_data)->enable;
}
static int iwl_xvt_echo_notif(struct iwl_xvt* xvt) {
return iwl_xvt_user_send_notif(xvt, IWL_XVT_CMD_ECHO_NOTIF, NULL, 0, GFP_KERNEL);
}
static int iwl_xvt_handle_driver_cmd(struct iwl_xvt* xvt, struct iwl_tm_data* data_in,
struct iwl_tm_data* data_out) {
struct iwl_xvt_driver_command_req* req = data_in->data;
struct iwl_xvt_driver_command_resp* resp = NULL;
__u32 cmd_id = req->command_id;
int err = 0;
IWL_DEBUG_INFO(xvt, "handle driver command 0x%X\n", cmd_id);
if (req->max_out_length > 0) {
resp = kzalloc(sizeof(*resp) + req->max_out_length, GFP_KERNEL);
if (!resp) {
return -ENOMEM;
}
}
/* resp->length and resp->resp_data should be set in command handler */
switch (cmd_id) {
case IWL_DRV_CMD_CONFIG_TX_QUEUE:
err = iwl_xvt_config_txq(xvt, req, resp);
break;
case IWL_DRV_CMD_SET_TX_PAYLOAD:
err = iwl_xvt_set_tx_payload(xvt, req);
break;
case IWL_DRV_CMD_TX_START:
err = iwl_xvt_start_tx(xvt, req);
break;
case IWL_DRV_CMD_TX_STOP:
err = iwl_xvt_stop_tx(xvt);
break;
case IWL_DRV_CMD_GET_RX_AGG_STATS:
err = iwl_xvt_get_rx_agg_stats_cmd(xvt, req, resp);
break;
case IWL_DRV_CMD_CONFIG_RX_MPDU:
iwl_xvt_config_rx_mpdu(xvt, req);
break;
case IWL_DRV_CMD_ECHO_NOTIF:
err = iwl_xvt_echo_notif(xvt);
break;
default:
IWL_ERR(xvt, "no command handler found for cmd_id[%u]\n", cmd_id);
err = -EOPNOTSUPP;
}
if (err) {
goto out_free;
}
if (req->max_out_length > 0) {
if (WARN_ONCE(resp->length == 0, "response was not set correctly\n")) {
err = -ENODATA;
goto out_free;
}
resp->command_id = cmd_id;
data_out->len = resp->length + sizeof(struct iwl_xvt_driver_command_resp);
data_out->data = resp;
return err;
}
out_free:
kfree(resp);
return err;
}
int iwl_xvt_user_cmd_execute(struct iwl_testmode* testmode, uint32_t cmd,
struct iwl_tm_data* data_in, struct iwl_tm_data* data_out,
bool* supported_cmd) {
struct iwl_xvt* xvt = testmode->op_mode;
int ret = 0;
*supported_cmd = true;
if (WARN_ON_ONCE(!xvt || !data_in)) {
return -EINVAL;
}
IWL_DEBUG_INFO(xvt, "%s cmd=0x%X\n", __func__, cmd);
mutex_lock(&xvt->mutex);
switch (cmd) {
/* Testmode custom cases */
case IWL_TM_USER_CMD_GET_DEVICE_INFO:
ret = iwl_xvt_get_dev_info(xvt, data_in, data_out);
break;
case IWL_TM_USER_CMD_SV_IO_TOGGLE:
ret = iwl_xvt_sdio_io_toggle(xvt, data_in, data_out);
break;
/* xVT cases */
case IWL_XVT_CMD_START:
ret = iwl_xvt_start_op_mode(xvt);
break;
case IWL_XVT_CMD_STOP:
iwl_xvt_stop_op_mode(xvt);
break;
case IWL_XVT_CMD_CONTINUE_INIT:
ret = iwl_xvt_continue_init(xvt);
break;
case IWL_XVT_CMD_GET_PHY_DB_ENTRY:
ret = iwl_xvt_get_phy_db(xvt, data_in, data_out);
break;
case IWL_XVT_CMD_SET_CONFIG:
ret = iwl_xvt_set_sw_config(xvt, data_in);
break;
case IWL_XVT_CMD_GET_CONFIG:
ret = iwl_xvt_get_sw_config(xvt, data_in, data_out);
break;
case IWL_XVT_CMD_MOD_TX:
ret = iwl_xvt_modulated_tx(xvt, data_in);
break;
case IWL_XVT_CMD_RX_HDRS_MODE:
ret = iwl_xvt_rx_hdrs_mode(xvt, data_in);
break;
case IWL_XVT_CMD_APMG_PD_MODE:
ret = iwl_xvt_apmg_pd_mode(xvt, data_in);
break;
case IWL_XVT_CMD_ALLOC_DMA:
ret = iwl_xvt_allocate_dma(xvt, data_in, data_out);
break;
case IWL_XVT_CMD_GET_DMA:
ret = iwl_xvt_get_dma(xvt, data_in, data_out);
break;
case IWL_XVT_CMD_FREE_DMA:
ret = iwl_xvt_free_dma(xvt, data_in);
break;
case IWL_XVT_CMD_GET_CHIP_ID:
ret = iwl_xvt_get_chip_id(xvt, data_out);
break;
case IWL_XVT_CMD_GET_MAC_ADDR_INFO:
ret = iwl_xvt_get_mac_addr_info(xvt, data_out);
break;
case IWL_XVT_CMD_MOD_TX_STOP:
ret = iwl_xvt_modulated_tx_infinite_stop(xvt, data_in);
break;
case IWL_XVT_CMD_TX_QUEUE_CFG:
ret = iwl_xvt_tx_queue_cfg(xvt, data_in);
break;
case IWL_XVT_CMD_DRIVER_CMD:
ret = iwl_xvt_handle_driver_cmd(xvt, data_in, data_out);
break;
default:
*supported_cmd = false;
ret = -EOPNOTSUPP;
IWL_DEBUG_INFO(xvt, "%s (cmd=0x%X) Not supported by xVT\n", __func__, cmd);
break;
}
mutex_unlock(&xvt->mutex);
if (ret && *supported_cmd) {
IWL_ERR(xvt, "%s (cmd=0x%X) ret=%d\n", __func__, cmd, ret);
} else {
IWL_DEBUG_INFO(xvt, "%s (cmd=0x%X) ended Ok\n", __func__, cmd);
}
return ret;
}