Skip to content

Commit 9a2f13c

Browse files
committed
wifi: iwlwifi: implement reset escalation
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 <[email protected]> Signed-off-by: Miri Korenblit <[email protected]> Link: https://patch.msgid.link/20241231135726.804e005403d8.I9558f09cd68eec16b02373b1e47adafd28fdffa3@changeid Signed-off-by: Johannes Berg <[email protected]>
1 parent 9673c35 commit 9a2f13c

File tree

4 files changed

+152
-23
lines changed

4 files changed

+152
-23
lines changed

drivers/net/wireless/intel/iwlwifi/iwl-drv.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,7 @@ module_init(iwl_drv_init);
19491949
static void __exit iwl_drv_exit(void)
19501950
{
19511951
iwl_pci_unregister_driver();
1952+
iwl_trans_free_restart_list();
19521953

19531954
#ifdef CONFIG_IWLWIFI_DEBUGFS
19541955
debugfs_remove_recursive(iwl_dbgfs_root);

drivers/net/wireless/intel/iwlwifi/iwl-trans.c

Lines changed: 137 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77
#include <linux/kernel.h>
88
#include <linux/bsearch.h>
9+
#include <linux/list.h>
910

1011
#include "fw/api/tx.h"
1112
#include "iwl-trans.h"
@@ -16,6 +17,68 @@
1617
#include "pcie/internal.h"
1718
#include "iwl-context-info-gen3.h"
1819

20+
struct iwl_trans_dev_restart_data {
21+
struct list_head list;
22+
unsigned int restart_count;
23+
time64_t last_error;
24+
char name[];
25+
};
26+
27+
static LIST_HEAD(restart_data_list);
28+
static DEFINE_SPINLOCK(restart_data_lock);
29+
30+
static struct iwl_trans_dev_restart_data *
31+
iwl_trans_get_restart_data(struct device *dev)
32+
{
33+
struct iwl_trans_dev_restart_data *tmp, *data = NULL;
34+
const char *name = dev_name(dev);
35+
36+
spin_lock(&restart_data_lock);
37+
list_for_each_entry(tmp, &restart_data_list, list) {
38+
if (strcmp(tmp->name, name))
39+
continue;
40+
data = tmp;
41+
break;
42+
}
43+
spin_unlock(&restart_data_lock);
44+
45+
if (data)
46+
return data;
47+
48+
data = kzalloc(struct_size(data, name, strlen(name) + 1), GFP_ATOMIC);
49+
if (!data)
50+
return NULL;
51+
52+
strcpy(data->name, name);
53+
spin_lock(&restart_data_lock);
54+
list_add_tail(&data->list, &restart_data_list);
55+
spin_unlock(&restart_data_lock);
56+
57+
return data;
58+
}
59+
60+
static void iwl_trans_inc_restart_count(struct device *dev)
61+
{
62+
struct iwl_trans_dev_restart_data *data;
63+
64+
data = iwl_trans_get_restart_data(dev);
65+
if (data) {
66+
data->last_error = ktime_get_boottime_seconds();
67+
data->restart_count++;
68+
}
69+
}
70+
71+
void iwl_trans_free_restart_list(void)
72+
{
73+
struct iwl_trans_dev_restart_data *tmp;
74+
75+
while ((tmp = list_first_entry_or_null(&restart_data_list,
76+
typeof(*tmp), list))) {
77+
list_del(&tmp->list);
78+
kfree(tmp);
79+
}
80+
}
81+
1982
struct iwl_trans_reprobe {
2083
struct device *dev;
2184
struct work_struct work;
@@ -34,10 +97,52 @@ static void iwl_trans_reprobe_wk(struct work_struct *wk)
3497
module_put(THIS_MODULE);
3598
}
3699

100+
#define IWL_TRANS_RESET_OK_TIME 180 /* seconds */
101+
102+
static enum iwl_reset_mode
103+
iwl_trans_determine_restart_mode(struct iwl_trans *trans)
104+
{
105+
struct iwl_trans_dev_restart_data *data;
106+
enum iwl_reset_mode at_least = 0;
107+
unsigned int index;
108+
static const enum iwl_reset_mode escalation_list[] = {
109+
IWL_RESET_MODE_SW_RESET,
110+
IWL_RESET_MODE_REPROBE,
111+
IWL_RESET_MODE_REPROBE,
112+
IWL_RESET_MODE_FUNC_RESET,
113+
/* FIXME: add TOP reset */
114+
IWL_RESET_MODE_PROD_RESET,
115+
/* FIXME: add TOP reset */
116+
IWL_RESET_MODE_PROD_RESET,
117+
/* FIXME: add TOP reset */
118+
IWL_RESET_MODE_PROD_RESET,
119+
};
120+
121+
if (trans->restart.during_reset)
122+
at_least = IWL_RESET_MODE_REPROBE;
123+
124+
data = iwl_trans_get_restart_data(trans->dev);
125+
if (!data)
126+
return at_least;
127+
128+
if (ktime_get_boottime_seconds() - data->last_error >=
129+
IWL_TRANS_RESET_OK_TIME)
130+
data->restart_count = 0;
131+
132+
index = data->restart_count;
133+
if (index >= ARRAY_SIZE(escalation_list))
134+
index = ARRAY_SIZE(escalation_list) - 1;
135+
136+
return max(at_least, escalation_list[index]);
137+
}
138+
139+
#define IWL_TRANS_RESET_DELAY (HZ * 60)
140+
37141
static void iwl_trans_restart_wk(struct work_struct *wk)
38142
{
39143
struct iwl_trans *trans = container_of(wk, typeof(*trans), restart.wk);
40144
struct iwl_trans_reprobe *reprobe;
145+
enum iwl_reset_mode mode;
41146

42147
if (!trans->op_mode)
43148
return;
@@ -62,32 +167,41 @@ static void iwl_trans_restart_wk(struct work_struct *wk)
62167
if (!iwlwifi_mod_params.fw_restart)
63168
return;
64169

65-
if (!trans->restart.during_reset) {
66-
iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type);
67-
return;
68-
}
170+
mode = iwl_trans_determine_restart_mode(trans);
69171

70-
IWL_ERR(trans,
71-
"Device error during reconfiguration - reprobe!\n");
172+
iwl_trans_inc_restart_count(trans->dev);
72173

73-
/*
74-
* get a module reference to avoid doing this while unloading
75-
* anyway and to avoid scheduling a work with code that's
76-
* being removed.
77-
*/
78-
if (!try_module_get(THIS_MODULE)) {
79-
IWL_ERR(trans, "Module is being unloaded - abort\n");
80-
return;
81-
}
82-
83-
reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
84-
if (!reprobe) {
85-
module_put(THIS_MODULE);
86-
return;
174+
switch (mode) {
175+
case IWL_RESET_MODE_SW_RESET:
176+
IWL_ERR(trans, "Device error - SW reset\n");
177+
iwl_trans_opmode_sw_reset(trans, trans->restart.mode.type);
178+
break;
179+
case IWL_RESET_MODE_REPROBE:
180+
IWL_ERR(trans, "Device error - reprobe!\n");
181+
182+
/*
183+
* get a module reference to avoid doing this while unloading
184+
* anyway and to avoid scheduling a work with code that's
185+
* being removed.
186+
*/
187+
if (!try_module_get(THIS_MODULE)) {
188+
IWL_ERR(trans, "Module is being unloaded - abort\n");
189+
return;
190+
}
191+
192+
reprobe = kzalloc(sizeof(*reprobe), GFP_KERNEL);
193+
if (!reprobe) {
194+
module_put(THIS_MODULE);
195+
return;
196+
}
197+
reprobe->dev = get_device(trans->dev);
198+
INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk);
199+
schedule_work(&reprobe->work);
200+
break;
201+
default:
202+
iwl_trans_pcie_reset(trans, mode);
203+
break;
87204
}
88-
reprobe->dev = get_device(trans->dev);
89-
INIT_WORK(&reprobe->work, iwl_trans_reprobe_wk);
90-
schedule_work(&reprobe->work);
91205
}
92206

93207
struct iwl_trans *iwl_trans_alloc(unsigned int priv_size,

drivers/net/wireless/intel/iwlwifi/iwl-trans.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,8 @@ static inline bool iwl_trans_is_hw_error_value(u32 val)
12401240
return ((val & ~0xf) == 0xa5a5a5a0) || ((val & ~0xf) == 0x5a5a5a50);
12411241
}
12421242

1243+
void iwl_trans_free_restart_list(void);
1244+
12431245
/*****************************************************
12441246
* PCIe handling
12451247
*****************************************************/
@@ -1248,6 +1250,10 @@ void iwl_pci_unregister_driver(void);
12481250

12491251
/* Note: order matters */
12501252
enum iwl_reset_mode {
1253+
/* upper level modes: */
1254+
IWL_RESET_MODE_SW_RESET,
1255+
IWL_RESET_MODE_REPROBE,
1256+
/* PCIE level modes: */
12511257
IWL_RESET_MODE_REMOVE_ONLY,
12521258
IWL_RESET_MODE_RESCAN,
12531259
IWL_RESET_MODE_FUNC_RESET,

drivers/net/wireless/intel/iwlwifi/pcie/trans.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2351,6 +2351,9 @@ void iwl_trans_pcie_reset(struct iwl_trans *trans, enum iwl_reset_mode mode)
23512351
struct iwl_trans_pcie_removal *removal;
23522352
char _msg = 0, *msg = &_msg;
23532353

2354+
if (WARN_ON(mode < IWL_RESET_MODE_REMOVE_ONLY))
2355+
return;
2356+
23542357
if (test_bit(STATUS_TRANS_DEAD, &trans->status))
23552358
return;
23562359

@@ -3255,6 +3258,8 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file,
32553258
{
32563259
struct iwl_trans *trans = file->private_data;
32573260
static const char * const modes[] = {
3261+
[IWL_RESET_MODE_SW_RESET] = "n/a",
3262+
[IWL_RESET_MODE_REPROBE] = "n/a",
32583263
[IWL_RESET_MODE_REMOVE_ONLY] = "remove",
32593264
[IWL_RESET_MODE_RESCAN] = "rescan",
32603265
[IWL_RESET_MODE_FUNC_RESET] = "function",
@@ -3273,6 +3278,9 @@ static ssize_t iwl_dbgfs_reset_write(struct file *file,
32733278
if (mode < 0)
32743279
return mode;
32753280

3281+
if (mode < IWL_RESET_MODE_REMOVE_ONLY)
3282+
return -EINVAL;
3283+
32763284
iwl_trans_pcie_reset(trans, mode);
32773285

32783286
return count;

0 commit comments

Comments
 (0)