1 /* 2 * Copyright (c) 2025 The FreeBSD Foundation 3 * 4 * This software was developed by Björn Zeeb under sponsorship from 5 * the FreeBSD Foundation. 6 * 7 * SPDX-License-Identifier: BSD-2-Clause 8 */ 9 10 #include <sys/param.h> 11 #include <sys/kernel.h> 12 #include <sys/bus.h> 13 #include <sys/module.h> 14 15 #include <linux/pci.h> 16 #include <dev/pci/pcivar.h> 17 #include "linux_80211.h" 18 19 #include <net80211/ieee80211_var.h> 20 21 struct lkpi_80211_pm_softc { 22 /* PCI */ 23 int (*suspend) (struct pci_dev *pdev, pm_message_t state); 24 int (*resume) (struct pci_dev *pdev); 25 }; 26 27 static int 28 lkpi_80211_pm_suspend(struct pci_dev *pdev, pm_message_t state) 29 { 30 const struct dev_pm_ops *pmops; 31 struct lkpi_80211_pm_softc *sc; 32 struct ieee80211com *ic; 33 device_t dev; 34 int error; 35 36 dev = device_find_child(pdev->dev.bsddev, "lkpi80211_pm", 37 DEVICE_UNIT_ANY); 38 if (dev == NULL) { 39 /* Must not happen, so abort suspend if it does. */ 40 device_printf(pdev->dev.bsddev, 41 "%s: cannot find lkpi80211_pm child for %s\n", 42 __func__, device_get_name(pdev->dev.bsddev)); 43 return (ENXIO); 44 } 45 sc = device_get_softc(dev); 46 error = 0; 47 48 /* Call order: wireless then pdev. */ 49 50 ic = ieee80211_find_com(device_get_nameunit(pdev->dev.bsddev)); 51 if (ic != NULL) { 52 error = lkpi_80211_suspend(ic, state); 53 } else { 54 device_printf(pdev->dev.bsddev, 55 "%s: WARNING: wireless device not found\n", __func__); 56 } 57 if (error != 0) 58 goto err; 59 60 /* Logic duplicated from linux_pci_suspend(). */ 61 pmops = pdev->pdrv->driver.pm; 62 if (sc->suspend != NULL) 63 error = sc->suspend(pdev, state); 64 else if (pmops != NULL && pmops->suspend != NULL) { 65 error = -pmops->suspend(&pdev->dev); 66 if (error == 0 && pmops->suspend_late != NULL) 67 error = -pmops->suspend_late(&pdev->dev); 68 if (error == 0 && pmops->suspend_noirq != NULL) 69 error = -pmops->suspend_noirq(&pdev->dev); 70 } 71 72 err: 73 if (error < 0) 74 error = -error; 75 76 if (error != 0) 77 device_printf(pdev->dev.bsddev, 78 "%s: WARNING: SUSPEND FAILED: %d\n", __func__, error); 79 80 return (error); 81 } 82 83 static int 84 lkpi_80211_pm_resume(struct pci_dev *pdev) 85 { 86 const struct dev_pm_ops *pmops; 87 struct lkpi_80211_pm_softc *sc; 88 struct ieee80211com *ic; 89 device_t dev; 90 int error; 91 92 dev = device_find_child(pdev->dev.bsddev, "lkpi80211_pm", 93 DEVICE_UNIT_ANY); 94 if (dev == NULL) { 95 /* Must not happen, so abort suspend if it does. */ 96 device_printf(pdev->dev.bsddev, 97 "%s: cannot find lkpi80211_pm child\n", __func__); 98 return (ENXIO); 99 } 100 sc = device_get_softc(dev); 101 error = 0; 102 103 /* Call order: pdev then wireless. */ 104 105 /* Logic duplicated from linux_pci_resume(). */ 106 pmops = pdev->pdrv->driver.pm; 107 if (sc->resume != NULL) { 108 error = sc->resume(pdev); 109 } else if (pmops != NULL && pmops->resume != NULL) { 110 if (pmops->resume_early != NULL) 111 error = -pmops->resume_early(&pdev->dev); 112 if (error == 0 && pmops->resume != NULL) 113 error = -pmops->resume(&pdev->dev); 114 } 115 if (error != 0) 116 device_printf(pdev->dev.bsddev, "%s: resume failed!\n", __func__); 117 /* Do not error out but give wireless also a chance. */ 118 119 ic = ieee80211_find_com(device_get_nameunit(pdev->dev.bsddev)); 120 if (ic != NULL) { 121 error = lkpi_80211_resume(ic); 122 } else { 123 device_printf(pdev->dev.bsddev, 124 "%s: WARNING: wireless device not found\n", __func__); 125 } 126 127 if (error < 0) 128 error = -error; 129 130 return (error); 131 } 132 133 /* -------------------------------------------------------------------------- */ 134 static void 135 lkpi_80211_pm_identify(driver_t *driver, device_t parent) 136 { 137 138 /* Make sure we're not being doubly invoked per parent. */ 139 if (device_find_child(parent, driver->name, DEVICE_UNIT_ANY) != NULL) 140 return; 141 142 /* Make sure this is PCI for now. */ 143 if (!is_pci_device(parent)) 144 return; 145 146 if (BUS_ADD_CHILD(parent, 0, driver->name, DEVICE_UNIT_ANY) == NULL) 147 device_printf(parent, "%s: failed to add child\n", __func__); 148 } 149 150 static int 151 lkpi_80211_pm_probe(device_t dev) 152 { 153 device_set_descf(dev, "LinuxKPI 802.11 %s mac80211 PM", 154 device_get_nameunit(device_get_parent(dev))); 155 return (BUS_PROBE_DEFAULT); 156 } 157 158 static int 159 lkpi_80211_pm_attach(device_t dev) 160 { 161 struct lkpi_80211_pm_softc *sc; 162 struct pci_dev *pdev; 163 164 sc = device_get_softc(dev); 165 pdev = device_get_softc(device_get_parent(dev)); 166 167 /* Intercept the driver suspend/resume calls. */ 168 sc->suspend = pdev->pdrv->suspend; 169 pdev->pdrv->suspend = lkpi_80211_pm_suspend; 170 sc->resume = pdev->pdrv->resume; 171 pdev->pdrv->resume = lkpi_80211_pm_resume; 172 173 return (0); 174 } 175 176 static int 177 lkpi_80211_pm_detach(device_t dev) 178 { 179 struct lkpi_80211_pm_softc *sc; 180 struct pci_dev *pdev; 181 182 sc = device_get_softc(dev); 183 pdev = device_get_softc(device_get_parent(dev)); 184 185 /* Restore the original notifications. */ 186 pdev->pdrv->suspend = sc->suspend; 187 pdev->pdrv->resume = sc->resume; 188 189 return (0); 190 } 191 192 static device_method_t lkpi_80211_pm_methods[] = { 193 /* Device interface */ 194 DEVMETHOD(device_identify, lkpi_80211_pm_identify), 195 DEVMETHOD(device_probe, lkpi_80211_pm_probe), 196 DEVMETHOD(device_attach, lkpi_80211_pm_attach), 197 DEVMETHOD(device_detach, lkpi_80211_pm_detach), 198 /* 199 * Do not think about device_suspend/resume here. 200 * We are not a PCI device and LinuxKPI PCI linux_pci_suspend/resume 201 * are getting the notifications so we have to hijack the 202 * LinuxKPI upcalls. 203 */ 204 205 DEVMETHOD_END 206 }; 207 208 driver_t lkpi_80211_pm_driver = { 209 "lkpi80211_pm", 210 lkpi_80211_pm_methods, 211 sizeof(struct lkpi_80211_pm_softc), 212 }; 213 214 MODULE_DEPEND(lkpi80211_pm, linuxkpi_wlan, 1, 1, 1); 215 MODULE_VERSION(lkpi80211_pm, 1); 216