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 /*
277c478bd9Sstevel@tonic-gate * CPR driver support routines
287c478bd9Sstevel@tonic-gate */
297c478bd9Sstevel@tonic-gate
307c478bd9Sstevel@tonic-gate #include <sys/types.h>
317c478bd9Sstevel@tonic-gate #include <sys/errno.h>
327c478bd9Sstevel@tonic-gate #include <sys/kmem.h>
337c478bd9Sstevel@tonic-gate #include <sys/systm.h>
347c478bd9Sstevel@tonic-gate #include <sys/sunddi.h>
357c478bd9Sstevel@tonic-gate #include <sys/ddi_impldefs.h>
367c478bd9Sstevel@tonic-gate #include <sys/epm.h>
377c478bd9Sstevel@tonic-gate #include <sys/cpr.h>
387c478bd9Sstevel@tonic-gate
397c478bd9Sstevel@tonic-gate #define CPR_BUFSIZE 128
407c478bd9Sstevel@tonic-gate
417c478bd9Sstevel@tonic-gate extern int devi_detach(dev_info_t *, int);
427c478bd9Sstevel@tonic-gate extern int devi_attach(dev_info_t *, int);
437c478bd9Sstevel@tonic-gate
447c478bd9Sstevel@tonic-gate static char *devi_string(dev_info_t *, char *);
457c478bd9Sstevel@tonic-gate static int cpr_is_real_device(dev_info_t *);
46*2df1fe9cSrandyf /*
47*2df1fe9cSrandyf * Xen uses this code to suspend _all_ drivers quickly and easily.
48*2df1fe9cSrandyf * Suspend and Resume uses it for the same reason, but also has
49*2df1fe9cSrandyf * to contend with some platform specific code that Xen does not.
50*2df1fe9cSrandyf * it is also used as a test entry point for developers/testers to
51*2df1fe9cSrandyf * execute code without going through a complete suspend. So additions
52*2df1fe9cSrandyf * that have platform implications shall need #if[n]def's.
53*2df1fe9cSrandyf */
54*2df1fe9cSrandyf #ifndef __xpv
55*2df1fe9cSrandyf extern void i_cpr_save_configuration(dev_info_t *);
56*2df1fe9cSrandyf extern void i_cpr_restore_configuration(dev_info_t *);
57*2df1fe9cSrandyf #endif
587c478bd9Sstevel@tonic-gate
597c478bd9Sstevel@tonic-gate /*
607c478bd9Sstevel@tonic-gate * Traverse the dev info tree:
617c478bd9Sstevel@tonic-gate * Call each device driver in the system via a special case
627c478bd9Sstevel@tonic-gate * of the detach() entry point to quiesce itself.
637c478bd9Sstevel@tonic-gate * Suspend children first.
647c478bd9Sstevel@tonic-gate *
657c478bd9Sstevel@tonic-gate * We only suspend/resume real devices.
667c478bd9Sstevel@tonic-gate */
677c478bd9Sstevel@tonic-gate
687c478bd9Sstevel@tonic-gate int
cpr_suspend_devices(dev_info_t * dip)697c478bd9Sstevel@tonic-gate cpr_suspend_devices(dev_info_t *dip)
707c478bd9Sstevel@tonic-gate {
717c478bd9Sstevel@tonic-gate int error;
727c478bd9Sstevel@tonic-gate char buf[CPR_BUFSIZE];
737c478bd9Sstevel@tonic-gate
747c478bd9Sstevel@tonic-gate for (; dip != NULL; dip = ddi_get_next_sibling(dip)) {
757c478bd9Sstevel@tonic-gate if (cpr_suspend_devices(ddi_get_child(dip)))
767c478bd9Sstevel@tonic-gate return (ENXIO);
777c478bd9Sstevel@tonic-gate if (!cpr_is_real_device(dip))
787c478bd9Sstevel@tonic-gate continue;
79ae115bc7Smrj CPR_DEBUG(CPR_DEBUG2, "Suspending device %s\n",
80ae115bc7Smrj devi_string(dip, buf));
817c478bd9Sstevel@tonic-gate ASSERT((DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED) == 0);
827c478bd9Sstevel@tonic-gate
83*2df1fe9cSrandyf #ifndef __xpv
84*2df1fe9cSrandyf i_cpr_save_configuration(dip);
85*2df1fe9cSrandyf #endif
86*2df1fe9cSrandyf
87*2df1fe9cSrandyf
88*2df1fe9cSrandyf if (!i_ddi_devi_attached(dip)) {
897c478bd9Sstevel@tonic-gate error = DDI_FAILURE;
90*2df1fe9cSrandyf } else {
91*2df1fe9cSrandyf #ifndef __xpv
92*2df1fe9cSrandyf if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
93*2df1fe9cSrandyf (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
94*2df1fe9cSrandyf cpr_device == ddi_driver_major(dip))) {
95*2df1fe9cSrandyf #endif
96*2df1fe9cSrandyf error = devi_detach(dip, DDI_SUSPEND);
97*2df1fe9cSrandyf #ifndef __xpv
98*2df1fe9cSrandyf } else {
99*2df1fe9cSrandyf error = DDI_SUCCESS;
100*2df1fe9cSrandyf }
101*2df1fe9cSrandyf #endif
102*2df1fe9cSrandyf }
1037c478bd9Sstevel@tonic-gate
104*2df1fe9cSrandyf if (error == DDI_SUCCESS) {
1057c478bd9Sstevel@tonic-gate DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
106*2df1fe9cSrandyf }
107*2df1fe9cSrandyf
1087c478bd9Sstevel@tonic-gate else {
109ae115bc7Smrj CPR_DEBUG(CPR_DEBUG2,
110ae115bc7Smrj "WARNING: Unable to suspend device %s\n",
111ae115bc7Smrj devi_string(dip, buf));
1127c478bd9Sstevel@tonic-gate cpr_err(CE_WARN, "Unable to suspend device %s.",
113*2df1fe9cSrandyf devi_string(dip, buf));
1147c478bd9Sstevel@tonic-gate cpr_err(CE_WARN, "Device is busy or does not "
115*2df1fe9cSrandyf "support suspend/resume.");
116*2df1fe9cSrandyf #ifndef __xpv
117*2df1fe9cSrandyf /*
118*2df1fe9cSrandyf * the device has failed to suspend however,
119*2df1fe9cSrandyf * if cpr_test_point == FORCE_SUSPEND_TO_RAM
120*2df1fe9cSrandyf * after putting out the warning message above,
121*2df1fe9cSrandyf * we carry on as if suspending the device had
122*2df1fe9cSrandyf * been successful
123*2df1fe9cSrandyf */
124*2df1fe9cSrandyf if (cpr_test_point == FORCE_SUSPEND_TO_RAM)
125*2df1fe9cSrandyf DEVI(dip)->devi_cpr_flags |= DCF_CPR_SUSPENDED;
126*2df1fe9cSrandyf else
127*2df1fe9cSrandyf #endif
128*2df1fe9cSrandyf return (ENXIO);
1297c478bd9Sstevel@tonic-gate }
1307c478bd9Sstevel@tonic-gate }
1317c478bd9Sstevel@tonic-gate return (0);
1327c478bd9Sstevel@tonic-gate }
1337c478bd9Sstevel@tonic-gate
1347c478bd9Sstevel@tonic-gate /*
1357c478bd9Sstevel@tonic-gate * Traverse the dev info tree:
1367c478bd9Sstevel@tonic-gate * Call each device driver in the system via a special case
1377c478bd9Sstevel@tonic-gate * of the attach() entry point to restore itself.
1387c478bd9Sstevel@tonic-gate * This is a little tricky because it has to reverse the traversal
1397c478bd9Sstevel@tonic-gate * order of cpr_suspend_devices().
1407c478bd9Sstevel@tonic-gate */
1417c478bd9Sstevel@tonic-gate int
cpr_resume_devices(dev_info_t * start,int resume_failed)1427c478bd9Sstevel@tonic-gate cpr_resume_devices(dev_info_t *start, int resume_failed)
1437c478bd9Sstevel@tonic-gate {
1447c478bd9Sstevel@tonic-gate dev_info_t *dip, *next, *last = NULL;
1457c478bd9Sstevel@tonic-gate int did_suspend;
1467c478bd9Sstevel@tonic-gate int error = resume_failed;
1477c478bd9Sstevel@tonic-gate char buf[CPR_BUFSIZE];
1487c478bd9Sstevel@tonic-gate
1497c478bd9Sstevel@tonic-gate while (last != start) {
1507c478bd9Sstevel@tonic-gate dip = start;
1517c478bd9Sstevel@tonic-gate next = ddi_get_next_sibling(dip);
1527c478bd9Sstevel@tonic-gate while (next != last) {
1537c478bd9Sstevel@tonic-gate dip = next;
1547c478bd9Sstevel@tonic-gate next = ddi_get_next_sibling(dip);
1557c478bd9Sstevel@tonic-gate }
1567c478bd9Sstevel@tonic-gate
1577c478bd9Sstevel@tonic-gate /*
1587c478bd9Sstevel@tonic-gate * cpr is the only one that uses this field and the device
1597c478bd9Sstevel@tonic-gate * itself hasn't resumed yet, there is no need to use a
1607c478bd9Sstevel@tonic-gate * lock, even though kernel threads are active by now.
1617c478bd9Sstevel@tonic-gate */
1627c478bd9Sstevel@tonic-gate did_suspend = DEVI(dip)->devi_cpr_flags & DCF_CPR_SUSPENDED;
1637c478bd9Sstevel@tonic-gate if (did_suspend)
1647c478bd9Sstevel@tonic-gate DEVI(dip)->devi_cpr_flags &= ~DCF_CPR_SUSPENDED;
1657c478bd9Sstevel@tonic-gate
166*2df1fe9cSrandyf /*
167*2df1fe9cSrandyf * Always attempt to restore device configuration before
168*2df1fe9cSrandyf * attempting resume
169*2df1fe9cSrandyf */
170*2df1fe9cSrandyf #ifndef __xpv
171*2df1fe9cSrandyf i_cpr_restore_configuration(dip);
172*2df1fe9cSrandyf #endif
173*2df1fe9cSrandyf
1747c478bd9Sstevel@tonic-gate /*
1757c478bd9Sstevel@tonic-gate * There may be background attaches happening on devices
1767c478bd9Sstevel@tonic-gate * that were not originally suspended by cpr, so resume
1777c478bd9Sstevel@tonic-gate * only devices that were suspended by cpr. Also, stop
1787c478bd9Sstevel@tonic-gate * resuming after the first resume failure, but traverse
179*2df1fe9cSrandyf * the entire tree to clear the suspend flag unless the
180*2df1fe9cSrandyf * FORCE_SUSPEND_TO_RAM test point is set.
1817c478bd9Sstevel@tonic-gate */
182*2df1fe9cSrandyf #ifndef __xpv
183*2df1fe9cSrandyf if (did_suspend && (!error ||
184*2df1fe9cSrandyf cpr_test_point == FORCE_SUSPEND_TO_RAM)) {
185*2df1fe9cSrandyf #else
1867c478bd9Sstevel@tonic-gate if (did_suspend && !error) {
187*2df1fe9cSrandyf #endif
188ae115bc7Smrj CPR_DEBUG(CPR_DEBUG2, "Resuming device %s\n",
189ae115bc7Smrj devi_string(dip, buf));
1907c478bd9Sstevel@tonic-gate /*
1917c478bd9Sstevel@tonic-gate * If a device suspended by cpr gets detached during
1927c478bd9Sstevel@tonic-gate * the resume process (for example, due to hotplugging)
1937c478bd9Sstevel@tonic-gate * before cpr gets around to issuing it a DDI_RESUME,
1947c478bd9Sstevel@tonic-gate * we'll have problems.
1957c478bd9Sstevel@tonic-gate */
196737d277aScth if (!i_ddi_devi_attached(dip)) {
197ae115bc7Smrj CPR_DEBUG(CPR_DEBUG2, "WARNING: Skipping "
198ae115bc7Smrj "%s, device not ready for resume\n",
199ae115bc7Smrj devi_string(dip, buf));
2007c478bd9Sstevel@tonic-gate cpr_err(CE_WARN, "Skipping %s, device "
2017c478bd9Sstevel@tonic-gate "not ready for resume",
2027c478bd9Sstevel@tonic-gate devi_string(dip, buf));
203*2df1fe9cSrandyf #ifndef __xpv
204*2df1fe9cSrandyf } else if (cpr_test_point != DEVICE_SUSPEND_TO_RAM ||
205*2df1fe9cSrandyf (cpr_test_point == DEVICE_SUSPEND_TO_RAM &&
206*2df1fe9cSrandyf cpr_device == ddi_driver_major(dip))) {
207*2df1fe9cSrandyf #else
208*2df1fe9cSrandyf } else {
209*2df1fe9cSrandyf #endif
210*2df1fe9cSrandyf if (devi_attach(dip, DDI_RESUME) !=
211*2df1fe9cSrandyf DDI_SUCCESS) {
212*2df1fe9cSrandyf error = ENXIO;
213*2df1fe9cSrandyf }
2147c478bd9Sstevel@tonic-gate }
2157c478bd9Sstevel@tonic-gate }
2167c478bd9Sstevel@tonic-gate
217*2df1fe9cSrandyf if (error == ENXIO) {
218*2df1fe9cSrandyf CPR_DEBUG(CPR_DEBUG2,
219*2df1fe9cSrandyf "WARNING: Unable to resume device %s\n",
220*2df1fe9cSrandyf devi_string(dip, buf));
221*2df1fe9cSrandyf cpr_err(CE_WARN, "Unable to resume device %s",
222*2df1fe9cSrandyf devi_string(dip, buf));
223*2df1fe9cSrandyf }
224*2df1fe9cSrandyf
2257c478bd9Sstevel@tonic-gate error = cpr_resume_devices(ddi_get_child(dip), error);
2267c478bd9Sstevel@tonic-gate last = dip;
2277c478bd9Sstevel@tonic-gate }
2287c478bd9Sstevel@tonic-gate
2297c478bd9Sstevel@tonic-gate return (error);
2307c478bd9Sstevel@tonic-gate }
2317c478bd9Sstevel@tonic-gate
2327c478bd9Sstevel@tonic-gate /*
2337c478bd9Sstevel@tonic-gate * Returns a string which contains device name and address.
2347c478bd9Sstevel@tonic-gate */
2357c478bd9Sstevel@tonic-gate static char *
2367c478bd9Sstevel@tonic-gate devi_string(dev_info_t *devi, char *buf)
2377c478bd9Sstevel@tonic-gate {
2387c478bd9Sstevel@tonic-gate char *name;
2397c478bd9Sstevel@tonic-gate char *address;
2407c478bd9Sstevel@tonic-gate int size;
2417c478bd9Sstevel@tonic-gate
2427c478bd9Sstevel@tonic-gate name = ddi_node_name(devi);
2437c478bd9Sstevel@tonic-gate address = ddi_get_name_addr(devi);
244*2df1fe9cSrandyf size = (name == NULL) ? strlen("<null name>") : strlen(name);
245*2df1fe9cSrandyf size += (address == NULL) ? strlen("<null>") : strlen(address);
2467c478bd9Sstevel@tonic-gate
2477c478bd9Sstevel@tonic-gate /*
2487c478bd9Sstevel@tonic-gate * Make sure that we don't over-run the buffer.
2497c478bd9Sstevel@tonic-gate * There are 2 additional characters in the string.
2507c478bd9Sstevel@tonic-gate */
2517c478bd9Sstevel@tonic-gate ASSERT((size + 2) <= CPR_BUFSIZE);
2527c478bd9Sstevel@tonic-gate
2537c478bd9Sstevel@tonic-gate if (name == NULL)
2547c478bd9Sstevel@tonic-gate (void) strcpy(buf, "<null name>");
2557c478bd9Sstevel@tonic-gate else
2567c478bd9Sstevel@tonic-gate (void) strcpy(buf, name);
2577c478bd9Sstevel@tonic-gate
2587c478bd9Sstevel@tonic-gate (void) strcat(buf, "@");
2597c478bd9Sstevel@tonic-gate if (address == NULL)
2607c478bd9Sstevel@tonic-gate (void) strcat(buf, "<null>");
2617c478bd9Sstevel@tonic-gate else
2627c478bd9Sstevel@tonic-gate (void) strcat(buf, address);
2637c478bd9Sstevel@tonic-gate
2647c478bd9Sstevel@tonic-gate return (buf);
2657c478bd9Sstevel@tonic-gate }
2667c478bd9Sstevel@tonic-gate
2677c478bd9Sstevel@tonic-gate /*
2687c478bd9Sstevel@tonic-gate * This function determines whether the given device is real (and should
2697c478bd9Sstevel@tonic-gate * be suspended) or not (pseudo like). If the device has a "reg" property
2707c478bd9Sstevel@tonic-gate * then it is presumed to have register state to save/restore.
2717c478bd9Sstevel@tonic-gate */
2727c478bd9Sstevel@tonic-gate static int
2737c478bd9Sstevel@tonic-gate cpr_is_real_device(dev_info_t *dip)
2747c478bd9Sstevel@tonic-gate {
2757c478bd9Sstevel@tonic-gate struct regspec *regbuf;
2767c478bd9Sstevel@tonic-gate int length;
2777c478bd9Sstevel@tonic-gate int rc;
2787c478bd9Sstevel@tonic-gate
2797c478bd9Sstevel@tonic-gate if (ddi_get_driver(dip) == NULL)
2807c478bd9Sstevel@tonic-gate return (0);
2817c478bd9Sstevel@tonic-gate
2827c478bd9Sstevel@tonic-gate /*
2837c478bd9Sstevel@tonic-gate * First those devices for which special arrangements have been made
2847c478bd9Sstevel@tonic-gate */
2857c478bd9Sstevel@tonic-gate if (DEVI(dip)->devi_pm_flags & (PMC_NEEDS_SR|PMC_PARENTAL_SR))
2867c478bd9Sstevel@tonic-gate return (1);
2877c478bd9Sstevel@tonic-gate if (DEVI(dip)->devi_pm_flags & PMC_NO_SR)
2887c478bd9Sstevel@tonic-gate return (0);
2897c478bd9Sstevel@tonic-gate
2907c478bd9Sstevel@tonic-gate /*
2917c478bd9Sstevel@tonic-gate * now the general case
2927c478bd9Sstevel@tonic-gate */
293a3282898Scth rc = ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "reg",
2947c478bd9Sstevel@tonic-gate (caddr_t)®buf, &length);
2957c478bd9Sstevel@tonic-gate ASSERT(rc != DDI_PROP_NO_MEMORY);
2967c478bd9Sstevel@tonic-gate if (rc != DDI_PROP_SUCCESS) {
2977c478bd9Sstevel@tonic-gate return (0);
2987c478bd9Sstevel@tonic-gate } else {
2997c478bd9Sstevel@tonic-gate kmem_free((caddr_t)regbuf, length);
3007c478bd9Sstevel@tonic-gate return (1);
3017c478bd9Sstevel@tonic-gate }
3027c478bd9Sstevel@tonic-gate }
303