xref: /titanic_44/usr/src/uts/common/cpr/cpr_driver.c (revision 2df1fe9ca32bb227b9158c67f5c00b54c20b10fd)
17c478bd9Sstevel@tonic-gate /*
27c478bd9Sstevel@tonic-gate  * CDDL HEADER START
37c478bd9Sstevel@tonic-gate  *
47c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5ae115bc7Smrj  * Common Development and Distribution License (the "License").
6ae115bc7Smrj  * You may not use this file except in compliance with the License.
77c478bd9Sstevel@tonic-gate  *
87c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
97c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
107c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
117c478bd9Sstevel@tonic-gate  * and limitations under the License.
127c478bd9Sstevel@tonic-gate  *
137c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
147c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
157c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
167c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
177c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
187c478bd9Sstevel@tonic-gate  *
197c478bd9Sstevel@tonic-gate  * CDDL HEADER END
207c478bd9Sstevel@tonic-gate  */
217c478bd9Sstevel@tonic-gate /*
22ae115bc7Smrj  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
237c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
247c478bd9Sstevel@tonic-gate  */
257c478bd9Sstevel@tonic-gate 
267c478bd9Sstevel@tonic-gate #pragma ident	"%Z%%M%	%I%	%E% SMI"
277c478bd9Sstevel@tonic-gate 
287c478bd9Sstevel@tonic-gate /*
297c478bd9Sstevel@tonic-gate  * CPR driver support routines
307c478bd9Sstevel@tonic-gate  */
317c478bd9Sstevel@tonic-gate 
327c478bd9Sstevel@tonic-gate #include <sys/types.h>
337c478bd9Sstevel@tonic-gate #include <sys/errno.h>
347c478bd9Sstevel@tonic-gate #include <sys/kmem.h>
357c478bd9Sstevel@tonic-gate #include <sys/systm.h>
367c478bd9Sstevel@tonic-gate #include <sys/sunddi.h>
377c478bd9Sstevel@tonic-gate #include <sys/ddi_impldefs.h>
387c478bd9Sstevel@tonic-gate #include <sys/epm.h>
397c478bd9Sstevel@tonic-gate #include <sys/cpr.h>
407c478bd9Sstevel@tonic-gate 
417c478bd9Sstevel@tonic-gate #define	CPR_BUFSIZE	128
427c478bd9Sstevel@tonic-gate 
437c478bd9Sstevel@tonic-gate extern int devi_detach(dev_info_t *, int);
447c478bd9Sstevel@tonic-gate extern int devi_attach(dev_info_t *, int);
457c478bd9Sstevel@tonic-gate 
467c478bd9Sstevel@tonic-gate static char 	*devi_string(dev_info_t *, char *);
477c478bd9Sstevel@tonic-gate static int	cpr_is_real_device(dev_info_t *);
48*2df1fe9cSrandyf /*
49*2df1fe9cSrandyf  * Xen uses this code to suspend _all_ drivers quickly and easily.
50*2df1fe9cSrandyf  * Suspend and Resume uses it for the same reason, but also has
51*2df1fe9cSrandyf  * to contend with some platform specific code that Xen does not.
52*2df1fe9cSrandyf  * it is also used as a test entry point for developers/testers to
53*2df1fe9cSrandyf  * execute code without going through a complete suspend.  So additions
54*2df1fe9cSrandyf  * that have platform implications shall need #if[n]def's.
55*2df1fe9cSrandyf  */
56*2df1fe9cSrandyf #ifndef __xpv
57*2df1fe9cSrandyf extern void	i_cpr_save_configuration(dev_info_t *);
58*2df1fe9cSrandyf extern void	i_cpr_restore_configuration(dev_info_t *);
59*2df1fe9cSrandyf #endif
607c478bd9Sstevel@tonic-gate 
617c478bd9Sstevel@tonic-gate /*
627c478bd9Sstevel@tonic-gate  * Traverse the dev info tree:
637c478bd9Sstevel@tonic-gate  *	Call each device driver in the system via a special case
647c478bd9Sstevel@tonic-gate  *	of the detach() entry point to quiesce itself.
657c478bd9Sstevel@tonic-gate  *	Suspend children first.
667c478bd9Sstevel@tonic-gate  *
677c478bd9Sstevel@tonic-gate  * We only suspend/resume real devices.
687c478bd9Sstevel@tonic-gate  */
697c478bd9Sstevel@tonic-gate 
707c478bd9Sstevel@tonic-gate int
cpr_suspend_devices(dev_info_t * dip)717c478bd9Sstevel@tonic-gate cpr_suspend_devices(dev_info_t *dip)
727c478bd9Sstevel@tonic-gate {
737c478bd9Sstevel@tonic-gate 	int		error;
747c478bd9Sstevel@tonic-gate 	char		buf[CPR_BUFSIZE];
757c478bd9Sstevel@tonic-gate 
767c478bd9Sstevel@tonic-gate 	for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
777c478bd9Sstevel@tonic-gate 		if (cpr_suspend_devices(ddi_get_child(dip)))
787c478bd9Sstevel@tonic-gate 			return (ENXIO);
797c478bd9Sstevel@tonic-gate 		if (!cpr_is_real_device(dip))
807c478bd9Sstevel@tonic-gate 			continue;
81ae115bc7Smrj 		CPR_DEBUG(CPR_DEBUG2, "Suspending device %s\n",
82ae115bc7Smrj 		    devi_string(dip, buf));
837c478bd9Sstevel@tonic-gate 		ASSERT((DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED) == 0);
847c478bd9Sstevel@tonic-gate 
85*2df1fe9cSrandyf #ifndef __xpv
86*2df1fe9cSrandyf 		i_cpr_save_configuration(dip);
87*2df1fe9cSrandyf #endif
887c478bd9Sstevel@tonic-gate 
89*2df1fe9cSrandyf 
90*2df1fe9cSrandyf 		if (!i_ddi_devi_attached(dip)) {
91*2df1fe9cSrandyf 			error = DDI_FAILURE;
92*2df1fe9cSrandyf 		} else {
93*2df1fe9cSrandyf #ifndef __xpv
94*2df1fe9cSrandyf 			if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
95*2df1fe9cSrandyf 			    (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
96*2df1fe9cSrandyf 			    cpr_device == ddi_driver_major(dip))) {
97*2df1fe9cSrandyf #endif
98*2df1fe9cSrandyf 				error = devi_detach(dip, DDI_SUSPEND);
99*2df1fe9cSrandyf #ifndef __xpv
100*2df1fe9cSrandyf 			} else {
101*2df1fe9cSrandyf 				error = DDI_SUCCESS;
102*2df1fe9cSrandyf 			}
103*2df1fe9cSrandyf #endif
104*2df1fe9cSrandyf 		}
105*2df1fe9cSrandyf 
106*2df1fe9cSrandyf 		if (error == DDI_SUCCESS) {
1077c478bd9Sstevel@tonic-gate 			DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
108*2df1fe9cSrandyf 		}
109*2df1fe9cSrandyf 
1107c478bd9Sstevel@tonic-gate 		else {
111ae115bc7Smrj 			CPR_DEBUG(CPR_DEBUG2,
112ae115bc7Smrj 			    "WARNING: Unable to suspend device %s\n",
113ae115bc7Smrj 			    devi_string(dip, buf));
1147c478bd9Sstevel@tonic-gate 			cpr_err(CE_WARN, "Unable to suspend device %s.",
1157c478bd9Sstevel@tonic-gate 			    devi_string(dip, buf));
1167c478bd9Sstevel@tonic-gate 			cpr_err(CE_WARN, "Device is busy or does not "
1177c478bd9Sstevel@tonic-gate 			    "support suspend/resume.");
118*2df1fe9cSrandyf #ifndef __xpv
119*2df1fe9cSrandyf 			/*
120*2df1fe9cSrandyf 			 * the device has failed to suspend however,
121*2df1fe9cSrandyf 			 * if cpr_test_point == FORCE_SUSPEND_TO_RAM
122*2df1fe9cSrandyf 			 * after putting out the warning message above,
123*2df1fe9cSrandyf 			 * we carry on as if suspending the device had
124*2df1fe9cSrandyf 			 * been successful
125*2df1fe9cSrandyf 			 */
126*2df1fe9cSrandyf 			if (cpr_test_point == FORCE_SUSPEND_TO_RAM)
127*2df1fe9cSrandyf 				DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
128*2df1fe9cSrandyf 			else
129*2df1fe9cSrandyf #endif
1307c478bd9Sstevel@tonic-gate 				return (ENXIO);
1317c478bd9Sstevel@tonic-gate 		}
1327c478bd9Sstevel@tonic-gate 	}
1337c478bd9Sstevel@tonic-gate 	return (0);
1347c478bd9Sstevel@tonic-gate }
1357c478bd9Sstevel@tonic-gate 
1367c478bd9Sstevel@tonic-gate /*
1377c478bd9Sstevel@tonic-gate  * Traverse the dev info tree:
1387c478bd9Sstevel@tonic-gate  *	Call each device driver in the system via a special case
1397c478bd9Sstevel@tonic-gate  *	of the attach() entry point to restore itself.
1407c478bd9Sstevel@tonic-gate  *	This is a little tricky because it has to reverse the traversal
1417c478bd9Sstevel@tonic-gate  *	order of cpr_suspend_devices().
1427c478bd9Sstevel@tonic-gate  */
1437c478bd9Sstevel@tonic-gate int
cpr_resume_devices(dev_info_t * start,int resume_failed)1447c478bd9Sstevel@tonic-gate cpr_resume_devices(dev_info_t *start, int resume_failed)
1457c478bd9Sstevel@tonic-gate {
1467c478bd9Sstevel@tonic-gate 	dev_info_t	*dip, *next, *last = NULL;
1477c478bd9Sstevel@tonic-gate 	int		did_suspend;
1487c478bd9Sstevel@tonic-gate 	int		error = resume_failed;
1497c478bd9Sstevel@tonic-gate 	char		buf[CPR_BUFSIZE];
1507c478bd9Sstevel@tonic-gate 
1517c478bd9Sstevel@tonic-gate 	while (last != start) {
1527c478bd9Sstevel@tonic-gate 		dip = start;
1537c478bd9Sstevel@tonic-gate 		next = ddi_get_next_sibling(dip);
1547c478bd9Sstevel@tonic-gate 		while (next != last) {
1557c478bd9Sstevel@tonic-gate 			dip = next;
1567c478bd9Sstevel@tonic-gate 			next = ddi_get_next_sibling(dip);
1577c478bd9Sstevel@tonic-gate 		}
1587c478bd9Sstevel@tonic-gate 
1597c478bd9Sstevel@tonic-gate 		/*
1607c478bd9Sstevel@tonic-gate 		 * cpr is the only one that uses this field and the device
1617c478bd9Sstevel@tonic-gate 		 * itself hasn't resumed yet, there is no need to use a
1627c478bd9Sstevel@tonic-gate 		 * lock, even though kernel threads are active by now.
1637c478bd9Sstevel@tonic-gate 		 */
1647c478bd9Sstevel@tonic-gate 		did_suspend = DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED;
1657c478bd9Sstevel@tonic-gate 		if (did_suspend)
1667c478bd9Sstevel@tonic-gate 			DEVI(dip)->devi_cpr_flags &= ~DCF_CPR_SUSPENDED;
1677c478bd9Sstevel@tonic-gate 
1687c478bd9Sstevel@tonic-gate 		/*
169*2df1fe9cSrandyf 		 * Always attempt to restore device configuration before
170*2df1fe9cSrandyf 		 * attempting resume
171*2df1fe9cSrandyf 		 */
172*2df1fe9cSrandyf #ifndef __xpv
173*2df1fe9cSrandyf 		i_cpr_restore_configuration(dip);
174*2df1fe9cSrandyf #endif
175*2df1fe9cSrandyf 
176*2df1fe9cSrandyf 		/*
1777c478bd9Sstevel@tonic-gate 		 * There may be background attaches happening on devices
1787c478bd9Sstevel@tonic-gate 		 * that were not originally suspended by cpr, so resume
1797c478bd9Sstevel@tonic-gate 		 * only devices that were suspended by cpr. Also, stop
1807c478bd9Sstevel@tonic-gate 		 * resuming after the first resume failure, but traverse
181*2df1fe9cSrandyf 		 * the entire tree to clear the suspend flag unless the
182*2df1fe9cSrandyf 		 * FORCE_SUSPEND_TO_RAM test point is set.
1837c478bd9Sstevel@tonic-gate 		 */
184*2df1fe9cSrandyf #ifndef __xpv
185*2df1fe9cSrandyf 		if (did_suspend && (!error ||
186*2df1fe9cSrandyf 		    cpr_test_point == FORCE_SUSPEND_TO_RAM)) {
187*2df1fe9cSrandyf #else
1887c478bd9Sstevel@tonic-gate 		if (did_suspend && !error) {
189*2df1fe9cSrandyf #endif
190ae115bc7Smrj 			CPR_DEBUG(CPR_DEBUG2, "Resuming device %s\n",
191ae115bc7Smrj 			    devi_string(dip, buf));
1927c478bd9Sstevel@tonic-gate 			/*
1937c478bd9Sstevel@tonic-gate 			 * If a device suspended by cpr gets detached during
1947c478bd9Sstevel@tonic-gate 			 * the resume process (for example, due to hotplugging)
1957c478bd9Sstevel@tonic-gate 			 * before cpr gets around to issuing it a DDI_RESUME,
1967c478bd9Sstevel@tonic-gate 			 * we'll have problems.
1977c478bd9Sstevel@tonic-gate 			 */
198737d277aScth 			if (!i_ddi_devi_attached(dip)) {
199ae115bc7Smrj 				CPR_DEBUG(CPR_DEBUG2, "WARNING: Skipping "
200ae115bc7Smrj 				    "%s, device not ready for resume\n",
201ae115bc7Smrj 				    devi_string(dip, buf));
2027c478bd9Sstevel@tonic-gate 				cpr_err(CE_WARN, "Skipping %s, device "
2037c478bd9Sstevel@tonic-gate 				    "not ready for resume",
2047c478bd9Sstevel@tonic-gate 				    devi_string(dip, buf));
205*2df1fe9cSrandyf #ifndef __xpv
206*2df1fe9cSrandyf 			} else if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
207*2df1fe9cSrandyf 			    (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
208*2df1fe9cSrandyf 			    cpr_device == ddi_driver_major(dip))) {
209*2df1fe9cSrandyf #else
210*2df1fe9cSrandyf 			} else {
211*2df1fe9cSrandyf #endif
212*2df1fe9cSrandyf 				if (devi_attach(dip, DDI_RESUME) !=
2137c478bd9Sstevel@tonic-gate 				    DDI_SUCCESS) {
214*2df1fe9cSrandyf 					error = ENXIO;
215*2df1fe9cSrandyf 				}
216*2df1fe9cSrandyf 			}
217*2df1fe9cSrandyf 		}
218*2df1fe9cSrandyf 
219*2df1fe9cSrandyf 		if (error == ENXIO) {
220ae115bc7Smrj 			CPR_DEBUG(CPR_DEBUG2,
2217c478bd9Sstevel@tonic-gate 			    "WARNING: Unable to resume device %s\n",
222ae115bc7Smrj 			    devi_string(dip, buf));
2237c478bd9Sstevel@tonic-gate 			cpr_err(CE_WARN, "Unable to resume device %s",
2247c478bd9Sstevel@tonic-gate 			    devi_string(dip, buf));
2257c478bd9Sstevel@tonic-gate 		}
2267c478bd9Sstevel@tonic-gate 
2277c478bd9Sstevel@tonic-gate 		error = cpr_resume_devices(ddi_get_child(dip), error);
2287c478bd9Sstevel@tonic-gate 		last = dip;
2297c478bd9Sstevel@tonic-gate 	}
2307c478bd9Sstevel@tonic-gate 
2317c478bd9Sstevel@tonic-gate 	return (error);
2327c478bd9Sstevel@tonic-gate }
2337c478bd9Sstevel@tonic-gate 
2347c478bd9Sstevel@tonic-gate /*
2357c478bd9Sstevel@tonic-gate  * Returns a string which contains device name and address.
2367c478bd9Sstevel@tonic-gate  */
2377c478bd9Sstevel@tonic-gate static char *
2387c478bd9Sstevel@tonic-gate devi_string(dev_info_t *devi, char *buf)
2397c478bd9Sstevel@tonic-gate {
2407c478bd9Sstevel@tonic-gate 	char *name;
2417c478bd9Sstevel@tonic-gate 	char *address;
2427c478bd9Sstevel@tonic-gate 	int size;
2437c478bd9Sstevel@tonic-gate 
2447c478bd9Sstevel@tonic-gate 	name = ddi_node_name(devi);
2457c478bd9Sstevel@tonic-gate 	address = ddi_get_name_addr(devi);
246*2df1fe9cSrandyf 	size = (name == NULL) ? strlen("<null name>") : strlen(name);
247*2df1fe9cSrandyf 	size += (address == NULL) ? strlen("<null>") : strlen(address);
2487c478bd9Sstevel@tonic-gate 
2497c478bd9Sstevel@tonic-gate 	/*
2507c478bd9Sstevel@tonic-gate 	 * Make sure that we don't over-run the buffer.
2517c478bd9Sstevel@tonic-gate 	 * There are 2 additional characters in the string.
2527c478bd9Sstevel@tonic-gate 	 */
2537c478bd9Sstevel@tonic-gate 	ASSERT((size + 2) <= CPR_BUFSIZE);
2547c478bd9Sstevel@tonic-gate 
2557c478bd9Sstevel@tonic-gate 	if (name == NULL)
2567c478bd9Sstevel@tonic-gate 		(void) strcpy(buf, "<null name>");
2577c478bd9Sstevel@tonic-gate 	else
2587c478bd9Sstevel@tonic-gate 		(void) strcpy(buf, name);
2597c478bd9Sstevel@tonic-gate 
2607c478bd9Sstevel@tonic-gate 	(void) strcat(buf, "@");
2617c478bd9Sstevel@tonic-gate 	if (address == NULL)
2627c478bd9Sstevel@tonic-gate 		(void) strcat(buf, "<null>");
2637c478bd9Sstevel@tonic-gate 	else
2647c478bd9Sstevel@tonic-gate 		(void) strcat(buf, address);
2657c478bd9Sstevel@tonic-gate 
2667c478bd9Sstevel@tonic-gate 	return (buf);
2677c478bd9Sstevel@tonic-gate }
2687c478bd9Sstevel@tonic-gate 
2697c478bd9Sstevel@tonic-gate /*
2707c478bd9Sstevel@tonic-gate  * This function determines whether the given device is real (and should
2717c478bd9Sstevel@tonic-gate  * be suspended) or not (pseudo like).  If the device has a "reg" property
2727c478bd9Sstevel@tonic-gate  * then it is presumed to have register state to save/restore.
2737c478bd9Sstevel@tonic-gate  */
2747c478bd9Sstevel@tonic-gate static int
2757c478bd9Sstevel@tonic-gate cpr_is_real_device(dev_info_t *dip)
2767c478bd9Sstevel@tonic-gate {
2777c478bd9Sstevel@tonic-gate 	struct regspec *regbuf;
2787c478bd9Sstevel@tonic-gate 	int length;
2797c478bd9Sstevel@tonic-gate 	int rc;
2807c478bd9Sstevel@tonic-gate 
2817c478bd9Sstevel@tonic-gate 	if (ddi_get_driver(dip) == NULL)
2827c478bd9Sstevel@tonic-gate 		return (0);
2837c478bd9Sstevel@tonic-gate 
2847c478bd9Sstevel@tonic-gate 	/*
2857c478bd9Sstevel@tonic-gate 	 * First those devices for which special arrangements have been made
2867c478bd9Sstevel@tonic-gate 	 */
2877c478bd9Sstevel@tonic-gate 	if (DEVI(dip)->devi_pm_flags & (PMC_NEEDS_SR|PMC_PARENTAL_SR))
2887c478bd9Sstevel@tonic-gate 		return (1);
2897c478bd9Sstevel@tonic-gate 	if (DEVI(dip)->devi_pm_flags & PMC_NO_SR)
2907c478bd9Sstevel@tonic-gate 		return (0);
2917c478bd9Sstevel@tonic-gate 
2927c478bd9Sstevel@tonic-gate 	/*
2937c478bd9Sstevel@tonic-gate 	 * now the general case
2947c478bd9Sstevel@tonic-gate 	 */
295a3282898Scth 	rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg",
2967c478bd9Sstevel@tonic-gate 	    (caddr_t)&regbuf, &length);
2977c478bd9Sstevel@tonic-gate 	ASSERT(rc != DDI_PROP_NO_MEMORY);
2987c478bd9Sstevel@tonic-gate 	if (rc != DDI_PROP_SUCCESS) {
2997c478bd9Sstevel@tonic-gate 		return (0);
3007c478bd9Sstevel@tonic-gate 	} else {
3017c478bd9Sstevel@tonic-gate 		kmem_free((caddr_t)regbuf, length);
3027c478bd9Sstevel@tonic-gate 		return (1);
3037c478bd9Sstevel@tonic-gate 	}
3047c478bd9Sstevel@tonic-gate }
305