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