xref: /illumos-gate/usr/src/uts/common/cpr/cpr_driver.c (revision 7c478bd9)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * CPR driver support routines
31  */
32 
33 #include <sys/types.h>
34 #include <sys/errno.h>
35 #include <sys/kmem.h>
36 #include <sys/systm.h>
37 #include <sys/sunddi.h>
38 #include <sys/ddi_impldefs.h>
39 #include <sys/epm.h>
40 #include <sys/cpr.h>
41 
42 #define	CPR_BUFSIZE	128
43 
44 extern int devi_detach(dev_info_t *, int);
45 extern int devi_attach(dev_info_t *, int);
46 
47 static char 	*devi_string(dev_info_t *, char *);
48 static int	cpr_is_real_device(dev_info_t *);
49 
50 /*
51  * Traverse the dev info tree:
52  *	Call each device driver in the system via a special case
53  *	of the detach() entry point to quiesce itself.
54  *	Suspend children first.
55  *
56  * We only suspend/resume real devices.
57  */
58 
59 int
60 cpr_suspend_devices(dev_info_t *dip)
61 {
62 	int		error;
63 	char		buf[CPR_BUFSIZE];
64 
65 	for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
66 		if (cpr_suspend_devices(ddi_get_child(dip)))
67 			return (ENXIO);
68 		if (!cpr_is_real_device(dip))
69 			continue;
70 		DEBUG2(errp("Suspending device %s\n", devi_string(dip, buf)));
71 		ASSERT((DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED) == 0);
72 
73 		if (i_ddi_node_state(dip) != DS_READY)
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 			DEBUG2(errp("WARNING: Unable to suspend device %s\n",
82 				devi_string(dip, buf)));
83 			cpr_err(CE_WARN, "Unable to suspend device %s.",
84 				devi_string(dip, buf));
85 			cpr_err(CE_WARN, "Device is busy or does not "
86 				"support suspend/resume.");
87 			return (ENXIO);
88 		}
89 	}
90 	return (0);
91 }
92 
93 /*
94  * Traverse the dev info tree:
95  *	Call each device driver in the system via a special case
96  *	of the attach() entry point to restore itself.
97  *	This is a little tricky because it has to reverse the traversal
98  *	order of cpr_suspend_devices().
99  */
100 int
101 cpr_resume_devices(dev_info_t *start, int resume_failed)
102 {
103 	dev_info_t	*dip, *next, *last = NULL;
104 	int		did_suspend;
105 	int		error = resume_failed;
106 	char		buf[CPR_BUFSIZE];
107 
108 	while (last != start) {
109 		dip = start;
110 		next = ddi_get_next_sibling(dip);
111 		while (next != last) {
112 			dip = next;
113 			next = ddi_get_next_sibling(dip);
114 		}
115 
116 		/*
117 		 * cpr is the only one that uses this field and the device
118 		 * itself hasn't resumed yet, there is no need to use a
119 		 * lock, even though kernel threads are active by now.
120 		 */
121 		did_suspend = DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED;
122 		if (did_suspend)
123 			DEVI(dip)->devi_cpr_flags &= ~DCF_CPR_SUSPENDED;
124 
125 		/*
126 		 * There may be background attaches happening on devices
127 		 * that were not originally suspended by cpr, so resume
128 		 * only devices that were suspended by cpr. Also, stop
129 		 * resuming after the first resume failure, but traverse
130 		 * the entire tree to clear the suspend flag.
131 		 */
132 		if (did_suspend && !error) {
133 			DEBUG2(errp("Resuming device %s\n",
134 			    devi_string(dip, buf)));
135 			/*
136 			 * If a device suspended by cpr gets detached during
137 			 * the resume process (for example, due to hotplugging)
138 			 * before cpr gets around to issuing it a DDI_RESUME,
139 			 * we'll have problems.
140 			 */
141 			if (i_ddi_node_state(dip) != DS_READY) {
142 				DEBUG2(errp("WARNING: Skipping %s, device "
143 				    "not ready for resume\n",
144 				    devi_string(dip, buf)));
145 				cpr_err(CE_WARN, "Skipping %s, device "
146 				    "not ready for resume",
147 				    devi_string(dip, buf));
148 			} else if (devi_attach(dip, DDI_RESUME) !=
149 			    DDI_SUCCESS) {
150 				DEBUG2(errp(
151 				    "WARNING: Unable to resume device %s\n",
152 				    devi_string(dip, buf)));
153 				cpr_err(CE_WARN, "Unable to resume device %s",
154 				    devi_string(dip, buf));
155 				error = ENXIO;
156 			}
157 		}
158 
159 		error = cpr_resume_devices(ddi_get_child(dip), error);
160 		last = dip;
161 	}
162 
163 	return (error);
164 }
165 
166 /*
167  * Returns a string which contains device name and address.
168  */
169 static char *
170 devi_string(dev_info_t *devi, char *buf)
171 {
172 	char *name;
173 	char *address;
174 	int size;
175 
176 	name = ddi_node_name(devi);
177 	address = ddi_get_name_addr(devi);
178 	size = (name == NULL) ?
179 		strlen("<null name>") : strlen(name);
180 	size += (address == NULL) ?
181 		strlen("<null>") : strlen(address);
182 
183 	/*
184 	 * Make sure that we don't over-run the buffer.
185 	 * There are 2 additional characters in the string.
186 	 */
187 	ASSERT((size + 2) <= CPR_BUFSIZE);
188 
189 	if (name == NULL)
190 		(void) strcpy(buf, "<null name>");
191 	else
192 		(void) strcpy(buf, name);
193 
194 	(void) strcat(buf, "@");
195 	if (address == NULL)
196 		(void) strcat(buf, "<null>");
197 	else
198 		(void) strcat(buf, address);
199 
200 	return (buf);
201 }
202 
203 /*
204  * This function determines whether the given device is real (and should
205  * be suspended) or not (pseudo like).  If the device has a "reg" property
206  * then it is presumed to have register state to save/restore.
207  */
208 static int
209 cpr_is_real_device(dev_info_t *dip)
210 {
211 	struct regspec *regbuf;
212 	int length;
213 	int rc;
214 
215 	if (ddi_get_driver(dip) == NULL)
216 		return (0);
217 
218 	/*
219 	 * First those devices for which special arrangements have been made
220 	 */
221 	if (DEVI(dip)->devi_pm_flags & (PMC_NEEDS_SR|PMC_PARENTAL_SR))
222 		return (1);
223 	if (DEVI(dip)->devi_pm_flags & PMC_NO_SR)
224 		return (0);
225 
226 	/*
227 	 * now the general case
228 	 */
229 	rc = ddi_getlongprop(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, "reg",
230 	    (caddr_t)&regbuf, &length);
231 	ASSERT(rc != DDI_PROP_NO_MEMORY);
232 	if (rc != DDI_PROP_SUCCESS) {
233 		return (0);
234 	} else {
235 		kmem_free((caddr_t)regbuf, length);
236 		return (1);
237 	}
238 }
239 
240 /*
241  * Power down the system.
242  */
243 void
244 cpr_power_down(void)
245 {
246 	int is_defined = 0;
247 	char *wordexists = "p\" power-off\" find nip swap l! ";
248 	char *req = "power-off";
249 
250 	/*
251 	 * is_defined has value -1 when defined
252 	 */
253 	prom_interpret(wordexists, (uintptr_t)&is_defined, 0, 0, 0, 0);
254 	if (is_defined) {
255 		DEBUG1(errp("\ncpr: %s...\n", req));
256 		prom_interpret(req, 0, 0, 0, 0, 0);
257 	}
258 }
259