xref: /illumos-gate/usr/src/uts/common/cpr/cpr_driver.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
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 /*
50  * Traverse the dev info tree:
51  *	Call each device driver in the system via a special case
52  *	of the detach() entry point to quiesce itself.
53  *	Suspend children first.
54  *
55  * We only suspend/resume real devices.
56  */
57 
58 int
59 cpr_suspend_devices(dev_info_t *dip)
60 {
61 	int		error;
62 	char		buf[CPR_BUFSIZE];
63 
64 	for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
65 		if (cpr_suspend_devices(ddi_get_child(dip)))
66 			return (ENXIO);
67 		if (!cpr_is_real_device(dip))
68 			continue;
69 		CPR_DEBUG(CPR_DEBUG2, "Suspending device %s\n",
70 		    devi_string(dip, buf));
71 		ASSERT((DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED) == 0);
72 
73 		if (!i_ddi_devi_attached(dip))
74 			error = DDI_FAILURE;
75 		else
76 			error = devi_detach(dip, DDI_SUSPEND);
77 
78 		if (error == DDI_SUCCESS)
79 			DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
80 		else {
81 			CPR_DEBUG(CPR_DEBUG2,
82 			    "WARNING: Unable to suspend device %s\n",
83 			    devi_string(dip, buf));
84 			cpr_err(CE_WARN, "Unable to suspend device %s.",
85 				devi_string(dip, buf));
86 			cpr_err(CE_WARN, "Device is busy or does not "
87 				"support suspend/resume.");
88 			return (ENXIO);
89 		}
90 	}
91 	return (0);
92 }
93 
94 /*
95  * Traverse the dev info tree:
96  *	Call each device driver in the system via a special case
97  *	of the attach() entry point to restore itself.
98  *	This is a little tricky because it has to reverse the traversal
99  *	order of cpr_suspend_devices().
100  */
101 int
102 cpr_resume_devices(dev_info_t *start, int resume_failed)
103 {
104 	dev_info_t	*dip, *next, *last = NULL;
105 	int		did_suspend;
106 	int		error = resume_failed;
107 	char		buf[CPR_BUFSIZE];
108 
109 	while (last != start) {
110 		dip = start;
111 		next = ddi_get_next_sibling(dip);
112 		while (next != last) {
113 			dip = next;
114 			next = ddi_get_next_sibling(dip);
115 		}
116 
117 		/*
118 		 * cpr is the only one that uses this field and the device
119 		 * itself hasn't resumed yet, there is no need to use a
120 		 * lock, even though kernel threads are active by now.
121 		 */
122 		did_suspend = DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED;
123 		if (did_suspend)
124 			DEVI(dip)->devi_cpr_flags &= ~DCF_CPR_SUSPENDED;
125 
126 		/*
127 		 * There may be background attaches happening on devices
128 		 * that were not originally suspended by cpr, so resume
129 		 * only devices that were suspended by cpr. Also, stop
130 		 * resuming after the first resume failure, but traverse
131 		 * the entire tree to clear the suspend flag.
132 		 */
133 		if (did_suspend && !error) {
134 			CPR_DEBUG(CPR_DEBUG2, "Resuming device %s\n",
135 			    devi_string(dip, buf));
136 			/*
137 			 * If a device suspended by cpr gets detached during
138 			 * the resume process (for example, due to hotplugging)
139 			 * before cpr gets around to issuing it a DDI_RESUME,
140 			 * we'll have problems.
141 			 */
142 			if (!i_ddi_devi_attached(dip)) {
143 				CPR_DEBUG(CPR_DEBUG2, "WARNING: Skipping "
144 				    "%s, device not ready for resume\n",
145 				    devi_string(dip, buf));
146 				cpr_err(CE_WARN, "Skipping %s, device "
147 				    "not ready for resume",
148 				    devi_string(dip, buf));
149 			} else if (devi_attach(dip, DDI_RESUME) !=
150 			    DDI_SUCCESS) {
151 				CPR_DEBUG(CPR_DEBUG2,
152 				    "WARNING: Unable to resume device %s\n",
153 				    devi_string(dip, buf));
154 				cpr_err(CE_WARN, "Unable to resume device %s",
155 				    devi_string(dip, buf));
156 				error = ENXIO;
157 			}
158 		}
159 
160 		error = cpr_resume_devices(ddi_get_child(dip), error);
161 		last = dip;
162 	}
163 
164 	return (error);
165 }
166 
167 /*
168  * Returns a string which contains device name and address.
169  */
170 static char *
171 devi_string(dev_info_t *devi, char *buf)
172 {
173 	char *name;
174 	char *address;
175 	int size;
176 
177 	name = ddi_node_name(devi);
178 	address = ddi_get_name_addr(devi);
179 	size = (name == NULL) ?
180 		strlen("<null name>") : strlen(name);
181 	size += (address == NULL) ?
182 		strlen("<null>") : strlen(address);
183 
184 	/*
185 	 * Make sure that we don't over-run the buffer.
186 	 * There are 2 additional characters in the string.
187 	 */
188 	ASSERT((size + 2) <= CPR_BUFSIZE);
189 
190 	if (name == NULL)
191 		(void) strcpy(buf, "<null name>");
192 	else
193 		(void) strcpy(buf, name);
194 
195 	(void) strcat(buf, "@");
196 	if (address == NULL)
197 		(void) strcat(buf, "<null>");
198 	else
199 		(void) strcat(buf, address);
200 
201 	return (buf);
202 }
203 
204 /*
205  * This function determines whether the given device is real (and should
206  * be suspended) or not (pseudo like).  If the device has a "reg" property
207  * then it is presumed to have register state to save/restore.
208  */
209 static int
210 cpr_is_real_device(dev_info_t *dip)
211 {
212 	struct regspec *regbuf;
213 	int length;
214 	int rc;
215 
216 	if (ddi_get_driver(dip) == NULL)
217 		return (0);
218 
219 	/*
220 	 * First those devices for which special arrangements have been made
221 	 */
222 	if (DEVI(dip)->devi_pm_flags & (PMC_NEEDS_SR|PMC_PARENTAL_SR))
223 		return (1);
224 	if (DEVI(dip)->devi_pm_flags & PMC_NO_SR)
225 		return (0);
226 
227 	/*
228 	 * now the general case
229 	 */
230 	rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg",
231 	    (caddr_t)&regbuf, &length);
232 	ASSERT(rc != DDI_PROP_NO_MEMORY);
233 	if (rc != DDI_PROP_SUCCESS) {
234 		return (0);
235 	} else {
236 		kmem_free((caddr_t)regbuf, length);
237 		return (1);
238 	}
239 }
240 
241 /*
242  * Power down the system.
243  */
244 void
245 cpr_power_down(void)
246 {
247 #if defined(__sparc)
248 	/*
249 	 * XXX	This platform firmware implementation dependency
250 	 *	doesn't belong in common code!
251 	 */
252 	int is_defined = 0;
253 	char *wordexists = "p\" power-off\" find nip swap l! ";
254 	char *req = "power-off";
255 
256 	/*
257 	 * is_defined has value -1 when defined
258 	 */
259 	prom_interpret(wordexists, (uintptr_t)&is_defined, 0, 0, 0, 0);
260 	if (is_defined) {
261 		CPR_DEBUG(CPR_DEBUG1, "\ncpr: %s...\n", req);
262 		prom_interpret(req, 0, 0, 0, 0, 0);
263 	}
264 #endif
265 }
266