diff options
Diffstat (limited to 'drivers/net/wireless/iwlwifi/mvm/utils.c')
| -rw-r--r-- | drivers/net/wireless/iwlwifi/mvm/utils.c | 299 |
1 files changed, 285 insertions, 14 deletions
diff --git a/drivers/net/wireless/iwlwifi/mvm/utils.c b/drivers/net/wireless/iwlwifi/mvm/utils.c index 687b34e387a..aa9fc77e841 100644 --- a/drivers/net/wireless/iwlwifi/mvm/utils.c +++ b/drivers/net/wireless/iwlwifi/mvm/utils.c @@ -5,7 +5,7 @@ * * GPL LICENSE SUMMARY * - * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * Copyright(c) 2012 - 2014 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 @@ -30,7 +30,7 @@ * * BSD LICENSE * - * Copyright(c) 2012 - 2013 Intel Corporation. All rights reserved. + * Copyright(c) 2012 - 2014 Intel Corporation. All rights reserved. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,6 +64,7 @@ #include "iwl-debug.h" #include "iwl-io.h" +#include "iwl-prph.h" #include "mvm.h" #include "fw-api-rs.h" @@ -76,6 +77,11 @@ int iwl_mvm_send_cmd(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd) { int ret; +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + /* * Synchronous commands from this op-mode must hold * the mutex, this ensures we don't try to send two @@ -125,6 +131,11 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, lockdep_assert_held(&mvm->mutex); +#if defined(CONFIG_IWLWIFI_DEBUGFS) && defined(CONFIG_PM_SLEEP) + if (WARN_ON(mvm->d3_test_active)) + return -EIO; +#endif + /* * Only synchronous commands can wait for status, * we use WANT_SKB so the caller can't. @@ -133,7 +144,7 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, "cmd flags %x", cmd->flags)) return -EINVAL; - cmd->flags |= CMD_SYNC | CMD_WANT_SKB; + cmd->flags |= CMD_WANT_SKB; ret = iwl_trans_send_cmd(mvm->trans, cmd); if (ret == -ERFKILL) { @@ -158,8 +169,8 @@ int iwl_mvm_send_cmd_status(struct iwl_mvm *mvm, struct iwl_host_cmd *cmd, goto out_free_resp; } - resp_len = le32_to_cpu(pkt->len_n_flags) & FH_RSCSR_FRAME_SIZE_MSK; - if (WARN_ON_ONCE(resp_len != sizeof(pkt->hdr) + sizeof(*resp))) { + resp_len = iwl_rx_packet_payload_len(pkt); + if (WARN_ON_ONCE(resp_len != sizeof(*resp))) { ret = -EIO; goto out_free_resp; } @@ -279,8 +290,8 @@ u8 iwl_mvm_next_antenna(struct iwl_mvm *mvm, u8 valid, u8 last_idx) return last_idx; } -static struct { - char *name; +static const struct { + const char *name; u8 num; } advanced_lookup[] = { { "NMI_INTERRUPT_WDG", 0x34 }, @@ -366,9 +377,67 @@ struct iwl_error_event_table { u32 flow_handler; /* FH read/write pointers, RX credit */ } __packed; +/* + * UMAC error struct - relevant starting from family 8000 chip. + * Note: This structure is read from the device with IO accesses, + * and the reading already does the endian conversion. As it is + * read with u32-sized accesses, any members with a different size + * need to be ordered correctly though! + */ +struct iwl_umac_error_event_table { + u32 valid; /* (nonzero) valid, (0) log is empty */ + u32 error_id; /* type of error */ + u32 pc; /* program counter */ + u32 blink1; /* branch link */ + u32 blink2; /* branch link */ + u32 ilink1; /* interrupt link */ + u32 ilink2; /* interrupt link */ + u32 data1; /* error-specific data */ + u32 data2; /* error-specific data */ + u32 line; /* source code line of error */ + u32 umac_ver; /* umac version */ +} __packed; + #define ERROR_START_OFFSET (1 * sizeof(u32)) #define ERROR_ELEM_SIZE (7 * sizeof(u32)) +static void iwl_mvm_dump_umac_error_log(struct iwl_mvm *mvm) +{ + struct iwl_trans *trans = mvm->trans; + struct iwl_umac_error_event_table table; + u32 base; + + base = mvm->umac_error_event_table; + + if (base < 0x800000 || base >= 0x80C000) { + IWL_ERR(mvm, + "Not valid error log pointer 0x%08X for %s uCode\n", + base, + (mvm->cur_ucode == IWL_UCODE_INIT) + ? "Init" : "RT"); + return; + } + + iwl_trans_read_mem_bytes(trans, base, &table, sizeof(table)); + + if (ERROR_START_OFFSET <= table.valid * ERROR_ELEM_SIZE) { + IWL_ERR(trans, "Start IWL Error Log Dump:\n"); + IWL_ERR(trans, "Status: 0x%08lX, count: %d\n", + mvm->status, table.valid); + } + + IWL_ERR(mvm, "0x%08X | %-28s\n", table.error_id, + desc_lookup(table.error_id)); + IWL_ERR(mvm, "0x%08X | umac uPc\n", table.pc); + IWL_ERR(mvm, "0x%08X | umac branchlink1\n", table.blink1); + IWL_ERR(mvm, "0x%08X | umac branchlink2\n", table.blink2); + IWL_ERR(mvm, "0x%08X | umac interruptlink1\n", table.ilink1); + IWL_ERR(mvm, "0x%08X | umac interruptlink2\n", table.ilink2); + IWL_ERR(mvm, "0x%08X | umac data1\n", table.data1); + IWL_ERR(mvm, "0x%08X | umac data2\n", table.data2); + IWL_ERR(mvm, "0x%08X | umac version\n", table.umac_ver); +} + void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) { struct iwl_trans *trans = mvm->trans; @@ -384,7 +453,7 @@ void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) base = mvm->fw->inst_errlog_ptr; } - if (base < 0x800000 || base >= 0x80C000) { + if (base < 0x800000) { IWL_ERR(mvm, "Not valid error log pointer 0x%08X for %s uCode\n", base, @@ -401,6 +470,10 @@ void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) mvm->status, table.valid); } + /* Do not change this output - scripts rely on it */ + + IWL_ERR(mvm, "Loaded firmware version: %s\n", mvm->fw->fw_version); + trace_iwlwifi_dev_ucode_error(trans->dev, table.error_id, table.tsf_low, table.data1, table.data2, table.data3, table.blink1, table.blink2, table.ilink1, @@ -441,7 +514,75 @@ void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) IWL_ERR(mvm, "0x%08X | lmpm_pmg_sel\n", table.lmpm_pmg_sel); IWL_ERR(mvm, "0x%08X | timestamp\n", table.u_timestamp); IWL_ERR(mvm, "0x%08X | flow_handler\n", table.flow_handler); + + if (mvm->support_umac_log) + iwl_mvm_dump_umac_error_log(mvm); +} + +#ifdef CONFIG_IWLWIFI_DEBUGFS +void iwl_mvm_fw_error_sram_dump(struct iwl_mvm *mvm) +{ + const struct fw_img *img; + u32 ofs, sram_len; + void *sram; + + if (!mvm->ucode_loaded || mvm->fw_error_sram || mvm->fw_error_dump) + return; + + img = &mvm->fw->img[mvm->cur_ucode]; + ofs = img->sec[IWL_UCODE_SECTION_DATA].offset; + sram_len = img->sec[IWL_UCODE_SECTION_DATA].len; + + sram = kzalloc(sram_len, GFP_ATOMIC); + if (!sram) + return; + + iwl_trans_read_mem_bytes(mvm->trans, ofs, sram, sram_len); + mvm->fw_error_sram = sram; + mvm->fw_error_sram_len = sram_len; +} + +void iwl_mvm_fw_error_rxf_dump(struct iwl_mvm *mvm) +{ + int i, reg_val; + unsigned long flags; + + if (!mvm->ucode_loaded || mvm->fw_error_rxf || mvm->fw_error_dump) + return; + + /* reading buffer size */ + reg_val = iwl_trans_read_prph(mvm->trans, RXF_SIZE_ADDR); + mvm->fw_error_rxf_len = + (reg_val & RXF_SIZE_BYTE_CNT_MSK) >> RXF_SIZE_BYTE_CND_POS; + + /* the register holds the value divided by 128 */ + mvm->fw_error_rxf_len = mvm->fw_error_rxf_len << 7; + + if (!mvm->fw_error_rxf_len) + return; + + mvm->fw_error_rxf = kzalloc(mvm->fw_error_rxf_len, GFP_ATOMIC); + if (!mvm->fw_error_rxf) { + mvm->fw_error_rxf_len = 0; + return; + } + + if (!iwl_trans_grab_nic_access(mvm->trans, false, &flags)) { + kfree(mvm->fw_error_rxf); + mvm->fw_error_rxf = NULL; + mvm->fw_error_rxf_len = 0; + return; + } + + for (i = 0; i < (mvm->fw_error_rxf_len / sizeof(u32)); i++) { + iwl_trans_write_prph(mvm->trans, RXF_LD_FENCE_OFFSET_ADDR, + i * sizeof(u32)); + mvm->fw_error_rxf[i] = + iwl_trans_read_prph(mvm->trans, RXF_FIFO_RD_FENCE_ADDR); + } + iwl_trans_release_nic_access(mvm->trans, &flags); } +#endif /** * iwl_mvm_send_lq_cmd() - Send link quality command @@ -453,21 +594,151 @@ void iwl_mvm_dump_nic_error_log(struct iwl_mvm *mvm) * this case to clear the state indicating that station creation is in * progress. */ -int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, - u8 flags, bool init) +int iwl_mvm_send_lq_cmd(struct iwl_mvm *mvm, struct iwl_lq_cmd *lq, bool init) { struct iwl_host_cmd cmd = { .id = LQ_CMD, .len = { sizeof(struct iwl_lq_cmd), }, - .flags = flags, + .flags = init ? 0 : CMD_ASYNC, .data = { lq, }, }; if (WARN_ON(lq->sta_id == IWL_MVM_STATION_COUNT)) return -EINVAL; - if (WARN_ON(init && (cmd.flags & CMD_ASYNC))) - return -EINVAL; - return iwl_mvm_send_cmd(mvm, &cmd); } + +/** + * iwl_mvm_update_smps - Get a requst to change the SMPS mode + * @req_type: The part of the driver who call for a change. + * @smps_requests: The request to change the SMPS mode. + * + * Get a requst to change the SMPS mode, + * and change it according to all other requests in the driver. + */ +void iwl_mvm_update_smps(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + enum iwl_mvm_smps_type_request req_type, + enum ieee80211_smps_mode smps_request) +{ + struct iwl_mvm_vif *mvmvif; + enum ieee80211_smps_mode smps_mode; + int i; + + lockdep_assert_held(&mvm->mutex); + + /* SMPS is irrelevant for NICs that don't have at least 2 RX antenna */ + if (num_of_ant(mvm->fw->valid_rx_ant) == 1) + return; + + if (vif->type == NL80211_IFTYPE_AP) + smps_mode = IEEE80211_SMPS_OFF; + else + smps_mode = IEEE80211_SMPS_AUTOMATIC; + + mvmvif = iwl_mvm_vif_from_mac80211(vif); + mvmvif->smps_requests[req_type] = smps_request; + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC) { + smps_mode = IEEE80211_SMPS_STATIC; + break; + } + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) + smps_mode = IEEE80211_SMPS_DYNAMIC; + } + + ieee80211_request_smps(vif, smps_mode); +} + +static void iwl_mvm_diversity_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + bool *result = _data; + int i; + + for (i = 0; i < NUM_IWL_MVM_SMPS_REQ; i++) { + if (mvmvif->smps_requests[i] == IEEE80211_SMPS_STATIC || + mvmvif->smps_requests[i] == IEEE80211_SMPS_DYNAMIC) + *result = false; + } +} + +bool iwl_mvm_rx_diversity_allowed(struct iwl_mvm *mvm) +{ + bool result = true; + + lockdep_assert_held(&mvm->mutex); + + if (num_of_ant(mvm->fw->valid_rx_ant) == 1) + return false; + + if (!mvm->cfg->rx_with_siso_diversity) + return false; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_diversity_iter, &result); + + return result; +} + +int iwl_mvm_update_low_latency(struct iwl_mvm *mvm, struct ieee80211_vif *vif, + bool value) +{ + struct iwl_mvm_vif *mvmvif = iwl_mvm_vif_from_mac80211(vif); + int res; + + lockdep_assert_held(&mvm->mutex); + + if (mvmvif->low_latency == value) + return 0; + + mvmvif->low_latency = value; + + res = iwl_mvm_update_quotas(mvm, NULL); + if (res) + return res; + + iwl_mvm_bt_coex_vif_change(mvm); + + return iwl_mvm_power_update_mac(mvm); +} + +static void iwl_mvm_ll_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + bool *result = _data; + + if (iwl_mvm_vif_low_latency(iwl_mvm_vif_from_mac80211(vif))) + *result = true; +} + +bool iwl_mvm_low_latency(struct iwl_mvm *mvm) +{ + bool result = false; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_ll_iter, &result); + + return result; +} + +static void iwl_mvm_idle_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + bool *idle = _data; + + if (!vif->bss_conf.idle) + *idle = false; +} + +bool iwl_mvm_is_idle(struct iwl_mvm *mvm) +{ + bool idle = true; + + ieee80211_iterate_active_interfaces_atomic( + mvm->hw, IEEE80211_IFACE_ITER_NORMAL, + iwl_mvm_idle_iter, &idle); + + return idle; +} |
