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