Skip to content

Commit 52f3bf4

Browse files
meehienfrank-w
authored andcommitted
wifi: mt76: mt7996: recover the device via PCIe slot reset
The previous patches convert a post-error MMIO access from a kernel panic into a clean disconnect, but the WiFi stays down until a manual power cycle. Implement actual PCIe error recovery so the device can come back on its own. error_detected() now returns PCI_ERS_RESULT_NEED_RESET (unless the channel has permanently failed), asking the PCIe core to perform a secondary bus reset - which asserts PERST# to the endpoint and retrains the link - and then call slot_reset(). The crash logs show this bus reset already succeeds ('Root Port link has been reset', 'ready 0ms after bus reset'); recovery previously failed only because the driver registered no callbacks. slot_reset() restores config space (clobbered by the bus reset), optionally hard power-cycles the card through a board reset-gpios line - needed when PERST# cannot clear an MCU wedged by a supply brownout, a no-op when the GPIO is absent - and confirms the chip answers MMIO (MT_HW_REV) before clearing MT76_REMOVED and driving the existing full chip reset (mt7996_mac_full_reset() reloads firmware and rebuilds DMA/NAPI/token state). If the chip does not answer, it returns DISCONNECT, leaving the safe WiFi-down state of the earlier patches. pci_save_state() is added at the end of probe so slot_reset() has valid state to restore. The hif2 function gets a minimal slot_reset that restores its own config space; the primary function drives the shared chip reinit. Signed-off-by: Mihai Ordean <research@mihaiordean.com>
1 parent f32a1f6 commit 52f3bf4

2 files changed

Lines changed: 115 additions & 20 deletions

File tree

drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,12 @@ struct mt7996_dev {
419419
struct wiphy_radio_freq_range radio_freqs[MT7996_MAX_RADIOS];
420420

421421
struct mt7996_hif *hif2;
422+
423+
/* Optional board GPIO wired to the WiFi card reset/power-enable (in
424+
* parallel with the manual switch), used for hard recovery after a PCIe
425+
* error when PERST# alone does not revive a brownout-wedged MCU.
426+
*/
427+
struct gpio_desc *reset_gpio;
422428
struct mt7996_reg_desc reg;
423429
u8 q_id[MT7996_MAX_QUEUE];
424430
u32 q_int_mask[MT7996_MAX_QUEUE];

drivers/net/wireless/mediatek/mt76/mt7996/pci.c

Lines changed: 109 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright (C) 2022 MediaTek Inc.
44
*/
55

6+
#include <linux/delay.h>
7+
#include <linux/gpio/consumer.h>
68
#include <linux/kernel.h>
79
#include <linux/module.h>
810
#include <linux/pci.h>
@@ -142,6 +144,12 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
142144
if (hif2)
143145
hif2->mt7996 = dev;
144146

147+
/* Optional reset line for slot_reset() recovery; absent on stock DT. */
148+
dev->reset_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
149+
GPIOD_OUT_LOW);
150+
if (IS_ERR(dev->reset_gpio))
151+
dev->reset_gpio = NULL;
152+
145153
mt76_npu_init(mdev, pci_resource_start(pdev, 0),
146154
pdev->bus && pci_domain_nr(pdev->bus) ? 3 : 2);
147155

@@ -198,6 +206,11 @@ static int mt7996_pci_probe(struct pci_dev *pdev,
198206
if (ret)
199207
goto free_hif2_irq;
200208

209+
/* Snapshot config space so slot_reset() can restore BARs and the
210+
* command register after a PCIe bus reset clobbers them.
211+
*/
212+
pci_save_state(pdev);
213+
201214
return 0;
202215

203216
free_hif2_irq:
@@ -243,37 +256,36 @@ static void mt7996_pci_remove(struct pci_dev *pdev)
243256
mt7996_unregister_device(dev);
244257
}
245258

246-
static pci_ers_result_t mt7996_pci_mark_removed(struct mt76_dev *mdev)
259+
static void mt7996_pci_mark_removed(struct mt76_dev *mdev)
247260
{
248261
/* Flag the device gone so every liveness check - including
249262
* mt7996_dev_gone() guarding the reset worker - short-circuits before
250-
* it can touch the now-unreachable MMIO window. This is the same idiom
251-
* the mt792x PCIe driver uses on surprise removal, and it does not rely
252-
* on the controller updating pci_dev->error_state.
263+
* it can touch the now-unreachable MMIO window, and wake anyone blocked
264+
* on an MCU response so it fails fast instead of stalling to timeout.
253265
*/
254266
set_bit(MT76_REMOVED, &mdev->phy.state);
255-
256-
/* Wake anyone blocked on an MCU response so it fails fast instead of
257-
* stalling to timeout against a dead endpoint.
258-
*/
259267
wake_up(&mdev->mcu.wait);
260-
261-
/* The MAC and firmware state cannot survive a PCIe link loss and this
262-
* driver has no slot_reset re-init path, so request a clean disconnect
263-
* rather than a doomed recovery attempt.
264-
*/
265-
return PCI_ERS_RESULT_DISCONNECT;
266268
}
267269

268270
static pci_ers_result_t
269271
mt7996_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state)
270272
{
271273
struct mt76_dev *mdev = pci_get_drvdata(pdev);
272274

273-
dev_err(&pdev->dev, "PCIe error detected (state=%u), marking device removed\n",
275+
if (state == pci_channel_io_perm_failure)
276+
return PCI_ERS_RESULT_DISCONNECT;
277+
278+
dev_err(&pdev->dev, "PCIe error detected (state=%u), requesting slot reset\n",
274279
state);
275280

276-
return mt7996_pci_mark_removed(mdev);
281+
mt7996_pci_mark_removed(mdev);
282+
283+
/* Ask the PCIe core to perform a secondary bus reset (PERST#) and then
284+
* call .slot_reset, where the chip is reinitialised. If recovery there
285+
* fails, slot_reset returns DISCONNECT and we end up in the same safe
286+
* state as before - WiFi down, the rest of the system alive.
287+
*/
288+
return PCI_ERS_RESULT_NEED_RESET;
277289
}
278290

279291
static pci_ers_result_t
@@ -282,26 +294,103 @@ mt7996_hif_error_detected(struct pci_dev *pdev, pci_channel_state_t state)
282294
struct mt7996_hif *hif = pci_get_drvdata(pdev);
283295
struct mt7996_dev *dev = hif ? hif->mt7996 : NULL;
284296

297+
if (state == pci_channel_io_perm_failure)
298+
return PCI_ERS_RESULT_DISCONNECT;
299+
285300
dev_err(&pdev->dev, "PCIe error detected (state=%u) on hif2\n", state);
286301

287302
/* hif2 is the second PCIe function of the same physical chip; a link
288303
* loss here downs the whole device. Mark the owning mt76 device removed
289-
* so its reset worker bails out too. The primary function may not have
290-
* claimed this hif yet (still probing), in which case there is nothing
291-
* to mark - just report the disconnect.
304+
* so its reset worker bails out too, then request the shared slot reset.
305+
* If the primary function has not claimed this hif yet, there is nothing
306+
* to reinitialise from here - just disconnect this function.
292307
*/
293308
if (!dev)
294309
return PCI_ERS_RESULT_DISCONNECT;
295310

296-
return mt7996_pci_mark_removed(&dev->mt76);
311+
mt7996_pci_mark_removed(&dev->mt76);
312+
313+
return PCI_ERS_RESULT_NEED_RESET;
314+
}
315+
316+
static pci_ers_result_t mt7996_pci_slot_reset(struct pci_dev *pdev)
317+
{
318+
struct mt76_dev *mdev = pci_get_drvdata(pdev);
319+
struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
320+
321+
dev_info(&pdev->dev, "PCIe slot reset, reinitialising device\n");
322+
323+
/* The secondary bus reset clobbered config space; restore BARs and the
324+
* command register before any MMIO, and re-enable bus mastering.
325+
*/
326+
pci_restore_state(pdev);
327+
pci_set_master(pdev);
328+
329+
/* Optional hard power-cycle of the card via a board GPIO wired in
330+
* parallel with the manual power switch. PERST# from the bus reset above
331+
* may not clear an MCU wedged by a supply brownout; cycling the rail
332+
* does. No-op when no reset-gpios is described in DT.
333+
*/
334+
if (dev->reset_gpio) {
335+
gpiod_set_value_cansleep(dev->reset_gpio, 1);
336+
msleep(20);
337+
gpiod_set_value_cansleep(dev->reset_gpio, 0);
338+
msleep(50);
339+
}
340+
341+
/* Confirm the chip is actually back on the bus before promising the
342+
* core a recovery; a dead read returns all-ones.
343+
*/
344+
if (mt76_rr(dev, MT_HW_REV) == 0xffffffff) {
345+
dev_err(&pdev->dev, "device unreachable after slot reset\n");
346+
return PCI_ERS_RESULT_DISCONNECT;
347+
}
348+
349+
/* Config space is valid and the channel is online again, so the
350+
* mt7996_dev_gone() guard must stop tripping for the reinit to run.
351+
*/
352+
clear_bit(MT76_REMOVED, &mdev->phy.state);
353+
354+
/* Reload firmware and rebuild DMA/NAPI/token state through the existing
355+
* full chip reset path (mt7996_mac_reset_work -> mt7996_mac_full_reset).
356+
*/
357+
dev->recovery.restart = true;
358+
queue_work(mdev->wq, &dev->reset_work);
359+
360+
return PCI_ERS_RESULT_RECOVERED;
361+
}
362+
363+
static pci_ers_result_t mt7996_hif_slot_reset(struct pci_dev *pdev)
364+
{
365+
/* hif2 shares the chip with the primary function, which drives the
366+
* actual reinit from its own slot_reset. Here we only need this
367+
* function's own config space restored so the reinit can reach the
368+
* hif2 register window.
369+
*/
370+
pci_restore_state(pdev);
371+
pci_set_master(pdev);
372+
373+
return PCI_ERS_RESULT_RECOVERED;
374+
}
375+
376+
static void mt7996_pci_io_resume(struct pci_dev *pdev)
377+
{
378+
/* The chip reinit queued from slot_reset ends in ieee80211_restart_hw(),
379+
* which restarts the queues, so nothing extra is required here.
380+
*/
381+
dev_info(&pdev->dev, "PCIe recovery complete\n");
297382
}
298383

299384
static const struct pci_error_handlers mt7996_pci_err_handler = {
300385
.error_detected = mt7996_pci_error_detected,
386+
.slot_reset = mt7996_pci_slot_reset,
387+
.resume = mt7996_pci_io_resume,
301388
};
302389

303390
static const struct pci_error_handlers mt7996_hif_err_handler = {
304391
.error_detected = mt7996_hif_error_detected,
392+
.slot_reset = mt7996_hif_slot_reset,
393+
.resume = mt7996_pci_io_resume,
305394
};
306395

307396
struct pci_driver mt7996_hif_driver = {

0 commit comments

Comments
 (0)