Skip to content

Commit

Permalink
wifi: iwlwifi: implement reset escalation
Browse files Browse the repository at this point in the history
If the normal reset methods don't work well, attempt to
escalate to ever increasing methods. TOP reset will only
be available for SC (and presumably higher) devices, and
still needs to be filled in.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20241231135726.804e005403d8.I9558f09cd68eec16b02373b1e47adafd28fdffa3@changeid
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
  • Loading branch information
Johannes Berg committed Jan 13, 2025
1 parent 9673c35 commit 9a2f13c
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 23 deletions.
1 change: 1 addition & 0 deletions drivers/net/wireless/intel/iwlwifi/iwl-drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,7 @@ module_init(iwl_drv_init);
static void __exit iwl_drv_exit(void)
{
iwl_pci_unregister_driver();
iwl_trans_free_restart_list();

#ifdef CONFIG_IWLWIFI_DEBUGFS
debugfs_remove_recursive(iwl_dbgfs_root);
Expand Down
160 changes: 137 additions & 23 deletions drivers/net/wireless/intel/iwlwifi/iwl-trans.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
#include <linux/kernel.h>
#include <linux/bsearch.h>
#include <linux/list.h>

#include "fw/api/tx.h"
#include "iwl-trans.h"
Expand All @@ -16,6 +17,68 @@
#include "pcie/internal.h"
#include "iwl-context-info-gen3.h"

struct iwl_trans_dev_restart_data {
struct list_head list;
unsigned int restart_count;
time64_t last_error;
char name[];
};

static LIST_HEAD(restart_data_list);
static DEFINE_SPINLOCK(restart_data_lock);

static struct iwl_trans_dev_restart_data *
iwl_trans_get_restart_data(struct device *dev)
{
struct iwl_trans_dev_restart_data *tmp, *data = NULL;
const char *name = dev_name(dev);

spin_lock(&restart_data_lock);
list_for_each_entry(tmp, &restart_data_list, list) {
if (strcmp(tmp->name, name))
continue;
data = tmp;
break;
}
spin_unlock(&restart_data_lock);

if (data)
return data;

data = kzalloc(struct_size(data, name, strlen(name) + 1), GFP_ATOMIC);
if (!data)
return NULL;

strcpy(data->name, name);
spin_lock(&restart_data_lock);
list_add_tail(&data->list, &restart_data_list);
spin_unlock(&restart_data_lock);

return data;
}

static void iwl_trans_inc_restart_count(struct device *dev)
{
struct iwl_trans_dev_restart_data *data;

data = iwl_trans_get_restart_data(dev);
if (data) {
data->last_error = ktime_get_boottime_seconds();
data->restart_count++;
}
}

void iwl_trans_free_restart_list(void)
{
struct iwl_trans_dev_restart_data *tmp;

while ((tmp = list_first_entry_or_null(&restart_data_list,
typeof(*tmp), list))) {
list_del(&tmp->list);
kfree(tmp);
}
}

struct iwl_trans_reprobe {
struct device *dev;
struct work_struct work;
Expand All @@ -34,10 +97,52 @@ static void iwl_trans_reprobe_wk(struct work_struct *wk)
module_put(THIS_MODULE);
}

#define IWL_TRANS_RESET_OK_TIME 180 /* seconds */

static enum iwl_reset_mode
iwl_trans_determine_restart_mode(struct iwl_trans *trans)
{
struct iwl_trans_dev_restart_data *data;
enum iwl_reset_mode at_least = 0;
unsigned int index;
static const enum iwl_reset_mode escalation_list[] = {
IWL_RESET_MODE_SW_RESET,
IWL_RESET_MODE_REPROBE,
IWL_RESET_MODE_REPROBE,
IWL_RESET_MODE_FUNC_RESET,
/* FIXME: add TOP reset */
IWL_RESET_MODE_PROD_RESET,
/* FIXME: add TOP reset */
IWL_RESET_MODE_PROD_RESET,
/* FIXME: add TOP reset */
IWL_RESET_MODE_PROD_RESET,
};

if (trans->restart.during_reset)
at_least = IWL_RESET_MODE_REPROBE;

data = iwl_trans_get_restart_data(trans->dev);
if (!data)
return at_least;

if (ktime_get_boottime_seconds() - data->last_error >=
IWL_TRANS_RESET_OK_TIME)
data->restart_count = 0;

index = data->restart_count;
if (index >= ARRAY_SIZE(escalation_list))
index = ARRAY_SIZE(escalation_list) - 1;

return max(at_least, escalation_list[index]);
}

#define IWL_TRANS_RESET_DELAY (HZ * 60)

static void iwl_trans_restart_wk(struct work_struct *wk)
{
struct iwl_trans *trans = container_of(wk, typeof(*trans), restart.wk);
struct iwl_trans_reprobe *reprobe;
enum iwl_reset_mode mode;

if (!trans->op_mode)
return;
Expand All @@ -62,32 +167,41 @@ static void iwl_trans_restart_wk(struct work_struct *wk)
if (!iwlwifi_mod_params.fw_restart)
return;

if (!trans->restart.during_reset) {
iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type);
return;
}
mode = iwl_trans_determine_restart_mode(trans);

IWL_ERR(trans,
"Device error during reconfiguration - reprobe!\n");
iwl_trans_inc_restart_count(trans->dev);

/*
* get a module reference to avoid doing this while unloading
* anyway and to avoid scheduling a work with code that's
* being removed.
*/
if (!try_module_get(THIS_MODULE)) {
IWL_ERR(trans, "Module is being unloaded - abort\n");
return;
}

reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
if (!reprobe) {
module_put(THIS_MODULE);
return;
switch (mode) {
case IWL_RESET_MODE_SW_RESET:
IWL_ERR(trans, "Device error - SW reset\n");
iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type);
break;
case IWL_RESET_MODE_REPROBE:
IWL_ERR(trans, "Device error - reprobe!\n");

/*
* get a module reference to avoid doing this while unloading
* anyway and to avoid scheduling a work with code that's
* being removed.
*/
if (!try_module_get(THIS_MODULE)) {
IWL_ERR(trans, "Module is being unloaded - abort\n");
return;
}

reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
if (!reprobe) {
module_put(THIS_MODULE);
return;
}
reprobe->dev = get_device(trans->dev);
INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk);
schedule_work(&reprobe->work);
break;
default:
iwl_trans_pcie_reset(trans, mode);
break;
}
reprobe->dev = get_device(trans->dev);
INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk);
schedule_work(&reprobe->work);
}

struct iwl_trans *iwl_trans_alloc(unsigned int priv_size,
Expand Down
6 changes: 6 additions & 0 deletions drivers/net/wireless/intel/iwlwifi/iwl-trans.h
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,8 @@ static inline bool iwl_trans_is_hw_error_value(u32 val)
return ((val & ~0xf) == 0xa5a5a5a0) || ((val & ~0xf) == 0x5a5a5a50);
}

void iwl_trans_free_restart_list(void);

/*****************************************************
* PCIe handling
*****************************************************/
Expand All @@ -1248,6 +1250,10 @@ void iwl_pci_unregister_driver(void);

/* Note: order matters */
enum iwl_reset_mode {
/* upper level modes: */
IWL_RESET_MODE_SW_RESET,
IWL_RESET_MODE_REPROBE,
/* PCIE level modes: */
IWL_RESET_MODE_REMOVE_ONLY,
IWL_RESET_MODE_RESCAN,
IWL_RESET_MODE_FUNC_RESET,
Expand Down
8 changes: 8 additions & 0 deletions drivers/net/wireless/intel/iwlwifi/pcie/trans.c
Original file line number Diff line number Diff line change
Expand Up @@ -2351,6 +2351,9 @@ void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode)
struct iwl_trans_pcie_removal *removal;
char _msg = 0, *msg = &_msg;

if (WARN_ON(mode < IWL_RESET_MODE_REMOVE_ONLY))
return;

if (test_bit(STATUS_TRANS_DEAD, &trans->status))
return;

Expand Down Expand Up @@ -3255,6 +3258,8 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file,
{
struct iwl_trans *trans = file->private_data;
static const char * const modes[] = {
[IWL_RESET_MODE_SW_RESET] = "n/a",
[IWL_RESET_MODE_REPROBE] = "n/a",
[IWL_RESET_MODE_REMOVE_ONLY] = "remove",
[IWL_RESET_MODE_RESCAN] = "rescan",
[IWL_RESET_MODE_FUNC_RESET] = "function",
Expand All @@ -3273,6 +3278,9 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file,
if (mode < 0)
return mode;

if (mode < IWL_RESET_MODE_REMOVE_ONLY)
return -EINVAL;

iwl_trans_pcie_reset(trans, mode);

return count;
Expand Down

0 comments on commit 9a2f13c

Please sign in to comment.