xref: /freebsd/sys/compat/linuxkpi/common/src/linuxkpi_80211_pm.c (revision 5f58d9207469dcd2d3fa24db7f5276a681df91e5)
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