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
43extern int devi_detach(dev_info_t *, int);
44extern int devi_attach(dev_info_t *, int);
45
46static char 	*devi_string(dev_info_t *, char *);
47static 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
57extern void	i_cpr_save_configuration(dev_info_t *);
58extern 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
70int
71cpr_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 */
143int
144cpr_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 */
237static char *
238devi_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 */
274static int
275cpr_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