xref: /illumos-gate/usr/src/uts/common/cpr/cpr_driver.c (revision 2d6eb4a5e0a47d30189497241345dc5466bb68ab)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * CPR driver support routines
28  */
29 
30 #include <sys/types.h>
31 #include <sys/errno.h>
32 #include <sys/kmem.h>
33 #include <sys/systm.h>
34 #include <sys/sunddi.h>
35 #include <sys/ddi_impldefs.h>
36 #include <sys/epm.h>
37 #include <sys/cpr.h>
38 
39 #define	CPR_BUFSIZE	128
40 
41 extern int devi_detach(dev_info_t *, int);
42 extern int devi_attach(dev_info_t *, int);
43 
44 static char 	*devi_string(dev_info_t *, char *);
45 static int	cpr_is_real_device(dev_info_t *);
46 /*
47  * Xen uses this code to suspend _all_ drivers quickly and easily.
48  * Suspend and Resume uses it for the same reason, but also has
49  * to contend with some platform specific code that Xen does not.
50  * it is also used as a test entry point for developers/testers to
51  * execute code without going through a complete suspend.  So additions
52  * that have platform implications shall need #if[n]def's.
53  */
54 #ifndef __xpv
55 extern void	i_cpr_save_configuration(dev_info_t *);
56 extern void	i_cpr_restore_configuration(dev_info_t *);
57 #endif
58 
59 /*
60  * Traverse the dev info tree:
61  *	Call each device driver in the system via a special case
62  *	of the detach() entry point to quiesce itself.
63  *	Suspend children first.
64  *
65  * We only suspend/resume real devices.
66  */
67 
68 int
cpr_suspend_devices(dev_info_t * dip)69 cpr_suspend_devices(dev_info_t *dip)
70 {
71 	int		error;
72 	char		buf[CPR_BUFSIZE];
73 
74 	for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
75 		if (cpr_suspend_devices(ddi_get_child(dip)))
76 			return (ENXIO);
77 		if (!cpr_is_real_device(dip))
78 			continue;
79 		CPR_DEBUG(CPR_DEBUG2, "Suspending device %s\n",
80 		    devi_string(dip, buf));
81 		ASSERT((DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED) == 0);
82 
83 #ifndef __xpv
84 		i_cpr_save_configuration(dip);
85 #endif
86 
87 
88 		if (!i_ddi_devi_attached(dip)) {
89 			error = DDI_FAILURE;
90 		} else {
91 #ifndef __xpv
92 			if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
93 			    (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
94 			    cpr_device == ddi_driver_major(dip))) {
95 #endif
96 				error = devi_detach(dip, DDI_SUSPEND);
97 #ifndef __xpv
98 			} else {
99 				error = DDI_SUCCESS;
100 			}
101 #endif
102 		}
103 
104 		if (error == DDI_SUCCESS) {
105 			DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
106 		}
107 
108 		else {
109 			CPR_DEBUG(CPR_DEBUG2,
110 			    "WARNING: Unable to suspend device %s\n",
111 			    devi_string(dip, buf));
112 			cpr_err(CE_WARN, "Unable to suspend device %s.",
113 			    devi_string(dip, buf));
114 			cpr_err(CE_WARN, "Device is busy or does not "
115 			    "support suspend/resume.");
116 #ifndef __xpv
117 			/*
118 			 * the device has failed to suspend however,
119 			 * if cpr_test_point == FORCE_SUSPEND_TO_RAM
120 			 * after putting out the warning message above,
121 			 * we carry on as if suspending the device had
122 			 * been successful
123 			 */
124 			if (cpr_test_point == FORCE_SUSPEND_TO_RAM)
125 				DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
126 			else
127 #endif
128 				return (ENXIO);
129 		}
130 	}
131 	return (0);
132 }
133 
134 /*
135  * Traverse the dev info tree:
136  *	Call each device driver in the system via a special case
137  *	of the attach() entry point to restore itself.
138  *	This is a little tricky because it has to reverse the traversal
139  *	order of cpr_suspend_devices().
140  */
141 int
cpr_resume_devices(dev_info_t * start,int resume_failed)142 cpr_resume_devices(dev_info_t *start, int resume_failed)
143 {
144 	dev_info_t	*dip, *next, *last = NULL;
145 	int		did_suspend;
146 	int		error = resume_failed;
147 	char		buf[CPR_BUFSIZE];
148 
149 	while (last != start) {
150 		dip = start;
151 		next = ddi_get_next_sibling(dip);
152 		while (next != last) {
153 			dip = next;
154 			next = ddi_get_next_sibling(dip);
155 		}
156 
157 		/*
158 		 * cpr is the only one that uses this field and the device
159 		 * itself hasn't resumed yet, there is no need to use a
160 		 * lock, even though kernel threads are active by now.
161 		 */
162 		did_suspend = DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED;
163 		if (did_suspend)
164 			DEVI(dip)->devi_cpr_flags &= ~DCF_CPR_SUSPENDED;
165 
166 		/*
167 		 * Always attempt to restore device configuration before
168 		 * attempting resume
169 		 */
170 #ifndef __xpv
171 		i_cpr_restore_configuration(dip);
172 #endif
173 
174 		/*
175 		 * There may be background attaches happening on devices
176 		 * that were not originally suspended by cpr, so resume
177 		 * only devices that were suspended by cpr. Also, stop
178 		 * resuming after the first resume failure, but traverse
179 		 * the entire tree to clear the suspend flag unless the
180 		 * FORCE_SUSPEND_TO_RAM test point is set.
181 		 */
182 #ifndef __xpv
183 		if (did_suspend && (!error ||
184 		    cpr_test_point == FORCE_SUSPEND_TO_RAM)) {
185 #else
186 		if (did_suspend && !error) {
187 #endif
188 			CPR_DEBUG(CPR_DEBUG2, "Resuming device %s\n",
189 			    devi_string(dip, buf));
190 			/*
191 			 * If a device suspended by cpr gets detached during
192 			 * the resume process (for example, due to hotplugging)
193 			 * before cpr gets around to issuing it a DDI_RESUME,
194 			 * we'll have problems.
195 			 */
196 			if (!i_ddi_devi_attached(dip)) {
197 				CPR_DEBUG(CPR_DEBUG2, "WARNING: Skipping "
198 				    "%s, device not ready for resume\n",
199 				    devi_string(dip, buf));
200 				cpr_err(CE_WARN, "Skipping %s, device "
201 				    "not ready for resume",
202 				    devi_string(dip, buf));
203 #ifndef __xpv
204 			} else if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
205 			    (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
206 			    cpr_device == ddi_driver_major(dip))) {
207 #else
208 			} else {
209 #endif
210 				if (devi_attach(dip, DDI_RESUME) !=
211 				    DDI_SUCCESS) {
212 					error = ENXIO;
213 				}
214 			}
215 		}
216 
217 		if (error == ENXIO) {
218 			CPR_DEBUG(CPR_DEBUG2,
219 			    "WARNING: Unable to resume device %s\n",
220 			    devi_string(dip, buf));
221 			cpr_err(CE_WARN, "Unable to resume device %s",
222 			    devi_string(dip, buf));
223 		}
224 
225 		error = cpr_resume_devices(ddi_get_child(dip), error);
226 		last = dip;
227 	}
228 
229 	return (error);
230 }
231 
232 /*
233  * Returns a string which contains device name and address.
234  */
235 static char *
236 devi_string(dev_info_t *devi, char *buf)
237 {
238 	char *name;
239 	char *address;
240 	int size;
241 
242 	name = ddi_node_name(devi);
243 	address = ddi_get_name_addr(devi);
244 	size = (name == NULL) ? strlen("<null name>") : strlen(name);
245 	size += (address == NULL) ? strlen("<null>") : strlen(address);
246 
247 	/*
248 	 * Make sure that we don't over-run the buffer.
249 	 * There are 2 additional characters in the string.
250 	 */
251 	ASSERT((size + 2) <= CPR_BUFSIZE);
252 
253 	if (name == NULL)
254 		(void) strcpy(buf, "<null name>");
255 	else
256 		(void) strcpy(buf, name);
257 
258 	(void) strcat(buf, "@");
259 	if (address == NULL)
260 		(void) strcat(buf, "<null>");
261 	else
262 		(void) strcat(buf, address);
263 
264 	return (buf);
265 }
266 
267 /*
268  * This function determines whether the given device is real (and should
269  * be suspended) or not (pseudo like).  If the device has a "reg" property
270  * then it is presumed to have register state to save/restore.
271  */
272 static int
273 cpr_is_real_device(dev_info_t *dip)
274 {
275 	struct regspec *regbuf;
276 	int length;
277 	int rc;
278 
279 	if (ddi_get_driver(dip) == NULL)
280 		return (0);
281 
282 	/*
283 	 * First those devices for which special arrangements have been made
284 	 */
285 	if (DEVI(dip)->devi_pm_flags & (PMC_NEEDS_SR|PMC_PARENTAL_SR))
286 		return (1);
287 	if (DEVI(dip)->devi_pm_flags & PMC_NO_SR)
288 		return (0);
289 
290 	/*
291 	 * now the general case
292 	 */
293 	rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg",
294 	    (caddr_t)&regbuf, &length);
295 	ASSERT(rc != DDI_PROP_NO_MEMORY);
296 	if (rc != DDI_PROP_SUCCESS) {
297 		return (0);
298 	} else {
299 		kmem_free((caddr_t)regbuf, length);
300 		return (1);
301 	}
302 }
303