iwlwifi: add FW recovery flow
authorMordechay Goodstein <mordechay.goodstein@intel.com>
Thu, 13 Dec 2018 21:04:51 +0000 (23:04 +0200)
committerLuca Coelho <luciano.coelho@intel.com>
Thu, 14 Feb 2019 09:29:45 +0000 (11:29 +0200)
Add new API and TLV for the ability to send commands in the beginning
and end of reset flow.

The full flow of recovery is:

1. While loading FW, get address (from the TLV) of target buffer
   to read in case of reset
2. If an error/assert happens read the address data from step 1.
3. Reset the HW and load the FW.
4. Send the data read in step 2.
5. Add station keys
6. Send notification to FW that reset flow is done.

The main use of the recovery flow is for support in PN/SN recovery
when offloaded

Signed-off-by: Mordechay Goodstein <mordechay.goodstein@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
drivers/net/wireless/intel/iwlwifi/fw/api/alive.h
drivers/net/wireless/intel/iwlwifi/fw/api/commands.h
drivers/net/wireless/intel/iwlwifi/fw/file.h
drivers/net/wireless/intel/iwlwifi/fw/img.h
drivers/net/wireless/intel/iwlwifi/iwl-drv.c
drivers/net/wireless/intel/iwlwifi/mvm/fw.c
drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c
drivers/net/wireless/intel/iwlwifi/mvm/mvm.h
drivers/net/wireless/intel/iwlwifi/mvm/ops.c

index 0026e259fd8759107433e1bf55fe0b2850fbc149..df1bd0d2450e5bcccab3b274836868acc88fdaf3 100644 (file)
@@ -197,4 +197,24 @@ struct iwl_card_state_notif {
        __le32 flags;
 } __packed; /* CARD_STATE_NTFY_API_S_VER_1 */
 
+/**
+ * enum iwl_error_recovery_flags - flags for error recovery cmd
+ * @ERROR_RECOVERY_UPDATE_DB: update db from blob sent
+ * @ERROR_RECOVERY_END_OF_RECOVERY: end of recovery
+ */
+enum iwl_error_recovery_flags {
+       ERROR_RECOVERY_UPDATE_DB = BIT(0),
+       ERROR_RECOVERY_END_OF_RECOVERY = BIT(1),
+};
+
+/**
+ * struct iwl_fw_error_recovery_cmd - recovery cmd sent upon assert
+ * @flags: &enum iwl_error_recovery_flags
+ * @buf_size: db buffer size in bytes
+ */
+struct iwl_fw_error_recovery_cmd {
+       __le32 flags;
+       __le32 buf_size;
+} __packed; /* ERROR_RECOVERY_CMD_HDR_API_S_VER_1 */
+
 #endif /* __iwl_fw_api_alive_h__ */
index 0290b333d86046ef2a7945b9e6563dd67b0a1486..4d2274bcc0b50a115f27e348d100d281b7ac1f4b 100644 (file)
@@ -643,6 +643,11 @@ enum iwl_system_subcmd_ids {
         * @INIT_EXTENDED_CFG_CMD: &struct iwl_init_extended_cfg_cmd
         */
        INIT_EXTENDED_CFG_CMD = 0x03,
+
+       /**
+        * @FW_ERROR_RECOVERY_CMD: &struct iwl_fw_error_recovery_cmd
+        */
+       FW_ERROR_RECOVERY_CMD = 0x7,
 };
 
 #endif /* __iwl_fw_api_commands_h__ */
index 886a620e03cf793fbacc58e83dda3c483d50b902..3f61dc50c2d50fb7ecacda7f7d61199f441e18d5 100644 (file)
@@ -145,6 +145,7 @@ enum iwl_ucode_tlv_type {
        IWL_UCODE_TLV_IML               = 52,
        IWL_UCODE_TLV_UMAC_DEBUG_ADDRS  = 54,
        IWL_UCODE_TLV_LMAC_DEBUG_ADDRS  = 55,
+       IWL_UCODE_TLV_FW_RECOVERY_INFO  = 57,
        IWL_UCODE_TLV_TYPE_BUFFER_ALLOCATION    = IWL_UCODE_INI_TLV_GROUP | 0x1,
        IWL_UCODE_TLV_TYPE_HCMD                 = IWL_UCODE_INI_TLV_GROUP | 0x2,
        IWL_UCODE_TLV_TYPE_REGIONS              = IWL_UCODE_INI_TLV_GROUP | 0x3,
index 6ffa2e39a25cd9f8d5221900266493ec463dbfbe..f4c5a4d732068e7a7be08dae233b8cd25186e7d8 100644 (file)
@@ -105,6 +105,8 @@ struct iwl_ucode_capabilities {
        u32 n_scan_channels;
        u32 standard_phy_calibration_size;
        u32 flags;
+       u32 error_log_addr;
+       u32 error_log_size;
        unsigned long _api[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_API)];
        unsigned long _capa[BITS_TO_LONGS(NUM_IWL_UCODE_TLV_CAPA)];
 };
index 784e07b648e6f200d6afb3ebece0d1855d0a53da..91ec90e5eb6722c7bef6843a857292be65ce176b 100644 (file)
@@ -1088,6 +1088,20 @@ static int iwl_parse_tlv_firmware(struct iwl_drv *drv,
                                return -ENOMEM;
                        break;
                        }
+               case IWL_UCODE_TLV_FW_RECOVERY_INFO: {
+                       struct {
+                               __le32 buf_addr;
+                               __le32 buf_size;
+                       } *recov_info = (void *)tlv_data;
+
+                       if (tlv_len != sizeof(*recov_info))
+                               goto invalid_tlv_len;
+                       capa->error_log_addr =
+                               le32_to_cpu(recov_info->buf_addr);
+                       capa->error_log_size =
+                               le32_to_cpu(recov_info->buf_size);
+                       }
+                       break;
                case IWL_UCODE_TLV_UMAC_DEBUG_ADDRS: {
                        struct iwl_umac_debug_addrs *dbg_ptrs =
                                (void *)tlv_data;
index cf7f8c340ffeea53d04b477ceaf38f6e6f9a965a..28ef204c9cf765bb899af5ebcdf00747e1937416 100644 (file)
@@ -976,6 +976,57 @@ int iwl_mvm_get_sar_geo_profile(struct iwl_mvm *mvm)
 }
 #endif /* CONFIG_ACPI */
 
+void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags)
+{
+       u32 error_log_size = mvm->fw->ucode_capa.error_log_size;
+       int ret;
+       u32 resp;
+
+       struct iwl_fw_error_recovery_cmd recovery_cmd = {
+               .flags = cpu_to_le32(flags),
+               .buf_size = 0,
+       };
+       struct iwl_host_cmd host_cmd = {
+               .id = WIDE_ID(SYSTEM_GROUP, FW_ERROR_RECOVERY_CMD),
+               .flags = CMD_WANT_SKB,
+               .data = {&recovery_cmd, },
+               .len = {sizeof(recovery_cmd), },
+       };
+
+       /* no error log was defined in TLV */
+       if (!error_log_size)
+               return;
+
+       if (flags & ERROR_RECOVERY_UPDATE_DB) {
+               /* no buf was allocated while HW reset */
+               if (!mvm->error_recovery_buf)
+                       return;
+
+               host_cmd.data[1] = mvm->error_recovery_buf;
+               host_cmd.len[1] =  error_log_size;
+               host_cmd.dataflags[1] = IWL_HCMD_DFL_NOCOPY;
+               recovery_cmd.buf_size = cpu_to_le32(error_log_size);
+       }
+
+       ret = iwl_mvm_send_cmd(mvm, &host_cmd);
+       kfree(mvm->error_recovery_buf);
+       mvm->error_recovery_buf = NULL;
+
+       if (ret) {
+               IWL_ERR(mvm, "Failed to send recovery cmd %d\n", ret);
+               return;
+       }
+
+       /* skb respond is only relevant in ERROR_RECOVERY_UPDATE_DB */
+       if (flags & ERROR_RECOVERY_UPDATE_DB) {
+               resp = le32_to_cpu(*(__le32 *)host_cmd.resp_pkt->data);
+               if (resp)
+                       IWL_ERR(mvm,
+                               "Failed to send recovery cmd blob was invalid %d\n",
+                               resp);
+       }
+}
+
 static int iwl_mvm_sar_init(struct iwl_mvm *mvm)
 {
        int ret;
@@ -1212,6 +1263,9 @@ int iwl_mvm_up(struct iwl_mvm *mvm)
        if (!test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
                iwl_mvm_unref(mvm, IWL_MVM_REF_UCODE_DOWN);
 
+       if (test_bit(IWL_MVM_STATUS_IN_HW_RESTART, &mvm->status))
+               iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_UPDATE_DB);
+
        ret = iwl_mvm_sar_init(mvm);
        if (ret == 0) {
                ret = iwl_mvm_sar_geo_init(mvm);
index 9377fca39edf286361bc2b1796bb4968d76c644a..c025597667129b9a009e059e12a8cc4b4e15a32f 100644 (file)
@@ -1326,6 +1326,8 @@ static void iwl_mvm_restart_complete(struct iwl_mvm *mvm)
        /* allow transport/FW low power modes */
        iwl_mvm_unref(mvm, IWL_MVM_REF_UCODE_DOWN);
 
+       iwl_mvm_send_recovery_cmd(mvm, ERROR_RECOVERY_END_OF_RECOVERY);
+
        /*
         * If we have TDLS peers, remove them. We don't know the last seqno/PN
         * of packets the FW sent out, so we must reconnect.
index 0255157378db20e9130eae74d35883d068b659cf..c70fc90680af4a1f41ba0fa2aeec98be7fcf02aa 100644 (file)
@@ -1014,6 +1014,7 @@ struct iwl_mvm {
 
        /* -1 for always, 0 for never, >0 for that many times */
        s8 fw_restart;
+       u8 *error_recovery_buf;
 
 #ifdef CONFIG_IWLWIFI_LEDS
        struct led_classdev led;
@@ -1657,6 +1658,7 @@ void iwl_mvm_rx_queue_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb,
 void iwl_mvm_rx_tx_cmd(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb);
 void iwl_mvm_mfu_assert_dump_notif(struct iwl_mvm *mvm,
                                   struct iwl_rx_cmd_buffer *rxb);
+void iwl_mvm_send_recovery_cmd(struct iwl_mvm *mvm, u32 flags);
 void iwl_mvm_rx_ba_notif(struct iwl_mvm *mvm, struct iwl_rx_cmd_buffer *rxb);
 void iwl_mvm_rx_ant_coupling_notif(struct iwl_mvm *mvm,
                                   struct iwl_rx_cmd_buffer *rxb);
index 0c276124bf0f61731a53d48f83920c2678c748d5..f8a5a7074dc1f9a9224eb82322b77cca725729a8 100644 (file)
@@ -422,6 +422,7 @@ static const struct iwl_hcmd_names iwl_mvm_legacy_names[] = {
 static const struct iwl_hcmd_names iwl_mvm_system_names[] = {
        HCMD_NAME(SHARED_MEM_CFG_CMD),
        HCMD_NAME(INIT_EXTENDED_CFG_CMD),
+       HCMD_NAME(FW_ERROR_RECOVERY_CMD),
 };
 
 /* Please keep this array *SORTED* by hex value.
@@ -921,6 +922,9 @@ static void iwl_op_mode_mvm_stop(struct iwl_op_mode *op_mode)
        kfree(mvm->mcast_filter_cmd);
        mvm->mcast_filter_cmd = NULL;
 
+       kfree(mvm->error_recovery_buf);
+       mvm->error_recovery_buf = NULL;
+
 #if defined(CONFIG_PM_SLEEP) && defined(CONFIG_IWLWIFI_DEBUGFS)
        kfree(mvm->d3_resume_sram);
 #endif
@@ -1301,6 +1305,20 @@ void iwl_mvm_nic_restart(struct iwl_mvm *mvm, bool fw_error)
                /* don't let the transport/FW power down */
                iwl_mvm_ref(mvm, IWL_MVM_REF_UCODE_DOWN);
 
+               if (mvm->fw->ucode_capa.error_log_size) {
+                       u32 src_size = mvm->fw->ucode_capa.error_log_size;
+                       u32 src_addr = mvm->fw->ucode_capa.error_log_addr;
+                       u8 *recover_buf = kzalloc(src_size, GFP_ATOMIC);
+
+                       if (recover_buf) {
+                               mvm->error_recovery_buf = recover_buf;
+                               iwl_trans_read_mem_bytes(mvm->trans,
+                                                        src_addr,
+                                                        recover_buf,
+                                                        src_size);
+                       }
+               }
+
                if (fw_error && mvm->fw_restart > 0)
                        mvm->fw_restart--;
                set_bit(IWL_MVM_STATUS_HW_RESTART_REQUESTED, &mvm->status);