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