]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: wwan: iosm: Fix hibernation by re-binding the driver around it
authorMaciej S. Szmigiero <mail@maciej.szmigiero.name>
Wed, 8 Jan 2025 23:33:50 +0000 (00:33 +0100)
committerJakub Kicinski <kuba@kernel.org>
Thu, 16 Jan 2025 01:39:54 +0000 (17:39 -0800)
Currently, the driver is seriously broken with respect to the
hibernation (S4): after image restore the device is back into
IPC_MEM_EXEC_STAGE_BOOT (which AFAIK means bootloader stage) and needs
full re-launch of the rest of its firmware, but the driver restore
handler treats the device as merely sleeping and just sends it a
wake-up command.

This wake-up command times out but device nodes (/dev/wwan*) remain
accessible.
However attempting to use them causes the bootloader to crash and
enter IPC_MEM_EXEC_STAGE_CD_READY stage (which apparently means "a crash
dump is ready").

It seems that the device cannot be re-initialized from this crashed
stage without toggling some reset pin (on my test platform that's
apparently what the device _RST ACPI method does).

While it would theoretically be possible to rewrite the driver to tear
down the whole MUX / IPC layers on hibernation (so the bootloader does
not crash from improper access) and then re-launch the device on
restore this would require significant refactoring of the driver
(believe me, I've tried), since there are quite a few assumptions
hard-coded in the driver about the device never being partially
de-initialized (like channels other than devlink cannot be closed,
for example).
Probably this would also need some programming guide for this hardware.

Considering that the driver seems orphaned [1] and other people are
hitting this issue too [2] fix it by simply unbinding the PCI driver
before hibernation and re-binding it after restore, much like
USB_QUIRK_RESET_RESUME does for USB devices that exhibit a similar
problem.

Tested on XMM7360 in HP EliteBook 855 G7 both with s2idle (which uses
the existing suspend / resume handlers) and S4 (which uses the new code).

[1]: https://lore.kernel.org/all/c248f0b4-2114-4c61-905f-466a786bdebb@leemhuis.info/
[2]:
https://github.com/xmm7360/xmm7360-pci/issues/211#issuecomment-1804139413

Reviewed-by: Sergey Ryazanov <ryazanov.s.a@gmail.com>
Signed-off-by: Maciej S. Szmigiero <mail@maciej.szmigiero.name>
Link: https://patch.msgid.link/e60287ebdb0ab54c4075071b72568a40a75d0205.1736372610.git.mail@maciej.szmigiero.name
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/wwan/iosm/iosm_ipc_pcie.c

index 04517bd3325a2abb3871f640aab0666d53467352..a066977af0be5c8abb96d0220c65eaf0293a05b8 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/acpi.h>
 #include <linux/bitfield.h>
 #include <linux/module.h>
+#include <linux/suspend.h>
 #include <net/rtnetlink.h>
 
 #include "iosm_ipc_imem.h"
@@ -18,6 +19,7 @@ MODULE_LICENSE("GPL v2");
 /* WWAN GUID */
 static guid_t wwan_acpi_guid = GUID_INIT(0xbad01b75, 0x22a8, 0x4f48, 0x87, 0x92,
                                       0xbd, 0xde, 0x94, 0x67, 0x74, 0x7d);
+static bool pci_registered;
 
 static void ipc_pcie_resources_release(struct iosm_pcie *ipc_pcie)
 {
@@ -448,7 +450,6 @@ static struct pci_driver iosm_ipc_driver = {
        },
        .id_table = iosm_ipc_ids,
 };
-module_pci_driver(iosm_ipc_driver);
 
 int ipc_pcie_addr_map(struct iosm_pcie *ipc_pcie, unsigned char *data,
                      size_t size, dma_addr_t *mapping, int direction)
@@ -530,3 +531,56 @@ void ipc_pcie_kfree_skb(struct iosm_pcie *ipc_pcie, struct sk_buff *skb)
        IPC_CB(skb)->mapping = 0;
        dev_kfree_skb(skb);
 }
+
+static int pm_notify(struct notifier_block *nb, unsigned long mode, void *_unused)
+{
+       if (mode == PM_HIBERNATION_PREPARE || mode == PM_RESTORE_PREPARE) {
+               if (pci_registered) {
+                       pci_unregister_driver(&iosm_ipc_driver);
+                       pci_registered = false;
+               }
+       } else if (mode == PM_POST_HIBERNATION || mode == PM_POST_RESTORE) {
+               if (!pci_registered) {
+                       int ret;
+
+                       ret = pci_register_driver(&iosm_ipc_driver);
+                       if (ret) {
+                               pr_err(KBUILD_MODNAME ": unable to re-register PCI driver: %d\n",
+                                      ret);
+                       } else {
+                               pci_registered = true;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+static struct notifier_block pm_notifier = {
+       .notifier_call = pm_notify,
+};
+
+static int __init iosm_ipc_driver_init(void)
+{
+       int ret;
+
+       ret = pci_register_driver(&iosm_ipc_driver);
+       if (ret)
+               return ret;
+
+       pci_registered = true;
+
+       register_pm_notifier(&pm_notifier);
+
+       return 0;
+}
+module_init(iosm_ipc_driver_init);
+
+static void __exit iosm_ipc_driver_exit(void)
+{
+       unregister_pm_notifier(&pm_notifier);
+
+       if (pci_registered)
+               pci_unregister_driver(&iosm_ipc_driver);
+}
+module_exit(iosm_ipc_driver_exit);