summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/iwlwifi/dvm/testmode.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/iwlwifi/dvm/testmode.c')
-rw-r--r--drivers/net/wireless/iwlwifi/dvm/testmode.c471
1 files changed, 471 insertions, 0 deletions
diff --git a/drivers/net/wireless/iwlwifi/dvm/testmode.c b/drivers/net/wireless/iwlwifi/dvm/testmode.c
new file mode 100644
index 000000000000..57b918ce3b5f
--- /dev/null
+++ b/drivers/net/wireless/iwlwifi/dvm/testmode.c
@@ -0,0 +1,471 @@
+/******************************************************************************
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2010 - 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110,
+ * USA
+ *
+ * The full GNU General Public License is included in this distribution
+ * in the file called LICENSE.GPL.
+ *
+ * Contact Information:
+ * Intel Linux Wireless <ilw@linux.intel.com>
+ * Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2010 - 2012 Intel Corporation. All rights reserved.
+ * 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 <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <net/net_namespace.h>
+#include <linux/netdevice.h>
+#include <net/cfg80211.h>
+#include <net/mac80211.h>
+#include <net/netlink.h>
+
+#include "iwl-debug.h"
+#include "iwl-trans.h"
+#include "dev.h"
+#include "agn.h"
+#include "iwl-test.h"
+#include "iwl-testmode.h"
+
+static int iwl_testmode_send_cmd(struct iwl_op_mode *op_mode,
+ struct iwl_host_cmd *cmd)
+{
+ struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode);
+ return iwl_dvm_send_cmd(priv, cmd);
+}
+
+static bool iwl_testmode_valid_hw_addr(u32 addr)
+{
+ if (iwlagn_hw_valid_rtc_data_addr(addr))
+ return true;
+
+ if (IWLAGN_RTC_INST_LOWER_BOUND <= addr &&
+ addr < IWLAGN_RTC_INST_UPPER_BOUND)
+ return true;
+
+ return false;
+}
+
+static u32 iwl_testmode_get_fw_ver(struct iwl_op_mode *op_mode)
+{
+ struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode);
+ return priv->fw->ucode_ver;
+}
+
+static struct sk_buff*
+iwl_testmode_alloc_reply(struct iwl_op_mode *op_mode, int len)
+{
+ struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode);
+ return cfg80211_testmode_alloc_reply_skb(priv->hw->wiphy, len);
+}
+
+static int iwl_testmode_reply(struct iwl_op_mode *op_mode, struct sk_buff *skb)
+{
+ return cfg80211_testmode_reply(skb);
+}
+
+static struct sk_buff *iwl_testmode_alloc_event(struct iwl_op_mode *op_mode,
+ int len)
+{
+ struct iwl_priv *priv = IWL_OP_MODE_GET_DVM(op_mode);
+ return cfg80211_testmode_alloc_event_skb(priv->hw->wiphy, len,
+ GFP_ATOMIC);
+}
+
+static void iwl_testmode_event(struct iwl_op_mode *op_mode, struct sk_buff *skb)
+{
+ return cfg80211_testmode_event(skb, GFP_ATOMIC);
+}
+
+static struct iwl_test_ops tst_ops = {
+ .send_cmd = iwl_testmode_send_cmd,
+ .valid_hw_addr = iwl_testmode_valid_hw_addr,
+ .get_fw_ver = iwl_testmode_get_fw_ver,
+ .alloc_reply = iwl_testmode_alloc_reply,
+ .reply = iwl_testmode_reply,
+ .alloc_event = iwl_testmode_alloc_event,
+ .event = iwl_testmode_event,
+};
+
+void iwl_testmode_init(struct iwl_priv *priv)
+{
+ iwl_test_init(&priv->tst, priv->trans, &tst_ops);
+}
+
+void iwl_testmode_free(struct iwl_priv *priv)
+{
+ iwl_test_free(&priv->tst);
+}
+
+static int iwl_testmode_cfg_init_calib(struct iwl_priv *priv)
+{
+ struct iwl_notification_wait calib_wait;
+ static const u8 calib_complete[] = {
+ CALIBRATION_COMPLETE_NOTIFICATION
+ };
+ int ret;
+
+ iwl_init_notification_wait(&priv->notif_wait, &calib_wait,
+ calib_complete, ARRAY_SIZE(calib_complete),
+ NULL, NULL);
+ ret = iwl_init_alive_start(priv);
+ if (ret) {
+ IWL_ERR(priv, "Fail init calibration: %d\n", ret);
+ goto cfg_init_calib_error;
+ }
+
+ ret = iwl_wait_notification(&priv->notif_wait, &calib_wait, 2 * HZ);
+ if (ret)
+ IWL_ERR(priv, "Error detecting"
+ " CALIBRATION_COMPLETE_NOTIFICATION: %d\n", ret);
+ return ret;
+
+cfg_init_calib_error:
+ iwl_remove_notification(&priv->notif_wait, &calib_wait);
+ return ret;
+}
+
+/*
+ * This function handles the user application commands for driver.
+ *
+ * It retrieves command ID carried with IWL_TM_ATTR_COMMAND and calls to the
+ * handlers respectively.
+ *
+ * If it's an unknown commdn ID, -ENOSYS is replied; otherwise, the returned
+ * value of the actual command execution is replied to the user application.
+ *
+ * If there's any message responding to the user space, IWL_TM_ATTR_SYNC_RSP
+ * is used for carry the message while IWL_TM_ATTR_COMMAND must set to
+ * IWL_TM_CMD_DEV2APP_SYNC_RSP.
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @tb: gnl message fields from the user space
+ */
+static int iwl_testmode_driver(struct ieee80211_hw *hw, struct nlattr **tb)
+{
+ struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
+ struct iwl_trans *trans = priv->trans;
+ struct sk_buff *skb;
+ unsigned char *rsp_data_ptr = NULL;
+ int status = 0, rsp_data_len = 0;
+ u32 inst_size = 0, data_size = 0;
+ const struct fw_img *img;
+
+ switch (nla_get_u32(tb[IWL_TM_ATTR_COMMAND])) {
+ case IWL_TM_CMD_APP2DEV_GET_DEVICENAME:
+ rsp_data_ptr = (unsigned char *)priv->cfg->name;
+ rsp_data_len = strlen(priv->cfg->name);
+ skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy,
+ rsp_data_len + 20);
+ if (!skb) {
+ IWL_ERR(priv, "Memory allocation fail\n");
+ return -ENOMEM;
+ }
+ if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND,
+ IWL_TM_CMD_DEV2APP_SYNC_RSP) ||
+ nla_put(skb, IWL_TM_ATTR_SYNC_RSP,
+ rsp_data_len, rsp_data_ptr))
+ goto nla_put_failure;
+ status = cfg80211_testmode_reply(skb);
+ if (status < 0)
+ IWL_ERR(priv, "Error sending msg : %d\n", status);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_LOAD_INIT_FW:
+ status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_INIT);
+ if (status)
+ IWL_ERR(priv, "Error loading init ucode: %d\n", status);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB:
+ iwl_testmode_cfg_init_calib(priv);
+ priv->ucode_loaded = false;
+ iwl_trans_stop_device(trans);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW:
+ status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_REGULAR);
+ if (status) {
+ IWL_ERR(priv,
+ "Error loading runtime ucode: %d\n", status);
+ break;
+ }
+ status = iwl_alive_start(priv);
+ if (status)
+ IWL_ERR(priv,
+ "Error starting the device: %d\n", status);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW:
+ iwl_scan_cancel_timeout(priv, 200);
+ priv->ucode_loaded = false;
+ iwl_trans_stop_device(trans);
+ status = iwl_load_ucode_wait_alive(priv, IWL_UCODE_WOWLAN);
+ if (status) {
+ IWL_ERR(priv,
+ "Error loading WOWLAN ucode: %d\n", status);
+ break;
+ }
+ status = iwl_alive_start(priv);
+ if (status)
+ IWL_ERR(priv,
+ "Error starting the device: %d\n", status);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_GET_EEPROM:
+ if (priv->eeprom_blob) {
+ skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy,
+ priv->eeprom_blob_size + 20);
+ if (!skb) {
+ IWL_ERR(priv, "Memory allocation fail\n");
+ return -ENOMEM;
+ }
+ if (nla_put_u32(skb, IWL_TM_ATTR_COMMAND,
+ IWL_TM_CMD_DEV2APP_EEPROM_RSP) ||
+ nla_put(skb, IWL_TM_ATTR_EEPROM,
+ priv->eeprom_blob_size,
+ priv->eeprom_blob))
+ goto nla_put_failure;
+ status = cfg80211_testmode_reply(skb);
+ if (status < 0)
+ IWL_ERR(priv, "Error sending msg : %d\n",
+ status);
+ } else
+ return -ENODATA;
+ break;
+
+ case IWL_TM_CMD_APP2DEV_FIXRATE_REQ:
+ if (!tb[IWL_TM_ATTR_FIXRATE]) {
+ IWL_ERR(priv, "Missing fixrate setting\n");
+ return -ENOMSG;
+ }
+ priv->tm_fixed_rate = nla_get_u32(tb[IWL_TM_ATTR_FIXRATE]);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_GET_FW_INFO:
+ skb = cfg80211_testmode_alloc_reply_skb(hw->wiphy, 20 + 8);
+ if (!skb) {
+ IWL_ERR(priv, "Memory allocation fail\n");
+ return -ENOMEM;
+ }
+ if (!priv->ucode_loaded) {
+ IWL_ERR(priv, "No uCode has not been loaded\n");
+ return -EINVAL;
+ } else {
+ img = &priv->fw->img[priv->cur_ucode];
+ inst_size = img->sec[IWL_UCODE_SECTION_INST].len;
+ data_size = img->sec[IWL_UCODE_SECTION_DATA].len;
+ }
+ if (nla_put_u32(skb, IWL_TM_ATTR_FW_TYPE, priv->cur_ucode) ||
+ nla_put_u32(skb, IWL_TM_ATTR_FW_INST_SIZE, inst_size) ||
+ nla_put_u32(skb, IWL_TM_ATTR_FW_DATA_SIZE, data_size))
+ goto nla_put_failure;
+ status = cfg80211_testmode_reply(skb);
+ if (status < 0)
+ IWL_ERR(priv, "Error sending msg : %d\n", status);
+ break;
+
+ default:
+ IWL_ERR(priv, "Unknown testmode driver command ID\n");
+ return -ENOSYS;
+ }
+ return status;
+
+nla_put_failure:
+ kfree_skb(skb);
+ return -EMSGSIZE;
+}
+
+/*
+ * This function handles the user application switch ucode ownership.
+ *
+ * It retrieves the mandatory fields IWL_TM_ATTR_UCODE_OWNER and
+ * decide who the current owner of the uCode
+ *
+ * If the current owner is OWNERSHIP_TM, then the only host command
+ * can deliver to uCode is from testmode, all the other host commands
+ * will dropped.
+ *
+ * default driver is the owner of uCode in normal operational mode
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @tb: gnl message fields from the user space
+ */
+static int iwl_testmode_ownership(struct ieee80211_hw *hw, struct nlattr **tb)
+{
+ struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
+ u8 owner;
+
+ if (!tb[IWL_TM_ATTR_UCODE_OWNER]) {
+ IWL_ERR(priv, "Missing ucode owner\n");
+ return -ENOMSG;
+ }
+
+ owner = nla_get_u8(tb[IWL_TM_ATTR_UCODE_OWNER]);
+ if (owner == IWL_OWNERSHIP_DRIVER) {
+ priv->ucode_owner = owner;
+ iwl_test_enable_notifications(&priv->tst, false);
+ } else if (owner == IWL_OWNERSHIP_TM) {
+ priv->ucode_owner = owner;
+ iwl_test_enable_notifications(&priv->tst, true);
+ } else {
+ IWL_ERR(priv, "Invalid owner\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* The testmode gnl message handler that takes the gnl message from the
+ * user space and parses it per the policy iwl_testmode_gnl_msg_policy, then
+ * invoke the corresponding handlers.
+ *
+ * This function is invoked when there is user space application sending
+ * gnl message through the testmode tunnel NL80211_CMD_TESTMODE regulated
+ * by nl80211.
+ *
+ * It retrieves the mandatory field, IWL_TM_ATTR_COMMAND, before
+ * dispatching it to the corresponding handler.
+ *
+ * If IWL_TM_ATTR_COMMAND is missing, -ENOMSG is replied to user application;
+ * -ENOSYS is replied to the user application if the command is unknown;
+ * Otherwise, the command is dispatched to the respective handler.
+ *
+ * @hw: ieee80211_hw object that represents the device
+ * @data: pointer to user space message
+ * @len: length in byte of @data
+ */
+int iwlagn_mac_testmode_cmd(struct ieee80211_hw *hw, void *data, int len)
+{
+ struct nlattr *tb[IWL_TM_ATTR_MAX];
+ struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
+ int result;
+
+ result = iwl_test_parse(&priv->tst, tb, data, len);
+ if (result)
+ return result;
+
+ /* in case multiple accesses to the device happens */
+ mutex_lock(&priv->mutex);
+ switch (nla_get_u32(tb[IWL_TM_ATTR_COMMAND])) {
+ case IWL_TM_CMD_APP2DEV_UCODE:
+ case IWL_TM_CMD_APP2DEV_DIRECT_REG_READ32:
+ case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE32:
+ case IWL_TM_CMD_APP2DEV_DIRECT_REG_WRITE8:
+ case IWL_TM_CMD_APP2DEV_BEGIN_TRACE:
+ case IWL_TM_CMD_APP2DEV_END_TRACE:
+ case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_READ:
+ case IWL_TM_CMD_APP2DEV_NOTIFICATIONS:
+ case IWL_TM_CMD_APP2DEV_GET_FW_VERSION:
+ case IWL_TM_CMD_APP2DEV_GET_DEVICE_ID:
+ case IWL_TM_CMD_APP2DEV_INDIRECT_BUFFER_WRITE:
+ result = iwl_test_handle_cmd(&priv->tst, tb);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_GET_DEVICENAME:
+ case IWL_TM_CMD_APP2DEV_LOAD_INIT_FW:
+ case IWL_TM_CMD_APP2DEV_CFG_INIT_CALIB:
+ case IWL_TM_CMD_APP2DEV_LOAD_RUNTIME_FW:
+ case IWL_TM_CMD_APP2DEV_GET_EEPROM:
+ case IWL_TM_CMD_APP2DEV_FIXRATE_REQ:
+ case IWL_TM_CMD_APP2DEV_LOAD_WOWLAN_FW:
+ case IWL_TM_CMD_APP2DEV_GET_FW_INFO:
+ IWL_DEBUG_INFO(priv, "testmode cmd to driver\n");
+ result = iwl_testmode_driver(hw, tb);
+ break;
+
+ case IWL_TM_CMD_APP2DEV_OWNERSHIP:
+ IWL_DEBUG_INFO(priv, "testmode change uCode ownership\n");
+ result = iwl_testmode_ownership(hw, tb);
+ break;
+
+ default:
+ IWL_ERR(priv, "Unknown testmode command\n");
+ result = -ENOSYS;
+ break;
+ }
+ mutex_unlock(&priv->mutex);
+
+ if (result)
+ IWL_ERR(priv, "Test cmd failed result=%d\n", result);
+ return result;
+}
+
+int iwlagn_mac_testmode_dump(struct ieee80211_hw *hw, struct sk_buff *skb,
+ struct netlink_callback *cb,
+ void *data, int len)
+{
+ struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
+ int result;
+ u32 cmd;
+
+ if (cb->args[3]) {
+ /* offset by 1 since commands start at 0 */
+ cmd = cb->args[3] - 1;
+ } else {
+ struct nlattr *tb[IWL_TM_ATTR_MAX];
+
+ result = iwl_test_parse(&priv->tst, tb, data, len);
+ if (result)
+ return result;
+
+ cmd = nla_get_u32(tb[IWL_TM_ATTR_COMMAND]);
+ cb->args[3] = cmd + 1;
+ }
+
+ /* in case multiple accesses to the device happens */
+ mutex_lock(&priv->mutex);
+ result = iwl_test_dump(&priv->tst, cmd, skb, cb);
+ mutex_unlock(&priv->mutex);
+ return result;
+}