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